Files
leaudit-platform-frontend/app/routes/rule-groups._index.tsx
T
LiangShiyong 30e100ef3e feat: 1. 本地化思源黑体的字体包并优先使用。
2. 添加权限映射表和全局查看权限的hook,便于路由控制不同权限按钮显示/隐藏。
3. 删除评查点分组的部分旧api方法。
4. 对接评查点分组接口,文档类型接口, 提示词管理接口, 入口模块管理的接口。
5. 优化角色权限管理的接口,完善不用地区的访问权限认证。
6. 优化主页交叉评查和设置的入口样式和布局。
7. 优化评查点分组,评查规则的功能权限校验。
2025-11-29 10:37:35 +08:00

866 lines
28 KiB
TypeScript
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 { type MetaFunction } from "@remix-run/node";
import { useLoaderData, Link, useNavigate, useSearchParams } from "@remix-run/react";
import { useState, useEffect } from "react";
import indexStyles from "~/styles/pages/rule-groups_index.css?url";
import { Card } from "~/components/ui/Card";
import { Button } from "~/components/ui/Button";
import { StatusDot } from "~/components/ui/StatusDot";
import { Table } from "~/components/ui/Table";
import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel";
// import { Pagination } from "~/components/ui/Pagination";
import {
getEvaluationPointGroups,
getChildGroups,
type RuleGroup,
deleteEvaluationPointGroup,
batchUpdateEvaluationPointGroupStatus,
batchDeleteEvaluationPointGroups
} from "~/api/evaluation_points/rule-groups";
import { toastService, messageService } from "~/components/ui";
import { usePermission } from "~/hooks/usePermission";
export function links() {
return [{ rel: "stylesheet", href: indexStyles }];
}
export const meta: MetaFunction = () => {
return [
{ title: "评查点分组 - 中国烟草AI合同及卷宗审核系统" },
{ name: "description", content: "管理评查点分组,包括创建、编辑和删除分组" },
];
};
export async function loader({ request }: { request: Request }) {
try {
// 获取用户会话信息
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
// 🆕 解析URL查询参数(服务端筛选和分页)
const url = new URL(request.url);
const name = url.searchParams.get('name') || undefined;
const code = url.searchParams.get('code') || undefined;
const is_enabled = url.searchParams.get('is_enabled');
const page = parseInt(url.searchParams.get('page') || '1');
const pageSize = parseInt(url.searchParams.get('pageSize') || '50');
// 🆕 调用 FastAPI v3 的 getEvaluationPointGroups API
const response = await getEvaluationPointGroups({
name,
code,
is_enabled: is_enabled ? is_enabled === 'true' : undefined,
pid: null, // 仅获取一级分组
page,
pageSize,
token: frontendJWT
}, frontendJWT);
if (response.error) {
throw new Error(response.error);
}
return Response.json({
groups: response.data || [],
totalCount: ('totalCount' in response) ? (response.totalCount || 0) : 0,
page,
pageSize,
frontendJWT
});
} catch (error) {
console.error('加载评查点分组失败:', error);
return Response.json({
groups: [],
totalCount: 0,
page: 1,
pageSize: 50
});
}
}
export default function RuleGroupsIndex() {
const loaderData = useLoaderData<typeof loader>();
const { groups: initialGroups, frontendJWT } = loaderData;
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const [expandedGroups, setExpandedGroups] = useState<string[]>([]);
const [groups, setGroups] = useState<RuleGroup[]>(initialGroups || []);
const [loading, setLoading] = useState<Record<string, boolean>>({});
const [filteredChildrenMap, setFilteredChildrenMap] = useState<Record<string, RuleGroup[]>>({});
const [initialLoading, setInitialLoading] = useState<boolean>(true);
const [selectedIds, setSelectedIds] = useState<string[]>([]); // 🆕 批量选择状态
// ✅ 使用权限 Hook
const { canCreate, canUpdate, canDelete, canBatch } = usePermission();
const canCreateGroup = canCreate('evaluation_group');
const canUpdateGroup = canUpdate('evaluation_group');
const canDeleteGroup = canDelete('evaluation_group');
const canBatchOperation = canBatch('evaluation_group'); // ✅ 批量操作权限
// 初始加载时自动加载所有子分组
useEffect(() => {
const loadAllChildGroups = async () => {
if (!initialGroups || initialGroups.length === 0) {
setInitialLoading(false);
return;
}
try {
// 创建一个加载状态对象,标记所有父分组正在加载
const loadingState: Record<string, boolean> = {};
initialGroups.forEach((group: RuleGroup) => {
loadingState[group.id] = true;
});
setLoading(loadingState);
// 并行加载所有父分组的子分组
const promises = initialGroups.map(async (group: RuleGroup) => {
try {
const response = await getChildGroups(group.id, frontendJWT);
if (response.error) {
console.error(`加载分组 ${group.id} 的子分组失败:`, response.error);
return { parentId: group.id, children: [] };
}
return { parentId: group.id, children: response.data };
} catch (error) {
console.error(`加载分组 ${group.id} 的子分组出错:`, error);
return { parentId: group.id, children: [] };
}
});
const results = await Promise.all(promises);
// 更新分组数据
setGroups(prev => {
const newGroups = [...prev];
results.forEach(({ parentId, children }) => {
const parentIndex = newGroups.findIndex(g => g.id === parentId);
if (parentIndex !== -1) {
newGroups[parentIndex] = {
...newGroups[parentIndex],
children
};
}
});
return newGroups;
});
// 展开所有父分组
setExpandedGroups(initialGroups.map((group: RuleGroup) => group.id));
} catch (error) {
console.error('自动加载所有子分组失败:', error);
} finally {
// 清除所有加载状态
setLoading({});
setInitialLoading(false);
}
};
loadAllChildGroups();
}, [initialGroups]);
// 处理展开/收起
const toggleGroup = async (groupId: string) => {
if (expandedGroups.includes(groupId)) {
// 收起分组
setExpandedGroups(prev => prev.filter(id => id !== groupId));
return;
}
// 展开分组
setLoading(prev => ({ ...prev, [groupId]: true }));
try {
const parentGroup = groups.find(g => g.id === groupId);
// 如果已经加载过子分组,直接展开
if (parentGroup && parentGroup.children && parentGroup.children.length > 0) {
setExpandedGroups(prev => [...prev, groupId]);
// 应用现有的过滤条件到已加载的子分组
const nameFilter = searchParams.get('name') || '';
const codeFilter = searchParams.get('code') || '';
const statusFilter = searchParams.get('is_enabled') || '';
if ((nameFilter || codeFilter || statusFilter) && parentGroup.children) {
applyFiltersToChildren(groupId, parentGroup.children);
}
setLoading(prev => ({ ...prev, [groupId]: false }));
return;
}
// 否则加载子分组
const response = await getChildGroups(groupId, frontendJWT);
if (response.error) {
throw new Error(response.error);
}
// 更新分组数据
setGroups(prev => prev.map(group => {
if (group.id === groupId) {
return {
...group,
children: response.data
};
}
return group;
}));
setExpandedGroups(prev => [...prev, groupId]);
// 应用现有的过滤条件到新加载的子分组
const nameFilter = searchParams.get('name') || '';
const codeFilter = searchParams.get('code') || '';
const statusFilter = searchParams.get('is_enabled') || '';
if ((nameFilter || codeFilter || statusFilter) && response.data) {
applyFiltersToChildren(groupId, response.data);
}
} catch (error) {
console.error('加载子分组失败:', error);
} finally {
setLoading(prev => ({ ...prev, [groupId]: false }));
}
};
// 展开/收起全部
const toggleAll = async (expand: boolean) => {
if (expand) {
// 展开所有分组
const expandedIds = groups.map(g => g.id);
setExpandedGroups(expandedIds);
} else {
setExpandedGroups([]);
}
};
// 处理删除分组
const handleDeleteGroup = async (groupId: string) => {
// ✅ 检查删除权限
if (!canDeleteGroup) {
toastService.warning('您没有删除权限');
return;
}
messageService.show({
title: "确认删除",
message: "确定要删除该分组吗?此操作将同时删除该分组下的所有评查点,且不可恢复。",
type: "warning",
confirmText: "删除",
cancelText: "取消",
confirmDelay: 4,
onConfirm: async () => {
try {
const result = await deleteEvaluationPointGroup(groupId, frontendJWT);
if (result.success) {
// 从本地状态中移除被删除的分组
setGroups(prev => {
// 如果是一级分组,直接过滤掉
const filteredGroups = prev.filter(g => g.id !== groupId);
// 如果是二级分组,需要从父分组的 children 中移除
return filteredGroups.map(group => {
if (group.children) {
return {
...group,
children: group.children.filter(child => child.id !== groupId)
};
}
return group;
});
});
// 如果被删除的分组当前是展开状态,从展开列表中移除
setExpandedGroups(prev => prev.filter(id => id !== groupId));
// 显示成功消息
toastService.success('删除成功')
} else {
toastService.error(`删除失败: ${result.error}`);
console.error(`删除失败: ${result.error}`);
}
} catch (error) {
console.error('删除分组失败:', error);
toastService.error('删除分组失败,请稍后重试');
}
}
});
};
// 🆕 批量启用/禁用
const handleBatchEnable = async (enable: boolean) => {
// ✅ 检查更新权限
if (!canUpdateGroup) {
toastService.warning('您没有更新权限');
return;
}
if (selectedIds.length === 0) {
toastService.warning('请先选择要操作的分组');
return;
}
try {
const result = await batchUpdateEvaluationPointGroupStatus(selectedIds, enable, frontendJWT);
if (result.success) {
toastService.success(`成功${enable ? '启用' : '禁用'} ${result.updated_count} 个分组`);
// 刷新页面以重新加载数据
window.location.reload();
} else {
toastService.error(`批量操作失败:${result.failed_ids.length} 个分组操作失败`);
}
} catch (error) {
console.error('批量操作失败:', error);
toastService.error('批量操作失败,请稍后重试');
}
};
// 🆕 批量删除
const handleBatchDelete = async () => {
// ✅ 检查删除权限
if (!canDeleteGroup) {
toastService.warning('您没有删除权限');
return;
}
if (selectedIds.length === 0) {
toastService.warning('请先选择要删除的分组');
return;
}
messageService.show({
title: "确认批量删除",
message: `确定要删除选中的 ${selectedIds.length} 个分组吗?此操作不可恢复。`,
type: "warning",
confirmText: "删除",
cancelText: "取消",
confirmDelay: 4,
onConfirm: async () => {
try {
const result = await batchDeleteEvaluationPointGroups(selectedIds, frontendJWT);
// 检查返回状态
if (!result.success) {
toastService.error(result.error || '删除失败');
return;
}
toastService.success(`成功删除 ${result.deleted_groups || 0} 个分组`);
// 刷新页面以重新加载数据
window.location.reload();
} catch (error) {
console.error('批量删除失败:', error);
toastService.error('批量删除失败,请稍后重试');
}
}
});
};
// 🆕 处理全选/取消全选
const handleSelectAll = () => {
if (selectedIds.length === groups.length) {
setSelectedIds([]);
} else {
setSelectedIds(groups.map(g => g.id));
}
};
// 🆕 处理单选
const handleSelectRow = (id: string) => {
setSelectedIds(prev =>
prev.includes(id) ? prev.filter(selectedId => selectedId !== id) : [...prev, id]
);
};
// 处理搜索名称
const handleNameSearch = (value: string) => {
const newParams = new URLSearchParams(searchParams);
if (value) {
newParams.set('name', value);
} else {
newParams.delete('name');
}
newParams.set('page', '1');
setSearchParams(newParams);
};
// 处理搜索编码
const handleCodeSearch = (value: string) => {
const newParams = new URLSearchParams(searchParams);
if (value) {
newParams.set('code', value);
} else {
newParams.delete('code');
}
newParams.set('page', '1');
setSearchParams(newParams);
};
// 处理状态筛选变更
const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const { value } = e.target;
const newParams = new URLSearchParams(searchParams);
if (value) {
newParams.set('is_enabled', value);
} else {
newParams.delete('is_enabled');
}
newParams.set('page', '1');
setSearchParams(newParams);
};
// 处理重置筛选
const handleReset = () => {
setSearchParams(new URLSearchParams());
setFilteredChildrenMap({});
// 清空输入框内容(通过DOM操作)
const nameInput = document.querySelector('input[placeholder="请输入分组名称"]') as HTMLInputElement;
const codeInput = document.querySelector('input[placeholder="请输入分组编码"]') as HTMLInputElement;
const statusSelect = document.querySelector('select[name="is_enabled"]') as HTMLSelectElement;
if (nameInput) nameInput.value = '';
if (codeInput) codeInput.value = '';
if (statusSelect) statusSelect.value = '';
};
// 应用筛选条件到子分组
const applyFiltersToChildren = (parentId: string, children: RuleGroup[]) => {
const nameFilter = searchParams.get('name') || '';
const codeFilter = searchParams.get('code') || '';
const statusFilter = searchParams.get('is_enabled') || '';
if (!nameFilter && !codeFilter && !statusFilter) {
setFilteredChildrenMap(prev => ({...prev, [parentId]: []}));
return;
}
const filteredChildren = children.filter(child => {
// 筛选名称
if (nameFilter && !child.name.toLowerCase().includes(nameFilter.toLowerCase())) {
return false;
}
// 筛选编码
if (codeFilter && (!child.code || !child.code.toLowerCase().includes(codeFilter.toLowerCase()))) {
return false;
}
// 筛选状态
if (statusFilter) {
const isEnabled = statusFilter === 'true';
if (child.is_enabled !== isEnabled) {
return false;
}
}
return true;
});
setFilteredChildrenMap(prev => ({...prev, [parentId]: filteredChildren}));
};
// 当筛选条件变化时,重新计算过滤结果
useEffect(() => {
const nameFilter = searchParams.get('name') || '';
const codeFilter = searchParams.get('code') || '';
const statusFilter = searchParams.get('is_enabled') || '';
if (!nameFilter && !codeFilter && !statusFilter) {
setFilteredChildrenMap({});
return;
}
// 检查所有已加载的子分组
groups.forEach(group => {
if (group.children && group.children.length > 0) {
applyFiltersToChildren(group.id, group.children);
}
});
// 查找匹配的子分组,自动展开它们的父级
const parentsToExpand: string[] = [];
groups.forEach(group => {
if (group.children && group.children.length > 0) {
const hasMatchingChild = group.children.some(child => {
const nameMatch = !nameFilter || child.name.toLowerCase().includes(nameFilter.toLowerCase());
const codeMatch = !codeFilter || (child.code && child.code.toLowerCase().includes(codeFilter.toLowerCase()));
const statusMatch = !statusFilter || (statusFilter === 'true' ? child.is_enabled : !child.is_enabled);
return nameMatch && codeMatch && statusMatch;
});
if (hasMatchingChild && !expandedGroups.includes(group.id)) {
parentsToExpand.push(group.id);
}
}
});
if (parentsToExpand.length > 0) {
setExpandedGroups(prev => [...prev, ...parentsToExpand]);
}
}, [searchParams, groups]);
// 检查父级分组是否匹配筛选条件
const parentMatchesFilter = (group: RuleGroup): boolean => {
const nameFilter = searchParams.get('name') || '';
const codeFilter = searchParams.get('code') || '';
const statusFilter = searchParams.get('is_enabled') || '';
// 筛选名称
if (nameFilter && !group.name.toLowerCase().includes(nameFilter.toLowerCase())) {
return false;
}
// 筛选编码
if (codeFilter && (!group.code || !group.code.toLowerCase().includes(codeFilter.toLowerCase()))) {
return false;
}
// 筛选状态
if (statusFilter) {
const isEnabled = statusFilter === 'true';
if (group.is_enabled !== isEnabled) {
return false;
}
}
return true;
};
// 检查父级分组是否有匹配的子分组
const hasMatchingChildren = (parentId: string): boolean => {
const filteredChildren = filteredChildrenMap[parentId];
return filteredChildren && filteredChildren.length > 0;
};
// 处理表格数据,包括父子级关系
const processedData = groups.flatMap(group => {
const parentMatches = parentMatchesFilter(group);
const hasMatched = hasMatchingChildren(group.id);
// 如果有筛选条件但父级和子级都不匹配,则不显示
const nameFilter = searchParams.get('name') || '';
const codeFilter = searchParams.get('code') || '';
const statusFilter = searchParams.get('is_enabled') || '';
const hasFilters = nameFilter || codeFilter || statusFilter;
if (hasFilters && !parentMatches && !hasMatched) {
return [];
}
// 先添加父级分组
const result: (RuleGroup & { isParent?: boolean, parentId?: string })[] = [
{ ...group, isParent: true }
];
// 如果有子级分组并且当前已展开,则添加子级分组
if (group.children && expandedGroups.includes(group.id)) {
// 如果有筛选条件并且有过滤后的子分组,则显示过滤后的子分组
if (hasFilters && filteredChildrenMap[group.id]) {
filteredChildrenMap[group.id].forEach(child => {
result.push({ ...child, parentId: group.id });
});
}
// 否则显示所有子分组
else if (!hasFilters) {
group.children.forEach(child => {
result.push({ ...child, parentId: group.id });
});
}
}
return result;
});
// 计算一级分组的评查点数量(累加其所有二级分组的评查点数量)
const calculateTotalRuleCount = (record: RuleGroup & { isParent?: boolean, parentId?: string }) => {
// 如果是二级分组,直接返回其评查点数量
if (!record.isParent) {
return record.ruleCount || 0;
}
// 如果是一级分组,累加其所有子分组的评查点数量
let totalCount = 0;
if (record.children && record.children.length > 0) {
totalCount = record.children.reduce((sum, child) => sum + (child.ruleCount || 0), 0);
}
return totalCount;
};
// 定义表格列配置
const columns = [
// 🆕 复选框列 - 仅在有批量操作权限时显示
...(canBatchOperation ? [{
title: (
<input
type="checkbox"
checked={selectedIds.length > 0 && selectedIds.length === groups.length}
onChange={handleSelectAll}
style={{ cursor: 'pointer' }}
/>
),
key: "selection",
width: "50px",
render: (_: unknown, record: RuleGroup) => (
<input
type="checkbox"
checked={selectedIds.includes(record.id)}
onChange={() => handleSelectRow(record.id)}
onClick={(e) => e.stopPropagation()}
style={{ cursor: 'pointer' }}
/>
)
}] : []),
{
title: "分组名称",
key: "name",
width: "35%",
render: (_: unknown, record: RuleGroup & { isParent?: boolean, parentId?: string }) => (
<div className={`flex items-center ${!record.isParent ? 'ml-8' : ''}`}>
{record.isParent && (
<span
className="expand-icon"
onClick={() => toggleGroup(record.id)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleGroup(record.id);
}
}}
>
{loading[record.id] ? (
<i className="ri-loader-4-line animate-spin"></i>
) : (
<i className={`ri-arrow-${expandedGroups.includes(record.id) ? 'down' : 'right'}-s-line`}></i>
)}
</span>
)}
<Link
to={`/rule-groups/new?id=${record.id}`}
className="group-name-link flex items-center ml-1 text-green-800"
>
<i className={`${record.isParent ? 'ri-folder-line' : 'ri-file-list-line'} mr-1 text-green-800`}></i> {record.name}
</Link>
<span className={`group-badge ${record.isParent ? 'parent-badge' : 'child-badge'}`}>
{record.isParent ? '一级分组' : '二级分组'}
</span>
</div>
)
},
{
title: "分组编码",
key: "code",
render: (_: unknown, record: RuleGroup) => (
<span>{record.code || '-'}</span>
)
},
{
title: "评查点数量",
key: "ruleCount",
width: "12%",
render: (_: unknown, record: RuleGroup & { isParent?: boolean, parentId?: string }) => (
<div className="badge bg-primary text-white">
{/* <button onClick={() => navigate(`/rules/list?${!record.isParent ? `ruleType=${record.parentId}&groupId=${record.id}` : `ruleType=${record.id}`}`)} className="badge bg-primary text-white"> */}
<span className="text-xs">{calculateTotalRuleCount(record)}</span>
</div>
)
},
{
title: "状态",
key: "is_enabled",
render: (_: unknown, record: RuleGroup) => (
<StatusDot
status={record.is_enabled ? 'success' : 'error'}
text={record.is_enabled ? '启用' : '禁用'}
align="left"
/>
)
},
{
title: "创建时间",
key: "createdAt",
width: "15%",
render: (_: unknown, record: RuleGroup) => (
<span>{record.createdAt || '-'}</span>
)
},
{
title: "操作",
key: "operation",
width: "180px",
render: (_: unknown, record: RuleGroup) => (
<div className="operations-cell">
<button
onClick={() => navigate(`/rule-groups/new?id=${record.id}`)}
className="operation-btn"
>
<i className="ri-edit-line"></i> {canUpdateGroup ? '编辑' : '查看'}
</button>
{canDeleteGroup && (
<button
type="button"
className="operation-btn !text-[--color-error]"
onClick={() => handleDeleteGroup(record.id)}
>
<i className="ri-delete-bin-line"></i>
</button>
)}
</div>
)
}
];
return (
// <div className="content-container rule-groups-page">
<div className="rule-groups-page">
{/* 页面头部 */}
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-medium flex">
<div className="flex items-center bg-white px-3 py-1 rounded-md">
<span className="text-sm text-secondary"></span>
<span className="text-base font-normal text-primary ml-1">{processedData.length}</span>
</div>
</h2>
<div className="flex">
<Button
type="default"
icon="ri-arrow-down-s-line"
onClick={() => toggleAll(true)}
className="mr-2"
>
</Button>
<Button
type="default"
icon="ri-arrow-up-s-line"
onClick={() => toggleAll(false)}
className="mr-2"
>
</Button>
{/* ✅ 批量启用/禁用按钮:仅当有更新权限且有选中项时显示 */}
{canUpdateGroup && selectedIds.length > 0 && (
<>
<Button
type="default"
icon="ri-checkbox-circle-line"
onClick={() => handleBatchEnable(true)}
className="mr-2"
>
({selectedIds.length})
</Button>
<Button
type="default"
icon="ri-close-circle-line"
onClick={() => handleBatchEnable(false)}
className="mr-2"
>
({selectedIds.length})
</Button>
</>
)}
{/* ✅ 批量删除按钮:仅当有删除权限且有选中项时显示 */}
{canDeleteGroup && selectedIds.length > 0 && (
<Button
type="danger"
icon="ri-delete-bin-line"
onClick={handleBatchDelete}
className="mr-2"
>
({selectedIds.length})
</Button>
)}
{canCreateGroup && (
<Button
type="primary"
icon="ri-add-line"
onClick={() => navigate("/rule-groups/new")}
>
</Button>
)}
</div>
</div>
{/* 搜索栏 - 使用FilterPanel */}
<FilterPanel
className="mb-4"
actions={
<>
<Button type="default" icon="ri-refresh-line" onClick={handleReset} className="mr-2">
</Button>
{/* <Button type="primary" icon="ri-search-line" onClick={() => {}}>
搜索
</Button> */}
</>
}
noActionDivider={true}
>
<SearchFilter
label="分组名称"
placeholder="请输入分组名称"
value={searchParams.get('name') || ''}
onSearch={handleNameSearch}
className="flex-1 min-w-[200px]"
instantSearch={true}
/>
<SearchFilter
label="分组编码"
placeholder="请输入分组编码"
value={searchParams.get('code') || ''}
onSearch={handleCodeSearch}
className="flex-1 min-w-[200px]"
instantSearch={true}
/>
<FilterSelect
label="状态"
name="is_enabled"
value={searchParams.get('is_enabled') || ''}
options={[
{ value: "true", label: "启用" },
{ value: "false", label: "禁用" }
]}
onChange={handleStatusChange}
className="flex-1 min-w-[200px]"
/>
</FilterPanel>
{/* 数据表格 - 使用Table组件 */}
<Card bodyClassName="px-4 py-4">
{initialLoading ? (
<div className="flex justify-center items-center py-10">
<i className="ri-loader-4-line animate-spin text-2xl mr-2"></i>
<span>...</span>
</div>
) : (
<>
<Table
columns={columns}
dataSource={processedData}
rowKey={(record) => record.isParent ? `parent-${record.id}` : `child-${record.id}`}
emptyText="暂无分组数据"
className="tree-table"
/>
{/* 分页 - 使用Pagination组件 */}
{/* <Pagination
currentPage={1}
total={processedData.length}
pageSize={10}
onChange={() => {}}
showTotal={true}
/> */}
</>
)}
</Card>
</div>
);
}