重新构建路由和配置样式文件
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user