// app/routes/rule-groups.new.tsx import { redirect, json, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node"; import { useLoaderData, useActionData, useNavigation, Form } from "@remix-run/react"; import { useEffect, useState } from "react"; import { Button } from "~/components/ui/Button"; import { Card } from "~/components/ui/Card"; import ruleGroupsNewStyles from "~/styles/pages/rule-groups_new.css?url"; import { getRuleGroups, getRuleGroup, createRuleGroup, updateRuleGroup, type RuleGroup as ApiRuleGroup, type RuleGroupCreateUpdateDto } from "~/api/evaluation_points/rule-groups"; // 类型定义 interface RuleGroup { id: string; name: string; code: string; description?: string; status: 'active' | 'inactive'; parentId?: string | null; sortOrder?: number; } interface ParentGroup { id: string; name: string; } // 定义加载器返回数据类型 export interface LoaderData { group?: RuleGroup; parentGroups: ParentGroup[]; isEdit: boolean; error?: string; } // 定义action返回数据类型 export interface ActionData { success?: boolean; errors?: { name?: string; code?: string; parentId?: string; general?: string; }; values?: Record; } // 样式链接 export function links() { return [{ rel: "stylesheet", href: ruleGroupsNewStyles }]; } // 动态面包屑 export const handle = { breadcrumb: (data: LoaderData) => { return data.isEdit ? "编辑分组" : "新增分组"; } }; // 页面元数据 export const meta: MetaFunction = ({ location }) => { const isEdit = new URLSearchParams(location.search).has("id"); const title = isEdit ? "编辑评查点分组" : "新建评查点分组"; return [ { title: `${title} - 中国烟草AI合同及卷宗审核系统` }, { name: "description", content: "创建新的评查点分组,包括分组名称、编码、描述和状态" }, ]; }; // 将API分组转换为前端分组模型 function mapApiToFrontend(apiGroup: ApiRuleGroup): RuleGroup { return { id: apiGroup.id, name: apiGroup.name, code: apiGroup.code || '', description: apiGroup.description, status: apiGroup.is_enabled ? 'active' : 'inactive', parentId: apiGroup.pid === '0' ? null : apiGroup.pid, sortOrder: 0 // API中不存在sortOrder字段,使用默认值 }; } // 数据加载器 export async function loader({ request }: LoaderFunctionArgs) { console.log("rule-groups.new loader被调用,URL:", request.url); try { const url = new URL(request.url); const id = url.searchParams.get("id"); console.log("获取到的ID参数:", id); // 获取一级分组列表 (用于选择父级分组) const parentGroupsResponse = await getRuleGroups(); if (parentGroupsResponse.error) { console.error("获取父分组列表失败:", parentGroupsResponse.error); throw new Error(parentGroupsResponse.error); } const parentGroups: ParentGroup[] = parentGroupsResponse.data ? parentGroupsResponse.data.map(group => ({ id: group.id, name: group.name })) : []; // 初始化分组数据 let group: RuleGroup | undefined = undefined; // 如果有ID,获取分组详情 if (id) { const groupResponse = await getRuleGroup(id); if (groupResponse.error) { console.error("获取分组详情失败:", groupResponse.error); throw new Error(groupResponse.error); } if (groupResponse.data) { group = mapApiToFrontend(groupResponse.data); } } // 返回加载的数据 return Response.json({ group, parentGroups, isEdit: !!group, error: undefined }); } catch (error) { console.error("loader函数出错:", error); // 返回一个基本的响应,避免500错误 return Response.json({ group: undefined, parentGroups: [], isEdit: false, error: error instanceof Error ? error.message : "加载数据时出错" }); } } // 表单处理 export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); // 提取表单数据 const id = formData.get("id") as string | null; const name = formData.get("name") as string; const code = formData.get("code") as string; const description = formData.get("description") as string; const status = formData.get("status") as string || "active"; const groupType = formData.get("groupType") as string; const parentId = groupType === "secondary" ? formData.get("parentId") as string : null; // 表单验证 const errors: ActionData["errors"] = {}; if (!name || name.trim() === "") { errors.name = "分组名称不能为空"; } if (!code || code.trim() === "") { errors.code = "分组编码不能为空"; } else if (!/^[a-zA-Z0-9-_]+$/.test(code)) { errors.code = "分组编码只能包含字母、数字、连字符和下划线"; } if (groupType === "secondary" && (!parentId || parentId.trim() === "")) { errors.parentId = "请选择上级分组"; } if (Object.keys(errors).length > 0) { return Response.json({ errors, values: Object.fromEntries(formData) as Record }); } // 构建保存数据 const saveData: RuleGroupCreateUpdateDto = { name: name.trim(), code: code.trim(), description: description?.trim() || "", is_enabled: status === "active", pid: parentId === null ? "0" : parentId }; try { // 根据是否有ID决定是创建还是更新 let response; if (id) { response = await updateRuleGroup(id, saveData); } else { response = await createRuleGroup(saveData); } // 处理API响应 if (response.error) { console.error("保存分组失败:", response.error); return json({ success: false, errors: { general: response.error }, values: Object.fromEntries(formData) as Record }); } // 保存成功,重定向到列表页 return redirect("/rule-groups"); } catch (error) { console.error("保存分组失败:", error); return json({ success: false, errors: { general: error instanceof Error ? error.message : "保存分组失败,请稍后重试" }, values: Object.fromEntries(formData) as Record }); } } // 页面组件 export default function RuleGroupNew() { // 所有Hooks必须在组件顶部无条件调用 const data = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); const isSubmitting = navigation.state === "submitting"; // 表单状态 const [groupType, setGroupType] = useState<"primary" | "secondary">("primary"); const [showParentSelect, setShowParentSelect] = useState(false); // 解构数据 const { group, parentGroups, isEdit, error } = data; // 初始化表单状态 useEffect(() => { if (group) { if (group.parentId) { setGroupType("secondary"); setShowParentSelect(true); } else { setGroupType("primary"); setShowParentSelect(false); } } }, [group]); // 处理分组类型变更 const handleGroupTypeChange = (e: React.ChangeEvent) => { const value = e.target.value as "primary" | "secondary"; setGroupType(value); setShowParentSelect(value === "secondary"); }; // 如果加载数据时出错,显示错误信息 if (error) { return (

