|
|
@@ -10,6 +10,7 @@ import { DICT_TYPE, getDictLabel } from '../utils/dict';
|
|
|
import { dateFormatter } from '../utils/formatTime';
|
|
|
import { setConfAndFields2, FormCreateData } from '../utils/formCreate';
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
+import FormUploadField from './FormUploadField';
|
|
|
|
|
|
export default function FormManagement() {
|
|
|
const { t } = useTranslation();
|
|
|
@@ -45,6 +46,7 @@ export default function FormManagement() {
|
|
|
formSize: 'middle',
|
|
|
labelSuffix: '',
|
|
|
labelWidth: 100,
|
|
|
+ layoutColumns: 1 as 1 | 2 | 3,
|
|
|
hideRequiredMark: false,
|
|
|
showValidationError: true,
|
|
|
inlineValidation: false,
|
|
|
@@ -52,11 +54,18 @@ export default function FormManagement() {
|
|
|
showResetButton: false,
|
|
|
};
|
|
|
|
|
|
+ // 合并表单配置:兼容 option.formConfig 或 option 即 formConfig 两种后端结构
|
|
|
+ const getMergedFormConfig = () => ({
|
|
|
+ ...defaultFormConfig,
|
|
|
+ ...(detailData.option?.formConfig || detailData.option || {}),
|
|
|
+ });
|
|
|
+
|
|
|
// 渲染字段预览(支持嵌套结构)
|
|
|
const renderFieldPreview = (field: any, parentSpanStyle?: React.CSSProperties): React.ReactNode => {
|
|
|
- const formConfig = detailData.option?.formConfig || defaultFormConfig;
|
|
|
+ const formConfig = getMergedFormConfig();
|
|
|
const layoutColumns = formConfig.layoutColumns || 1;
|
|
|
const spanStyle = parentSpanStyle || (layoutColumns > 1 ? { gridColumn: `span ${Math.min(layoutColumns, field.span || 1)}` } : undefined);
|
|
|
+ const wrapperStyle: React.CSSProperties = spanStyle || {};
|
|
|
|
|
|
// 处理容器类型(card 和 grid)
|
|
|
if (field.type === 'card') {
|
|
|
@@ -126,17 +135,31 @@ export default function FormManagement() {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ // 与表单设计器一致:每字段 labelCol/wrapperCol
|
|
|
+ const getItemLayout = (f: any) => {
|
|
|
+ const isTop = formConfig.labelPosition === 'top';
|
|
|
+ const w = f.labelWidth ?? formConfig.labelWidth ?? 100;
|
|
|
+ const effective = (typeof w === 'number' && w > 0) ? w : 100;
|
|
|
+ return {
|
|
|
+ labelCol: isTop ? undefined : { flex: `${effective}px`, style: { minWidth: `${effective}px`, textAlign: formConfig.labelPosition === 'right' ? 'right' : 'left' } },
|
|
|
+ wrapperCol: isTop ? undefined : { flex: 'auto', style: { minWidth: 0 } },
|
|
|
+ };
|
|
|
+ };
|
|
|
+ const itemLayout = getItemLayout(field);
|
|
|
+
|
|
|
// 处理普通字段
|
|
|
switch (field.type) {
|
|
|
case 'textarea':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseInput') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<Input.TextArea
|
|
|
placeholder={typeof field.placeholder === 'string' ? field.placeholder : t('common.pleaseInput')}
|
|
|
@@ -152,13 +175,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'password':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseInput') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<Input.Password
|
|
|
placeholder={typeof field.placeholder === 'string' ? field.placeholder : t('common.pleaseInput')}
|
|
|
@@ -173,13 +198,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'number':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseInput') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<InputNumber
|
|
|
style={{ width: '100%' }}
|
|
|
@@ -194,13 +221,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'select':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelect') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<Select
|
|
|
placeholder={typeof field.placeholder === 'string' ? field.placeholder : t('common.pleaseSelect')}
|
|
|
@@ -217,13 +246,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'date':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelectDate') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<DatePicker
|
|
|
className="w-full"
|
|
|
@@ -237,13 +268,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'daterange':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelectDateRange') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<DatePicker.RangePicker
|
|
|
className="w-full"
|
|
|
@@ -257,13 +290,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'datetime':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelectDateTime') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<DatePicker
|
|
|
className="w-full"
|
|
|
@@ -279,13 +314,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'timepicker':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelectTime') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<TimePicker
|
|
|
className="w-full"
|
|
|
@@ -300,7 +337,7 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'switch':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
@@ -308,6 +345,8 @@ export default function FormManagement() {
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelect') }] : []}
|
|
|
help={field.hint}
|
|
|
valuePropName="checked"
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<Switch disabled={field.disabled} />
|
|
|
</AntdForm.Item>
|
|
|
@@ -315,13 +354,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'radio':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelect') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<Radio.Group disabled={field.disabled}>
|
|
|
{(field.options || []).map((opt: any, idx: number) => (
|
|
|
@@ -333,13 +374,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'checkbox':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelect') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<Checkbox.Group disabled={field.disabled}>
|
|
|
{(field.options || []).map((opt: any, idx: number) => (
|
|
|
@@ -351,13 +394,15 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'cascader':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseSelect') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<Cascader
|
|
|
placeholder={typeof field.placeholder === 'string' ? field.placeholder : t('common.pleaseSelect')}
|
|
|
@@ -372,42 +417,36 @@ export default function FormManagement() {
|
|
|
);
|
|
|
case 'upload':
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.upload') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
- <Upload
|
|
|
+ <FormUploadField
|
|
|
+ uploadType={field.uploadType}
|
|
|
+ maxCount={field.maxCount}
|
|
|
+ accept={field.accept}
|
|
|
disabled={field.disabled}
|
|
|
- maxCount={field.uploadType === 'single-image' ? 1 : field.maxCount || (field.uploadType === 'multiple-image' ? 9 : undefined)}
|
|
|
- accept={field.accept || (field.uploadType === 'single-image' || field.uploadType === 'multiple-image' ? 'image/*' : undefined)}
|
|
|
- listType={(field.uploadType === 'file' ? 'text' : 'picture-card') as 'text' | 'picture-card' | 'picture'}
|
|
|
- multiple={field.uploadType !== 'single-image'}
|
|
|
- >
|
|
|
- {field.uploadType === 'file' ? (
|
|
|
- <Button icon={<UploadOutlined />}>{t('common.uploadFile')}</Button>
|
|
|
- ) : (
|
|
|
- <div>
|
|
|
- <UploadOutlined />
|
|
|
- <div style={{ marginTop: 8 }}>{t('common.upload')}</div>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </Upload>
|
|
|
+ />
|
|
|
</AntdForm.Item>
|
|
|
</div>
|
|
|
);
|
|
|
default:
|
|
|
return (
|
|
|
- <div key={field.id} style={spanStyle}>
|
|
|
+ <div key={field.id} style={wrapperStyle}>
|
|
|
<AntdForm.Item
|
|
|
label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
|
|
|
name={field.name || field.field}
|
|
|
required={field.required && !formConfig.hideRequiredMark}
|
|
|
rules={field.required ? [{ required: true, message: field.requiredMessage || t('common.pleaseInput') }] : []}
|
|
|
help={field.hint}
|
|
|
+ labelCol={itemLayout.labelCol}
|
|
|
+ wrapperCol={itemLayout.wrapperCol}
|
|
|
>
|
|
|
<Input
|
|
|
type={field.inputType || 'text'}
|
|
|
@@ -833,11 +872,11 @@ export default function FormManagement() {
|
|
|
</Button>
|
|
|
</div>,
|
|
|
]}
|
|
|
- width={800}
|
|
|
+ width={960}
|
|
|
>
|
|
|
<div className="p-2">
|
|
|
{(() => {
|
|
|
- const formConfig = detailData.option?.formConfig || defaultFormConfig;
|
|
|
+ const formConfig = getMergedFormConfig();
|
|
|
const layoutColumns = formConfig.layoutColumns || 1;
|
|
|
const gridStyle = layoutColumns > 1 ? {
|
|
|
display: 'grid',
|
|
|
@@ -846,31 +885,29 @@ export default function FormManagement() {
|
|
|
rowGap: '16px',
|
|
|
} : undefined;
|
|
|
|
|
|
+ const isHorizontal = formConfig.labelPosition !== 'top';
|
|
|
+ const effectiveLabelWidth = Math.max(0, Number(formConfig.labelWidth) || defaultFormConfig.labelWidth);
|
|
|
return (
|
|
|
- <AntdForm
|
|
|
- form={detailForm}
|
|
|
- layout={formConfig.labelPosition === 'top' ? 'vertical' : formConfig.labelPosition === 'left' ? 'horizontal' : 'horizontal'}
|
|
|
- size={formConfig.formSize === 'default' ? 'middle' : formConfig.formSize}
|
|
|
- requiredMark={formConfig.hideRequiredMark ? false : undefined}
|
|
|
- labelCol={formConfig.labelWidth ? {
|
|
|
- style: {
|
|
|
- width: `${formConfig.labelWidth}px`,
|
|
|
- textAlign: formConfig.labelPosition === 'left' ? 'left' : formConfig.labelPosition === 'right' ? 'right' : 'left'
|
|
|
- }
|
|
|
- } : undefined}
|
|
|
- >
|
|
|
- <div
|
|
|
- style={gridStyle}
|
|
|
- className={layoutColumns === 1 ? 'space-y-4' : 'form-detail-grid'}
|
|
|
+ <>
|
|
|
+ <style>{`
|
|
|
+ .form-preview-grid .ant-form-item { margin-bottom: 12px; }
|
|
|
+ .form-preview-grid .ant-form-item-control { min-width: 0; }
|
|
|
+ `}</style>
|
|
|
+ <AntdForm
|
|
|
+ form={detailForm}
|
|
|
+ layout={formConfig.labelPosition === 'top' ? 'vertical' : 'horizontal'}
|
|
|
+ size={formConfig.formSize === 'default' ? 'middle' : formConfig.formSize}
|
|
|
+ requiredMark={formConfig.hideRequiredMark ? false : undefined}
|
|
|
+ wrapperCol={formConfig.labelPosition === 'top' ? undefined : { flex: 'auto', style: { minWidth: 0 } }}
|
|
|
>
|
|
|
- <style>{`
|
|
|
- .form-detail-grid .ant-form-item {
|
|
|
- margin-bottom: 12px;
|
|
|
- }
|
|
|
- `}</style>
|
|
|
- {(detailData.rule || []).map((field: any) => renderFieldPreview(field))}
|
|
|
- </div>
|
|
|
- </AntdForm>
|
|
|
+ <div
|
|
|
+ style={gridStyle}
|
|
|
+ className={layoutColumns === 1 ? 'space-y-4' : 'form-preview-grid'}
|
|
|
+ >
|
|
|
+ {(detailData.rule || []).map((field: any) => renderFieldPreview(field))}
|
|
|
+ </div>
|
|
|
+ </AntdForm>
|
|
|
+ </>
|
|
|
);
|
|
|
})()}
|
|
|
</div>
|