状态管理
CondorAdmin 使用 Pinia 作为状态管理库,采用模块化设计,将不同业务的状态拆分为独立的 Store。
Pinia 架构
Store 模块
src/store/
├── index.ts # Store 入口
├── plugins/ # Store 插件
│ └── index.ts
└── modules/
├── auth/ # 认证模块
│ ├── index.ts
│ └── shared.ts
├── route/ # 路由模块
│ ├── index.ts
│ └── shared.ts
├── tab/ # 标签页模块
│ ├── index.ts
│ └── shared.ts
├── theme/ # 主题模块
│ ├── index.ts
│ └── shared.ts
├── app/ # 应用模块
│ └── index.ts
├── captcha/ # 验证码模块
│ └── index.ts
└── condor/ # Condor 业务模块
└── index.tsStore ID 枚举
typescript
// src/enum/index.ts
export enum SetupStoreId {
Auth = 'auth-store',
Route = 'route-store',
Tab = 'tab-store',
Theme = 'theme-store',
App = 'app-store',
Captcha = 'captcha-store',
Condor = 'condor-store'
}核心 Store
1. useAuthStore(认证模块)
职责:管理用户登录状态、用户信息、Token、权限列表。
位置:src/store/modules/auth/index.ts
状态:
typescript
const token = ref<string>(''); // 访问 Token
const userInfo = reactive({ // 用户信息
userId: '',
username: '',
email: '',
avatar: '',
roles: [], // 角色列表
buttons: [] // 按钮权限列表
});
const isLogin = computed(() => Boolean(token.value));核心方法:
login() - 登录
typescript
async function login(params: Api.Auth.LoginParams, redirect = true) {
startLoading();
// 1. 获取公钥
const { data } = await fetchGetPublicKey();
// 2. RSA 加密密码
const jse = new JSEncrypt();
jse.setPublicKey(data.publicKey);
const encryptedPassword = jse.encrypt(params.password);
// 3. 登录请求
const { data: loginToken, error } = await fetchLogin({
...params,
password: encryptedPassword
});
if (!error) {
// 4. 存储 Token
localStg.set('token', loginToken.access_token);
// 5. 获取用户信息
await getUserInfo();
// 6. 重定向
if (redirect) {
await redirectFromLogin();
}
}
endLoading();
}getUserInfo() - 获取用户信息
typescript
async function getUserInfo() {
const { data: info, error } = await fetchGetUserInfo();
if (!error) {
// 更新用户信息
Object.assign(userInfo, info);
// 启动 SSE
if (status.value !== 'open') {
start();
}
// 初始化字典
if (!condorStore.status) {
condorStore.initDict();
}
return true;
}
return false;
}hasPermission() - 权限判断
typescript
function hasPermission(permission: string | string[]) {
if (!permission) return true;
const roles = [...userInfo.buttons, ...userInfo.roles];
// 超级管理员
if (roles.includes('*')) return true;
// 数组权限(与关系)
if (Array.isArray(permission)) {
return permission.every(item => roles.includes(item));
}
// 单个权限
return roles.includes(permission);
}resetStore() - 重置状态
typescript
async function resetStore() {
clearAuthStorage();
authStore.$reset();
if (!route.meta.constant) {
await toLogin();
}
tabStore.cacheTabs();
routeStore.resetStore();
}使用示例:
vue
<script setup lang="ts">
import { useAuthStore } from '@/store';
const authStore = useAuthStore();
// 登录
const handleLogin = async () => {
await authStore.login({
username: 'admin',
password: '123456',
captcha: '1234'
});
};
// 权限判断
const canAdd = authStore.hasPermission('admin:add');
const canEdit = authStore.hasPermission(['admin:edit', 'admin:view']);
</script>2. useRouteStore(路由模块)
职责:管理动态路由、菜单树、面包屑。
位置:src/store/modules/route/index.ts
状态:
typescript
const authRouteMode = ref<ImportMetaEnv['VITE_AUTH_ROUTE_MODE']>('static');
const isInitAuthRoute = ref(false); // 是否已初始化路由
const menus = ref<App.Global.Menu[]>([]); // 菜单树
const searchMenus = computed(() => { // 扁平化菜单(用于搜索)
return flattenMenus(menus.value);
});核心方法:
initAuthRoute() - 初始化权限路由
typescript
async function initAuthRoute() {
if (isInitAuthRoute.value) return;
// 1. 获取用户路由
const routes = await getUserRoutes();
// 2. 添加到 Router
routes.forEach(route => {
router.addRoute(route);
});
// 3. 生成菜单树
menus.value = transformMenus(routes);
isInitAuthRoute.value = true;
}getSelectedMenuKeyPath() - 获取当前菜单路径
typescript
function getSelectedMenuKeyPath(selectedKey: string) {
const menuKey = selectedKey || activeMenu.value;
const path: string[] = [];
function findPath(menus: App.Global.Menu[], key: string) {
for (const menu of menus) {
if (menu.key === key) {
path.unshift(menu.key);
return true;
}
if (menu.children?.length) {
if (findPath(menu.children, key)) {
path.unshift(menu.key);
return true;
}
}
}
return false;
}
findPath(menus.value, menuKey);
return path;
}3. useTabStore(标签页模块)
职责:管理多标签页状态、缓存页面。
位置:src/store/modules/tab/index.ts
状态:
typescript
const tabs = ref<App.Global.Tab[]>([]); // 标签页列表
const activeTab = ref<string>(''); // 当前激活标签
const cacheRoutes = ref<string[]>([]); // 缓存的路由名称核心方法:
addTab() - 添加标签页
typescript
function addTab(route: RouteLocationNormalizedLoaded) {
const tab: App.Global.Tab = {
id: route.name as string,
label: route.meta.title || '',
routeName: route.name as string,
routePath: route.path,
fullPath: route.fullPath,
fixedIndex: route.meta.fixedIndexInTab,
icon: route.meta.icon
};
const index = tabs.value.findIndex(item => item.id === tab.id);
if (index === -1) {
tabs.value.push(tab);
} else {
tabs.value[index] = tab;
}
activeTab.value = tab.id;
// 添加到缓存
if (route.meta.keepAlive) {
addCacheRoute(route.name as string);
}
}removeTab() - 移除标签页
typescript
async function removeTab(tabId: string) {
const index = tabs.value.findIndex(tab => tab.id === tabId);
if (index === -1) return;
const isActiveTab = activeTab.value === tabId;
const tab = tabs.value[index];
tabs.value.splice(index, 1);
removeCacheRoute(tab.routeName);
// 如果关闭的是当前标签,跳转到相邻标签
if (isActiveTab && tabs.value.length > 0) {
const nextTab = tabs.value[index] || tabs.value[index - 1];
await router.push(nextTab.fullPath);
}
}clearTabs() - 清空标签页
typescript
function clearTabs() {
tabs.value = [];
cacheRoutes.value = [];
}cacheTabs() - 持久化标签页
typescript
function cacheTabs() {
localStg.set('globalTabs', tabs.value);
}4. useThemeStore(主题模块)
职责:管理主题配置、暗黑模式、布局设置。
位置:src/store/modules/theme/index.ts
状态:
typescript
const settings = ref<App.Theme.ThemeSetting>(initThemeSettings());
const darkMode = computed(() => {
if (settings.value.themeScheme === 'auto') {
return osTheme.value === 'dark';
}
return settings.value.themeScheme === 'dark';
});
const themeColors = computed(() => ({
primary: settings.value.themeColor,
...settings.value.otherColor
}));核心配置:
typescript
interface ThemeSetting {
themeScheme: 'light' | 'dark' | 'auto'; // 主题模式
themeColor: string; // 主题色
otherColor: {
info: string;
success: string;
warning: string;
error: string;
};
layout: {
mode: 'vertical' | 'horizontal'; // 布局模式
scrollMode: 'content' | 'wrapper'; // 滚动模式
};
page: {
animate: boolean; // 页面切换动画
animateMode: 'fade' | 'fade-slide'; // 动画模式
};
header: {
height: number; // 头部高度
breadcrumb: {
visible: boolean; // 显示面包屑
showIcon: boolean; // 显示图标
};
};
tab: {
visible: boolean; // 显示标签页
height: number; // 标签页高度
mode: 'chrome' | 'button'; // 标签页样式
};
sider: {
width: number; // 侧边栏宽度
collapsedWidth: number; // 折叠宽度
};
footer: {
visible: boolean; // 显示页脚
fixed: boolean; // 固定页脚
height: number; // 页脚高度
};
}核心方法:
setThemeScheme() - 设置主题模式
typescript
function setThemeScheme(scheme: 'light' | 'dark' | 'auto') {
settings.value.themeScheme = scheme;
toggleCssDarkMode(darkMode.value);
}setThemeColor() - 设置主题色
typescript
function setThemeColor(color: string) {
settings.value.themeColor = color;
addThemeVarsToGlobal(themeColors.value);
}5. useAppStore(应用模块)
职责:管理全局应用状态(侧边栏折叠、移动端等)。
位置:src/store/modules/app/index.ts
状态:
typescript
const siderCollapse = ref(false); // 侧边栏折叠
const isMobile = ref(false); // 是否移动端
const contentFullScreen = ref(false); // 内容全屏
const fullScreen = ref(false); // 全屏模式核心方法:
typescript
function toggleSiderCollapse() {
siderCollapse.value = !siderCollapse.value;
}
function toggleContentFullScreen() {
contentFullScreen.value = !contentFullScreen.value;
}6. useCondorStore(Condor 业务模块)
职责:管理字典数据、系统配置等业务状态。
位置:src/store/modules/condor/index.ts
状态:
typescript
const dictData = ref<Record<string, App.Dict[]>>({}); // 字典数据
const config = ref<Record<string, any>>({}); // 系统配置
const status = ref(false); // 是否已初始化核心方法:
initDict() - 初始化字典
typescript
async function initDict() {
const { data, error } = await fetchDictData();
if (!error && data) {
dictData.value = data;
status.value = true;
}
}getDict() - 获取字典
typescript
function getDict(code: string): App.Dict[] {
return dictData.value[code] || [];
}getDictLabel() - 获取字典标签
typescript
function getDictLabel(code: string, value: string | number): string {
const dict = getDict(code).find(item => item.value === value);
return dict?.label || '';
}使用示例:
vue
<script setup lang="ts">
import { useCondorStore } from '@/store';
const condorStore = useCondorStore();
// 获取字典
const statusDict = condorStore.getDict('status');
// [{ label: '启用', value: 1 }, { label: '禁用', value: 0 }]
// 获取字典标签
const statusLabel = condorStore.getDictLabel('status', 1);
// '启用'
</script>Store 组合
跨 Store 调用
typescript
import { useAuthStore } from './auth';
import { useRouteStore } from './route';
export const useAppStore = defineStore('app', () => {
const authStore = useAuthStore();
const routeStore = useRouteStore();
function init() {
authStore.initUserInfo();
routeStore.initAuthRoute();
}
return { init };
});响应式依赖
typescript
// 监听其他 Store 的状态变化
watch(
() => authStore.isLogin,
(isLogin) => {
if (isLogin) {
routeStore.initAuthRoute();
}
}
);持久化
LocalStorage 持久化
typescript
// shared.ts
import { localStg } from '@/utils/storage';
export function getToken() {
return localStg.get('token') || '';
}
export function clearAuthStorage() {
localStg.remove('token');
localStg.remove('refreshToken');
}自动持久化插件
typescript
// src/store/plugins/index.ts
import type { PiniaPluginContext } from 'pinia';
export function piniaPlugin(context: PiniaPluginContext) {
const { store } = context;
// 监听状态变化,自动持久化
store.$subscribe(() => {
if (store.$id === 'theme-store') {
localStg.set('theme-settings', store.settings);
}
});
}最佳实践
1. 使用 Setup Store
typescript
// ✅ 推荐:Setup Store(Composition API 风格)
export const useAuthStore = defineStore('auth', () => {
const token = ref('');
function login() { }
return { token, login };
});
// ❌ 不推荐:Options Store
export const useAuthStore = defineStore('auth', {
state: () => ({ token: '' }),
actions: { login() { } }
});2. 状态拆分
typescript
// ✅ 按职责拆分多个 Store
useAuthStore() // 认证
useRouteStore() // 路由
useTabStore() // 标签页
// ❌ 单个巨大的 Store
useGlobalStore() // 所有状态3. 避免直接修改状态
typescript
// ✅ 通过方法修改
authStore.login(params);
// ❌ 直接修改
authStore.token = 'abc';4. 类型定义
typescript
// 定义 Store 类型
export type AuthStore = ReturnType<typeof useAuthStore>;
// 使用类型
function useAuth(): AuthStore {
return useAuthStore();
}调试
Vue DevTools
安装 Vue DevTools 扩展,可查看所有 Store 的状态和历史记录。
日志
typescript
// 在 Store 中打印日志
console.log('[AuthStore] Login success', userInfo);相关文档
- 📖 Packages 详解
- 🎨 主题系统
- 🔧 路由系统
- 🔐 权限系统