278 lines
6.4 KiB
TypeScript
278 lines
6.4 KiB
TypeScript
/**
|
|
* 字段渲染器
|
|
* 根据字段类型渲染对应的表单组件
|
|
*/
|
|
|
|
import React from 'react';
|
|
import {
|
|
Form,
|
|
Input,
|
|
InputNumber,
|
|
Select,
|
|
Radio,
|
|
Checkbox,
|
|
DatePicker,
|
|
TimePicker,
|
|
Switch,
|
|
Slider,
|
|
Rate,
|
|
Upload,
|
|
Cascader,
|
|
Divider,
|
|
Button,
|
|
Typography,
|
|
} from 'antd';
|
|
import { UploadOutlined } from '@ant-design/icons';
|
|
import type { FieldConfig } from '../types';
|
|
import GridField from './GridField';
|
|
import { useFieldOptions } from '../hooks/useFieldOptions';
|
|
import { useCascaderOptions, useCascaderLoadData } from '../hooks/useCascaderOptions';
|
|
import '../styles.css';
|
|
|
|
const { Text } = Typography;
|
|
|
|
const { TextArea } = Input;
|
|
|
|
interface FieldRendererProps {
|
|
field: FieldConfig;
|
|
value?: any;
|
|
onChange?: (value: any) => void;
|
|
isPreview?: boolean; // 是否为预览模式
|
|
selectedFieldId?: string; // 当前选中的字段ID
|
|
onSelectField?: (fieldId: string) => void;
|
|
onDeleteField?: (fieldId: string) => void;
|
|
labelAlign?: 'left' | 'right' | 'top'; // 标签对齐方式
|
|
}
|
|
|
|
const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
field,
|
|
value,
|
|
onChange,
|
|
isPreview = false,
|
|
selectedFieldId,
|
|
onSelectField,
|
|
onDeleteField,
|
|
labelAlign = 'right',
|
|
}) => {
|
|
// 获取字段选项(支持静态和动态数据源)
|
|
const options = useFieldOptions(field);
|
|
|
|
// 获取级联选择器选项和懒加载函数
|
|
const cascadeOptions = useCascaderOptions(field);
|
|
const loadData = useCascaderLoadData(field);
|
|
|
|
// 布局组件特殊处理
|
|
if (field.type === 'divider') {
|
|
return <Divider>{field.label}</Divider>;
|
|
}
|
|
|
|
if (field.type === 'text') {
|
|
return (
|
|
<div style={{ padding: '8px 0' }}>
|
|
<Text>{field.content || '这是一段文字'}</Text>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (field.type === 'grid') {
|
|
return (
|
|
<GridField
|
|
field={field}
|
|
isPreview={isPreview}
|
|
selectedFieldId={selectedFieldId}
|
|
onSelectField={onSelectField}
|
|
onDeleteField={onDeleteField}
|
|
labelAlign={labelAlign}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const renderField = () => {
|
|
switch (field.type) {
|
|
case 'input':
|
|
return (
|
|
<Input
|
|
placeholder={field.placeholder}
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={(e) => onChange?.(e.target.value)}
|
|
/>
|
|
);
|
|
|
|
case 'textarea':
|
|
return (
|
|
<TextArea
|
|
rows={field.rows || 4}
|
|
placeholder={field.placeholder}
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={(e) => onChange?.(e.target.value)}
|
|
/>
|
|
);
|
|
|
|
case 'number':
|
|
return (
|
|
<InputNumber
|
|
style={{ width: '100%' }}
|
|
placeholder={field.placeholder}
|
|
disabled={field.disabled}
|
|
min={field.min}
|
|
max={field.max}
|
|
value={value}
|
|
onChange={onChange}
|
|
/>
|
|
);
|
|
|
|
case 'select':
|
|
return (
|
|
<Select
|
|
placeholder={field.placeholder}
|
|
disabled={field.disabled}
|
|
options={options}
|
|
value={value}
|
|
onChange={onChange}
|
|
style={{ width: '100%' }}
|
|
/>
|
|
);
|
|
|
|
case 'radio':
|
|
return (
|
|
<Radio.Group
|
|
options={options}
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={(e) => onChange?.(e.target.value)}
|
|
/>
|
|
);
|
|
|
|
case 'checkbox':
|
|
return (
|
|
<Checkbox.Group
|
|
options={options}
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={onChange}
|
|
/>
|
|
);
|
|
|
|
case 'date':
|
|
return (
|
|
<DatePicker
|
|
style={{ width: '100%' }}
|
|
placeholder={field.placeholder}
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={onChange}
|
|
/>
|
|
);
|
|
|
|
case 'datetime':
|
|
return (
|
|
<DatePicker
|
|
showTime
|
|
style={{ width: '100%' }}
|
|
placeholder={field.placeholder}
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={onChange}
|
|
/>
|
|
);
|
|
|
|
case 'time':
|
|
return (
|
|
<TimePicker
|
|
style={{ width: '100%' }}
|
|
placeholder={field.placeholder}
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={onChange}
|
|
/>
|
|
);
|
|
|
|
case 'switch':
|
|
return (
|
|
<Switch
|
|
disabled={field.disabled}
|
|
checked={value}
|
|
onChange={onChange}
|
|
/>
|
|
);
|
|
|
|
case 'slider':
|
|
return (
|
|
<Slider
|
|
min={field.min}
|
|
max={field.max}
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={onChange}
|
|
/>
|
|
);
|
|
|
|
case 'rate':
|
|
return (
|
|
<Rate
|
|
disabled={field.disabled}
|
|
value={value}
|
|
onChange={onChange}
|
|
/>
|
|
);
|
|
|
|
case 'upload':
|
|
return (
|
|
<Upload
|
|
maxCount={field.maxCount}
|
|
disabled={field.disabled}
|
|
>
|
|
<Button icon={<UploadOutlined />}>上传文件</Button>
|
|
</Upload>
|
|
);
|
|
|
|
case 'cascader':
|
|
return (
|
|
<Cascader
|
|
style={{ width: '100%' }}
|
|
placeholder={field.placeholder}
|
|
disabled={field.disabled}
|
|
options={cascadeOptions as any}
|
|
value={value}
|
|
onChange={onChange}
|
|
loadData={loadData as any}
|
|
changeOnSelect={field.dataSourceType === 'predefined'}
|
|
/>
|
|
);
|
|
|
|
default:
|
|
return <Input placeholder={field.placeholder} />;
|
|
}
|
|
};
|
|
|
|
// 预览模式下,外部已经用 Form.Item 包裹了,这里直接返回控件
|
|
if (isPreview) {
|
|
return renderField();
|
|
}
|
|
|
|
// 设计模式下,用 Form 和 Form.Item 包裹以显示标签,并支持动态布局
|
|
const isVertical = labelAlign === 'top';
|
|
|
|
return (
|
|
<Form
|
|
layout={isVertical ? 'vertical' : 'horizontal'}
|
|
labelAlign={isVertical ? undefined : labelAlign}
|
|
>
|
|
<Form.Item
|
|
label={field.label}
|
|
required={field.required}
|
|
style={{ marginBottom: 0 }}
|
|
labelCol={isVertical ? undefined : { span: 6 }}
|
|
wrapperCol={isVertical ? undefined : { span: 18 }}
|
|
>
|
|
{renderField()}
|
|
</Form.Item>
|
|
</Form>
|
|
);
|
|
};
|
|
|
|
export default FieldRenderer;
|
|
|