# 前端引入 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 统一收口、页面按需消费”的结构，更适合当前项目继续演进。
