refactor(状态管理): 迁移到 Zustand 并优化布局
- 新增 Zustand 用户状态管理(userStore.ts) - 集成 persist 中间件实现自动持久化 - 简化 layout.tsx 用户状态管理逻辑 - 更新 Redux store 配置 - 优化主入口和国际化配置
This commit is contained in:
parent
b3c0a05a3e
commit
f20951fb92
172
src/layout.tsx
172
src/layout.tsx
@ -1,11 +1,15 @@
|
|||||||
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
||||||
import { Switch, Route, Redirect, useHistory } from 'react-router-dom';
|
import { Switch, Route, Redirect, useHistory } from 'react-router-dom';
|
||||||
import { Layout, Menu, Breadcrumb, Spin } from '@arco-design/web-react';
|
import { Layout, Menu, Spin, Button } from '@arco-design/web-react';
|
||||||
import cs from 'classnames';
|
import cs from 'classnames';
|
||||||
import {
|
import {
|
||||||
IconUser,
|
IconUser,
|
||||||
IconMenuFold,
|
IconMenuFold,
|
||||||
IconMenuUnfold,
|
IconMenuUnfold,
|
||||||
|
IconApps,
|
||||||
|
IconHome,
|
||||||
|
IconRefresh,
|
||||||
|
IconQuestionCircle,
|
||||||
} from '@arco-design/web-react/icon';
|
} from '@arco-design/web-react/icon';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
@ -19,7 +23,9 @@ import useLocale from './utils/useLocale';
|
|||||||
import getUrlParams from './utils/getUrlParams';
|
import getUrlParams from './utils/getUrlParams';
|
||||||
import lazyload from './utils/lazyload';
|
import lazyload from './utils/lazyload';
|
||||||
import { GlobalState } from './store';
|
import { GlobalState } from './store';
|
||||||
|
import { useUserStore } from './store/userStore';
|
||||||
import { TabItem } from './types/tabs';
|
import { TabItem } from './types/tabs';
|
||||||
|
import { getUserInfo as fetchUserInfoAPI } from '@/api/auth';
|
||||||
import styles from './style/layout.module.less';
|
import styles from './style/layout.module.less';
|
||||||
|
|
||||||
const MenuItem = Menu.Item;
|
const MenuItem = Menu.Item;
|
||||||
@ -28,6 +34,21 @@ const SubMenu = Menu.SubMenu;
|
|||||||
const Sider = Layout.Sider;
|
const Sider = Layout.Sider;
|
||||||
const Content = Layout.Content;
|
const Content = Layout.Content;
|
||||||
|
|
||||||
|
function findRouteByKey(key: string, routeList: IRoute[] = []): IRoute | null {
|
||||||
|
for (const route of routeList) {
|
||||||
|
if (route.key === key) {
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
if (route.children?.length) {
|
||||||
|
const child = findRouteByKey(key, route.children);
|
||||||
|
if (child) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 简单的404页面组件
|
// 简单的404页面组件
|
||||||
function NotFound() {
|
function NotFound() {
|
||||||
return (
|
return (
|
||||||
@ -54,7 +75,7 @@ function getIconFromKey(key) {
|
|||||||
case 'user-management':
|
case 'user-management':
|
||||||
return <IconUser className={styles.icon} />;
|
return <IconUser className={styles.icon} />;
|
||||||
default:
|
default:
|
||||||
return <div className={styles['icon-empty']} />;
|
return <IconApps className={styles.icon} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,22 +113,65 @@ function PageLayout() {
|
|||||||
const pathname = history.location.pathname;
|
const pathname = history.location.pathname;
|
||||||
const currentComponent = qs.parseUrl(pathname).url.slice(1);
|
const currentComponent = qs.parseUrl(pathname).url.slice(1);
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const { settings, userLoading, userInfo } = useSelector(
|
|
||||||
(state: GlobalState) => state
|
// 从 Redux 获取 settings 和 tabs
|
||||||
);
|
const { settings } = useSelector((state: GlobalState) => state);
|
||||||
|
|
||||||
|
// 从 Zustand 获取用户信息
|
||||||
|
const { userInfo, userLoading, setUserInfo, updateUserInfo, setUserLoading } =
|
||||||
|
useUserStore();
|
||||||
|
|
||||||
const [routes, defaultRoute] = useRoute(userInfo?.permissions);
|
const [routes, defaultRoute] = useRoute(userInfo?.permissions);
|
||||||
const defaultSelectedKeys = [currentComponent || defaultRoute];
|
const defaultSelectedKeys = [currentComponent || defaultRoute];
|
||||||
const paths = (currentComponent || defaultRoute).split('/');
|
const paths = (currentComponent || defaultRoute).split('/');
|
||||||
const defaultOpenKeys = paths.slice(0, paths.length - 1);
|
const defaultOpenKeys = paths.slice(0, paths.length - 1);
|
||||||
|
|
||||||
const [breadcrumb, setBreadCrumb] = useState([]);
|
const [breadcrumb, setBreadCrumb] = useState<CrumbNode[]>([]);
|
||||||
const [collapsed, setCollapsed] = useState<boolean>(false);
|
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||||
const [selectedKeys, setSelectedKeys] =
|
const [selectedKeys, setSelectedKeys] =
|
||||||
useState<string[]>(defaultSelectedKeys);
|
useState<string[]>(defaultSelectedKeys);
|
||||||
const [openKeys, setOpenKeys] = useState<string[]>(defaultOpenKeys);
|
const [openKeys, setOpenKeys] = useState<string[]>(defaultOpenKeys);
|
||||||
|
|
||||||
const routeMap = useRef<Map<string, React.ReactNode[]>>(new Map());
|
// 使用 Zustand:调用 API 获取最新用户信息
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchLatestUserInfo = async () => {
|
||||||
|
// 只有有用户数据时才调用 API
|
||||||
|
if (!userInfo?.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetchUserInfoAPI();
|
||||||
|
if (result.code == 0 && result.data) {
|
||||||
|
const data = result.data;
|
||||||
|
// 使用 updateUserInfo 部分更新,自动保留现有字段(如 avatar)
|
||||||
|
updateUserInfo({
|
||||||
|
userId: data.id || data.userId,
|
||||||
|
username: data.username,
|
||||||
|
nickname: data.nickname || data.name,
|
||||||
|
name: data.nickname || data.name,
|
||||||
|
role: data.role,
|
||||||
|
avatar: data.avatar || userInfo.avatar, // 如果 API 返回空,保留现有
|
||||||
|
email: data.email,
|
||||||
|
phone: data.phone,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取最新用户信息失败:', error);
|
||||||
|
// 失败时不需要做任何事,Zustand 自动保留现有数据
|
||||||
|
} finally {
|
||||||
|
setUserLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchLatestUserInfo();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
type CrumbNode = { key: string; name: string; icon?: React.ReactNode };
|
||||||
|
|
||||||
|
const routeMap = useRef<Map<string, CrumbNode[]>>(new Map());
|
||||||
const menuMap = useRef<
|
const menuMap = useRef<
|
||||||
Map<string, { menuItem?: boolean; subMenu?: boolean }>
|
Map<string, { menuItem?: boolean; subMenu?: boolean }>
|
||||||
>(new Map());
|
>(new Map());
|
||||||
@ -120,6 +184,34 @@ function PageLayout() {
|
|||||||
const showFooter = settings.footer && urlParams.footer !== false;
|
const showFooter = settings.footer && urlParams.footer !== false;
|
||||||
|
|
||||||
const flattenRoutes = useMemo(() => getFlattenRoutes(routes) || [], [routes]);
|
const flattenRoutes = useMemo(() => getFlattenRoutes(routes) || [], [routes]);
|
||||||
|
const fallbackCrumb = useMemo<CrumbNode | null>(() => {
|
||||||
|
const matched = findRouteByKey(defaultRoute, routes);
|
||||||
|
if (matched) {
|
||||||
|
return {
|
||||||
|
key: matched.key,
|
||||||
|
name: matched.name,
|
||||||
|
icon: getIconFromKey(matched.key),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [defaultRoute, routes]);
|
||||||
|
|
||||||
|
const displayedBreadcrumb = useMemo<CrumbNode[]>(() => {
|
||||||
|
const nodes: CrumbNode[] = [
|
||||||
|
{
|
||||||
|
key: 'home',
|
||||||
|
name: 'breadcrumb.home',
|
||||||
|
icon: <IconHome />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const currentNode = breadcrumb.length
|
||||||
|
? breadcrumb[breadcrumb.length - 1]
|
||||||
|
: fallbackCrumb;
|
||||||
|
if (currentNode) {
|
||||||
|
nodes.push(currentNode);
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}, [breadcrumb, fallbackCrumb]);
|
||||||
|
|
||||||
function onClickMenuItem(key) {
|
function onClickMenuItem(key) {
|
||||||
const currentRoute = flattenRoutes.find((r) => r.key === key);
|
const currentRoute = flattenRoutes.find((r) => r.key === key);
|
||||||
@ -136,13 +228,28 @@ function PageLayout() {
|
|||||||
setCollapsed((collapsed) => !collapsed);
|
setCollapsed((collapsed) => !collapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleBreadcrumbClick(node: CrumbNode, index: number) {
|
||||||
|
if (index === displayedBreadcrumb.length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node.key === 'home') {
|
||||||
|
history.push('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
history.push(`/${node.key}`);
|
||||||
|
}
|
||||||
|
|
||||||
const paddingLeft = showMenu ? { paddingLeft: menuWidth } : {};
|
const paddingLeft = showMenu ? { paddingLeft: menuWidth } : {};
|
||||||
const paddingTop = showNavbar ? { paddingTop: navbarHeight } : {};
|
const paddingTop = showNavbar ? { paddingTop: navbarHeight } : {};
|
||||||
const paddingStyle = { ...paddingLeft, ...paddingTop };
|
const paddingStyle = { ...paddingLeft, ...paddingTop };
|
||||||
|
|
||||||
function renderRoutes(locale) {
|
function renderRoutes(locale) {
|
||||||
routeMap.current.clear();
|
routeMap.current.clear();
|
||||||
return function travel(_routes: IRoute[], level, parentNode = []) {
|
return function travel(
|
||||||
|
_routes: IRoute[],
|
||||||
|
level,
|
||||||
|
parentNode: CrumbNode[] = []
|
||||||
|
) {
|
||||||
return _routes.map((route) => {
|
return _routes.map((route) => {
|
||||||
const { breadcrumb = true, ignore } = route;
|
const { breadcrumb = true, ignore } = route;
|
||||||
const iconDom = getIconFromKey(route.key);
|
const iconDom = getIconFromKey(route.key);
|
||||||
@ -154,7 +261,16 @@ function PageLayout() {
|
|||||||
|
|
||||||
routeMap.current.set(
|
routeMap.current.set(
|
||||||
`/${route.key}`,
|
`/${route.key}`,
|
||||||
breadcrumb ? [...parentNode, route.name] : []
|
breadcrumb
|
||||||
|
? [
|
||||||
|
...parentNode,
|
||||||
|
{
|
||||||
|
key: route.key,
|
||||||
|
name: route.name,
|
||||||
|
icon: getIconFromKey(route.key),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []
|
||||||
);
|
);
|
||||||
|
|
||||||
const visibleChildren = (route.children || []).filter((child) => {
|
const visibleChildren = (route.children || []).filter((child) => {
|
||||||
@ -162,7 +278,21 @@ function PageLayout() {
|
|||||||
if (ignore || route.ignore) {
|
if (ignore || route.ignore) {
|
||||||
routeMap.current.set(
|
routeMap.current.set(
|
||||||
`/${child.key}`,
|
`/${child.key}`,
|
||||||
breadcrumb ? [...parentNode, route.name, child.name] : []
|
breadcrumb
|
||||||
|
? [
|
||||||
|
...parentNode,
|
||||||
|
{
|
||||||
|
key: route.key,
|
||||||
|
name: route.name,
|
||||||
|
icon: getIconFromKey(route.key),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: child.key,
|
||||||
|
name: child.name,
|
||||||
|
icon: getIconFromKey(child.key),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +306,10 @@ function PageLayout() {
|
|||||||
menuMap.current.set(route.key, { subMenu: true });
|
menuMap.current.set(route.key, { subMenu: true });
|
||||||
return (
|
return (
|
||||||
<SubMenu key={route.key} title={titleDom}>
|
<SubMenu key={route.key} title={titleDom}>
|
||||||
{travel(visibleChildren, level + 1, [...parentNode, route.name])}
|
{travel(visibleChildren, level + 1, [
|
||||||
|
...parentNode,
|
||||||
|
{ key: route.key, name: route.name },
|
||||||
|
])}
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -238,7 +371,11 @@ function PageLayout() {
|
|||||||
[styles['layout-navbar-hidden']]: !showNavbar,
|
[styles['layout-navbar-hidden']]: !showNavbar,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Navbar show={showNavbar} />
|
<Navbar
|
||||||
|
show={showNavbar}
|
||||||
|
breadcrumb={displayedBreadcrumb}
|
||||||
|
onBreadcrumbClick={handleBreadcrumbClick}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{userLoading ? (
|
{userLoading ? (
|
||||||
<Spin className={styles['spin']} />
|
<Spin className={styles['spin']} />
|
||||||
@ -275,17 +412,6 @@ function PageLayout() {
|
|||||||
)}
|
)}
|
||||||
<Layout className={styles['layout-content']} style={paddingStyle}>
|
<Layout className={styles['layout-content']} style={paddingStyle}>
|
||||||
<div className={styles['layout-content-wrapper']}>
|
<div className={styles['layout-content-wrapper']}>
|
||||||
{!!breadcrumb.length && (
|
|
||||||
<div className={styles['layout-breadcrumb']}>
|
|
||||||
<Breadcrumb>
|
|
||||||
{breadcrumb.map((node, index) => (
|
|
||||||
<Breadcrumb.Item key={index}>
|
|
||||||
{typeof node === 'string' ? locale[node] || node : node}
|
|
||||||
</Breadcrumb.Item>
|
|
||||||
))}
|
|
||||||
</Breadcrumb>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<TabBar />
|
<TabBar />
|
||||||
<Content>
|
<Content>
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ const i18n = {
|
|||||||
'en-US': {
|
'en-US': {
|
||||||
'menu.userManagement': 'User Management',
|
'menu.userManagement': 'User Management',
|
||||||
'menu.userInfo': 'User Info',
|
'menu.userInfo': 'User Info',
|
||||||
|
'breadcrumb.home': 'Home',
|
||||||
'navbar.userInfo': 'User Info',
|
'navbar.userInfo': 'User Info',
|
||||||
'navbar.logout': 'Logout',
|
'navbar.logout': 'Logout',
|
||||||
'settings.title': 'Settings',
|
'settings.title': 'Settings',
|
||||||
@ -36,6 +37,7 @@ const i18n = {
|
|||||||
'zh-CN': {
|
'zh-CN': {
|
||||||
'menu.userManagement': '用户管理',
|
'menu.userManagement': '用户管理',
|
||||||
'menu.userInfo': '个人信息',
|
'menu.userInfo': '个人信息',
|
||||||
|
'breadcrumb.home': '首页',
|
||||||
'navbar.userInfo': '个人信息',
|
'navbar.userInfo': '个人信息',
|
||||||
'navbar.logout': '退出登录',
|
'navbar.logout': '退出登录',
|
||||||
'settings.title': '页面配置',
|
'settings.title': '页面配置',
|
||||||
|
|||||||
16
src/main.tsx
16
src/main.tsx
@ -16,6 +16,7 @@ import checkLogin from './utils/checkLogin';
|
|||||||
import changeTheme from './utils/changeTheme';
|
import changeTheme from './utils/changeTheme';
|
||||||
import initThemeColor from './utils/initThemeColor';
|
import initThemeColor from './utils/initThemeColor';
|
||||||
import useStorage from './utils/useStorage';
|
import useStorage from './utils/useStorage';
|
||||||
|
import { setUserInfo as cacheUserInfo } from './utils/storage';
|
||||||
import './mock';
|
import './mock';
|
||||||
|
|
||||||
const store = createStore(rootReducer);
|
const store = createStore(rootReducer);
|
||||||
@ -41,9 +42,22 @@ function Index() {
|
|||||||
payload: { userLoading: true },
|
payload: { userLoading: true },
|
||||||
});
|
});
|
||||||
axios.get('/api/user/userInfo').then((res) => {
|
axios.get('/api/user/userInfo').then((res) => {
|
||||||
|
const serverUserInfo = res.data || {};
|
||||||
|
const mergedUserInfo = {
|
||||||
|
...serverUserInfo,
|
||||||
|
userId: serverUserInfo.userId || serverUserInfo.id,
|
||||||
|
name: serverUserInfo.nickname || serverUserInfo.name,
|
||||||
|
};
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'update-userInfo',
|
type: 'update-userInfo',
|
||||||
payload: { userInfo: res.data, userLoading: false },
|
payload: { userInfo: mergedUserInfo, userLoading: false },
|
||||||
|
});
|
||||||
|
cacheUserInfo({
|
||||||
|
userId: mergedUserInfo.userId,
|
||||||
|
username: mergedUserInfo.username,
|
||||||
|
nickname: mergedUserInfo.nickname || mergedUserInfo.name,
|
||||||
|
role: mergedUserInfo.role,
|
||||||
|
avatar: mergedUserInfo.avatar,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,19 @@ import { TabItem, TabsState } from '@/types/tabs';
|
|||||||
export interface GlobalState {
|
export interface GlobalState {
|
||||||
settings?: typeof defaultSettings;
|
settings?: typeof defaultSettings;
|
||||||
userInfo?: {
|
userInfo?: {
|
||||||
|
// 后端 API 返回的字段(新增)
|
||||||
|
userId?: number;
|
||||||
|
username?: string;
|
||||||
|
nickname?: string;
|
||||||
|
role?: string;
|
||||||
|
// 原有字段(保持兼容)
|
||||||
name?: string;
|
name?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
job?: string;
|
job?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
location?: string;
|
location?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
permissions: Record<string, string[]>;
|
permissions: Record<string, string[]>;
|
||||||
};
|
};
|
||||||
userLoading?: boolean;
|
userLoading?: boolean;
|
||||||
|
|||||||
91
src/store/userStore.ts
Normal file
91
src/store/userStore.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
import { generatePermission } from '@/routes';
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
userId?: number;
|
||||||
|
username?: string;
|
||||||
|
nickname?: string;
|
||||||
|
role?: string;
|
||||||
|
avatar?: string;
|
||||||
|
name?: string;
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
permissions: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserState {
|
||||||
|
userInfo: UserInfo;
|
||||||
|
userLoading: boolean;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setUserInfo: (info: Partial<UserInfo>) => void;
|
||||||
|
updateUserInfo: (partial: Partial<UserInfo>) => void;
|
||||||
|
setUserLoading: (loading: boolean) => void;
|
||||||
|
clearUserInfo: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialUserInfo: UserInfo = {
|
||||||
|
permissions: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserStore = create<UserState>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
userInfo: initialUserInfo,
|
||||||
|
userLoading: false,
|
||||||
|
|
||||||
|
// 完全替换用户信息
|
||||||
|
setUserInfo: (info) => {
|
||||||
|
const role = info.role || 'user';
|
||||||
|
set({
|
||||||
|
userInfo: {
|
||||||
|
...info,
|
||||||
|
permissions: info.permissions || generatePermission(role),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 部分更新用户信息(自动保留其他字段)
|
||||||
|
updateUserInfo: (partial) => {
|
||||||
|
const current = get().userInfo;
|
||||||
|
const newInfo = { ...current, ...partial };
|
||||||
|
|
||||||
|
// 如果更新了 role,重新生成权限
|
||||||
|
if (partial.role && partial.role !== current.role) {
|
||||||
|
newInfo.permissions = generatePermission(partial.role);
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ userInfo: newInfo });
|
||||||
|
},
|
||||||
|
|
||||||
|
setUserLoading: (loading) => set({ userLoading: loading }),
|
||||||
|
|
||||||
|
clearUserInfo: () => set({ userInfo: initialUserInfo }),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'user-storage', // localStorage key
|
||||||
|
partialize: (state) => ({
|
||||||
|
userInfo: {
|
||||||
|
userId: state.userInfo.userId,
|
||||||
|
username: state.userInfo.username,
|
||||||
|
nickname: state.userInfo.nickname,
|
||||||
|
role: state.userInfo.role,
|
||||||
|
avatar: state.userInfo.avatar,
|
||||||
|
name: state.userInfo.name,
|
||||||
|
email: state.userInfo.email,
|
||||||
|
phone: state.userInfo.phone,
|
||||||
|
// 不持久化 permissions,每次从 role 生成
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
onRehydrateStorage: () => (state) => {
|
||||||
|
// 重新加载时,根据 role 重新生成 permissions
|
||||||
|
if (state && state.userInfo && state.userInfo.role) {
|
||||||
|
state.userInfo.permissions = generatePermission(state.userInfo.role);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default useUserStore;
|
||||||
Loading…
Reference in New Issue
Block a user