Files
photo-live/docs/plans/2026-09-22-用户列表分页与同步信息展示-实施计划.md
浅念 37d88da4bc docs(ui05,ui06): admin analysis+HTML preview, app-preview evidence, UI06 unified visual plan
- admin-preview-2026-09-22/ 20页新版后台HTML原型+截图+validation(UI05 交付)
- app-preview-2026-09-22/ 截图/validation/check 脚本入库及小改
- admin-redesign-concept 概念图
- UI05 管理后台分析与新版设计说明、用户列表分页计划文档
- UI06 全端视觉统一实施方案(以两个 HTML 预览为唯一视觉基准)
- services/api g4-sync-once 手动同步脚本

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 22:42:00 +08:00

124 lines
10 KiB
Markdown
Raw Permalink 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.
# 用户列表分页与同步信息展示 实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 管理后台用户列表换成标准页码分页(页码/每页条数/总数),并把同步源带来的工号/邮箱/公司/头像等档案信息落库展示。
**Architecture:** 同步快照存 `external_user_mappings.profile`(新增 nullable JSON 列——守「users 表不加源身份列」红线,源数据隔离在映射表);端口 `ExternalUserRecord` 增可选 `sourceProfile`,引擎建/更映射时随版本写入。`listAdminUsers` 管理列表模式新增 `page/pageSize` 偏移分页并返回 `total`(游标模式原样保留,契约只增不改);`AdminUser` 增可选 `synced` 对象。前端用户列表换 `ElPagination` 页码分页 + 新列(工号/邮箱/公司,头像并入姓名列)。
**Tech Stack:** Prisma 手写迁移(utf8mb4_090_bin 约定)、openapi.yaml + `pnpm gen`、NestJS、Vue3 + Element Plus、i18n(apps/admin/src/locales/langs/{zh,en}.json)。
**红线遵循:** users 表零改动;同步决策绝不依赖 profile(纯展示快照,坏 profile 不影响行应用);契约字段全部 optional/nullable(旧客户端零 break);ov 栈不涉及(用户域 cn 单域)。
## 文件结构
| 文件 | 动作 |
|---|---|
| `packages/db-cn/prisma/schema.prisma` + 新迁移目录 | external_user_mappings += `profile Json?` |
| `services/api/src/user-sync/user-source.port.ts` | `ExternalUserProfile` + `sourceProfile?` |
| `services/api/src/user-sync/http-user-source.ts` | 填充 sourceProfile |
| `services/api/src/user-sync/mock-user-source.ts` | MockExternalRecord += sourceProfile?(透传) |
| `services/api/src/user-sync/user-sync.service.ts` | 映射 create/update 写 profile |
| `packages/contracts/openapi.yaml` + gen | AdminUser.synced、AdminUserList.total、page/pageSize |
| `services/api/src/admin/admin-users.service.ts` | page 模式(COUNT+OFFSET)+ synced 合并 helper;治理回显带 synced |
| `services/api/src/admin/users-lookup.controller.ts` | page/pageSize 解析透传 |
| `services/api/test/user-sync.e2e-spec.ts` | profile 落库/随版本刷新断言 |
| `services/api/test/admin-users.e2e-spec.ts`(或 users-lookup) | page 分页 + synced 展示断言 |
| `apps/admin/src/types/api/adminUser.d.ts`、`api/adminUsers.ts` | 类型与参数 |
| `apps/admin/src/views/system/users/index.vue` | ElPagination + 新列 |
| `apps/admin/src/locales/langs/{zh,en}.json` | 新文案 |
---
### Task 1: DB 迁移 + 端口/引擎/适配器写入 profile
- [ ] schema.prisma `external_user_mappings` 加 `profile Json?`(注释:同步源档案快照,纯展示,不参与同步决策)
- [ ] 新迁移 `packages/db-cn/prisma/migrations/20260922<HHMMSS>_mapping_profile/migration.sql`(手写,格式对齐 20260920114149 先例):
```sql
-- G4 后续:映射表增 profile JSON 列——同步源档案快照(工号/邮箱/公司/头像),
-- 纯展示维度:不参与同步决策(引擎幂等仍只看 sourceVersion),users 表零改动
--(源身份隔离红线)。nullable:mock 源/历史行无快照。
ALTER TABLE `external_user_mappings` ADD COLUMN `profile` JSON NULL;
```
- [ ] user-source.port.ts:
```ts
/** 同步源档案快照(纯展示,不参与同步决策;http 源填充,mock 可不填)。 */
export interface ExternalUserProfile {
/** 工号。 */
sn?: string;
/** 企业邮箱。 */
email?: string;
/** 公司全称。 */
companyName?: string;
/** 公司简称。 */
companyAbbr?: string;
/** 头像 URL。 */
avatar?: string;
}
```
`ExternalUserRecord` 加 `sourceProfile?: ExternalUserProfile;`(注释:引擎原样落 mapping.profile,绝不据此做同步决策)。
- [ ] http-user-source.ts records 映射加:
```ts
sourceProfile: {
sn: raw.sn || undefined,
email: raw.email || undefined,
companyName: raw.company_name || undefined,
companyAbbr: raw.company_abbr || undefined,
avatar: raw.avatar || undefined,
},
```
- [ ] mock-user-source.ts:`MockExternalRecord` 加 `sourceProfile?: ExternalUserProfile`;items 映射透传 `sourceProfile: user.sourceProfile`。
- [ ] user-sync.service.ts **四处**映射写入(新建关联 / 既有更新 / 重建改指 / 全新用户建映射)统一加 `profile: item.sourceProfile ?? undefined`(undefined=Prisma 不改列)。
- [ ] **幂等跳过路径的 profile 回填**(存量 230 用户已处于当前版本,版本门直接 return——不补写则 profile 永远 NULL):`validated.version <= mapping.sourceVersion` 分支内,若 `item.sourceProfile` 非空且与 `mapping.profile` 深比较不同 → 仅 `external_user_mappings.update({ profile })`(**不动 sourceVersion/users 行/会话/counts,bucket 仍 null**——展示维度补偿写,不违反「同步决策不依赖 profile」红线);文件头登记。
- [ ] 验证:`pnpm --filter @photo/api-cn typecheck`;本地库应用迁移(prisma migrate dev 或 db push 对齐项目流程后跑 user-sync e2e)。
- [ ] Commit: `feat(api,db-cn): persist synced source profile snapshot on mappings`
### Task 2: 契约扩展 + 服务端 page 分页与 synced 合并
- [ ] openapi.yaml:
- `AdminUser.properties` 加 `synced`(`type: [object, 'null']`,readOnly,字段 sn/email/companyName/companyAbbr/avatar 均 string;描述:同步源档案快照,非同步用户为 null)。
- `AdminUserList` allOf 内对象加 `total`(`type: [integer, 'null']`,描述:仅 page 模式返回的总数;游标模式为 null)。
- `listAdminUsers` parameters 加 `page`(integer ≥1)与 `pageSize`(integer 1..100)均 optional;描述注明:提供 cursor 走游标模式(忽略 page);提供 page(无 cursor)走偏移分页并返回 total(此时 nextCursor=null 且 hasMore=false——守 CursorPageMeta「hasMore⟺nextCursor≠null」不变式,翻页由 total 驱动)。
- `pnpm --filter @photo/contracts gen` 重新生成类型(根上无 gen 脚本)。
- [ ] users-lookup.controller.ts:`parsePage`(**未提供 → undefined**,提供则 ≥1)/`parsePageSize`(未提供 → undefined,提供则 1..100);page 模式内的缺省值(1/20)在服务层 page 模式选定后才应用——保证「未带 page 的旧客户端仍走游标模式」。
- [ ] admin-users.service.ts:
- `list(user, query, status, cursor, limit, page?: number, pageSize?: number)`:`page !== undefined && cursor === undefined` → page 模式:同一 WHERE(去游标条件)`COUNT(*)` + `LIMIT pageSize OFFSET (page-1)*pageSize`,`nextCursor=null`、`hasMore=false`、`total`;否则原游标逻辑(total=null)。
- 导出 `attachSyncedProfiles(items: AdminUserItem[]): Promise<AdminUserItem[]>`:按 id 批查 `external_user_mappings`(`userId IN`),每用户取 `sourceVersion` 最大行,`profile` → `synced`(无映射/profile → `synced: null`);list 两种模式与治理回显(update/activate/deactivate)统一套用;前缀 lookup 模式不套(邀请候选无需档案,文件头注明)。
- [ ] e2e:admin-users/users-lookup spec 增用例——直种 2 用户 + 1 映射带 profile → `?page=1&pageSize=1` 返回 total=2、单条且 `synced` 字段正确;`?page=2` 返回第二条、`hasMore=false`;游标模式(不带 page)`total` 为 null;无映射用户 `synced` null;前缀模式(accountPrefix)响应干净不含 synced/total。user-sync e2e:U2 场景行加 `sourceProfile` → ① 断言 mapping.profile 落库;② 改名场景换 companyAbbr → 断言 profile 随版本刷新;③ **同版本重跑(版本门跳过)+ profile 此前为空 → profile 被回填且 counts 全 0**(存量用户回填路径)。
- [ ] 验证:typecheck + `test -- http-user-source` + `test:e2e -- user-sync admin-users users-lookup`。
- [ ] Commit: `feat(contracts,api): page-mode pagination + synced profile on admin users list`
### Task 3: 管理后台前端(分页器 + 新列)
- [ ] types/api/adminUser.d.ts:`Item` 加 `synced?: {...} | null`;`ListResponse` 加 `total?: number | null`;`ListParams` 加 `page?/pageSize?`。
- [ ] api/adminUsers.ts:params 直传(类型已覆盖)。
- [ ] views/system/users/index.vue:
- 去 `useCursorList`/loadMore,改 `items/total/page/pageSize/loading/error` + `loadPage()`;`search()/resetFilters()` 重置 page=1;分页器 `ElPagination`(layout `total, sizes, prev, pager, next`,sizes [20,50,100])。
- 列调整:姓名列并入头像(ElAvatar src=synced?.avatar,无则首字符);新增 工号(synced.sn)、邮箱(synced.email)、公司(companyAbbr || companyName)三列;「—」占位非同步用户。
- 行内治理操作(编辑/重置/激活/停用/解锁)原样保留,回显 replaceItem 兼容。
- [ ] locales zh/en:`usersManage.list.colSn/colEmail/colCompany`、分页相关沿用 Element 内建文案。
- [ ] 验证:admin 前端 typecheck/build(`pnpm --filter admin build` 或项目等价脚本)。
- [ ] Commit: `feat(admin): standard pagination + synced profile columns on users list`
### Task 4: 部署测试服务器(server + cn web 双镜像 + 迁移)
- [ ] server 镜像 `git archive HEAD | docker build -f docker/server.Dockerfile`,tag `<sha>-20260922b`;web 镜像同法 `docker/web.Dockerfile`,tag `admin-<sha>-20260922b`(ov 栈 web 保持 viewer-fix 不动——本任务无 viewer 改动,避免吞掉未提交 viewer 工作)。
- [ ] save/gzip → scp → load。
- [ ] 服务器**先迁移**:`docker run --rm --network 1panel-network --env-file .env.production-cn photo-live-server:<tag> migrate-deploy all`。
- [ ] cn 栈 `SERVER_IMAGE=<新> WEB_IMAGE=photo-live-web:admin-<sha>-20260922b up -d`;ov 栈仅换 SERVER_IMAGE(WEB_IMAGE=viewer-fix)。
- [ ] 验证:`http://219.157.255.213:32005/admin/` 用户列表分页可翻页、工号/邮箱/公司/头像显示(230 真实用户);`/v1/admin/users?page=1&pageSize=2` 带 total;user-sync status 正常(下一 cron 批不因 profile 列失败)。
- [ ] 补验收记录(G4 记录追加或新条目),Commit:`docs: ...`
## 明确不做(YAGNI)
- 相册等其他列表的分页交互改造(同为游标"加载更多"模式)——用户列表先行,需要再说。
- synced 信息进用户编辑弹窗(列表展示已覆盖诉求)。
- ov 栈/观众页任何改动。