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

418 lines
14 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, { useState } from 'react';
import { redirect, type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData, useActionData, Form, useSubmit, useNavigate } from '@remix-run/react';
import { Button } from '~/components/ui/Button';
import { Card } from '~/components/ui/Card';
import { Breadcrumb } from '~/components/layout/Breadcrumb';
import type { Rule, RuleType, RulePriority } from '~/models/rule';
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 评查规则详情" },
{ name: "description", content: "评查规则详情编辑页面" }
];
};
export const handle = {
breadcrumb: '编辑评查点'
};
interface LoaderData {
rule: Rule;
ruleTypes: { label: string; value: RuleType }[];
rulePriorities: { label: string; value: RulePriority }[];
groupOptions: { label: string; value: string }[];
}
export async function loader({ params }: LoaderFunctionArgs) {
const { ruleId } = params;
// 判断是否为新建规则
const isNewRule = ruleId === 'new';
// 模拟数据,实际项目中应从API获取
const rule: Rule = isNewRule ? {
id: '',
name: '',
description: '',
content: '',
type: 'text',
priority: 'medium',
groupId: '',
groupName: '',
isActive: true,
createdAt: '',
updatedAt: ''
} : {
id: ruleId,
name: '许可证编号格式检查',
description: '检查烟草专卖零售许可证编号是否符合"烟零许(年份)序号号"的标准格式',
content: '许可证编号应当符合"烟零许(年份)序号号"的标准格式,如"烟零许(2023)12345号"',
type: 'regex',
priority: 'high',
groupId: '1',
groupName: '专卖许可证规则组',
isActive: true,
createdAt: '2023-10-15 09:30',
updatedAt: '2023-12-10 14:20'
};
// 规则类型选项
const ruleTypes = [
{ label: '文本匹配', value: 'text' },
{ label: '正则表达式', value: 'regex' },
{ label: '数值范围', value: 'range' },
{ label: '日期检查', value: 'date' },
{ label: 'AI智能检查', value: 'ai' }
];
// 规则优先级选项
const rulePriorities = [
{ label: '低', value: 'low' },
{ label: '中', value: 'medium' },
{ label: '高', value: 'high' },
{ label: '关键', value: 'critical' }
];
// 规则组选项
const groupOptions = [
{ label: '专卖许可证规则组', value: '1' },
{ label: '合同协议规则组', value: '2' },
{ label: '财务票据规则组', value: '3' },
{ label: '采购订单规则组', value: '4' },
{ label: '销售报表规则组', value: '5' }
];
return Response.json({
rule,
ruleTypes,
rulePriorities,
groupOptions
});
}
interface ActionData {
success?: boolean;
errors?: {
name?: string;
description?: string;
content?: string;
type?: string;
priority?: string;
groupId?: string;
general?: string;
};
}
export async function action({ request, params }: ActionFunctionArgs) {
const { ruleId } = params;
const formData = await request.formData();
const isNewRule = ruleId === 'new';
// 获取表单数据
const name = formData.get('name')?.toString() || '';
const description = formData.get('description')?.toString() || '';
const content = formData.get('content')?.toString() || '';
const type = formData.get('type')?.toString() || '';
const priority = formData.get('priority')?.toString() || '';
const groupId = formData.get('groupId')?.toString() || '';
const isActive = formData.get('isActive') === 'true';
// 表单验证
const errors: ActionData['errors'] = {};
if (!name.trim()) {
errors.name = '规则名称不能为空';
}
if (!content.trim()) {
errors.content = '规则内容不能为空';
}
if (!type) {
errors.type = '必须选择规则类型';
}
if (!priority) {
errors.priority = '必须选择规则优先级';
}
if (!groupId) {
errors.groupId = '必须选择规则所属组';
}
if (Object.keys(errors).length > 0) {
return Response.json({ errors });
}
// 模拟API保存操作,实际项目中应调用API
try {
// 在这里调用API进行保存
console.log('保存规则:', {
id: isNewRule ? 'new-id' : ruleId,
name,
description,
content,
type,
priority,
groupId,
isActive
});
// 成功后重定向到规则列表页
return redirect('/rules');
} catch (error) {
return Response.json({
errors: {
general: '保存规则失败,请重试'
}
});
}
}
export default function RuleDetail() {
const { rule, ruleTypes, rulePriorities, groupOptions } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
const navigate = useNavigate();
const submit = useSubmit();
const [formData, setFormData] = useState({
name: rule.name,
description: rule.description,
content: rule.content,
type: rule.type,
priority: rule.priority,
groupId: rule.groupId,
isActive: rule.isActive
});
const isNewRule = !rule.id;
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSwitchChange = (name: string, checked: boolean) => {
setFormData(prev => ({ ...prev, [name]: checked }));
};
const handleCancel = () => {
navigate('/rules');
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// 使用useSubmit提交表单
const formElement = e.currentTarget;
submit(formElement, { method: 'post' });
};
return (
<div>
<Breadcrumb
items={[
{ title: '评查规则', to: '/rules' },
{ title: isNewRule ? '新增规则' : '编辑规则', to: `/rules/${rule.id}` }
]}
/>
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-medium">{isNewRule ? '新增评查规则' : '编辑评查规则'}</h2>
</div>
<Card>
<Form method="post" onSubmit={handleSubmit}>
{actionData?.errors?.general && (
<div className="error-message mb-4">
<i className="ri-error-warning-line mr-1"></i>
{actionData.errors.general}
</div>
)}
<div className="form-section mb-6">
<h3 className="form-section-title"></h3>
<div className="form-row">
<div className="form-group col-span-6">
<label htmlFor="name" className="form-label required"></label>
<input
type="text"
id="name"
name="name"
className={`form-input ${actionData?.errors?.name ? 'error' : ''}`}
value={formData.name}
onChange={handleChange}
required
/>
{actionData?.errors?.name && (
<div className="form-error">{actionData.errors.name}</div>
)}
</div>
<div className="form-group col-span-6">
<label htmlFor="groupId" className="form-label required"></label>
<select
id="groupId"
name="groupId"
className={`form-select ${actionData?.errors?.groupId ? 'error' : ''}`}
value={formData.groupId}
onChange={handleChange}
required
>
<option value=""></option>
{groupOptions.map((option: { value: string; label: string }) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
{actionData?.errors?.groupId && (
<div className="form-error">{actionData.errors.groupId}</div>
)}
</div>
</div>
<div className="form-row">
<div className="form-group col-span-12">
<label htmlFor="description" className="form-label"></label>
<textarea
id="description"
name="description"
className="form-textarea"
rows={3}
value={formData.description}
onChange={handleChange}
></textarea>
</div>
</div>
</div>
<div className="form-section mb-6">
<h3 className="form-section-title"></h3>
<div className="form-row">
<div className="form-group col-span-4">
<label htmlFor="type" className="form-label required"></label>
<select
id="type"
name="type"
className={`form-select ${actionData?.errors?.type ? 'error' : ''}`}
value={formData.type}
onChange={handleChange}
required
>
<option value=""></option>
{ruleTypes.map((option: { value: string; label: string }) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
{actionData?.errors?.type && (
<div className="form-error">{actionData.errors.type}</div>
)}
</div>
<div className="form-group col-span-4">
<label htmlFor="priority" className="form-label required"></label>
<select
id="priority"
name="priority"
className={`form-select ${actionData?.errors?.priority ? 'error' : ''}`}
value={formData.priority}
onChange={handleChange}
required
>
<option value=""></option>
{rulePriorities.map((option: { value: string; label: string }) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</select>
{actionData?.errors?.priority && (
<div className="form-error">{actionData.errors.priority}</div>
)}
</div>
<div className="form-group col-span-4">
<label htmlFor="isActive" className="form-label"></label>
<div className="flex items-center h-10 mt-1">
<label className="switch" aria-label="切换规则状态">
<input
type="checkbox"
name="isActive"
checked={formData.isActive}
onChange={(e) => handleSwitchChange('isActive', e.target.checked)}
/>
<span className="slider round"></span>
</label>
<input type="hidden" name="isActive" value={formData.isActive ? 'true' : 'false'} />
<span className="ml-2">{formData.isActive ? '启用' : '禁用'}</span>
</div>
</div>
</div>
<div className="form-row">
<div className="form-group col-span-12">
<label htmlFor="content" className="form-label required"></label>
<textarea
id="content"
name="content"
className={`form-textarea code-editor ${actionData?.errors?.content ? 'error' : ''}`}
rows={8}
value={formData.content}
onChange={handleChange}
required
></textarea>
{actionData?.errors?.content && (
<div className="form-error">{actionData.errors.content}</div>
)}
{formData.type === 'regex' && (
<div className="text-xs text-gray-500 mt-1">
<i className="ri-information-line mr-1"></i>
</div>
)}
{formData.type === 'ai' && (
<div className="text-xs text-gray-500 mt-1">
<i className="ri-information-line mr-1"></i>
使AI将自动理解并执行检查
</div>
)}
</div>
</div>
</div>
<div className="form-section mb-6">
<h3 className="form-section-title"></h3>
<div className="p-4 bg-gray-50 rounded-md">
<div className="mb-4">
<label htmlFor="testContent" className="form-label"></label>
<textarea
id="testContent"
className="form-textarea"
rows={4}
placeholder="粘贴待测试的文本内容..."
></textarea>
</div>
<div className="flex justify-end">
<Button type="default">
<i className="ri-test-tube-line mr-1"></i>
</Button>
</div>
</div>
</div>
<div className="flex justify-end space-x-2">
<Button type="default" onClick={handleCancel}>
</Button>
<Button type="primary">
{isNewRule ? '创建规则' : '保存修改'}
</Button>
</div>
</Form>
</Card>
</div>
);
}