redux 加入
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
15
src/main.tsx
15
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(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
<Provider store={store}>
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
</Provider>
|
||||
|
||||
);
|
||||
|
||||
@@ -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
10
src/store/index.js
Normal 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
37
src/store/userSlice.js
Normal 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;
|
||||
Reference in New Issue
Block a user