Files
leaudit-platform-frontend/app/routes/rules.list.tsx
T
LiangShiyong 6dc9b4e468 feat: 1. 完善文档列表的显示效果,数据对接后端接口返回。
2. 对评查点分组和文档类型的编辑删除新增操作进行限制。
2025-11-20 15:26:11 +08:00

809 lines
27 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 React, { useState, useEffect, useCallback, useRef } from 'react';
import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, useSearchParams, Link, useNavigate, useFetcher, useRouteLoaderData, useLocation } from "@remix-run/react";
import { Button } from '~/components/ui/Button';
import { Card } from '~/components/ui/Card';
import { Tag } from '~/components/ui/Tag';
import { StatusDot } from '~/components/ui/StatusDot';
import { TableRowSkeleton, LoadingIndicator, NumberSkeleton } from '~/components/ui/SkeletonScreen';
import rulesStyles from "~/styles/pages/rules_index.css?url";
import type { Rule, RuleType, RulePriority } from '~/models/rule';
import { RULE_TYPE_COLORS, RULE_PRIORITY_LABELS, RULE_PRIORITY_COLORS } from '~/models/rule';
import type { TagColor } from '~/components/ui/Tag';
import { Table } from '~/components/ui/Table';
import { FilterPanel, FilterSelect, SearchFilter } from '~/components/ui/FilterPanel';
import { Pagination } from '~/components/ui/Pagination';
import { messageService } from '~/components/ui/MessageModal';
import { toastService } from '~/components/ui/Toast';
import {
getRulesList,
deleteRule,
getRuleTypes,
getRuleGroupsByType,
type RuleType as ApiRuleType,
type RuleGroup
} from '~/api/evaluation_points/rules';
import type { UserRole } from '~/root';
export const links = () => [
{ rel: "stylesheet", href: rulesStyles }
];
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 评查点列表" },
{ name: "rules", content: "管理评查点规则,支持根据类型、规则组和状态进行筛选" },
{ name: "keywords", content: "评查点,合同审核,规则管理,中国烟草" }
];
};
// export const handle = {
// breadcrumb: "评查点列表"
// };
// 声明loader返回的数据类型
export type LoaderData = {
rules: Rule[];
totalCount: number;
currentPage: number;
pageSize: number;
totalPages: number;
ruleTypes: ApiRuleType[]; // 添加评查点类型
initialLoad?: boolean; // 添加初始加载标志
};
// API返回的数据映射到前端模型
interface ApiRule {
id: string;
code: string;
name: string;
ruleType: string;
groupId: string;
groupName: string;
priority: string;
description: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
interface ActionResponse {
result: boolean;
message: string;
}
function mapApiRuleToModel(apiRule: ApiRule): Rule {
// 🔑 清洗评查点编码:移除最后一个 '--' 及其后面的字符
// 例如:'code-mis--mz' --> 'code-mis', 'code-mbs--alsi--gz' --> 'code-mbs--alsi'
let cleanedCode = apiRule.code;
const lastDoubleHyphenIndex = cleanedCode.lastIndexOf('--');
if (lastDoubleHyphenIndex !== -1) {
cleanedCode = cleanedCode.substring(0, lastDoubleHyphenIndex);
}
return {
id: apiRule.id,
code: cleanedCode,
name: apiRule.name,
ruleType: apiRule.ruleType as RuleType, // 类型转换
ruleGroupId: apiRule.groupId,
groupName: apiRule.groupName,
priority: apiRule.priority as RulePriority, // 类型转换
description: apiRule.description,
checkMethod: 'automatic', // 默认值
prompt: apiRule.description, // 使用描述作为默认prompt
isActive: apiRule.isActive,
createdAt: apiRule.createdAt,
updatedAt: apiRule.updatedAt
};
}
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
// 从 URL 参数中提取查询条件
const params = {
page: parseInt(url.searchParams.get("page") || "1", 10),
pageSize: parseInt(url.searchParams.get("pageSize") || "10", 10)
};
try {
// 🔑 使用 handleServerAuth 包装,自动处理 token 过期
const { handleServerAuth } = await import("~/utils/server-auth-handler");
return handleServerAuth(async () => {
// 获取用户会话信息
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
// 返回初始空数据,客户端将根据 sessionStorage 中的 documentTypeIds 加载实际数据
return Response.json({
rules: [],
totalCount: 0,
currentPage: params.page,
pageSize: params.pageSize,
ruleTypes: [], // 服务端无法访问 sessionStorage,客户端加载
initialLoad: true,
frontendJWT
}, {
headers: {
"Cache-Control": "max-age=60, s-maxage=180"
}
});
}, url.pathname);
} catch (error) {
console.error('加载评查点列表失败:', error);
return Response.json({
error: error || '加载评查点列表失败',
status: 500
}, { status: 500 });
}
}
export async function action({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const formData = await request.formData();
const _action = formData.get('_action');
const ruleId = formData.get('ruleId');
if (!ruleId) {
return Response.json({ result: false, message: "缺少评查点ID" }, { status: 400 });
}
try {
// 🔑 使用 handleServerAuth 包装,自动处理 token 过期
const { handleServerAuth } = await import("~/utils/server-auth-handler");
return handleServerAuth(async () => {
// 获取用户会话信息
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
if (_action === 'delete') {
// 调用API删除评查点
const deleteResponse = await deleteRule(ruleId as string, frontendJWT);
if (deleteResponse.error) {
return Response.json({ result: false, message: deleteResponse.error }, { status: deleteResponse.status || 500 });
}
return Response.json({ result: true, message: "评查点删除成功" }, { status: 200 });
}
return Response.json({ result: false, message: "未知操作" }, { status: 400 });
}, url.pathname);
} catch (error) {
console.error('操作评查点失败:', error);
return Response.json({ result: false, message: error instanceof Error ? error.message : "操作失败" }, { status: 500 });
}
}
// 规则优先级的描述标签映射
const priorityLabels = {
'high': '高',
'medium': '中',
'low': '低'
};
export default function RulesIndex() {
const loaderData = useLoaderData<typeof loader>();
const rootData = useRouteLoaderData("root") as { userRole: UserRole };
const { rules: initialRules, totalCount: initialTotalCount, currentPage, pageSize, ruleTypes: initialRuleTypes, initialLoad } = loaderData;
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const fetcher = useFetcher<ActionResponse>();
const location = useLocation();
// 状态管理
const [ruleGroups, setRuleGroups] = useState<RuleGroup[]>([]);
const [loadingGroups, setLoadingGroups] = useState(false);
const [loading, setLoading] = useState(false);
const [filteredRules, setFilteredRules] = useState<Rule[]>(initialRules);
const [filteredTotalCount, setFilteredTotalCount] = useState<number>(initialTotalCount);
const [ruleTypes, setRuleTypes] = useState<ApiRuleType[]>(initialRuleTypes);
// 添加一个状态来跟踪是否执行了删除操作
const [isDeleting, setIsDeleting] = useState(false);
// 使用 ref 跟踪是否正在加载数据,避免重复加载
const isLoadingRef = useRef(false);
// 查询参数记忆 key 与保存/恢复
const SEARCH_PARAMS_STORAGE_KEY = 'rules.searchParams';
const persistSearchParams = (params: URLSearchParams) => {
if (typeof window !== 'undefined') {
sessionStorage.setItem(SEARCH_PARAMS_STORAGE_KEY, params.toString());
}
};
// 首次进入页且 URL 无参数时尝试恢复
useEffect(() => {
if (typeof window === 'undefined') return;
const hasAnyParam = Array.from(searchParams.keys()).length > 0;
const stored = sessionStorage.getItem(SEARCH_PARAMS_STORAGE_KEY);
if (!hasAnyParam && stored) {
setSearchParams(new URLSearchParams(stored));
}
// 仅初始化检查一次
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 获取当前的ruleType值
const ruleTypeParam = searchParams.get('ruleType');
// 判断是否禁用规则组选择
const isRuleGroupSelectDisabled = loadingGroups || !ruleTypeParam || ruleGroups.length === 0;
// 检查用户是否为开发者角色
const userRole = rootData?.userRole || 'common';
const isDeveloper = userRole.includes('admin');
// 调试日志
// console.log("🔑 [Rules List] rootData:", rootData);
// console.log("🔑 [Rules List] 用户角色:", userRole);
// console.log("🔑 [Rules List] 是否为管理员:", isDeveloper);
// 在组件渲染时初始化状态
// useEffect(() => {
// setFilteredRules(initialRules);
// setFilteredTotalCount(initialTotalCount);
// setRuleTypes(initialRuleTypes);
// }, [initialRules, initialTotalCount, initialRuleTypes]);
// 使用useEffect监听loaderData.error变化并显示Toast
useEffect(() => {
if(loaderData.error) {
toastService.error(loaderData.error);
}
// ❌ 不再检查 loaderData.ruleTypes,因为服务端永远返回空数组
// 如果需要检查评查点类型数据,应该在 fetchData 完成后检查状态 ruleTypes
}, [loaderData.error]);
// 客户端数据加载函数
const fetchData = useCallback(async () => {
try {
// 🔑 如果正在加载,避免重复调用
if (isLoadingRef.current) {
console.log('📋 [fetchData] 正在加载中,跳过重复调用');
return;
}
// 🔑 从 sessionStorage 获取 documentTypeIds
const typeIdsStr = typeof window !== 'undefined' ? sessionStorage.getItem('documentTypeIds') : null;
const documentTypeIds = typeIdsStr ? JSON.parse(typeIdsStr) : null;
if (!documentTypeIds || documentTypeIds.length === 0) {
console.warn('无法加载评查点数据:未找到 documentTypeIds');
return;
}
isLoadingRef.current = true;
// 🔑 从 localStorage 获取 user_info 中的 area
let userArea: string | undefined = undefined;
if (typeof window !== 'undefined') {
try {
const userInfoStr = localStorage.getItem('user_info');
if (userInfoStr) {
const userInfo = JSON.parse(userInfoStr);
userArea = userInfo.area;
console.log("📋 [fetchData] 从 localStorage 获取到用户地区:", userArea);
}
} catch (error) {
console.error('解析 user_info 失败:', error);
}
}
console.log("📋 [fetchData] 开始加载评查点数据, documentTypeIds:", documentTypeIds, "area:", userArea);
setLoading(true);
// 🔑 获取评查点类型(通过 documentTypeIds
let loadedRuleTypes: ApiRuleType[] = [];
try {
const typeResponse = await getRuleTypes(documentTypeIds, loaderData.frontendJWT);
if (typeResponse.data) {
loadedRuleTypes = typeResponse.data;
setRuleTypes(loadedRuleTypes);
console.log("📋 [fetchData] 获取到评查点类型:", loadedRuleTypes);
}
} catch (error) {
console.error('加载评查点类型失败:', error);
}
// 构建查询参数
// 🔑 当选择"全部"或未选择评查点类型时,使用下拉框中所有评查点类型的 id 组合
let finalRuleType: string | undefined = undefined;
if (ruleTypeParam && ruleTypeParam !== 'all') {
// 选择了具体的评查点类型
finalRuleType = ruleTypeParam;
} else if (loadedRuleTypes && loadedRuleTypes.length > 0) {
// 选择"全部"或未选择,使用刚加载的评查点类型的 id
finalRuleType = loadedRuleTypes.map(type => type.id).join(',');
console.log("📋 [fetchData] 选择全部类型,使用 loadedRuleTypes 的 id 组合:", finalRuleType);
}
const queryParams = {
ruleType: finalRuleType,
groupId: searchParams.get('groupId') || undefined,
isActive: searchParams.get('isActive') ? searchParams.get('isActive') === 'true' : undefined,
keyword: searchParams.get('keyword') || undefined,
area: userArea, // 添加地区过滤
page: currentPage,
pageSize,
token: loaderData.frontendJWT
};
// 调用 API 获取数据
const response = await getRulesList(queryParams);
if (response.data) {
const apiRules = response.data.rules || [];
const total = response.data.totalCount || 0;
const mappedRules = apiRules.map((apiRule: ApiRule) => mapApiRuleToModel(apiRule));
setFilteredRules(mappedRules);
setFilteredTotalCount(total);
}
} catch (error) {
console.error('客户端加载评查点列表失败:', error);
toastService.error('加载评查点列表失败');
} finally {
setLoading(false);
isLoadingRef.current = false;
}
}, [ruleTypeParam, searchParams, currentPage, pageSize, loaderData.frontendJWT]);
// 当评查点类型变化时,加载对应的规则组
useEffect(() => {
// 如果选择了"全部"或未选择,则清空规则组
if (!ruleTypeParam || ruleTypeParam === 'all') {
setRuleGroups([]);
return;
}
// 加载当前类型的规则组
const loadRuleGroups = async () => {
setLoadingGroups(true);
try {
const response = await getRuleGroupsByType(ruleTypeParam, loaderData.frontendJWT);
if (response.data) {
setRuleGroups(response.data);
} else if (response.error) {
console.error('加载规则组失败:', response.error);
setRuleGroups([]);
}
} catch (error) {
console.error('加载规则组出错:', error);
setRuleGroups([]);
} finally {
setLoadingGroups(false);
}
};
loadRuleGroups();
}, [ruleTypeParam]);
// 使用useEffect监听fetcher状态变化并显示Toast fetcher.state有以下几种状态: 通过fetcher提交数据后,action返回结果,fetcher.state会发生变化
// idle: 空闲状态
// loading: 加载中状态
// submittting: 提交中状态
// loading: 加载中状态
// idle: 空闲状态
useEffect(() => {
// 仅在fetcher有数据且状态为idle时处理
if (fetcher.data && fetcher.state === 'idle' && isDeleting) {
// 重置删除状态
setIsDeleting(false);
if (fetcher.data.result) {
toastService.success(fetcher.data.message);
// 删除成功后重新加载数据
fetchData();
} else if (!fetcher.data.result) {
// 删除失败只显示错误信息,不刷新数据
if(fetcher.data.message.includes("evaluation_results_evaluation_point_id_fkey")) {
toastService.error('对表evaluation_points进行更新或删除违反了表evaluation results上的外键约束evaluations results_evaluation _point_id_fkey');
} else {
toastService.error(fetcher.data.message);
}
// 删除失败不刷新数据
}
}
}, [fetcher.data, fetcher.state, fetchData, isDeleting]);
// 在组件挂载时从 sessionStorage 获取 documentTypeIds 并加载数据
useEffect(() => {
try {
if (typeof window !== 'undefined') {
const typeIdsStr = sessionStorage.getItem('documentTypeIds');
const documentTypeIds = typeIdsStr ? JSON.parse(typeIdsStr) : null;
console.log("📋 组件挂载,从 sessionStorage 获取 documentTypeIds:", documentTypeIds);
// 如果有 documentTypeIds,加载数据
if (documentTypeIds && documentTypeIds.length > 0) {
// 使用 setTimeout 确保该操作在其他状态更新之后执行
setTimeout(() => {
fetchData();
}, 0);
}
}
} catch (error) {
console.error('获取 sessionStorage 中的 documentTypeIds 失败:', error);
}
}, [initialLoad, fetchData]);
// 注释掉重复的路由监听逻辑,避免与searchParams监听重复触发
// useEffect(() => {
// if (routeChangeCount > 0) {
// console.log("📋 路由变化触发数据刷新,计数:", routeChangeCount);
// const typeIdsStr = typeof window !== 'undefined' ? sessionStorage.getItem('documentTypeIds') : null;
// const documentTypeIds = typeIdsStr ? JSON.parse(typeIdsStr) : null;
// console.log("📋 documentTypeIds:", documentTypeIds);
// if (documentTypeIds && documentTypeIds.length > 0) {
// // 使用 setTimeout 确保该操作在其他状态更新之后执行
// setTimeout(() => {
// fetchData();
// }, 0);
// }
// }
// }, [routeChangeCount, fetchData]);
// 监听 URL 参数变化,重新获取数据
useEffect(() => {
// 检查是否有 documentTypeIds
const typeIdsStr = typeof window !== 'undefined' ? sessionStorage.getItem('documentTypeIds') : null;
const documentTypeIds = typeIdsStr ? JSON.parse(typeIdsStr) : null;
if (documentTypeIds && documentTypeIds.length > 0) {
fetchData();
}
}, [searchParams, fetchData]);
// 筛选评查点
const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>) => {
const { name, value } = e.target;
const newParams = new URLSearchParams(searchParams);
// 如果是规则组选择,但是当前应该被禁用,则不处理
if (name === 'groupId' && isRuleGroupSelectDisabled) {
return;
}
if (value) {
newParams.set(name, value);
// 如果是评查点类型变更,清空规则组选择
if (name === 'ruleType') {
newParams.delete('groupId');
// 如果选择了"全部"或空值,也清空规则组选择
if (value === '' || value === 'all') {
setRuleGroups([]);
}
}
} else {
newParams.delete(name);
// 如果清除评查点类型,也清除规则组
if (name === 'ruleType') {
newParams.delete('groupId');
setRuleGroups([]);
}
}
// 切换筛选条件时,重置到第一页
newParams.set('page', '1');
persistSearchParams(newParams);
setSearchParams(newParams);
};
// 搜索评查点
const handleSearch = (keyword: string) => {
const newParams = new URLSearchParams(searchParams);
if (keyword) {
newParams.set('keyword', keyword);
} else {
newParams.delete('keyword');
}
// 搜索时,重置到第一页
newParams.set('page', '1');
persistSearchParams(newParams);
setSearchParams(newParams);
};
// 删除评查点
const handleDeleteClick = (rule: Rule) => {
messageService.show({
title: "确认删除",
message: `确认删除评查点【${rule.name}】吗?`,
type: "warning",
confirmText: "删除",
cancelText: "取消",
onConfirm: () => {
// 设置删除状态为true
setIsDeleting(true);
const form = new FormData();
form.append("_action", "delete");
form.append("ruleId", rule.id);
fetcher.submit(form, { method: "post" });
}
});
};
// 复制评查点
const handleCopy = (rule: Rule) => {
navigate(`/rules/new?id=${rule.id}&mode=copy`);
};
const handlePageChange = (page: number) => {
const newParams = new URLSearchParams(searchParams);
newParams.set('page', page.toString());
persistSearchParams(newParams);
setSearchParams(newParams);
};
// 处理每页条数变化
const handlePageSizeChange = (size: number) => {
const newParams = new URLSearchParams(searchParams);
newParams.set('pageSize', size.toString());
newParams.set('page', '1'); // 更改每页条数时,重置到第一页
persistSearchParams(newParams);
setSearchParams(newParams);
};
// 处理重置筛选
const handleReset = () => {
const input = document.querySelector('input[placeholder="输入评查点名称或编码"]');
if (input) {
(input as HTMLInputElement).value = '';
}
const newParams = new URLSearchParams();
if (typeof window !== 'undefined') {
sessionStorage.removeItem(SEARCH_PARAMS_STORAGE_KEY);
}
setSearchParams(newParams);
};
// 定义表格列配置
const columns = [
{
title: "评查点编码",
dataIndex: "code" as keyof Rule,
key: "code",
align: "left" as const,
width: "20%",
className: "whitespace-normal break-all",
render: (value: string) => (
<div className="whitespace-normal break-all overflow-visible">{value}</div>
)
},
{
title: "评查点名称",
dataIndex: "name" as keyof Rule,
key: "name",
align: "left" as const,
width: "20%"
},
{
title: "评查点类型",
key: "ruleType",
align: "left" as const,
width: "12%",
render: (_: unknown, record: Rule) => {
const typeColor = RULE_TYPE_COLORS[record.ruleType] as TagColor;
return (
record.ruleType ? <Tag color={typeColor}>
{record.ruleType}
</Tag> : null
);
}
},
{
title: "所属规则组",
dataIndex: "groupName" as keyof Rule,
key: "groupName",
align: "left" as const,
width: "10%"
},
{
title: "优先级",
key: "priority",
align: "left" as const,
width: "8%",
render: (_: unknown, record: Rule) => {
const priorityColor = RULE_PRIORITY_COLORS[record.priority] as TagColor;
return (
<Tag color={priorityColor}>
{priorityLabels[record.priority as keyof typeof priorityLabels] || RULE_PRIORITY_LABELS[record.priority]}
</Tag>
);
}
},
{
title: "状态",
key: "isActive",
align: "left" as const,
width: "8%",
render: (_: unknown, record: Rule) => (
<StatusDot status={record.isActive} text={record.isActive ? "启用" : "禁用"} />
)
},
{
title: "创建时间",
dataIndex: "createdAt" as keyof Rule,
key: "createdAt",
align: "left" as const,
width: "10%"
},
{
title: "操作",
key: "operation",
align: "left" as const,
width: "10%",
render: (_: unknown, record: Rule) => (
<div className="operations-cell">
{isDeveloper ? (
// 开发者可以看到编辑、复制、删除
<>
<Link to={`/rules/new?id=${record.id}`} className="operation-btn">
<i className="ri-edit-line"></i>
</Link>
<button className="operation-btn" onClick={() => handleCopy(record)}>
<i className="ri-file-copy-line"></i>
</button>
<button className="operation-btn operation-btn-danger" onClick={() => handleDeleteClick(record)}>
<i className="ri-delete-bin-line"></i>
</button>
</>
) : (
// 普通用户只能查看
<Link to={`/rules/new?id=${record.id}&mode=view`} className="operation-btn">
<i className="ri-eye-line"></i>
</Link>
)}
</div>
)
}
];
return (
<div className="rules-page">
{/* 页面头部 */}
<div className="flex justify-between items-center mb-4">
<div className="flex items-center">
<h2 className="text-xl font-medium"></h2>
{loading ? (
<div className="flex items-center ml-4 bg-white px-3 py-1 rounded-md">
<NumberSkeleton className="ml-1" />
</div>
) : (
<div className="flex items-center ml-4 bg-white px-3 py-1 rounded-md">
<i className="ri-file-list-3-line text-primary text-lg mr-1"></i>
<span className="text-sm text-secondary"></span>
<span className="text-base font-normal text-primary ml-1">{filteredTotalCount}</span>
</div>
)}
</div>
{isDeveloper && (
<Button type="primary" icon="ri-add-line" to="/rules/new" className="btn-add-rule">
</Button>
)}
</div>
{/* 筛选区域 */}
<FilterPanel className="px-3 py-3" noActionDivider={true}
actions={
<>
<Button type="default" icon="ri-refresh-line" onClick={handleReset} className="mr-2 hover:!border-gray-300">
</Button>
</>
}
>
<FilterSelect
label="评查点类型"
name="ruleType"
value={searchParams.get('ruleType') || ''}
options={[
...ruleTypes.map((type: ApiRuleType) => ({
value: type.id,
label: type.name
}))
]}
onChange={handleFilterChange}
className="mr-3 w-[15%]"
/>
<FilterSelect
label="所属规则组"
name="groupId"
value={searchParams.get('groupId') || ''}
options={[
...(isRuleGroupSelectDisabled ? [{ value: "", label: "请先选择评查点类型" }] : []),
...ruleGroups.map(group => ({
value: group.id,
label: group.name
}))
]}
onChange={handleFilterChange}
className={`mr-3 w-[20%] ${isRuleGroupSelectDisabled ? 'opacity-50' : ''}`}
/>
<FilterSelect
label="状态"
name="isActive"
value={searchParams.get('isActive') || ''}
options={[
{ value: "true", label: "启用" },
{ value: "false", label: "禁用" }
]}
onChange={handleFilterChange}
className="mr-3 w-[15%]"
/>
<SearchFilter
label="搜索"
placeholder="输入评查点名称或编码"
value={searchParams.get('keyword') || ''}
buttonText="搜索"
onSearch={handleSearch}
className="min-w-[200px] flex-1"
/>
</FilterPanel>
{/* 评查点列表 - 使用Table组件 */}
<Card className="ant-card">
{loading && <LoadingIndicator />}
<div className={loading ? "opacity-70 pointer-events-none transition-opacity" : ""}>
{loading && filteredRules.length === 0 ? (
<TableRowSkeleton count={5} />
) : (
<Table
columns={columns}
dataSource={filteredRules}
rowKey="id"
// emptyText={loading ? "正在加载数据..." : "暂无评查点数据"}
className="rules-table"
/>
)}
{/* 分页 */}
{filteredTotalCount > 0 && (
<Pagination
currentPage={currentPage}
total={filteredTotalCount}
pageSize={pageSize}
onChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
showTotal={true}
showPageSizeChanger={true}
pageSizeOptions={[10, 20, 30, 50]}
/>
)}
</div>
</Card>
</div>
);
}
// 错误边界
export function ErrorBoundary() {
return (
<div className="error-container p-6">
<h1 className="text-xl font-bold text-red-500 mb-4"></h1>
<p className="mb-4"></p>
<Button type="primary" to="/"></Button>
</div>
);
}