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:
@@ -12,7 +12,7 @@ import { useState, useCallback } from 'react';
|
||||
import { Modal, Input, Button, Typography, App } from 'antd';
|
||||
import { LockOutlined, KeyOutlined, SafetyOutlined } from '@ant-design/icons';
|
||||
|
||||
import { useTheme } from '@/components/ThemeProvider';
|
||||
import { useTheme } from '@/components/providers/ThemeProvider';
|
||||
import { AuthAPI } from '@/services/modules/auth';
|
||||
import { validatePassword, validateConfirmPassword, runFieldChecks } from '../config/validators';
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
KeyOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
import { useTheme } from '@/components/ThemeProvider';
|
||||
import { useTheme } from '@/components/providers/ThemeProvider';
|
||||
import { AuthAPI } from '@/services/modules/auth';
|
||||
import {
|
||||
validatePhone,
|
||||
@@ -10,8 +10,8 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Modal, Tabs, Typography, App } from 'antd';
|
||||
|
||||
import { useAppContext } from '@/components/AppProvider';
|
||||
import { useTheme } from '@/components/ThemeProvider';
|
||||
import { useAppContext } from '@/components/providers/AppProvider';
|
||||
import { useTheme } from '@/components/providers/ThemeProvider';
|
||||
import { LoginForm } from './components/LoginForm';
|
||||
import { RegisterForm } from './components/RegisterForm';
|
||||
import { ForgotPasswordModal } from './components/ForgotPasswordModal';
|
||||
@@ -1,5 +0,0 @@
|
||||
export function Test3() {
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// ---------- 常量 ----------
|
||||
|
||||
/** 左列最小/默认宽度 */
|
||||
|
||||
43
src/pages/home/HomeContext.tsx
Normal file
43
src/pages/home/HomeContext.tsx
Normal 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;
|
||||
}
|
||||
@@ -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 执行实际上传
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
@@ -1,5 +1,5 @@
|
||||
// ============================================================
|
||||
// useUI/index.ts — Widget 注册表
|
||||
// controls/index.ts — Widget 注册表
|
||||
//
|
||||
// 职责:
|
||||
// 1. 定义 WidgetProps 统一接口
|
||||
@@ -7,7 +7,7 @@
|
||||
// 3. 导出 WidgetType 联合类型供外部使用
|
||||
//
|
||||
// 新增控件类型只需两步:
|
||||
// ① 在 useUI/ 下创建组件文件
|
||||
// ① 在 controls/ 下创建组件文件
|
||||
// ② 在 WIDGET_MAP 中加一行
|
||||
// ============================================================
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// ============================================================
|
||||
// useUI/types.ts — Widget 类型定义(与 index.ts 分离以避免循环引用)
|
||||
// controls/types.ts — Widget 类型定义(与 index.ts 分离以避免循环引用)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
@@ -4,7 +4,7 @@
|
||||
// 布局:Header(模型信息+meta) → Body(fields.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,
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
@@ -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';
|
||||
|
||||
// ---------- 输出类型 ----------
|
||||
|
||||
@@ -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();
|
||||
@@ -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';
|
||||
|
||||
// ---------- 常量 ----------
|
||||
@@ -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();
|
||||
@@ -14,13 +14,13 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
import { SettingOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { useAppContext } from '@/components/AppProvider';
|
||||
import { useAppContext } from '@/components/providers/AppProvider';
|
||||
import { isMacOS } from '@/utils/platform';
|
||||
import { ThemeSetting } from './blocks/ThemeSetting';
|
||||
import { StoragePathSetting } from './blocks/StoragePathSetting';
|
||||
import { SystemInfoSetting } from './blocks/SystemInfoSetting';
|
||||
import { UpdateSetting } from './blocks/UpdateSetting';
|
||||
import { PasswordSetting } from './blocks/PasswordSetting';
|
||||
import { ThemeSetting } from './sections/ThemeSetting';
|
||||
import { StoragePathSetting } from './sections/StoragePathSetting';
|
||||
import { SystemInfoSetting } from './sections/SystemInfoSetting';
|
||||
import { UpdateSetting } from './sections/UpdateSetting';
|
||||
import { PasswordSetting } from './sections/PasswordSetting';
|
||||
|
||||
interface SettingsPageProps {
|
||||
open: boolean;
|
||||
|
||||
@@ -10,8 +10,8 @@ import { useState } from 'react';
|
||||
import { Card, Button, Typography } from 'antd';
|
||||
import { LockOutlined, PhoneOutlined } from '@ant-design/icons';
|
||||
|
||||
import { ChangePasswordModal } from '@/pages/login/components/ChangePasswordModal';
|
||||
import { ForgotPasswordModal } from '@/pages/login/components/ForgotPasswordModal';
|
||||
import { ChangePasswordModal } from '@/pages/auth/components/ChangePasswordModal';
|
||||
import { ForgotPasswordModal } from '@/pages/auth/components/ForgotPasswordModal';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useState, useCallback } from 'react';
|
||||
import { Card, Button, Input, Typography, Space, App } from 'antd';
|
||||
import { FolderOpenOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import type { Edition } from '@shared/types';
|
||||
import { useSettings } from '@/components/SettingsProvider';
|
||||
import { useSettings } from '@/components/providers/SettingsProvider';
|
||||
import { hasIpc, safeIpcInvoke } from '@/utils/ipc';
|
||||
import { BIDIRECTIONAL } from '@shared/constants/ipc-channels';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
InfoCircleOutlined,
|
||||
EnvironmentOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useAppContext } from '@/components/AppProvider';
|
||||
import { useAppContext } from '@/components/providers/AppProvider';
|
||||
import { getPlatformName } from '@/utils/platform';
|
||||
import { APP_VERSION } from '@shared/constants/version';
|
||||
import { getEditionName } from '@shared/constants/app';
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { Card, Segmented, Typography } from 'antd';
|
||||
import { BulbOutlined, BulbFilled } from '@ant-design/icons';
|
||||
import { useTheme } from '@/components/ThemeProvider';
|
||||
import { useTheme } from '@/components/providers/ThemeProvider';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
Reference in New Issue
Block a user