From 19fcfb38c8dcdea4793d5432b5800c450e320a05 Mon Sep 17 00:00:00 2001
From: xie2can <384968446@qq.com>
Date: Fri, 15 May 2026 19:45:40 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E7=8A=B6=E6=80=81?=
=?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96(persist)=20+=20=E5=BA=94=E7=94=A8?=
=?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=88=B7=E6=96=B0=E7=94=A8=E6=88=B7?=
=?UTF-8?q?=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/App.tsx | 32 ++++++++++++++++++++-------
src/api/auth.ts | 14 +++++++++++-
src/components/AuthGuard.tsx | 15 +++++++++++++
src/hooks/useAppInit.ts | 42 ++++++++++++++++++++++++++++++++++++
src/mock/auth.ts | 20 +++++++++++++++++
src/router.tsx | 16 +++++++++-----
src/store/user.ts | 20 +++++++++++------
7 files changed, 139 insertions(+), 20 deletions(-)
create mode 100644 src/components/AuthGuard.tsx
create mode 100644 src/hooks/useAppInit.ts
diff --git a/src/App.tsx b/src/App.tsx
index f072a18..a23bff7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,19 +1,35 @@
-import { App as AntdApp, ConfigProvider } from 'antd';
+import { App as AntdApp, ConfigProvider, Spin } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import { RouterProvider } from 'react-router';
import router from '@/router';
+import useAppInit from '@/hooks/useAppInit';
import './App.css';
dayjs.locale('zh-cn');
-const App = () => (
-
-
-
-
-
-);
+const App = () => {
+ const initialized = useAppInit();
+
+ // 初始化完成前显示全局 loading
+ if (!initialized) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ );
+};
export default App;
diff --git a/src/api/auth.ts b/src/api/auth.ts
index ff738bd..c9bb0c4 100644
--- a/src/api/auth.ts
+++ b/src/api/auth.ts
@@ -1,4 +1,4 @@
-import { post } from '@/utils/request';
+import { get, post } from '@/utils/request';
/** 登录请求参数(不导出) */
interface LoginParams {
@@ -16,8 +16,20 @@ interface LoginResult {
token: string;
}
+/** 当前用户信息(不导出,与 LoginResult 结构相同) */
+interface CurrentUser {
+ id: string;
+ userName: string;
+ nickName: string;
+ roles: string[];
+ token: string;
+}
+
/**
* 登录接口
* @param data 登录参数
*/
export const login = (data: LoginParams) => post('/api/auth/login', data);
+
+/** 获取当前登录用户信息(应用初始化时调用,刷新 userInfo) */
+export const getCurrentUser = () => get('/api/auth/me');
diff --git a/src/components/AuthGuard.tsx b/src/components/AuthGuard.tsx
new file mode 100644
index 0000000..0b06ed6
--- /dev/null
+++ b/src/components/AuthGuard.tsx
@@ -0,0 +1,15 @@
+import { Navigate, Outlet } from 'react-router';
+import { useUserStore } from '@/store';
+
+/** 路由守卫:未登录时跳转到登录页 */
+const AuthGuard = () => {
+ const userInfo = useUserStore((s) => s.userInfo);
+
+ if (!userInfo) {
+ return ;
+ }
+
+ return ;
+};
+
+export default AuthGuard;
diff --git a/src/hooks/useAppInit.ts b/src/hooks/useAppInit.ts
new file mode 100644
index 0000000..bbd09a2
--- /dev/null
+++ b/src/hooks/useAppInit.ts
@@ -0,0 +1,42 @@
+import { useEffect, useState } from 'react';
+import { getCurrentUser } from '@/api/auth';
+import { useUserStore } from '@/store';
+
+/**
+ * 应用初始化 hook
+ * 刷新页面时,如果 localStorage 中有 token,则调用 /api/auth/me 刷新用户信息
+ * token 失效时自动清除 userInfo
+ */
+const useAppInit = () => {
+ const [initialized, setInitialized] = useState(false);
+ const setUserInfo = useUserStore((s) => s.setUserInfo);
+ const clearUserInfo = useUserStore((s) => s.clearUserInfo);
+ const token = useUserStore((s) => s.userInfo?.token);
+
+ useEffect(() => {
+ // 没有 token,跳过初始化
+ if (!token) {
+ setInitialized(true);
+ return;
+ }
+
+ const init = async () => {
+ try {
+ const res = await getCurrentUser();
+ // 用接口返回的最新数据更新 userInfo(保留原 token)
+ setUserInfo({ ...res.data!, token });
+ } catch {
+ // token 失效,清除用户信息
+ clearUserInfo();
+ } finally {
+ setInitialized(true);
+ }
+ };
+
+ init();
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ return initialized;
+};
+
+export default useAppInit;
diff --git a/src/mock/auth.ts b/src/mock/auth.ts
index 5363e40..624c638 100644
--- a/src/mock/auth.ts
+++ b/src/mock/auth.ts
@@ -3,6 +3,9 @@ import { http, HttpResponse } from 'msw';
/** 包装为统一响应格式 */
const ok = (data: unknown) => ({ code: '0', msg: 'ok', data, time: Date.now(), ok: true });
+/** 包装为错误响应格式 */
+const fail = (msg: string) => ({ code: '401', msg, data: null, time: Date.now(), ok: false });
+
export const authHandlers = [
http.post('/api/auth/login', async ({ request }) => {
const body = await request.json() as { tenantId: string; userName: string; password: string };
@@ -16,4 +19,21 @@ export const authHandlers = [
};
return HttpResponse.json(ok(result));
}),
+
+ http.get('/api/auth/me', ({ request }) => {
+ // 从请求头中获取 token,有则返回用户信息,无则返回 401
+ const authHeader = request.headers.get('Authorization');
+ if (!authHeader) {
+ return HttpResponse.json(fail('未登录'), { status: 401 });
+ }
+ // 模拟返回当前用户信息
+ const result = {
+ id: '1',
+ userName: 'admin',
+ nickName: '管理员',
+ roles: ['admin'],
+ token: authHeader.replace('Bearer ', ''),
+ };
+ return HttpResponse.json(ok(result));
+ }),
];
diff --git a/src/router.tsx b/src/router.tsx
index e10aef9..0b500dd 100644
--- a/src/router.tsx
+++ b/src/router.tsx
@@ -1,17 +1,23 @@
import { createBrowserRouter } from 'react-router';
+import AuthGuard from '@/components/AuthGuard';
import RootLayout from '@/layouts/RootLayout';
import Login from '@/pages/login/index';
import { routes } from '@/routes';
import { toRouteObjects } from '@/routes/utils';
const router = createBrowserRouter([
- // 登录页:独立路由,不加载布局
+ // 登录页:独立路由,不加载布局,无需鉴权
{ path: '/login', element: },
- // 带布局的主应用
+ // 需要鉴权的路由:AuthGuard 判断登录状态
{
- path: '/',
- element: ,
- children: toRouteObjects(routes),
+ element: ,
+ children: [
+ {
+ path: '/',
+ element: ,
+ children: toRouteObjects(routes),
+ },
+ ],
},
]);
diff --git a/src/store/user.ts b/src/store/user.ts
index b2ee886..f1d6566 100644
--- a/src/store/user.ts
+++ b/src/store/user.ts
@@ -1,4 +1,5 @@
import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
/** 当前登录用户信息 */
interface UserInfo {
@@ -24,12 +25,19 @@ interface UserState {
clearUserInfo: () => void;
}
-/** 用户状态 store */
-const useUserStore = create((set) => ({
- userInfo: null,
- setUserInfo: (info) => set({ userInfo: info }),
- clearUserInfo: () => set({ userInfo: null }),
-}));
+/** 用户状态 store,使用 persist 中间件持久化到 localStorage */
+const useUserStore = create()(
+ persist(
+ (set) => ({
+ userInfo: null,
+ setUserInfo: (info) => set({ userInfo: info }),
+ clearUserInfo: () => set({ userInfo: null }),
+ }),
+ {
+ name: 'taotie-user',
+ },
+ ),
+);
export default useUserStore;
export type { UserInfo };