Vue Vben Admin 状态管理指南:@vben/stores 的 User Store 与 Timezone Store 实战解析
2026/9/10 13:22:00 网站建设 项目流程

Vue Vben Admin 状态管理指南:@vben/stores 的 User Store 与 Timezone Store 实战解析

【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin

导读

在 Vue Vben Admin(基于 Vue 3 + Pinia 的现代中后台管理模板)中,@vben/stores是统一封装的状态管理包,内置了用户信息、权限、页签、时区等核心 Store。本篇以官方文档 docs/src/guide/essentials/stores.md 为主线,深入讲解useUserStoreuseTimezoneStore的 API 用法、持久化策略与底层实现,并结合 packages/stores/src 源码与测试用例,帮助你在业务侧正确、规范地使用这些 Store,实现用户信息同步、角色管理与时区偏好持久化。


一、包结构速览:@vben/stores提供了什么

@vben/stores是独立发包的状态管理模块,其入口文件 packages/stores/src/index.ts 内容如下:

export * from './modules'; export * from './setup'; export { defineStore, storeToRefs } from 'pinia';

也就是说,该包除导出各业务 Store 与初始化函数外,同时重新导出了 pinia 的defineStorestoreToRefs,业务侧可以统一从@vben/stores引入,无需再单独安装或从pinia直接导入。所有 Store 定义集中在 packages/stores/src/modules 目录下:

文件Storestore id职责
user.tsuseUserStorecore-user用户信息与角色
access.tsuseAccessStorecore-access权限码、菜单、路由、Token、锁屏等
tabbar.tsuseTabbarStorecore-tabbar多页签状态
timezone.tsuseTimezoneStorecore-timezone时区状态(setup store)

各 Store 均通过acceptHMRUpdate处理开发期热更新(见 user.ts、timezone.ts),保证模块热替换时状态不丢失。

@vben/stores已在各个app(如apps/web-antdplayground等)下统一引入,业务代码无需单独安装,直接 import 即可。


二、全局初始化:initStores与持久化插件

在深入单个 Store 前,先了解它的运行底座。setup.ts 中的initStores(app, options)完成三件事:

  1. 创建createPinia()实例;
  2. 安装pinia-plugin-persistedstate持久化插件;
  3. 通过app.use(pinia)注册到应用。

其中值得注意的持久化细节:

  • 命名空间隔离initStores接收{ namespace }选项,持久化 key 格式为${namespace}-${store.id}。由于@vben/stores是共享包,后续可能存在多个 app 共用的情况,配置不同的命名空间可防止缓存冲突;
  • 开发/生产差异化存储:开发环境(import.meta.env.DEV)直接使用localStorage;生产环境则通过secure-lsAES 加密 + 压缩的方式写入(加密密钥来自import.meta.env.VITE_APP_STORE_SECURE_KEY,meta 前缀为${namespace}-secure-meta),见 setup.ts;
  • 统一重置resetAllStores()遍历 pinia 实例内部的所有 store 并调用其$reset(),用于退出登录等场景的全局状态清理。

注意:pinia-plugin-persistedstate运行时动态 import的,这意味着@vben/stores本身不把持久化插件作为强依赖打进包内。


三、用户信息 Store:useUserStore

3.1 状态定义

useUserStore的 store id 为core-user,采用options store风格定义。状态结构定义在 user.ts:

