bash
import { message } from 'antd';
import service from '@/utils/request2';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import Cookies from 'js-cookie';
import { Result } from '@/interface/ResultInterface';
interface userInterface {
token: string;
username: string;
isAdmin: string;
id: string;
}
// 异步Thunk:登录并获取用户信息
export const loginAndFetchUser = createAsyncThunk(
'auth/loginAndFetchUser',
async (loginData: any, { rejectWithValue }) => {
try {
// 1. 调用登录接口
const loginResponse: Result<userInterface> = await service.post('/auth/login', loginData);
const { token, username, isAdmin, id } = loginResponse.data;
// 2. 将token存储到localStorage和Redux state
// 3. 使用token获取用户详细信息
// const userInfoResponse = await getUserProfile(token);
return { token, username, isAdmin, id };
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
// 2. 提供初始状态,并明确指定其类型
const initialState: userInterface = {
token: Cookies.get('header-access-token-dianchi') || null,
username: Cookies.get('username') || null,
isAdmin: Cookies.get('isAdmin') || null,
id: Cookies.get('id') || null,
};
const userStore = createSlice({
name: 'user',
initialState,
reducers: {
logout: (state) => {
state.token = null;
state.username = null;
Cookies.remove('username');
Cookies.remove('header-access-token-dianchi');
Cookies.remove('isAdmin');
Cookies.remove('id');
},
},
extraReducers: (builder) => {
builder
.addCase(loginAndFetchUser.pending, (state) => { })
.addCase(loginAndFetchUser.fulfilled, (state, action) => {
state.token = action.payload.token;
state.username = action.payload.username;
state.isAdmin = action.payload.isAdmin.toString();
state.id = action.payload.id.toString();
Cookies.set('header-access-token-dianchi', state.token);
Cookies.set('username', state.username);
Cookies.set('isAdmin', state.isAdmin);
Cookies.set('id', state.id);
})
.addCase(loginAndFetchUser.rejected, (state, action) => { });
},
});
//解构出action对象的函数
export const { logout } = userStore.actions;
//获取reducer函数
const userReducer = userStore.reducer;
export default userReducer;
bash
import { MenuItem } from '@/types/MenuItem.type';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface MenuState {
menuList: MenuItem[]; // menuList 应该是 MenuItem 类型的数组
// 如果你有其他状态字段,也在这里定义,例如:
isHasMenu: boolean;
}
// 2. 提供初始状态,并明确指定其类型
const initialState: MenuState = {
menuList: [], // 初始为空数组,符合 MenuItem[] 类型
isHasMenu: false,
};
const menuStore = createSlice({
name: 'menu',
initialState,
reducers: {
setIsHasMenu(state: MenuState) {
state.isHasMenu = true;
},
// 示例1:设置整个菜单列表]
setMenuList(state: MenuState, action: PayloadAction<MenuItem[]>) {
state.menuList = action.payload;
},
// 示例2:添加单个菜单项
addMenuItem(state: MenuState, action: PayloadAction<MenuItem>) {
state.menuList.push(action.payload);
},
// 示例3:根据ID移除菜单项
removeMenuItem(state: MenuState, action: PayloadAction<string>) {
// 假设ID是字符串类型
state.menuList = state.menuList.filter((item: MenuItem) => item.path !== action.payload);
},
},
});
//解构出action对象的函数
const { setMenuList, addMenuItem, removeMenuItem,setIsHasMenu } = menuStore.actions;
//获取reducer函数
const menuReducer = menuStore.reducer;
export { setIsHasMenu,setMenuList, addMenuItem, removeMenuItem };
export default menuReducer;
bash
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface ThemeState {
theme: 'light' | 'dark';
}
// 从 localStorage 读取主题设置,默认为 light
const getInitialTheme = (): 'light' | 'dark' => {
const savedTheme = localStorage.getItem('theme');
return (savedTheme === 'dark' || savedTheme === 'light') ? savedTheme : 'light';
};
const initialState: ThemeState = {
theme: getInitialTheme()
};
const themeSlice = createSlice({
name: 'theme',
initialState,
reducers: {
toggleTheme: (state) => {
state.theme = state.theme === 'light' ? 'dark' : 'light';
localStorage.setItem('theme', state.theme);
// 更新 document 的 data-theme 属性
document.documentElement.setAttribute('data-theme', state.theme);
},
setTheme: (state, action: PayloadAction<'light' | 'dark'>) => {
state.theme = action.payload;
localStorage.setItem('theme', state.theme);
// 更新 document 的 data-theme 属性
document.documentElement.setAttribute('data-theme', state.theme);
}
}
});
// 导出 actions
export const { toggleTheme, setTheme } = themeSlice.actions;
// 导出 reducer
const themeReducer = themeSlice.reducer;
export default themeReducer;
bash
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './slice/counterSlice';
import menuReducer from './slice/menuSlice';
import userReducer from './slice/userSlice';
import themeReducer from './slice/themeSlice';
const store = configureStore({
reducer: {
counterReducer: counterReducer,
menuReducer:menuReducer,
userReducer:userReducer,
themeReducer:themeReducer,
}
})
export type AppDispatch = typeof store.dispatch; // 导出 Dispatch 类型,用于异步 Action
export default store;
使用
bash
import React, { useState } from 'react';
import { Form, Input, Button, message } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import './index.css'; // 样式见下方
import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { loginAndFetchUser } from '@/store/slice/userSlice';
import { useSelector } from 'react-redux';
const Login = () => {
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch<any>();
const theme = useSelector((state: any) => state.themeReducer.theme);
const onFinish = async ({ account, password }) => {
let params = {
account: account,
password,
};
try {
try {
await dispatch(loginAndFetchUser(params)).unwrap();
navigate('/');
// 登录成功后的跳转逻辑在下面的useEffect中处理
} catch (error) {
// 错误信息已在Redux中存储,这里可以显示错误提示
console.error('Login failed:', error);
}
} catch (error) {
navigate('/');
}
};
return (
<div className="login-wrapper">
<div className="login-card">
<h1 className="login-title">电池管理项目</h1>
<Form
name="login"
layout="vertical"
onFinish={onFinish}
autoComplete="off"
initialValues={{
account: 'admin',
password: '123456',
}}
>
<Form.Item
name="account"
rules={[{ required: true, message: '请输入账户' }]}
>
<Input
prefix={<UserOutlined />}
placeholder="账户 / Email"
size="large"
/>
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password
prefix={<LockOutlined />}
placeholder="密码"
size="large"
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={loading}
size="large"
block
className="login-btn"
>
立 即 登 录
</Button>
</Form.Item>
</Form>
</div>
</div>
);
};
export default Login;