refactor: 重组 components/ 和 pages/ 文件结构,按职责分层

components/ 拆为三层:
  - providers/   — 状态管理(App/Theme/SettingsProvider)
  - shell/       — 应用外壳(Layout/NavBar/PageDispatcher)
  - ui/          — 通用 UI 原子(FloatingPanel/LegalTextModal)

pages/ 重命名 + 重组:
  - headers/ → panels/        NavBar 触发的面板页
  - login/   → auth/          认证模块(含注册/忘记密码)
  - settings/blocks/ → sections/
  - home/components/ → panels/ features/ controls/ hooks/
    按职责分:panels(布局壳) + features(功能面板) + controls(表单控件) + hooks

其他清理:
  - 删除空占位 Test3.tsx
  - HomeContext 从 HomeContent 拆出独立文件
  - uploadValidation 内联到 ImageInput
  - 修复过时注释(useUI → controls)
This commit is contained in:
2026-06-15 11:19:06 +08:00
parent bc33b81121
commit 0e6d996718
63 changed files with 123 additions and 123 deletions

View File

@@ -3,52 +3,22 @@
// 可拖拽调整三栏宽度,所有数据请求在登录后发送
// ============================================================
import React, { createContext, useContext, useState, useCallback, useRef, useEffect, useMemo } from 'react';
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { theme as antTheme, Empty } from 'antd';
import { useAppContext } from '@/components/AppProvider';
import { useAppContext } from '@/components/providers/AppProvider';
import type { ModelsListResponseBody } from '@/services/modules/models';
import type { TaskInfoItemResponseBody } from '@/services/modules/task';
import { LeftPanel } from './components/LeftPanel';
import { CenterPanel } from './components/CenterPanel';
import { RightPanel } from './components/RightPanel';
import { LeftPanel } from './panels/LeftPanel';
import { CenterPanel } from './panels/CenterPanel';
import { RightPanel } from './panels/RightPanel';
import { Typography } from 'antd';
import { HomeContext } from './HomeContext';
const { Text } = Typography;
// ---------- Context 类型 ----------
export interface HomeContextValue {
/** 当前选中的模型 */
selectedModel: ModelsListResponseBody | null;
setSelectedModel: (model: ModelsListResponseBody | null) => void;
/** 任务记录分页 — 当前页码 */
taskPage: number;
/** 任务记录分页 — 每页条数 */
taskPageSize: number;
setTaskPage: (page: number) => void;
setTaskPageSize: (size: number) => void;
/** 当前选中的任务记录(用于右侧预览) */
selectedTask: TaskInfoItemResponseBody | null;
setSelectedTask: (task: TaskInfoItemResponseBody | null) => void;
/** 侧边栏折叠状态 */
isLeftCollapsed: boolean;
toggleLeftCollapsed: () => void;
}
export const HomeContext = createContext<HomeContextValue | null>(null);
// ---------- Consumer Hook ----------
export function useHomeContext(): HomeContextValue {
const ctx = useContext(HomeContext);
if (!ctx) {
throw new Error('useHomeContext() 必须在 <HomeContent> 内部调用');
}
return ctx;
}
// ---------- 常量 ----------
/** 左列最小/默认宽度 */

View File

@@ -0,0 +1,43 @@
// ============================================================
// HomeContext — 首页共享状态Context + Provider + Hook
//
// 原与 HomeContent.tsx 耦合在同一文件,现独立以便:
// - 消费者可以明确只 import 状态 Hook不关心布局细节
// - 新增 consumer 时不需要翻阅 358 行 HomeContent
// ============================================================
import { createContext, useContext } from 'react';
import type { ModelsListResponseBody } from '@/services/modules/models';
import type { TaskInfoItemResponseBody } from '@/services/modules/task';
// ---------- Context 类型 ----------
export interface HomeContextValue {
/** 当前选中的模型 */
selectedModel: ModelsListResponseBody | null;
setSelectedModel: (model: ModelsListResponseBody | null) => void;
/** 任务记录分页 — 当前页码 */
taskPage: number;
/** 任务记录分页 — 每页条数 */
taskPageSize: number;
setTaskPage: (page: number) => void;
setTaskPageSize: (size: number) => void;
/** 当前选中的任务记录(用于右侧预览) */
selectedTask: TaskInfoItemResponseBody | null;
setSelectedTask: (task: TaskInfoItemResponseBody | null) => void;
/** 侧边栏折叠状态 */
isLeftCollapsed: boolean;
toggleLeftCollapsed: () => void;
}
export const HomeContext = createContext<HomeContextValue | null>(null);
// ---------- Consumer Hook ----------
export function useHomeContext(): HomeContextValue {
const ctx = useContext(HomeContext);
if (!ctx) {
throw new Error('useHomeContext() 必须在 <HomeContent> 内部调用');
}
return ctx;
}

View File

@@ -1,26 +0,0 @@
// ============================================================
// 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 true; // 校验通过,交由 customRequest 执行实际上传
};
}

View File

