diff --git a/frontend/src/pages/Workflow2/Design/components/NodeConfigModal.tsx b/frontend/src/pages/Workflow2/Design/components/NodeConfigModal.tsx index 9c635380..a03cf6c4 100644 --- a/frontend/src/pages/Workflow2/Design/components/NodeConfigModal.tsx +++ b/frontend/src/pages/Workflow2/Design/components/NodeConfigModal.tsx @@ -1,12 +1,26 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { Drawer, Tabs, Button, Space, message } from 'antd'; import { SaveOutlined, ReloadOutlined, CloseOutlined } from '@ant-design/icons'; -import { BetaSchemaForm } from '@ant-design/pro-components'; -import { convertJsonSchemaToColumns } from '@/utils/jsonSchemaUtils'; +import { FormItem, Input, NumberPicker, Select, FormLayout, Switch } from '@formily/antd-v5'; +import { createForm } from '@formily/core'; +import { createSchemaField, FormProvider, ISchema } from '@formily/react'; import type { FlowNode, FlowNodeData } from '../types'; import type { WorkflowNodeDefinition } from '../nodes/types'; import { isConfigurableNode } from '../nodes/types'; +// 创建Schema组件 +const SchemaField = createSchemaField({ + components: { + FormItem, + Input, + NumberPicker, + Select, + FormLayout, + Switch, + 'Input.TextArea': Input.TextArea, + }, +}); + interface NodeConfigModalProps { visible: boolean; node: FlowNode | null; @@ -14,12 +28,6 @@ interface NodeConfigModalProps { onOk: (nodeId: string, updatedData: Partial) => void; } -interface FormData { - configs?: Record; - inputMapping?: Record; - outputMapping?: Record; -} - const NodeConfigModal: React.FC = ({ visible, node, @@ -28,46 +36,85 @@ const NodeConfigModal: React.FC = ({ }) => { const [loading, setLoading] = useState(false); const [activeTab, setActiveTab] = useState('config'); - const [formData, setFormData] = useState({}); // 获取节点定义 const nodeDefinition: WorkflowNodeDefinition | null = node?.data?.nodeDefinition || null; + // 创建Formily表单实例 + const configForm = useMemo(() => createForm(), []); + const inputForm = useMemo(() => createForm(), []); + const outputForm = useMemo(() => createForm(), []); + + // 初始化表单数据 useEffect(() => { if (visible && node && nodeDefinition) { - // 从节点数据中获取现有配置 const nodeData = node.data || {}; - // 准备默认的基本信息配置 + // 准备默认配置 const defaultConfig = { - nodeName: nodeDefinition.nodeName, // 默认节点名称 - nodeCode: nodeDefinition.nodeCode, // 默认节点编码 - description: nodeDefinition.description // 默认节点描述 + nodeName: nodeDefinition.nodeName, + nodeCode: nodeDefinition.nodeCode, + description: nodeDefinition.description }; - // 合并默认值和已保存的配置 - setFormData({ - configs: { ...defaultConfig, ...(nodeData.configs || {}) }, - inputMapping: nodeData.inputMapping || {}, - outputMapping: nodeData.outputMapping || {}, - }); - } else { - setFormData({}); + // 设置表单初始值 + configForm.setInitialValues({ ...defaultConfig, ...(nodeData.configs || {}) }); + configForm.reset(); + + if (isConfigurableNode(nodeDefinition)) { + inputForm.setInitialValues(nodeData.inputMapping || {}); + inputForm.reset(); + + outputForm.setInitialValues(nodeData.outputMapping || {}); + outputForm.reset(); + } } - }, [visible, node, nodeDefinition]); + }, [visible, node, nodeDefinition, configForm, inputForm, outputForm]); - const handleSubmit = () => { - if (!node) return; + // 递归处理表单值,将JSON字符串转换为对象 + const processFormValues = (values: Record, schema: ISchema | undefined): Record => { + const result: Record = {}; + + if (!schema?.properties || typeof schema.properties !== 'object') return values; + + Object.entries(values).forEach(([key, value]) => { + const propSchema = (schema.properties as Record)?.[key]; + + // 如果是object类型且值是字符串,尝试解析 + if (propSchema?.type === 'object' && typeof value === 'string') { + try { + result[key] = JSON.parse(value); + } catch { + result[key] = value; // 解析失败保持原值 + } + } else { + result[key] = value; + } + }); + + return result; + }; + + const handleSubmit = async () => { + if (!node || !nodeDefinition) return; try { setLoading(true); + // 获取表单值并转换 + const configs = processFormValues(configForm.values, nodeDefinition.configSchema); + const inputMapping = isConfigurableNode(nodeDefinition) + ? processFormValues(inputForm.values, nodeDefinition.inputMappingSchema) + : {}; + const outputMapping = isConfigurableNode(nodeDefinition) + ? processFormValues(outputForm.values, nodeDefinition.outputMappingSchema) + : {}; + const updatedData: Partial = { - // 更新节点标签为配置中的节点名称 - label: formData.configs?.nodeName || node.data.label, - configs: formData.configs, - inputMapping: formData.inputMapping, - outputMapping: formData.outputMapping, + label: configs.nodeName || node.data.label, + configs, + inputMapping, + outputMapping, }; onOk(node.id, updatedData); @@ -83,40 +130,118 @@ const NodeConfigModal: React.FC = ({ }; const handleReset = () => { - if (nodeDefinition) { - const defaultConfig = { - nodeName: nodeDefinition.nodeName, - nodeCode: nodeDefinition.nodeCode, - description: nodeDefinition.description + configForm.reset(); + inputForm.reset(); + outputForm.reset(); + message.info('已重置为初始值'); + }; + + // 将JSON Schema转换为Formily Schema(扩展配置) + const convertToFormilySchema = (jsonSchema: ISchema): ISchema => { + const schema: ISchema = { + type: 'object', + properties: {} + }; + + if (!jsonSchema.properties || typeof jsonSchema.properties !== 'object') return schema; + + Object.entries(jsonSchema.properties as Record).forEach(([key, prop]: [string, any]) => { + const field: any = { + title: prop.title || key, + description: prop.description, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + tooltip: prop.description, + labelCol: 6, // 标签占6列 + wrapperCol: 18, // 内容占18列(剩余空间) + }, }; - - setFormData({ - configs: defaultConfig, - inputMapping: {}, - outputMapping: {}, - }); - } - }; - const handleConfigChange = (values: Record) => { - setFormData(prev => ({ - ...prev, - configs: values - })); - }; + // 根据类型设置组件 + switch (prop.type) { + case 'string': + if (prop.enum) { + field['x-component'] = 'Select'; + field['x-component-props'] = { + style: { width: '100%' }, // 统一宽度 + options: prop.enum.map((v: any, i: number) => ({ + label: prop.enumNames?.[i] || v, + value: v + })) + }; + } else if (prop.format === 'password') { + field['x-component'] = 'Input'; + field['x-component-props'] = { + type: 'password', + style: { width: '100%' } // 统一宽度 + }; + } else { + field['x-component'] = 'Input'; + field['x-component-props'] = { + style: { width: '100%' } // 统一宽度 + }; + } + break; + case 'number': + case 'integer': + field['x-component'] = 'NumberPicker'; + field['x-component-props'] = { + min: prop.minimum, + max: prop.maximum, + style: { width: '100%' } // 统一宽度 + }; + break; + case 'boolean': + field['x-component'] = 'Switch'; + break; + case 'object': + field['x-component'] = 'Input.TextArea'; + field['x-component-props'] = { + rows: 4, + style: { width: '100%' }, // 统一宽度 + placeholder: '请输入JSON格式,例如:{"key": "value"}' + }; + // ✅ 关键修复:将object类型的default值转换为JSON字符串 + if (prop.default !== undefined && typeof prop.default === 'object') { + field.default = JSON.stringify(prop.default, null, 2); + } else { + field.default = prop.default; + } + // Formily会自动处理object的序列化 + field['x-validator'] = (value: any) => { + if (!value) return true; + if (typeof value === 'string') { + try { + JSON.parse(value); + return true; + } catch { + return '请输入有效的JSON格式'; + } + } + return true; + }; + break; + default: + field['x-component'] = 'Input'; + field['x-component-props'] = { + style: { width: '100%' } // 统一宽度 + }; + } - const handleInputMappingChange = (values: Record) => { - setFormData(prev => ({ - ...prev, - inputMapping: values - })); - }; + // 设置默认值(非object类型) + if (prop.type !== 'object' && prop.default !== undefined) { + field.default = prop.default; + } - const handleOutputMappingChange = (values: Record) => { - setFormData(prev => ({ - ...prev, - outputMapping: values - })); + // 设置必填 + if (Array.isArray(jsonSchema.required) && jsonSchema.required.includes(key)) { + field.required = true; + } + + (schema.properties as Record)[key] = field; + }); + + return schema; }; if (!nodeDefinition) { @@ -128,18 +253,15 @@ const NodeConfigModal: React.FC = ({ { key: 'config', label: '基本配置', - children: ( + children: activeTab === 'config' ? (
- handleConfigChange(allValues)} - submitter={false} - /> + + + + +
- ), + ) : null, }, ]; @@ -149,18 +271,15 @@ const NodeConfigModal: React.FC = ({ tabItems.push({ key: 'input', label: '输入映射', - children: ( + children: activeTab === 'input' ? (
- handleInputMappingChange(allValues)} - submitter={false} - /> + + + + +
- ), + ) : null, }); } @@ -168,18 +287,15 @@ const NodeConfigModal: React.FC = ({ tabItems.push({ key: 'output', label: '输出映射', - children: ( + children: activeTab === 'output' ? (
- handleOutputMappingChange(allValues)} - submitter={false} - /> + + + + +
- ), + ) : null, }); } } diff --git a/frontend/src/pages/Workflow2/Design/hooks/useWorkflowLoad.ts b/frontend/src/pages/Workflow2/Design/hooks/useWorkflowLoad.ts index 7faa3531..8da0ad99 100644 --- a/frontend/src/pages/Workflow2/Design/hooks/useWorkflowLoad.ts +++ b/frontend/src/pages/Workflow2/Design/hooks/useWorkflowLoad.ts @@ -3,7 +3,7 @@ import { message } from 'antd'; import * as definitionService from '../../Definition/service'; import type { FlowNode, FlowEdge } from '../types'; import type { WorkflowDefinition } from '../../Definition/types'; -import { NODE_DEFINITIONS } from '../nodes'; +import { NODE_DEFINITIONS, NodeType, getNodeCategory } from '../nodes'; interface LoadedWorkflowData { nodes: FlowNode[]; @@ -15,47 +15,88 @@ export const useWorkflowLoad = () => { const [loading, setLoading] = useState(false); const [workflowDefinition, setWorkflowDefinition] = useState(null); + // 获取已注册的节点类型 + const getRegisteredNodeTypes = useCallback((): NodeType[] => { + return NODE_DEFINITIONS.map(def => def.nodeType); + }, []); + // 从后端数据转换为React Flow格式 const convertToFlowFormat = useCallback((definition: WorkflowDefinition): LoadedWorkflowData => { - const nodes: FlowNode[] = (definition.graph?.nodes || []).map(node => { - // 根据nodeType查找对应的节点定义 - const nodeDefinition = NODE_DEFINITIONS.find(def => def.nodeType === node.nodeType); - - return { - id: node.id.toString(), - type: node.nodeType, - position: node.position || { x: 100, y: 100 }, - data: { - label: node.nodeName, - nodeType: node.nodeType as any, - category: getNodeCategory(node.nodeType) as any, - icon: nodeDefinition?.uiConfig.style.icon || getNodeIcon(node.nodeType), - color: nodeDefinition?.uiConfig.style.fill || getNodeColor(node.nodeType), - configs: node.configs || {}, - inputMapping: node.inputMapping || {}, - outputMapping: node.outputMapping || {}, - // 添加节点定义引用,用于配置弹窗 - nodeDefinition - }, - selected: false, - dragging: false - }; - }); - - const edges: FlowEdge[] = (definition.graph?.edges || []).map(edge => ({ - id: edge.id, - source: edge.from, // 后端使用from字段作为source - target: edge.to, // 后端使用to字段作为target - type: 'default', - animated: true, - data: { - label: edge.name || '连接', - condition: { - type: 'DEFAULT', - priority: 0 + const registeredTypes = getRegisteredNodeTypes(); + + const nodes: FlowNode[] = (definition.graph?.nodes || []) + .filter(node => { + // 过滤掉不支持的节点类型 + const isSupported = registeredTypes.includes(node.nodeType as any); + if (!isSupported) { + console.warn(`节点类型 "${node.nodeType}" 不支持,已跳过`, node); } - } - })); + return isSupported; + }) + .map(node => { + // 根据nodeType查找对应的节点定义 + const nodeDefinition = NODE_DEFINITIONS.find(def => def.nodeType === node.nodeType); + + return { + id: node.id.toString(), + type: node.nodeType, + position: node.position || { x: 100, y: 100 }, + data: { + label: node.nodeName, + nodeType: node.nodeType as any, + category: getNodeCategory(node.nodeType) as any, + icon: nodeDefinition?.uiConfig.style.icon || getNodeIcon(node.nodeType), + color: nodeDefinition?.uiConfig.style.fill || getNodeColor(node.nodeType), + configs: node.configs || {}, + inputMapping: node.inputMapping || {}, + outputMapping: node.outputMapping || {}, + // 添加节点定义引用,用于配置弹窗 + nodeDefinition + }, + selected: false, + dragging: false + }; + }); + + // 创建节点ID集合(用于过滤边) + const nodeIds = new Set(nodes.map(n => n.id)); + + const edges: FlowEdge[] = (definition.graph?.edges || []) + .filter(edge => { + // 只保留两端节点都存在的边 + const sourceExists = nodeIds.has(edge.from); + const targetExists = nodeIds.has(edge.to); + if (!sourceExists || !targetExists) { + console.warn(`边的节点不存在,已跳过:`, edge); + } + return sourceExists && targetExists; + }) + .map(edge => { + const edgeConfig: any = edge.config || {}; + const condition = edgeConfig.condition || { type: 'DEFAULT', priority: 10 }; + const label = condition.expression || edge.name || ''; + + return { + id: edge.id, + source: edge.from, // 后端使用from字段作为source + target: edge.to, // 后端使用to字段作为target + type: 'smoothstep', + animated: true, + style: { + stroke: '#94a3b8', + strokeWidth: 2, + }, + markerEnd: { + type: 'arrowclosed' as const, + color: '#94a3b8', + }, + label, + data: { + label, + condition + } + }; + }); return { nodes, @@ -64,36 +105,19 @@ export const useWorkflowLoad = () => { }; }, []); - // 根据节点类型获取分类 - const getNodeCategory = (nodeType: string): string => { - const categoryMap: Record = { - 'START_EVENT': 'EVENT', - 'END_EVENT': 'EVENT', - 'USER_TASK': 'TASK', - 'SERVICE_TASK': 'TASK', - 'SCRIPT_TASK': 'TASK', - 'DEPLOY_NODE': 'TASK', - 'JENKINS_BUILD': 'TASK', - 'GATEWAY_NODE': 'GATEWAY', - 'SUB_PROCESS': 'CONTAINER', - 'CALL_ACTIVITY': 'CONTAINER' - }; - return categoryMap[nodeType] || 'TASK'; - }; - // 根据节点类型获取图标(图标名称格式) const getNodeIcon = (nodeType: string): string => { const iconMap: Record = { - 'START_EVENT': 'play-circle', - 'END_EVENT': 'stop-circle', - 'USER_TASK': 'user', - 'SERVICE_TASK': 'api', - 'SCRIPT_TASK': 'code', - 'DEPLOY_NODE': 'build', - 'JENKINS_BUILD': 'jenkins', - 'GATEWAY_NODE': 'gateway', - 'SUB_PROCESS': 'container', - 'CALL_ACTIVITY': 'phone' + [NodeType.START_EVENT]: 'play-circle', + [NodeType.END_EVENT]: 'stop-circle', + [NodeType.USER_TASK]: 'user', + [NodeType.SERVICE_TASK]: 'api', + [NodeType.SCRIPT_TASK]: 'code', + [NodeType.DEPLOY_NODE]: 'build', + [NodeType.JENKINS_BUILD]: 'jenkins', + [NodeType.GATEWAY_NODE]: 'gateway', + [NodeType.SUB_PROCESS]: 'container', + [NodeType.CALL_ACTIVITY]: 'phone' }; return iconMap[nodeType] || 'api'; }; @@ -101,16 +125,16 @@ export const useWorkflowLoad = () => { // 根据节点类型获取颜色 const getNodeColor = (nodeType: string): string => { const colorMap: Record = { - 'START_EVENT': '#52c41a', - 'END_EVENT': '#ff4d4f', - 'USER_TASK': '#722ed1', - 'SERVICE_TASK': '#fa8c16', - 'SCRIPT_TASK': '#8b5cf6', - 'DEPLOY_NODE': '#1890ff', - 'JENKINS_BUILD': '#f97316', - 'GATEWAY_NODE': '#84cc16', - 'SUB_PROCESS': '#ec4899', - 'CALL_ACTIVITY': '#14b8a6' + [NodeType.START_EVENT]: '#52c41a', + [NodeType.END_EVENT]: '#ff4d4f', + [NodeType.USER_TASK]: '#722ed1', + [NodeType.SERVICE_TASK]: '#fa8c16', + [NodeType.SCRIPT_TASK]: '#8b5cf6', + [NodeType.DEPLOY_NODE]: '#1890ff', + [NodeType.JENKINS_BUILD]: '#52c41a', + [NodeType.GATEWAY_NODE]: '#84cc16', + [NodeType.SUB_PROCESS]: '#ec4899', + [NodeType.CALL_ACTIVITY]: '#14b8a6' }; return colorMap[nodeType] || '#6b7280'; }; @@ -125,7 +149,15 @@ export const useWorkflowLoad = () => { const flowData = convertToFlowFormat(definition); - message.success(`工作流 "${definition.name}" 加载成功`); + // 提示不支持的节点 + const totalNodes = definition.graph?.nodes?.length || 0; + const loadedNodes = flowData.nodes.length; + if (totalNodes > loadedNodes) { + message.warning(`工作流加载成功,但有 ${totalNodes - loadedNodes} 个节点类型不支持,已跳过`); + } else { + message.success(`工作流 "${definition.name}" 加载成功`); + } + return flowData; } catch (error) { console.error('加载工作流失败:', error); diff --git a/frontend/src/pages/Workflow2/Design/index.tsx b/frontend/src/pages/Workflow2/Design/index.tsx index 24f1741e..aaab7191 100644 --- a/frontend/src/pages/Workflow2/Design/index.tsx +++ b/frontend/src/pages/Workflow2/Design/index.tsx @@ -478,38 +478,63 @@ const WorkflowDesignInner: React.FC = () => { // 键盘快捷键支持 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { + // 检查焦点是否在输入框、文本域或可编辑元素内 + const target = e.target as HTMLElement; + const tagName = target.tagName?.toUpperCase(); + const isInputElement = tagName === 'INPUT' || + tagName === 'TEXTAREA' || + target.isContentEditable || + target.getAttribute('contenteditable') === 'true'; + const isInDrawer = target.closest('.ant-drawer-body') !== null; + const isInModal = target.closest('.ant-modal') !== null; + + // 在抽屉或模态框内,且在输入元素中时,允许原生行为 + const shouldSkipShortcut = isInputElement || isInDrawer || isInModal; + const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; const ctrlKey = isMac ? e.metaKey : e.ctrlKey; - // Ctrl+Z / Cmd+Z - 撤销 + // Ctrl+Z / Cmd+Z - 撤销(仅在画布区域) if (ctrlKey && e.key === 'z' && !e.shiftKey) { - e.preventDefault(); - handleUndo(); + if (!shouldSkipShortcut) { + e.preventDefault(); + handleUndo(); + } } - // Ctrl+Shift+Z / Cmd+Shift+Z - 重做 + // Ctrl+Shift+Z / Cmd+Shift+Z - 重做(仅在画布区域) else if (ctrlKey && e.key === 'z' && e.shiftKey) { - e.preventDefault(); - handleRedo(); + if (!shouldSkipShortcut) { + e.preventDefault(); + handleRedo(); + } } - // Ctrl+C / Cmd+C - 复制 + // Ctrl+C / Cmd+C - 复制节点(仅在画布区域) else if (ctrlKey && e.key === 'c') { - e.preventDefault(); - handleCopy(); + if (!shouldSkipShortcut) { + e.preventDefault(); + handleCopy(); + } } - // Ctrl+V / Cmd+V - 粘贴 + // Ctrl+V / Cmd+V - 粘贴节点(仅在画布区域) else if (ctrlKey && e.key === 'v') { - e.preventDefault(); - handlePaste(); + if (!shouldSkipShortcut) { + e.preventDefault(); + handlePaste(); + } } - // Ctrl+A / Cmd+A - 全选 + // Ctrl+A / Cmd+A - 全选节点(仅在画布区域) else if (ctrlKey && e.key === 'a') { - e.preventDefault(); - handleSelectAll(); + if (!shouldSkipShortcut) { + e.preventDefault(); + handleSelectAll(); + } } - // Delete / Backspace - 删除 + // Delete / Backspace - 删除节点(仅在画布区域) else if (e.key === 'Delete' || e.key === 'Backspace') { - e.preventDefault(); - handleDelete(); + if (!shouldSkipShortcut) { + e.preventDefault(); + handleDelete(); + } } }; diff --git a/frontend/src/pages/Workflow2/Design/nodes/types.ts b/frontend/src/pages/Workflow2/Design/nodes/types.ts index f3fa0374..ae702bc5 100644 --- a/frontend/src/pages/Workflow2/Design/nodes/types.ts +++ b/frontend/src/pages/Workflow2/Design/nodes/types.ts @@ -6,23 +6,46 @@ export enum NodeCategory { CONTAINER = 'CONTAINER' } -// 节点类型 +// 节点类型(完整定义,包含所有可能的类型) export enum NodeType { START_EVENT = 'START_EVENT', END_EVENT = 'END_EVENT', + USER_TASK = 'USER_TASK', + SERVICE_TASK = 'SERVICE_TASK', SCRIPT_TASK = 'SCRIPT_TASK', + DEPLOY_NODE = 'DEPLOY_NODE', JENKINS_BUILD = 'JENKINS_BUILD', GATEWAY_NODE = 'GATEWAY_NODE', + SUB_PROCESS = 'SUB_PROCESS', + CALL_ACTIVITY = 'CALL_ACTIVITY' } +// 节点类型到分类的映射 +export const NODE_CATEGORY_MAP: Record = { + [NodeType.START_EVENT]: NodeCategory.EVENT, + [NodeType.END_EVENT]: NodeCategory.EVENT, + [NodeType.USER_TASK]: NodeCategory.TASK, + [NodeType.SERVICE_TASK]: NodeCategory.TASK, + [NodeType.SCRIPT_TASK]: NodeCategory.TASK, + [NodeType.DEPLOY_NODE]: NodeCategory.TASK, + [NodeType.JENKINS_BUILD]: NodeCategory.TASK, + [NodeType.GATEWAY_NODE]: NodeCategory.GATEWAY, + [NodeType.SUB_PROCESS]: NodeCategory.CONTAINER, + [NodeType.CALL_ACTIVITY]: NodeCategory.CONTAINER, +}; + +// 获取节点分类的工具函数 +export const getNodeCategory = (nodeType: NodeType | string): NodeCategory => { + return NODE_CATEGORY_MAP[nodeType as NodeType] || NodeCategory.TASK; +}; + // JSON Schema 定义 -export interface JSONSchema { - type: string; - properties?: Record; - required?: string[]; - title?: string; - description?: string; -} +/** + * JSON Schema 接口 - 直接使用 Formily 官方提供的 ISchema 类型 + * 从 @formily/react 导入(它会从 @formily/json-schema 重新导出) + */ +import type { ISchema } from '@formily/react'; +export type JSONSchema = ISchema; // UI 配置 export interface NodeSize { diff --git a/frontend/src/utils/jsonSchemaUtils.ts b/frontend/src/utils/jsonSchemaUtils.ts index ffa3aa92..757d6012 100644 --- a/frontend/src/utils/jsonSchemaUtils.ts +++ b/frontend/src/utils/jsonSchemaUtils.ts @@ -117,7 +117,7 @@ export const convertJsonSchemaToColumns = (schema: JsonSchema): ProFormColumnsTy formItemProps: { required: schema.required?.includes(key), }, - initialValue: value.default, + // 不在这里设置initialValue,在表单层面设置 readonly: value.readOnly, }; @@ -289,10 +289,30 @@ export const convertJsonSchemaToColumns = (schema: JsonSchema): ProFormColumnsTy case 'object': return { ...baseConfig, - valueType: 'jsonCode', + valueType: 'textarea', fieldProps: { - placeholder: `请输入${value.title || key}`, + placeholder: `请输入JSON格式数据,例如:{"key": "value"}`, disabled: value.readOnly, + rows: 4, + autoSize: { minRows: 4, maxRows: 8 }, + }, + // 转换值:object → JSON string(显示) + transform: (value: any) => { + if (typeof value === 'object' && value !== null) { + return JSON.stringify(value, null, 2); + } + return value; + }, + // 转换值:JSON string → object(保存) + convertValue: (val: any) => { + if (typeof val === 'string') { + try { + return JSON.parse(val); + } catch { + return value.default || {}; + } + } + return val; }, };