import React, { useState } from 'react'; import { json, type MetaFunction, type LoaderFunctionArgs, redirect } from "@remix-run/node"; import { useLoaderData, useSearchParams, useSubmit } 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 rulesStyles from "~/styles/pages/rules_index.css?url"; import type { Rule } from '~/models/rule'; import { RULE_TYPE_LABELS, RULE_TYPE_COLORS, RULE_PRIORITY_LABELS, RULE_PRIORITY_COLORS } from '~/models/rule'; import type { TagColor } from '~/components/ui/Tag'; import { Link } from '@remix-run/react'; import { Table } from '~/components/ui/Table'; import { FilterPanel, FilterSelect, SearchFilter } from '~/components/ui/FilterPanel'; import { Pagination } from '~/components/ui/Pagination'; export const links = () => [ { rel: "stylesheet", href: rulesStyles } ]; // export const handle = { // breadcrumb: "评查点列表" // }; export const meta: MetaFunction = () => { return [ { title: "中国烟草AI合同及卷宗审核系统 - 评查点列表" }, { name: "description", content: "管理评查点规则,支持根据类型、规则组和状态进行筛选" }, { name: "keywords", content: "评查点,合同审核,规则管理,中国烟草" } ]; }; interface LoaderData { rules: Rule[]; groups: { id: string; name: string; }[]; totalCount: number; currentPage: number; pageSize: number; totalPages: 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") || ""; const currentPage = parseInt(url.searchParams.get("page") || "1", 10); const pageSize = parseInt(url.searchParams.get("pageSize") || "10", 10); try { // 模拟数据,实际项目中应从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" }, { id: "6", code: "CP006", name: "交货期限有效性检查", ruleGroupId: "2", groupName: "销售合同专项检查", ruleType: "business", priority: "medium", description: "检查合同中交货期限是否明确、合理", checkMethod: "automatic", prompt: "检查合同中是否明确约定了交货期限,并且期限设置是否合理", isActive: true, createdAt: "2023-08-01 14:40", updatedAt: "2023-08-01 14:40" }, { id: "7", code: "CP007", name: "合同条款矛盾性检查", ruleGroupId: "3", groupName: "采购合同专项检查", ruleType: "legal", priority: "high", description: "检查合同条款之间是否存在矛盾或冲突", checkMethod: "mixed", prompt: "分析合同各条款,检查是否存在相互矛盾或冲突的内容", isActive: true, createdAt: "2023-08-10 09:15", updatedAt: "2023-08-10 09:15" } ]; 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) ); } // 计算分页信息 const totalCount = filteredRules.length; const totalPages = Math.ceil(totalCount / pageSize); // 验证页码范围 if (currentPage < 1 || (totalCount > 0 && currentPage > totalPages)) { // 如果页码超出范围,重定向到第一页 const newUrl = new URL(request.url); newUrl.searchParams.set('page', '1'); return redirect(newUrl.pathname + newUrl.search); } // 分页截取 const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; const paginatedRules = filteredRules.slice(startIndex, endIndex); return json({ rules: paginatedRules, groups, totalCount, currentPage, pageSize, totalPages }, { headers: { // 添加缓存控制,在生产环境中可以调整 "Cache-Control": "max-age=60, s-maxage=180" } }); } catch (error) { console.error('加载评查点列表失败:', error); throw new Response('加载评查点列表失败', { status: 500 }); } } export async function action({ request }: LoaderFunctionArgs) { const formData = await request.formData(); const _action = formData.get('_action'); const ruleId = formData.get('ruleId'); if (!ruleId) { return json({ success: false, error: "缺少评查点ID" }, { status: 400 }); } try { if (_action === 'delete') { // 实际项目中应调用API删除评查点 console.log(`删除评查点 ${ruleId}`); // 模拟API调用 // const response = await fetch(`/api/rules/${ruleId}`, { // method: 'DELETE', // }); // if (!response.ok) { // throw new Error(`删除失败: ${response.status}`); // } return json({ success: true }); } if (_action === 'duplicate') { // 实际项目中应调用API复制评查点 console.log(`复制评查点 ${ruleId}`); // 模拟API调用 // const response = await fetch(`/api/rules/${ruleId}/duplicate`, { // method: 'POST', // }); // if (!response.ok) { // throw new Error(`复制失败: ${response.status}`); // } return json({ success: true }); } return json({ success: false, error: "未知操作" }, { status: 400 }); } catch (error) { console.error('操作评查点失败:', error); return json({ success: false, error: "操作失败" }, { status: 500 }); } } export function ErrorBoundary() { return (

