/* 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 . For commercial licensing, please contact support@quantumnous.com */ import React, { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Table, Card, Skeleton, Pagination, Empty, Button, Collapsible } from '@douyinfe/semi-ui'; import { IconChevronDown, IconChevronUp } from '@douyinfe/semi-icons'; import PropTypes from 'prop-types'; import { useIsMobile } from '../../../hooks/common/useIsMobile'; /** * CardTable 响应式表格组件 * * 在桌面端渲染 Semi-UI 的 Table 组件,在移动端则将每一行数据渲染成 Card 形式。 * 该组件与 Table 组件的大部分 API 保持一致,只需将原 Table 换成 CardTable 即可。 */ const CardTable = ({ columns = [], dataSource = [], loading = false, rowKey = 'key', hidePagination = false, // 新增参数,控制是否隐藏内部分页 ...tableProps }) => { const isMobile = useIsMobile(); const { t } = useTranslation(); // Skeleton 显示控制,确保至少展示 500ms 动效 const [showSkeleton, setShowSkeleton] = useState(loading); const loadingStartRef = useRef(Date.now()); useEffect(() => { if (loading) { loadingStartRef.current = Date.now(); setShowSkeleton(true); } else { const elapsed = Date.now() - loadingStartRef.current; const remaining = Math.max(0, 500 - elapsed); if (remaining === 0) { setShowSkeleton(false); } else { const timer = setTimeout(() => setShowSkeleton(false), remaining); return () => clearTimeout(timer); } } }, [loading]); // 解析行主键 const getRowKey = (record, index) => { if (typeof rowKey === 'function') return rowKey(record); return record[rowKey] !== undefined ? record[rowKey] : index; }; // 如果不是移动端,直接渲染原 Table if (!isMobile) { // 如果要隐藏分页,则从tableProps中移除pagination const finalTableProps = hidePagination ? { ...tableProps, pagination: false } : tableProps; return ( ); } // 加载中占位:根据列信息动态模拟真实布局 if (showSkeleton) { const visibleCols = columns.filter((col) => { if (tableProps?.visibleColumns && col.key) { return tableProps.visibleColumns[col.key]; } return true; }); const renderSkeletonCard = (key) => { const placeholder = (
{visibleCols.map((col, idx) => { if (!col.title) { return (
); } return (
); })}
); return ( ); }; return (
{[1, 2, 3].map((i) => renderSkeletonCard(i))}
); } // 渲染移动端卡片 const isEmpty = !showSkeleton && (!dataSource || dataSource.length === 0); // 移动端行卡片组件(含可折叠详情) const MobileRowCard = ({ record, index }) => { const [showDetails, setShowDetails] = useState(false); const rowKeyVal = getRowKey(record, index); const hasDetails = tableProps.expandedRowRender && (!tableProps.rowExpandable || tableProps.rowExpandable(record)); return ( {columns.map((col, colIdx) => { // 忽略隐藏列 if (tableProps?.visibleColumns && !tableProps.visibleColumns[col.key]) { return null; } const title = col.title; const cellContent = col.render ? col.render(record[col.dataIndex], record, index) : record[col.dataIndex]; // 空标题列(通常为操作按钮)单独渲染 if (!title) { return (
{cellContent}
); } return (
{title}
{cellContent !== undefined && cellContent !== null ? cellContent : '-'}
); })} {hasDetails && ( <>
{tableProps.expandedRowRender(record, index)}
)}
); }; if (isEmpty) { // 若传入 empty 属性则使用之,否则使用默认 Empty if (tableProps.empty) return tableProps.empty; return (
); } return (
{dataSource.map((record, index) => ( ))} {/* 分页组件 - 只在不隐藏分页且有pagination配置时显示 */} {!hidePagination && tableProps.pagination && dataSource.length > 0 && (
)}
); }; CardTable.propTypes = { columns: PropTypes.array.isRequired, dataSource: PropTypes.array, loading: PropTypes.bool, rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), hidePagination: PropTypes.bool, // 控制是否隐藏内部分页 }; export default CardTable;