diff --git a/app/api/files/files-upload.ts b/app/api/files/files-upload.ts new file mode 100644 index 0000000..c317975 --- /dev/null +++ b/app/api/files/files-upload.ts @@ -0,0 +1,211 @@ +import { postgrestGet, postgrestPut, postgrestPost, type PostgrestParams } from '../postgrest-client'; +import dayjs from 'dayjs'; + +/** + * 格式化日期 + * @param dateString 日期字符串 + * @returns 格式化后的日期字符串 + */ +function formatDate(dateString: string): string { + if (!dateString) return ''; + try { + return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss'); + } catch (error) { + console.error('日期格式化失败:', error); + return dateString; + } +} + + +/** + * 从不同格式的 API 响应中提取数据 + * @param responseData API 响应数据 + * @returns 提取后的数据或 null + */ +function extractApiData(responseData: unknown): T | null { + if (!responseData) return null; + + // 格式1: { code: number, msg: string, data: T } + if (typeof responseData === 'object' && responseData !== null && + 'code' in responseData && + 'data' in responseData && + (responseData as { data: unknown }).data) { + return (responseData as { data: T }).data; + } + + // 格式2: 直接是数据对象 + return responseData as T; +} + +// 文档状态枚举 +export enum DocumentStatus { + CUTTING = "Cutting", + EXTRACTIONING = "extractioning", + REVIEWING = "reviewing", + COMPLETED = "completed" +} + +// 文档类型接口 +export interface DocumentType { + id: number; + name: string; +} + +// 提取结果接口 +interface ExtractedResult { + [key: string]: unknown; +} + +// 摘要接口 +interface Summary { + [key: string]: unknown; +} + +// 文档接口 +export interface Document { + id: number; + name: string; + type_id: number; + file_size: number; + status: DocumentStatus; + created_at: string; + document_number?: string; + path?: string; + storage_type?: string; + is_test_document?: boolean; + evaluation_level?: string; + ocr_result?: Record; + extracted_results?: ExtractedResult; + sumary?: Summary; + remark?: string; +} + +/** + * 获取当天的文档列表 + * @returns 文档列表 + */ +export async function getTodayDocuments(): Promise<{data: Document[]; error?: never} | {data?: never; error: string; status?: number}> { + try { + const today = dayjs().startOf('day').format('YYYY-MM-DD'); + console.log('查询当天文档,日期范围:', today); + + const params: PostgrestParams = { + select: ` + id, + name, + type_id, + file_size, + status, + created_at, + document_number, + path, + storage_type, + is_test_document, + evaluation_level, + ocr_result, + extracted_results, + sumary, + remark + `, + order: 'created_at.desc', + filter: { + 'created_at': `gte.${today}` + } + }; + + // console.log('发送请求参数:', params); + const response = await postgrestGet('documents', params); + // console.log('API 响应:', response); + + if (response.error) { + console.error('API 返回错误:', response.error); + return { error: response.error, status: response.status }; + } + + const extractedData = extractApiData(response.data); + // console.log('提取后的数据:', extractedData); + + if (!extractedData) { + console.error('数据提取失败'); + return { error: '获取数据失败', status: 500 }; + } + + return { data: extractedData }; + } catch (error) { + console.error('获取当天文档列表失败:', error); + return { + error: error instanceof Error ? error.message : '获取当天文档列表失败', + status: 500 + }; + } +} + +/** + * 获取文档类型列表 + * @returns 文档类型列表 + */ +export async function getDocumentTypes(): Promise<{data: DocumentType[]; error?: never} | {data?: never; error: string; status?: number}> { + try { + const params: PostgrestParams = { + select: 'id, name' + }; + + const response = await postgrestGet('document_types', params); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + const extractedData = extractApiData(response.data); + if (!extractedData) { + return { error: '获取数据失败', status: 500 }; + } + + return { data: extractedData }; + } catch (error) { + console.error('获取文档类型列表失败:', error); + return { + error: error instanceof Error ? error.message : '获取文档类型列表失败', + status: 500 + }; + } +} + +/** + * 获取指定文档的状态 + * @param documentIds 文档ID列表 + * @returns 文档状态列表 + */ +export async function getDocumentsStatus(documentIds: number[]): Promise<{data: Document[]; error?: never} | {data?: never; error: string; status?: number}> { + try { + if (!documentIds || documentIds.length === 0) { + return { data: [] }; + } + + const params: PostgrestParams = { + select: 'id, status', + filter: { + 'id': `in.(${documentIds.join(',')})` + } + }; + + const response = await postgrestGet('documents', params); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + const extractedData = extractApiData(response.data); + if (!extractedData) { + return { error: '获取数据失败', status: 500 }; + } + + return { data: extractedData }; + } catch (error) { + console.error('获取文档状态失败:', error); + return { + error: error instanceof Error ? error.message : '获取文档状态失败', + status: 500 + }; + } +} \ No newline at end of file diff --git a/app/api/system_setting/config-lists.ts b/app/api/system_setting/config-lists.ts index e69de29..743f13c 100644 --- a/app/api/system_setting/config-lists.ts +++ b/app/api/system_setting/config-lists.ts @@ -0,0 +1,321 @@ +import { postgrestGet, postgrestPut, postgrestPost, type PostgrestParams } from '../postgrest-client'; +import dayjs from 'dayjs'; +// 配置项接口 +export interface ConfigItem { + id: number; + name: string; + type: string; + description: string; + environment: string; + config: Record; + is_active: boolean; + version: string; + created_by: number; + created_at: string; + updated_at: string; +} +/** + * 格式化日期 + * @param dateString 日期字符串 + * @returns 格式化后的日期字符串 + */ +function formatDate(dateString: string): string { + if (!dateString) return ''; + try { + return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss'); + } catch (error) { + console.error('日期格式化失败:', error); + return dateString; + } +} + + +/** + * 从不同格式的 API 响应中提取数据 + * @param responseData API 响应数据 + * @returns 提取后的数据或 null + */ +function extractApiData(responseData: unknown): T | null { + if (!responseData) return null; + + // 格式1: { code: number, msg: string, data: T } + if (typeof responseData === 'object' && responseData !== null && + 'code' in responseData && + 'data' in responseData && + (responseData as { data: unknown }).data) { + return (responseData as { data: T }).data; + } + + // 格式2: 直接是数据对象 + return responseData as T; +} + + + + + +// 获取配置列表 +export async function getConfigLists(params: { + name?: string; + type?: string; + environment?: string; + is_active?: boolean; + page?: number; + pageSize?: number; +}): Promise<{data: ConfigItem[]; total: number; error?: never} | {data?: never; error: string}> { + try { + const { + name, + type, + environment, + is_active, + page = 1, + pageSize = 10 + } = params; + + // 构建查询参数 + const queryParams: PostgrestParams = { + select: '*', + order: 'created_at.desc', + limit: pageSize, + offset: (page-1)*pageSize, + filter: {} as Record, + headers: { + 'Prefer': 'count=exact' + } + }; + + // 添加筛选条件 + const filter: Record = {}; + if (name) { + filter['name'] = `ilike.%${name}%`; + } + if (type) { + filter['type'] = `eq.${type}`; + } + if (environment) { + filter['environment'] = `eq.${environment}`; + } + if (is_active !== undefined) { + filter['is_active'] = `eq.${is_active}`; + } + queryParams.filter = filter; + + // 获取数据 + const response = await postgrestGet('configurations', queryParams); + + if (response.error) { + return { error: response.error }; + } + + const data = extractApiData(response.data); + if (!data) { + return { error: '获取数据失败' }; + } + + // 格式化日期 + const formattedData = data.map(item => ({ + ...item, + created_at: formatDate(item.created_at), + updated_at: formatDate(item.updated_at) + })); + + // 从响应头中获取总数 + let totalCount = 0; + const responseWithHeaders = response as { data: ConfigItem[]; headers: Record }; + if (responseWithHeaders.headers) { + const rangeHeader = responseWithHeaders.headers['content-range']; + if (rangeHeader) { + const total = rangeHeader.split('/')[1]; + if (total !== '*') { + totalCount = parseInt(total, 10); + } + } + } + + return { + data: formattedData, + total: totalCount + }; + } catch (error) { + console.error('获取配置列表失败:', error); + return { error: error instanceof Error ? error.message : '获取配置列表失败' }; + } +} + +// 获取配置类型和环境选项 +export async function getConfigOptions(): Promise<{data: {types: string[]; environments: string[]}; error?: never} | {data?: never; error: string}> { + try { + // 获取类型选项 + const typeResponse = await postgrestGet<{type: string}[]>('configurations', { + select: 'type' + }); + + if (typeResponse.error) { + return { error: typeResponse.error }; + } + + const typesData = extractApiData<{type: string}[]>(typeResponse.data); + if (!typesData) { + return { error: '获取类型选项失败' }; + } + + // 获取环境选项 + const envResponse = await postgrestGet<{environment: string}[]>('configurations', { + select: 'environment' + }); + + if (envResponse.error) { + return { error: envResponse.error }; + } + + const envData = extractApiData<{environment: string}[]>(envResponse.data); + if (!envData) { + return { error: '获取环境选项失败' }; + } + + // 手动去重 + const types = [...new Set(typesData.map(item => item.type))]; + const environments = [...new Set(envData.map(item => item.environment))]; + + return { + data: { + types, + environments + } + }; + } catch (error) { + console.error('获取配置选项失败:', error); + return { error: error instanceof Error ? error.message : '获取配置选项失败' }; + } +} + +// 获取配置详情 +export async function getConfigDetail(id: string): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> { + try { + const response = await postgrestGet('configurations', { + filter: { + 'id': `eq.${id}` + } + }); + + if (response.error) { + return { error: response.error }; + } + + const data = extractApiData(response.data); + if (!data || data.length === 0) { + return { error: '未找到配置' }; + } + + const config = data[0]; + return { + data: { + ...config, + created_at: formatDate(config.created_at), + updated_at: formatDate(config.updated_at) + } + }; + } catch (error) { + console.error('获取配置详情失败:', error); + return { error: error instanceof Error ? error.message : '获取配置详情失败' }; + } +} + +// 创建配置 +export async function createConfig(data: { + name: string; + type: string; + environment: string; + config: Record; + is_active: boolean; + remarks?: string; +}): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> { + try { + const response = await postgrestPost('configurations', data); + + if (response.error) { + return { error: response.error }; + } + + const createdData = extractApiData(response.data); + if (!createdData) { + return { error: '创建配置失败' }; + } + + return { + data: { + ...createdData, + created_at: formatDate(createdData.created_at), + updated_at: formatDate(createdData.updated_at) + } + }; + } catch (error) { + console.error('创建配置失败:', error); + return { error: error instanceof Error ? error.message : '创建配置失败' }; + } +} + +// 更新配置 +export async function updateConfig(id: string, data: { + name: string; + type: string; + environment: string; + config: Record; + is_active: boolean; + remarks?: string; +}): Promise<{data: ConfigItem; error?: never} | {data?: never; error: string}> { + try { + const response = await postgrestPut('configurations', data, { + id: id.toString() + }); + + if (response.error) { + return { error: response.error }; + } + + const updatedData = extractApiData(response.data); + if (!updatedData) { + return { error: '更新配置失败' }; + } + + return { + data: { + ...updatedData, + created_at: formatDate(updatedData.created_at), + updated_at: formatDate(updatedData.updated_at) + } + }; + } catch (error) { + console.error('更新配置失败:', error); + return { error: error instanceof Error ? error.message : '更新配置失败' }; + } +} + +// 更新配置状态 +export async function updateConfigStatus(id: number, is_active: boolean): Promise<{success: boolean; error?: string}> { + try { + const response = await postgrestPut( + 'configurations', + { is_active }, + { id: id.toString() } + ); + + if (response.error) { + return { success: false, error: response.error }; + } + + const updatedData = extractApiData(response.data); + if (!updatedData) { + return { success: false, error: '更新配置状态失败' }; + } + + return { success: true }; + } catch (error) { + console.error('更新配置状态失败:', error); + return { + success: false, + error: error instanceof Error ? error.message : '更新配置状态失败' + }; + } +} diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx index a819030..0ea5319 100644 --- a/app/components/layout/Sidebar.tsx +++ b/app/components/layout/Sidebar.tsx @@ -37,12 +37,12 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) { path: '/files/upload', icon: 'ri-upload-cloud-line' }, - { - id: 'file-list', - title: '文件列表', - path: '/files', - icon: 'ri-file-list-3-line' - }, + // { + // id: 'file-list', + // title: '文件列表', + // path: '/files', + // icon: 'ri-file-list-3-line' + // }, { id:'documents', title:'文档列表', @@ -80,15 +80,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) { title: '新增评查点', path: '/rules/new', icon: 'ri-add-circle-line' - } - ] - }, - { - id: 'review-management', - title: '评查结果', - path: '/reviews', - icon: 'ri-bar-chart-box-line', - children: [ + }, { id: 'review-detail', title: '评查详情', diff --git a/app/components/ui/UploadArea.tsx b/app/components/ui/UploadArea.tsx index 84c01a9..2998412 100644 --- a/app/components/ui/UploadArea.tsx +++ b/app/components/ui/UploadArea.tsx @@ -50,7 +50,15 @@ export const UploadArea = forwardRef(({ })); const handleClick = useCallback(() => { - if (!disabled && !shouldPreventFileSelect && fileInputRef.current) { + if (disabled) return; + + if (shouldPreventFileSelect) { + // 如果应该阻止文件选择,则触发表单提交 + const form = fileInputRef.current?.closest('form'); + if (form) { + form.requestSubmit(); + } + } else if (fileInputRef.current) { fileInputRef.current.click(); } }, [disabled, shouldPreventFileSelect]); @@ -76,7 +84,15 @@ export const UploadArea = forwardRef(({ e.preventDefault(); setIsDragOver(false); - if (!disabled && !shouldPreventFileSelect && e.dataTransfer.files.length > 0) { + if (disabled) return; + + if (shouldPreventFileSelect) { + // 如果应该阻止文件选择,则触发表单提交 + const form = e.currentTarget.closest('form'); + if (form) { + form.requestSubmit(); + } + } else if (e.dataTransfer.files.length > 0) { onFilesSelected(e.dataTransfer.files); } }, [disabled, shouldPreventFileSelect, onFilesSelected]); diff --git a/app/routes/config-lists._index.tsx b/app/routes/config-lists._index.tsx index 95d7b9a..764abc5 100644 --- a/app/routes/config-lists._index.tsx +++ b/app/routes/config-lists._index.tsx @@ -7,13 +7,13 @@ import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterP import { Pagination } from "~/components/ui/Pagination"; import { Table } from "~/components/ui/Table"; import { Tag } from "~/components/ui/Tag"; +import { getConfigLists, getConfigOptions, updateConfigStatus, type ConfigItem } from "~/api/system_setting/config-lists"; import configListsStyles from "~/styles/pages/config-lists_index.css?url"; export const links = () => [ { rel: "stylesheet", href: configListsStyles } ]; - export const meta: MetaFunction = () => { return [ { title: "系统配置管理 - 中国烟草AI合同及卷宗审核系统" }, @@ -54,165 +54,55 @@ export const MODULE_LABELS: Record = { [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; + types: string[]; + environments: string[]; } 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 name = url.searchParams.get("name") || ""; + const type = url.searchParams.get("type") || ""; const environment = url.searchParams.get("environment") || ""; - const isActive = url.searchParams.get("isActive") || ""; + const is_active = url.searchParams.get("is_active") ? url.searchParams.get("is_active") === "true" : undefined; 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()) - ); + // 获取配置列表 + const configsResponse = await getConfigLists({ + name, + type, + environment, + is_active, + page: currentPage, + pageSize + }); + + if (configsResponse.error || !configsResponse.data) { + throw new Error(configsResponse.error || "获取配置列表失败"); } - - if (module) { - filteredConfigs = filteredConfigs.filter(config => config.module === module); + + // 获取配置选项 + const optionsResponse = await getConfigOptions(); + + if (optionsResponse.error || !optionsResponse.data) { + throw new Error(optionsResponse.error || "获取配置选项失败"); } - - 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, + configs: configsResponse.data, + totalCount: configsResponse.total, currentPage, pageSize, - totalPages + totalPages: Math.ceil(configsResponse.total / pageSize), + types: optionsResponse.data.types, + environments: optionsResponse.data.environments }, { headers: { "Cache-Control": "max-age=60, s-maxage=180" @@ -233,28 +123,18 @@ export async function action({ request }: ActionFunctionArgs) { return json({ success: false, error: "缺少配置ID" }, { status: 400 }); } + // 进行更新启用和禁用的状态 try { if (_action === 'toggleStatus') { - const isActive = formData.get('isActive') === 'true'; - const newStatus = !isActive; + const is_active = formData.get('is_active') === 'true'; - // 实际项目中应调用API更新状态 - console.log(`切换配置 ${configId} 状态为: ${newStatus}`); + const response = await updateConfigStatus(parseInt(configId as string), is_active); - // 模拟API调用 - // const response = await fetch(`/api/configs/${configId}/status`, { - // method: 'PATCH', - // headers: { - // 'Content-Type': 'application/json', - // }, - // body: JSON.stringify({ isActive: newStatus }), - // }); + if (!response.success) { + return json({ success: false, error: response.error }, { status: 500 }); + } - // if (!response.ok) { - // throw new Error(`状态切换失败: ${response.status}`); - // } - - return json({ success: true, newStatus }); + return json({ success: true }); } return json({ success: false, error: "未知操作" }, { status: 400 }); @@ -275,7 +155,7 @@ export function ErrorBoundary() { } export default function ConfigListsIndex() { - const { configs, totalCount, currentPage, pageSize } = useLoaderData(); + const { configs, totalCount, currentPage, pageSize, types, environments } = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); const submit = useSubmit(); const [showDetailModal, setShowDetailModal] = useState(false); @@ -300,9 +180,9 @@ export default function ConfigListsIndex() { const handleConfigNameSearch = (value: string) => { const newParams = new URLSearchParams(searchParams); if (value) { - newParams.set('configName', value); + newParams.set('name', value); } else { - newParams.delete('configName'); + newParams.delete('name'); } // 搜索时,重置到第一页 @@ -312,11 +192,11 @@ export default function ConfigListsIndex() { }; const handleToggleStatus = (config: ConfigItem) => { - if (window.confirm(`确定要${config.isActive ? '禁用' : '启用'}该配置吗?`)) { + if (window.confirm(`确定要${config.is_active ? '禁用' : '启用'}该配置吗?`)) { const formData = new FormData(); formData.append('_action', 'toggleStatus'); - formData.append('configId', config.id); - formData.append('isActive', String(config.isActive)); + formData.append('configId', config.id.toString()); + formData.append('is_active', String(!config.is_active)); submit(formData, { method: 'post' }); } @@ -342,10 +222,20 @@ export default function ConfigListsIndex() { // 处理重置筛选 const handleReset = () => { + const nameInput = document.querySelector('input[placeholder="请输入配置名称"]') as HTMLInputElement; + const typeSelect = document.querySelector('select[name="type"]') as HTMLInputElement; + const environmentSelect = document.querySelector('select[name="environment"]') as HTMLInputElement; + const statusSelect = document.querySelector('select[name="is_active"]') as HTMLInputElement; + setSearchParams(new URLSearchParams()); + + if(nameInput) nameInput.value = '' + if(typeSelect) typeSelect.value = '' + if(environmentSelect) environmentSelect.value = '' + if(statusSelect) statusSelect.value = '' + }; - // 关闭详情模态框 const closeDetailModal = () => { setShowDetailModal(false); @@ -356,43 +246,42 @@ export default function ConfigListsIndex() { const columns = [ { title: "配置名称", - dataIndex: "configName" as keyof ConfigItem, - key: "configName", + dataIndex: "name" as keyof ConfigItem, + key: "name", width: "20%" }, { title: "所属模块", - key: "module", + key: "type", width: "10%", - render: (_: unknown, record: ConfigItem) => MODULE_LABELS[record.module] + render: (_: unknown, record: ConfigItem) => record.type }, { title: "环境", key: "environment", width: "15%", render: (_: unknown, record: ConfigItem) => { - const envClass = `env-tag env-tag-${record.environment}`; return ( - - {ENVIRONMENT_LABELS[record.environment]} + + {record.environment} ); } }, { title: "状态", - key: "isActive", + key: "is_active", width: "15%", render: (_: unknown, record: ConfigItem) => ( - - {record.isActive ? '已启用' : '已禁用'} + + {record.is_active ? '已启用' : '已禁用'} ) }, { title: "最后更新时间", - dataIndex: "updatedAt" as keyof ConfigItem, - key: "updatedAt", + dataIndex: "updated_at" as keyof ConfigItem, + key: "updated_at", width: "15%" }, { @@ -417,29 +306,17 @@ export default function ConfigListsIndex() { ) } ]; - // 生成环境选项 - const environmentOptions = Object.entries(ENVIRONMENT_LABELS).map(([value, label]) => ({ - value, - label - })); - - // 生成模块选项 - const moduleOptions = Object.entries(MODULE_LABELS).map(([value, label]) => ({ - value, - label - })); - return (
{/* 页面头部 */} @@ -458,9 +335,9 @@ export default function ConfigListsIndex() { - + */} } noActionDivider={true} @@ -468,7 +345,7 @@ export default function ConfigListsIndex() { ({ value: type, label: type }))]} onChange={handleFilterChange} className="flex-1 min-w-[200px]" /> @@ -487,17 +364,16 @@ export default function ConfigListsIndex() { label="环境" name="environment" value={searchParams.get('environment') || ''} - options={[{ value: '', label: '全部' }, ...environmentOptions]} + options={[ ...environments.map(env => ({ value: env, label: env }))]} onChange={handleFilterChange} className="flex-1 min-w-[200px]" />
配置名称
-
{selectedConfig.configName}
+
{selectedConfig.name}
所属模块
-
{MODULE_LABELS[selectedConfig.module]}
+
{selectedConfig.type}
环境
- - {ENVIRONMENT_LABELS[selectedConfig.environment]} + + {selectedConfig.environment}
@@ -565,8 +441,8 @@ export default function ConfigListsIndex() {
状态
- - {selectedConfig.isActive ? '已启用' : '已禁用'} + + {selectedConfig.is_active ? '已启用' : '已禁用'}
@@ -574,19 +450,19 @@ export default function ConfigListsIndex() {
配置数据
-                  {JSON.stringify(selectedConfig.configData, null, 2)}
+                  {JSON.stringify(selectedConfig.config, null, 2)}
                 
创建时间
-
{selectedConfig.createdAt}
+
{selectedConfig.created_at}
更新时间
-
{selectedConfig.updatedAt}
+
{selectedConfig.updated_at}
diff --git a/app/routes/config-lists.new.tsx b/app/routes/config-lists.new.tsx index 6e4301d..e5cb2bf 100644 --- a/app/routes/config-lists.new.tsx +++ b/app/routes/config-lists.new.tsx @@ -4,6 +4,7 @@ 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 = () => [ @@ -40,17 +41,19 @@ export const EXTENDED_ENVIRONMENT_LABELS: Record = { interface ConfigData { id: string; - configName: string; - module: ConfigModule; - environment: string; // 使用扩展的环境类型 - isActive: boolean; - configData: string; // JSON字符串 - remarks?: string; // 添加备注字段 + name: string; + type: string; + environment: string; + is_active: boolean; + config: Record; + remarks?: string; } interface LoaderData { config?: ConfigData; isEdit: boolean; + types: string[]; + environments: string[]; } export async function loader({ request }: LoaderFunctionArgs) { @@ -58,156 +61,72 @@ export async function loader({ request }: LoaderFunctionArgs) { 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 - } + // 获取配置选项 + const optionsResponse = await getConfigOptions(); + if (optionsResponse.error) { + throw new Error(optionsResponse.error); } - return Response.json({ + if (id) { + // 获取配置详情 + const detailResponse = await getConfigDetail(id); + if (detailResponse.error) { + throw new Error(detailResponse.error); + } + config = detailResponse.data; + } + + return json({ config, - isEdit: !!config + isEdit: !!config, + types: optionsResponse.data.types, + environments: optionsResponse.data.environments }); } - interface ActionData { success?: boolean; errors?: { - configName?: string; - module?: string; + name?: string; + type?: string; environment?: string; - configData?: string; + config?: 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 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 configData = formData.get("configData") as string; - const isActive = formData.get("isActive") === "true"; + const config = formData.get("config") as string; + const is_active = formData.get("is_active") === "true"; const remarks = formData.get("remarks") as string; const errors: ActionData["errors"] = {}; // 表单验证 - if (!configName || configName.trim() === "") { - errors.configName = "配置名称不能为空"; + if (!name || name.trim() === "") { + errors.name = "配置名称不能为空"; } - if (!module) { - errors.module = "请选择所属模块"; + if (!type) { + errors.type = "请选择所属模块"; } if (!environment) { errors.environment = "请选择环境"; } - if (!configData || configData.trim() === "") { - errors.configData = "配置数据不能为空"; + if (!config || config.trim() === "") { + errors.config = "配置数据不能为空"; } else { try { - JSON.parse(configData); + JSON.parse(config); } catch (e) { - errors.configData = "配置数据必须是有效的JSON格式"; + errors.config = "配置数据必须是有效的JSON格式"; } } @@ -216,31 +135,29 @@ export async function action({ request }: ActionFunctionArgs) { } try { - // 实际应用中,应调用API保存数据 - console.log("保存配置:", { configId, configName, module, environment, configData, isActive, remarks }); + const configData = { + name, + type, + environment, + config: JSON.parse(config), + is_active, + 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}`); - // } + 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); @@ -290,8 +207,7 @@ const JSON_TEMPLATES = { }; export default function ConfigNew() { - const { config, isEdit } = useLoaderData(); - + const { config, isEdit, types, environments } = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); const isSubmitting = navigation.state === "submitting"; @@ -306,16 +222,9 @@ export default function ConfigNew() { useEffect(() => { // 初始化配置数据 - if (config?.configData) { - setConfigDataValue(config.configData); - } - - // 初始化模块和环境的选中状态 - if (config?.module) { - setSelectedModule(config.module); - } - - if (config?.environment) { + if (config) { + setConfigDataValue(JSON.stringify(config.config, null, 2)); + setSelectedModule(config.type); setSelectedEnvironment(config.environment); } @@ -333,7 +242,6 @@ export default function ConfigNew() { }, feature_flags: ["new_ui", "analytics_v2"] }, null, 2)); - }, [config]); // 处理JSON数据变更 @@ -382,16 +290,6 @@ export default function ConfigNew() { setJsonError(null); }; - // 模块标签点击 - const handleModuleTagClick = (module: string) => { - setSelectedModule(module); - }; - - // 环境标签点击 - const handleEnvironmentTagClick = (env: string) => { - setSelectedEnvironment(env); - }; - // 显示JSON语法高亮 const renderJsonWithSyntaxHighlight = (json: string) => { try { @@ -452,18 +350,18 @@ export default function ConfigNew() { {/* 配置名称和状态 */}
- + - {actionData?.errors?.configName && ( -
{actionData.errors.configName}
+ {actionData?.errors?.name && ( +
{actionData.errors.name}
)}
唯一标识符,配置名称应使用英文,推荐使用下划线命名方式 @@ -471,18 +369,18 @@ export default function ConfigNew() {
- +
-
@@ -495,33 +393,33 @@ export default function ConfigNew() { {/* 所属模块 */}
- + setSelectedModule(e.target.value)} placeholder="请输入或选择所属模块" - readOnly required /> - {actionData?.errors?.module && ( -
{actionData.errors.module}
+ {actionData?.errors?.type && ( +
{actionData.errors.type}
)}
- {Object.entries(MODULE_LABELS).map(([value, label]) => ( + {types.map(type => ( ))}
@@ -542,23 +440,23 @@ export default function ConfigNew() { type="text" id="environmentDisplay" className={`form-input ${actionData?.errors?.environment ? 'input-error' : ''}`} - value={selectedEnvironment ? EXTENDED_ENVIRONMENT_LABELS[selectedEnvironment] || selectedEnvironment : ''} + value={selectedEnvironment} + onChange={(e) => setSelectedEnvironment(e.target.value)} placeholder="请输入或选择环境" - readOnly required /> {actionData?.errors?.environment && (
{actionData.errors.environment}
)}
- {Object.entries(EXTENDED_ENVIRONMENT_LABELS).map(([value, label]) => ( + {environments.map(env => ( ))}
@@ -569,14 +467,14 @@ export default function ConfigNew() { {/* 配置数据 */}
- +
{/* 左侧JSON编辑区 */}