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

@@ -1,12 +1,12 @@
import { useState, useEffect, useCallback } from 'react';
import { useAppContext } from '@/components/AppProvider';
import { LoginPage } from './pages/login';
import { useAppContext } from '@/components/providers/AppProvider';
import { LoginPage } from './pages/auth';
import { SettingsPage } from './pages/settings';
import { on, off, EVENTS } from './utils/event-bus';
import { Layout } from '@/components/Layout';
import { Layout } from '@/components/shell/Layout';
import { HomePage } from '@/pages/home/HomePage';
import { PageDispatcher } from '@/components/PageDispatcher';
import { PageDispatcher } from '@/components/shell/PageDispatcher';
/**
* 根组件 — 主窗口

View File

@@ -12,7 +12,7 @@ import { flushSync } from 'react-dom';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { getThemeConfig } from '../theme/antd-theme';
import { getThemeConfig } from '../../theme/antd-theme';
// ---------- Context 类型 ----------

View File

@@ -6,7 +6,7 @@
import type { ReactNode } from 'react';
import { theme as antTheme } from 'antd';
import { NavBar } from './navbar';
import { BannerCarousel } from '@/pages/home/components/BannerCarousel';
import { BannerCarousel } from '@/pages/home/features/BannerCarousel';
export function Layout({ children }: { children: ReactNode }) {
const { token } = antTheme.useToken();

View File

@@ -21,9 +21,9 @@ import {Drawer, Modal} from 'antd';
import {EVENTS, off, on} from '@/utils/event-bus';
import {FloatingPanel} from '@/components/ui/FloatingPanel';
import {WechatWorkPage} from '@/pages/headers/WechatWorkPage';
import {AccountInfo} from '@/pages/headers/AccountInfo.tsx';
import {BillingInfo} from "@/pages/headers/BillingInfo.tsx";
import {WechatWorkPage} from '@/pages/panels/WechatWorkPage';
import {AccountInfo} from '@/pages/panels/AccountInfo';
import {BillingInfo} from '@/pages/panels/BillingInfo';
// ---------- 类型 ----------

View File

@@ -10,8 +10,8 @@
import {useState, useCallback, useMemo} from 'react';
import {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 {emit, EVENTS} from '@/utils/event-bus.ts';
import {NavItem} from './NavItem';
import {AnnouncementDrawer} from './AnnouncementDrawer';

View File

@@ -3,7 +3,7 @@
// 布局图标SVG在上文字在下
// ============================================================
import { useTheme } from '@/components/ThemeProvider';
import { useTheme } from '@/components/providers/ThemeProvider';
import type { NavItemConfig } from './types';
interface NavItemProps {

View File

@@ -12,7 +12,7 @@
// ============================================================
import { useState, useRef, useCallback, useEffect, type ReactNode } from 'react';
import { useTheme } from '@/components/ThemeProvider';
import { useTheme } from '@/components/providers/ThemeProvider';
// ---------- 类型 ----------

View File

@@ -6,7 +6,7 @@
import { Modal, Typography } from 'antd';
import { FileTextOutlined } from '@ant-design/icons';
import { useTheme } from '@/components/ThemeProvider';
import { useTheme } from '@/components/providers/ThemeProvider';
// Vite ?raw 导入:将 .txt 内容作为字符串直接内联
import serviceAgreement from '@/assets/legal/用户服务协议.txt?raw';

View File

@@ -13,7 +13,7 @@
// ============================================================
import {useMemo, useState, useRef, useEffect, useCallback} from 'react';
import {useAppContext} from '@/components/AppProvider';
import {useAppContext} from '@/components/providers/AppProvider';
import { TaskAPI, POLLING_STATUSES, type TaskListRequestParams, type TaskInfoItemResponseBody, type CreatTaskRequestBody, type TaskStatusResponseBody, type TaskStatusString } from '@/services/modules/task';
import { ModelAPI, MODEL_CATEGORY_LABELS, type ModelsListResponseBody, type ModelCategoryStringEnum } from '@/services/modules/models';
import { fetchBanners, type Banner } from '@/services/modules/banner';

View File

@@ -3,9 +3,9 @@ import ReactDOM from 'react-dom/client';
import {App as AntdApp} from 'antd';
import App from './App';
import {ThemeProvider} from './components/ThemeProvider';
import {AppProvider} from './components/AppProvider';
import {SettingsProvider} from './components/SettingsProvider';
import {ThemeProvider} from './components/providers/ThemeProvider';
import {AppProvider} from './components/providers/AppProvider';
import {SettingsProvider} from './components/providers/SettingsProvider';
// 全局样式(包含 Tailwind 指令 + 主题扩展 + 重置)
import './theme/globals.css';

View File

@@ -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';

View File

@@ -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,

View File

@@ -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';

View File

@@ -1,5 +0,0 @@
export function Test3() {
return (
<></>
);
}

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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -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';

View File

@@ -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';

View File

@@ -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;

View File