# 统一用户管理 — 设计方案

> 所属端：超管端
> 状态：已实现（待验收）
> 创建日期：2026-03-27
> 最后更新：2026-03-27

---

## 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 张卡片横排，每张显示：类型图标 + 类型名称 + 数量
- 选中的卡片高亮（主色边框），点击 = 设置 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个）
├── 姓名 / 年龄 / 年级 / 城市 / 学校

报名记录（近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: { children, 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

仅超管可访问，返回各类型用户数量：

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

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

#### 3.3.3 改造 GET /api/users/:id

超管调用时：
- 不做 tenantId 过滤
- 公众用户额外返回：`children`（子女列表）、`contestRegistrations`（近20条报名记录，含活动名和子女名）
- 评委额外返回：`contestJudges`（参与的评审活动列表）

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

专门用于禁用/启用，区分于通用的 update 接口：

```json
// 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 错误（原有错误均为已有代码）
