From 374a74e4aba3a4fe9ce1e89bdad0f32e1c2a1c0c Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 24 Oct 2025 01:12:11 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A8=E5=8D=95=E8=AE=BE=E8=AE=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/FormDesigner/Designer.tsx | 32 ++++++++--- .../components/ComponentPanel.tsx | 54 ++++++++++--------- .../FormDesigner/components/FormPreview.tsx | 36 ++++++------- .../src/components/FormDesigner/styles.css | 15 +++++- 4 files changed, 85 insertions(+), 52 deletions(-) diff --git a/frontend/src/components/FormDesigner/Designer.tsx b/frontend/src/components/FormDesigner/Designer.tsx index c34bb1fe..aa63b4ea 100644 --- a/frontend/src/components/FormDesigner/Designer.tsx +++ b/frontend/src/components/FormDesigner/Designer.tsx @@ -18,7 +18,7 @@ * ``` */ -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useState, useCallback, useEffect, useRef } from 'react'; import { Button, Space, message, Modal } from 'antd'; import { SaveOutlined, @@ -41,7 +41,7 @@ import { arrayMove } from '@dnd-kit/sortable'; import ComponentPanel from './components/ComponentPanel'; import DesignCanvas from './components/DesignCanvas'; import PropertyPanel from './components/PropertyPanel'; -import FormPreview from './components/FormPreview'; +import FormPreview, { type FormPreviewRef } from './components/FormPreview'; import { COMPONENT_LIST } from './config'; import type { FieldConfig, FormConfig, FormSchema } from './types'; import './styles.css'; @@ -63,6 +63,9 @@ const FormDesigner: React.FC = ({ showToolbar = true, extraActions }) => { + // 表单预览 ref + const formPreviewRef = useRef(null); + // 内部状态(非受控模式使用) const [internalFields, setInternalFields] = useState([]); const [internalFormConfig, setInternalFormConfig] = useState({ @@ -208,10 +211,10 @@ const FormDesigner: React.FC = ({ if (over.id === 'canvas') { newFields.push(newField); } else { - // 如果拖到某个字段上,插入到该字段位置 + // 如果拖到某个字段上,插入到该字段之后 const overIndex = newFields.findIndex((f) => f.id === over.id); if (overIndex >= 0) { - newFields.splice(overIndex, 0, newField); + newFields.splice(overIndex + 1, 0, newField); // 插入到目标字段之后 } else { newFields.push(newField); } @@ -685,9 +688,26 @@ const FormDesigner: React.FC = ({ open={previewVisible} onCancel={() => setPreviewVisible(false)} width={formConfig.formWidth || 600} - footer={null} + bodyStyle={{ + maxHeight: 'calc(100vh - 200px)', + overflowY: 'auto', + overflowX: 'hidden', + padding: 0, + }} + footer={ +
+ + + + +
+ } > - + ); diff --git a/frontend/src/components/FormDesigner/components/ComponentPanel.tsx b/frontend/src/components/FormDesigner/components/ComponentPanel.tsx index 48d3c820..b873f9c5 100644 --- a/frontend/src/components/FormDesigner/components/ComponentPanel.tsx +++ b/frontend/src/components/FormDesigner/components/ComponentPanel.tsx @@ -38,8 +38,8 @@ const DraggableComponent: React.FC<{ component: ComponentMeta }> = ({ component {...attributes} className="form-designer-component-item" > - - {component.label} + + {component.label} ); }; @@ -57,30 +57,32 @@ const ComponentPanel: React.FC = () => { 组件列表 - { - const components = componentsByCategory[category]; - if (!components || components.length === 0) return null; - - return { - key: category, - label: category, - children: ( -
- {components.map((component) => ( - - ))} -
- ), - }; - }) - .filter(Boolean) as any[] - } - /> +
+ { + const components = componentsByCategory[category]; + if (!components || components.length === 0) return null; + + return { + key: category, + label: category, + children: ( +
+ {components.map((component) => ( + + ))} +
+ ), + }; + }) + .filter(Boolean) as any[] + } + /> +
); }; diff --git a/frontend/src/components/FormDesigner/components/FormPreview.tsx b/frontend/src/components/FormDesigner/components/FormPreview.tsx index da903c98..d754518f 100644 --- a/frontend/src/components/FormDesigner/components/FormPreview.tsx +++ b/frontend/src/components/FormDesigner/components/FormPreview.tsx @@ -3,8 +3,8 @@ * 预览设计好的表单效果 */ -import React, { useState, useEffect } from 'react'; -import { Form, Button, message } from 'antd'; +import React, { useState, useEffect, useImperativeHandle, forwardRef } from 'react'; +import { Form, message } from 'antd'; import type { Rule } from 'antd/es/form'; import type { FieldConfig, FormConfig, ValidationRule, LinkageRule } from '../types'; import FieldRenderer from './FieldRenderer'; @@ -16,7 +16,12 @@ interface FormPreviewProps { formConfig: FormConfig; } -const FormPreview: React.FC = ({ fields, formConfig }) => { +export interface FormPreviewRef { + submit: () => Promise; + reset: () => void; +} + +const FormPreview = forwardRef(({ fields, formConfig }, ref) => { const [form] = Form.useForm(); const [formData, setFormData] = useState>({}); const [fieldStates, setFieldStates] = useState = ({ fields, formConfig }) => { message.info('表单已重置'); }; + // 暴露方法给父组件 + useImperativeHandle(ref, () => ({ + submit: handleSubmit, + reset: handleReset, + })); + // 将验证规则转换为Ant Design的Rule数组 const convertValidationRules = (validationRules?: ValidationRule[]): Rule[] => { if (!validationRules || validationRules.length === 0) return []; @@ -307,21 +318,6 @@ const FormPreview: React.FC = ({ fields, formConfig }) => { )} {renderFields(fields)} - - -
- - -
-
{Object.keys(formData).length > 0 && ( @@ -332,7 +328,9 @@ const FormPreview: React.FC = ({ fields, formConfig }) => { )} ); -}; +}); + +FormPreview.displayName = 'FormPreview'; export default FormPreview; diff --git a/frontend/src/components/FormDesigner/styles.css b/frontend/src/components/FormDesigner/styles.css index 5418bb4d..a906829b 100644 --- a/frontend/src/components/FormDesigner/styles.css +++ b/frontend/src/components/FormDesigner/styles.css @@ -44,6 +44,13 @@ padding: 16px; border-bottom: 1px solid #e8e8e8; background: #fff; + flex-shrink: 0; +} + +.form-designer-component-panel-body { + flex: 1; + overflow-y: auto; + overflow-x: hidden; } .form-designer-component-list { @@ -56,13 +63,19 @@ .form-designer-component-item { display: flex; align-items: center; - padding: 10px 12px; + justify-content: center; + padding: 8px 6px; background: #fff; border: 1px solid #d9d9d9; border-radius: 6px; transition: all 0.3s ease; user-select: none; cursor: move; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-height: 36px; } .form-designer-component-item:hover {