保存规则库 YAML 维护改造进展

This commit is contained in:
2026-04-28 22:00:00 +08:00
parent 7b86293263
commit dce5ac0c9a
96 changed files with 36801 additions and 615 deletions
+260
View File
@@ -0,0 +1,260 @@
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>
);
}