表单设计器

This commit is contained in:
dengqichen 2025-10-24 00:06:19 +08:00
parent 8c9813ed55
commit b4836b1c91
6 changed files with 179 additions and 35 deletions

View File

@ -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>
); );
}; };

View File

@ -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');
@ -247,6 +253,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 (

View File

@ -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}

View File

@ -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>
); );
}; };

View File

@ -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);
}; };

View File

@ -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>