表单设计器
This commit is contained in:
parent
f880c5f59d
commit
0dabb6aef7
@ -49,6 +49,9 @@ const DraggableComponent: React.FC<{ component: ComponentMeta }> = ({ component
|
|||||||
const ComponentPanel: React.FC = () => {
|
const ComponentPanel: React.FC = () => {
|
||||||
const componentsByCategory = getComponentsByCategory();
|
const componentsByCategory = getComponentsByCategory();
|
||||||
|
|
||||||
|
// 定义分类显示顺序
|
||||||
|
const categoryOrder = ['布局字段', '基础字段', '高级字段'];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-designer-component-panel">
|
<div className="form-designer-component-panel">
|
||||||
<div className="form-designer-component-panel-header">
|
<div className="form-designer-component-panel-header">
|
||||||
@ -60,7 +63,11 @@ const ComponentPanel: React.FC = () => {
|
|||||||
ghost
|
ghost
|
||||||
bordered={false}
|
bordered={false}
|
||||||
>
|
>
|
||||||
{Object.entries(componentsByCategory).map(([category, components]) => (
|
{categoryOrder.map((category) => {
|
||||||
|
const components = componentsByCategory[category];
|
||||||
|
if (!components || components.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
<Panel header={category} key={category}>
|
<Panel header={category} key={category}>
|
||||||
<div className="form-designer-component-list">
|
<div className="form-designer-component-list">
|
||||||
{components.map((component) => (
|
{components.map((component) => (
|
||||||
@ -68,7 +75,8 @@ const ComponentPanel: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -28,14 +28,22 @@ const GridField: React.FC<GridFieldProps> = ({
|
|||||||
labelAlign = 'right',
|
labelAlign = 'right',
|
||||||
}) => {
|
}) => {
|
||||||
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; // 平均分配
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '8px 0', width: '100%' }}>
|
<div style={{ padding: '8px 0', width: '100%' }}>
|
||||||
<Row gutter={field.gutter || 16}>
|
<Row gutter={field.gutter || 16}>
|
||||||
{children.map((columnFields, colIndex) => {
|
{children.map((columnFields, colIndex) => {
|
||||||
const dropId = `grid-${field.id}-col-${colIndex}`;
|
const dropId = `grid-${field.id}-col-${colIndex}`;
|
||||||
|
const colSpan = getColSpan(colIndex);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridColumn
|
<GridColumn
|
||||||
|
|||||||
@ -45,23 +45,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
|
|
||||||
const handleFieldUpdate = (changedValues: any) => {
|
const handleFieldUpdate = (changedValues: any) => {
|
||||||
if (selectedField) {
|
if (selectedField) {
|
||||||
let updatedField = { ...selectedField, ...changedValues };
|
onFieldChange({ ...selectedField, ...changedValues });
|
||||||
|
|
||||||
// 特殊处理:栅格列数变化时,调整 children 数组
|
|
||||||
if (selectedField.type === 'grid' && changedValues.columns !== undefined) {
|
|
||||||
const newColumns = changedValues.columns;
|
|
||||||
const oldChildren = selectedField.children || [];
|
|
||||||
const newChildren: FieldConfig[][] = [];
|
|
||||||
|
|
||||||
// 保留旧数据,调整数组长度
|
|
||||||
for (let i = 0; i < newColumns; i++) {
|
|
||||||
newChildren[i] = oldChildren[i] || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedField = { ...updatedField, children: newChildren };
|
|
||||||
}
|
|
||||||
|
|
||||||
onFieldChange(updatedField);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,6 +95,9 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
onValuesChange={handleFieldUpdate}
|
onValuesChange={handleFieldUpdate}
|
||||||
style={{ padding: 16 }}
|
style={{ padding: 16 }}
|
||||||
>
|
>
|
||||||
|
{/* 布局组件不显示字段标签和字段名称 */}
|
||||||
|
{selectedField.type !== 'divider' && selectedField.type !== 'grid' && selectedField.type !== 'text' && (
|
||||||
|
<>
|
||||||
<Form.Item label="字段标签" name="label" rules={[{ required: true }]}>
|
<Form.Item label="字段标签" name="label" rules={[{ required: true }]}>
|
||||||
<Input placeholder="请输入字段标签" />
|
<Input placeholder="请输入字段标签" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -118,8 +105,13 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
<Form.Item label="字段名称" name="name" rules={[{ required: true }]}>
|
<Form.Item label="字段名称" name="name" rules={[{ required: true }]}>
|
||||||
<Input placeholder="请输入字段名称(英文)" />
|
<Input placeholder="请输入字段名称(英文)" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{selectedField.type !== 'divider' && (
|
{/* 只有实际表单字段才显示这些属性 */}
|
||||||
|
{selectedField.type !== 'divider' &&
|
||||||
|
selectedField.type !== 'grid' &&
|
||||||
|
selectedField.type !== 'text' && (
|
||||||
<>
|
<>
|
||||||
<Form.Item label="占位提示" name="placeholder">
|
<Form.Item label="占位提示" name="placeholder">
|
||||||
<Input placeholder="请输入占位提示" />
|
<Input placeholder="请输入占位提示" />
|
||||||
@ -166,9 +158,102 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
|
|
||||||
{selectedField.type === 'grid' && (
|
{selectedField.type === 'grid' && (
|
||||||
<>
|
<>
|
||||||
<Form.Item label="列数" name="columns">
|
<Divider style={{ margin: '16px 0' }} />
|
||||||
<InputNumber min={1} max={24} style={{ width: '100%' }} />
|
<div style={{ marginBottom: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
</Form.Item>
|
<Text strong>列配置项</Text>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
size="small"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
// 如果没有配置 columnSpans,先初始化为当前列数的平均分配
|
||||||
|
const currentSpans = selectedField.columnSpans || (() => {
|
||||||
|
const cols = selectedField.columns || 2;
|
||||||
|
const avgSpan = Math.floor(24 / cols);
|
||||||
|
return Array(cols).fill(avgSpan);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const total = currentSpans.reduce((a, b) => a + b, 0);
|
||||||
|
if (total >= 24) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newSpan = Math.min(12, 24 - total);
|
||||||
|
const newSpans = [...currentSpans, newSpan];
|
||||||
|
const currentChildren = selectedField.children || [];
|
||||||
|
const newChildren = [...currentChildren, []];
|
||||||
|
|
||||||
|
onFieldChange({
|
||||||
|
...selectedField,
|
||||||
|
columnSpans: newSpans,
|
||||||
|
columns: newSpans.length,
|
||||||
|
children: newChildren,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
selectedField.columnSpans &&
|
||||||
|
selectedField.columnSpans.reduce((a: number, b: number) => a + b, 0) >= 24
|
||||||
|
}
|
||||||
|
>
|
||||||
|
添加列
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }} size="small">
|
||||||
|
{(selectedField.columnSpans || [12, 12]).map((span, index) => (
|
||||||
|
<Space key={index} style={{ display: 'flex', width: '100%' }} align="center">
|
||||||
|
<Text style={{ minWidth: 20 }}>{index + 1}.</Text>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={24}
|
||||||
|
value={span}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (value) {
|
||||||
|
const currentSpans = selectedField.columnSpans || [12, 12];
|
||||||
|
const newSpans = [...currentSpans];
|
||||||
|
newSpans[index] = value;
|
||||||
|
onFieldChange({
|
||||||
|
...selectedField,
|
||||||
|
columnSpans: newSpans,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
addonAfter="/24"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
danger
|
||||||
|
onClick={() => {
|
||||||
|
const currentSpans = selectedField.columnSpans || [12, 12];
|
||||||
|
const newSpans = currentSpans.filter((_, i) => i !== index);
|
||||||
|
const newChildren = (selectedField.children || [[], []]).filter((_, i) => i !== index);
|
||||||
|
|
||||||
|
// 如果删除后没有列了,重置为默认的2列配置
|
||||||
|
if (newSpans.length === 0) {
|
||||||
|
onFieldChange({
|
||||||
|
...selectedField,
|
||||||
|
columnSpans: [12, 12],
|
||||||
|
columns: 2,
|
||||||
|
children: [[], []],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onFieldChange({
|
||||||
|
...selectedField,
|
||||||
|
columnSpans: newSpans,
|
||||||
|
columns: newSpans.length,
|
||||||
|
children: newChildren,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
<div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
|
||||||
|
总宽度:{(selectedField.columnSpans || [12, 12]).reduce((a: number, b: number) => a + b, 0)} / 24
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '16px 0' }} />
|
||||||
<Form.Item label="间距" name="gutter">
|
<Form.Item label="间距" name="gutter">
|
||||||
<InputNumber min={0} max={48} style={{ width: '100%' }} addonAfter="px" />
|
<InputNumber min={0} max={48} style={{ width: '100%' }} addonAfter="px" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -34,6 +34,26 @@ export interface ComponentMeta {
|
|||||||
|
|
||||||
// 组件列表配置
|
// 组件列表配置
|
||||||
export const COMPONENT_LIST: ComponentMeta[] = [
|
export const COMPONENT_LIST: ComponentMeta[] = [
|
||||||
|
// 布局字段
|
||||||
|
{
|
||||||
|
type: 'grid',
|
||||||
|
label: '栅格布局',
|
||||||
|
icon: BorderOutlined,
|
||||||
|
category: '布局字段',
|
||||||
|
defaultConfig: {
|
||||||
|
columns: 2,
|
||||||
|
columnSpans: [12, 12], // 默认两列,各占12格
|
||||||
|
gutter: 16,
|
||||||
|
children: [[], []], // 初始化两列空数组
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
label: '分割线',
|
||||||
|
icon: MinusOutlined,
|
||||||
|
category: '布局字段',
|
||||||
|
defaultConfig: {},
|
||||||
|
},
|
||||||
// 基础字段
|
// 基础字段
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: 'input',
|
||||||
@ -131,6 +151,15 @@ export const COMPONENT_LIST: ComponentMeta[] = [
|
|||||||
placeholder: '请选择时间',
|
placeholder: '请选择时间',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
label: '文字',
|
||||||
|
icon: FileTextOutlined,
|
||||||
|
category: '基础字段',
|
||||||
|
defaultConfig: {
|
||||||
|
content: '这是一段文字',
|
||||||
|
},
|
||||||
|
},
|
||||||
// 高级字段
|
// 高级字段
|
||||||
{
|
{
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
@ -180,34 +209,6 @@ export const COMPONENT_LIST: ComponentMeta[] = [
|
|||||||
] as any,
|
] as any,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 布局字段
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
label: '文字',
|
|
||||||
icon: FileTextOutlined,
|
|
||||||
category: '布局字段',
|
|
||||||
defaultConfig: {
|
|
||||||
content: '这是一段文字',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'grid',
|
|
||||||
label: '栅格布局',
|
|
||||||
icon: BorderOutlined,
|
|
||||||
category: '布局字段',
|
|
||||||
defaultConfig: {
|
|
||||||
columns: 2,
|
|
||||||
gutter: 16,
|
|
||||||
children: [[], []], // 初始化两列空数组
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'divider',
|
|
||||||
label: '分割线',
|
|
||||||
icon: MinusOutlined,
|
|
||||||
category: '布局字段',
|
|
||||||
defaultConfig: {},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 根据分类分组组件
|
// 根据分类分组组件
|
||||||
|
|||||||
@ -49,6 +49,7 @@ export interface FieldConfig {
|
|||||||
span?: number; // 栅格占位格数(该字段在栅格中占几列)
|
span?: number; // 栅格占位格数(该字段在栅格中占几列)
|
||||||
content?: string; // 文本内容(用于 text 组件)
|
content?: string; // 文本内容(用于 text 组件)
|
||||||
columns?: number; // 栅格列数(用于 grid 组件)
|
columns?: number; // 栅格列数(用于 grid 组件)
|
||||||
|
columnSpans?: number[]; // 栅格每列宽度(用于 grid 组件,如 [2, 18, 2],总和不超过24)
|
||||||
gutter?: number; // 栅格间距(用于 grid 组件)
|
gutter?: number; // 栅格间距(用于 grid 组件)
|
||||||
children?: FieldConfig[][]; // 子字段(用于容器组件,二维数组,每个子数组代表一列)
|
children?: FieldConfig[][]; // 子字段(用于容器组件,二维数组,每个子数组代表一列)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user