@@ -14,15 +14,33 @@
// ============================================================
import { useState, useCallback } from 'react';
import { Upload, Switch, Space, Typography } from 'antd';
import { Upload, Switch, Space, Typography, message } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload';
import type { UploadFile, RcFile } from 'antd/es/upload';
import type { WidgetProps } from './types';
import { createImageBeforeUpload } from './uploadValidation';
import { useFileUpload } from '@/hooks/use-api';
const { Text } = Typography;
/**
* & ImageInput / ImageListInput
* @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 true;
};
}
/** 构建图片 accept 字符串 */
function buildAccept(filters?: string[]): string {
if (!filters || filters.length === 0) return 'image/png,image/jpg,image/jpeg,image/webp';

View File

@@ -7,7 +7,7 @@ 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';
import { createImageBeforeUpload } from './ImageInput';
import { useFileUpload } from '@/hooks/use-api';
const { Dragger } = Upload;

View File

@@ -1,5 +1,5 @@
// ============================================================
// useUI/index.ts — Widget 注册表
// controls/index.ts — Widget 注册表
//
// 职责:
// 1. 定义 WidgetProps 统一接口
@@ -7,7 +7,7 @@
// 3. 导出 WidgetType 联合类型供外部使用
//
// 新增控件类型只需两步:
// ① 在 useUI/ 下创建组件文件
// ① 在 controls/ 下创建组件文件
// ② 在 WIDGET_MAP 中加一行
// ============================================================

View File

@@ -1,5 +1,5 @@
// ============================================================
// useUI/types.ts — Widget 类型定义(与 index.ts 分离以避免循环引用)
// controls/types.ts — Widget 类型定义(与 index.ts 分离以避免循环引用)
// ============================================================
/**

View File

@@ -4,7 +4,7 @@
// 布局Header模型信息+meta → Bodyfields.map 渲染) → Footer标签+重置+提交)
//
// 与旧版的区别:
// - 控件渲染逻辑已提取到 useUI/ 组件体系
// - 控件渲染逻辑已提取到 controls/ 组件体系
// - param_schema 解析已提取到 useModelUI Hook
// - 字段间条件依赖constraints_config.requires自动处理禁用
// - 本组件只负责布局、Form 容器、提交逻辑、依赖禁用
@@ -27,13 +27,13 @@ import type { FormInstance } from 'antd';
import { SendOutlined, ClockCircleOutlined, ReloadOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload';
import { useHomeContext } from '@/pages/home/HomeContent';
import { useAppContext } from '@/components/AppProvider';
import { useHomeContext } from '@/pages/home/HomeContext';
import { useAppContext } from '@/components/providers/AppProvider';
import { useSubmitTask } from '@/hooks/use-api';
import { emit, EVENTS } from '@/utils/event-bus';
import { OutputTypeMap } from '@/services/modules/task';
import type { OutputTypeString, TaskInfoItemResponseBody } from '@/services/modules/task';
import { useModelUI, isValueEmpty, type UIFieldConfig } from './useModelUI';
import { useModelUI, isValueEmpty, type UIFieldConfig } from '../hooks/useModelUI';
import {
calculateCost,
formatYuan,

View File

@@ -7,7 +7,7 @@ import React, { useState, useMemo, useCallback } from 'react';
import { Tabs, Spin, Empty, Tag, Typography, Button, Result, message, theme as antTheme } from 'antd';
import { AppstoreOutlined } from '@ant-design/icons';
import { useHomeContext } from '@/pages/home/HomeContent';
import { useHomeContext } from '@/pages/home/HomeContext';
import { MODEL_CATEGORIES, type ModelsListResponseBody, type ModelCategoryStringEnum } from '@/services/modules/models';
import { EnumResolver } from '@/utils/enum-resolver';

View File

@@ -11,7 +11,7 @@ import {
CloseCircleOutlined,
} from '@ant-design/icons';
import { useHomeContext } from '@/pages/home/HomeContent';
import { useHomeContext } from '@/pages/home/HomeContext';
import { useTaskDetail } from '@/hooks/use-api';
import { POLLING_STATUSES, type TaskStatusString } from '@/services/modules/task';

View File

@@ -19,9 +19,9 @@ import { HistoryOutlined, SearchOutlined, SettingOutlined } from '@ant-design/ic
import { useTaskList, useTaskPolling } from '@/hooks/use-api';
import type { ModelsListResponseBody } from '@/services/modules/models';
import { useAppContext } from '@/components/AppProvider';
import { useHomeContext } from '@/pages/home/HomeContent';
import { useTheme } from '@/components/ThemeProvider';
import { useAppContext } from '@/components/providers/AppProvider';
import { useHomeContext } from '@/pages/home/HomeContext';
import { useTheme } from '@/components/providers/ThemeProvider';
import { on, off, EVENTS } from '@/utils/event-bus';
import type {
TaskInfoItemResponseBody,

View File

@@ -15,8 +15,8 @@ import { useMemo } from 'react';
import type { ComponentType } from 'react';
import type { ModelsListResponseBody } from '@/services/modules/models';
import { WIDGET_MAP, FALLBACK_WIDGET } from './useUI';
import type { WidgetProps } from './useUI';
import { WIDGET_MAP, FALLBACK_WIDGET } from '../controls';
import type { WidgetProps } from '../controls';
// ---------- 输出类型 ----------

View File

@@ -3,7 +3,7 @@
// ============================================================
import { theme as antTheme } from 'antd';
import { ModelInputForm } from './ModelInputForm';
import { ModelInputForm } from '../features/ModelInputForm';
export function CenterPanel() {
const { token } = antTheme.useToken();

View File

@@ -7,8 +7,8 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import { theme as antTheme } from 'antd';
import { ModelSelector } from './ModelSelector';
import { TaskHistory } from './TaskHistory';
import { ModelSelector } from '../features/ModelSelector';
import { TaskHistory } from '../features/TaskHistory';
import { useModelList } from '@/hooks/use-api';
// ---------- 常量 ----------

View File

@@ -3,7 +3,7 @@
// ============================================================
import { theme as antTheme } from 'antd';
import { OutputPreview } from './OutputPreview';
import { OutputPreview } from '../features/OutputPreview';
export function RightPanel() {
const { token } = antTheme.useToken();