Become a sponsor

说明
前端使用 Pinia 进行状态管理,核心模块包括 user(用户信息与权限)、asyncRoute(动态路由与菜单)、projectSetting(项目配置)、designSetting(主题设计)等。
// src/store/index.ts
import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia;Store 模块位于 src/store/modules/ 目录:
src/store/modules/
├── user.ts # 用户登录状态、Token、用户信息、权限列表
├── asyncRoute.ts # 动态路由生成、菜单管理、组件缓存
├── projectSetting.ts # 项目配置(权限模式、水印、动画等)
├── designSetting.ts # 主题色、深色模式
├── lockscreen.ts # 锁屏状态
├── tabsView.ts # 标签页管理
└── ossConfig.ts # OSS 配置管理用户登录状态、Token、用户信息和权限列表,是核心 Store:
// src/store/modules/user.ts
import { defineStore } from 'pinia';
import { createStorage } from '@/utils/Storage';
import { ACCESS_TOKEN, CURRENT_USER } from '@/store/mutation-types';
import { getUserInfo, login, logout } from '@/api/common/user';
const Storage = createStorage({ storage: localStorage });
export interface IUserState {
token: string;
username: string;
welcome: string;
avatar: string;
permissions: any[];
info: any;
}
export const useUserStore = defineStore({
id: 'app-user',
state: (): IUserState => ({
token: Storage.get(ACCESS_TOKEN, ''), // 从 localStorage 恢复 token
username: '',
welcome: '',
avatar: '',
permissions: [],
info: Storage.get(CURRENT_USER, {}), // 从 localStorage 恢复用户信息
}),
getters: {
getToken(): string { return this.token; },
getPermissions(): [any][] { return this.permissions; },
getUserInfo(): object { return this.info; },
},
actions: {
// 登录
async login(userInfo) {
const response = await login(userInfo);
const { code, data } = response;
if (code === ResultEnum.SUCCESS) {
const ex = 7 * 24 * 60 * 60 * 1000; // 7 天过期
storage.set(ACCESS_TOKEN, data.access_token, ex);
this.setToken(data.access_token);
}
return Promise.resolve(response);
},
// 获取用户信息
GetInfo() {
return new Promise((resolve, reject) => {
getUserInfo().then((res) => {
if (res.permissions && res.permissions.length) {
this.setPermissions(res.permissions);
}
this.setUserInfo(res);
this.setAvatar(res.avatar);
resolve(res);
}).catch(reject);
});
},
// 登出
async logout() {
return new Promise((resolve, reject) => {
logout().then(() => {
this.setPermissions([]);
this.setUserInfo('');
storage.remove(ACCESS_TOKEN);
storage.remove(CURRENT_USER);
storage.clear();
resolve('');
}).catch(reject);
});
},
},
});Token 通过 Storage 工具类存储在 localStorage 中,键名为 ACCESS-TOKEN,自动大写转换,支持 7 天过期时间:
// src/utils/Storage.ts
export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) => {
const Storage = class {
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
const stringData = JSON.stringify({
value,
expire: expire !== null ? Date.now() + expire : null,
});
this.storage.setItem(this.getKey(key), stringData);
}
get(key: string, def: any = null) {
const item = this.storage.getItem(this.getKey(key));
if (item) {
const data = JSON.parse(item);
const { value, expire } = data;
if (expire === null || expire >= Date.now()) return value;
this.remove(key); // 过期则删除
}
return def;
}
};
return new Storage();
};
export const storage = createStorage();管理动态路由和菜单,是路由系统的核心:
// src/store/modules/asyncRoute.ts
export const useAsyncRouteStore = defineStore({
id: 'app-async-route',
state: (): IAsyncRouteState => ({
menus: [], // 菜单列表
routers: constantRouter, // 静态路由
addRouters: [], // 动态路由
keepAliveComponents: [], // 需要缓存的组件名
isDynamicAddedRoute: false, // 路由是否已添加
}),
actions: {
async generateRoutes(data) {
const { getPermissionMode } = useProjectSetting();
const permissionMode = unref(getPermissionMode);
let accessedRouters;
if (permissionMode === 'BACK') {
// 后端控制模式:从后端获取菜单生成路由
accessedRouters = await generatorDynamicRouter();
} else {
// 前端控制模式:根据权限过滤静态路由表
accessedRouters = filter(asyncRoutes, routeFilter);
}
this.setRouters(accessedRouters);
this.setMenus(accessedRouters);
return toRaw(accessedRouters);
},
setRouters(routers) {
this.addRouters = routers;
this.routers = constantRouter.concat(routers);
},
setMenus(menus) { this.menus = menus; },
setDynamicAddedRoute(added: boolean) { this.isDynamicAddedRoute = added; },
},
});登录流程中 Store 协作:
// 路由守卫中的调用(router-guards.ts)
const userStore = useUserStoreWidthOut();
const asyncRouteStore = useAsyncRouteStoreWidthOut();
// 1. 获取用户信息(设置 permissions)
const userInfo = await userStore.GetInfo();
// 2. 根据用户信息生成动态路由
const routes = await asyncRouteStore.generateRoutes(userInfo);
// 3. 动态添加路由
routes.forEach((item) => router.addRoute(item));在组件中使用 store:
<script setup>
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
// 获取用户信息
const userInfo = computed(() => userStore.getUserInfo);
// 获取权限列表
const permissions = computed(() => userStore.getPermissions);
// 登出
const handleLogout = async () => {
await userStore.logout();
router.push('/login');
};
</script>在 setup 外使用 store(需要传入 pinia 实例):
import { store } from '@/store';
import { useUserStore } from '@/store/modules/user';
export function useUserStoreWidthOut() {
return useUserStore(store);
}温馨提示
Token 通过 Storage 工具类管理,支持自动过期(默认 7 天)。退出登录时 storage.clear() 会清除所有 localStorage 数据。权限列表 permissions 在 GetInfo 时从后端获取,存储在 user store 中供 v-perm 指令使用。
Pinia 状态管理包含 user(登录与权限)、asyncRoute(动态路由与菜单)、projectSetting(项目配置)等核心模块。user store 管理 Token 和权限列表,asyncRoute store 管理动态路由生成和组件缓存。Store 之间通过路由守卫协调工作,完成登录 → 获取信息 → 生成路由 → 挂载应用的完整流程。