From afadd79fe8d0e15202e71af708f3745adc85cd2d Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Fri, 28 Mar 2025 15:41:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=85=8D=E7=BD=AE=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=92=8C=E9=85=8D=E7=BD=AE=E6=96=B0=E5=A2=9E=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/layout/Sidebar.tsx | 6 + app/root.tsx | 6 +- app/routes/_index.tsx | 7 +- app/routes/config-lists._index.tsx | 605 +++++++++++++++++++++ app/routes/config-lists.new.tsx | 682 ++++++++++++++++++++++++ app/routes/config-lists.tsx | 23 + app/routes/rules.tsx | 5 +- app/routes/rules/rules.$ruleId.tsx | 418 --------------- app/styles/components/button.css | 51 +- app/styles/components/form.css | 2 +- app/styles/main.css | 24 - app/styles/pages/config-lists_index.css | 89 ++++ app/styles/pages/config-lists_new.css | 139 +++++ app/styles/pages/home.css | 16 +- app/styles/pages/temp-file.css | Bin 0 -> 2184 bytes html/配置-新增.html | 8 +- 16 files changed, 1608 insertions(+), 473 deletions(-) create mode 100644 app/routes/config-lists._index.tsx create mode 100644 app/routes/config-lists.new.tsx create mode 100644 app/routes/config-lists.tsx delete mode 100644 app/routes/rules/rules.$ruleId.tsx create mode 100644 app/styles/pages/config-lists_index.css create mode 100644 app/styles/pages/config-lists_new.css create mode 100644 app/styles/pages/temp-file.css diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx index c6ff34e..49c81b8 100644 --- a/app/components/layout/Sidebar.tsx +++ b/app/components/layout/Sidebar.tsx @@ -97,6 +97,12 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) { path: '/settings', icon: 'ri-settings-4-line', children: [ + { + id: 'config-lists', + title: '配置列表', + path: '/config-lists', + icon: 'ri-list-check-3' + }, { id: 'basic-settings', title: '基础设置', diff --git a/app/root.tsx b/app/root.tsx index 0c813a8..ce49e15 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -43,9 +43,9 @@ export const meta: MetaFunction = () => { export function links() { return [ { rel: "stylesheet", href: styles }, - { rel: "preconnect", href: "https://fonts.googleapis.com" }, - { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, - { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" } + // { rel: "preconnect", href: "https://fonts.googleapis.com" }, + // { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, + // { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" } ]; } diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 358d6ce..c3aedeb 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -3,9 +3,10 @@ import { type MetaFunction } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; import { Card } from "~/components/ui/Card"; import { Button } from "~/components/ui/Button"; +import homeStyles from "~/styles/pages/home.css?url"; export const links = () => [ - { rel: "stylesheet", href: "app/styles/pages/home.css" } + { rel: "stylesheet", href: homeStyles } ]; export const meta: MetaFunction = () => { @@ -255,7 +256,7 @@ function StatusBadge({ status }: StatusBadgeProps) { warning: { label: '警告', className: 'status-badge status-warning', - icon: 'ri-error-warning-line' + icon: 'ri-alert-line' }, fail: { label: '不通过', @@ -264,7 +265,7 @@ function StatusBadge({ status }: StatusBadgeProps) { }, pending: { label: '待确认', - className: 'status-badge', + className: 'status-badge status-processing', icon: 'ri-time-line' } }; diff --git a/app/routes/config-lists._index.tsx b/app/routes/config-lists._index.tsx new file mode 100644 index 0000000..c541a52 --- /dev/null +++ b/app/routes/config-lists._index.tsx @@ -0,0 +1,605 @@ +import { json, type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node"; +import { useLoaderData, useSearchParams, useSubmit, Link } from "@remix-run/react"; +import { useState } from "react"; +import { Button } from "~/components/ui/Button"; +import { Card } from "~/components/ui/Card"; +import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel"; +import { Pagination } from "~/components/ui/Pagination"; +import { Table } from "~/components/ui/Table"; +import { Tag } from "~/components/ui/Tag"; +import configListsStyles from "~/styles/pages/config-lists_index.css?url"; + +export const links = () => [ + { rel: "stylesheet", href: configListsStyles } +]; + +// export const handle = { +// breadcrumb: "系统配置管理" +// }; + +export const meta: MetaFunction = () => { + return [ + { title: "系统配置管理 - 中国烟草AI合同及卷宗审核系统" }, + { name: "description", content: "管理系统配置,包括数据库、文件存储、AI引擎等配置项" }, + { name: "keywords", content: "系统配置,配置管理,中国烟草,参数设置" } + ]; +}; + +// 配置环境枚举 +export enum ConfigEnvironment { + DEV = 'dev', + TEST = 'test', + PROD = 'prod' +} + +// 配置模块枚举 +export enum ConfigModule { + SYSTEM = 'system', + AUTH = 'auth', + FILE = 'file', + AI = 'ai', + NOTIFICATION = 'notification' +} + +// 环境标签映射 +export const ENVIRONMENT_LABELS: Record = { + [ConfigEnvironment.DEV]: '开发环境', + [ConfigEnvironment.TEST]: '测试环境', + [ConfigEnvironment.PROD]: '生产环境' +}; + +// 模块标签映射 +export const MODULE_LABELS: Record = { + [ConfigModule.SYSTEM]: '系统', + [ConfigModule.AUTH]: '认证', + [ConfigModule.FILE]: '文件', + [ConfigModule.AI]: 'AI配置', + [ConfigModule.NOTIFICATION]: '通知' +}; + +// 配置数据类型 +interface ConfigDataType { + [key: string]: string | number | boolean | string[] | ConfigDataType | ConfigDataType[]; +} + +// 配置项模型 +interface ConfigItem { + id: string; + configName: string; + module: ConfigModule; + environment: ConfigEnvironment; + isActive: boolean; + configData: ConfigDataType; + createdAt: string; + updatedAt: string; +} + +interface LoaderData { + configs: ConfigItem[]; + totalCount: number; + currentPage: number; + pageSize: number; + totalPages: number; +} + +export async function loader({ request }: LoaderFunctionArgs) { + const url = new URL(request.url); + const configName = url.searchParams.get("configName") || ""; + const module = url.searchParams.get("module") || ""; + const environment = url.searchParams.get("environment") || ""; + const isActive = url.searchParams.get("isActive") || ""; + const currentPage = parseInt(url.searchParams.get("page") || "1", 10); + const pageSize = parseInt(url.searchParams.get("pageSize") || "10", 10); + + try { + // 模拟数据,实际项目中应从API获取 + const mockConfigs: ConfigItem[] = [ + { + id: "1", + configName: "database_connection", + module: ConfigModule.SYSTEM, + environment: ConfigEnvironment.PROD, + isActive: true, + configData: { + database: { + host: "db.cluster.com", + port: 5432, + pool_size: 20, + ssl: true + }, + cache: { + ttl: 3600, + max_entries: 1000 + }, + feature_flags: ["new_ui", "analytics_v2"] + }, + createdAt: "2023-07-10 10:15:23", + updatedAt: "2023-07-15 14:30:26" + }, + { + id: "2", + configName: "text_extraction_ai", + module: ConfigModule.AI, + environment: ConfigEnvironment.TEST, + isActive: true, + configData: { + model: "gpt-4", + parameters: { + temperature: 0.7, + max_tokens: 2000 + }, + api_key: "sk-**********", + timeout: 30 + }, + createdAt: "2023-07-12 08:45:12", + updatedAt: "2023-07-14 09:15:33" + }, + { + id: "3", + configName: "notification_service", + module: ConfigModule.NOTIFICATION, + environment: ConfigEnvironment.DEV, + isActive: false, + configData: { + email: { + smtp_server: "smtp.example.com", + port: 587, + use_tls: true, + sender: "noreply@example.com" + }, + sms: { + provider: "aliyun", + region: "cn-hangzhou", + sign_name: "AI审核系统" + } + }, + createdAt: "2023-07-05 13:20:45", + updatedAt: "2023-07-10 16:45:19" + }, + { + id: "4", + configName: "file_storage", + module: ConfigModule.FILE, + environment: ConfigEnvironment.PROD, + isActive: true, + configData: { + type: "oss", + region: "cn-shanghai", + bucket: "contracts-ai-review", + access_control: "private", + lifecycle_rules: [ + { + prefix: "temp/", + ttl_days: 7 + } + ] + }, + createdAt: "2023-06-28 09:30:18", + updatedAt: "2023-07-08 11:22:07" + } + ]; + + // 过滤数据 + let filteredConfigs = [...mockConfigs]; + + if (configName) { + filteredConfigs = filteredConfigs.filter(config => + config.configName.toLowerCase().includes(configName.toLowerCase()) + ); + } + + if (module) { + filteredConfigs = filteredConfigs.filter(config => config.module === module); + } + + if (environment) { + filteredConfigs = filteredConfigs.filter(config => config.environment === environment); + } + + if (isActive) { + const activeValue = isActive === 'true'; + filteredConfigs = filteredConfigs.filter(config => config.isActive === activeValue); + } + + // 计算分页信息 + const totalCount = filteredConfigs.length; + const totalPages = Math.ceil(totalCount / pageSize); + + // 分页截取 + const startIndex = (currentPage - 1) * pageSize; + const endIndex = startIndex + pageSize; + const paginatedConfigs = filteredConfigs.slice(startIndex, endIndex); + + return json({ + configs: paginatedConfigs, + totalCount, + currentPage, + pageSize, + totalPages + }, { + headers: { + "Cache-Control": "max-age=60, s-maxage=180" + } + }); + } catch (error) { + console.error('加载配置列表失败:', error); + throw new Response('加载配置列表失败', { status: 500 }); + } +} + +export async function action({ request }: ActionFunctionArgs) { + const formData = await request.formData(); + const _action = formData.get('_action'); + const configId = formData.get('configId'); + + if (!configId) { + return json({ success: false, error: "缺少配置ID" }, { status: 400 }); + } + + try { + if (_action === 'toggleStatus') { + const isActive = formData.get('isActive') === 'true'; + const newStatus = !isActive; + + // 实际项目中应调用API更新状态 + console.log(`切换配置 ${configId} 状态为: ${newStatus}`); + + // 模拟API调用 + // const response = await fetch(`/api/configs/${configId}/status`, { + // method: 'PATCH', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ isActive: newStatus }), + // }); + + // if (!response.ok) { + // throw new Error(`状态切换失败: ${response.status}`); + // } + + return json({ success: true, newStatus }); + } + + return json({ success: false, error: "未知操作" }, { status: 400 }); + } catch (error) { + console.error('操作配置失败:', error); + return json({ success: false, error: "操作失败" }, { status: 500 }); + } +} + +export function ErrorBoundary() { + return ( +
+

出错了

+

加载配置列表时发生错误。请稍后再试,或联系管理员。

+ +
+ ); +} + +export default function ConfigListsIndex() { + const { configs, totalCount, currentPage, pageSize } = useLoaderData(); + const [searchParams, setSearchParams] = useSearchParams(); + const submit = useSubmit(); + const [showDetailModal, setShowDetailModal] = useState(false); + const [selectedConfig, setSelectedConfig] = useState(null); + + const handleFilterChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + const newParams = new URLSearchParams(searchParams); + + if (value) { + newParams.set(name, value); + } else { + newParams.delete(name); + } + + // 切换筛选条件时,重置到第一页 + newParams.set('page', '1'); + + setSearchParams(newParams); + }; + + const handleConfigNameSearch = (value: string) => { + const newParams = new URLSearchParams(searchParams); + if (value) { + newParams.set('configName', value); + } else { + newParams.delete('configName'); + } + + // 搜索时,重置到第一页 + newParams.set('page', '1'); + + setSearchParams(newParams); + }; + + const handleToggleStatus = (config: ConfigItem) => { + if (window.confirm(`确定要${config.isActive ? '禁用' : '启用'}该配置吗?`)) { + const formData = new FormData(); + formData.append('_action', 'toggleStatus'); + formData.append('configId', config.id); + formData.append('isActive', String(config.isActive)); + + submit(formData, { method: 'post' }); + } + }; + + const handleViewDetail = (config: ConfigItem) => { + setSelectedConfig(config); + setShowDetailModal(true); + }; + + const handlePageChange = (page: number) => { + const newParams = new URLSearchParams(searchParams); + newParams.set('page', page.toString()); + setSearchParams(newParams); + }; + + const handlePageSizeChange = (size: number) => { + const newParams = new URLSearchParams(searchParams); + newParams.set('pageSize', size.toString()); + newParams.set('page', '1'); // 更改每页条数时,重置到第一页 + setSearchParams(newParams); + }; + + // 处理重置筛选 + const handleReset = () => { + setSearchParams(new URLSearchParams()); + }; + + + // 关闭详情模态框 + const closeDetailModal = () => { + setShowDetailModal(false); + setSelectedConfig(null); + }; + + // 定义表格列配置 + const columns = [ + { + title: "配置名称", + dataIndex: "configName" as keyof ConfigItem, + key: "configName", + width: "20%" + }, + { + title: "所属模块", + key: "module", + width: "10%", + render: (_: unknown, record: ConfigItem) => MODULE_LABELS[record.module] + }, + { + title: "环境", + key: "environment", + width: "15%", + render: (_: unknown, record: ConfigItem) => { + const envClass = `env-tag env-tag-${record.environment}`; + return ( + + {ENVIRONMENT_LABELS[record.environment]} + + ); + } + }, + { + title: "状态", + key: "isActive", + width: "15%", + render: (_: unknown, record: ConfigItem) => ( + + {record.isActive ? '已启用' : '已禁用'} + + ) + }, + { + title: "最后更新时间", + dataIndex: "updatedAt" as keyof ConfigItem, + key: "updatedAt", + width: "15%" + }, + { + title: "操作", + key: "operation", + width: "25%", + render: (_: unknown, record: ConfigItem) => ( +
+ + + 编辑 + + +
+ ) + } + ]; + + // 生成环境选项 + const environmentOptions = Object.entries(ENVIRONMENT_LABELS).map(([value, label]) => ({ + value, + label + })); + + // 生成模块选项 + const moduleOptions = Object.entries(MODULE_LABELS).map(([value, label]) => ({ + value, + label + })); + + return ( +
+ {/* 页面头部 */} +
+

系统配置管理

+ +
+ + {/* 搜索区域 */} + + + + + } + noActionDivider={true} + > + + + + + + + + + + {/* 表格区域 */} + + + + {/* 分页区域 */} + {totalCount > 0 && ( + + )} + + + {/* 配置详情模态框 */} + {showDetailModal && selectedConfig && ( +
+
+
+

查看配置详情

+ +
+ +
+
+
配置名称
+
{selectedConfig.configName}
+
+ +
+
所属模块
+
{MODULE_LABELS[selectedConfig.module]}
+
+ +
+
环境
+
+ + {ENVIRONMENT_LABELS[selectedConfig.environment]} + +
+
+ +
+
状态
+
+ + {selectedConfig.isActive ? '已启用' : '已禁用'} + +
+
+ +
+
配置数据
+
+                  {JSON.stringify(selectedConfig.configData, null, 2)}
+                
+
+ +
+
+
创建时间
+
{selectedConfig.createdAt}
+
+ +
+
更新时间
+
{selectedConfig.updatedAt}
+
+
+
+ +
+ +
+
+
+ )} + + ); +} diff --git a/app/routes/config-lists.new.tsx b/app/routes/config-lists.new.tsx new file mode 100644 index 0000000..ce5061e --- /dev/null +++ b/app/routes/config-lists.new.tsx @@ -0,0 +1,682 @@ +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编辑区 */} +
+ -
-
-
- -
-

