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

384 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 { Link } from "@remix-run/react";
import { json, type MetaFunction } from "@remix-run/node";
import { useLoaderData, useNavigate } from "@remix-run/react";
import { Button } from "~/components/ui/Button";
// import stylesUrl from "~/styles/pages/rule-groups.css";
// 引入CSS
// export function links() {
// return [
// { rel: "stylesheet", href: stylesUrl }
// ];
// }
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 评查点分组列表" },
{ name: "description", content: "评查点分组管理" }
];
};
// 分组接口定义
interface RuleGroup {
id: string;
name: string;
code: string;
ruleCount: number;
childGroupCount: number;
isActive: boolean;
parentId: string | null;
createdAt: string;
level: 1 | 2; // 1-一级分组,2-二级分组
}
interface LoaderData {
groups: RuleGroup[];
}
export async function loader() {
// 模拟数据,实际项目中应该从API获取
const groups: RuleGroup[] = [
{
id: "1",
name: "合同基本要素检查",
code: "contract-base",
ruleCount: 18,
childGroupCount: 2,
isActive: true,
parentId: null,
createdAt: "2023-10-01 14:30",
level: 1
},
{
id: "2",
name: "必备要素检查",
code: "essential-elements",
ruleCount: 7,
childGroupCount: 0,
isActive: true,
parentId: "1",
createdAt: "2023-10-02 10:15",
level: 2
},
{
id: "3",
name: "合同主体检查",
code: "contract-parties",
ruleCount: 5,
childGroupCount: 0,
isActive: true,
parentId: "1",
createdAt: "2023-10-02 11:40",
level: 2
},
{
id: "4",
name: "销售合同专项检查",
code: "sales-contract",
ruleCount: 10,
childGroupCount: 2,
isActive: true,
parentId: null,
createdAt: "2023-10-03 09:20",
level: 1
},
{
id: "5",
name: "交付条款检查",
code: "delivery-terms",
ruleCount: 4,
childGroupCount: 0,
isActive: true,
parentId: "4",
createdAt: "2023-10-03 14:30",
level: 2
},
{
id: "6",
name: "付款条款检查",
code: "payment-terms",
ruleCount: 6,
childGroupCount: 0,
isActive: true,
parentId: "4",
createdAt: "2023-10-03 15:45",
level: 2
},
{
id: "7",
name: "采购合同专项检查",
code: "purchase-contract",
ruleCount: 8,
childGroupCount: 0,
isActive: true,
parentId: null,
createdAt: "2023-10-04 10:15",
level: 1
},
{
id: "8",
name: "行政处罚规范性检查",
code: "admin-punishment",
ruleCount: 12,
childGroupCount: 0,
isActive: false,
parentId: null,
createdAt: "2023-10-05 16:30",
level: 1
}
];
return json<LoaderData>({ groups });
}
export default function RuleGroupsPage() {
const { groups } = useLoaderData<typeof loader>();
const navigate = useNavigate();
// 过滤出父级分组和子级分组
const parentGroups = groups.filter(group => group.parentId === null);
// 根据父级ID获取子分组
const getChildGroups = (parentId: string) => {
return groups.filter(group => group.parentId === parentId);
};
// 模拟删除操作
const handleDelete = (id: string) => {
if (window.confirm("确定要删除该分组吗?删除后无法恢复,且会删除该分组下的所有评查点。")) {
alert(`删除分组: ${id}`);
}
};
// 创建新分组
const handleCreate = () => {
navigate("/rule-groups/new");
};
// 展开/收起状态(实际项目中可以使用useState管理)
const toggleExpand = (groupId: string) => {
const childRows = document.querySelectorAll(`.child-of-${groupId}`);
childRows.forEach(row => {
(row as HTMLElement).style.display =
(row as HTMLElement).style.display === "none" ? "table-row" : "none";
});
// 切换图标
const icon = document.querySelector(`span.expand-icon[data-group-id="${groupId}"] i`);
if (icon) {
icon.classList.toggle("ri-arrow-down-s-line");
icon.classList.toggle("ri-arrow-right-s-line");
}
};
// 键盘处理器
const handleKeyDown = (e: React.KeyboardEvent, groupId: string) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleExpand(groupId);
}
};
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">当前页面: 评查点分组列表 (rule-groups._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>
<div className="flex">
<Button type="default" className="mr-2" icon="ri-arrow-down-s-line"
onClick={() => document.querySelectorAll(".child-row").forEach(row => (row as HTMLElement).style.display = "table-row")}>
</Button>
<Button type="default" className="mr-2" icon="ri-arrow-up-s-line"
onClick={() => document.querySelectorAll(".child-row").forEach(row => (row as HTMLElement).style.display = "none")}>
</Button>
<Button type="primary" icon="ri-add-line" onClick={handleCreate}>
</Button>
</div>
</div>
{/* 搜索栏 */}
<div className="card mb-4">
<div className="card-body">
<div className="flex flex-wrap items-end gap-4">
<div className="flex-1 min-w-[200px]">
<label htmlFor="groupName" className="form-label"></label>
<input type="text" id="groupName" className="form-input" placeholder="请输入分组名称" />
</div>
<div className="flex-1 min-w-[200px]">
<label htmlFor="groupCode" className="form-label"></label>
<input type="text" id="groupCode" className="form-input" placeholder="请输入分组编码" />
</div>
<div className="flex-1 min-w-[200px]">
<label htmlFor="groupStatus" className="form-label"></label>
<select id="groupStatus" className="form-select">
<option value=""></option>
<option value="true"></option>
<option value="false"></option>
</select>
</div>
<div className="flex items-center">
<Button type="default" className="mr-2" icon="ri-refresh-line">
</Button>
<Button type="primary" icon="ri-search-line">
</Button>
</div>
</div>
</div>
</div>
{/* 数据表格 */}
<div className="card">
<div className="card-body">
<div className="overflow-x-auto">
<table className="table tree-table">
<thead>
<tr>
<th style={{ width: "400px" }}></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th style={{ width: "180px" }}></th>
</tr>
</thead>
<tbody>
{parentGroups.map(parent => (
<React.Fragment key={parent.id}>
{/* 一级分组 */}
<tr className="group-row parent-row" data-group-id={parent.id}>
<td>
<div className="flex items-center">
<span
className="expand-icon"
data-group-id={parent.id}
onClick={() => toggleExpand(parent.id)}
onKeyDown={(e) => handleKeyDown(e, parent.id)}
role="button"
tabIndex={0}
aria-label="展开/收起"
>
<i className="ri-arrow-down-s-line text-primary"></i>
</span>
<Link
to={`/rules?groupId=${parent.id}`}
className="text-primary hover:underline flex items-center ml-1"
>
<i className="ri-folder-line mr-1"></i> {parent.name}
</Link>
<span className="group-badge parent-badge"></span>
</div>
</td>
<td>{parent.code}</td>
<td>
<Link to={`/rules?groupId=${parent.id}`} className="badge bg-primary text-white">
{parent.ruleCount}
</Link>
{parent.childGroupCount > 0 && (
<span className="text-secondary text-sm ml-1">
| : {parent.childGroupCount}
</span>
)}
</td>
<td>
<span className={`status-dot ${parent.isActive ? 'status-success' : 'status-error'}`}></span>
{parent.isActive ? '启用' : '禁用'}
</td>
<td>{parent.createdAt}</td>
<td className="py-3 px-2 text-center">
<Button
type="default"
size="small"
className="text-primary mr-2"
icon="ri-edit-line"
onClick={() => navigate(`/rule-groups/${parent.id}/edit`)}
>
</Button>
<Button
type="danger"
size="small"
icon="ri-delete-bin-line"
onClick={() => handleDelete(parent.id)}
>
</Button>
</td>
</tr>
{/* 二级分组 */}
{getChildGroups(parent.id).map(child => (
<tr
key={child.id}
className={`group-row child-row child-of-${parent.id}`}
data-parent-id={parent.id}
data-group-id={child.id}
>
<td>
<div className="flex items-center ml-8">
<Link
to={`/rules?groupId=${child.id}`}
className="text-primary hover:underline flex items-center"
>
<i className="ri-file-list-line mr-1"></i> {child.name}
</Link>
<span className="group-badge child-badge"></span>
</div>
</td>
<td>{child.code}</td>
<td>
<Link to={`/rules?groupId=${child.id}`} className="badge bg-primary text-white">
{child.ruleCount}
</Link>
</td>
<td>
<span className={`status-dot ${child.isActive ? 'status-success' : 'status-error'}`}></span>
{child.isActive ? '启用' : '禁用'}
</td>
<td>{child.createdAt}</td>
<td className="py-3 px-2 text-center">
<Button
type="default"
size="small"
className="text-primary mr-2"
icon="ri-edit-line"
onClick={() => navigate(`/rule-groups/${child.id}/edit`)}
>
</Button>
<Button
type="danger"
size="small"
icon="ri-delete-bin-line"
onClick={() => handleDelete(child.id)}
>
</Button>
</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}