11 KiB
11 KiB
前端引入 Pinia 方案(已完成)
当前状态
当前方案涉及的核心改造已经完成,现状如下:
Pinia已正式接入项目auth / appearance / menu / notification四个 store 已完成落地- “建议进入 Pinia”的状态范围已全部进入对应 store
- 认证、外观、菜单、通知、租户切换上下文均已由 store 统一收口
- 页面侧对认证与外观状态的消费已改为直接读取 store
- 原
utils/auth.ts与utils/appearance.ts兼容层已完成清理 - 前端构建验证已通过
1. 背景
当前前端技术栈为 Vue 3 + Vue Router + Element Plus + Axios,尚未引入 Pinia。从现有代码看,跨页面共享状态主要通过以下方式维护:
frontend/src/utils/auth.ts- 使用
localStorage持久化 token、用户信息、租户信息、权限、界面偏好。
- 使用
frontend/src/utils/appearance.ts- 使用
reactive单例维护主题和密度状态。
- 使用
frontend/src/views/layout/AppLayout.vue- 集中维护菜单、租户切换、未读站内信、WebSocket 连接、跨标签页同步等全局状态。
window.dispatchEvent/window.addEventListener- 通过
auth:token-updated、in-app-unread-changed、storage事件做组件间和多 Tab 同步。
- 通过
该方案在当前阶段可以工作,但随着前端功能继续增加,已经出现以下趋势:
- 全局状态来源分散,排查问题时需要同时阅读
utils、布局组件和页面组件。 - 业务状态与持久化逻辑、事件广播逻辑耦合较深。
- 租户切换、登录态变更、菜单刷新、通知未读数刷新等链路缺少统一的 store 边界。
- 后续若继续增加全局缓存,会放大“旧租户状态残留”和“页面使用过期缓存”的风险。
结论:建议引入 Pinia,但采用渐进式迁移,不进行一次性重构。
2. 目标
本方案目标如下:
- 建立统一的前端全局状态管理入口。
- 将认证、租户、菜单、外观、通知等跨页面共享状态从工具函数和布局组件中抽离。
- 降低租户切换、重新登录、刷新 token、多标签页同步等复杂链路的维护成本。
- 为后续增加全局缓存、数据预取、业务上下文复用提供稳定边界。
3. 非目标
本次方案不包含以下内容:
- 不将所有页面内
ref/reactive状态迁入 Pinia。 - 不将表单输入、弹窗开关、分页条件等纯页面局部状态全局化。
- 不在首轮引入额外状态管理插件或复杂中间件。
- 不在首轮重构全部页面的接口调用方式。
4. 是否建议引入
建议引入,原因如下:
- 项目已经存在明确的全局共享状态。
- 当前共享状态分散在
localStorage、响应式单例、布局组件和浏览器事件之间。 - 多租户切换已经是核心链路,继续扩展时需要稳定的 store 清理机制。
- 后续若继续做全局搜索、通知中心、更多个性化设置、页面缓存,Pinia 会显著降低复杂度。
不建议一次性全面迁移,原因如下:
- 大部分业务页面仍以单页局部状态为主,强行迁移收益不高。
- 一次性改动风险大,容易引入登录态、权限态和租户态回归问题。
5. 适合进入 Pinia 的状态范围(已完成)
已完成进入 Pinia:
- 认证态
- token
- 当前用户
- 当前租户
- scope
- roles
- permissions
- 外观态
- themeMode
- density
- 初始化状态
- 菜单与布局态
- 当前菜单列表
- 当前租户 logo
- 是否正在切换租户
- 通知态
- 未读数
- 通知列表
- 轮询和 WebSocket 生命周期状态
- 租户切换上下文
- 当前租户 ID
- 切换后是否需要清理的业务缓存版本号
不建议进入 Pinia:
- 单页面查询条件
- 表单内容
- 弹窗显隐
- 临时上传进度
- 仅在单个页面使用的表格数据
6. 推荐目录结构
建议在 frontend/src 下新增如下目录:
stores/
index.ts
auth.ts
appearance.ts
menu.ts
notification.ts
职责建议如下:
stores/index.ts- 统一创建
pinia实例。
- 统一创建
stores/auth.ts- 管理登录态、用户信息、权限、租户信息、登录态广播、登出清理。
stores/appearance.ts- 管理主题、密度、文档样式同步。
stores/menu.ts- 管理租户端和平台端菜单加载、菜单刷新。
stores/notification.ts- 管理未读数、通知列表、轮询、WebSocket 连接和重连。
7. 设计原则
7.1 store 负责状态和动作,工具函数只保留纯函数能力
例如:
utils/auth.ts- 逐步收缩为解析、序列化、兼容读取等纯工具。
- 登录保存、登出清理、权限变更广播
- 迁移到
authStore。
- 迁移到
7.2 持久化与内存态分层
Pinia中维护运行时状态。localStorage仅作为持久化载体,不再作为主要读取入口。- 页面和组件默认从 store 读取,不直接散落读取
localStorage。
7.3 租户切换必须具备显式清理机制
租户切换后,至少要做到:
- 刷新
authStore - 刷新
menuStore - 刷新
notificationStore - 清空或失效租户相关业务缓存
- 触发页面重新拉数或重建
建议在 authStore 中增加:
authVersiontenantContextVersion
通过版本号驱动需要重新加载的数据,而不是只依赖浏览器事件和页面手工处理。
7.4 不引入“超大 store”
避免把全部全局状态堆进一个 appStore。按领域拆分,降低耦合。
8. 分阶段实施方案
阶段 1:基础接入(已完成)
目标:
- 引入
pinia - 完成基础目录和初始化接入
- 不改变现有业务行为
实施内容:
- 安装
pinia - 新增
frontend/src/stores/index.ts - 在
frontend/src/main.ts中接入app.use(pinia) - 新增空的
authStore、appearanceStore、menuStore、notificationStore
产出:
- 项目具备 store 基础设施
- 不触发业务逻辑迁移
阶段 2:迁移认证与外观(已完成)
目标:
- 优先迁移最稳定、最全局的状态
实施内容:
- 将
utils/auth.ts中的状态读写入口迁移到authStore - 将
utils/appearance.ts中的响应式单例迁移到appearanceStore - 保留
localStorage持久化,但统一由 store 写入 - 页面不再直接读取
localStorage获取认证信息
涉及文件重点:
frontend/src/utils/auth.tsfrontend/src/utils/appearance.tsfrontend/src/main.tsfrontend/src/App.vuefrontend/src/router/index.tsfrontend/src/views/modules/ProfilePage.vue
验收标准:
- 登录、刷新、退出登录功能不回归
- 主题和密度切换行为不回归
- 多 Tab 基本同步仍然可用
阶段 3:迁移菜单与租户切换链路(已完成)
目标:
- 将当前集中在
AppLayout.vue的布局级共享状态抽出
实施内容:
- 将菜单加载迁移到
menuStore - 将租户切换迁移到
authStore或专门的tenant contextaction - 将租户 Logo、租户切换状态从布局组件迁移到 store
- 明确切租户后的 store 级缓存清理动作
涉及文件重点:
frontend/src/views/layout/AppLayout.vuefrontend/src/api/modules.tsfrontend/src/router/index.ts
验收标准:
- 租户切换后菜单正确刷新
- 原路由保留逻辑仍然成立
- 不出现旧租户菜单、旧租户 logo、旧租户权限残留
阶段 4:迁移通知中心与 WebSocket 生命周期(已完成)
目标:
- 将布局中的通知状态与连接管理从视图层抽离
实施内容:
- 将未读数、通知列表迁移到
notificationStore - 将轮询、WebSocket 建连、重连、关闭逻辑迁移到 store action
- 保留
AppLayout.vue作为纯展示和交互入口
验收标准:
- 未读数更新正常
- 标记已读/全部已读正常
- 页面切换不导致重复连接
- 登出或切租户后连接被正确关闭
9. 与现有代码的映射关系(已完成)
建议按如下方式收口:
- 当前
frontend/src/utils/auth.ts- 收口到
authStore
- 收口到
- 当前
frontend/src/utils/appearance.ts- 收口到
appearanceStore
- 收口到
- 当前
frontend/src/views/layout/AppLayout.vue- 收口到
menuStore + notificationStore + authStore
- 收口到
- 当前直接调用
hasPermission()的页面- 后续逐步改为从
authStore暴露的 getter 或 helper 读取
- 后续逐步改为从
10. 持久化策略建议
首轮建议不引入额外持久化插件,采用手工持久化,原因如下:
- 当前状态字段较少,手工维护可控。
- 可以保留现有
localStorage键,降低兼容成本。 - 可以在迁移期同时兼容旧逻辑,便于灰度替换。
后续如 store 数量和持久化字段继续增多,再评估是否引入持久化插件。
11. 风险与应对
风险 1:登录态回归
表现:
- 登录后页面未刷新权限
- token 刷新后页面仍使用旧状态
应对:
- 认证迁移阶段保留兼容读取逻辑
- 为登录、登出、refreshAuth、tenant switch 增加回归测试清单
风险 2:租户切换后残留旧缓存
表现:
- 菜单、通知、页面数据、权限按钮残留旧租户信息
应对:
- 引入
tenantContextVersion - 在切换租户 action 中显式调用各 store 的
reset或reload
风险 3:组件和 store 职责混乱
表现:
- 视图层继续直接读写
localStorage - store 同时处理过多页面局部逻辑
应对:
- 代码评审时明确约束
- 新增共享状态优先进入 store
- 页面局部状态继续留在页面组件
12. 预估改造顺序
推荐顺序:
pinia基础接入appearanceStoreauthStoremenuStorenotificationStore- 页面侧逐步替换直接读取
localStorage的逻辑
这样做的原因:
appearance和auth边界最清晰,迁移成本最低。menu和notification与租户切换耦合更深,适合第二阶段处理。- 页面层替换可以跟随 store 稳定后分批推进。
13. 验收结果(已完成)
完成首轮引入后,应满足以下标准:
- 项目已正式接入
Pinia - 认证态、外观态不再以分散的
localStorage读取为主入口 - 租户切换存在明确的 store 级清理机制
- 菜单和通知逻辑从
AppLayout.vue中部分或全部抽离 - 现有登录、登出、刷新 token、切换租户、多 Tab 同步能力不回退
14. 最终状态(已完成)
建议采用“引入基础设施 + 迁移最核心全局状态 + 保留页面局部状态”的方式推进。
具体建议如下:
- 建议引入
Pinia - 不建议一次性重构所有页面
- 第一批只迁移
auth / appearance / menu / notification - 明确把“租户切换后的 store 清理机制”作为本次方案的硬性要求
该方案执行后,前端状态管理会从“工具函数 + 布局组件 + 浏览器事件”的分散模式,过渡到“store 统一收口、页面按需消费”的结构,更适合当前项目继续演进。