deploy-ease-platform/frontend/src/pages/FormDesigner/components/FieldRenderer.tsx
2025-10-23 23:17:48 +08:00

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;