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

@@ -0,0 +1,208 @@
// ============================================================
// AppProvider — 全局应用状态 Context + Provider + Hook
//
// 导出:
// AppContext, useAppContext() — Context + Consumer Hook
// AppContextValue — 类型
// AppProvider — Provider 组件
// ============================================================
import { createContext, useContext, useState, useMemo, useCallback, useEffect, type ReactNode } from 'react';
import type { Platform, Edition } from '@shared/types';
import { getPlatform, isDev } from '@/utils/platform';
import { safeIpcOn, safeIpcOff } from '@/utils/ipc';
import { AuthAPI, type LoginResponseBody } from '@/services/modules/auth';
import { setToken, clearToken, initTokenStore } from '@/services/request';
import { emit, EVENTS } from '@/utils/event-bus';
import { logger } from '@/utils/logger';
import { getAutoLoginFlag, clearAutoLoginFlag } from '@/services/auth-persistence';
import {
initAuthRefresh,
getStoredRefreshToken,
setStoredRefreshToken,
clearStoredRefreshToken,
initRefreshTokenStore,
scheduleProactiveRefresh,
clearProactiveRefresh,
} from '@/services/auth-token';
import {initModelCache} from '@/services/cache/model-cache';
// ---------- Context 类型 ----------
export interface AppContextValue {
/** 当前操作系统平台 */
platform: Platform;
/** 产品版本(个人版 / 企业版) */
edition: Edition;
/** 用户是否已登录 */
isLoggedIn: boolean;
/** 当前登录用户信息(未登录时为 null结构与 LoginResponseBody 一致) */
user: LoginResponseBody | null;
/** 登录:保存 Token 并更新登录态auth 为登录接口返回值,异步加密存储) */
login: (auth: LoginResponseBody) => Promise<void>;
/** 退出登录:清除 Token 和用户状态 */
logout: () => void;
/** 是否为开发环境 */
isDevMode: boolean;
}
export const AppContext = createContext<AppContextValue | null>(null);
// ---------- Consumer Hook ----------
export function useAppContext(): AppContextValue {
const ctx = useContext(AppContext);
if (!ctx) {
throw new Error('useAppContext() 必须在 <AppProvider> 内部调用');
}
return ctx;
}
// ---------- Provider 组件 ----------
interface AppProviderProps {
children: ReactNode;
}
export function AppProvider({ children }: AppProviderProps) {
const [platform] = useState<Platform>(getPlatform);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [user, setUser] = useState<LoginResponseBody | null>(null);
/**
* edition 从登录用户的 account_type 派生(运行时判断,无需构建变量)
*
* 映射关系:
* "individual" → personal ← 个人用户
* "company" | "employee" → enterprise ← 企业用户
* null未登录 → personal ← 默认最小功能集
*
* 安全性edition 是 useMemo 派生值,不可独立赋值;
* 不写入 localStorage重启/登出后回退默认值,必须重新登录才能确权。
*/
const edition: Edition = useMemo(() => {
if (!user) return 'personal';
return user.account_type === 'individual' ? 'personal' : 'enterprise';
}, [user]);
// ---------- 登录 / 退出 ----------
const login = useCallback(async (auth: LoginResponseBody) => {
await setToken(auth.access_token);
await setStoredRefreshToken(auth.refresh_token);
// 启动主动刷新定时器(在 access_token 过期前自动续期)
scheduleProactiveRefresh(auth.access_token);
setUser(auth);
setIsLoggedIn(true);
logger.info('auth', 'User logged in', {
userId: auth.user_id,
username: auth.username,
});
emit(EVENTS.LOGIN_SUCCESS, auth);
}, []);
const logout = useCallback(() => {
logger.info('auth', 'User logged out');
clearToken();
clearStoredRefreshToken();
clearProactiveRefresh();
setUser(null);
setIsLoggedIn(false);
emit(EVENTS.LOGOUT);
}, []);
// ---------- 启动时初始化 token 存储 + 自动登录 ----------
//
// 注意React StrictMode开发模式会挂载→卸载→重新挂载以暴露副作用问题。
// aborted 标志确保卸载后不再执行异步回调,避免 refreshToken / getUserInfo 重复请求。
useEffect(() => {
let aborted = false;
// 从加密存储恢复 token + 模型缓存 到内存缓存
Promise.all([initTokenStore(), initRefreshTokenStore(), initModelCache()]).then(() => {
if (aborted) return;
// 初始化 401 刷新回调token 缓存就绪后才注册)
initAuthRefresh();
// 自动登录refresh_token 续期)
if (!getAutoLoginFlag()) return;
const refreshToken = getStoredRefreshToken();
if (!refreshToken) return;
AuthAPI.refreshToken({ refresh_token: refreshToken })
.then(async (res) => {
if (aborted) return;
await setToken(res.access_token);
await setStoredRefreshToken(res.refresh_token);
scheduleProactiveRefresh(res.access_token);
setIsLoggedIn(true);
logger.info('auth', 'Auto-login succeeded');
// 补全用户信息
AuthAPI.getUserInfo().then((info) => {
if (aborted) return;
const u: LoginResponseBody = {
access_token: res.access_token,
refresh_token: res.refresh_token,
token_type: res.token_type,
expires_in: res.expires_in,
refresh_expires_in: res.refresh_expires_in,
user_id: info.id,
username: info.username,
role: info.role,
email: info.email ?? '',
balance_cent: info.balance_cent,
account_type: info.account_type,
display_name: info.display_name ?? '',
company_id: info.company_id ?? '',
real_name: info.real_name ?? '',
phone: info.phone,
license_code: '',
admin_permissions: info.admin_permissions ?? [],
};
setUser(u);
emit(EVENTS.LOGIN_SUCCESS, u);
});
})
.catch(() => {
if (aborted) return;
logger.warn('auth', 'Auto-login failed, refresh_token expired');
clearStoredRefreshToken();
// 仅清除自动登录标记,保留用户名供下次表单回填
clearAutoLoginFlag();
});
});
return () => { aborted = true; };
}, []);
// ---------- 监听主进程推送的平台/版本信息 ----------
useEffect(() => {
const handler = (_event: unknown, info: unknown) => {
const data = info as { platform?: string; edition?: string } | undefined;
if (data) {
logger.debug('app', 'Received platform info from main process', data as Record<string, unknown>);
}
};
safeIpcOn('platform-info', handler);
return () => {
safeIpcOff('platform-info', handler);
};
}, []);
const value: AppContextValue = {
platform,
edition,
isLoggedIn,
user,
login,
logout,
isDevMode: isDev(),
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}