docs: 扩充 AGENTS 中文速查与管理端切页性能分析文档
This commit is contained in:
236
AGENTS.md
236
AGENTS.md
@@ -1,46 +1,206 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
## Runtime And Workspace
|
> 给 AI 编程助手(Cursor 等)看的项目速查手册,浓缩开发约定与易错点。
|
||||||
- Use Node 22+ and pnpm 11; `packageManager` pins `pnpm@11.5.2`.
|
> 人类日常开发请优先看 `README.md` 与 `docs/`。
|
||||||
- This is a pnpm workspace: `apps/api` is NestJS/Prisma, `apps/player` is Vue H5 on `:5173`, `apps/admin` is the unified platform-admin + agent Vue app on `:5174`, and `packages/shared` holds shared types/constants plus `public` assets.
|
|
||||||
- The frontends alias `@thebet365/shared` to `packages/shared/src/index.ts` because the built shared package is CommonJS; update shared source exports, not only `dist`.
|
|
||||||
- `packages/shared` build runs `scripts/generate-phone-countries.mjs` before `tsc`.
|
|
||||||
|
|
||||||
## Local Setup
|
## 目录
|
||||||
- Start local infrastructure with `docker compose up -d`; this exposes PostgreSQL on `5432` and Redis on `6379`.
|
|
||||||
- Copy env for the API as `cp .env.example apps/api/.env`; Nest reads `.env` from the API working directory when run via the workspace filter.
|
|
||||||
- First database setup from repo root: `pnpm db:generate`, then `pnpm db:migrate`, then `pnpm db:seed`.
|
|
||||||
- `pnpm dev:api` runs `scripts/ensure-port-free.mjs 3000` and will kill any listener on port `3000` before starting Nest watch.
|
|
||||||
- If starting apps separately, start API before `pnpm dev:player` or `pnpm dev:admin`; both Vite servers proxy `/api` to `http://localhost:3000`.
|
|
||||||
|
|
||||||
## Commands
|
| 章节 | 何时查 |
|
||||||
- `pnpm dev` builds shared once, then runs all workspace `dev` scripts in parallel.
|
|------|--------|
|
||||||
- `pnpm dev:admin` and `pnpm dev:manage` are the same admin app.
|
| [运行环境与仓库结构](#运行环境与仓库结构) | 不确定 monorepo 各 app 分工 |
|
||||||
- Full verification is `pnpm build` and `pnpm test`; there is no root lint or formatter script in current manifests.
|
| [本地启动](#本地启动) | 起 dev、数据库、端口 |
|
||||||
- API tests: `pnpm --filter @thebet365/api test`.
|
| [常用命令](#常用命令) | build / test / 打镜像 |
|
||||||
- Focused API Jest test: `pnpm --filter @thebet365/api exec jest settlement-calculator.spec.ts --runInBand`; avoid `pnpm ... test -- ...` for focused args because pnpm passes the literal `--` through to Jest here.
|
| [API 领域结构](#api-领域结构) | 业务代码该放哪个 domain |
|
||||||
- Frontend typecheck/build: `pnpm --filter @thebet365/player build` and `pnpm --filter @thebet365/admin build`.
|
| [API 约定](#api-约定) | 鉴权、钱包、迁移、赔率快照 |
|
||||||
- Admin bundle report: `pnpm --filter @thebet365/admin build:analyze`.
|
| [前端约定](#前端约定) | 改 .vue/.ts、权限、dist |
|
||||||
|
| [i18n 改文案流程](#i18n-改文案流程) | 三语文案改哪几个文件 |
|
||||||
|
| [主题分支](#主题分支player-多皮肤) | theme-2/3/4 与 main 同步禁忌 |
|
||||||
|
| [演示账号](#演示账号开发-seed) | 本地登录测试号 |
|
||||||
|
| [文档索引](#文档索引) | 详细说明在 `docs/` 哪篇 |
|
||||||
|
| [测试与冒烟](#测试与冒烟) | Jest、UAT、smoke-tests |
|
||||||
|
| [部署注意](#部署注意) | 生产 compose、seed、打包 |
|
||||||
|
| [AI 改代码时请避免](#ai-改代码时请避免) | 常见踩坑清单 |
|
||||||
|
|
||||||
## API Notes
|
> **AI 会读吗?** Cursor 等工具会在对话开始时把 `AGENTS.md` 注入上下文;当前约 200 行,一般能整篇读入。文件继续变长时,索引有助于人和 AI 快速定位章节;细节仍以 `docs/` 为准,此处只写约定与指针。
|
||||||
- API URLs use the global `/api` prefix; Swagger is at `/api/docs` in non-production or when `ENABLE_SWAGGER` is truthy.
|
|
||||||
- `AppModule` installs a global `JwtAuthGuard`; public endpoints must use `@Public()` from `apps/api/src/shared/common/decorators.ts`.
|
|
||||||
- Keep business rules in `apps/api/src/domains/*`; `apps/api/src/applications/{player,admin,agent}` should stay as portal/controller orchestration.
|
|
||||||
- Wallet changes should go through ledger/wallet services; UAT docs explicitly forbid direct balance edits for settlement fixes.
|
|
||||||
- After editing `apps/api/prisma/schema.prisma`, run `pnpm db:generate`; use `pnpm db:migrate` for dev migrations and `pnpm db:migrate:deploy` for deployment.
|
|
||||||
|
|
||||||
## Frontend Notes
|
## 运行环境与仓库结构
|
||||||
- Edit `.ts` and `.vue` sources in `apps/player/src` and `apps/admin/src`; many `.js` siblings exist under `src`, but both `index.html` files load `/src/main.ts` and Vite resolution prefers TS before JS.
|
|
||||||
- Admin is one app for both `ADMIN` and `AGENT` accounts; route/menu gating is in `apps/admin/src/router/index.ts` and `apps/admin/src/stores/auth.ts`.
|
|
||||||
- Player is mobile-first; performance expectations and required mobile paths are in `docs/player-mobile-performance.md`.
|
|
||||||
|
|
||||||
## Tests And Smoke Checks
|
- 使用 Node 22+、pnpm 11;`packageManager` 固定为 `pnpm@11.5.2`。
|
||||||
- Jest specs live under `apps/api/src/**/*.spec.ts` with `rootDir: src`; the documented unit/regression suite is rule-oriented and does not require a live DB.
|
- pnpm workspace:`apps/api`(NestJS/Prisma)、`apps/player`(Vue H5,`:5173`)、`apps/admin`(平台管理 + 代理合一,`:5174`)、`packages/shared`(共享类型/常量与 `public` 静态资源)。
|
||||||
- DB-backed smoke tests are exposed in the admin UI under `smoke-tests`; `SmokeTestService` allows them outside production, or in production only with `ALLOW_SMOKE_TESTS=true`.
|
- 前端将 `@thebet365/shared` 别名到 `packages/shared/src/index.ts`(构建产物为 CommonJS);改 shared 时须更新源码导出,不要只改 `dist`。
|
||||||
- UAT regression flow is documented in `docs/UAT_CHECKLIST.md`; it includes admin UI smoke tests plus manual wallet/agent-credit checks.
|
- `packages/shared` 构建前会执行 `scripts/generate-phone-countries.mjs`,再跑 `tsc`。
|
||||||
|
- 本地开发 compose:`docker-compose.yml`(仅 Postgres + Redis);生产 compose:`docker-compose.prod.yml` + `.env.docker`。
|
||||||
|
|
||||||
## Deployment Gotchas
|
## 本地启动
|
||||||
- Production compose uses `.env.docker`: `docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build` or `pnpm docker:up` after creating `.env.docker`.
|
|
||||||
- In production, keep `SEED_DATABASE=false` after first seed; production seed creates only `admin` plus WC2026 sample data, while dev seed includes agent/player demo accounts.
|
- 基础设施:`docker compose up -d`(PostgreSQL `5432`、Redis `6379`)。
|
||||||
- `scripts/prod-init-db.sh` is destructive by design: it requires `CONFIRM=YES`, backs up unless `--skip-backup`, truncates business data, then seeds production data.
|
- API 环境变量:`cp .env.example apps/api/.env`;通过 workspace filter 启动时,Nest 从 API 工作目录读 `.env`。
|
||||||
- Deployment packaging is `pnpm pack:deploy`; `pack.mjs` removes legacy shared public directories except `flags` and `players` before creating `release/thebet365-deploy-*.zip`.
|
- 首次数据库(仓库根目录):`pnpm db:generate` → `pnpm db:migrate` → `pnpm db:seed`。
|
||||||
|
- `pnpm dev:api` 会先执行 `scripts/ensure-port-free.mjs 3000`,释放 3000 端口再启动 watch。
|
||||||
|
- 分应用启动时,**先 API**,再 `pnpm dev:player` 或 `pnpm dev:admin`;两个 Vite 都把 `/api` 代理到 `http://localhost:3000`。
|
||||||
|
- Windows PowerShell 不支持 `&&` 链式命令,需分步执行或用 `;`。
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
- `pnpm dev`:先构建 shared,再并行跑各 workspace 的 `dev`。
|
||||||
|
- `pnpm dev:admin` 与 `pnpm dev:manage` 是同一个管理端应用。
|
||||||
|
- 全量校验:`pnpm build`、`pnpm test`;根目录**没有**统一的 lint/format 脚本。
|
||||||
|
- API 测试:`pnpm --filter @thebet365/api test`。
|
||||||
|
- 单测文件:`pnpm --filter @thebet365/api exec jest settlement-calculator.spec.ts --runInBand`;不要用 `pnpm ... test -- ...` 传参,pnpm 会把字面量 `--` 传给 Jest。
|
||||||
|
- 前端构建:`pnpm --filter @thebet365/player build`、`pnpm --filter @thebet365/admin build`。
|
||||||
|
- 管理端体积分析:`pnpm --filter @thebet365/admin build:analyze`。
|
||||||
|
- 本地镜像打包(Windows):`docs\docker\build-and-export-images.bat --tag latest`。
|
||||||
|
- 生产更新(服务器):`./scripts/deploy-update.sh --images thebet365-images-<tag>.tar --tag <tag>`(详见 `docs/Docker部署指南.md` 第八节)。
|
||||||
|
|
||||||
|
## API 领域结构
|
||||||
|
|
||||||
|
业务规则放在 `apps/api/src/domains/*`,应用层只做编排:
|
||||||
|
|
||||||
|
| 领域 | 路径 | 职责 |
|
||||||
|
|------|------|------|
|
||||||
|
| identity | `domains/identity/` | 登录、用户、员工、RBAC |
|
||||||
|
| agent | `domains/agent/` | 代理网络、授信 |
|
||||||
|
| ledger | `domains/ledger/` | 钱包、账变 |
|
||||||
|
| catalog | `domains/catalog/` | 赛事 |
|
||||||
|
| odds | `domains/odds/` | 盘口 |
|
||||||
|
| betting | `domains/betting/` | 注单 |
|
||||||
|
| settlement | `domains/settlement/` | 结算 |
|
||||||
|
| operations | `domains/operations/` | 返水、内容、审计等 |
|
||||||
|
| player-messages / presence | 各子模块 | 站内信、在线状态 |
|
||||||
|
|
||||||
|
门户控制器:`applications/{player,admin,agent}/`。
|
||||||
|
|
||||||
|
## API 约定
|
||||||
|
|
||||||
|
- 全局前缀 `/api`;非生产或 `ENABLE_SWAGGER=true` 时 Swagger 在 `/api/docs`。
|
||||||
|
- `AppModule` 注册了全局 `JwtAuthGuard`;公开接口须加 `@Public()`(`apps/api/src/shared/common/decorators.ts`)。
|
||||||
|
- 业务规则放在 `domains/*`;`applications/*` 只做门户/控制器编排,不要写领域规则。
|
||||||
|
- 钱包变动走 ledger/wallet 服务;UAT 明确禁止为修结算直接改余额。
|
||||||
|
- 下注赔率以提交时快照为准(`BetSelection.odds` + `oddsVersion`),结算用快照,不受后续盘口变动影响。
|
||||||
|
- 改 `apps/api/prisma/schema.prisma` 后执行 `pnpm db:generate`;开发迁移 `pnpm db:migrate`,部署由 `deploy-update.sh` 执行 `prisma migrate deploy`。
|
||||||
|
- 新增迁移后,theme 分支若只改 player 样式,也须同步 API/Admin 与迁移文件。
|
||||||
|
|
||||||
|
## 前端约定
|
||||||
|
|
||||||
|
- 改 `apps/player/src`、`apps/admin/src` 下的 **`.ts` / `.vue`**;`src` 下虽有 `.js` 旁文件,但 `index.html` 入口是 `/src/main.ts`,Vite 优先解析 TS。
|
||||||
|
- 管理端同一应用服务 `ADMIN` 与 `AGENT` 账号;路由/菜单权限在 `router/index.ts`、`stores/auth.ts`;员工可见菜单字段 `visibleMenus`。
|
||||||
|
- 管理端权限常量:`apps/admin/src/constants/permissions.ts`(`AdminPerm`)。
|
||||||
|
- 玩家端移动优先;性能验收见 `docs/player-mobile-performance.md`。
|
||||||
|
- 管理端切页慢的分析与优化任务见 `docs/admin-page-switch-performance.md`。
|
||||||
|
- **不要**把 `apps/player/dist/` 提交进 Git;生产由 Docker 构建生成静态资源。
|
||||||
|
|
||||||
|
## i18n 改文案流程
|
||||||
|
|
||||||
|
支持语言均为 **zh-CN / en-US / ms-MY**(马来语)。改文案须**三语同步**,不要只改中文。
|
||||||
|
|
||||||
|
### 玩家端(`apps/player`)
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 文案文件 | `apps/player/src/i18n/zh-CN.ts`、`en-US.ts`、`ms-MY.ts` |
|
||||||
|
| 结构 | **嵌套对象**(如 `bet.place_bet`、`nav.home`) |
|
||||||
|
| 组件用法 | `useI18n()` → `t('bet.odds_changed')` |
|
||||||
|
| 语言切换 | `useAppLocale().setLocale()`;按需 `ensurePlayerLocale` 懒加载对应 ts 文件 |
|
||||||
|
| 存储 key | `localStorage.locale`;登录用户会 `POST /player/language` 同步后端 |
|
||||||
|
|
||||||
|
**改文案步骤:**
|
||||||
|
|
||||||
|
1. 在 Vue 里用 `t('模块.key')` 引用,**不要**在模板写死中文。
|
||||||
|
2. 在 **三个** locale 文件的**相同路径**下各加一条(保持 key 一致)。
|
||||||
|
3. 带占位符用 vue-i18n 约定,如 `'共 {n} 场': '共 {n} 场'` → `t('key', { n: 3 })`。
|
||||||
|
4. 本地 `pnpm dev:player` 切换语言各看一遍;热更新一般即时生效。
|
||||||
|
5. theme 分支合并 main 功能时:**只合并新增 key**,勿用 main 文件整文件覆盖(theme 可能有不同措辞)。
|
||||||
|
|
||||||
|
### 管理端(`apps/admin`)
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| 核心入口 | `apps/admin/src/i18n/admin-messages.ts`(三语扁平 `Record<string, string>`) |
|
||||||
|
| 列表/弹窗大段文案 | `admin-pages.ts`(中/英)、`admin-pages-ms.ts`(马来)→ 通过 spread 并入 `admin-messages` |
|
||||||
|
| 表单校验 key | `form-validation.ts`(`err.*` 等,用 `resolveFormError` 展示) |
|
||||||
|
| 组件用法 | `useAdminLocale().t('nav.bets')` 或 `t('key', { n: 1 })` 占位符 `{n}` |
|
||||||
|
| 语言切换 | `useAdminLocale().setLocale()` → `preloadAdminLocale` + `localStorage.admin_locale` |
|
||||||
|
| 动态拆包 | `bundles/{zh-CN,en-US,ms-MY}.ts` + `locale-loader.ts`(当前主包仍含三语全文,拆包未完全落地) |
|
||||||
|
|
||||||
|
**改文案步骤:**
|
||||||
|
|
||||||
|
1. 确定 key 命名:导航 `nav.*`、通用 `common.*`、页面 `user.*` / `match.*` / `deposit.*` 等,与现有前缀保持一致。
|
||||||
|
2. 在 `admin-messages.ts` 的 **zh / en / ms 三个对象**里各加同名 key(核心短文案)。
|
||||||
|
3. 若属于某列表页/弹窗长文案,优先加到 `admin-pages.ts`(中/英)与 `admin-pages-ms.ts`(马来)的对应 export,它们会 spread 进 `admin-messages`。
|
||||||
|
4. 表单校验错误:throw `FormValidationError('err.xxx')` 并在三语里定义 `err.xxx`。
|
||||||
|
5. 本地 `pnpm dev:admin`,右上角切换语言验证;改 `admin-pages*` 后若未刷新,重启 dev 一次。
|
||||||
|
6. Admin 与 Player **文案独立**,改玩家端不要只改 admin 文件(反之亦然)。
|
||||||
|
|
||||||
|
### 常见错误
|
||||||
|
|
||||||
|
- 只改 `zh-CN` 未改 `en-US`、`ms-MY` → 切换语言显示 key 或英文 fallback。
|
||||||
|
- 玩家端 key 层级不一致(如中文有 `bet.foo` 英文漏了 `bet` 层)→ `t()` 返回 key 字符串。
|
||||||
|
- 管理端在 `admin-pages.ts` 只加了 `adminPagesZh` 未加 `adminPagesEn` / `adminPagesMs`。
|
||||||
|
- theme 分支用 `git checkout main -- apps/player/src/i18n/` 覆盖 → 冲掉主题分支已有翻译。
|
||||||
|
|
||||||
|
### 与业务/后台内容的关系
|
||||||
|
|
||||||
|
- **i18n 文件**:前端固定 UI 标签、按钮、提示。
|
||||||
|
- **管理端「公共管理」/ 公告 / Banner**:运营可配正文,走 API 与数据库,**不要**硬编码进 i18n(玩家端公告详情等展示 API 返回内容)。
|
||||||
|
|
||||||
|
## 主题分支(player 多皮肤)
|
||||||
|
|
||||||
|
| 分支 | 风格 | 注意 |
|
||||||
|
|------|------|------|
|
||||||
|
| `main` | 暗金主题 | 生产默认发版分支 |
|
||||||
|
| `theme-2` | Pinnacle 蓝白 | 玩家 UI 与 main 分叉,**禁止** `git checkout main -- apps/player/...` 整文件覆盖 |
|
||||||
|
| `theme-3` | 统一移动端视觉 | 同 theme-2 |
|
||||||
|
| `theme-4` | 海军蓝暗色极简 | 含悬浮客服等独有组件 |
|
||||||
|
|
||||||
|
同步功能到 theme 分支:API/Admin/迁移可整目录 checkout;Player 只做逻辑合并 + 保留各分支 `styles.css` 与主题资源。发版前在目标分支打包镜像。
|
||||||
|
|
||||||
|
## 演示账号(开发 seed)
|
||||||
|
|
||||||
|
| 角色 | 用户名 | 密码 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 超级管理员 | admin | Admin@123 |
|
||||||
|
| 一级代理 | agent1 | Agent@123 |
|
||||||
|
| 二级代理 | agent2 | Agent@123 |
|
||||||
|
| 玩家 | player1 | Player@123 |
|
||||||
|
|
||||||
|
生产 seed 仅创建 admin + WC2026 样例数据,不含代理/玩家演示号。详见 `docs/默认数据说明.md`。
|
||||||
|
|
||||||
|
## 文档索引
|
||||||
|
|
||||||
|
| 文档 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `docs/项目启动指南.md` | 本地开发、排错、端口 |
|
||||||
|
| `docs/Docker部署指南.md` | 生产部署、备份、回滚、发版流程 |
|
||||||
|
| `docs/docker/镜像构建与导出.md` | 本地打镜像 tar |
|
||||||
|
| `docs/默认数据说明.md` | seed 数据、WC2026、48 强 |
|
||||||
|
| `docs/投注玩法说明.md` | 玩法与判赢规则 |
|
||||||
|
| `docs/结算与返水金额规则.md` | 派彩/返水金额公式 |
|
||||||
|
| `docs/settlement-and-fund-flow-analysis.md` | 结算操作流程 |
|
||||||
|
| `docs/手动充值功能说明.md` | 充值审核流程 |
|
||||||
|
| `docs/短信调试与日志说明.md` | 创蓝短信排错 |
|
||||||
|
| `docs/UAT_CHECKLIST.md` | 上线前回归清单 |
|
||||||
|
| `docs/player-mobile-performance.md` | 玩家端性能验收 |
|
||||||
|
| `docs/admin-page-switch-performance.md` | 管理端切页优化任务 |
|
||||||
|
|
||||||
|
## 测试与冒烟
|
||||||
|
|
||||||
|
- Jest 用例在 `apps/api/src/**/*.spec.ts`,`rootDir: src`;文档中的单元/规则测试一般不依赖真实数据库。
|
||||||
|
- 管理端「冒烟测试」页调用 DB 相关检查;`SmokeTestService` 非生产默认可用,生产需 `ALLOW_SMOKE_TESTS=true`。
|
||||||
|
- UAT 回归流程见 `docs/UAT_CHECKLIST.md`(含管理端 UI 冒烟与钱包/代理额度手工检查)。
|
||||||
|
|
||||||
|
## 部署注意
|
||||||
|
|
||||||
|
- 生产 compose 使用 `.env.docker`:`docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build`,或配置好后 `pnpm docker:up`。
|
||||||
|
- **不要**用 example 覆盖线上 `.env.docker`;部署脚本通常只回写 `IMAGE_TAG`。
|
||||||
|
- 生产首次 seed 后保持 `SEED_DATABASE=false`;迁移由 `deploy-update.sh` 执行,勿在 API 容器设 `RUN_MIGRATIONS_ON_START=true`(除非应急)。
|
||||||
|
- `scripts/prod-init-db.sh` **会清空业务数据**:须 `CONFIRM=YES`,默认先备份,再 truncate 并灌生产数据。
|
||||||
|
- 打包部署 zip:`pnpm pack:deploy`;`pack.mjs` 会删除 `packages/shared/public` 下除 `flags`、`players` 外的遗留中文目录。
|
||||||
|
- 构建 player/admin 若报 `ENOENT ... public/球员`:清理 `packages/shared/public` 下错误中文目录后重试。
|
||||||
|
|
||||||
|
## AI 改代码时请避免
|
||||||
|
|
||||||
|
- 不要 `git merge main` 进 theme 分支做 player 同步(会把 main 暗金样式大量带入)。
|
||||||
|
- 不要直接改数据库余额修结算;走结算/钱包服务。
|
||||||
|
- 不要提交 `.env`、`.env.docker`、镜像 tar、`apps/*/dist/`。
|
||||||
|
- 改 player 主题相关文件时,先确认当前分支是 `main` 还是 `theme-*`,避免用错 CSS 变量(如 theme-2 无 `--gold`)。
|
||||||
|
- 管理端改列表页性能时,参考 `docs/admin-page-switch-performance.md` 任务清单,优先 KeepAlive + 缓存而非盲目减 API 字段。
|
||||||
|
|||||||
247
docs/admin-page-switch-performance.md
Normal file
247
docs/admin-page-switch-performance.md
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
# 管理端页面切换性能分析与优化任务
|
||||||
|
|
||||||
|
> 分析日期: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 时常 **并发 3–10 个请求**,同步开销被放大。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、按影响排序的瓶颈清单
|
||||||
|
|
||||||
|
### 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` 全屏 loading(router 已做过 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 — 切页体验(1–2 天,收益最大)
|
||||||
|
|
||||||
|
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 — 重型页与 API(2–3 天)
|
||||||
|
|
||||||
|
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 — 守卫与 bundle(3–5 天)
|
||||||
|
|
||||||
|
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 DevTools,Fast 3G):
|
||||||
|
|
||||||
|
- 切回已访问列表页:无重复全量列表 API(或仅 background refresh)
|
||||||
|
- `/users` 二次进入:API 数从 3+ 降到 0–1
|
||||||
|
- 侧边栏切换: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` |
|
||||||
Reference in New Issue
Block a user