diff --git a/frontend/src/components/FormDesigner/Preview.tsx b/frontend/src/components/FormDesigner/Preview.tsx index 8db383e9..03575272 100644 --- a/frontend/src/components/FormDesigner/Preview.tsx +++ b/frontend/src/components/FormDesigner/Preview.tsx @@ -35,6 +35,7 @@ import { useImperativeHandle, forwardRef } from 'react'; import { Form, message } from 'antd'; import type { FieldConfig, FormConfig } from './types'; import { useFormCore } from './hooks/useFormCore'; +import { computeFormLayout } from './utils/formLayoutHelper'; import FormFieldsRenderer from './components/FormFieldsRenderer'; import './styles.css'; @@ -96,20 +97,18 @@ const FormPreview = forwardRef(({ fields, form ); } - const formLayout = formConfig.labelAlign === 'top' ? 'vertical' : 'horizontal'; - const labelColSpan = formConfig.labelAlign === 'left' ? 6 : formConfig.labelAlign === 'right' ? 6 : undefined; - const wrapperColSpan = formConfig.labelAlign === 'left' || formConfig.labelAlign === 'right' ? 18 : undefined; + const layoutConfig = computeFormLayout(formConfig); return (
{formConfig.title && ( diff --git a/frontend/src/components/FormDesigner/Renderer.tsx b/frontend/src/components/FormDesigner/Renderer.tsx index 1ca8b9a3..9974af74 100644 --- a/frontend/src/components/FormDesigner/Renderer.tsx +++ b/frontend/src/components/FormDesigner/Renderer.tsx @@ -50,6 +50,7 @@ import type { ComponentMeta } from './config'; import { COMPONENT_LIST } from './config'; import { ComponentsContext } from './Designer'; import { useFormCore } from './hooks/useFormCore'; +import { computeFormLayout } from './utils/formLayoutHelper'; import FormFieldsRenderer from './components/FormFieldsRenderer'; import './styles.css'; @@ -230,21 +231,19 @@ const FormRenderer = forwardRef((props, ref) ); } - const formLayout = formConfig.labelAlign === 'top' ? 'vertical' : 'horizontal'; - const labelColSpan = formConfig.labelAlign === 'left' ? 6 : formConfig.labelAlign === 'right' ? 6 : undefined; - const wrapperColSpan = formConfig.labelAlign === 'left' || formConfig.labelAlign === 'right' ? 18 : undefined; + const layoutConfig = computeFormLayout(formConfig); return (
{formConfig.title && ( @@ -260,7 +259,10 @@ const FormRenderer = forwardRef((props, ref) {(showSubmit || showCancel) && (
{showSubmit && ( diff --git a/frontend/src/components/FormDesigner/components/FieldRenderer.tsx b/frontend/src/components/FormDesigner/components/FieldRenderer.tsx index 96f2b65d..f8e3887d 100644 --- a/frontend/src/components/FormDesigner/components/FieldRenderer.tsx +++ b/frontend/src/components/FormDesigner/components/FieldRenderer.tsx @@ -8,32 +8,19 @@ import { ComponentsContext } from '../Designer'; import { Form, Input, - InputNumber, - Select, - Radio, - Checkbox, - DatePicker, - TimePicker, - Switch, - Slider, - Rate, - Upload, - Cascader, Divider, - Button, Typography, } from 'antd'; -import { UploadOutlined } from '@ant-design/icons'; import type { FieldConfig } from '../types'; import GridField from './GridField'; import { useFieldOptions } from '../hooks/useFieldOptions'; import { useCascaderOptions, useCascaderLoadData } from '../hooks/useCascaderOptions'; +import { fieldRenderStrategyRegistry } from '../renderers/FieldRenderStrategyRegistry'; +import type { FieldRenderContext } from '../renderers/IFieldRenderStrategy'; import '../styles.css'; const { Text } = Typography; -const { TextArea } = Input; - interface FieldRendererProps { field: FieldConfig; value?: any; @@ -63,40 +50,27 @@ const FieldRenderer: React.FC = ({ hasClipboard = false, duplicateFieldIds = new Set(), }) => { - // 获取字段选项(支持静态和动态数据源) + // 获取字段选项和级联选项(支持静态和动态数据源) const options = useFieldOptions(field); - - // 获取级联选择器选项和懒加载函数 const cascadeOptions = useCascaderOptions(field); const loadData = useCascaderLoadData(field); - // 🔌 检查是否有自定义渲染组件(插件式扩展) + // 🔌 优先检查自定义渲染组件(插件式扩展) const allComponents = useContext(ComponentsContext); const componentMeta = allComponents.find(c => c.type === field.type); if (componentMeta?.FieldRendererComponent) { const CustomRenderer = componentMeta.FieldRendererComponent; - console.log('✅ FieldRenderer: 使用自定义渲染组件', { - fieldType: field.type, - isPreview, - hasRenderer: !!componentMeta.FieldRendererComponent - }); return ; } - // 布局组件特殊处理 + // 布局组件特殊处理(这些组件不通过策略模式) if (field.type === 'divider') { - const showText = field.showText !== false; // 默认显示文字 + const showText = field.showText !== false; const dividerText = field.dividerText || field.label || '分割线'; const dividerColor = field.dividerColor || 'rgba(5, 5, 5, 0.06)'; - return ( - + {showText ? dividerText : null} ); @@ -104,10 +78,7 @@ const FieldRenderer: React.FC = ({ if (field.type === 'text') { return ( -
+
= ({ ); } + // 使用策略模式渲染字段(工厂模式获取策略) + const strategy = fieldRenderStrategyRegistry.get(field.type); + + // 计算只读样式 + const isReadonly = field.readonly || false; + const readonlyStyle = isReadonly ? { + backgroundColor: '#f5f5f5', + cursor: 'not-allowed', + color: '#00000040', + } : undefined; + + // 构造渲染上下文 + const renderContext: FieldRenderContext = { + field, + value, + onChange, + isDisabled: field.disabled || false, + isReadonly, + readonlyStyle, + options, + cascadeOptions, + loadData, + }; + + // 渲染字段组件 const renderField = () => { - switch (field.type) { - case 'input': + if (strategy) { + return strategy.render(renderContext); + } + + // 兜底策略:未注册的字段类型使用默认 Input return ( onChange?.(e.target.value)} + readOnly={isReadonly} + disabled={field.disabled && !isReadonly} /> ); - - case 'textarea': - return ( -