diff --git a/docs/superpowers/specs/2026-07-10-fix-student-import-name-design.md b/docs/superpowers/specs/2026-07-10-fix-student-import-name-design.md new file mode 100644 index 0000000..6a165d7 --- /dev/null +++ b/docs/superpowers/specs/2026-07-10-fix-student-import-name-design.md @@ -0,0 +1,112 @@ +# 修复学生导入:姓名与账号字段 — 设计文档 + +> 日期:2026-07-10 +> 状态:已确认 +> 关联:PRD 钉钉集成批次 2 + +--- + +## 1. 背景 + +`batchImportStudents` 创建 Student 时: +- `name` 填的是占位符 `dd_${dingUserId}` +- `phone` 未设置 + +正确行为: +- `name` = 钉钉用户真实姓名 +- `phone` = 钉钉用户手机号;无手机号时填 `dt_${dingUserId}` + +## 2. 方案:前端传用户信息 + +前端组织架构树已有 `{ userid, name, mobile }` 数据(来自 `getDeptUsers`),导入时直接传给后端,零额外钉钉 API 调用。 + +## 3. 改动清单 + +### 3.1 后端 DTO + +**文件:** `apps/server/src/classes/dto/class.dto.ts` + +`BatchImportStudentsDto`: + +```typescript +// 旧 +export class BatchImportStudentsDto { + dingUserIds: string[]; +} + +// 新 +export class BatchImportStudentsDto { + @IsArray() @ArrayNotEmpty() + users: Array<{ dingUserId: string; name: string; mobile?: string }>; +} +``` + +`CreateClassDto.dingUserIds` 同改为 `users` 字段。 + +### 3.2 后端 Service + +**文件:** `apps/server/src/classes/classes.service.ts` + +`batchImportStudents` 签名改为: + +```typescript +async batchImportStudents(classId: number, users: Array<{ + dingUserId: string; name: string; mobile?: string; +}>): Promise<{ imported: number; skipped: number }> +``` + +创建 Student 时: + +```typescript +this.studentRepo.create({ + name: u.name, + phone: u.mobile || `dt_${u.dingUserId}`, + status: 'active', +}) +``` + +`create()` 方法中 `dingUserIds` → `users` 透传。 + +### 3.3 后端 Controller + +**文件:** `apps/server/src/classes/classes.controller.ts` + +`batchImportStudents` 端点传参改为: + +```typescript +return this.service.batchImportStudents(+id, dto.users); +``` + +### 3.4 前端 + +**文件:** `apps/admin/src/pages/IntegrationConfig/index.tsx` + +`handleJoinClass` 和 `handleCreateClass`:从 `checkedKeys` 反查 `orgTree` 提取完整用户信息,不再只传 ID。 + +```typescript +// 旧 +const userIds = checkedKeys.filter(...).map(k => k.replace('user-', '')); +api.post('/classes/.../import', { dingUserIds: userIds }); + +// 新 +const checkedUsers = extractCheckedUsers(checkedKeys, orgTree); +api.post('/classes/.../import', { users: checkedUsers }); +``` + +`extractCheckedUsers` 工具函数遍历 `orgTree`,匹配 `checkedKeys` 中的 user 节点,返回 `{ dingUserId, name, mobile }[]`。 + +## 4. 不改的 + +- `DingTalkService.syncOneUser` / `syncAll` — 不受影响 +- `Student` entity — 字段不变 +- 数据库 schema — 不变 +- `StudentDingMapping` — 不受影响 + +## 5. 验收标准 + +1. 导入新用户后,Student.name 为真实姓名,非 `dd_xxx` +2. 有手机号的用户,Student.phone = 手机号 +3. 无手机号的用户,Student.phone = `dt_` +4. 已存在 mapping 的用户跳过,不重复创建 +5. 班级创建时传入 `users` 同样生效 +6. `tsc --noEmit` 编译通过