import { json, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node"; import { Form, useActionData, useLoaderData, useNavigation } from "@remix-run/react"; import { useEffect, useState } from "react"; import { Button } from "~/components/ui/Button"; import { Card } from "~/components/ui/Card"; import { ConfigModule, MODULE_LABELS, ENVIRONMENT_LABELS } from "./config-lists._index"; import configNewStyles from "~/styles/pages/config-lists_new.css?url"; export const links = () => [ { rel: "stylesheet", href: configNewStyles } ]; export const handle = { breadcrumb: ({ location }: { location: Location }) => { const hasId = new URLSearchParams(location.search).has("id"); return hasId ? "编辑配置" : "新增配置"; } }; 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: string; configName: string; module: ConfigModule; environment: string; // 使用扩展的环境类型 isActive: boolean; configData: string; // JSON字符串 remarks?: string; // 添加备注字段 } interface LoaderData { config?: ConfigData; isEdit: boolean; } export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const id = url.searchParams.get("id"); let config: ConfigData | undefined = undefined; if (id) { try { // 实际应用中,应从API获取配置详情 // const response = await fetch(`${process.env.API_BASE_URL}/api/configs/${id}`); // if (!response.ok) throw new Error(`获取配置详情失败: ${response.status}`); // config = await response.json(); // config.configData = JSON.stringify(config.configData, null, 2); // 使用模拟数据 if (id === "1") { config = { id: "1", configName: "database_connection", module: ConfigModule.SYSTEM, environment: ExtendedConfigEnvironment.PROD, isActive: true, remarks: "数据库连接配置,包含主库和从库配置", configData: 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) }; } else if (id === "2") { config = { id: "2", configName: "text_extraction_ai", module: ConfigModule.AI, environment: ExtendedConfigEnvironment.TEST, isActive: true, remarks: "AI文本抽取服务配置", configData: JSON.stringify({ model: "gpt-4", parameters: { temperature: 0.7, max_tokens: 2000 }, api_key: "sk-**********", timeout: 30 }, null, 2) }; } else if (id === "3") { config = { id: "3", configName: "notification_service", module: ConfigModule.NOTIFICATION, environment: ExtendedConfigEnvironment.DEV, isActive: false, remarks: "通知服务配置,目前处于开发测试阶段", configData: JSON.stringify({ email: { smtp_server: "smtp.example.com", port: 587, use_tls: true, sender: "noreply@example.com" }, sms: { provider: "aliyun", region: "cn-hangzhou", sign_name: "AI审核系统" } }, null, 2) }; } else if (id === "4") { config = { id: "4", configName: "file_storage", module: ConfigModule.FILE, environment: ExtendedConfigEnvironment.COMMON, isActive: true, remarks: "文件存储通用配置,适用于所有环境", configData: JSON.stringify({ type: "oss", region: "cn-shanghai", bucket: "contracts-ai-review", access_control: "private", lifecycle_rules: [ { prefix: "temp/", ttl_days: 7 } ] }, null, 2) }; } } catch (error) { console.error("获取配置详情失败:", error); // 在实际应用中,应该将错误信息返回给客户端 // 这里简单处理,返回空config } } return json({ config, isEdit: !!config }); } interface ActionData { success?: boolean; errors?: { configName?: string; module?: string; environment?: string; configData?: string; general?: string; }; } export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const configId = formData.get("id") as string; const configName = formData.get("configName") as string; const module = formData.get("module") as string; const environment = formData.get("environment") as string; const configData = formData.get("configData") as string; const isActive = formData.get("isActive") === "true"; const remarks = formData.get("remarks") as string; const errors: ActionData["errors"] = {}; // 表单验证 if (!configName || configName.trim() === "") { errors.configName = "配置名称不能为空"; } if (!module) { errors.module = "请选择所属模块"; } if (!environment) { errors.environment = "请选择环境"; } if (!configData || configData.trim() === "") { errors.configData = "配置数据不能为空"; } else { try { JSON.parse(configData); } catch (e) { errors.configData = "配置数据必须是有效的JSON格式"; } } if (Object.keys(errors).length > 0) { return json({ errors }); } try { // 实际应用中,应调用API保存数据 console.log("保存配置:", { configId, configName, module, environment, configData, isActive, remarks }); // 模拟API调用 // const response = await fetch(`${process.env.API_BASE_URL}/api/configs${configId ? `/${configId}` : ''}`, { // method: configId ? "PUT" : "POST", // headers: { // "Content-Type": "application/json", // }, // body: JSON.stringify({ // id: configId, // configName, // module, // environment, // configData: JSON.parse(configData), // isActive, // remarks, // }), // }); // // if (!response.ok) { // throw new Error(`保存失败: ${response.status}`); // } // 保存成功后重定向到列表页 return redirect("/config-lists"); } catch (error) { console.error("保存配置失败:", error); return json({ success: false, errors: { general: "保存配置失败,请稍后重试" } }); } } // JSON模板数据 const JSON_TEMPLATES = { database: { database: { host: "localhost", port: 5432, username: "db_user", password: "******", name: "app_database", pool_size: 10, timeout: 5000, ssl: false } }, 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 default function ConfigNew() { const { config, isEdit } = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); const isSubmitting = navigation.state === "submitting"; const [jsonError, setJsonError] = useState(null); const [configDataValue, setConfigDataValue] = useState(""); const [exampleJsonValue, setExampleJsonValue] = useState(""); // 标签选择状态 const [selectedModule, setSelectedModule] = useState(""); const [selectedEnvironment, setSelectedEnvironment] = useState(""); useEffect(() => { // 初始化配置数据 if (config?.configData) { setConfigDataValue(config.configData); } // 初始化模块和环境的选中状态 if (config?.module) { setSelectedModule(config.module); } if (config?.environment) { setSelectedEnvironment(config.environment); } // 初始化示例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]); // 处理JSON数据变更 const handleConfigDataChange = (e: React.ChangeEvent) => { const value = e.target.value; setConfigDataValue(value); if (value.trim() === "") { setJsonError(null); return; } try { JSON.parse(value); setJsonError(null); } catch (error) { if (error instanceof Error) { setJsonError(`配置数据必须是有效的JSON格式: ${error.message}`); } else { setJsonError("配置数据必须是有效的JSON格式"); } } }; // 格式化JSON const handleFormatJson = () => { if (configDataValue.trim() === "") return; try { const parsed = JSON.parse(configDataValue); setConfigDataValue(JSON.stringify(parsed, null, 2)); setJsonError(null); } catch (error) { if (error instanceof Error) { setJsonError(`当前不是有效的JSON,无法格式化: ${error.message}`); } else { setJsonError("当前不是有效的JSON,无法格式化"); } } }; // 加载JSON模板 const handleLoadTemplate = (type: keyof typeof JSON_TEMPLATES) => { const template = JSON_TEMPLATES[type]; setConfigDataValue(JSON.stringify(template, null, 2)); setJsonError(null); }; // 模块标签点击 const handleModuleTagClick = (module: string) => { setSelectedModule(module); }; // 环境标签点击 const handleEnvironmentTagClick = (env: string) => { setSelectedEnvironment(env); }; // 显示JSON语法高亮 const renderJsonWithSyntaxHighlight = (json: string) => { try { // 如果是空字符串,直接返回 if (!json.trim()) return ""; // 解析并格式化JSON const parsed = JSON.parse(json); const formatted = JSON.stringify(parsed, null, 2); // 添加语法高亮 return formatted .replace(/&/g, '&') .replace(//g, '>') .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, (match) => { let cls = 'number'; if (/^"/.test(match)) { if (/:$/.test(match)) { cls = 'key'; match = match.replace(':', ''); } else { cls = 'string'; } } else if (/true|false/.test(match)) { cls = 'boolean'; } else if (/null/.test(match)) { cls = 'null'; } return `${match}`; }); } catch (e) { // 如果解析失败,返回原始JSON return json; } }; return (
{isEdit ? "编辑系统配置" : "新增系统配置"}
{config?.id && }
{config?.id && } {/* 配置名称和状态 */}
{actionData?.errors?.configName && (
{actionData.errors.configName}
)}
唯一标识符,配置名称应使用英文,推荐使用下划线命名方式
禁用配置后,系统将不会读取此配置
{/* 所属模块 */}
{actionData?.errors?.module && (
{actionData.errors.module}
)}
{Object.entries(MODULE_LABELS).map(([value, label]) => ( ))}
将配置按功能模块进行分类,便于管理和查找
{/* 环境 */}
{actionData?.errors?.environment && (
{actionData.errors.environment}
)}
{Object.entries(EXTENDED_ENVIRONMENT_LABELS).map(([value, label]) => ( ))}
不同环境可以使用相同的配置名称,系统会自动识别当前环境并使用对应配置
{/* 配置数据 */}
{/* 左侧JSON编辑区 */}