Files
new-api/web/src/components/common/ui/CardPro.js
T
t0ng7u 1d27da16e7 refactor: move table pagination to CardPro footer for consistent layout
Implement unified pagination system by moving pagination from CardTable
to CardPro footer area, ensuring consistent visual layout across all
table pages.

## Changes Made

### Core Components
- **CardPro**: Add `paginationArea` prop to display pagination in card footer
- **CardTable**: Add `hidePagination` prop to control internal pagination visibility
- **utils.js**: Add `createCardProPagination` helper with responsive design
  - Mobile: small size + showQuickJumper + showTotal
  - Desktop: default size + showTotal only

### Table Pages Updated
- Users table (type1): Add external pagination control
- Channels table (type3): Move pagination to CardPro footer
- Tokens table (type1): Implement unified pagination layout
- Redemptions table (type1): Apply consistent pagination pattern
- Usage-logs table (type2): Migrate to external pagination
- MJ-logs table (type2): Update pagination configuration
- Task-logs table (type2): Standardize pagination approach

### Bug Fixes
- Fix CardTable desktop pagination visibility when hidePagination=true
- Standardize data access pattern across all table components
- Remove redundant data destructuring in users table for consistency

## Benefits
-  Consistent pagination position across all tables
-  Better visual hierarchy with fixed footer pagination
-  Responsive design optimized for mobile and desktop
-  Unified codebase with reusable pagination utility
-  Backward compatible with existing table functionality

## Files Modified
- `web/src/components/common/ui/CardPro.js`
- `web/src/components/common/ui/CardTable.js`
- `web/src/helpers/utils.js`
- `web/src/components/table/*/index.jsx` (7 tables)
- `web/src/components/table/*/*.jsx` (7 table components)
2025-07-20 02:27:33 +08:00

216 lines
6.3 KiB
JavaScript

/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
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 高级卡片组件
*
* 布局分为6个区域:
* 1. 统计信息区域 (statsArea)
* 2. 描述信息区域 (descriptionArea)
* 3. 类型切换/标签区域 (tabsArea)
* 4. 操作按钮区域 (actionsArea)
* 5. 搜索表单区域 (searchArea)
* 6. 分页区域 (paginationArea) - 固定在卡片底部
*
* 支持三种布局类型:
* - type1: 操作型 (如TokensTable) - 描述信息 + 操作按钮 + 搜索表单
* - type2: 查询型 (如LogsTable) - 统计信息 + 搜索表单
* - type3: 复杂型 (如ChannelsTable) - 描述信息 + 类型切换 + 操作按钮 + 搜索表单
*/
const CardPro = ({
type = 'type1',
className = '',
children,
// 各个区域的内容
statsArea,
descriptionArea,
tabsArea,
actionsArea,
searchArea,
paginationArea, // 新增分页区域
// 卡片属性
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>
</>
)}
{/* 操作按钮和搜索表单的容器 */}
<div
className={`flex flex-col gap-2 ${isMobile && !showMobileActions ? 'hidden' : ''}`}
>
{/* 操作按钮区域 - 用于type1和type3 */}
{(type === 'type1' || type === 'type3') && actionsArea && (
Array.isArray(actionsArea) ? (
actionsArea.map((area, idx) => (
<React.Fragment key={idx}>
{idx !== 0 && <Divider />}
<div className="w-full">
{area}
</div>
</React.Fragment>
))
) : (
<div className="w-full">
{actionsArea}
</div>
)
)}
{/* 当同时存在操作区和搜索区时,插入分隔线 */}
{(actionsArea && searchArea) && <Divider />}
{/* 搜索表单区域 - 所有类型都可能有 */}
{searchArea && (
<div className="w-full">
{searchArea}
</div>
)}
</div>
</div>
);
};
const headerContent = renderHeader();
// 渲染分页区域
const renderFooter = () => {
if (!paginationArea) return null;
return (
<div className="flex justify-center w-full pt-4 border-t" style={{ borderColor: 'var(--semi-color-border)' }}>
{paginationArea}
</div>
);
};
const footerContent = renderFooter();
return (
<Card
className={`table-scroll-card !rounded-2xl ${className}`}
title={headerContent}
footer={footerContent}
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.oneOfType([
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]),
searchArea: PropTypes.node,
paginationArea: PropTypes.node, // 新增分页区域
// 表格内容
children: PropTypes.node,
// 国际化函数
t: PropTypes.func,
};
export default CardPro;