Files
leaudit-platform-frontend/app/routes/rulesTest.list.tsx
T

261 lines
8.2 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 LoaderFunctionArgs, type MetaFunction } from '@remix-run/node';
import { Link, useLoaderData } from '@remix-run/react';
import { Card } from '~/components/ui/Card';
import { Table } from '~/components/ui/Table';
import { Tag, type TagColor } from '~/components/ui/Tag';
import { loadRuleYamlPacks, type RuleSummary, type RuleYamlPack } from '~/utils/rules-yaml-mock.server';
import styles from '~/styles/pages/rules_test.css?url';
export const links = () => [
{ rel: 'stylesheet', href: styles }
];
export const meta: MetaFunction = () => [
{ title: '规则 YAML 列表 - 智慧法务' }
];
type RuleRow = RuleSummary & {
rowId: string;
packId: string;
documentType: string;
moduleType: string;
mainType: string;
subtype: string;
yamlName: string;
yamlStatus: RuleYamlPack['sourceStatus'];
};
type LoaderData = {
rows: RuleRow[];
packs: RuleYamlPack[];
filters: {
documentType: string;
mainType: string;
subtype: string;
ruleGroup: string;
keyword: string;
};
options: {
documentTypes: string[];
mainTypes: string[];
subtypes: string[];
ruleGroups: string[];
};
};
function unique(values: string[]): string[] {
return Array.from(new Set(values.filter(Boolean)));
}
function riskColor(risk: string): TagColor {
if (risk === 'high') return 'red';
if (risk === 'medium') return 'orange';
if (risk === 'low') return 'green';
return 'gray';
}
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const requestedMainType = url.searchParams.get('mainType') || url.searchParams.get('ruleTypeName') || '';
const requestedSubtype = url.searchParams.get('subtype') || url.searchParams.get('documentAttributeType') || '';
const requestedRuleGroup = url.searchParams.get('ruleGroup') || url.searchParams.getAll('ruleGroups')[0] || '';
const requestedFilters = {
documentType: url.searchParams.get('documentType') || '',
mainType: requestedMainType,
subtype: requestedSubtype,
ruleGroup: requestedRuleGroup,
keyword: url.searchParams.get('keyword') || ''
};
const packs = await loadRuleYamlPacks();
const documentTypes = unique(packs.map(pack => pack.documentType));
const inferredDocumentType = packs.find(pack => pack.mainType === requestedFilters.mainType)?.documentType || '';
const currentDocumentType = documentTypes.includes(requestedFilters.documentType)
? requestedFilters.documentType
: inferredDocumentType || documentTypes[0] || '';
const scopedFilters = {
...requestedFilters,
documentType: currentDocumentType,
mainType: packs.some(pack => pack.documentType === currentDocumentType && pack.mainType === requestedFilters.mainType)
? requestedFilters.mainType
: '',
subtype: packs.some(pack =>
pack.documentType === currentDocumentType &&
(!requestedFilters.mainType || pack.mainType === requestedFilters.mainType) &&
pack.subtype === requestedFilters.subtype
)
? requestedFilters.subtype
: ''
};
const scopedPacks = packs.filter(pack =>
pack.documentType === scopedFilters.documentType &&
(!scopedFilters.mainType || pack.mainType === scopedFilters.mainType) &&
(!scopedFilters.subtype || pack.subtype === scopedFilters.subtype)
);
const ruleGroupOptions = unique(scopedPacks.flatMap(pack => pack.rules.map(rule => rule.group))).sort((a, b) => a.localeCompare(b, 'zh-CN'));
const filters = {
...scopedFilters,
ruleGroup: ruleGroupOptions.includes(requestedFilters.ruleGroup) ? requestedFilters.ruleGroup : ''
};
const visiblePacks = packs.filter(pack =>
pack.documentType === filters.documentType &&
(!filters.mainType || pack.mainType === filters.mainType) &&
(!filters.subtype || pack.subtype === filters.subtype)
);
const rows: RuleRow[] = visiblePacks.flatMap((pack): RuleRow[] => {
if (pack.rules.length === 0) {
return [{
rowId: `${pack.id}-empty`,
packId: pack.id,
documentType: pack.documentType,
moduleType: pack.moduleType,
mainType: pack.mainType,
subtype: pack.subtype,
yamlName: pack.metadata.name || '待配置 YAML',
yamlStatus: pack.sourceStatus,
id: `${pack.id}-empty`,
ruleId: '-',
name: '暂无规则配置',
group: '待配置',
risk: '-',
score: '-',
type: '-',
checkTypes: [],
logic: '',
subRules: [],
subRuleIds: [],
scope: [],
dependencies: [],
stageCount: 0,
appliesIn: [],
prompt: '',
description: '当前文档类型已保留规则列表与 YAML 配置页流程,等待后续接入规则文件。'
}];
}
return pack.rules.map(rule => ({
...rule,
rowId: `${pack.id}-${rule.ruleId || rule.id}`,
packId: pack.id,
documentType: pack.documentType,
moduleType: pack.moduleType,
mainType: pack.mainType,
subtype: pack.subtype,
yamlName: pack.metadata.name,
yamlStatus: pack.sourceStatus
}));
}).filter(row => {
if (filters.ruleGroup && row.group !== filters.ruleGroup) {
return false;
}
if (!filters.keyword) return true;
return [row.ruleId, row.name, row.group, row.yamlName, row.subtype]
.some(value => value.toLowerCase().includes(filters.keyword.toLowerCase()));
});
return Response.json({
rows,
packs,
filters,
options: {
documentTypes,
mainTypes: unique(packs.filter(pack => pack.documentType === filters.documentType).map(pack => pack.mainType)),
subtypes: unique(packs.filter(pack =>
pack.documentType === filters.documentType &&
(!filters.mainType || pack.mainType === filters.mainType)
).map(pack => pack.subtype)),
ruleGroups: ruleGroupOptions
}
} satisfies LoaderData);
}
export default function RulesTestList() {
const { rows } = useLoaderData<typeof loader>() as LoaderData;
const columns = [
{
title: '规则',
key: 'rule',
width: '24%',
render: (_: unknown, record: RuleRow) => (
<div className="rule-name">
<strong>{record.name}</strong>
<span>{record.ruleId}</span>
</div>
)
},
{
title: '子分类',
key: 'subtype',
width: '12%',
align: 'center' as const,
render: (_: unknown, record: RuleRow) => (
<div className="inline-tags">
<Tag color="blue" size="sm">{record.subtype}</Tag>
</div>
)
},
{
title: '规则组',
dataIndex: 'group' as keyof RuleRow,
key: 'group',
width: '14%',
align: 'center' as const
},
{
title: '风险',
key: 'risk',
width: '8%',
align: 'center' as const,
render: (_: unknown, record: RuleRow) => (
<Tag color={riskColor(record.risk)} size="sm">{record.risk}</Tag>
)
},
{
title: '分值',
key: 'score',
width: '8%',
align: 'center' as const,
render: (_: unknown, record: RuleRow) => (
<Tag color="gray" size="sm">{record.score}</Tag>
)
},
{
title: '依赖字段',
key: 'dependencies',
width: '20%',
render: (_: unknown, record: RuleRow) => (
<span>{record.dependencies.length > 0 ? record.dependencies.slice(0, 3).join('、') : '-'}</span>
)
},
{
title: '操作',
key: 'operation',
width: '14%',
align: 'center' as const,
render: (_: unknown, record: RuleRow) => (
<Link className="operation-btn" to={`/rulesTest/detail?packId=${encodeURIComponent(record.packId)}&ruleId=${encodeURIComponent(record.ruleId || record.id)}`}>
<i className="ri-settings-3-line"></i>
</Link>
)
}
];
return (
<div className="rules-test-page rules-page">
<div className="page-shell">
<Card className="ant-card">
<Table
className="rules-test-table rules-table"
columns={columns}
dataSource={rows}
rowKey="rowId"
emptyText={<div className="empty-state"> YAML </div>}
/>
</Card>
</div>
</div>
);
}