feat: constraints_config.requires 联动控制 — enable_switch 开关同步启用/禁用依赖字段
- DependentFormItem: 透传 disabled 为顶层 prop(之前藏在 config.disabled 中,widget 组件读取不到)
- ImageInput: 值语义分层 — undefined(开关关) / [](开关开但空) / [{...}](已上传)
- isValueEmpty: 空数组[] 不再视为空,配合 enable_switch 语义
- handleFileWidgetValue: 兼容 Upload DOM事件 和 enable_switch 裸值两种 onChange 形态
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,20 @@ import { useModelUI, isValueEmpty, type UIFieldConfig } from './useModelUI';
|
|||||||
|
|
||||||
const { Text, Title } = Typography;
|
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({
|
|||||||
<Component
|
<Component
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
disabled={effectiveDisabled}
|
||||||
config={effectiveConfig}
|
config={effectiveConfig}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -329,7 +344,7 @@ export function ModelInputForm() {
|
|||||||
}
|
}
|
||||||
valuePropName={field.valuePropName}
|
valuePropName={field.valuePropName}
|
||||||
{...(field.isFileWidget
|
{...(field.isFileWidget
|
||||||
? { getValueFromEvent: (e: { fileList: UploadFile[] }) => e.fileList }
|
? { getValueFromEvent: handleFileWidgetValue }
|
||||||
: {})}
|
: {})}
|
||||||
>
|
>
|
||||||
<DependentFormItem field={field} form={form} />
|
<DependentFormItem field={field} form={form} />
|
||||||
|
|||||||
@@ -169,11 +169,16 @@ export function isFileWidget(widgetName: string): boolean {
|
|||||||
|
|
||||||
// ---------- 值判空工具 ----------
|
// ---------- 值判空工具 ----------
|
||||||
|
|
||||||
/** 判断表单值是否为"空"(用于依赖禁用判断) */
|
/**
|
||||||
|
* 判断表单值是否为"空"(用于依赖禁用判断)。
|
||||||
|
*
|
||||||
|
* 注意:空数组 [] 不视为空。
|
||||||
|
* 对于 enable_switch 控件:undefined = 开关关闭, [] = 开关开启但未选文件。
|
||||||
|
* 配合 constraints_config.requires:开关开启即解锁依赖字段,无需等待文件上传。
|
||||||
|
*/
|
||||||
export function isValueEmpty(value: unknown): boolean {
|
export function isValueEmpty(value: unknown): boolean {
|
||||||
if (value === undefined || value === null) return true;
|
if (value === undefined || value === null) return true;
|
||||||
if (typeof value === 'string' && value.trim() === '') 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;
|
if (typeof value === 'boolean' && !value) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
// 当 API ui.enable_switch === true 时,默认禁用(灰色),
|
// 当 API ui.enable_switch === true 时,默认禁用(灰色),
|
||||||
// 需点击 Switch 开关后才能上传——匹配原 Qt 客户端行为。
|
// 需点击 Switch 开关后才能上传——匹配原 Qt 客户端行为。
|
||||||
// 开关关闭时清除已上传文件。
|
// 开关关闭时清除已上传文件。
|
||||||
|
//
|
||||||
|
// 表单值语义(配合 constraints_config.requires 依赖禁用):
|
||||||
|
// undefined → 开关关闭 / 未启用 → isValueEmpty = true → 依赖字段禁用
|
||||||
|
// [] → 开关开启但未上传文件 → isValueEmpty = false → 依赖字段启用
|
||||||
|
// [{...}] → 已上传文件 → isValueEmpty = false → 依赖字段启用
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
@@ -22,6 +27,9 @@ function buildAccept(filters?: string[]): string {
|
|||||||
return filters.map((ext) => `image/${ext}`).join(',');
|
return filters.map((ext) => `image/${ext}`).join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 标记"已启用但空"的空数组,避免 react 渲染时引用变化 */
|
||||||
|
const ENABLED_EMPTY: UploadFile[] = [];
|
||||||
|
|
||||||
export function ImageInput({ value, onChange, disabled, config }: WidgetProps) {
|
export function ImageInput({ value, onChange, disabled, config }: WidgetProps) {
|
||||||
const fileList = (value as UploadFile[]) || [];
|
const fileList = (value as UploadFile[]) || [];
|
||||||
const maxSizeMb = (config.maxFileSizeMb as number) || 10;
|
const maxSizeMb = (config.maxFileSizeMb as number) || 10;
|
||||||
@@ -35,16 +43,18 @@ export function ImageInput({ value, onChange, disabled, config }: WidgetProps) {
|
|||||||
(on: boolean) => {
|
(on: boolean) => {
|
||||||
setSwitchOn(on);
|
setSwitchOn(on);
|
||||||
if (!on) {
|
if (!on) {
|
||||||
// 关闭开关 → 清除已上传文件
|
// 关闭开关 → 表单值置为 undefined(isValueEmpty = true,依赖字段禁用)
|
||||||
onChange?.([] as unknown as UploadFile[]);
|
onChange?.(undefined);
|
||||||
|
} else {
|
||||||
|
// 开启开关 → 空数组 = "已启用但未上传"(isValueEmpty = false,依赖字段启用)
|
||||||
|
onChange?.(ENABLED_EMPTY as unknown as UploadFile[]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onChange],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space orientation={"vertical"}
|
<Space orientation="vertical" style={{ width: '100%' }} size={8}>
|
||||||
style={{ width: '100%' }} size={8}>
|
|
||||||
{hasEnableSwitch && (
|
{hasEnableSwitch && (
|
||||||
<Space size={8}>
|
<Space size={8}>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -64,7 +74,12 @@ export function ImageInput({ value, onChange, disabled, config }: WidgetProps) {
|
|||||||
maxCount={1}
|
maxCount={1}
|
||||||
accept={buildAccept(config.fileFilter as string[] | undefined)}
|
accept={buildAccept(config.fileFilter as string[] | undefined)}
|
||||||
fileList={fileList}
|
fileList={fileList}
|
||||||
onChange={({ fileList: newList }) => onChange?.(newList as unknown as UploadFile[])}
|
onChange={({ fileList: newList }) => {
|
||||||
|
// 仅当开关开启时上报文件变化
|
||||||
|
if (switchOn || !hasEnableSwitch) {
|
||||||
|
onChange?.(newList as unknown as UploadFile[]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
disabled={isUploadDisabled}
|
disabled={isUploadDisabled}
|
||||||
beforeUpload={(file) => {
|
beforeUpload={(file) => {
|
||||||
const isImage = file.type.startsWith('image/');
|
const isImage = file.type.startsWith('image/');
|
||||||
|
|||||||
Reference in New Issue
Block a user