表单设计器

This commit is contained in:
dengqichen 2025-10-23 22:09:14 +08:00
parent 0dabb6aef7
commit b2efb2ffa7
4 changed files with 101 additions and 14 deletions

View File

@ -25,6 +25,7 @@ import {
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import type { FieldConfig } from '../types'; import type { FieldConfig } from '../types';
import GridField from './GridField'; import GridField from './GridField';
import '../styles.css';
const { Text } = Typography; const { Text } = Typography;

View File

@ -8,6 +8,7 @@ import { Form, Button, message } from 'antd';
import type { FieldConfig, FormConfig } from '../types'; import type { FieldConfig, FormConfig } from '../types';
import FieldRenderer from './FieldRenderer'; import FieldRenderer from './FieldRenderer';
import GridFieldPreview from './GridFieldPreview'; import GridFieldPreview from './GridFieldPreview';
import '../styles.css';
interface FormPreviewProps { interface FormPreviewProps {
fields: FieldConfig[]; fields: FieldConfig[];
@ -96,11 +97,12 @@ const FormPreview: React.FC<FormPreviewProps> = ({ fields, formConfig }) => {
const wrapperColSpan = formConfig.labelAlign === 'left' || formConfig.labelAlign === 'right' ? 18 : undefined; const wrapperColSpan = formConfig.labelAlign === 'left' || formConfig.labelAlign === 'right' ? 18 : undefined;
return ( return (
<div> <div style={{ padding: '24px 40px' }}>
<Form <Form
form={form} form={form}
layout={formLayout} layout={formLayout}
size={formConfig.size} size={formConfig.size}
colon={true}
labelAlign={formConfig.labelAlign === 'top' ? undefined : formConfig.labelAlign} labelAlign={formConfig.labelAlign === 'top' ? undefined : formConfig.labelAlign}
labelCol={labelColSpan ? { span: labelColSpan } : undefined} labelCol={labelColSpan ? { span: labelColSpan } : undefined}
wrapperCol={wrapperColSpan ? { span: wrapperColSpan } : undefined} wrapperCol={wrapperColSpan ? { span: wrapperColSpan } : undefined}
@ -111,13 +113,19 @@ const FormPreview: React.FC<FormPreviewProps> = ({ fields, formConfig }) => {
{renderFields(fields)} {renderFields(fields)}
<Form.Item wrapperCol={wrapperColSpan ? { offset: labelColSpan, span: wrapperColSpan } : undefined}> <Form.Item
<Button type="primary" onClick={handleSubmit} style={{ marginRight: 8 }}> style={{ marginTop: 32, marginBottom: 0 }}
labelCol={{ span: 0 }}
</Button> wrapperCol={{ span: 24 }}
<Button onClick={handleReset}> >
<div style={{ display: 'flex', justifyContent: 'center', gap: 8 }}>
</Button> <Button type="primary" onClick={handleSubmit}>
</Button>
<Button onClick={handleReset}>
</Button>
</div>
</Form.Item> </Form.Item>
</Form> </Form>

View File

@ -20,9 +20,16 @@ const GridFieldPreview: React.FC<GridFieldPreviewProps> = ({
onFieldChange onFieldChange
}) => { }) => {
const columns = field.columns || 2; const columns = field.columns || 2;
const colSpan = 24 / columns;
const children = field.children || Array(columns).fill([]); const children = field.children || Array(columns).fill([]);
// 使用自定义列宽度或平均分配
const getColSpan = (colIndex: number) => {
if (field.columnSpans && field.columnSpans.length > colIndex) {
return field.columnSpans[colIndex];
}
return 24 / columns; // 平均分配
};
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)) {
@ -39,6 +46,7 @@ const GridFieldPreview: React.FC<GridFieldPreviewProps> = ({
key={childField.id} key={childField.id}
label={childField.label} label={childField.label}
name={childField.name} name={childField.name}
colon={true}
rules={[ rules={[
{ {
required: childField.required, required: childField.required,
@ -59,11 +67,14 @@ const GridFieldPreview: React.FC<GridFieldPreviewProps> = ({
return ( return (
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
<Row gutter={field.gutter || 16}> <Row gutter={field.gutter || 16}>
{children.map((columnFields, colIndex) => ( {children.map((columnFields, colIndex) => {
<Col key={colIndex} span={colSpan}> const colSpan = getColSpan(colIndex);
{columnFields.map((childField: FieldConfig) => renderFieldItem(childField))} return (
</Col> <Col key={colIndex} span={colSpan}>
))} {columnFields.map((childField: FieldConfig) => renderFieldItem(childField))}
</Col>
);
})}
</Row> </Row>
</div> </div>
); );

View File

@ -133,3 +133,70 @@
opacity: 1; opacity: 1;
} }
/* 确保所有表单组件高度一致 */
.ant-form-item .ant-input,
.ant-form-item .ant-input-number,
.ant-form-item .ant-input-number-input-wrap,
.ant-form-item .ant-select-selector,
.ant-form-item .ant-picker {
min-height: 32px !important;
height: 32px !important;
}
.ant-form-item .ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
height: 32px !important;
display: flex;
align-items: center;
padding: 0 11px !important;
}
.ant-form-item .ant-select-selection-search-input {
height: 30px !important;
}
.ant-form-item .ant-input-number-input {
height: 30px !important;
}
/* 中等尺寸 */
.ant-form-middle .ant-input,
.ant-form-middle .ant-input-number,
.ant-form-middle .ant-input-number-input-wrap,
.ant-form-middle .ant-select-selector,
.ant-form-middle .ant-picker {
min-height: 32px !important;
height: 32px !important;
}
.ant-form-middle .ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
height: 32px !important;
}
/* 大尺寸 */
.ant-form-large .ant-input,
.ant-form-large .ant-input-number,
.ant-form-large .ant-input-number-input-wrap,
.ant-form-large .ant-select-selector,
.ant-form-large .ant-picker {
min-height: 40px !important;
height: 40px !important;
}
.ant-form-large .ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
height: 40px !important;
}
/* 小尺寸 */
.ant-form-small .ant-input,
.ant-form-small .ant-input-number,
.ant-form-small .ant-input-number-input-wrap,
.ant-form-small .ant-select-selector,
.ant-form-small .ant-picker {
min-height: 24px !important;
height: 24px !important;
}
.ant-form-small .ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
height: 24px !important;
}