t0ng7u 8d964629e0 🌟 feat(ui): reusable CompactModeToggle & mobile-friendly CardPro
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)
2025-07-19 01:34:59 +08:00

164 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/**
* CardPro 高级卡片组件
*
* 布局分为5个区域
* 1. 统计信息区域 (statsArea)
* 2. 描述信息区域 (descriptionArea)
* 3. 类型切换/标签区域 (tabsArea)
* 4. 操作按钮区域 (actionsArea)
* 5. 搜索表单区域 (searchArea)
*
* 支持三种布局类型:
* - type1: 操作型 (如TokensTable) - 描述信息 + 操作按钮 + 搜索表单
* - type2: 查询型 (如LogsTable) - 统计信息 + 搜索表单
* - type3: 复杂型 (如ChannelsTable) - 描述信息 + 类型切换 + 操作按钮 + 搜索表单
*/
const CardPro = ({
type = 'type1',
className = '',
children,
// 各个区域的内容
statsArea,
descriptionArea,
tabsArea,
actionsArea,
searchArea,
// 卡片属性
shadows = 'always',
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;
if (!hasContent) return null;
return (
<div className="flex flex-col w-full">
{/* 统计信息区域 - 用于type2 */}
{type === 'type2' && statsArea && (
<>
{statsArea}
</>
)}
{/* 描述信息区域 - 用于type1和type3 */}
{(type === 'type1' || type === 'type3') && descriptionArea && (
<>
{descriptionArea}
</>
)}
{/* 第一个分隔线 - 在描述信息或统计信息后面 */}
{((type === 'type1' || type === 'type3') && descriptionArea) ||
(type === 'type2' && statsArea) ? (
<Divider margin="12px" />
) : null}
{/* 类型切换/标签区域 - 主要用于type3 */}
{type === 'type3' && tabsArea && (
<>
{tabsArea}
</>
)}
{/* 移动端操作切换按钮 */}
{isMobile && hasMobileHideableContent && (
<>
<div className="w-full mb-2">
<Button
onClick={toggleMobileActions}
icon={showMobileActions ? <IconEyeClosed /> : <IconEyeOpened />}
type="tertiary"
size="small"
block
>
{showMobileActions ? t('隐藏操作项') : t('显示操作项')}
</Button>
</div>
</>
)}
{/* 操作按钮和搜索表单的容器 */}
{/* 在移动端时根据showMobileActions状态控制显示在桌面端时始终显示 */}
{(!isMobile || showMobileActions) && (
<div className="flex flex-col gap-2">
{/* 操作按钮区域 - 用于type1和type3 */}
{(type === 'type1' || type === 'type3') && actionsArea && (
<div className="w-full">
{actionsArea}
</div>
)}
{/* 搜索表单区域 - 所有类型都可能有 */}
{searchArea && (
<div className="w-full">
{searchArea}
</div>
)}
</div>
)}
</div>
);
};
const headerContent = renderHeader();
return (
<Card
className={`table-scroll-card !rounded-2xl ${className}`}
title={headerContent}
shadows={shadows}
bordered={bordered}
style={style}
{...props}
>
{children}
</Card>
);
};
CardPro.propTypes = {
// 布局类型
type: PropTypes.oneOf(['type1', 'type2', 'type3']),
// 样式相关
className: PropTypes.string,
style: PropTypes.object,
shadows: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
bordered: PropTypes.bool,
// 内容区域
statsArea: PropTypes.node,
descriptionArea: PropTypes.node,
tabsArea: PropTypes.node,
actionsArea: PropTypes.node,
searchArea: PropTypes.node,
// 表格内容
children: PropTypes.node,
// 国际化函数
t: PropTypes.func,
};
export default CardPro;