chore: 文档更新 + 代码去重 + ESLint 修复,准备 0.0.16

- CHANGELOG: 新增 0.0.16 版本条目(模型输入栏重构/统一页面调度/依赖联动)
- TODO: 标记账户信息页为已完成,拆分未完成页面项
- ARCHITECTURE: 新增 PageDispatcher 叠加层说明 + OPEN_PAGE 事件
- 代码去重:runFieldChecks(validators) + createImageBeforeUpload(uploadValidation)
- ESLint: DependentFormItem 中 Form.useWatch 移到组件顶层(修复 rules-of-hooks)
- ESLint: message.success / async handleSubmit 加 void(修复 no-floating-promises)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 19:12:37 +08:00
parent 1c27283b56
commit d95d155ab5
11 changed files with 110 additions and 76 deletions

View File

@@ -3,7 +3,7 @@
// 可拖拽调整三栏宽度,所有数据请求在登录后发送
// ============================================================
import { createContext, useContext, useState, useCallback, useRef, useEffect, useMemo } from 'react';
import React, { createContext, useContext, useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { theme as antTheme, Empty } from 'antd';
import { useAppContext } from '@/components/AppProvider';

View File

@@ -75,12 +75,17 @@ function DependentFormItem({
onChange?: (value: unknown) => void;
disabled?: boolean;
}) {
const depValues = (field.dependsOn || []).map((depName) =>
Form.useWatch(depName, form),
);
// Form.useWatch 必须在组件顶层无条件调用(不能放 .map() 或条件分支中)。
// dependsOn 长度由 param_schema 约束(当前 API 最多 2 个),
// 预留 3 个固定槽位覆盖未来扩展;无依赖时 watch 空串 → 返回 undefined。
const deps = field.dependsOn || [];
const d0 = Form.useWatch(deps[0] || '', form);
const d1 = Form.useWatch(deps[1] || '', form);
const d2 = Form.useWatch(deps[2] || '', form);
const depValues = [d0, d1, d2].slice(0, deps.length);
const isDepDisabled =
field.dependsOn?.some((_, i) => isValueEmpty(depValues[i])) ?? false;
deps.some((_, i) => isValueEmpty(depValues[i]));
const effectiveDisabled = formItemDisabled || isDepDisabled;
const effectiveConfig = effectiveDisabled
@@ -115,7 +120,7 @@ export function ModelInputForm() {
// 提交突变 Hook
const { execute: submitTask, loading: submitting, errorMessage } = useSubmitTask({
onSuccess: useCallback((_data: TaskInfoItemResponseBody) => {
message.success('任务已提交');
void message.success('任务已提交');
emit(EVENTS.TASK_SUBMITTED);
}, []),
});
@@ -147,7 +152,7 @@ export function ModelInputForm() {
form.setFieldsValue(initialValues);
setTaskTag('');
}
}, [selectedModel?.id, initialValues]);
}, [selectedModel?.id, initialValues, selectedModel, form]);
// 重置参数(回到默认值 + 清空标签)
const handleReset = useCallback(() => {
@@ -384,7 +389,7 @@ export function ModelInputForm() {
<Button
type="primary"
icon={<SendOutlined />}
onClick={handleSubmit}
onClick={() => void handleSubmit()}
loading={submitting}
disabled={!isLoggedIn || !selectedModel}
size="large"

View File

@@ -14,10 +14,11 @@
// ============================================================
import { useState, useCallback } from 'react';
import { Upload, Switch, Space, Typography, message } from 'antd';
import { Upload, Switch, Space, Typography } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload';
import type { WidgetProps } from './types';
import { createImageBeforeUpload } from './uploadValidation';
const { Text } = Typography;
@@ -81,18 +82,7 @@ export function ImageInput({ value, onChange, disabled, config }: WidgetProps) {
}
}}
disabled={isUploadDisabled}
beforeUpload={(file) => {
const isImage = file.type.startsWith('image/');
if (!isImage) {
message.error('只能上传图片文件');
return Upload.LIST_IGNORE;
}
if (file.size / 1024 / 1024 >= maxSizeMb) {
message.error(`图片大小不能超过 ${maxSizeMb}MB`);
return Upload.LIST_IGNORE;
}
return false;
}}
beforeUpload={createImageBeforeUpload(maxSizeMb)}
>
{fileList.length < 1 && (
<div style={{ opacity: isUploadDisabled ? 0.4 : 1 }}>

View File

@@ -3,10 +3,11 @@
// 用于图生图模型的多图输入场景
// ============================================================
import { Upload, message } from 'antd';
import { Upload } from 'antd';
import { InboxOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload';
import type { WidgetProps } from './types';
import { createImageBeforeUpload } from './uploadValidation';
const { Dragger } = Upload;
@@ -28,18 +29,7 @@ export function ImageListInput({ value, onChange, disabled, config }: WidgetProp
fileList={fileList}
onChange={({ fileList: newList }) => onChange?.(newList)}
disabled={disabled}
beforeUpload={(file) => {
const isImage = file.type.startsWith('image/');
if (!isImage) {
message.error('只能上传图片文件');
return Upload.LIST_IGNORE;
}
if (file.size / 1024 / 1024 >= maxSizeMb) {
message.error(`图片大小不能超过 ${maxSizeMb}MB`);
return Upload.LIST_IGNORE;
}
return false;
}}
beforeUpload={createImageBeforeUpload(maxSizeMb)}
maxCount={maxCount}
>
<p className="ant-upload-drag-icon">

View File

@@ -0,0 +1,26 @@
// ============================================================
// uploadValidation — 文件上传前校验工具
// ImageInput / ImageListInput 共用
// ============================================================
import { Upload, message } from 'antd';
import type { RcFile } from 'antd/es/upload';
/**
* 创建图片上传前的类型 & 大小校验函数
* @param maxSizeMb 最大文件大小MB默认 10
*/
export function createImageBeforeUpload(maxSizeMb: number = 10) {
return (file: RcFile) => {
const isImage = file.type.startsWith('image/');
if (!isImage) {
message.error('只能上传图片文件');
return Upload.LIST_IGNORE;
}
if (file.size / 1024 / 1024 >= maxSizeMb) {
message.error(`图片大小不能超过 ${maxSizeMb}MB`);
return Upload.LIST_IGNORE;
}
return false;
};
}