规则设置

- -
-
- - - {actionData?.errors?.type && ( -
{actionData.errors.type}
- )} -
- -
- - - {actionData?.errors?.priority && ( -
{actionData.errors.priority}
- )} -
- -
- -
- - - {formData.isActive ? '启用' : '禁用'} -
-
-
- -
-
- - - {actionData?.errors?.content && ( -
{actionData.errors.content}
- )} - {formData.type === 'regex' && ( -
- - 输入正则表达式,用于匹配文档内容 -
- )} - {formData.type === 'ai' && ( -
- - 请使用自然语言描述规则检查的要求,AI将自动理解并执行检查 -
- )} -
-
-
- -
-

测试工具

- -
-
- - -
- -
- -
-
-
- -
- - -
- -
-
- ); -} \ No newline at end of file diff --git a/app/styles/components/button.css b/app/styles/components/button.css index de3b4af..38a3fbe 100644 --- a/app/styles/components/button.css +++ b/app/styles/components/button.css @@ -1,16 +1,16 @@ -/** +/* * * 按钮组件样式 */ -/* 基础按钮 */ +/* 基础按钮 .btn { @apply inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md font-medium text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed; -} +} */ -/* 按钮尺寸 */ +/* 按钮尺寸 .btn-xs { @apply px-2.5 py-1 text-xs; } @@ -21,13 +21,17 @@ .btn-lg { @apply px-5 py-2.5 text-base; -} +} */ -/* 按钮类型 */ +/* 按钮类型 .btn-primary { @apply bg-[#00684a] text-white hover:bg-[#005a3f] focus:ring-[#00684a]; } +.btn-default { + @apply bg-white text-gray-800 hover:bg-gray-300 focus:ring-gray-300; +} + .btn-secondary { @apply bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-300; } @@ -44,9 +48,9 @@ .btn-text { @apply bg-transparent text-[#00684a] shadow-none border-none hover:bg-[rgba(0,104,74,0.05)] focus:ring-0; -} +} */ -/* 图标按钮 */ +/* 图标按钮 .btn-icon { @apply p-2 rounded-full; } @@ -57,9 +61,9 @@ .btn-icon i, .btn-icon svg { @apply text-current w-5 h-5; -} +} */ -/* 按钮组 */ +/* 按钮组 .btn-group { @apply inline-flex; } @@ -74,4 +78,29 @@ .btn-group .btn:last-child { @apply rounded-r-md; -} \ No newline at end of file +} */ + + +/* 按钮样式 */ +.ant-btn { + @apply inline-flex items-center justify-center px-4 py-2 + border border-transparent rounded-md font-medium text-sm + focus:outline-none focus:ring-2 focus:ring-offset-2 transition-all duration-200 + disabled:opacity-50 disabled:cursor-not-allowed; +} + +.ant-btn-primary { + @apply bg-[#00684a] text-white hover:bg-[#005a3f] focus:ring-[#00684a]; +} + +.ant-btn-default { + @apply bg-white border border-gray-300 text-gray-800 hover:border-[#00684a] focus:ring-[#00684a]; +} + +.ant-btn-danger { + @apply bg-[#f5222d] text-white hover:bg-[#cf1f29] focus:ring-[#f5222d]; +} + +.ant-btn-sm { + @apply px-3 py-1.5 text-sm; +} \ No newline at end of file diff --git a/app/styles/components/form.css b/app/styles/components/form.css index ef37118..cb3ba6f 100644 --- a/app/styles/components/form.css +++ b/app/styles/components/form.css @@ -32,7 +32,7 @@ } .form-select { - @apply pr-10; + @apply pr-10 border-gray-300 border-[1px]; } .form-textarea { diff --git a/app/styles/main.css b/app/styles/main.css index 308d68d..7dc598e 100644 --- a/app/styles/main.css +++ b/app/styles/main.css @@ -214,30 +214,6 @@ /* === UI组件 === */ - /* 按钮样式 */ - .ant-btn { - @apply inline-flex items-center justify-center px-4 py-2 - border border-transparent rounded-md font-medium text-sm - focus:outline-none focus:ring-2 focus:ring-offset-2 transition-all duration-200 - disabled:opacity-50 disabled:cursor-not-allowed; - } - - .ant-btn-primary { - @apply bg-[#00684a] text-white hover:bg-[#005a3f] focus:ring-[#00684a]; - } - - .ant-btn-default { - @apply bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-300; - } - - .ant-btn-danger { - @apply bg-[#f5222d] text-white hover:bg-[#cf1f29] focus:ring-[#f5222d]; - } - - .ant-btn-sm { - @apply px-3 py-1.5 text-sm; - } - /* 卡片组件 */ .card { @apply bg-white rounded-lg shadow-sm border border-gray-200 p-4; diff --git a/app/styles/pages/config-lists_index.css b/app/styles/pages/config-lists_index.css new file mode 100644 index 0000000..d72694e --- /dev/null +++ b/app/styles/pages/config-lists_index.css @@ -0,0 +1,89 @@ +/** + * 配置列表页样式 + */ + +/* 配置页面容器 */ +.config-lists { + @apply p-6; +} + +/* 表格区域 */ +.config-lists .config-table { + @apply w-full; +} + +/* 选择框focus状态 */ +.config-lists .form-select:focus { + border-color: #00684a; + box-shadow: 0 0 0 2px rgba(0, 104, 74, 0.2); + outline: none; +} + +/* 环境标签 */ +.config-lists .env-tag { + @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium; +} + +.config-lists .env-tag-dev { + @apply bg-blue-100 text-blue-900; +} + +.config-lists .env-tag-test { + @apply bg-yellow-100 text-yellow-900; +} + +.config-lists .env-tag-prod { + @apply bg-green-100 text-green-900; +} + +/* 操作列样式 */ +.config-lists .operations-cell { + @apply flex space-x-2; +} + +.config-lists .operation-btn { + @apply text-sm flex items-center text-[--color-primary] bg-transparent hover:underline p-2; +} + + +.config-lists .operation-btn i { + @apply mr-1; +} + +/* 详情模态框样式 */ +.config-lists .config-detail-content { + @apply space-y-4; +} + +.config-lists .config-detail-item { + @apply mb-4; +} + +.config-lists .config-detail-label { + @apply text-sm text-gray-500 mb-1; +} + +.config-lists .config-detail-value { + @apply text-base; +} + +.config-lists .config-detail-code { + @apply bg-gray-50 p-3 rounded text-sm overflow-x-auto; +} + +.config-lists .ant-btn-primary{ + @apply !text-white +} + +/* 响应式调整 */ +@screen md { + .config-lists .config-filter-form { + @apply grid-cols-2; + } +} + +@screen lg { + .config-lists .config-filter-form { + @apply grid-cols-4; + } +} \ No newline at end of file diff --git a/app/styles/pages/config-lists_new.css b/app/styles/pages/config-lists_new.css new file mode 100644 index 0000000..db8c050 --- /dev/null +++ b/app/styles/pages/config-lists_new.css @@ -0,0 +1,139 @@ +/** + * 配置新增/编辑页样式 + */ + +/* 页面容器 */ +.config-new-page { + @apply p-6; +} + +/* 表单卡片 */ +.config-new-page .config-form-card { + @apply p-6; +} + +/* 表单样式 */ +.config-new-page .config-form { + @apply space-y-6; +} + +.config-new-page .form-row { + @apply grid grid-cols-1 md:grid-cols-2 gap-6; +} + +.config-new-page .form-row-full { + @apply md:col-span-2; +} + +.config-new-page .form-group { + @apply mb-4; +} + +.config-new-page .form-label { + @apply block text-sm font-medium mb-1 text-gray-700; +} + +.config-new-page .form-label.required::after { + content: " *"; + @apply text-red-500; +} + +.config-new-page .form-input, +.config-new-page .form-select, +.config-new-page .form-textarea { + @apply block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm; +} + +.config-new-page .form-textarea { + @apply font-mono; +} + +.config-new-page .form-checkbox { + @apply h-4 w-4 text-primary border-gray-300 rounded focus:ring-primary-500; +} + +.config-new-page .form-checkbox-label { + @apply ml-2 block text-sm text-gray-900; +} + +.config-new-page .input-error { + @apply border-red-500; +} + +.config-new-page .error-message { + @apply text-sm text-red-500 mt-1; +} + +.config-new-page .general-error { + @apply bg-red-50 p-3 rounded text-center; +} + +.config-new-page .form-help { + @apply text-xs text-gray-500 mt-1; +} + +.config-new-page .form-actions { + @apply flex justify-end space-x-3; +} + +.config-new-page .inline { + @apply inline-block; +} + +/* 标签按钮样式 */ +.config-new-page .tag-buttons { + @apply flex flex-wrap gap-2; +} + +.config-new-page .tag-button { + @apply inline-flex items-center px-3 py-1 rounded-full text-xs font-medium + bg-gray-100 text-gray-700 border border-gray-200 cursor-pointer transition-all duration-200 + hover:bg-[rgba(0,104,74,0.1)] hover:border-[#00684a] hover:text-[#00684a]; +} + +.config-new-page .tag-button.active { + @apply bg-[rgba(0,104,74,0.15)] border-[#00684a] text-[#00684a]; +} + +/* JSON编辑器 */ +.config-new-page .json-editor { + @apply w-full min-h-[320px] font-mono text-sm leading-relaxed + border border-gray-300 rounded-md p-3 bg-gray-50 text-gray-800 + focus:outline-none focus:ring-primary-500 focus:border-primary-500; +} + +.config-new-page .editor-actions { + @apply text-right mt-2; +} + +/* 示例卡片 */ +.config-new-page .example-card { + @apply h-full flex flex-col bg-gray-50 rounded-md border border-gray-200 overflow-hidden; +} + +.config-new-page .example-header { + @apply p-3 border-b border-gray-200 bg-gray-100; +} + +.config-new-page .example-title { + @apply font-medium text-gray-700; +} + +.config-new-page .example-content { + @apply p-3 flex-grow overflow-auto; +} + +.config-new-page .example-pre { + @apply m-0 font-mono text-sm leading-relaxed text-gray-800; +} + +.config-new-page .example-footer { + @apply p-3 border-t border-gray-200 bg-gray-100; +} + +/* 代码语法高亮基础样式 */ +.config-new-page .code-json .key { @apply text-blue-600; } +.config-new-page .code-json .string { @apply text-green-600; } +.config-new-page .code-json .number { @apply text-purple-600; } +.config-new-page .code-json .boolean { @apply text-blue-600; } +.config-new-page .code-json .null { @apply text-gray-500; } \ No newline at end of file diff --git a/app/styles/pages/home.css b/app/styles/pages/home.css index 64678a9..60d41c0 100644 --- a/app/styles/pages/home.css +++ b/app/styles/pages/home.css @@ -96,22 +96,26 @@ @apply flex items-center; } -/* 状态标签 - 与status-badge.css重复,已注释 +/* 状态标签 */ .status-badge { - @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium; + @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium; } .status-badge.status-success { - @apply bg-[rgba(0,104,74,0.1)] text-[#00684a]; + @apply bg-green-100 text-green-900; } .status-badge.status-warning { - @apply bg-[rgba(250,173,20,0.1)] text-[#faad14]; + @apply bg-yellow-100 text-yellow-900; } .status-badge.status-error { - @apply bg-[rgba(245,34,45,0.1)] text-[#f5222d]; -} */ + @apply bg-red-100 text-red-900; +} + +.status-badge.status-processing { + @apply bg-blue-100 text-blue-900; +} /* 卡片样式 */ .dashboard-card { diff --git a/app/styles/pages/temp-file.css b/app/styles/pages/temp-file.css new file mode 100644 index 0000000000000000000000000000000000000000..47db2cb1aa053d70824838acc4b7aeb016dcf7f6 GIT binary patch literal 2184 zcmcIl&1w@-6h6?MBJS126p9NWXbn)N2q3+i^{xvQQRe%)m&TB=TMa;Z;UQq-dwd1T^i zL2LiSoLrB}g2LCyy2KKx)T_Gbur1xQsO;S(0$O*?$A^Ti4 z*_sMq-;_PB5w5OcW{N)4&_5wKm*Xdy4^UxTd9&*KY`Cfd+d;gz67m*xtPjGw#}^;G z-=3R)XN>NjM+g6YJU>o%aCRbbS8*>ZFyQF%v|@JrF6W<)D@XlC!JGW#%i>I?Kskbg z9Gu;*4JIvkUsXH#+$C$SXH%+Z<)p*47sXgtvm3yQo)zW%64Vx?1NfZJXEg?Aiep-i zpr=-rlfGZBLn}K~)3Z!hw2LXvoynE0XAk#}iEazA&ikrsuNl8=E 中国烟草AI合同及卷宗审核系统 - 新增/编辑系统配置 - - + + + - +