import { redirect, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node"; import { Form, useActionData, useLoaderData, useNavigation } from "@remix-run/react"; import { useEffect, useState, useRef } from "react"; import { Button } from "~/components/ui/Button"; import { Card } from "~/components/ui/Card"; import { ENVIRONMENT_LABELS } from "./config-lists._index"; import { getConfigOptions, getConfigDetail, createConfig, updateConfig } from "~/api/system_setting/config-lists"; import configNewStyles from "~/styles/pages/config-lists_new.css?url"; import { toastService } from "~/components/ui/Toast"; export const links = () => [ { rel: "stylesheet", href: configNewStyles } ]; export const handle = { breadcrumb: (data:LoaderData) => { return data.isEdit ? "编辑配置" : "新增配置"; } }; export const meta: MetaFunction = () => { return [ { title: "新增配置 - 中国烟草AI合同及卷宗审核系统" }, { name: "description", content: "新增或编辑系统配置项" }, { name: "keywords", content: "配置管理,系统配置,新增配置,编辑配置" } ]; }; // 扩展环境枚举,添加"通用"选项 export enum ExtendedConfigEnvironment { DEV = 'dev', TEST = 'test', PROD = 'prod', COMMON = 'common' } // 扩展环境标签映射 export const EXTENDED_ENVIRONMENT_LABELS: Record = { ...ENVIRONMENT_LABELS, [ExtendedConfigEnvironment.COMMON]: '通用' }; // 新增配置表单数据 interface ConfigData { id: number; name: string; type: string; environment: string; is_active: boolean; config: Record; remark?: string; } // 加载器数据类型 interface LoaderData { config?: ConfigData; isEdit: boolean; types: string[]; environments: string[]; error?: string; } // 新增配置表单数据 interface ActionData { success?: boolean; errors?: { name?: string; type?: string; environment?: string; config?: string; general?: string; }; values?: Record; } // 配置模板常量 const CONFIG_TEMPLATES = { database: { host: "localhost", port: 5432, database: "mydb", username: "admin", password: "******", pool: { min: 2, max: 10 } }, file: { storage: { type: "local", // or "s3", "oss" path: "/data/uploads", allowed_types: ["pdf", "docx", "jpg", "png"], max_size: 10485760, // 10MB backup_enabled: true } }, ai: { ai_service: { provider: "openai", api_key: "sk-******", model: "gpt-4", max_tokens: 2000, temperature: 0.7, timeout: 30000, rate_limit: 10 // requests per minute } } }; export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const id = url.searchParams.get("id"); let config: ConfigData | undefined = undefined; try { // 获取配置选项 const optionsResponse = await getConfigOptions(); if (optionsResponse.error) { throw new Error(optionsResponse.error); } if (id) { // 获取配置详情 const detailResponse = await getConfigDetail(id); if (detailResponse.error) { throw new Error(detailResponse.error); } config = detailResponse.data; } return Response.json({ config, isEdit: !!config, types: optionsResponse.data?.types || [], environments: optionsResponse.data?.environments || [], error: undefined }); } catch (error) { console.error("加载配置数据失败:", error); return Response.json({ config: undefined, isEdit: false, types: [], environments: [], error: error instanceof Error ? error.message : "加载配置数据失败" }); } } export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const id = formData.get("id") as string; const name = formData.get("name") as string; const type = formData.get("type") as string; const environment = formData.get("environment") as string; const config = formData.get("config") as string; const is_active = formData.get("is_active") === "true"; const remark = formData.get("remark") as string; const errors: ActionData["errors"] = {}; // 表单验证 if (!name || name.trim() === "") { errors.name = "配置名称不能为空"; }else if(/^[a-zA-Z_]+$/.test(name)){ errors.name = "配置名称只能包含英文字母和下划线"; } if (!type) { errors.type = "请选择或输入所属模块"; } if (!environment) { errors.environment = "请选择环境"; } if (!config || config.trim() === "") { errors.config = "配置数据不能为空"; } else { try { JSON.parse(config); } catch (e) { errors.config = "配置数据必须是有效的JSON格式"; } } if (Object.keys(errors).length > 0) { return Response.json({ errors, values: Object.fromEntries(formData) as Record }); } try { const configData = { name, type, environment, config: JSON.parse(config), is_active, remark }; if (id) { // 更新配置 const response = await updateConfig(id, configData); if (response.error) { throw new Error(response.error); } } else { // 创建配置 const response = await createConfig(configData); if (response.error) { throw new Error(response.error); } } // 保存成功,显示成功提示并重定向 toastService.success("保存成功"); return redirect("/config-lists"); } catch (error) { console.error("保存配置失败:", error); return Response.json({ success: false, errors: { general: error instanceof Error ? error.message : "保存配置失败,请稍后重试" }, values: Object.fromEntries(formData) as Record }); } } export default function ConfigNew() { const data = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); const isSubmitting = navigation.state === "submitting"; const formRef = useRef(null); const { config, isEdit, types, environments, error } = data; // 表单状态管理 const [formValues, setFormValues] = useState<{ name: string; type: string; environment: string; config: string; is_active: boolean; remark: string; }>({ name: config?.name || "", type: config?.type || "", environment: config?.environment || "", config: config?.config ? JSON.stringify(config.config, null, 2) : "", is_active: config?.is_active ?? true, remark: config?.remark || "", }); // 表单验证错误状态 const [formErrors, setFormErrors] = useState<{ name?: string; type?: string; environment?: string; config?: string; general?: string; }>({}); // 字段是否被触摸过(用于确定何时显示错误) const [touchedFields, setTouchedFields] = useState<{ name: boolean; type: boolean; environment: boolean; config: boolean; }>({ name: false, type: false, environment: false, config: false }); // 示例JSON状态 const [exampleJsonValue, setExampleJsonValue] = useState(""); const [selectedTemplate, setSelectedTemplate] = useState(null); // 从 actionData 初始化表单错误 useEffect(() => { if (actionData?.errors) { setFormErrors(actionData.errors); } // 如果提交后有错误,则将所有字段标记为已触摸 if (actionData?.errors && Object.keys(actionData.errors).length > 0) { setTouchedFields({ name: true, type: true, environment: true, config: true }); } }, [actionData]); // 根据加载的配置数据初始化表单 useEffect(() => { if (config) { setFormValues({ name: config.name, type: config.type, environment: config.environment, config: JSON.stringify(config.config, null, 2), is_active: config.is_active, remark: config.remark || "" }); } // 初始化示例JSON setExampleJsonValue(JSON.stringify({ database: { host: "db.cluster.com", port: 5432, pool_size: 20, ssl: true }, cache: { ttl: 3600, max_entries: 1000 }, feature_flags: ["new_ui", "analytics_v2"] }, null, 2)); }, [config]); // 验证表单字段 const validateField = (field: string, value: string): string => { switch (field) { case 'name': if(value.trim() === ""){ return "配置名称不能为空"; }else if(!/^[a-zA-Z_]+$/.test(value)){ return "配置名称只能包含英文字母和下划线"; }else if(value.length > 100){ return "配置名称不能超过100个字符"; } return ""; case 'type': return value.trim() === "" ? "请选择所属模块" : ""; case 'environment': return value.trim() === "" ? "请选择环境" : ""; case 'config': if (value.trim() === "") { return "配置数据不能为空"; } else { try { JSON.parse(value); return ""; } catch (e) { return "配置数据必须是有效的JSON格式"; } } default: return ""; } }; // 处理字段改变 const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormValues(prev => ({ ...prev, [name]: value })); // 标记字段为已触摸 if (['name', 'type', 'environment', 'config'].includes(name)) { setTouchedFields(prev => ({ ...prev, [name]: true })); } // 实时验证 const error = validateField(name, value); setFormErrors(prev => ({ ...prev, [name]: error })); }; // 处理配置数据变更(JSON编辑器) const handleConfigDataChange = (e: React.ChangeEvent) => { const { value } = e.target; setFormValues(prev => ({ ...prev, config: value })); // 标记字段为已触摸 setTouchedFields(prev => ({ ...prev, config: true })); // 实时验证 const error = validateField('config', value); setFormErrors(prev => ({ ...prev, config: error })); }; // 格式化JSON const handleFormatJson = () => { if (formValues.config.trim() === "") return; try { const parsed = JSON.parse(formValues.config); const formatted = JSON.stringify(parsed, null, 2); setFormValues(prev => ({ ...prev, config: formatted })); setFormErrors(prev => ({ ...prev, config: "" })); } catch (error) { setFormErrors(prev => ({ ...prev, config: `当前不是有效的JSON,无法格式化: ${error instanceof Error ? error.message : '未知错误'}` })); } }; // 处理模块类型选择 const handleModuleSelect = (moduleType: string) => { setFormValues(prev => ({ ...prev, type: moduleType })); // 标记字段为已触摸 setTouchedFields(prev => ({ ...prev, type: true })); // 清除错误 setFormErrors(prev => ({ ...prev, type: "" })); }; // 处理环境选择 const handleEnvironmentSelect = (env: string) => { setFormValues(prev => ({ ...prev, environment: env })); // 标记字段为已触摸 setTouchedFields(prev => ({ ...prev, environment: true })); // 清除错误 setFormErrors(prev => ({ ...prev, environment: "" })); }; // 处理模板选择 const handleTemplateSelect = (templateKey: keyof typeof CONFIG_TEMPLATES) => { setExampleJsonValue(JSON.stringify(CONFIG_TEMPLATES[templateKey], null, 2)); setSelectedTemplate(templateKey); }; // 处理启用状态变更 const handleActiveChange = (e: React.ChangeEvent) => { setFormValues(prev => ({ ...prev, is_active: e.target.checked })); }; // 提交前验证 const handleBeforeSubmit = (e: React.FormEvent) => { // 标记所有字段为已触摸 setTouchedFields({ name: true, type: true, environment: true, config: true }); // 验证所有字段 const errors = { name: validateField('name', formValues.name), type: validateField('type', formValues.type), environment: validateField('environment', formValues.environment), config: validateField('config', formValues.config) }; setFormErrors(errors); // 如果有错误,阻止提交 if (errors.name || errors.type || errors.environment || errors.config) { e.preventDefault(); // 滚动到第一个错误字段 if (formRef.current) { const firstErrorField = Object.keys(errors).find(key => !!errors[key as keyof typeof errors]); if (firstErrorField) { const element = formRef.current.querySelector(`[name="${firstErrorField}"]`); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } } } }; // 如果加载数据时出错,显示错误信息 if (error) { return (

加载出错

{error}

); } return (
{isEdit ? "编辑系统配置" : "新增系统配置"}
{formErrors.general && (
{formErrors.general}
)}
{config?.id && } {/* 配置名称和状态 */}
{touchedFields.name && formErrors.name && (
{formErrors.name}
)}
唯一标识符,配置名称应使用英文,推荐使用下划线命名方式
禁用配置后,系统将不会读取此配置
{/* 所属模块 */}
{touchedFields.type && formErrors.type && (
{formErrors.type}
)}
{types.map((type: string) => ( ))}
将配置按功能模块进行分类,便于管理和查找
{/* 环境 */}
{touchedFields.environment && formErrors.environment && (
{formErrors.environment}
)}
{environments.map((env: string) => ( ))}
不同环境可以使用相同的配置名称,系统会自动识别当前环境并使用对应配置
{/* 配置数据 */}
{/* 左侧JSON编辑区 */}