library-picturebook-activity/docs/design/super-admin/unified-user-management.md
aid 4466e28b3b 超管端用户管理:「平台」更名为「运营团队」+ 子女信息适配独立账号模型
- 统计卡片和用户类型Tag从「平台」改为「运营团队」,避免命名歧义
- 公众用户详情从旧版Child模型(姓名/年级/学校)改为UserParentChild关系,展示子女独立账号信息
- 后端详情接口和列表_count同步从children切换到parentRelations
- 更新统一用户管理设计文档,补充实施记录

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 18:03:44 +08:00

13 KiB
Raw Blame History

统一用户管理 — 设计方案

所属端:超管端 状态:已实现(迭代中) 创建日期2026-03-27 最后更新2026-03-30


1. 背景与问题

超管端"用户中心"下有两个用户管理页面:"用户管理"和"公众用户管理",存在以下问题:

问题 说明
超管看不到全局用户 "用户管理"只显示 super 租户的用户,看不到 gdlib、judge、public 等租户的用户
用户类型无法区分 表格里没有"所属租户"或"用户类型"列,不知道用户属于哪个机构
两个页面职责重叠又割裂 "用户管理"管 super 用户,"公众用户"管 public 用户,机构用户和评委谁都管不到
公众用户缺少管理操作 不能禁用异常账号、不能编辑信息
用户管理缺少关键字段 没有手机号、来源、城市等字段展示
没有搜索功能 "用户管理"页面无任何筛选手段

目标:将两个页面合并为一个统一的用户管理页面,超管可以在全局视角下查看、筛选、管理所有类型的用户。


2. 现状分析

2.1 用户数据模型

系统通过 租户归属 + 角色 + 来源 三层组合区分用户类型:

用户类型推导规则:
├── tenant.isSuper === 1       → 平台用户(运营团队)
├── tenant.code === 'public'   → 公众用户(家长/独立参与者)
├── tenant.code === 'judge'    → 评委
└── 其他租户                    → 机构用户

User 模型关键字段:tenantId, username, nickname, phone, city, birthday, gender, userSource, status, organization

2.2 现有页面

用户管理views/system/users/Index.vue

  • 数据来源:GET /api/users,按 req.tenantId 过滤,超管只看到 super 租户用户
  • 表格列ID、用户名、昵称、邮箱、角色、状态、创建时间
  • 操作:新增、编辑、改密、删除
  • 无搜索功能

公众用户管理views/system/public-users/Index.vue

  • 数据来源:GET /api/public/users,专查 public 租户用户
  • 表格列:用户信息(头像+昵称)、手机号、城市、子女数、报名数、状态、注册时间
  • 操作查看详情Drawer 展示子女+报名记录)
  • 有关键词搜索,但不能禁用/编辑

2.3 现有后端接口

GET /api/users          — 按 tenantId 隔离,返回当前租户的用户
GET /api/users/:id      — 用户详情
POST /api/users         — 创建用户
PATCH /api/users/:id    — 更新用户
DELETE /api/users/:id   — 删除用户
GET /api/public/users   — 公众用户列表(独立接口)
GET /api/public/users/:id — 公众用户详情(含子女+报名)

3. 设计方案

3.1 整体思路

合并"用户管理"和"公众用户管理"为一个页面,超管端通过统计卡片 + 筛选条件实现分类查看,详情 Drawer 根据用户类型展示不同内容。菜单层面移除"公众用户管理"入口。

3.2 页面设计

页面结构

┌─ 标题卡片 ─────────────────────────────────────────────┐
│  用户管理                                               │
└────────────────────────────────────────────────────────┘

┌─ 统计卡片(一行,可点击切换)──────────────────────────────┐
│ [全部 128] [平台 3] [机构 15] [评委 8] [公众 102]         │
└────────────────────────────────────────────────────────┘

┌─ 筛选栏 ───────────────────────────────────────────────┐
│ 关键词:[_______] 所属机构:[下拉] 用户来源:[下拉]          │
│ 状态:[下拉]                          [搜索] [重置]       │
└────────────────────────────────────────────────────────┘

