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 { getConfigOptions, getConfigDetail, createConfig, updateConfig } from "~/api/system_setting/config-lists"; import configNewStyles from "~/styles/pages/config-lists_new.css?url"; 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[]; } export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const id = url.searchParams.get("id"); let config: ConfigData | undefined = undefined; // 获取配置选项 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 || [] }); } interface ActionData { success?: boolean; errors?: { name?: string; type?: string; environment?: string; config?: string; general?: string; }; } 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 = "配置名称不能为空"; } 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 }); } 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); } } return redirect("/config-lists"); } catch (error) { console.error("保存配置失败:", error); return Response.json({ success: false, errors: { general: "保存配置失败,请稍后重试" } }); } } // 配置模板常量 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 default function ConfigNew() { const { config, isEdit, types, environments } = 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(""); // 在 ConfigNew 组件中添加状态来跟踪当前选中的模板 const [selectedTemplate, setSelectedTemplate] = useState(null); useEffect(() => { // 初始化配置数据 if (config) { setConfigDataValue(JSON.stringify(config.config, null, 2)); setSelectedModule(config.type); 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,无法格式化"); } } }; return (
{isEdit ? "编辑系统配置" : "新增系统配置"}
{actionData?.errors?.general && (
{actionData.errors.general}
)}
{config?.id && } {/* 配置名称和状态 */}
{actionData?.errors?.name && (
{actionData.errors.name}
)}
唯一标识符,配置名称应使用英文,推荐使用下划线命名方式
禁用配置后,系统将不会读取此配置
{/* 所属模块 */}
setSelectedModule(e.target.value)} placeholder="请输入或选择所属模块" required /> {actionData?.errors?.type && (
{actionData.errors.type}
)}
{types.map((type: string) => ( ))}
将配置按功能模块进行分类,便于管理和查找
{/* 环境 */}
setSelectedEnvironment(e.target.value)} placeholder="请输入或选择环境" required /> {actionData?.errors?.environment && (
{actionData.errors.environment}
)}
{environments.map((env: string) => ( ))}
不同环境可以使用相同的配置名称,系统会自动识别当前环境并使用对应配置
{/* 配置数据 */}
{/* 左侧JSON编辑区 */}