加载出错

{error}

); } return (
{/* 页面头部 */}

{isEdit ? "编辑评查点分组" : "新增评查点分组"}

创建新的评查点分组,用于组织管理评查点

{/* 提示信息 */}

评查点分组用于对评查点进行分类管理,合理的分组结构有助于更高效地组织和查找评查点。

{/* 错误提示 */} {actionData?.errors?.general && (
{actionData.errors.general}
)} {/* 表单 */}
{/* 如果是编辑模式,添加ID */} {group?.id && } {/* 基本信息区域 */}

基本信息

{/* 分组类型选择 */}
分组类型 *
一级分组作为顶层分类,二级分组需要选择所属的一级分组
{/* 上级分组选择 */} {showParentSelect && (
{actionData?.errors?.parentId && (
{actionData.errors.parentId}
)}
选择此分组所属的上级分组
)} {/* 分组编码和名称 */}
{actionData?.errors?.code && (
{actionData.errors.code}
)}
编码只能包含字母、数字、连字符和下划线,且必须唯一
{actionData?.errors?.name && (
{actionData.errors.name}
)}
请使用简洁明了的名称,不超过30个字符
{/* 详细配置区域 */}

详细配置

{/* 分组描述 */}
详细描述有助于其他用户了解该分组的用途
{/* 状态 */}
禁用状态的分组及其下的评查点将不会参与评查
{/* 排序 */}
用于设置分组在列表中的显示顺序,默认为0
); }