┌─ 数据表格 ─────────────────────────────────────────────┐
│ 用户信息 | 用户类型 | 所属机构 | 手机号 | 城市 |          │
│          | 角色    | 来源    | 注册时间 | 操作 |          │
└────────────────────────────────────────────────────────┘

统计卡片

  • 5 张卡片横排,每张显示:类型图标 + 类型名称 + 数量
  • 类型命名:全部 / 运营团队 / 机构 / 评委 / 公众(平台 → 运营团队2026-03-30 更名)
  • 选中的卡片高亮(主色边框),点击 = 设置 userType 筛选
  • 点"全部"清除类型筛选
  • 数据来源:GET /api/users/stats

筛选栏

筛选项 组件 可选值 联动逻辑
关键词 Input 用户名/昵称/手机号
所属机构 Select 租户下拉列表 仅 userType 为空或 "org" 时显示
用户来源 Select 管理员创建 / 自主注册
状态 Select 正常 / 禁用

表格列

宽度 渲染方式
用户信息 220 头像 + 昵称 + @用户名(竖排布局)
用户类型 90 Tag颜色区分蓝=平台,绿=机构,橙=评委,紫=公众
所属机构 140 tenant.name公众/平台用户显示 "-"
手机号 130 phone 或 "-"
城市 100 city 或 "-"
角色 120 角色名 Tag 列表
来源 90 Tag管理创建/ 自主注册(蓝)
注册时间 160 YYYY-MM-DD HH:mm
操作 140 查看详情 / 更多下拉(禁用/启用、重置密码)

操作设计

操作 说明 权限
查看详情 打开右侧 Drawer 所有用户可查看
禁用/启用 切换 status 字段 不能禁用自己,不能禁用其他租户的唯一管理员
重置密码 弹窗输入新密码

注意:超管不提供"新增用户"和"删除用户"操作。新增用户应在各自端完成(机构管理端创建机构用户,公众端自主注册),删除用户风险过高,只提供禁用。

详情 Drawer按用户类型适配内容

通用区域(所有类型):

基本信息 — Descriptions 组件
├── 昵称 / 用户名 / 手机号 / 邮箱
├── 城市 / 性别 / 所属单位
├── 所属机构 / 角色 / 用户来源
└── 注册时间 / 状态

公众用户额外区域

子女账号N个— 基于 UserParentChild 关系,子女为独立 User
├── 头像 / 昵称 / @用户名 / 性别 / 城市 / 关系(父亲/母亲/监护人) / 状态

报名记录近20条
├── 活动名称 / 报名状态 / 参与者(本人/子女名) / 报名时间

评委额外区域

评审活动
├── 活动名称 / 评审状态 / 已评/总数

机构用户额外区域

权限概览
├── 拥有角色 / 权限数量

3.3 后端改动

3.3.1 改造 GET /api/users扩展查询参数

超管isSuper=1调用时

  • 不按 req.tenantId 过滤,查全部用户
  • 新增查询参数:
参数 类型 说明
userType string? platform / org / judge / public,映射到租户条件
filterTenantId number? 指定机构筛选
userSource string? admin_created / self_registered
status string? enabled / disabled
keyword string? 搜索范围增加 phone 字段
  • 返回字段增加:
    • tenant: { id, name, code, tenantType, isSuper }(用于前端推导用户类型)
    • _count: { parentRelations, contestRegistrations }(公众用户的子女账号数和报名数)

普通租户调用时:保持现有逻辑不变。

userType 映射逻辑:

platform → tenant.isSuper = 1
org      → tenant.isSuper = 0 AND tenant.code NOT IN ('public', 'judge')
judge    → tenant.code = 'judge'
public   → tenant.code = 'public'

3.3.2 新增 GET /api/users/stats

仅超管可访问,返回各类型用户数量:

{
  "total": 128,
  "platform": 3,
  "org": 15,
  "judge": 8,
  "public": 102
}

实现方式:分别 count 查询,按上述 userType 映射条件。

3.3.3 改造 GET /api/users/:id

