From 79f8252b080e00be1ed85ab56abc0292db5c6d8a Mon Sep 17 00:00:00 2001
From: mmc <1556375442@qq.com>
Date: Tue, 21 Apr 2026 10:33:48 +0800
Subject: [PATCH] =?UTF-8?q?redux=20=E5=8A=A0=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package-lock.json | 2 +
package.json | 2 +
src/api/request.ts | 119 +++++++++++++++---------------
src/components/ProtectedRoute.tsx | 13 ++--
src/main.tsx | 15 ++--
src/pages/Login.tsx | 8 ++
src/store/index.js | 10 +++
src/store/userSlice.js | 37 ++++++++++
8 files changed, 138 insertions(+), 68 deletions(-)
create mode 100644 src/store/index.js
create mode 100644 src/store/userSlice.js
diff --git a/package-lock.json b/package-lock.json
index 01ea515..34fab69 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@ant-design/icons": "^6.1.1",
"@google/genai": "^1.29.0",
+ "@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/vite": "^4.1.14",
"@vitejs/plugin-react": "^5.0.4",
"antd": "^5.24.1",
@@ -23,6 +24,7 @@
"qrcode": "^1.5.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-redux": "^9.2.0",
"react-router-dom": "^7.14.1",
"recharts": "^3.8.1",
"tailwind-merge": "^3.5.0",
diff --git a/package.json b/package.json
index 95b5ac6..75981ee 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"@ant-design/icons": "^6.1.1",
"@google/genai": "^1.29.0",
+ "@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/vite": "^4.1.14",
"@vitejs/plugin-react": "^5.0.4",
"antd": "^5.24.1",
@@ -26,6 +27,7 @@
"qrcode": "^1.5.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-redux": "^9.2.0",
"react-router-dom": "^7.14.1",
"recharts": "^3.8.1",
"tailwind-merge": "^3.5.0",
diff --git a/src/api/request.ts b/src/api/request.ts
index 0e61dbb..98fcf48 100644
--- a/src/api/request.ts
+++ b/src/api/request.ts
@@ -2,78 +2,81 @@ import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'ax
import envConfig from '../../env';
import Cookies from 'js-cookie';
import { message } from 'antd';
+import store from '../store';
+import { logout } from '../store/userSlice';
-const API_BASE_URL = envConfig.baseURL;
+const API_BASE_URL = envConfig.baseURL;
const api = axios.create({
- baseURL: API_BASE_URL,
- timeout: 20000,
- headers: {
- 'Content-Type': 'application/json',
+ baseURL: API_BASE_URL,
+ timeout: 20000,
+ headers: {
+ 'Content-Type': 'application/json',
- },
- withCredentials: true,
+ },
+ withCredentials: true,
});
// 请求拦截器:自动添加token
// 请求拦截器
api.interceptors.request.use(
- (config: InternalAxiosRequestConfig) => {
- // 从 localStorage 获取 token
- const token = localStorage.getItem('token');
- if (token && config.headers) {
- // 将 token 添加到请求头
- config.headers['Authorization'] = `Bearer ${token}`;
- }
- return config;
- },
- (error: AxiosError) => {
- return Promise.reject(error);
- }
+ (config: InternalAxiosRequestConfig) => {
+ // 从 localStorage 获取 token
+ const token = localStorage.getItem('token');
+ if (token && config.headers) {
+ // 将 token 添加到请求头
+ config.headers['Authorization'] = `Bearer ${token}`;
+ }
+ return config;
+ },
+ (error: AxiosError) => {
+ return Promise.reject(error);
+ }
);
// 响应拦截器
api.interceptors.response.use(
- (response: AxiosResponse) => {
- // 处理成功响应
- const { data } = response;
- // 如果后端规范定义了 code,可以在这里统一处理
- // if (data.code !== 200) {
- // message.error(data.message || '请求失败');
- // return Promise.reject(new Error(data.message || 'Error'));
- // }
- return data;
- },
- (error: AxiosError) => {
- // 处理错误响应
- if (error.response) {
- switch (error.response.status) {
- case 401:
- // 未授权,清除本地信息并跳转到登录页
- localStorage.removeItem('token');
- localStorage.removeItem('isAuthenticated');
- localStorage.removeItem('user');
- window.location.href = '/login';
- message.error('会话已过期,请重新登录');
- break;
- case 403:
- message.error('拒绝访问:权限不足');
- break;
- case 404:
- message.error('资源不存在');
- break;
- case 500:
- message.error('服务器内部错误');
- break;
- default:
- message.error(error.message || '网络错误');
- }
- } else {
- message.error('连接服务器失败');
- }
- return Promise.reject(error);
- }
+ (response: AxiosResponse) => {
+ // 处理成功响应
+ const { data } = response;
+ // 如果后端规范定义了 code,可以在这里统一处理
+ // if (data.code !== 200) {
+ // message.error(data.message || '请求失败');
+ // return Promise.reject(new Error(data.message || 'Error'));
+ // }
+ return data;
+ },
+ (error: AxiosError) => {
+ // 处理错误响应
+ if (error.response) {
+ switch (error.response.status) {
+ case 401:
+ // 未授权,清除本地信息并跳转到登录页
+ localStorage.removeItem('token');
+ localStorage.removeItem('isAuthenticated');
+ localStorage.removeItem('user');
+ store.dispatch(logout());
+ window.location.href = '/login';
+ message.error('会话已过期,请重新登录');
+ break;
+ case 403:
+ message.error('拒绝访问:权限不足');
+ break;
+ case 404:
+ message.error('资源不存在');
+ break;
+ case 500:
+ message.error('服务器内部错误');
+ break;
+ default:
+ message.error(error.message || '网络错误');
+ }
+ } else {
+ message.error('连接服务器失败');
+ }
+ return Promise.reject(error);
+ }
);
export default api;
diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx
index 6f94d10..95ce427 100644
--- a/src/components/ProtectedRoute.tsx
+++ b/src/components/ProtectedRoute.tsx
@@ -1,12 +1,15 @@
import React from 'react';
+import { useSelector } from 'react-redux';
import { Navigate, Outlet } from 'react-router-dom';
export default function ProtectedRoute() {
- const isAuthenticated = localStorage.getItem('isAuthenticated') === 'true';
+ const isAuthenticated = localStorage.getItem('isAuthenticated') === 'true';
+ //const { isAuthenticated } = useSelector(state => state.user);
- if (!isAuthenticated) {
- return ;
- }
- return ;
+ if (!isAuthenticated) {
+ return ;
+ }
+
+ return ;
}
diff --git a/src/main.tsx b/src/main.tsx
index 080dac3..2d3de05 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,15 @@
-import {StrictMode} from 'react';
-import {createRoot} from 'react-dom/client';
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
import App from './App.tsx';
+import { Provider } from 'react-redux';
+import { store } from './store';
import './index.css';
createRoot(document.getElementById('root')!).render(
-
-
- ,
+
+
+
+ ,
+
+
);
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
index 714d0ea..a6f3459 100644
--- a/src/pages/Login.tsx
+++ b/src/pages/Login.tsx
@@ -16,11 +16,14 @@ import {
import { motion } from 'motion/react';
import bgcImg from '../assets/bgc2.png';
import logincard from '../assets/logincard.png';
+import { useDispatch } from 'react-redux';
+import { login } from '../store/userSlice';
const { Text, Title } = Typography;
export default function Login() {
const navigate = useNavigate();
+ const dispatch = useDispatch();
// 账号密码登录
const onFinish = (values: any) => {
@@ -49,6 +52,11 @@ export default function Login() {
localStorage.setItem('user', JSON.stringify(userInfo));
message.success(`欢迎回来, ${name}`);
+ dispatch(login({
+ token: 'mock_token_' + Date.now(),
+ userInfo: userInfo
+ }));
+
navigate('/');
};
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..f8f21ce
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,10 @@
+import { configureStore } from '@reduxjs/toolkit';
+import userReducer from './userSlice';
+
+export const store = configureStore({
+ reducer: {
+ user: userReducer,
+ },
+});
+
+export default store;
diff --git a/src/store/userSlice.js b/src/store/userSlice.js
new file mode 100644
index 0000000..4ba7a19
--- /dev/null
+++ b/src/store/userSlice.js
@@ -0,0 +1,37 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ token: localStorage.getItem('token') || null,
+ userInfo: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : null,
+ isAuthenticated: localStorage.getItem('isAuthenticated') === 'true',
+};
+
+const userSlice = createSlice({
+ name: 'user',
+ initialState,
+ reducers: {
+ // 登录
+ login: (state, action) => {
+ state.token = action.payload.token;
+ state.userInfo = action.payload.userInfo;
+ state.isAuthenticated = true;
+
+ localStorage.setItem('token', action.payload.token);
+ localStorage.setItem('user', JSON.stringify(action.payload.userInfo));
+ localStorage.setItem('isAuthenticated', 'true');
+ },
+ // 退出
+ logout: (state) => {
+ state.token = null;
+ state.userInfo = null;
+ state.isAuthenticated = false;
+
+ localStorage.removeItem('token');
+ localStorage.removeItem('user');
+ localStorage.removeItem('isAuthenticated');
+ },
+ },
+});
+
+export const { login, logout } = userSlice.actions;
+export default userSlice.reducer;