表单设计器
This commit is contained in:
parent
8c9813ed55
commit
b4836b1c91
@ -11,7 +11,6 @@ import type { ComponentMeta } from '../config';
|
|||||||
import { getComponentsByCategory } from '../config';
|
import { getComponentsByCategory } from '../config';
|
||||||
import '../styles.css';
|
import '../styles.css';
|
||||||
|
|
||||||
const { Panel } = Collapse;
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
// 可拖拽组件项
|
// 可拖拽组件项
|
||||||
@ -62,22 +61,26 @@ const ComponentPanel: React.FC = () => {
|
|||||||
defaultActiveKey={['基础字段', '高级字段', '布局字段']}
|
defaultActiveKey={['基础字段', '高级字段', '布局字段']}
|
||||||
ghost
|
ghost
|
||||||
bordered={false}
|
bordered={false}
|
||||||
>
|
items={categoryOrder
|
||||||
{categoryOrder.map((category) => {
|
.map((category) => {
|
||||||
const components = componentsByCategory[category];
|
const components = componentsByCategory[category];
|
||||||
if (!components || components.length === 0) return null;
|
if (!components || components.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return {
|
||||||
<Panel header={category} key={category}>
|
key: category,
|
||||||
<div className="form-designer-component-list">
|
label: category,
|
||||||
{components.map((component) => (
|
children: (
|
||||||
<DraggableComponent key={component.type} component={component} />
|
<div className="form-designer-component-list">
|
||||||
))}
|
{components.map((component) => (
|
||||||
</div>
|
<DraggableComponent key={component.type} component={component} />
|
||||||
</Panel>
|
))}
|
||||||
);
|
</div>
|
||||||
})}
|
),
|
||||||
</Collapse>
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean) as any[]
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -234,6 +234,12 @@ const FormPreview: React.FC<FormPreviewProps> = ({ fields, formConfig }) => {
|
|||||||
// 合并基础验证规则和自定义验证规则
|
// 合并基础验证规则和自定义验证规则
|
||||||
const customRules = convertValidationRules(field.validationRules);
|
const customRules = convertValidationRules(field.validationRules);
|
||||||
|
|
||||||
|
// 🐛 调试:打印验证规则
|
||||||
|
if (field.validationRules && field.validationRules.length > 0) {
|
||||||
|
console.log(`📋 字段 "${field.label}" 的验证规则:`, field.validationRules);
|
||||||
|
console.log(`✅ 转换后的规则:`, customRules);
|
||||||
|
}
|
||||||
|
|
||||||
// 检查自定义验证规则中是否已经包含必填验证
|
// 检查自定义验证规则中是否已经包含必填验证
|
||||||
const hasRequiredRule = field.validationRules?.some(rule => rule.type === 'required');
|
const hasRequiredRule = field.validationRules?.some(rule => rule.type === 'required');
|
||||||
|
|
||||||
@ -248,6 +254,11 @@ const FormPreview: React.FC<FormPreviewProps> = ({ fields, formConfig }) => {
|
|||||||
|
|
||||||
const allRules = [...baseRules, ...customRules];
|
const allRules = [...baseRules, ...customRules];
|
||||||
|
|
||||||
|
// 🐛 调试:打印最终规则
|
||||||
|
if (allRules.length > 0) {
|
||||||
|
console.log(`🎯 字段 "${field.label}" 最终验证规则:`, allRules);
|
||||||
|
}
|
||||||
|
|
||||||
// 普通表单组件使用 Form.Item 包裹
|
// 普通表单组件使用 Form.Item 包裹
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Row, Col, Form } from 'antd';
|
import { Row, Col, Form } from 'antd';
|
||||||
import type { FieldConfig, FormConfig } from '../types';
|
import type { Rule } from 'antd/es/form';
|
||||||
|
import type { FieldConfig, FormConfig, ValidationRule } from '../types';
|
||||||
import FieldRenderer from './FieldRenderer';
|
import FieldRenderer from './FieldRenderer';
|
||||||
|
|
||||||
interface GridFieldPreviewProps {
|
interface GridFieldPreviewProps {
|
||||||
@ -32,6 +33,51 @@ const GridFieldPreview: React.FC<GridFieldPreviewProps> = ({
|
|||||||
return 24 / columns; // 平均分配
|
return 24 / columns; // 平均分配
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 将验证规则转换为Ant Design的Rule数组(和FormPreview保持一致)
|
||||||
|
const convertValidationRules = (validationRules?: ValidationRule[]): Rule[] => {
|
||||||
|
if (!validationRules || validationRules.length === 0) return [];
|
||||||
|
|
||||||
|
return validationRules.map(rule => {
|
||||||
|
const antdRule: Rule = {
|
||||||
|
type: rule.type === 'email' ? 'email' : rule.type === 'url' ? 'url' : undefined,
|
||||||
|
message: rule.message || `请输入正确的${rule.type}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (rule.type) {
|
||||||
|
case 'required':
|
||||||
|
antdRule.required = true;
|
||||||
|
break;
|
||||||
|
case 'pattern':
|
||||||
|
if (rule.value) {
|
||||||
|
antdRule.pattern = new RegExp(rule.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'min':
|
||||||
|
antdRule.type = 'number';
|
||||||
|
antdRule.min = rule.value;
|
||||||
|
break;
|
||||||
|
case 'max':
|
||||||
|
antdRule.type = 'number';
|
||||||
|
antdRule.max = rule.value;
|
||||||
|
break;
|
||||||
|
case 'minLength':
|
||||||
|
antdRule.min = rule.value;
|
||||||
|
break;
|
||||||
|
case 'maxLength':
|
||||||
|
antdRule.max = rule.value;
|
||||||
|
break;
|
||||||
|
case 'phone':
|
||||||
|
antdRule.pattern = /^1[3-9]\d{9}$/;
|
||||||
|
break;
|
||||||
|
case 'idCard':
|
||||||
|
antdRule.pattern = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return antdRule;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const renderFieldItem = (childField: FieldConfig) => {
|
const renderFieldItem = (childField: FieldConfig) => {
|
||||||
// 布局组件直接渲染,不需要 Form.Item
|
// 布局组件直接渲染,不需要 Form.Item
|
||||||
if (['text', 'divider', 'grid'].includes(childField.type)) {
|
if (['text', 'divider', 'grid'].includes(childField.type)) {
|
||||||
@ -45,6 +91,34 @@ const GridFieldPreview: React.FC<GridFieldPreviewProps> = ({
|
|||||||
// 根据表单配置决定布局方式
|
// 根据表单配置决定布局方式
|
||||||
const isVertical = formConfig?.labelAlign === 'top';
|
const isVertical = formConfig?.labelAlign === 'top';
|
||||||
|
|
||||||
|
// 合并基础验证规则和自定义验证规则(和FormPreview保持一致)
|
||||||
|
const customRules = convertValidationRules(childField.validationRules);
|
||||||
|
|
||||||
|
// 🐛 调试:打印验证规则
|
||||||
|
if (childField.validationRules && childField.validationRules.length > 0) {
|
||||||
|
console.log(`📋 [GridFieldPreview] 字段 "${childField.label}" 的验证规则:`, childField.validationRules);
|
||||||
|
console.log(`✅ [GridFieldPreview] 转换后的规则:`, customRules);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查自定义验证规则中是否已经包含必填验证
|
||||||
|
const hasRequiredRule = childField.validationRules?.some(rule => rule.type === 'required');
|
||||||
|
|
||||||
|
// 基础规则:只有在没有自定义必填验证时,才使用字段属性中的"是否必填"
|
||||||
|
const baseRules: Rule[] = [];
|
||||||
|
if (childField.required && !hasRequiredRule) {
|
||||||
|
baseRules.push({
|
||||||
|
required: true,
|
||||||
|
message: `请输入${childField.label}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const allRules = [...baseRules, ...customRules];
|
||||||
|
|
||||||
|
// 🐛 调试:打印最终规则
|
||||||
|
if (allRules.length > 0) {
|
||||||
|
console.log(`🎯 [GridFieldPreview] 字段 "${childField.label}" 最终验证规则:`, allRules);
|
||||||
|
}
|
||||||
|
|
||||||
// 普通表单字段用 Form.Item 包裹
|
// 普通表单字段用 Form.Item 包裹
|
||||||
// 栅格内字段:根据对齐方式使用不同的布局
|
// 栅格内字段:根据对齐方式使用不同的布局
|
||||||
return (
|
return (
|
||||||
@ -55,12 +129,7 @@ const GridFieldPreview: React.FC<GridFieldPreviewProps> = ({
|
|||||||
colon={true}
|
colon={true}
|
||||||
labelCol={isVertical ? { span: 24 } : { span: 8 }}
|
labelCol={isVertical ? { span: 24 } : { span: 8 }}
|
||||||
wrapperCol={isVertical ? { span: 24 } : { span: 16 }}
|
wrapperCol={isVertical ? { span: 24 } : { span: 16 }}
|
||||||
rules={[
|
rules={allRules}
|
||||||
{
|
|
||||||
required: childField.required,
|
|
||||||
message: `请输入${childField.label}`,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<FieldRenderer
|
<FieldRenderer
|
||||||
field={childField}
|
field={childField}
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import ValidationRuleEditor from './ValidationRuleEditor';
|
|||||||
import LinkageRuleEditor from './LinkageRuleEditor';
|
import LinkageRuleEditor from './LinkageRuleEditor';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
interface PropertyPanelProps {
|
interface PropertyPanelProps {
|
||||||
selectedField: FieldConfig | null;
|
selectedField: FieldConfig | null;
|
||||||
@ -62,6 +61,8 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
predefinedCascadeDataSource: selectedField.predefinedCascadeDataSource || {
|
predefinedCascadeDataSource: selectedField.predefinedCascadeDataSource || {
|
||||||
sourceType: '',
|
sourceType: '',
|
||||||
},
|
},
|
||||||
|
validationRules: selectedField.validationRules || [],
|
||||||
|
linkageRules: selectedField.linkageRules || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
form.setFieldsValue(formValues);
|
form.setFieldsValue(formValues);
|
||||||
@ -132,9 +133,18 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
key !== 'predefinedDataSource' &&
|
key !== 'predefinedDataSource' &&
|
||||||
key !== 'predefinedCascadeDataSource') {
|
key !== 'predefinedCascadeDataSource') {
|
||||||
(updatedField as any)[key] = changedValues[key];
|
(updatedField as any)[key] = changedValues[key];
|
||||||
|
|
||||||
|
// 🐛 调试:打印验证规则和联动规则的更新
|
||||||
|
if (key === 'validationRules') {
|
||||||
|
console.log('💾 [PropertyPanel] 验证规则已更新:', changedValues[key]);
|
||||||
|
}
|
||||||
|
if (key === 'linkageRules') {
|
||||||
|
console.log('💾 [PropertyPanel] 联动规则已更新:', changedValues[key]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('🔄 [PropertyPanel] 字段配置更新完成:', updatedField);
|
||||||
onFieldChange(updatedField);
|
onFieldChange(updatedField);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -659,14 +669,22 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
<Text strong>属性配置</Text>
|
<Text strong>属性配置</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs defaultActiveKey="field" style={{ padding: '0 0 0 16px' }}>
|
<Tabs
|
||||||
<TabPane tab="字段属性" key="field">
|
defaultActiveKey="field"
|
||||||
{renderFieldProperties()}
|
style={{ padding: '0 0 0 16px' }}
|
||||||
</TabPane>
|
items={[
|
||||||
<TabPane tab="表单属性" key="form">
|
{
|
||||||
{renderFormProperties()}
|
key: 'field',
|
||||||
</TabPane>
|
label: '字段属性',
|
||||||
</Tabs>
|
children: renderFieldProperties(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'form',
|
||||||
|
label: '表单属性',
|
||||||
|
children: renderFormProperties(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,17 +21,21 @@ const ValidationRuleEditor: React.FC<ValidationRuleEditorProps> = ({ value = [],
|
|||||||
message: '',
|
message: '',
|
||||||
trigger: 'blur',
|
trigger: 'blur',
|
||||||
};
|
};
|
||||||
onChange?.([...value, newRule]);
|
const newRules = [...value, newRule];
|
||||||
|
console.log('➕ [ValidationRuleEditor] 添加验证规则:', newRules);
|
||||||
|
onChange?.(newRules);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteRule = (index: number) => {
|
const handleDeleteRule = (index: number) => {
|
||||||
const newRules = value.filter((_, i) => i !== index);
|
const newRules = value.filter((_, i) => i !== index);
|
||||||
|
console.log('🗑️ [ValidationRuleEditor] 删除验证规则:', newRules);
|
||||||
onChange?.(newRules);
|
onChange?.(newRules);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRuleChange = (index: number, field: keyof ValidationRule, fieldValue: any) => {
|
const handleRuleChange = (index: number, field: keyof ValidationRule, fieldValue: any) => {
|
||||||
const newRules = [...value];
|
const newRules = [...value];
|
||||||
newRules[index] = { ...newRules[index], [field]: fieldValue };
|
newRules[index] = { ...newRules[index], [field]: fieldValue };
|
||||||
|
console.log(`✏️ [ValidationRuleEditor] 修改验证规则 [${field}]:`, newRules);
|
||||||
onChange?.(newRules);
|
onChange?.(newRules);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -248,6 +248,12 @@ const FormDesigner: React.FC = () => {
|
|||||||
|
|
||||||
// 更新字段属性(支持更新栅格内的字段)
|
// 更新字段属性(支持更新栅格内的字段)
|
||||||
const handleFieldChange = useCallback((updatedField: FieldConfig) => {
|
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[] => {
|
const updateFieldRecursive = (fieldList: FieldConfig[]): FieldConfig[] => {
|
||||||
return fieldList.map(f => {
|
return fieldList.map(f => {
|
||||||
@ -267,7 +273,11 @@ const FormDesigner: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
setFields(prev => updateFieldRecursive(prev));
|
setFields(prev => {
|
||||||
|
const newFields = updateFieldRecursive(prev);
|
||||||
|
console.log('💾 [FormDesigner] 字段列表已更新');
|
||||||
|
return newFields;
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 更新表单配置
|
// 更新表单配置
|
||||||
@ -527,6 +537,35 @@ const FormDesigner: React.FC = () => {
|
|||||||
onCancel={() => setPreviewVisible(false)}
|
onCancel={() => setPreviewVisible(false)}
|
||||||
width={formConfig.formWidth || 600}
|
width={formConfig.formWidth || 600}
|
||||||
footer={null}
|
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);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FormPreview fields={fields} formConfig={formConfig} />
|
<FormPreview fields={fields} formConfig={formConfig} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user