超管调用时:

  • 不做 tenantId 过滤
  • 公众用户额外返回:parentRelations(子女账号列表,含 child User 信息)、contestRegistrations近20条报名记录含活动名和子女名
  • 评委额外返回:contestJudges(参与的评审活动列表)

3.3.4 新增 PATCH /api/users/:id/status

专门用于禁用/启用,区分于通用的 update 接口:

// Request
{ "status": "enabled" | "disabled" }

// 校验规则
// - 不能操作自己
// - 不能禁用其他租户的唯一管理员(查该租户下 tenant_admin 角色用户数)

3.4 前端改动

文件 操作 说明
frontend/src/api/users.ts 修改 扩展 UserQueryParams 类型,新增 stats 和 updateStatus 接口
frontend/src/views/system/users/Index.vue 重写 统一用户管理页面
frontend/src/views/system/public-users/Index.vue 废弃 功能合并到用户管理,文件保留但不再使用
后端菜单配置 修改 超管端移除"公众用户管理"菜单项

4. 改动范围

后端

文件 改动类型
backend/src/users/users.service.ts 修改findAll 增加跨租户查询逻辑、新增 getStats 方法、findOne 增加关联数据
backend/src/users/users.controller.ts 修改findAll 增加查询参数、新增 stats 和 updateStatus 端点
backend/src/users/dto/create-user.dto.ts 修改:新增查询参数 DTO

前端

文件 改动类型
frontend/src/api/users.ts 修改:扩展类型和接口
frontend/src/views/system/users/Index.vue 重写
frontend/src/views/system/public-users/Index.vue 废弃(保留文件不删除)

菜单配置

改动 说明
超管端菜单 用户中心下移除"公众用户管理"子菜单

5. 实施记录

2026-03-27 — 首次实现

后端改动3 个文件):

  • backend/src/users/users.service.ts — findAll 重构为参数对象模式,超管跨租户查询;新增 getStats()、updateStatus() 方法findOne 超管调用时返回子女/报名/评审关联数据
  • backend/src/users/users.controller.ts — 新增 GET /api/users/stats、PATCH /api/users/:id/status 端点findAll 增加 userType/filterTenantId/userSource/status 查询参数
  • backend/src/auth/strategies/jwt.strategy.ts — JWT validate 时查租户 isSuper 字段,注入 isSuperTenant 到 req.user

前端改动2 个文件):

  • frontend/src/api/users.ts — 扩展 UserQueryParams、User、UserStats 类型;新增 getUserStats()、updateUserStatus() 接口;保留 usersApi 兼容导出
  • frontend/src/views/system/users/Index.vue — 完全重写:统计卡片 + 筛选栏 + 统一表格 + 详情 Drawer按用户类型适配+ 禁用/启用/重置密码操作

待手动操作:

  • 超管端菜单管理中删除"公众用户管理"菜单项(数据在数据库中,非 menus.json

验证结果:

  • 后端 TSC 编译通过NestJS 启动成功,/api/users/stats 和 /api/users/:id/status 路由注册正常
  • 前端无新增 TS 错误(原有错误均为已有代码)

2026-03-30 — 命名优化 + 子女账号独立化适配

问题:

  1. 统计卡片和 Tag 中「平台」命名易误解为"平台全部用户",实际指运营管理人员
  2. 公众用户详情仍展示旧版 Child 模型(姓名/年级/学校),子女已独立为 User 后应使用 UserParentChild 关系

改动3 个文件):

  • backend/src/users/users.service.ts — findOne 详情查询:children(旧 Child 表)→ parentRelationsUserParentChild + child User列表 _count.children_count.parentRelations
  • frontend/src/api/users.ts — User 类型定义:children 数组 → parentRelations 数组(含 child 独立用户信息 + relationship + controlMode
  • frontend/src/views/system/users/Index.vue — 统计卡片和 Tag 标签:「平台」→「运营团队」;详情 Drawer 子女区域:旧版姓名/年级/学校列表 → 新版子女账号卡片(头像+昵称+用户名+关系+状态)

验证结果:

  • 后端重启成功,编译无错误
  • 前端 HMR 热更新生效,无新增 TS 错误