829 lines
25 KiB
TypeScript
829 lines
25 KiB
TypeScript
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";
|
||
import { getUserSession } from "~/api/login/auth.server";
|
||
|
||
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<string, string> = {
|
||
...ENVIRONMENT_LABELS,
|
||
[ExtendedConfigEnvironment.COMMON]: '通用'
|
||
};
|
||
|
||
// 新增配置表单数据
|
||
interface ConfigData {
|
||
id: number;
|
||
name: string;
|
||
type: string;
|
||
environment: string;
|
||
is_active: boolean;
|
||
config: Record<string, unknown>;
|
||
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<string, string>;
|
||
}
|
||
|
||
// 配置模板常量
|
||
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;
|
||
|
||
// 获取JWT token
|
||
const { frontendJWT } = await getUserSession(request);
|
||
|
||
try {
|
||
// 获取配置选项
|
||
const optionsResponse = await getConfigOptions(frontendJWT);
|
||
if (optionsResponse.error) {
|
||
throw new Error(optionsResponse.error);
|
||
}
|
||
|
||
if (id) {
|
||
// 获取配置详情
|
||
const detailResponse = await getConfigDetail(id, frontendJWT);
|
||
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;
|
||
|
||
// 获取JWT token
|
||
const { frontendJWT } = await getUserSession(request);
|
||
|
||
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<string, string>
|
||
});
|
||
}
|
||
|
||
try {
|
||
const configData = {
|
||
name,
|
||
type,
|
||
environment,
|
||
config: JSON.parse(config),
|
||
is_active,
|
||
remark
|
||
};
|
||
|
||
if (id) {
|
||
// 更新配置
|
||
const response = await updateConfig(id, configData, frontendJWT);
|
||
if (response.error) {
|
||
throw new Error(response.error);
|
||
}
|
||
} else {
|
||
// 创建配置
|
||
const response = await createConfig(configData, frontendJWT);
|
||
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<string, string>
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
export default function ConfigNew() {
|
||
const data = useLoaderData<typeof loader>();
|
||
const actionData = useActionData<typeof action>();
|
||
const navigation = useNavigation();
|
||
const isSubmitting = navigation.state === "submitting";
|
||
const formRef = useRef<HTMLFormElement>(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<keyof typeof CONFIG_TEMPLATES | null>(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<HTMLInputElement | HTMLTextAreaElement>) => {
|
||
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<HTMLTextAreaElement>) => {
|
||
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<HTMLInputElement>) => {
|
||
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 (
|
||
<div className="error-container p-6">
|
||
<h1 className="text-xl font-bold text-red-500 mb-4">加载出错</h1>
|
||
<p className="mb-4">{error}</p>
|
||
<Button type="primary" to="/config-lists">返回列表</Button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="config-new-page">
|
||
<div className="flex justify-between items-center mb-4">
|
||
<span className="block text-xl font-medium">{isEdit ? "编辑系统配置" : "新增系统配置"}</span>
|
||
<div className="form-actions">
|
||
<Button type="default" to="/config-lists">
|
||
<i className="ri-arrow-left-line mr-1"></i>
|
||
返回
|
||
</Button>
|
||
<Button type="primary" disabled={isSubmitting} form="configForm">
|
||
<i className="ri-save-line mr-1"></i>
|
||
{isSubmitting ? '保存中...' : '保存'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{formErrors.general && (
|
||
<div className="mb-4 w-full">
|
||
<div className="error-message general-error">{formErrors.general}</div>
|
||
</div>
|
||
)}
|
||
|
||
<Card className="config-form-card">
|
||
<Form
|
||
method="post"
|
||
id="configForm"
|
||
className="config-form"
|
||
ref={formRef}
|
||
onSubmit={handleBeforeSubmit}
|
||
>
|
||
{config?.id && <input type="hidden" name="id" value={config.id} />}
|
||
|
||
{/* 配置名称和状态 */}
|
||
<div className="form-row">
|
||
<div className="form-group">
|
||
<label htmlFor="name" className="form-label ">配置名称 <span className="text-red-500">*</span></label>
|
||
<input
|
||
type="text"
|
||
id="name"
|
||
name="name"
|
||
className={`form-input ${touchedFields.name && formErrors.name ? 'input-error' : ''}`}
|
||
value={formValues.name}
|
||
onChange={handleInputChange}
|
||
placeholder="请输入配置名称,如database_connection"
|
||
/>
|
||
{touchedFields.name && formErrors.name && (
|
||
<div className="error-message">{formErrors.name}</div>
|
||
)}
|
||
<div className="form-help">
|
||
唯一标识符,配置名称应使用英文,推荐使用下划线命名方式
|
||
</div>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="is_active" className="form-label">状态</label>
|
||
<div className="mt-2">
|
||
<div className="flex items-center">
|
||
<input
|
||
type="checkbox"
|
||
id="is_active"
|
||
name="is_active"
|
||
value="true"
|
||
className="form-checkbox"
|
||
checked={formValues.is_active}
|
||
onChange={handleActiveChange}
|
||
/>
|
||
<label htmlFor="is_active" className="form-checkbox-label">
|
||
启用此配置
|
||
</label>
|
||
</div>
|
||
<div className="form-help">
|
||
禁用配置后,系统将不会读取此配置
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 所属模块 */}
|
||
<div className="form-group">
|
||
<label htmlFor="type" className="form-label">所属模块 <span className="text-red-500">*</span></label>
|
||
<input
|
||
type="hidden"
|
||
name="type"
|
||
value={formValues.type}
|
||
/>
|
||
<input
|
||
type="text"
|
||
id="typeDisplay"
|
||
className={`form-input ${touchedFields.type && formErrors.type ? 'input-error' : ''}`}
|
||
value={formValues.type}
|
||
onChange={handleInputChange}
|
||
name="type"
|
||
placeholder="请输入或选择所属模块"
|
||
|
||
/>
|
||
{touchedFields.type && formErrors.type && (
|
||
<div className="error-message">{formErrors.type}</div>
|
||
)}
|
||
<div className="tag-buttons mt-2">
|
||
{types.map((type: string) => (
|
||
<button
|
||
key={type}
|
||
type="button"
|
||
className={`tag-button ${formValues.type === type ? 'active' : ''}`}
|
||
onClick={() => handleModuleSelect(type)}
|
||
>
|
||
{type}
|
||
</button>
|
||
))}
|
||
</div>
|
||
<div className="form-help">
|
||
将配置按功能模块进行分类,便于管理和查找
|
||
</div>
|
||
</div>
|
||
|
||
{/* 环境 */}
|
||
<div className="form-group">
|
||
<label htmlFor="environment" className="form-label">环境 <span className="text-red-500">*</span></label>
|
||
<input
|
||
type="hidden"
|
||
name="environment"
|
||
value={formValues.environment}
|
||
/>
|
||
<input
|
||
type="text"
|
||
id="environmentDisplay"
|
||
className={`form-input ${touchedFields.environment && formErrors.environment ? 'input-error' : ''}`}
|
||
value={formValues.environment}
|
||
onChange={handleInputChange}
|
||
name="environment"
|
||
placeholder="请输入或选择环境"
|
||
/>
|
||
{touchedFields.environment && formErrors.environment && (
|
||
<div className="error-message">{formErrors.environment}</div>
|
||
)}
|
||
<div className="tag-buttons mt-2">
|
||
{environments.map((env: string) => (
|
||
<button
|
||
key={env}
|
||
type="button"
|
||
className={`tag-button ${formValues.environment === env ? 'active' : ''}`}
|
||
onClick={() => handleEnvironmentSelect(env)}
|
||
>
|
||
{env}
|
||
</button>
|
||
))}
|
||
</div>
|
||
<div className="form-help">
|
||
不同环境可以使用相同的配置名称,系统会自动识别当前环境并使用对应配置
|
||
</div>
|
||
</div>
|
||
|
||
{/* 配置数据 */}
|
||
<div className="form-group">
|
||
<label htmlFor="config" className="form-label ">配置数据 (JSON) <span className="text-red-500">*</span></label>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4" style={{ minHeight: '390px' }}>
|
||
{/* 左侧JSON编辑区 */}
|
||
<div className="h-full">
|
||
<textarea
|
||
id="config"
|
||
name="config"
|
||
className={`json-editor ${touchedFields.config && formErrors.config ? 'input-error' : ''}`}
|
||
value={formValues.config}
|
||
onChange={handleConfigDataChange}
|
||
|
||
placeholder='请输入JSON格式的配置数据'
|
||
/>
|
||
<div className="editor-actions">
|
||
<Button
|
||
type="default"
|
||
size="small"
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
handleFormatJson();
|
||
}}
|
||
>
|
||
<i className="ri-braces-line mr-1"></i> 格式化JSON
|
||
</Button>
|
||
</div>
|
||
{touchedFields.config && formErrors.config && (
|
||
<div className="error-message">{formErrors.config}</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 右侧示例区 */}
|
||
<div className="h-full">
|
||
<div className="example-card">
|
||
<div className="example-header">
|
||
<div className="example-title">配置示例</div>
|
||
</div>
|
||
<div className="example-content">
|
||
<div className="json-display">
|
||
{exampleJsonValue.split('\n').map((line, index) => (
|
||
<div key={index} className="json-line">
|
||
{line.split('').map((char, charIndex) => {
|
||
let className = 'json-char';
|
||
if (char === '{' || char === '}') className += ' json-brace';
|
||
if (char === '[' || char === ']') className += ' json-bracket';
|
||
if (char === '"') className += ' json-quote';
|
||
if (char === ':') className += ' json-colon';
|
||
if (char === ',') className += ' json-comma';
|
||
return (
|
||
<span key={charIndex} className={className}>
|
||
{char}
|
||
</span>
|
||
);
|
||
})}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div className="example-footer">
|
||
<div className="text-sm font-medium mb-2">常用配置模板:</div>
|
||
<div className="flex flex-wrap gap-2">
|
||
<Button
|
||
type="default"
|
||
size="small"
|
||
className={`${selectedTemplate === 'database' ? 'border-[#00684a] text-[#00684a] focus:ring-0' : ''}`}
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
handleTemplateSelect('database');
|
||
}}
|
||
>
|
||
数据库配置
|
||
</Button>
|
||
<Button
|
||
type="default"
|
||
size="small"
|
||
className={`${selectedTemplate === 'file' ? 'border-[#00684a] text-[#00684a] focus:ring-0' : ''}`}
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
handleTemplateSelect('file');
|
||
}}
|
||
>
|
||
文件存储配置
|
||
</Button>
|
||
<Button
|
||
type="default"
|
||
size="small"
|
||
className={`${selectedTemplate === 'ai' ? 'border-[#00684a] text-[#00684a] focus:ring-0' : ''}`}
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
handleTemplateSelect('ai');
|
||
}}
|
||
>
|
||
AI服务配置
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="form-help">
|
||
请输入JSON格式的配置数据,支持嵌套结构
|
||
</div>
|
||
</div>
|
||
|
||
{/* 备注 */}
|
||
<div className="form-group">
|
||
<label htmlFor="remark" className="form-label">备注</label>
|
||
<textarea
|
||
id="remark"
|
||
name="remark"
|
||
className="form-textarea"
|
||
value={formValues.remark}
|
||
onChange={handleInputChange}
|
||
rows={2}
|
||
placeholder="请输入配置备注信息"
|
||
/>
|
||
<div className="form-help">
|
||
可选填项,用于描述配置的用途、注意事项等
|
||
</div>
|
||
</div>
|
||
|
||
|
||
</Form>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|