import { type MetaFunction } from "@remix-run/node"; import { useLoaderData, Link, useNavigate, useSearchParams, useRouteLoaderData } from "@remix-run/react"; import { useState, useEffect } from "react"; import indexStyles from "~/styles/pages/rule-groups_index.css?url"; import { Card } from "~/components/ui/Card"; import { Button } from "~/components/ui/Button"; import { StatusDot } from "~/components/ui/StatusDot"; import { Table } from "~/components/ui/Table"; import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel"; // import { Pagination } from "~/components/ui/Pagination"; import { getRuleGroups, getChildGroups, type RuleGroup, deleteRuleGroup } from "~/api/evaluation_points/rule-groups"; export function links() { return [{ rel: "stylesheet", href: indexStyles }]; } export const meta: MetaFunction = () => { return [ { title: "评查点分组 - 中国烟草AI合同及卷宗审核系统" }, { name: "description", content: "管理评查点分组,包括创建、编辑和删除分组" }, ]; }; export async function loader() { try { const response = await getRuleGroups(); if (response.error) { throw new Error(response.error); } return Response.json({ groups: response.data }); } catch (error) { console.error('加载评查点分组失败:', error); return Response.json({ groups: [] }); } } export default function RuleGroupsIndex() { const { groups: initialGroups } = useLoaderData(); const rootData = useRouteLoaderData("root") as { userRole: string }; const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const [expandedGroups, setExpandedGroups] = useState([]); const [groups, setGroups] = useState(initialGroups || []); const [loading, setLoading] = useState>({}); const [filteredChildrenMap, setFilteredChildrenMap] = useState>({}); const [initialLoading, setInitialLoading] = useState(true); const userRole = rootData?.userRole || 'common'; // 初始加载时自动加载所有子分组 useEffect(() => { const loadAllChildGroups = async () => { if (!initialGroups || initialGroups.length === 0) { setInitialLoading(false); return; } try { // 创建一个加载状态对象,标记所有父分组正在加载 const loadingState: Record = {}; initialGroups.forEach((group: RuleGroup) => { loadingState[group.id] = true; }); setLoading(loadingState); // 并行加载所有父分组的子分组 const promises = initialGroups.map(async (group: RuleGroup) => { try { const response = await getChildGroups(group.id); if (response.error) { console.error(`加载分组 ${group.id} 的子分组失败:`, response.error); return { parentId: group.id, children: [] }; } return { parentId: group.id, children: response.data }; } catch (error) { console.error(`加载分组 ${group.id} 的子分组出错:`, error); return { parentId: group.id, children: [] }; } }); const results = await Promise.all(promises); // 更新分组数据 setGroups(prev => { const newGroups = [...prev]; results.forEach(({ parentId, children }) => { const parentIndex = newGroups.findIndex(g => g.id === parentId); if (parentIndex !== -1) { newGroups[parentIndex] = { ...newGroups[parentIndex], children }; } }); return newGroups; }); // 展开所有父分组 setExpandedGroups(initialGroups.map((group: RuleGroup) => group.id)); } catch (error) { console.error('自动加载所有子分组失败:', error); } finally { // 清除所有加载状态 setLoading({}); setInitialLoading(false); } }; loadAllChildGroups(); }, [initialGroups]); // 处理展开/收起 const toggleGroup = async (groupId: string) => { if (expandedGroups.includes(groupId)) { // 收起分组 setExpandedGroups(prev => prev.filter(id => id !== groupId)); return; } // 展开分组 setLoading(prev => ({ ...prev, [groupId]: true })); try { const parentGroup = groups.find(g => g.id === groupId); // 如果已经加载过子分组,直接展开 if (parentGroup && parentGroup.children && parentGroup.children.length > 0) { setExpandedGroups(prev => [...prev, groupId]); // 应用现有的过滤条件到已加载的子分组 const nameFilter = searchParams.get('name') || ''; const codeFilter = searchParams.get('code') || ''; const statusFilter = searchParams.get('is_enabled') || ''; if ((nameFilter || codeFilter || statusFilter) && parentGroup.children) { applyFiltersToChildren(groupId, parentGroup.children); } setLoading(prev => ({ ...prev, [groupId]: false })); return; } // 否则加载子分组 const response = await getChildGroups(groupId); if (response.error) { throw new Error(response.error); } // 更新分组数据 setGroups(prev => prev.map(group => { if (group.id === groupId) { return { ...group, children: response.data }; } return group; })); setExpandedGroups(prev => [...prev, groupId]); // 应用现有的过滤条件到新加载的子分组 const nameFilter = searchParams.get('name') || ''; const codeFilter = searchParams.get('code') || ''; const statusFilter = searchParams.get('is_enabled') || ''; if ((nameFilter || codeFilter || statusFilter) && response.data) { applyFiltersToChildren(groupId, response.data); } } catch (error) { console.error('加载子分组失败:', error); } finally { setLoading(prev => ({ ...prev, [groupId]: false })); } }; // 展开/收起全部 const toggleAll = async (expand: boolean) => { if (expand) { // 展开所有分组 const expandedIds = groups.map(g => g.id); setExpandedGroups(expandedIds); } else { setExpandedGroups([]); } }; // 处理删除分组 const handleDeleteGroup = async (groupId: string) => { if (confirm("确定要删除该分组吗?此操作将同时删除该分组下的所有评查点,且不可恢复。")) { try { const result = await deleteRuleGroup(groupId); if (result.success) { // 从本地状态中移除被删除的分组 setGroups(prev => { // 如果是一级分组,直接过滤掉 const filteredGroups = prev.filter(g => g.id !== groupId); // 如果是二级分组,需要从父分组的 children 中移除 return filteredGroups.map(group => { if (group.children) { return { ...group, children: group.children.filter(child => child.id !== groupId) }; } return group; }); }); // 如果被删除的分组当前是展开状态,从展开列表中移除 setExpandedGroups(prev => prev.filter(id => id !== groupId)); // 显示成功消息 alert('删除成功'); } else { alert(`删除失败: ${result.error}`); } } catch (error) { console.error('删除分组失败:', error); alert('删除分组失败,请稍后重试'); } } }; // 处理搜索名称 const handleNameSearch = (value: string) => { const newParams = new URLSearchParams(searchParams); if (value) { newParams.set('name', value); } else { newParams.delete('name'); } newParams.set('page', '1'); setSearchParams(newParams); }; // 处理搜索编码 const handleCodeSearch = (value: string) => { const newParams = new URLSearchParams(searchParams); if (value) { newParams.set('code', value); } else { newParams.delete('code'); } newParams.set('page', '1'); setSearchParams(newParams); }; // 处理状态筛选变更 const handleStatusChange = (e: React.ChangeEvent) => { const { value } = e.target; const newParams = new URLSearchParams(searchParams); if (value) { newParams.set('is_enabled', value); } else { newParams.delete('is_enabled'); } newParams.set('page', '1'); setSearchParams(newParams); }; // 处理重置筛选 const handleReset = () => { setSearchParams(new URLSearchParams()); setFilteredChildrenMap({}); // 清空输入框内容(通过DOM操作) const nameInput = document.querySelector('input[placeholder="请输入分组名称"]') as HTMLInputElement; const codeInput = document.querySelector('input[placeholder="请输入分组编码"]') as HTMLInputElement; const statusSelect = document.querySelector('select[name="is_enabled"]') as HTMLSelectElement; if (nameInput) nameInput.value = ''; if (codeInput) codeInput.value = ''; if (statusSelect) statusSelect.value = ''; }; // 应用筛选条件到子分组 const applyFiltersToChildren = (parentId: string, children: RuleGroup[]) => { const nameFilter = searchParams.get('name') || ''; const codeFilter = searchParams.get('code') || ''; const statusFilter = searchParams.get('is_enabled') || ''; if (!nameFilter && !codeFilter && !statusFilter) { setFilteredChildrenMap(prev => ({...prev, [parentId]: []})); return; } const filteredChildren = children.filter(child => { // 筛选名称 if (nameFilter && !child.name.toLowerCase().includes(nameFilter.toLowerCase())) { return false; } // 筛选编码 if (codeFilter && (!child.code || !child.code.toLowerCase().includes(codeFilter.toLowerCase()))) { return false; } // 筛选状态 if (statusFilter) { const isEnabled = statusFilter === 'true'; if (child.is_enabled !== isEnabled) { return false; } } return true; }); setFilteredChildrenMap(prev => ({...prev, [parentId]: filteredChildren})); }; // 当筛选条件变化时,重新计算过滤结果 useEffect(() => { const nameFilter = searchParams.get('name') || ''; const codeFilter = searchParams.get('code') || ''; const statusFilter = searchParams.get('is_enabled') || ''; if (!nameFilter && !codeFilter && !statusFilter) { setFilteredChildrenMap({}); return; } // 检查所有已加载的子分组 groups.forEach(group => { if (group.children && group.children.length > 0) { applyFiltersToChildren(group.id, group.children); } }); // 查找匹配的子分组,自动展开它们的父级 const parentsToExpand: string[] = []; groups.forEach(group => { if (group.children && group.children.length > 0) { const hasMatchingChild = group.children.some(child => { const nameMatch = !nameFilter || child.name.toLowerCase().includes(nameFilter.toLowerCase()); const codeMatch = !codeFilter || (child.code && child.code.toLowerCase().includes(codeFilter.toLowerCase())); const statusMatch = !statusFilter || (statusFilter === 'true' ? child.is_enabled : !child.is_enabled); return nameMatch && codeMatch && statusMatch; }); if (hasMatchingChild && !expandedGroups.includes(group.id)) { parentsToExpand.push(group.id); } } }); if (parentsToExpand.length > 0) { setExpandedGroups(prev => [...prev, ...parentsToExpand]); } }, [searchParams, groups]); // 检查父级分组是否匹配筛选条件 const parentMatchesFilter = (group: RuleGroup): boolean => { const nameFilter = searchParams.get('name') || ''; const codeFilter = searchParams.get('code') || ''; const statusFilter = searchParams.get('is_enabled') || ''; // 筛选名称 if (nameFilter && !group.name.toLowerCase().includes(nameFilter.toLowerCase())) { return false; } // 筛选编码 if (codeFilter && (!group.code || !group.code.toLowerCase().includes(codeFilter.toLowerCase()))) { return false; } // 筛选状态 if (statusFilter) { const isEnabled = statusFilter === 'true'; if (group.is_enabled !== isEnabled) { return false; } } return true; }; // 检查父级分组是否有匹配的子分组 const hasMatchingChildren = (parentId: string): boolean => { const filteredChildren = filteredChildrenMap[parentId]; return filteredChildren && filteredChildren.length > 0; }; // 处理表格数据,包括父子级关系 const processedData = groups.flatMap(group => { const parentMatches = parentMatchesFilter(group); const hasMatched = hasMatchingChildren(group.id); // 如果有筛选条件但父级和子级都不匹配,则不显示 const nameFilter = searchParams.get('name') || ''; const codeFilter = searchParams.get('code') || ''; const statusFilter = searchParams.get('is_enabled') || ''; const hasFilters = nameFilter || codeFilter || statusFilter; if (hasFilters && !parentMatches && !hasMatched) { return []; } // 先添加父级分组 const result: (RuleGroup & { isParent?: boolean, parentId?: string })[] = [ { ...group, isParent: true } ]; // 如果有子级分组并且当前已展开,则添加子级分组 if (group.children && expandedGroups.includes(group.id)) { // 如果有筛选条件并且有过滤后的子分组,则显示过滤后的子分组 if (hasFilters && filteredChildrenMap[group.id]) { filteredChildrenMap[group.id].forEach(child => { result.push({ ...child, parentId: group.id }); }); } // 否则显示所有子分组 else if (!hasFilters) { group.children.forEach(child => { result.push({ ...child, parentId: group.id }); }); } } return result; }); // 计算一级分组的评查点数量(累加其所有二级分组的评查点数量) const calculateTotalRuleCount = (record: RuleGroup & { isParent?: boolean, parentId?: string }) => { // 如果是二级分组,直接返回其评查点数量 if (!record.isParent) { return record.ruleCount || 0; } // 如果是一级分组,累加其所有子分组的评查点数量 let totalCount = 0; if (record.children && record.children.length > 0) { totalCount = record.children.reduce((sum, child) => sum + (child.ruleCount || 0), 0); } return totalCount; }; // 定义表格列配置 const columns = [ { title: "分组名称", key: "name", width: "35%", render: (_: unknown, record: RuleGroup & { isParent?: boolean, parentId?: string }) => (
{record.isParent && ( toggleGroup(record.id)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleGroup(record.id); } }} > {loading[record.id] ? ( ) : ( )} )} {record.name} {record.isParent ? '一级分组' : '二级分组'}
) }, { title: "分组编码", key: "code", render: (_: unknown, record: RuleGroup) => ( {record.code || '-'} ) }, { title: "评查点数量", key: "ruleCount", width: "12%", render: (_: unknown, record: RuleGroup & { isParent?: boolean, parentId?: string }) => ( ) }, { title: "状态", key: "is_enabled", render: (_: unknown, record: RuleGroup) => ( ) }, { title: "创建时间", key: "createdAt", width: "15%", render: (_: unknown, record: RuleGroup) => ( {record.createdAt || '-'} ) }, { title: "操作", key: "operation", width: "180px", render: (_: unknown, record: RuleGroup) => (
{userRole !== 'common' && ( )}
) } ]; return ( //
{/* 页面头部 */}

评查点分组管理
分组数: {processedData.length}

{userRole !== 'common' && ( )}
{/* 搜索栏 - 使用FilterPanel */} {/* */} } noActionDivider={true} > {/* 数据表格 - 使用Table组件 */} {initialLoading ? (
正在加载分组数据...
) : ( <> {/* 分页 - 使用Pagination组件 */} {/* {}} showTotal={true} /> */} )} ); }