Files
leaudit-platform-frontend/app/routes/rules._index.tsx
T

351 lines
13 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 from 'react';
import { json, type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, useSearchParams } from "@remix-run/react";
import { Button } from '~/components/ui/Button';
import { Card } from '~/components/ui/Card';
import { Tag, type TagColor } from '~/components/ui/Tag';
import type { Rule } from '~/models/rule';
import { RULE_TYPE_LABELS, RULE_TYPE_COLORS, RULE_PRIORITY_LABELS, RULE_PRIORITY_COLORS } from '~/models/rule';
export const links = () => [
{ rel: "stylesheet", href: "/rules_index.css" }
];
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 评查点列表" },
{ name: "description", content: "评查点管理列表" }
];
};
interface LoaderData {
rules: Rule[];
groups: {
id: string;
name: string;
}[];
totalCount: number;
}
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const ruleType = url.searchParams.get("ruleType") || "";
const groupId = url.searchParams.get("groupId") || "";
const isActive = url.searchParams.get("isActive") || "";
const keyword = url.searchParams.get("keyword") || "";
// 模拟数据,实际项目中应从API获取
const rules: Rule[] = [
{
id: "1",
code: "CP001",
name: "合同主体信息完整性检查",
ruleGroupId: "1",
groupName: "合同基本要素检查",
ruleType: "essential",
priority: "high",
description: "检查合同中是否完整包含签约方的基本信息,包括名称、地址、法定代表人等",
checkMethod: "automatic",
prompt: "检查合同主体双方信息是否完整,包括企业名称、注册地址、法定代表人或授权代表、联系方式等",
isActive: true,
createdAt: "2023-06-15 10:30",
updatedAt: "2023-06-15 10:30"
},
{
id: "2",
code: "CP002",
name: "合同金额一致性校验",
ruleGroupId: "1",
groupName: "合同基本要素检查",
ruleType: "content",
priority: "high",
description: "检查合同大小写金额是否一致",
checkMethod: "automatic",
prompt: "检查合同中的金额大写和小写表示是否一致,如¥10,000.00(壹万元整)",
isActive: true,
createdAt: "2023-06-20 14:15",
updatedAt: "2023-06-20 14:15"
},
{
id: "3",
code: "CP003",
name: "保密条款合规性审核",
ruleGroupId: "1",
groupName: "合同基本要素检查",
ruleType: "legal",
priority: "medium",
description: "检查合同是否包含保密条款并符合行业要求",
checkMethod: "mixed",
prompt: "检查合同中的保密条款是否完整、清晰,包含保密范围、期限、违约责任等",
isActive: true,
createdAt: "2023-07-05 09:45",
updatedAt: "2023-07-05 09:45"
},
{
id: "4",
code: "CP004",
name: "合同签约日期格式检查",
ruleGroupId: "1",
groupName: "合同基本要素检查",
ruleType: "format",
priority: "low",
description: "检查合同签约日期格式是否规范",
checkMethod: "automatic",
prompt: "检查合同签约日期格式是否符合YYYY年MM月DD日的规范格式",
isActive: false,
createdAt: "2023-07-10 16:20",
updatedAt: "2023-07-10 16:20"
},
{
id: "5",
code: "CP005",
name: "违约责任条款完整性检查",
ruleGroupId: "2",
groupName: "销售合同专项检查",
ruleType: "legal",
priority: "high",
description: "检查合同违约责任条款是否明确、完整",
checkMethod: "mixed",
prompt: "检查合同中的违约责任条款是否包含违约情形、违约金计算方式、责任承担方式等内容",
isActive: true,
createdAt: "2023-07-15 11:30",
updatedAt: "2023-07-15 11:30"
}
];
const groups = [
{ id: "1", name: "合同基本要素检查" },
{ id: "2", name: "销售合同专项检查" },
{ id: "3", name: "采购合同专项检查" },
{ id: "4", name: "专卖许可证审核规则" },
{ id: "5", name: "行政处罚规范性检查" }
];
// 过滤数据
let filteredRules = [...rules];
if (ruleType) {
filteredRules = filteredRules.filter(rule => rule.ruleType === ruleType);
}
if (groupId) {
filteredRules = filteredRules.filter(rule => rule.ruleGroupId === groupId);
}
if (isActive) {
const activeValue = isActive === 'true';
filteredRules = filteredRules.filter(rule => rule.isActive === activeValue);
}
if (keyword) {
const lowerKeyword = keyword.toLowerCase();
filteredRules = filteredRules.filter(rule =>
rule.name.toLowerCase().includes(lowerKeyword) ||
rule.code.toLowerCase().includes(lowerKeyword)
);
}
return json<LoaderData>({
rules: filteredRules,
groups,
totalCount: rules.length
});
}
export default function RulesList() {
const { rules, groups } = useLoaderData<typeof loader>();
const [searchParams, setSearchParams] = useSearchParams();
const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>) => {
const { name, value } = e.target;
const newParams = new URLSearchParams(searchParams);
if (value) {
newParams.set(name, value);
} else {
newParams.delete(name);
}
setSearchParams(newParams);
};
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const keyword = formData.get('keyword') as string;
const newParams = new URLSearchParams(searchParams);
if (keyword) {
newParams.set('keyword', keyword);
} else {
newParams.delete('keyword');
}
setSearchParams(newParams);
};
const handleCopy = (rule: Rule) => {
// 实际项目中应调用API复制规则
alert(`复制规则: ${rule.name}`);
};
const handleDelete = (rule: Rule) => {
// 实际项目中应调用API删除规则
if (window.confirm(`确定要删除评查点"${rule.name}"吗?`)) {
alert(`删除规则: ${rule.name}`);
}
};
return (
<div className="p-6">
{/* 页面标识 */}
<div className="mb-4 p-3 bg-blue-100 border border-blue-300 rounded text-blue-800">
<h3 className="font-bold text-lg">当前页面: 评查点列表 (rules._index.tsx)</h3>
<p></p>
<div className="mt-2">
<a href="/debug" className="text-blue-600 hover:underline"></a> |
<a href="/" className="ml-2 text-blue-600 hover:underline"></a>
</div>
</div>
{/* 页面头部 */}
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-medium"></h2>
<Button type="primary" icon="ri-add-line" to="/rules/new">
</Button>
</div>
{/* 筛选区域 */}
<Card className="mb-4" noDivider>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 p-4">
<div>
<label htmlFor="ruleType" className="block text-sm mb-1"></label>
<select
id="ruleType"
className="form-select w-full rounded border-gray-300 shadow-sm"
name="ruleType"
value={searchParams.get('ruleType') || ''}
onChange={handleFilterChange}
>
<option value=""></option>
{Object.entries(RULE_TYPE_LABELS).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))}
</select>
</div>
<div>
<label htmlFor="groupId" className="block text-sm mb-1"></label>
<select
id="groupId"
className="form-select w-full rounded border-gray-300 shadow-sm"
name="groupId"
value={searchParams.get('groupId') || ''}
onChange={handleFilterChange}
>
<option value=""></option>
{groups.map((group) => (
<option key={group.id} value={group.id}>{group.name}</option>
))}
</select>
</div>
<div>
<label htmlFor="isActive" className="block text-sm mb-1"></label>
<select
id="isActive"
className="form-select w-full rounded border-gray-300 shadow-sm"
name="isActive"
value={searchParams.get('isActive') || ''}
onChange={handleFilterChange}
>
<option value=""></option>
<option value="true"></option>
<option value="false"></option>
</select>
</div>
<div>
<label htmlFor="keyword" className="block text-sm mb-1"></label>
<form onSubmit={handleSearch} className="flex items-center">
<input
type="text"
id="keyword"
name="keyword"
className="form-input rounded-l flex-1 border-gray-300 shadow-sm"
placeholder="输入评查点名称或编码"
defaultValue={searchParams.get('keyword') || ''}
/>
<Button type="primary" icon="ri-search-line" className="rounded-l-none">
</Button>
</form>
</div>
</div>
</Card>
{/* 评查点列表 */}
<Card noDivider>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead className="bg-gray-50">
<tr className="text-xs text-gray-500 border-b">
<th className="py-3 px-4"></th>
<th className="py-3 px-4"></th>
<th className="py-3 px-4"></th>
<th className="py-3 px-4"></th>
<th className="py-3 px-4"></th>
<th className="py-3 px-4"></th>
<th className="py-3 px-4"></th>
<th className="py-3 px-4"></th>
</tr>
</thead>
<tbody>
{rules.map((rule) => {
const typeColor = RULE_TYPE_COLORS[rule.ruleType] as TagColor;
const priorityColor = RULE_PRIORITY_COLORS[rule.priority] as TagColor;
return (
<tr key={rule.id} className="border-b hover:bg-gray-50">
<td className="py-3 px-4">{rule.code}</td>
<td className="py-3 px-4">{rule.name}</td>
<td className="py-3 px-4">
<Tag color={typeColor}>{RULE_TYPE_LABELS[rule.ruleType]}</Tag>
</td>
<td className="py-3 px-4">{rule.groupName}</td>
<td className="py-3 px-4">
<Tag color={priorityColor}>{RULE_PRIORITY_LABELS[rule.priority]}</Tag>
</td>
<td className="py-3 px-4">
{rule.isActive ? (
<span className="flex items-center">
<i className="inline-block w-2 h-2 rounded-full bg-green-500 mr-2"></i>
</span>
) : (
<span className="flex items-center">
<i className="inline-block w-2 h-2 rounded-full bg-gray-400 mr-2"></i>
</span>
)}
</td>
<td className="py-3 px-4">{rule.createdAt}</td>
<td className="py-3 px-4">
<Button type="default" size="small" icon="ri-edit-line" to={`/rules/${rule.id}`} className="mr-1">
</Button>
<Button type="default" size="small" icon="ri-file-copy-line" className="mr-1" onClick={() => handleCopy(rule)}>
</Button>
<Button type="danger" size="small" icon="ri-delete-bin-line" onClick={() => handleDelete(rule)}>
</Button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</Card>
</div>
);
}