出错了

加载评查点列表时发生错误。请稍后再试,或联系管理员。

); } // 规则类型和优先级的描述标签映射 const typeLabels = { 'essential': '基本要素类', 'content': '内容合规类', 'legal': '法律风险类', 'format': '格式规范类', 'business': '业务专项类' }; const priorityLabels = { 'high': '高', 'medium': '中', 'low': '低' }; export default function RulesIndex() { const { rules, groups, totalCount, currentPage, pageSize } = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); const submit = useSubmit(); // 状态管理 const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [ruleToDelete, setRuleToDelete] = useState(null); const handleFilterChange = (e: React.ChangeEvent) => { const { name, value } = e.target; const newParams = new URLSearchParams(searchParams); if (value) { newParams.set(name, value); } else { newParams.delete(name); } // 切换筛选条件时,重置到第一页 newParams.set('page', '1'); 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'); setSearchParams(newParams); }; const handleDeleteClick = (rule: Rule) => { setRuleToDelete(rule); setShowDeleteConfirm(true); }; const confirmDelete = () => { if (!ruleToDelete) return; const formData = new FormData(); formData.append('_action', 'delete'); formData.append('ruleId', ruleToDelete.id); submit(formData, { method: 'post' }); setShowDeleteConfirm(false); setRuleToDelete(null); }; const handleCopy = (rule: Rule) => { const formData = new FormData(); formData.append('_action', 'duplicate'); formData.append('ruleId', rule.id); submit(formData, { method: 'post' }); }; const handlePageChange = (page: number) => { const newParams = new URLSearchParams(searchParams); newParams.set('page', page.toString()); setSearchParams(newParams); }; const handlePageSizeChange = (size: number) => { const newParams = new URLSearchParams(searchParams); newParams.set('pageSize', size.toString()); newParams.set('page', '1'); // 更改每页条数时,重置到第一页 setSearchParams(newParams); }; // 处理重置筛选 const handleReset = () => { setSearchParams(new URLSearchParams()); }; // 定义表格列配置 const columns = [ { title: "评查点编码", dataIndex: "code" as keyof Rule, key: "code", align: "center" as const }, { title: "评查点名称", dataIndex: "name" as keyof Rule, key: "name", align: "center" as const }, { title: "评查点类型", key: "ruleType", align: "center" as const, render: (_: unknown, record: Rule) => { const typeColor = RULE_TYPE_COLORS[record.ruleType] as TagColor; return ( {typeLabels[record.ruleType as keyof typeof typeLabels] || RULE_TYPE_LABELS[record.ruleType]} ); } }, { title: "所属规则组", dataIndex: "groupName" as keyof Rule, key: "groupName", align: "center" as const }, { title: "优先级", key: "priority", align: "center" as const, render: (_: unknown, record: Rule) => { const priorityColor = RULE_PRIORITY_COLORS[record.priority] as TagColor; return ( {priorityLabels[record.priority as keyof typeof priorityLabels] || RULE_PRIORITY_LABELS[record.priority]} ); } }, { title: "状态", key: "isActive", align: "center" as const, className: "status-column", render: (_: unknown, record: Rule) => ( ) }, { title: "创建时间", dataIndex: "createdAt" as keyof Rule, key: "createdAt", align: "center" as const }, { title: "操作", key: "operation", align: "center" as const, render: (_: unknown, record: Rule) => (
编辑
) } ]; return (
{/* 页面头部 */}

评查点管理

{/* 筛选区域 */} ({ value: group.id, label: group.name }))} onChange={handleFilterChange} className="mr-3 w-80" /> {/* 评查点列表 - 使用Table组件 */} {/* 分页 */} {totalCount > 0 && ( )} {/* 删除确认对话框 */} {showDeleteConfirm && ruleToDelete && (

确认删除

确定要删除评查点“{ruleToDelete.name}”吗?

)} ); }