- 统计卡片和用户类型Tag从「平台」改为「运营团队」,避免命名歧义 - 公众用户详情从旧版Child模型(姓名/年级/学校)改为UserParentChild关系,展示子女独立账号信息 - 后端详情接口和列表_count同步从children切换到parentRelations - 更新统一用户管理设计文档,补充实施记录 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 KiB
13 KiB
统一用户管理 — 设计方案
所属端:超管端 状态:已实现(迭代中) 创建日期: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 — 命名优化 + 子女账号独立化适配
问题:
- 统计卡片和 Tag 中「平台」命名易误解为"平台全部用户",实际指运营管理人员
- 公众用户详情仍展示旧版
Child模型(姓名/年级/学校),子女已独立为User后应使用UserParentChild关系
改动(3 个文件):
backend/src/users/users.service.ts— findOne 详情查询:children(旧 Child 表)→parentRelations(UserParentChild + child User);列表_count.children→_count.parentRelationsfrontend/src/api/users.ts— User 类型定义:children数组 →parentRelations数组(含 child 独立用户信息 + relationship + controlMode)frontend/src/views/system/users/Index.vue— 统计卡片和 Tag 标签:「平台」→「运营团队」;详情 Drawer 子女区域:旧版姓名/年级/学校列表 → 新版子女账号卡片(头像+昵称+用户名+关系+状态)
验证结果:
- 后端重启成功,编译无错误
- 前端 HMR 热更新生效,无新增 TS 错误