拓冰建站拓冰建站
首页 / 资讯中心 / 正文

vue-vben-admin Stores 状态管理实战:深入解析 @vben/stores 的 User Store 与 Timezone Store

vue-vben-admin Stores 状态管理实战深入解析 vben/stores 的 User Store 与 Timezone Store【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin本指南以 vue-vben-admin 的 stores 官方文档 为骨架系统讲解vben/stores包中useUserStore用户 Store与useTimezoneStore时区 Store的设计理念、API 用法与持久化机制。你将掌握如何在业务代码中读写用户信息与角色、如何将时区偏好同步到 dayjs 并持久化到本地以及如何通过setTimezoneHandler接入后端接口实现用户时区的服务端保存。vben/stores包定位统一的 Pinia 入口在 vue-vben-admin 的 Monorepo 结构pnpm-workspace.yaml中vben/stores是 packages/stores 对外暴露的状态管理包。它的两个核心设计原则是免安装即用该包已经在每个app如apps/web-antd、playground等的启动流程中被统一初始化业务代码无需再单独安装 Pinia直接导入即可。统一出口包内重新导出了pinia的defineStore与storeToRefs业务代码可以统一从vben/stores导入避免混用多个来源。这一点可以在 packages/stores/src/index.ts 中看到export * from ./modules; export * from ./setup; export { defineStore, storeToRefs } from pinia;同时modules目录packages/stores/src/modules/index.ts统一导出了access、tabbar、timezone、user四个 Store。本文聚焦文档重点讲解的User Store与Timezone Store而useAccessStore权限令牌、菜单、路由与useTabbarStore标签栏同样通过该入口导出可在 access.ts 与 tabbar.ts 中查阅。User Store用户信息与角色的运行时状态useUserStore的 store id 为core-user用于封装用户信息userInfo与用户角色userRoles。状态定义文档给出的UserState定义如下字段默认值描述userInfonull用户信息userRoles[]用户角色对应源码实现位于 packages/stores/src/modules/user.ts使用 Pinia 的 options 风格定义interface AccessState { userInfo: BasicUserInfo | null; userRoles: string[]; } export const useUserStore defineStore(core-user, { actions: { setUserInfo(userInfo: BasicUserInfo | null) { this.userInfo userInfo; const roles userInfo?.roles ?? []; this.setUserRoles(roles); }, setUserRoles(roles: string[]) { this.userRoles roles; }, }, state: (): AccessState ({ userInfo: null, userRoles: [], }), });userInfo的类型来自vben-core/typings中的BasicUserInfo是用户的基础信息模型。设置用户信息setUserInfosetUserInfo(userInfo)会同时完成两件事写入userInfo并从userInfo.roles中同步角色列表到userRolesimport { useUserStore } from vben/stores; const userStore useUserStore(); userStore.setUserInfo({ id: 1, name: vben, roles: [admin] }); userStore.userRoles; // [admin]注意当传入null时角色列表会被同步清空为[]这一行为有单元测试覆盖packages/stores/src/modules/user.test.tsit(clears userInfo and userRoles when setting null userInfo, () { const store useUserStore(); store.setUserInfo(null as any); expect(store.userInfo).toBeNull(); expect(store.userRoles).toEqual([]); });持久化策略不持久化。useUserStore没有配置persist用户信息属于运行时状态通常在登录成功后由接口返回并写入退出登录时清空。这意味着刷新页面后userInfo会回到null需要重新拉取。设置用户角色setUserRoles当只需要更新角色而不改动其他用户信息时可直接调用import { useUserStore } from vben/stores; const userStore useUserStore(); userStore.setUserRoles([admin, editor]); userStore.userRoles; // [admin, editor]setUserInfo内部正是复用setUserRoles完成角色同步的二者职责单一、便于组合。获取用户信息useUserStore没有专门的 getter直接读取 state 即可。为了保证响应式推荐用storeToRefs解构import { storeToRefs, useUserStore } from vben/stores; const userStore useUserStore(); // 直接访问 userStore.userInfo; userStore.userRoles; // 解构并保持响应式 const { userInfo, userRoles } storeToRefs(userStore);登录流程中的典型用法在 playground 的登录状态模块 playground/src/store/auth.ts 中fetchUserInfo会调用用户信息接口并将结果写入 User Store正是setUserInfo的典型实战场景async function fetchUserInfo() { const userInfo await getUserInfoApi(); userStore.setUserInfo(userInfo); return userInfo; }从源码结构可以推断登录成功后由守卫playground/src/router/guard.ts触发fetchUserInfo将接口返回的用户信息灌入 Store供全局读取。Timezone Store时区状态与 dayjs 同步useTimezoneStore的 store id 为core-timezone是一个 setup 风格的 Store用于管理全局时区状态。它解决的问题是用户选择的时区不仅要保存在状态里还要同步生效到 dayjs 的默认时区从而保证所有日期时间格式化都按用户偏好展示。状态与方法一览文档列出的对外暴露项名称描述timezone当前时区初始值来自getCurrentTimezone()setTimezone(timezone)设置时区并同步到 dayjs 默认时区getTimezoneOptions()获取时区选项列表默认为DEFAULT_TIME_ZONE_OPTIONS$reset()将时区重置为getCurrentTimezone()对应源码 packages/stores/src/modules/timezone.ts 的核心实现const useTimezoneStore defineStore( core-timezone, () { const timezoneRef ref(getCurrentTimezone()); async function initTimezone() { const timezoneHandler getTimezoneHandler(); const timezone await timezoneHandler.getTimezone?.(); if (timezone) { timezoneRef.value timezone; } setCurrentTimezone(unref(timezoneRef)); } async function setTimezone(timezone: string) { const timezoneHandler getTimezoneHandler(); await timezoneHandler.setTimezone?.(timezone); timezoneRef.value timezone; setCurrentTimezone(timezone); } async function getTimezoneOptions() { const timezoneHandler getTimezoneHandler(); return (await timezoneHandler.getTimezoneOptions?.()) || []; } initTimezone().catch((error) { console.error(Failed to initialize timezone during store setup:, error); }); function $reset() { timezoneRef.value getCurrentTimezone(); } return { timezone: timezoneRef, setTimezone, getTimezoneOptions, $reset, }; }, { persist: { pick: [timezone], }, }, );关键机制Store 实例化时setup 阶段会立即调用initTimezone()若存在自定义 handler 的getTimezone会先尝试从外部如后端拉取用户时区随后调用setCurrentTimezone同步 dayjs 默认时区。dayjs 时区同步的底层实现getCurrentTimezone与setCurrentTimezone定义于 packages/core/base/shared/src/utils/date.tslet currentTimezone getSystemTimezone(); export const setCurrentTimezone (timezone?: string) { currentTimezone timezone || getSystemTimezone(); dayjs.tz.setDefault(currentTimezone); }; export const getCurrentTimezone () { return currentTimezone; };其中getSystemTimezone()使用dayjs.tz.guess()猜测浏览器/系统的本地时区作为初始兜底值setCurrentTimezone除了更新模块内缓存还会调用dayjs.tz.setDefault全局设置 dayjs 的默认时区——这就是为什么setTimezone之后所有 dayjs 格式化都按新时区输出的原因。该模块在 packages/core/base/shared/src/utils/tests/date.test.ts 中有配套测试。设置时区setTimezoneimport { useTimezoneStore } from vben/stores; const store useTimezoneStore(); await store.setTimezone(America/New_York); store.timezone; // America/New_YorksetTimezone是异步的它会先调用 handler 中的setTimezone若配置了再更新内部 ref最后同步 dayjs 默认时区。获取时区选项getTimezoneOptions默认情况下返回DEFAULT_TIME_ZONE_OPTIONS该常量定义于 packages/core/preferences/src/constants.ts包含 5 个常见时区偏移量timezonelabel-5America/New_YorkAmerica/New_York(GMT-5)0Europe/LondonEurope/London(GMT0)8Asia/ShanghaiAsia/Shanghai(GMT8)9Asia/TokyoAsia/Tokyo(GMT9)9Asia/SeoulAsia/Seoul(GMT9)使用时默认 handler 会把这些选项映射为{ label, value }结构const getDefaultTimezoneHandler (): TimezoneHandler { return { 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: Asia/Shanghai(GMT8), value: Asia/Shanghai }, ...]重置时区$reset$reset()将timezone重置回getCurrentTimezone()的当前值。注意它与setTimezone的关键区别它只重置 Store 内部的 ref不会同步 dayjs 的默认时区——只有setTimezone才会触发 dayjs 同步import { useTimezoneStore } from vben/stores; const store useTimezoneStore(); store.$reset(); store.timezone; // 回到 getCurrentTimezone() 的值这在退出登录、恢复默认设置等场景中非常有用避免误改 dayjs 全局状态。注入自定义时区 handlersetTimezoneHandlersetTimezoneHandler是useTimezoneStore的可扩展机制允许注入一个自定义 handler 模块覆盖getTimezone/getTimezoneOptions/setTimezone三个可选方法典型用途是通过后端 API 持久化用户时区偏好import { setTimezoneHandler, useTimezoneStore } from vben/stores; setTimezoneHandler({ async getTimezone() { return (await fetchUserSettings()).timezone; }, async setTimezone(timezone) { await saveUserSettings({ timezone }); }, async getTimezoneOptions() { return [{ label: UTC8, value: Asia/Shanghai }]; }, }); const store useTimezoneStore(); await store.setTimezone(Asia/Shanghai);从 timezone.ts 源码可以看清其实现逻辑模块内维护一个customTimezoneHandler变量getTimezoneHandler()将默认 handler 与自定义 handler 浅合并自定义优先级更高所有方法都走这一合并后的 handlerlet customTimezoneHandler: null | PartialTimezoneHandler null; const setTimezoneHandler (handler: PartialTimezoneHandler) { customTimezoneHandler handler; }; const getTimezoneHandler () { return { ...getDefaultTimezoneHandler(), ...customTimezoneHandler, }; };运行时配置不参与持久化handler 注入的逻辑是运行时配置刷新页面后会丢失因此需要在应用启动阶段如bootstrap尽早调用setTimezoneHandler完成注册。实战示例playground 中的时区初始化playground 应用提供了完整示例 playground/src/timezone-init.ts将时区读写全部接入 APIimport { setTimezoneHandler } from vben/stores; import { getTimezoneApi, getTimezoneOptionsApi, setTimezoneApi } from #/api; export function initTimezone() { setTimezoneHandler({ getTimezone() { return getTimezoneApi(); }, setTimezone(timezone: string) { return setTimezoneApi(timezone); }, getTimezoneOptions() { return getTimezoneOptionsApi(); }, }); }在 playground/src/bootstrap.ts 的启动流程中initStores之后紧接着调用initTimezone()完成 handler 注册随后 Store 实例化时即可从后端拉取用户时区// 配置 pinia store await initStores(app, { namespace }); // 初始化时区 handler initTimezone();后端 mock 接口可在 apps/backend-mock/api/timezone 中查看getTimezone.ts/setTimezone.ts/getTimezoneOptions.ts形成前端 Store 后端持久化的完整闭环。持久化机制initStores 与 SecureLS 加密存储vben/stores的持久化由 packages/stores/src/setup.ts 中的initStores统一配置它负责创建 Pinia 实例并挂载pinia-plugin-persistedstate插件export async function initStores(app: App, options: InitStoreOptions) { const { createPersistedState } await import(pinia-plugin-persistedstate); pinia createPinia(); const { namespace } options; const ls new SecureLSConstructor({ encodingType: aes, encryptionSecret: import.meta.env.VITE_APP_STORE_SECURE_KEY, isCompression: true, metaKey: ${namespace}-secure-meta, }); pinia.use( createPersistedState({ key: (storeKey) ${namespace}-${storeKey}, storage: import.meta.env.DEV ? localStorage : { getItem(key) { return ls.get(key); }, setItem(key, value) { ls.set(key, value); }, }, }), ); app.use(pinia); return pinia; }几个值得注意的点namespace参数由于vben/stores是公共包多个 app 可能共存namespace用于生成持久化 key 前缀${namespace}-${store.id}防止多应用缓存冲突。各 app 在bootstrap中传入自己的命名空间。开发/生产差异存储开发环境直接使用localStorage生产环境则改用secure-lsSecureLS进行 AES 加密、压缩存储避免明文敏感数据加密密钥来自VITE_APP_STORE_SECURE_KEY。按字段持久化每个 Store 通过persist.pick声明需要持久化的字段而不是整个状态全量落盘。Timezone Store 的持久化配置persist: { pick: [timezone]; }timezone字段会被持久化并在刷新页面后保留而setTimezoneHandler注入的 handler 逻辑是运行时配置不会被持久化因此每次刷新后需要重新注册 handler如 playground 的initTimezone()。此外setup.ts还提供了resetAllStores()会遍历所有已注册的 Store 并逐个调用$reset()常用于退出登录时清空全部状态可参见 playground/src/store/auth.ts 的 logout 流程。总结选择合适的状态管理方式回到文档的核心结论可以归纳出三条实践准则用户信息用 User Store登录后通过setUserInfo一次性写入用户信息与角色读取时用storeToRefs保持响应式它不持久化刷新后由路由守卫重新拉取。时区偏好用 Timezone StoresetTimezone会同步 dayjs 默认时区timezone字段自动持久化如需服务端保存用户时区用setTimezoneHandler注入后端 API并在应用启动时完成注册。统一从vben/stores导入defineStore、storeToRefs均已重新导出业务代码无需感知底层 Pinia 细节。vben/stores通过统一初始化 统一导出 可选加密持久化 handler 扩展的组合把状态管理的基础设施收敛到单一包内让业务侧聚焦于 Store 的读写本身。更多 Store如useAccessStore、useTabbarStore可在 packages/stores/src/modules 中继续查阅。【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门