redux 加入

This commit is contained in:
mmc
2026-04-21 10:33:48 +08:00
parent fc23b6fa22
commit 79f8252b08
8 changed files with 138 additions and 68 deletions

2
package-lock.json generated
View File

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

View File

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

View File

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

View File

@@ -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 <Navigate to="/login" replace />;
}
return <Outlet />;
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return <Outlet />;
}

View File

@@ -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(
<StrictMode>
<App />
</StrictMode>,
<Provider store={store}>
<StrictMode>
<App />
</StrictMode>,
</Provider>
);

View File

@@ -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('/');
};

10
src/store/index.js Normal file
View File

@@ -0,0 +1,10 @@
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
},
});
export default store;

37
src/store/userSlice.js Normal file
View File

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