From 56c1fbecea0dbff72d2a868a98514aaa9ae59c8b Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sat, 19 Jul 2025 01:34:59 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=9F=20feat(ui):=20reusable=20CompactMo?= =?UTF-8?q?deToggle=20&=20mobile-friendly=20CardPro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary ------- Introduce a reusable compact-mode toggle component and greatly improve the CardPro header for small screens. Removes duplicated code, adds i18n support, and refines overall responsiveness. Details ------- 🎨 UI / Components • Create `common/ui/CompactModeToggle.js` – Provides a single source of truth for switching between “Compact list” and “Adaptive list” – Automatically hides itself on mobile devices via `useIsMobile()` • Refactor table modules to use the new component – `Users`, `Tokens`, `Redemptions`, `Channels`, `TaskLogs`, `MjLogs`, `UsageLogs` – Deletes legacy in-file toggle buttons & reduces repetition 📱 CardPro improvements • Hide `actionsArea` and `searchArea` on mobile, showing a single “Show Actions / Hide Actions” toggle button • Add i18n: texts are now pulled from injected `t()` function (`显示操作项` / `隐藏操作项` etc.) • Extend PropTypes to accept the `t` prop; supply a safe fallback • Minor cleanup: remove legacy DOM observers & flag CSS, simplify logic 🔧 Integration • Pass the `t` translation function to every `CardPro` usage across table pages • Remove temporary custom class hooks after logic simplification Benefits -------- ✓ Consistent, DRY compact-mode handling across the entire dashboard ✓ Better mobile experience with decluttered headers ✓ Full translation support for newly added strings ✓ Easier future maintenance (single compact toggle, unified CardPro API) --- web/src/components/common/ui/CardPro.js | 69 ++++++++++++++----- .../components/common/ui/CompactModeToggle.js | 49 +++++++++++++ .../table/channels/ChannelsActions.jsx | 14 ++-- web/src/components/table/channels/index.jsx | 1 + .../table/mj-logs/MjLogsActions.jsx | 16 ++--- web/src/components/table/mj-logs/index.jsx | 1 + .../redemptions/RedemptionsDescription.jsx | 16 ++--- .../components/table/redemptions/index.jsx | 1 + .../table/task-logs/TaskLogsActions.jsx | 16 ++--- web/src/components/table/task-logs/index.jsx | 1 + .../table/tokens/TokensDescription.jsx | 16 ++--- web/src/components/table/tokens/index.jsx | 1 + .../table/usage-logs/UsageLogsActions.jsx | 16 ++--- web/src/components/table/usage-logs/index.jsx | 1 + .../table/users/UsersDescription.jsx | 16 ++--- web/src/components/table/users/index.jsx | 1 + web/src/i18n/locales/en.json | 4 +- 17 files changed, 160 insertions(+), 79 deletions(-) create mode 100644 web/src/components/common/ui/CompactModeToggle.js diff --git a/web/src/components/common/ui/CardPro.js b/web/src/components/common/ui/CardPro.js index 944f33c1..e295df58 100644 --- a/web/src/components/common/ui/CardPro.js +++ b/web/src/components/common/ui/CardPro.js @@ -1,6 +1,8 @@ -import React from 'react'; -import { Card, Divider, Typography } from '@douyinfe/semi-ui'; +import React, { useState } from 'react'; +import { Card, Divider, Typography, Button } from '@douyinfe/semi-ui'; import PropTypes from 'prop-types'; +import { useIsMobile } from '../../../hooks/common/useIsMobile'; +import { IconEyeOpened, IconEyeClosed } from '@douyinfe/semi-icons'; const { Text } = Typography; @@ -34,8 +36,21 @@ const CardPro = ({ bordered = false, // 自定义样式 style, + // 国际化函数 + t = (key) => key, // 默认函数,直接返回key ...props }) => { + const isMobile = useIsMobile(); + const [showMobileActions, setShowMobileActions] = useState(false); + + // 切换移动端操作项显示状态 + const toggleMobileActions = () => { + setShowMobileActions(!showMobileActions); + }; + + // 检查是否有需要在移动端隐藏的内容 + const hasMobileHideableContent = actionsArea || searchArea; + // 渲染头部内容 const renderHeader = () => { const hasContent = statsArea || descriptionArea || tabsArea || actionsArea || searchArea; @@ -70,22 +85,42 @@ const CardPro = ({ )} - {/* 操作按钮和搜索表单的容器 */} -
- {/* 操作按钮区域 - 用于type1和type3 */} - {(type === 'type1' || type === 'type3') && actionsArea && ( -
- {actionsArea} + {/* 移动端操作切换按钮 */} + {isMobile && hasMobileHideableContent && ( + <> +
+
- )} + + )} - {/* 搜索表单区域 - 所有类型都可能有 */} - {searchArea && ( -
- {searchArea} -
- )} -
+ {/* 操作按钮和搜索表单的容器 */} + {/* 在移动端时根据showMobileActions状态控制显示,在桌面端时始终显示 */} + {(!isMobile || showMobileActions) && ( +
+ {/* 操作按钮区域 - 用于type1和type3 */} + {(type === 'type1' || type === 'type3') && actionsArea && ( +
+ {actionsArea} +
+ )} + + {/* 搜索表单区域 - 所有类型都可能有 */} + {searchArea && ( +
+ {searchArea} +
+ )} +
+ )}
); }; @@ -122,6 +157,8 @@ CardPro.propTypes = { searchArea: PropTypes.node, // 表格内容 children: PropTypes.node, + // 国际化函数 + t: PropTypes.func, }; export default CardPro; \ No newline at end of file diff --git a/web/src/components/common/ui/CompactModeToggle.js b/web/src/components/common/ui/CompactModeToggle.js new file mode 100644 index 00000000..356c2d8f --- /dev/null +++ b/web/src/components/common/ui/CompactModeToggle.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { Button } from '@douyinfe/semi-ui'; +import PropTypes from 'prop-types'; +import { useIsMobile } from '../../../hooks/common/useIsMobile'; + +/** + * 紧凑模式切换按钮组件 + * 用于在自适应列表和紧凑列表之间切换 + * 在移动端时自动隐藏,因为移动端使用"显示操作项"按钮来控制内容显示 + */ +const CompactModeToggle = ({ + compactMode, + setCompactMode, + t, + size = 'small', + type = 'tertiary', + className = '', + ...props +}) => { + const isMobile = useIsMobile(); + + // 在移动端隐藏紧凑列表切换按钮 + if (isMobile) { + return null; + } + + return ( + + ); +}; + +CompactModeToggle.propTypes = { + compactMode: PropTypes.bool.isRequired, + setCompactMode: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, + size: PropTypes.string, + type: PropTypes.string, + className: PropTypes.string, +}; + +export default CompactModeToggle; \ No newline at end of file diff --git a/web/src/components/table/channels/ChannelsActions.jsx b/web/src/components/table/channels/ChannelsActions.jsx index ae64b188..ae3f5152 100644 --- a/web/src/components/table/channels/ChannelsActions.jsx +++ b/web/src/components/table/channels/ChannelsActions.jsx @@ -7,6 +7,7 @@ import { Typography, Select } from '@douyinfe/semi-ui'; +import CompactModeToggle from '../../common/ui/CompactModeToggle'; const ChannelsActions = ({ enableBatchDelete, @@ -150,14 +151,11 @@ const ChannelsActions = ({ - + {/* 右侧:设置开关区域 */} diff --git a/web/src/components/table/channels/index.jsx b/web/src/components/table/channels/index.jsx index f101ba95..a26c1d49 100644 --- a/web/src/components/table/channels/index.jsx +++ b/web/src/components/table/channels/index.jsx @@ -39,6 +39,7 @@ const ChannelsPage = () => { tabsArea={} actionsArea={} searchArea={} + t={channelsData.t} > diff --git a/web/src/components/table/mj-logs/MjLogsActions.jsx b/web/src/components/table/mj-logs/MjLogsActions.jsx index 85815c33..9c8a297a 100644 --- a/web/src/components/table/mj-logs/MjLogsActions.jsx +++ b/web/src/components/table/mj-logs/MjLogsActions.jsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Button, Skeleton, Typography } from '@douyinfe/semi-ui'; +import { Skeleton, Typography } from '@douyinfe/semi-ui'; import { IconEyeOpened } from '@douyinfe/semi-icons'; +import CompactModeToggle from '../../common/ui/CompactModeToggle'; const { Text } = Typography; @@ -32,14 +33,11 @@ const MjLogsActions = ({ )} - + ); }; diff --git a/web/src/components/table/mj-logs/index.jsx b/web/src/components/table/mj-logs/index.jsx index a017d390..20ea4d33 100644 --- a/web/src/components/table/mj-logs/index.jsx +++ b/web/src/components/table/mj-logs/index.jsx @@ -22,6 +22,7 @@ const MjLogsPage = () => { type="type2" statsArea={} searchArea={} + t={mjLogsData.t} > diff --git a/web/src/components/table/redemptions/RedemptionsDescription.jsx b/web/src/components/table/redemptions/RedemptionsDescription.jsx index ef5e1b06..d7db7514 100644 --- a/web/src/components/table/redemptions/RedemptionsDescription.jsx +++ b/web/src/components/table/redemptions/RedemptionsDescription.jsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Button, Typography } from '@douyinfe/semi-ui'; +import { Typography } from '@douyinfe/semi-ui'; import { Ticket } from 'lucide-react'; +import CompactModeToggle from '../../common/ui/CompactModeToggle'; const { Text } = Typography; @@ -12,14 +13,11 @@ const RedemptionsDescription = ({ compactMode, setCompactMode, t }) => { {t('兑换码可以批量生成和分发,适合用于推广活动或批量充值。')} - + ); }; diff --git a/web/src/components/table/redemptions/index.jsx b/web/src/components/table/redemptions/index.jsx index 064743d5..77a79c3a 100644 --- a/web/src/components/table/redemptions/index.jsx +++ b/web/src/components/table/redemptions/index.jsx @@ -80,6 +80,7 @@ const RedemptionsPage = () => { } + t={t} > diff --git a/web/src/components/table/task-logs/TaskLogsActions.jsx b/web/src/components/table/task-logs/TaskLogsActions.jsx index 0e1cec11..3d77e242 100644 --- a/web/src/components/table/task-logs/TaskLogsActions.jsx +++ b/web/src/components/table/task-logs/TaskLogsActions.jsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Button, Typography } from '@douyinfe/semi-ui'; +import { Typography } from '@douyinfe/semi-ui'; import { IconEyeOpened } from '@douyinfe/semi-icons'; +import CompactModeToggle from '../../common/ui/CompactModeToggle'; const { Text } = Typography; @@ -15,14 +16,11 @@ const TaskLogsActions = ({ {t('任务记录')} - + ); }; diff --git a/web/src/components/table/task-logs/index.jsx b/web/src/components/table/task-logs/index.jsx index f0c2b1b7..4b9f2208 100644 --- a/web/src/components/table/task-logs/index.jsx +++ b/web/src/components/table/task-logs/index.jsx @@ -22,6 +22,7 @@ const TaskLogsPage = () => { type="type2" statsArea={} searchArea={} + t={taskLogsData.t} > diff --git a/web/src/components/table/tokens/TokensDescription.jsx b/web/src/components/table/tokens/TokensDescription.jsx index d56d769c..a8af1917 100644 --- a/web/src/components/table/tokens/TokensDescription.jsx +++ b/web/src/components/table/tokens/TokensDescription.jsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Button, Typography } from '@douyinfe/semi-ui'; +import { Typography } from '@douyinfe/semi-ui'; import { Key } from 'lucide-react'; +import CompactModeToggle from '../../common/ui/CompactModeToggle'; const { Text } = Typography; @@ -12,14 +13,11 @@ const TokensDescription = ({ compactMode, setCompactMode, t }) => { {t('令牌用于API访问认证,可以设置额度限制和模型权限。')} - + ); }; diff --git a/web/src/components/table/tokens/index.jsx b/web/src/components/table/tokens/index.jsx index 91d14054..dc18461f 100644 --- a/web/src/components/table/tokens/index.jsx +++ b/web/src/components/table/tokens/index.jsx @@ -82,6 +82,7 @@ const TokensPage = () => { } + t={t} > diff --git a/web/src/components/table/usage-logs/UsageLogsActions.jsx b/web/src/components/table/usage-logs/UsageLogsActions.jsx index 6e3d8012..e69c78e6 100644 --- a/web/src/components/table/usage-logs/UsageLogsActions.jsx +++ b/web/src/components/table/usage-logs/UsageLogsActions.jsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Button, Tag, Space, Spin } from '@douyinfe/semi-ui'; +import { Tag, Space, Spin } from '@douyinfe/semi-ui'; import { renderQuota } from '../../../helpers'; +import CompactModeToggle from '../../common/ui/CompactModeToggle'; const LogsActions = ({ stat, @@ -49,14 +50,11 @@ const LogsActions = ({ - + ); diff --git a/web/src/components/table/usage-logs/index.jsx b/web/src/components/table/usage-logs/index.jsx index e53d71b3..43a53edc 100644 --- a/web/src/components/table/usage-logs/index.jsx +++ b/web/src/components/table/usage-logs/index.jsx @@ -21,6 +21,7 @@ const LogsPage = () => { type="type2" statsArea={} searchArea={} + t={logsData.t} > diff --git a/web/src/components/table/users/UsersDescription.jsx b/web/src/components/table/users/UsersDescription.jsx index 39e0b43f..80d8aa74 100644 --- a/web/src/components/table/users/UsersDescription.jsx +++ b/web/src/components/table/users/UsersDescription.jsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Button, Typography } from '@douyinfe/semi-ui'; +import { Typography } from '@douyinfe/semi-ui'; import { IconUserAdd } from '@douyinfe/semi-icons'; +import CompactModeToggle from '../../common/ui/CompactModeToggle'; const { Text } = Typography; @@ -11,14 +12,11 @@ const UsersDescription = ({ compactMode, setCompactMode, t }) => { {t('用户管理页面,可以查看和管理所有注册用户的信息、权限和状态。')} - + ); }; diff --git a/web/src/components/table/users/index.jsx b/web/src/components/table/users/index.jsx index 64885e99..95e3293e 100644 --- a/web/src/components/table/users/index.jsx +++ b/web/src/components/table/users/index.jsx @@ -85,6 +85,7 @@ const UsersPage = () => { /> } + t={t} > diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index cfddb57f..6cf1019a 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1780,5 +1780,7 @@ "启用全部密钥": "Enable all keys", "以充值价格显示": "Show with recharge price", "美元汇率(非充值汇率,仅用于定价页面换算)": "USD exchange rate (not recharge rate, only used for pricing page conversion)", - "美元汇率": "USD exchange rate" + "美元汇率": "USD exchange rate", + "隐藏操作项": "Hide actions", + "显示操作项": "Show actions" } \ No newline at end of file