- shadcn/ui 标准化:废弃纸感令牌,统一 bg-background/text-foreground 等 - Tailwind v4 + @theme inline,移除 tailwind.config.js - React 19 use() + Suspense 流式渲染,首屏骨架秒出 - 三级错误边界:Route → Section → Widget 层层兜底 - 错误上报:useErrorReport → sendBeacon → /api/log mock 端点 - 三层安全边界:L1 角色门禁 / L2 权限点门禁 / L3 数据范围 - 权限位图 base36 压缩:67 权限点 → ~14 字符,JWT 体积减少 ≥ 99% - notify 统一 Toast 封装,禁止业务直接 import sonner - PluginBoundary 替代 PluginLoader(错误边界 + Suspense + Skeleton 三件套) 验证:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功
94 lines
2.4 KiB
TypeScript
94 lines
2.4 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
||
import type { AuthState, UserSession } from "./types";
|
||
|
||
/**
|
||
* useAuth - 会话状态管理
|
||
*
|
||
* 用途:管理用户登录状态、token、用户信息。
|
||
* P2 阶段:token 存储在 localStorage(F12 裁决),P6 评估迁移 httpOnly Cookie。
|
||
*
|
||
* 注意:本 hook 提供"状态管理"骨架,具体的登录/登出 API 调用
|
||
* 由 apps/* 注入(通过 AuthProvider Context 或 props 传入 fetcher)。
|
||
*
|
||
* @example
|
||
* const { user, isAuthenticated, login, logout } = useAuth();
|
||
*/
|
||
|
||
const TOKEN_KEY = "edu_auth_token";
|
||
const USER_KEY = "edu_auth_user";
|
||
|
||
export interface UseAuthReturn extends AuthState {
|
||
/** 登录(由调用方注入 fetcher) */
|
||
login: (token: string, user: UserSession) => void;
|
||
/** 登出 */
|
||
logout: () => void;
|
||
/** 刷新用户信息 */
|
||
refreshUser: (user: UserSession) => void;
|
||
}
|
||
|
||
export function useAuth(): UseAuthReturn {
|
||
const [state, setState] = useState<AuthState>({
|
||
user: null,
|
||
token: null,
|
||
isAuthenticated: false,
|
||
isLoading: true,
|
||
});
|
||
|
||
// 初始化:从 localStorage 读取 token + user(F12 裁决 P2 用 localStorage)
|
||
useEffect(() => {
|
||
try {
|
||
const token = localStorage.getItem(TOKEN_KEY);
|
||
const userJson = localStorage.getItem(USER_KEY);
|
||
const user = userJson ? (JSON.parse(userJson) as UserSession) : null;
|
||
|
||
setState({
|
||
user,
|
||
token,
|
||
isAuthenticated: Boolean(token && user),
|
||
isLoading: false,
|
||
});
|
||
} catch {
|
||
setState({
|
||
user: null,
|
||
token: null,
|
||
isAuthenticated: false,
|
||
isLoading: false,
|
||
});
|
||
}
|
||
}, []);
|
||
|
||
const login = useCallback((token: string, user: UserSession): void => {
|
||
localStorage.setItem(TOKEN_KEY, token);
|
||
localStorage.setItem(USER_KEY, JSON.stringify(user));
|
||
setState({
|
||
user,
|
||
token,
|
||
isAuthenticated: true,
|
||
isLoading: false,
|
||
});
|
||
}, []);
|
||
|
||
const logout = useCallback((): void => {
|
||
localStorage.removeItem(TOKEN_KEY);
|
||
localStorage.removeItem(USER_KEY);
|
||
setState({
|
||
user: null,
|
||
token: null,
|
||
isAuthenticated: false,
|
||
isLoading: false,
|
||
});
|
||
}, []);
|
||
|
||
const refreshUser = useCallback((user: UserSession): void => {
|
||
localStorage.setItem(USER_KEY, JSON.stringify(user));
|
||
setState((prev) => ({ ...prev, user }));
|
||
}, []);
|
||
|
||
return {
|
||
...state,
|
||
login,
|
||
logout,
|
||
refreshUser,
|
||
};
|
||
}
|