Files
thebet365/docs/admin-page-switch-performance.md

248 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 管理端页面切换性能分析与优化任务
> 分析日期2026-06-18
> 范围:`apps/admin` 侧边栏切换路由时的响应速度(非玩家端)。
---
## 任务清单
- [ ] **measure-baseline**:用 DevTools Network/Performance 记录慢路径基线(`/users``/bets`、充值 tab 切换)
- [ ] **keepalive-layout**`ManageLayout``RouterView` 增加 `KeepAlive` + 列表页 `defineOptions({ name })`
- [ ] **list-stale-cache**:高频列表页改 `onActivated` + 模块级/短 TTL 缓存,避免 remount 全量 refetch
- [ ] **fix-deposit-tabs**`DepositManage``v-if``v-show` 或 keep-alive 子 tab
- [ ] **lighten-agent-manager**`AgentManager` 挂载 API 合并或按 tab 延迟加载
- [ ] **guard-session**`beforeEach` 去阻塞式 `ensureStaffSession``api` 拦截器减少 per-request reconcile
- [ ] **bundle-i18n-ep**Element Plus 按需引入 + 落地 `split-i18n` + `App.vue` CSS 瘦身
---
## 现象定义
用户感知的「切换慢」通常包含三段延迟叠加:
```mermaid
sequenceDiagram
participant User
participant RouterGuard
participant ChunkLoader
participant PageView
participant API
User->>RouterGuard: 点击侧边栏
RouterGuard->>RouterGuard: ensureStaffSession (可能 HTTP)
RouterGuard->>ChunkLoader: 动态 import 页面 chunk
ChunkLoader->>PageView: mount 组件
PageView->>API: onMounted 并发拉列表
API-->>PageView: 渲染表格
PageView-->>User: 页面可交互
```
---
## 一、架构结论
| 层级 | 现状 | 对切页的影响 |
|------|------|-------------|
| 路由 | 全部 lazy load`apps/admin/src/router/index.ts` | 首次进入某页需下载 chunk |
| 布局 | `ManageLayout.vue` 常驻 | 壳层不重载,合理 |
| **页面缓存** | **全项目无 `<KeepAlive>`** | **切走即销毁,回来必 remount + 重拉数据** |
| 数据层 | 无 Pinia仅少数 composable 有模块级缓存 | 绝大多数列表页无跨访问缓存 |
| 首屏 bundle | Element Plus 全量 + 三语 i18n 打进主包 | 影响首访/冷启动,对已登录切页影响次之 |
**最大根因:没有 KeepAlive + 页面 `onMounted` 全量 refetch。**
---
## 二、切页时实际发生什么
### 2.1 路由守卫(每条鉴权路由)
`apps/admin/src/router/index.ts``beforeEach`
- 有 token 时 **`await ensureStaffSession()`**
- `apps/admin/src/utils/session-hydrate.ts` 有 60s TTL过期后会 **`GET /manage/auth/me`****阻塞导航完成**
- 访问 smoke-tests 路由时额外 `await ensureLoaded()`
### 2.2 页面生命周期(无缓存)
`ManageLayout.vue` 内为裸 `<RouterView />`
- 旧页面 **unmount**
- 新页面 chunk **动态 import****mount**
- 典型模式:`onMounted(load)` / 顶层 `void load()`
**Dashboard 子页** 体验较好:`useAdminDashboard.ts` 模块级 `stats` 缓存,同 session 内切 `/``/dashboard/players` 可跳过 API`HomeEntry` 仍可能闪 boot 屏)。
### 2.3 布局层常驻副作用
`ManageLayout.vue` `onMounted`
- `useDepositPendingCount`:立即请求 + **每 30s 轮询** `pending-count`
- `useSmokeTestsAllowed`:一次性权限探测
不直接阻塞切页,但增加后台并发请求。
### 2.4 每个 API 请求的同步开销
`apps/admin/src/api.ts` 请求拦截器对每个请求调用 `reconcileStaffSessionFromToken()`JWT decode + localStorage。列表页 mount 时常 **并发 310 个请求**,同步开销被放大。
---
## 三、按影响排序的瓶颈清单
### P0 — 切换体验(每次切页都痛)
**1. 无 KeepAlive列表页反复 remount + refetch**
受影响页面(模式相同):
- `Bets.vue``Cashback.vue`
- `Matches.vue``MatchesOutrights.vue`
- `DepositOrders.vue``StaffManage.vue`
- `Contents.vue``FinanceLogs.vue`
**2. 重型页面挂载 API burst**
`AgentManager.vue``/users`)— 约 2900 行 SFC`onMounted` 并行:
- `GET /admin/users/page-init`
- `GET /admin/users`(全量玩家)
- `GET /admin/agents?level=1`
- page-init 后再按层级 **N+1** 拉子代理
每次从其他页回到 `/users` 都会重复上述 burst。
**3. Tab 用 `v-if` 导致子页销毁**
`DepositManage.vue`
```vue
<DepositOrders v-if="activeTab === 'orders'" />
<PaymentMethods v-if="activeTab === 'methods'" />
```
同页内切换 tab 也会 destroy + `onMounted(fetchList)`
**4. Matches 展开面板扇出请求**
`LeagueMatchesPanel.vue` `watch(..., { immediate: true })`:恢复 session 展开最多 3 个联赛时,**最多 3 路** `GET /admin/matches`;折叠再展开会 remount 重拉。
### P1 — 间歇性卡顿(特定路径 / 时间)
**5. Session hydrate 阻塞导航**
60s TTL 过期后,**每次切页**先等 `/manage/auth/me`。弱网或后端慢时,侧边栏点击后「卡住」数秒。
**6. HomeEntry 回 Dashboard 闪屏**
`HomeEntry.vue``onBeforeMount` 再次 `ensureStaffSession` + `booting` 全屏 loadingrouter 已做过 hydrate
**7. 首次进入大 chunk 的 JS 解析**
| 页面 | 风险 |
|------|------|
| `AgentManager.vue` | 巨型 SFC + 多子组件 |
| `Settlement.vue` | ~1500 行 + async echarts |
| `Contents.vue` | `ContentRichEditor.vue` |
### P2 — 首屏 / 冷启动
**8. Element Plus 全量注册**
`main.ts` 全量 `app.use(ElementPlus)` + `element-plus/dist/index.css`,无按需引入。
**9. i18n 三语未真正拆包**
`admin-messages.ts` 仍静态 import zh/en/ms 全文;`split-i18n.mjs` 未落地,首屏携带全部语言文案。
**10. App.vue 全局 CSS ~1900 行**
无 scoped 的暗色/浅色双套 Element 覆盖,与全量 EP CSS 叠加。
---
## 四、问题分层矩阵
| 症状 | 最可能原因 | 验证方式 |
|------|-----------|----------|
| 任意页切回上一页都慢 | 无 KeepAlive + onMounted refetch | Network切回同页重复相同 API |
| 仅 `/users` 特别慢 | AgentManager 多路并行 API + 大 chunk | mount 时 3+ 请求Performance 看 JS |
| 偶尔点菜单无反应数秒 | `ensureStaffSession` 阻塞 | 切页瞬间是否有 `/manage/auth/me` |
| 充值页 tab 切换慢 | DepositManage `v-if` | 切 tab 是否重复 `deposit-orders` 请求 |
| 首次进某页慢、之后再进仍慢 | 大 chunk + 仍无缓存 | 对比首次/二次 Network |
| 整体首次打开就慢 | EP 全量 + i18n 三语 + 全局 CSS | `pnpm --filter @thebet365/admin build:analyze` |
---
## 五、优化路径(分阶段)
### 阶段 A — 切页体验12 天,收益最大)
1. `ManageLayout.vue``<RouterView>` 外包 `<KeepAlive :max="8">`,列表页 `defineOptions({ name })`
2. 列表页改 `onActivated` + stale-while-revalidate有缓存先展示后台静默刷新
3. `DepositManage.vue``v-if``v-show` 或 keep-alive 两个 tab
4. `HomeEntry.vue`:去掉重复 hydrate / 仅首次 boot
### 阶段 B — 重型页与 API23 天)
1. 拆分 `AgentManager.vue`:按 tab lazy或合并 bootstrap 为单一 `page-init` 接口
2. 提取通用 `useListCache(key, fetcher, ttl)` 给 Bets/Users/Deposit 等
3. `LeagueMatchesPanel`:对已加载 `leagueId` 短 TTL 缓存
4. `api.ts``reconcileStaffSessionFromToken` 移到 token 变更时,而非每请求
### 阶段 C — 守卫与 bundle35 天)
1. `beforeEach` 改为同步 JWT/localStorage 校验;`/me` 仅登录/刷新时调用
2. Element Plus 改按需 + 去全量 CSS
3. 执行/合入 i18n `split-i18n.mjs`,首屏只加载当前语言
4. 瘦身 `App.vue` 全局样式
### 阶段 D — 度量与验收
```bash
pnpm --filter @thebet365/admin build:analyze
```
验收指标Chrome DevToolsFast 3G
- 切回已访问列表页:无重复全量列表 API或仅 background refresh
- `/users` 二次进入API 数从 3+ 降到 01
- 侧边栏切换guard 阶段无阻塞性 `/me`60s 内)
---
## 六、当前不必优先动的部分
- **ECharts**:已 async chunk仅 dashboard/settlement 加载
- **ContentRichEditor**:未全局引入,仅 contents 路由
- **deposit 30s 轮询**:后台流量,通常不是切页主因
- **ManageLayout computed 菜单**:开销相对小
---
## 七、推荐落地顺序
1. **只做一处**KeepAlive + 列表页 activated 缓存(阶段 A
2. **`/users` 最慢**:在 A 之后做 AgentManager API 合并/延迟加载(阶段 B
3. **首屏整体慢**:再动 Element Plus / i18n 拆分(阶段 C
---
## 八、关键文件索引
| 职责 | 路径 |
|------|------|
| 路由 + beforeEach | `apps/admin/src/router/index.ts` |
| 布局壳 | `apps/admin/src/layouts/ManageLayout.vue` |
| Session 水合 / TTL | `apps/admin/src/utils/session-hydrate.ts` |
| Auth store | `apps/admin/src/stores/auth.ts` |
| Axios 拦截器 | `apps/admin/src/api.ts` |
| Dashboard 入口 | `apps/admin/src/views/HomeEntry.vue` |
| Dashboard 数据缓存 | `apps/admin/src/composables/useAdminDashboard.ts` |
| Deposit 轮询 | `apps/admin/src/composables/useDepositPendingCount.ts` |
| Bootstrap | `apps/admin/src/main.ts` |
| Vite 分包 | `apps/admin/vite.config.ts` |
| 重型用户页 | `apps/admin/src/views/AgentManager.vue` |
| 充值 tab | `apps/admin/src/views/DepositManage.vue` |