362 lines
11 KiB
Markdown
362 lines
11 KiB
Markdown
# 前端引入 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` 下新增如下目录:
|
||
|
||
```text
|
||
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` 中增加:
|
||
|
||
- `authVersion`
|
||
- `tenantContextVersion`
|
||
|
||
通过版本号驱动需要重新加载的数据,而不是只依赖浏览器事件和页面手工处理。
|
||
|
||
### 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.ts`
|
||
- `frontend/src/utils/appearance.ts`
|
||
- `frontend/src/main.ts`
|
||
- `frontend/src/App.vue`
|
||
- `frontend/src/router/index.ts`
|
||
- `frontend/src/views/modules/ProfilePage.vue`
|
||
|
||
验收标准:
|
||
|
||
- 登录、刷新、退出登录功能不回归
|
||
- 主题和密度切换行为不回归
|
||
- 多 Tab 基本同步仍然可用
|
||
|
||
### 阶段 3:迁移菜单与租户切换链路(已完成)
|
||
|
||
目标:
|
||
|
||
- 将当前集中在 `AppLayout.vue` 的布局级共享状态抽出
|
||
|
||
实施内容:
|
||
|
||
- 将菜单加载迁移到 `menuStore`
|
||
- 将租户切换迁移到 `authStore` 或专门的 `tenant context` action
|
||
- 将租户 Logo、租户切换状态从布局组件迁移到 store
|
||
- 明确切租户后的 store 级缓存清理动作
|
||
|
||
涉及文件重点:
|
||
|
||
- `frontend/src/views/layout/AppLayout.vue`
|
||
- `frontend/src/api/modules.ts`
|
||
- `frontend/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. 预估改造顺序
|
||
|
||
推荐顺序:
|
||
|
||
1. `pinia` 基础接入
|
||
2. `appearanceStore`
|
||
3. `authStore`
|
||
4. `menuStore`
|
||
5. `notificationStore`
|
||
6. 页面侧逐步替换直接读取 `localStorage` 的逻辑
|
||
|
||
这样做的原因:
|
||
|
||
- `appearance` 和 `auth` 边界最清晰,迁移成本最低。
|
||
- `menu` 和 `notification` 与租户切换耦合更深,适合第二阶段处理。
|
||
- 页面层替换可以跟随 store 稳定后分批推进。
|
||
|
||
## 13. 验收结果(已完成)
|
||
|
||
完成首轮引入后,应满足以下标准:
|
||
|
||
- 项目已正式接入 `Pinia`
|
||
- 认证态、外观态不再以分散的 `localStorage` 读取为主入口
|
||
- 租户切换存在明确的 store 级清理机制
|
||
- 菜单和通知逻辑从 `AppLayout.vue` 中部分或全部抽离
|
||
- 现有登录、登出、刷新 token、切换租户、多 Tab 同步能力不回退
|
||
|
||
## 14. 最终状态(已完成)
|
||
|
||
建议采用“引入基础设施 + 迁移最核心全局状态 + 保留页面局部状态”的方式推进。
|
||
|
||
具体建议如下:
|
||
|
||
- 建议引入 `Pinia`
|
||
- 不建议一次性重构所有页面
|
||
- 第一批只迁移 `auth / appearance / menu / notification`
|
||
- 明确把“租户切换后的 store 清理机制”作为本次方案的硬性要求
|
||
|
||
该方案执行后,前端状态管理会从“工具函数 + 布局组件 + 浏览器事件”的分散模式,过渡到“store 统一收口、页面按需消费”的结构,更适合当前项目继续演进。
|