- 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>
10 KiB
用户列表分页与同步信息展示 实施计划
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 先例):
-- G4 后续:映射表增 profile JSON 列——同步源档案快照(工号/邮箱/公司/头像),
-- 纯展示维度:不参与同步决策(引擎幂等仍只看 sourceVersion),users 表零改动
--(源身份隔离红线)。nullable:mock 源/历史行无快照。
ALTER TABLE `external_user_mappings` ADD COLUMN `profile` JSON NULL;
- user-source.port.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 映射加:
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)。AdminUserListallOf 内对象加total(type: [integer, 'null'],描述:仅 page 模式返回的总数;游标模式为 null)。listAdminUsersparameters 加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;无映射用户syncednull;前缀模式(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(layouttotal, 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,tagadmin-<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 栈/观众页任何改动。