diff --git a/src/pages/home/components/ModelInputForm.tsx b/src/pages/home/components/ModelInputForm.tsx index 00f442c..4fd46e2 100644 --- a/src/pages/home/components/ModelInputForm.tsx +++ b/src/pages/home/components/ModelInputForm.tsx @@ -36,6 +36,20 @@ import { useModelUI, isValueEmpty, type UIFieldConfig } from './useModelUI'; const { Text, Title } = Typography; +// ---------- 工具 ---------- + +/** + * Form.Item 的 getValueFromEvent,兼容两种 onChange 参数形态: + * 1. Upload 组件的 DOM 事件:{ fileList: UploadFile[] } → 提取 fileList + * 2. enable_switch 的裸值:undefined / [] / [{...}] → 直接透传 + */ +function handleFileWidgetValue(e: unknown): unknown { + if (e && typeof e === 'object' && 'fileList' in e) { + return (e as { fileList: unknown }).fileList; + } + return e; +} + // ---------- 依赖字段包装组件 ---------- /** @@ -78,6 +92,7 @@ function DependentFormItem({ ); @@ -329,7 +344,7 @@ export function ModelInputForm() { } valuePropName={field.valuePropName} {...(field.isFileWidget - ? { getValueFromEvent: (e: { fileList: UploadFile[] }) => e.fileList } + ? { getValueFromEvent: handleFileWidgetValue } : {})} > diff --git a/src/pages/home/components/useModelUI.ts b/src/pages/home/components/useModelUI.ts index 003f192..d5238ca 100644 --- a/src/pages/home/components/useModelUI.ts +++ b/src/pages/home/components/useModelUI.ts @@ -169,11 +169,16 @@ export function isFileWidget(widgetName: string): boolean { // ---------- 值判空工具 ---------- -/** 判断表单值是否为"空"(用于依赖禁用判断) */ +/** + * 判断表单值是否为"空"(用于依赖禁用判断)。 + * + * 注意:空数组 [] 不视为空。 + * 对于 enable_switch 控件:undefined = 开关关闭, [] = 开关开启但未选文件。 + * 配合 constraints_config.requires:开关开启即解锁依赖字段,无需等待文件上传。 + */ export function isValueEmpty(value: unknown): boolean { if (value === undefined || value === null) return true; if (typeof value === 'string' && value.trim() === '') return true; - if (Array.isArray(value) && value.length === 0) return true; if (typeof value === 'boolean' && !value) return true; return false; } diff --git a/src/pages/home/components/useUI/ImageInput.tsx b/src/pages/home/components/useUI/ImageInput.tsx index 9cf77c5..a5f0c28 100644 --- a/src/pages/home/components/useUI/ImageInput.tsx +++ b/src/pages/home/components/useUI/ImageInput.tsx @@ -6,6 +6,11 @@ // 当 API ui.enable_switch === true 时,默认禁用(灰色), // 需点击 Switch 开关后才能上传——匹配原 Qt 客户端行为。 // 开关关闭时清除已上传文件。 +// +// 表单值语义(配合 constraints_config.requires 依赖禁用): +// undefined → 开关关闭 / 未启用 → isValueEmpty = true → 依赖字段禁用 +// [] → 开关开启但未上传文件 → isValueEmpty = false → 依赖字段启用 +// [{...}] → 已上传文件 → isValueEmpty = false → 依赖字段启用 // ============================================================ import { useState, useCallback } from 'react'; @@ -22,6 +27,9 @@ function buildAccept(filters?: string[]): string { return filters.map((ext) => `image/${ext}`).join(','); } +/** 标记"已启用但空"的空数组,避免 react 渲染时引用变化 */ +const ENABLED_EMPTY: UploadFile[] = []; + export function ImageInput({ value, onChange, disabled, config }: WidgetProps) { const fileList = (value as UploadFile[]) || []; const maxSizeMb = (config.maxFileSizeMb as number) || 10; @@ -35,16 +43,18 @@ export function ImageInput({ value, onChange, disabled, config }: WidgetProps) { (on: boolean) => { setSwitchOn(on); if (!on) { - // 关闭开关 → 清除已上传文件 - onChange?.([] as unknown as UploadFile[]); + // 关闭开关 → 表单值置为 undefined(isValueEmpty = true,依赖字段禁用) + onChange?.(undefined); + } else { + // 开启开关 → 空数组 = "已启用但未上传"(isValueEmpty = false,依赖字段启用) + onChange?.(ENABLED_EMPTY as unknown as UploadFile[]); } }, [onChange], ); return ( - + {hasEnableSwitch && ( onChange?.(newList as unknown as UploadFile[])} + onChange={({ fileList: newList }) => { + // 仅当开关开启时上报文件变化 + if (switchOn || !hasEnableSwitch) { + onChange?.(newList as unknown as UploadFile[]); + } + }} disabled={isUploadDisabled} beforeUpload={(file) => { const isImage = file.type.startsWith('image/');