字段默认值说明
userInfonull用户信息(类型BasicUserInfo \| null,来自@vben-core/typings
userRoles[]用户角色列表(string[]

3.2 设置用户信息:setUserInfo

setUserInfo(userInfo)在写入用户信息的同时,自动从userInfo.roles同步角色到userRoles。源码见 user.ts:

setUserInfo(userInfo: BasicUserInfo | null) { this.userInfo = userInfo; const roles = userInfo?.roles ?? []; this.setUserRoles(roles); }

注意userInfo?.roles ?? []:当传入null或缺少roles字段时,角色会被重置为空数组。这一行为有对应的单元测试验证(user.test.ts):将setUserInfo(null)后,userInfonulluserRoles清空为[]

典型用法:

import { useUserStore } from '@vben/stores'; const userStore = useUserStore(); userStore.setUserInfo({ id: 1, name: 'vben', roles: ['admin'] }); userStore.userRoles; // ['admin']

持久化策略useUserStore未配置persist(源码中无 persist 配置项)。原因在于用户信息属于运行时态——通常在登录后由接口返回,退出登录后即失效,不适合写入 localStorage 造成信息残留或过期。

3.3 设置用户角色:setUserRoles

setUserRoles(roles)直接覆盖角色列表(user.ts):

setUserRoles(roles: string[]) { this.userRoles = roles; }
import { useUserStore } from '@vben/stores'; const userStore = useUserStore(); userStore.setUserRoles(['admin', 'editor']); userStore.userRoles; // ['admin', 'editor']

3.4 获取用户信息

useUserStore未提供专门的 getter,直接访问 state 即可读取;需要响应式解构时使用storeToRefs(推荐统一从@vben/stores引入):

import { storeToRefs, useUserStore } from '@vben/stores'; const userStore = useUserStore(); // 直接访问 userStore.userInfo; userStore.userRoles; // 保持响应式(模板/计算属性中推荐) const { userInfo, userRoles } = storeToRefs(userStore);

storeToRefs的解构结果会保持响应性,适合在computedwatch或模板中使用;而直接const { userInfo } = userStore会丢失响应性,需要避免。


四、时区 Store:useTimezoneStore

4.1 Store 设计

useTimezoneStore的 store id 为core-timezone,采用setup store风格封装,源码见 timezone.ts。暴露的成员如下:

名称说明
timezone当前时区,初始值取自getCurrentTimezone()(ref)
setTimezone(timezone)设置时区,并同步到 dayjs 默认时区
getTimezoneOptions()获取时区选项列表,默认来自DEFAULT_TIME_ZONE_OPTIONS
$reset()重置时区到getCurrentTimezone()

4.2 底层:getCurrentTimezone/setCurrentTimezone

时区的初始值与 dayjs 同步逻辑来自@vben-core/shared的 date.ts:

let currentTimezone = getSystemTimezone(); export const setCurrentTimezone = (timezone?: string) => { currentTimezone = timezone || getSystemTimezone(); dayjs.tz.setDefault(currentTimezone); }; export const getCurrentTimezone = () => currentTimezone;

要点:

  • 模块内维护了一个currentTimezone变量,未显式设置时默认取dayjs.tz.guess()推断的系统时区;
  • setCurrentTimezone(timezone)会同时调用dayjs.tz.setDefault(),因此只有经过setTimezone(或initTimezone)的时区才会真正影响 dayjs 的全局默认时区
  • 对应的单元测试见 date.test.ts:设置Asia/ShanghaigetCurrentTimezone()返回该值;无参调用则回退到系统推断时区。

4.3 设置时区:setTimezone

setTimezone(timezone)依次执行:调用当前时区处理器的setTimezone(如有)→ 更新内部timezoneRef→ 调用setCurrentTimezone同步 dayjs 默认时区。核心实现(timezone.ts):

async function setTimezone(timezone: string) { const timezoneHandler = getTimezoneHandler(); await timezoneHandler.setTimezone?.(timezone); timezoneRef.value = timezone; setCurrentTimezone(timezone); }

用法:

import { useTimezoneStore } from '@vben/stores'; const store = useTimezoneStore(); await store.setTimezone('America/New_York'); store.timezone; // 'America/New_York'

4.4 获取时区选项:getTimezoneOptions

getTimezoneOptions()返回{ label, value }[]形式的选项列表,默认由DEFAULT_TIME_ZONE_OPTIONS映射而来;若通过setTimezoneHandler注入了自定义getTimezoneOptions,则以自定义实现为准。

默认选项定义在 constants.ts,包含纽约(GMT-5)、伦敦(GMT0)、上海(GMT+8)、东京(GMT+9)、首尔(GMT+9)五个常见时区,映射时把timezone字段转为value

// 默认处理器(getDefaultTimezoneHandler) getTimezoneOptions: () => { return Promise.resolve( DEFAULT_TIME_ZONE_OPTIONS.map((item) => ({ label: item.label, value: item.timezone, })), ); },

用法:

import { useTimezoneStore } from '@vben/stores'; const store = useTimezoneStore(); const options = await store.getTimezoneOptions(); // [{ label: 'America/New_York(GMT-5)', value: 'America/New_York' }, ...]

4.5 重置时区:$reset()

$reset()仅将内部timezoneRef重置为getCurrentTimezone()的返回值,不会调用setCurrentTimezone,因此不会同步 dayjs 默认时区——只有setTimezone才会同步(timezone.ts):

function $reset() { timezoneRef.value = getCurrentTimezone(); }
import { useTimezoneStore } from '@vben/stores'; const store = useTimezoneStore(); store.$reset(); store.timezone; // 回到 getCurrentTimezone() 的值

4.6 注入自定义时区处理器:setTimezoneHandler

setTimezoneHandler(handler)用于注入自定义时区处理模块,可覆盖getTimezone/getTimezoneOptions/setTimezone三个方法,典型场景是对接后端接口存储用户时区偏好。其实现采用"默认处理器 + 自定义处理器浅合并"的策略(timezone.ts):

let customTimezoneHandler: null | Partial<TimezoneHandler> = null; const setTimezoneHandler = (handler: Partial<TimezoneHandler>) => { customTimezoneHandler = handler; }; const getTimezoneHandler = () => ({ ...getDefaultTimezoneHandler(), ...customTimezoneHandler, });

TimezoneHandler接口定义(timezone.ts):

interface TimezoneHandler { getTimezone?: () => Promise<null | string | undefined>; getTimezoneOptions?: () => Promise<{ label: string; value: string }[]>; setTimezone?: (timezone: string) => Promise<void>; }

示例:将时区读写接入后端接口:

import { setTimezoneHandler, useTimezoneStore } from '@vben/stores'; setTimezoneHandler({ async getTimezone() { return (await fetchUserSettings()).timezone; }, async setTimezone(timezone) { await saveUserSettings({ timezone }); }, async getTimezoneOptions() { return [{ label: '东八区', value: 'Asia/Shanghai' }]; }, }); const store = useTimezoneStore(); await store.setTimezone('Asia/Shanghai');

注意setTimezoneHandler注入的是运行时配置,不会被持久化;且它需在useTimezoneStore使用前调用才生效。仓库 playground 中给出了完整示例 playground/src/timezone-init.ts:在应用启动时用getTimezoneApi/getTimezoneOptionsApi/setTimezoneApi三个接口封装注入。

此外,setup store 内部在初始化时(创建 store 实例时)会立即调用一次initTimezone():若自定义处理器提供了getTimezone,则用其返回值覆盖初始时区,并同步 dayjs 默认时区;异常会被捕获并打印Failed to initialize timezone during store setup警告(timezone.ts)。

4.7 持久化策略

useTimezoneStore通过pinia-plugin-persistedstate持久化,配置如下(timezone.ts):

persist: { // 持久化 pick: ['timezone'], }

含义:

  • timezone字段会被持久化,刷新页面后时区偏好得以保留;
  • 持久化的存储介质由全局initStores决定(开发环境localStorage,生产环境 AES 加密存储),key 形如${namespace}-core-timezone
  • setTimezoneHandler注入的处理逻辑属于运行时配置,不持久化——因此刷新页面后,若后端有用户时区偏好,仍会优先由getTimezone(经initTimezone)拉取覆盖。

五、业务落地建议

结合源码与文档,在业务中使用@vben/stores时有几点实践建议:

  1. 统一引入路径defineStorestoreToRefs及各 Store 一律从@vben/stores导入,保持依赖收敛;
  2. 用户信息赋值用setUserInfo:它会自动同步rolesuserRoles,避免手动重复赋值;登出时调用setUserInfo(null)即可一次性清空用户信息与角色(有测试保障:见 user.test.ts);
  3. 响应式读取用storeToRefs:在组件/组合式函数中解构userInfouserRolestimezone时务必通过storeToRefs,保持响应性;
  4. 时区接入后端:在应用入口(如 playground 的 timezone-init.ts)调用setTimezoneHandler注入getTimezone/setTimezone/getTimezoneOptions,即可将用户时区偏好落到服务端,同时利用timezone字段的持久化保证刷新后不回退;
  5. 需要全局清理时:使用initStores返回的 pinia 实例配合resetAllStores()统一重置(setup.ts),典型场景是切换账号后的状态复位。

六、小结

@vben/stores通过 options store(useUserStore)与 setup store(useTimezoneStore)两种风格,分别承载用户信息/角色与全局时区两类跨页面状态:

  • useUserStore无持久化,用户信息随登录态生命周期存在,setUserInfo自动同步角色;
  • useTimezoneStore持久化timezone字段,通过setTimezoneHandler可无缝对接后端时区偏好接口,并始终与 dayjs 默认时区保持同步。

理解这两个 Store 的 API 与底层实现(packages/stores/src/modules/user.ts、packages/stores/src/modules/timezone.ts、packages/@core/base/shared/src/utils/date.ts),即可在业务代码中规范、安全地管理这两类全局状态。

【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询