diff --git a/frontend/src/pages/FormDesigner/components/ValidationRuleEditor.tsx b/frontend/src/components/FormDesigner/components/ValidationRuleEditor.tsx
similarity index 100%
rename from frontend/src/pages/FormDesigner/components/ValidationRuleEditor.tsx
rename to frontend/src/components/FormDesigner/components/ValidationRuleEditor.tsx
diff --git a/frontend/src/pages/FormDesigner/config.ts b/frontend/src/components/FormDesigner/config.ts
similarity index 96%
rename from frontend/src/pages/FormDesigner/config.ts
rename to frontend/src/components/FormDesigner/config.ts
index 682cf4e9..e66bafb3 100644
--- a/frontend/src/pages/FormDesigner/config.ts
+++ b/frontend/src/components/FormDesigner/config.ts
@@ -119,6 +119,7 @@ export const COMPONENT_LIST: ComponentMeta[] = [
category: '基础字段',
defaultConfig: {
dataSourceType: 'static',
+ checkboxLayout: 'horizontal', // 默认行内布局
options: [
{ label: '选项1', value: '1' },
{ label: '选项2', value: '2' },
@@ -161,6 +162,9 @@ export const COMPONENT_LIST: ComponentMeta[] = [
category: '基础字段',
defaultConfig: {
content: '这是一段文字',
+ textAlign: 'left', // 默认左对齐
+ fontSize: 14, // 默认字体大小 14px
+ textColor: '#000000', // 默认黑色
},
},
// 高级字段
diff --git a/frontend/src/pages/FormDesigner/hooks/useCascaderOptions.ts b/frontend/src/components/FormDesigner/hooks/useCascaderOptions.ts
similarity index 100%
rename from frontend/src/pages/FormDesigner/hooks/useCascaderOptions.ts
rename to frontend/src/components/FormDesigner/hooks/useCascaderOptions.ts
diff --git a/frontend/src/pages/FormDesigner/hooks/useFieldOptions.ts b/frontend/src/components/FormDesigner/hooks/useFieldOptions.ts
similarity index 100%
rename from frontend/src/pages/FormDesigner/hooks/useFieldOptions.ts
rename to frontend/src/components/FormDesigner/hooks/useFieldOptions.ts
diff --git a/frontend/src/components/FormDesigner/index.tsx b/frontend/src/components/FormDesigner/index.tsx
new file mode 100644
index 00000000..5964b885
--- /dev/null
+++ b/frontend/src/components/FormDesigner/index.tsx
@@ -0,0 +1,37 @@
+/**
+ * 表单设计器组件库入口
+ *
+ * 提供两个核心组件:
+ * 1. FormDesigner - 设计时组件,用于可视化设计表单
+ * 2. FormRenderer - 运行时组件,用于渲染和提交表单
+ */
+
+export { default as FormDesigner } from './Designer';
+export type { FormDesignerProps } from './Designer';
+
+export { default as FormRenderer } from './Renderer';
+export type { FormRendererProps } from './Renderer';
+
+// 导出类型定义
+export type {
+ FieldType,
+ FieldOption,
+ CascadeFieldOption,
+ DataSourceType,
+ ValidationType,
+ ValidationRule,
+ LinkageType,
+ LinkageCondition,
+ LinkageRule,
+ ApiDataSource,
+ PredefinedDataSource,
+ PredefinedCascadeDataSource,
+ FieldConfig,
+ FormConfig,
+ FormSchema,
+} from './types';
+
+// 导出组件配置
+export { COMPONENT_LIST, getComponentsByCategory } from './config';
+export type { ComponentMeta } from './config';
+
diff --git a/frontend/src/pages/FormDesigner/styles.css b/frontend/src/components/FormDesigner/styles.css
similarity index 88%
rename from frontend/src/pages/FormDesigner/styles.css
rename to frontend/src/components/FormDesigner/styles.css
index 5ae68566..5418bb4d 100644
--- a/frontend/src/pages/FormDesigner/styles.css
+++ b/frontend/src/components/FormDesigner/styles.css
@@ -2,6 +2,35 @@
* 表单设计器样式
*/
+/* 设计器容器 */
+.form-designer {
+ height: calc(100vh - 150px); /* 默认高度,适应带导航的页面 */
+ min-height: 600px;
+ display: flex;
+ flex-direction: column;
+}
+
+.form-designer-header {
+ padding: 12px 16px;
+ background: #fff;
+ border-bottom: 1px solid #e8e8e8;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-shrink: 0;
+}
+
+.form-designer-title {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.form-designer-body {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
+
/* 组件面板 */
.form-designer-component-panel {
height: 100%;
diff --git a/frontend/src/pages/FormDesigner/types.ts b/frontend/src/components/FormDesigner/types.ts
similarity index 91%
rename from frontend/src/pages/FormDesigner/types.ts
rename to frontend/src/components/FormDesigner/types.ts
index 66ddd72d..4fe40957 100644
--- a/frontend/src/pages/FormDesigner/types.ts
+++ b/frontend/src/components/FormDesigner/types.ts
@@ -131,6 +131,10 @@ export interface FieldConfig {
maxCount?: number;
span?: number; // 栅格占位格数(该字段在栅格中占几列)
content?: string; // 文本内容(用于 text 组件)
+ textAlign?: 'left' | 'center' | 'right'; // 文字对齐方式(用于 text 组件)
+ fontSize?: number; // 文字大小(用于 text 组件)
+ textColor?: string; // 文字颜色(用于 text 组件)
+ checkboxLayout?: 'horizontal' | 'vertical'; // 多选框布局方式(horizontal:行内,vertical:块级)
columns?: number; // 栅格列数(用于 grid 组件)
columnSpans?: number[]; // 栅格每列宽度(用于 grid 组件,如 [2, 18, 2],总和不超过24)
gutter?: number; // 栅格间距(用于 grid 组件)
@@ -145,9 +149,15 @@ export interface FormConfig {
size?: 'small' | 'middle' | 'large';
}
-// 表单 Schema
+// 表单 Schema(标准化数据格式)
export interface FormSchema {
+ version: string; // Schema 版本号
formConfig: FormConfig;
fields: FieldConfig[];
+ metadata?: { // 元数据(可选)
+ createdAt?: string;
+ updatedAt?: string;
+ author?: string;
+ };
}
diff --git a/frontend/src/pages/FormDesigner/index.tsx b/frontend/src/pages/FormDesigner/index.tsx
index eaf6ffc7..c8b7c1c6 100644
--- a/frontend/src/pages/FormDesigner/index.tsx
+++ b/frontend/src/pages/FormDesigner/index.tsx
@@ -1,576 +1,349 @@
/**
- * 表单设计器主入口
+ * FormDesigner 组件使用示例页面
*
- * 功能:
- * - 左侧:组件面板
- * - 中间:设计画布
- * - 右侧:属性配置
- * - 导入/导出 JSON Schema
+ * 展示如何使用 @/components/FormDesigner 中的组件:
+ * - FormDesigner: 表单设计器(设计时)
+ * - FormRenderer: 表单渲染器(运行时)
*/
-import React, { useState, useCallback, useEffect } from 'react';
-import { Button, Space, message, Modal } from 'antd';
-import {
- SaveOutlined,
- DownloadOutlined,
- UploadOutlined,
- EyeOutlined,
- ClearOutlined,
-} from '@ant-design/icons';
-import { DndContext, DragEndEvent, DragOverlay, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
-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 { COMPONENT_LIST } from './config';
-import type { FieldConfig, FormConfig, FormSchema } from './types';
+import React, { useState } from 'react';
+import { message, Tabs, Card, Button, Modal, Space, Divider } from 'antd';
+import { FormDesigner, FormRenderer, type FormSchema } from '@/components/FormDesigner';
-const FormDesigner: React.FC = () => {
- // 表单字段列表
- const [fields, setFields] = useState
([]);
-
- // 选中的字段
- const [selectedFieldId, setSelectedFieldId] = useState();
-
- // 剪贴板(用于复制粘贴)
- const [clipboard, setClipboard] = useState(null);
-
- // 表单配置
- const [formConfig, setFormConfig] = useState({
- labelAlign: 'right',
- size: 'middle',
- formWidth: 600, // 表单弹窗宽度(像素)
- });
+const FormDesignerExamplesPage: React.FC = () => {
+ // ==================== 示例 1: 表单设计器 ====================
+ const [designerSchema, setDesignerSchema] = useState();
- // 预览模式
- const [previewVisible, setPreviewVisible] = useState(false);
-
- // 拖拽传感器
- const sensors = useSensors(
- useSensor(PointerSensor, {
- activationConstraint: {
- distance: 8,
- },
- })
- );
-
- // 生成唯一 ID
- const generateId = () => `field_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
-
- // 处理拖拽结束
- const handleDragEnd = useCallback((event: DragEndEvent) => {
- const { active, over } = event;
-
- if (!over) return;
-
- const overId = over.id.toString();
-
- // 从组件面板拖入新组件
- if (active.data.current?.isNew) {
- const componentType = active.id.toString().replace('new-', '');
- const componentMeta = COMPONENT_LIST.find(c => c.type === componentType);
-
- if (componentMeta) {
- const newField: FieldConfig = {
- id: generateId(),
- type: componentMeta.type,
- label: componentMeta.label,
- name: componentType + '_' + Date.now(),
- ...componentMeta.defaultConfig,
- };
-
- // 检测是否拖入栅格列
- if (overId.startsWith('grid-') && overId.includes('-col-')) {
- const match = overId.match(/grid-(.+)-col-(\d+)/);
- if (match) {
- const [, gridId, colIndexStr] = match;
- const colIndex = parseInt(colIndexStr, 10);
-
- setFields(prev => prev.map(field => {
- if (field.id === gridId && field.type === 'grid') {
- const newChildren = [...(field.children || [])];
- if (!newChildren[colIndex]) {
- newChildren[colIndex] = [];
- }
- newChildren[colIndex] = [...newChildren[colIndex], newField];
- return { ...field, children: newChildren };
- }
- return field;
- }));
- setSelectedFieldId(newField.id);
- message.success(`已添加${componentMeta.label}到栅格列`);
- return;
- }
- }
-
- // 拖入主画布
- setFields(prev => [...prev, newField]);
- setSelectedFieldId(newField.id);
- message.success(`已添加${componentMeta.label}`);
- }
- }
- // 画布内拖拽排序
- else if (over.id === 'canvas' || fields.some(f => f.id === over.id)) {
- const oldIndex = fields.findIndex(f => f.id === active.id);
- const newIndex = fields.findIndex(f => f.id === over.id);
-
- if (oldIndex !== -1 && newIndex !== -1 && oldIndex !== newIndex) {
- setFields(arrayMove(fields, oldIndex, newIndex));
- }
- }
- }, [fields]);
-
- // 选中字段
- const handleSelectField = useCallback((fieldId: string) => {
- setSelectedFieldId(fieldId);
- }, []);
-
- // 深拷贝字段(包括嵌套的栅格子字段)
- const deepCopyField = useCallback((field: FieldConfig): FieldConfig => {
- const newField: FieldConfig = {
- ...field,
- id: generateId(),
- name: field.name + '_copy_' + Date.now(),
- };
-
- // 如果是栅格布局,递归复制子字段
- if (field.type === 'grid' && field.children) {
- newField.children = field.children.map(columnFields =>
- columnFields.map(childField => deepCopyField(childField))
- );
- }
-
- return newField;
- }, []);
-
- // 复制字段到剪贴板
- const handleCopyField = useCallback((fieldId: string) => {
- // 递归查找字段
- const findField = (fieldList: FieldConfig[], id: string): FieldConfig | null => {
- for (const field of fieldList) {
- if (field.id === id) {
- return field;
- }
- // 如果是栅格布局,递归查找子字段
- if (field.type === 'grid' && field.children) {
- for (const columnFields of field.children) {
- const found = findField(columnFields, id);
- if (found) return found;
- }
- }
- }
- return null;
- };
-
- const field = findField(fields, fieldId);
- if (field) {
- setClipboard(field);
- message.success('已复制字段到剪贴板');
- }
- }, [fields]);
-
- // 粘贴字段到主列表末尾
- const handlePasteToCanvas = useCallback(() => {
- if (!clipboard) {
- message.warning('剪贴板为空');
- return;
- }
-
- const newField = deepCopyField(clipboard);
- setFields(prev => [...prev, newField]);
- setSelectedFieldId(newField.id);
- message.success('已粘贴字段');
- }, [clipboard, deepCopyField]);
-
- // 粘贴字段到栅格列
- const handlePasteToGrid = useCallback((gridId: string, colIndex: number) => {
- if (!clipboard) {
- message.warning('剪贴板为空');
- return;
- }
-
- const newField = deepCopyField(clipboard);
-
- setFields(prev => prev.map(field => {
- if (field.id === gridId && field.type === 'grid') {
- const newChildren = [...(field.children || [])];
- if (!newChildren[colIndex]) {
- newChildren[colIndex] = [];
- }
- newChildren[colIndex] = [...newChildren[colIndex], newField];
- return { ...field, children: newChildren };
- }
- return field;
- }));
-
- setSelectedFieldId(newField.id);
- message.success('已粘贴字段到栅格');
- }, [clipboard, deepCopyField]);
-
- // 删除字段(支持删除栅格内的字段)
- const handleDeleteField = useCallback((fieldId: string) => {
- let deleted = false;
-
- // 递归删除函数
- const deleteFieldRecursive = (fieldList: FieldConfig[]): FieldConfig[] => {
- return fieldList
- .filter(f => {
- if (f.id === fieldId) {
- deleted = true;
- return false;
- }
- return true;
- })
- .map(f => {
- // 如果是栅格布局,递归处理其子字段
- if (f.type === 'grid' && f.children) {
- return {
- ...f,
- children: f.children.map(columnFields =>
- deleteFieldRecursive(columnFields)
- ),
- };
- }
- return f;
- });
- };
-
- setFields(prev => deleteFieldRecursive(prev));
-
- if (deleted) {
- if (selectedFieldId === fieldId) {
- setSelectedFieldId(undefined);
- }
- message.success('已删除字段');
- }
- }, [selectedFieldId]);
-
- // 更新字段属性(支持更新栅格内的字段)
- const handleFieldChange = useCallback((updatedField: FieldConfig) => {
- // 🐛 调试:打印字段更新
- console.log('🔄 [FormDesigner] 字段更新:', updatedField);
- if (updatedField.validationRules && updatedField.validationRules.length > 0) {
- console.log('✅ [FormDesigner] 字段包含验证规则:', updatedField.validationRules);
- }
-
- // 递归更新函数
- const updateFieldRecursive = (fieldList: FieldConfig[]): FieldConfig[] => {
- return fieldList.map(f => {
- if (f.id === updatedField.id) {
- return updatedField;
- }
- // 如果是栅格布局,递归处理其子字段
- if (f.type === 'grid' && f.children) {
- return {
- ...f,
- children: f.children.map(columnFields =>
- updateFieldRecursive(columnFields)
- ),
- };
- }
- return f;
- });
- };
-
- setFields(prev => {
- const newFields = updateFieldRecursive(prev);
- console.log('💾 [FormDesigner] 字段列表已更新');
- return newFields;
- });
- }, []);
-
- // 更新表单配置
- const handleFormConfigChange = useCallback((newConfig: Partial) => {
- setFormConfig(prev => ({ ...prev, ...newConfig }));
- }, []);
-
- // 保存表单
- const handleSave = useCallback(() => {
- if (fields.length === 0) {
- message.warning('表单为空,请先添加字段');
- return;
- }
-
- const schema: FormSchema = {
- formConfig,
- fields,
- };
-
- console.log('保存的表单 Schema:', JSON.stringify(schema, null, 2));
- message.success('表单已保存!请查看控制台');
-
- // TODO: 调用后端 API 保存
- }, [fields, formConfig]);
-
- // 导出 JSON
- const handleExport = useCallback(() => {
- if (fields.length === 0) {
- message.warning('表单为空,无法导出');
- return;
- }
-
- const schema: FormSchema = {
- formConfig,
- fields,
- };
-
- const dataStr = JSON.stringify(schema, null, 2);
- const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
- const exportFileDefaultName = `form-schema-${Date.now()}.json`;
-
- const linkElement = document.createElement('a');
- linkElement.setAttribute('href', dataUri);
- linkElement.setAttribute('download', exportFileDefaultName);
- linkElement.click();
-
- message.success('JSON Schema 已导出');
- }, [fields, formConfig]);
-
- // 导入 JSON
- const handleImport = useCallback(() => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = '.json';
-
- input.onchange = (e: any) => {
- const file = e.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = (event: any) => {
- try {
- const schema: FormSchema = JSON.parse(event.target.result);
-
- if (!schema.fields || !Array.isArray(schema.fields)) {
- message.error('无效的表单 Schema 格式');
- return;
- }
-
- setFields(schema.fields);
- setFormConfig(schema.formConfig || formConfig);
- setSelectedFieldId(undefined);
- message.success('JSON Schema 导入成功');
- } catch (error) {
- message.error('JSON 格式错误');
- console.error('导入错误:', error);
- }
- };
- reader.readAsText(file);
- }
- };
-
- input.click();
- }, [formConfig]);
-
- // 清空表单
- const handleClear = useCallback(() => {
- Modal.confirm({
- title: '确认清空',
- content: '确定要清空所有字段吗?此操作不可恢复。',
- onOk: () => {
- setFields([]);
- setSelectedFieldId(undefined);
- message.info('表单已清空');
- },
- });
- }, []);
-
- // 递归查找选中的字段(包括栅格内的字段)
- const findFieldById = (fieldList: FieldConfig[], id?: string): FieldConfig | undefined => {
- if (!id) return undefined;
-
- for (const field of fieldList) {
- if (field.id === id) {
- return field;
- }
- // 如果是栅格布局,递归查找子字段
- if (field.type === 'grid' && field.children) {
- for (const columnFields of field.children) {
- const found = findFieldById(columnFields, id);
- if (found) return found;
- }
- }
- }
- return undefined;
+ const handleDesignerSave = async (schema: FormSchema) => {
+ console.log('💾 保存的表单 Schema:', JSON.stringify(schema, null, 2));
+ message.success('表单设计已保存!请查看控制台');
+ setPreviewSchema(schema); // 保存后更新预览
+ // 实际项目中这里会调用后端 API 保存
+ // await formSchemaService.save(schema);
};
- const selectedField = findFieldById(fields, selectedFieldId) || null;
+ // ==================== 示例 2: 表单渲染器(静态) ====================
+ const [previewSchema, setPreviewSchema] = useState();
+ const [staticFormData, setStaticFormData] = useState>({});
- // 获取所有字段(扁平化,包括grid中的嵌套字段)
- const getAllFields = useCallback((fieldList: FieldConfig[]): FieldConfig[] => {
- const result: FieldConfig[] = [];
-
- fieldList.forEach(field => {
- // 只添加实际的表单字段,不包括布局组件
- if (field.type !== 'divider' && field.type !== 'grid' && field.type !== 'text') {
- result.push(field);
- }
-
- // 如果是grid,递归获取其子字段
- if (field.type === 'grid' && field.children) {
- field.children.forEach(columnFields => {
- result.push(...getAllFields(columnFields));
- });
- }
- });
-
- return result;
- }, []);
+ const handleStaticFormSubmit = async (data: Record) => {
+ console.log('📤 静态表单提交数据:', data);
+ message.success('表单提交成功!请查看控制台');
+ };
- const allFields = getAllFields(fields);
+ // ==================== 示例 3: 工作流表单(弹窗) ====================
+ const [workflowModalVisible, setWorkflowModalVisible] = useState(false);
- // 快捷键支持
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- // 检查是否在输入框、文本域等可编辑元素中
- const target = e.target as HTMLElement;
- const isEditable =
- target.tagName === 'INPUT' ||
- target.tagName === 'TEXTAREA' ||
- target.isContentEditable;
+ const workflowFormSchema: FormSchema = {
+ version: '1.0',
+ formConfig: {
+ labelAlign: 'right',
+ size: 'middle',
+ formWidth: 600
+ },
+ fields: [
+ {
+ id: 'wf_field_1',
+ type: 'input',
+ label: '工作流名称',
+ name: 'workflowName',
+ placeholder: '请输入工作流名称',
+ required: true
+ },
+ {
+ id: 'wf_field_2',
+ type: 'select',
+ label: '目标环境',
+ name: 'environment',
+ placeholder: '请选择环境',
+ required: true,
+ dataSourceType: 'static',
+ options: [
+ { label: '开发环境', value: 'dev' },
+ { label: '测试环境', value: 'test' },
+ { label: '生产环境', value: 'prod' }
+ ]
+ },
+ {
+ id: 'wf_field_3',
+ type: 'textarea',
+ label: '执行说明',
+ name: 'description',
+ placeholder: '请输入执行说明',
+ rows: 4
+ },
+ ],
+ };
- // 如果在可编辑元素中,不触发快捷键
- if (isEditable) return;
+ const handleWorkflowSubmit = async (data: Record) => {
+ console.log('🚀 工作流启动参数:', data);
+ message.success('工作流已启动!');
+ setWorkflowModalVisible(false);
+ };
- const isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
- const ctrlKey = isMac ? e.metaKey : e.ctrlKey;
+ // ==================== 示例 4: 只读模式 ====================
+ const readonlySchema: FormSchema = {
+ version: '1.0',
+ formConfig: {
+ labelAlign: 'right',
+ size: 'middle',
+ formWidth: 600
+ },
+ fields: [
+ {
+ id: 'ro_field_1',
+ type: 'input',
+ label: '申请人',
+ name: 'applicant'
+ },
+ {
+ id: 'ro_field_2',
+ type: 'date',
+ label: '申请日期',
+ name: 'applyDate'
+ },
+ {
+ id: 'ro_field_3',
+ type: 'select',
+ label: '审批状态',
+ name: 'approvalStatus',
+ dataSourceType: 'static',
+ options: [
+ { label: '待审批', value: 'pending' },
+ { label: '已通过', value: 'approved' },
+ { label: '已拒绝', value: 'rejected' }
+ ]
+ },
+ {
+ id: 'ro_field_4',
+ type: 'textarea',
+ label: '审批意见',
+ name: 'approvalComment'
+ },
+ ],
+ };
- // Ctrl+C / Cmd+C: 复制选中的字段
- if (ctrlKey && e.key === 'c' && selectedFieldId) {
- e.preventDefault();
- handleCopyField(selectedFieldId);
- }
+ const readonlyData = {
+ applicant: '张三',
+ applyDate: '2024-07-20',
+ approvalStatus: 'approved',
+ approvalComment: '同意申请,准予执行。',
+ };
- // Ctrl+V / Cmd+V: 粘贴到画布
- if (ctrlKey && e.key === 'v' && clipboard) {
- e.preventDefault();
- handlePasteToCanvas();
- }
+ // ==================== 示例 5: 复杂表单(带栅格布局) ====================
+ const [complexFormData, setComplexFormData] = useState>({});
+
+ const complexFormSchema: FormSchema = {
+ version: '1.0',
+ formConfig: {
+ labelAlign: 'right',
+ size: 'middle',
+ formWidth: 800,
+ title: '用户信息登记表'
+ },
+ fields: [
+ {
+ id: 'grid_1',
+ type: 'grid',
+ label: '基本信息',
+ name: 'grid_basic',
+ columns: 2,
+ columnSpans: [12, 12],
+ gutter: 16,
+ children: [
+ [
+ { id: 'f1', type: 'input', label: '姓名', name: 'name', placeholder: '请输入姓名', required: true },
+ { id: 'f2', type: 'number', label: '年龄', name: 'age', placeholder: '请输入年龄', min: 1, max: 150 },
+ ],
+ [
+ { id: 'f3', type: 'radio', label: '性别', name: 'gender', dataSourceType: 'static', options: [{ label: '男', value: 'male' }, { label: '女', value: 'female' }] },
+ { id: 'f4', type: 'date', label: '出生日期', name: 'birthday', placeholder: '请选择日期' },
+ ],
+ ],
+ },
+ {
+ id: 'grid_2',
+ type: 'grid',
+ label: '联系方式',
+ name: 'grid_contact',
+ columns: 2,
+ columnSpans: [12, 12],
+ gutter: 16,
+ children: [
+ [
+ { id: 'f5', type: 'input', label: '手机号', name: 'phone', placeholder: '请输入手机号' },
+ { id: 'f6', type: 'input', label: '邮箱', name: 'email', placeholder: '请输入邮箱' },
+ ],
+ [
+ { id: 'f7', type: 'input', label: '微信', name: 'wechat', placeholder: '请输入微信号' },
+ { id: 'f8', type: 'input', label: 'QQ', name: 'qq', placeholder: '请输入QQ号' },
+ ],
+ ],
+ },
+ {
+ id: 'f9',
+ type: 'textarea',
+ label: '个人简介',
+ name: 'bio',
+ placeholder: '请输入个人简介',
+ rows: 4,
+ },
+ ],
+ };
- // Delete: 删除选中的字段
- if (e.key === 'Delete' && selectedFieldId) {
- e.preventDefault();
- handleDeleteField(selectedFieldId);
- }
- };
+ const handleComplexFormSubmit = async (data: Record) => {
+ console.log('📋 复杂表单提交数据:', data);
+ message.success('表单提交成功!');
+ };
- window.addEventListener('keydown', handleKeyDown);
- return () => {
- window.removeEventListener('keydown', handleKeyDown);
- };
- }, [selectedFieldId, clipboard, handleCopyField, handlePasteToCanvas, handleDeleteField]);
+ // ==================== Tab 配置 ====================
+ const tabItems = [
+ {
+ key: '1',
+ label: '📝 示例 1: 表单设计器',
+ children: (
+
+ 表单设计器(FormDesigner)
+
+ 可视化拖拽设计表单,支持导入/导出 JSON Schema,支持字段属性配置、验证规则、联动规则等。
+
+
+
+
+ ),
+ },
+ {
+ key: '2',
+ label: '🎨 示例 2: 静态表单渲染',
+ children: (
+
+ 表单渲染器(静态模式)
+
+ 根据设计器保存的 Schema 渲染表单,支持数据收集、验证和提交。
+
+
+ {previewSchema ? (
+
+ ) : (
+
+ 请先在"示例 1"中设计表单并保存
+
+ )}
+
+ ),
+ },
+ {
+ key: '3',
+ label: '🚀 示例 3: 工作流表单',
+ children: (
+
+ 工作流表单(弹窗模式)
+
+ 模拟工作流启动场景,在 Modal 中使用表单渲染器收集参数。
+
+
+
+
+
使用场景
+
+ - 工作流启动参数收集
+ - 任务审批表单
+ - 快捷操作弹窗
+
+
+
+
+ setWorkflowModalVisible(false)}
+ footer={null}
+ width={workflowFormSchema.formConfig.formWidth || 600}
+ >
+ setWorkflowModalVisible(false)}
+ showCancel
+ submitText="启动"
+ cancelText="取消"
+ />
+
+
+ ),
+ },
+ {
+ key: '4',
+ label: '👁️ 示例 4: 只读模式',
+ children: (
+
+ 只读模式
+
+ 查看已提交的表单数据,所有字段禁用编辑,不显示提交按钮。
+
+
+
+
使用场景
+
+ - 审批流程详情查看
+ - 历史数据展示
+ - 表单归档查看
+
+
+
+
+
+ ),
+ },
+ {
+ key: '5',
+ label: '📋 示例 5: 复杂表单',
+ children: (
+
+ 复杂表单(带栅格布局)
+
+ 展示包含栅格布局的复杂表单,支持多列排版。
+
+
+
+
+ ),
+ },
+ ];
return (
-
- {/* 顶部工具栏 */}
-
-
- 表单设计器
-
-
- } onClick={handleImport}>
- 导入 JSON
-
- } onClick={handleExport}>
- 导出 JSON
-
- } onClick={() => setPreviewVisible(true)}>
- 预览
-
- } danger onClick={handleClear}>
- 清空
-
- } onClick={handleSave}>
- 保存
-
-
+
+
+
FormDesigner 组件使用示例
+
+ 本页面展示如何使用 @/components/FormDesigner 中的 FormDesigner 和 FormRenderer 组件。
+
-
- {/* 主体区域 */}
-
-
- {/* 左侧组件面板 */}
-
-
-
-
- {/* 中间设计画布 */}
-
-
- {/* 右侧属性面板 */}
-
-
-
- {/* 拖拽时的视觉反馈 */}
-
-
- {/* 预览模态框 */}
-
setPreviewVisible(false)}
- width={formConfig.formWidth || 600}
- footer={null}
- afterOpenChange={(open) => {
- if (open) {
- console.log('🎬 [Modal] 打开预览,当前字段列表:', fields);
-
- // 递归检查所有字段(包括grid内的嵌套字段)
- const checkFieldsRecursive = (fieldList: FieldConfig[], level = 0) => {
- fieldList.forEach(field => {
- const indent = ' '.repeat(level);
- console.log(`${indent}📦 [Modal] 字段: "${field.label}" (${field.type})`, field);
-
- if (field.validationRules && field.validationRules.length > 0) {
- console.log(`${indent}📋 [Modal] ✅ 包含验证规则:`, field.validationRules);
- } else {
- console.log(`${indent}📋 [Modal] ❌ 没有验证规则`);
- }
-
- // 递归检查grid的子字段
- if (field.type === 'grid' && field.children) {
- field.children.forEach((columnFields, colIndex) => {
- console.log(`${indent} 🔸 Grid列 ${colIndex + 1}:`);
- checkFieldsRecursive(columnFields, level + 2);
- });
- }
- });
- };
-
- checkFieldsRecursive(fields);
- }
- }}
- >
-
-
+
);
};
-export default FormDesigner;
+export default FormDesignerExamplesPage;