From bf03bc30af09da6794cf6f64f18a38425e542d0d Mon Sep 17 00:00:00 2001 From: En Date: Mon, 23 Mar 2026 23:54:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=99=E5=B8=88=E6=89=8B=E6=9C=BA?= =?UTF-8?q?=E5=8F=B7=E5=94=AF=E4=B8=80=E6=80=A7=E6=A0=A1=E9=AA=8C=20&=20?= =?UTF-8?q?=E7=8F=AD=E7=BA=A7=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 教师手机号唯一性校验 (TeacherServiceImpl) - 添加 checkPhoneUnique 私有方法 - createTeacher 和 updateTeacher 方法中调用校验 - 租户隔离,编辑时排除当前教师 2. 班级相关功能优化 - SchoolClassController: 班级管理接口 - ClassService/ClassServiceImpl: 班级服务层 - ClassCreateRequest/ClassUpdateRequest: 请求 DTO 优化 - 新增班级教师相关 DTO: AddClassTeacherRequest, ClassTeacherUpdateRequest, UpdateClassTeacherRequest - 新增 ClassTeacherRole 枚举 3. 前端适配 - school.ts API 更新 - ClassListView.vue 班级列表页面优化 - ParentListView.vue 家长列表页面优化 Co-Authored-By: Claude Opus 4.6 --- reading-platform-frontend/src/api/school.ts | 2 - .../views/school/classes/ClassListView.vue | 70 +++++++++-- .../views/school/parents/ParentListView.vue | 2 +- .../common/enums/ClassTeacherRole.java | 53 ++++++++ .../school/SchoolClassController.java | 116 ++++++++++++------ .../dto/request/AddClassTeacherRequest.java | 23 ++++ .../dto/request/ClassCreateRequest.java | 3 + .../request/ClassTeacherUpdateRequest.java | 19 +++ .../dto/request/ClassUpdateRequest.java | 3 + .../request/UpdateClassTeacherRequest.java | 18 +++ .../platform/service/ClassService.java | 7 +- .../service/impl/TeacherServiceImpl.java | 35 ++++++ 12 files changed, 299 insertions(+), 52 deletions(-) create mode 100644 reading-platform-java/src/main/java/com/reading/platform/common/enums/ClassTeacherRole.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/AddClassTeacherRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassTeacherUpdateRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/UpdateClassTeacherRequest.java diff --git a/reading-platform-frontend/src/api/school.ts b/reading-platform-frontend/src/api/school.ts index e5b00ea..02b1503 100644 --- a/reading-platform-frontend/src/api/school.ts +++ b/reading-platform-frontend/src/api/school.ts @@ -88,12 +88,10 @@ export interface ClassTeacher { export interface AddClassTeacherDto { teacherId: number; role: 'MAIN' | 'ASSIST' | 'CARE'; - isPrimary?: boolean; } export interface UpdateClassTeacherDto { role?: 'MAIN' | 'ASSIST' | 'CARE'; - isPrimary?: boolean; } export interface TransferStudentDto { diff --git a/reading-platform-frontend/src/views/school/classes/ClassListView.vue b/reading-platform-frontend/src/views/school/classes/ClassListView.vue index 01d6191..a8fbbae 100644 --- a/reading-platform-frontend/src/views/school/classes/ClassListView.vue +++ b/reading-platform-frontend/src/views/school/classes/ClassListView.vue @@ -257,7 +257,6 @@ 配班 保育员 - 班主任 添加 @@ -269,18 +268,20 @@
{{ teacher.teacherName }} - {{ getRoleLabel(teacher.role) }} + 班主任 +
+
+ 主班 配班 保育员 - 班主任 + + 移除 +
- - 移除 -
@@ -494,10 +495,20 @@ const handleEdit = (record: ClassInfo) => { formState.id = record.id; formState.name = record.name; formState.grade = record.grade; - formState.teacherId = record.teacherId || null; + // 从 teachers 数组中提取班主任 ID + formState.teacherId = getPrimaryTeacherId(record.teachers) || null; modalVisible.value = true; }; +// 从教师数组中获取班主任 ID +const getPrimaryTeacherId = (teachers?: ClassTeacher[]): number | undefined => { + if (!teachers || teachers.length === 0) { + return undefined; + } + const primaryTeacher = teachers.find(t => t.isPrimary || t.role === 'MAIN'); + return primaryTeacher?.teacherId; +}; + const handleModalOk = async () => { try { await formRef.value?.validate(); @@ -583,11 +594,9 @@ const handleStudentsTableChange = (pag: any) => { const teacherFormState = reactive<{ teacherId: number | null; role: 'MAIN' | 'ASSIST' | 'CARE'; - isPrimary: boolean; }>({ teacherId: null, role: 'MAIN', - isPrimary: false, }); const editingTeacherId = ref(null); @@ -611,7 +620,6 @@ const loadClassTeachers = async (classId: number) => { const resetTeacherForm = () => { teacherFormState.teacherId = null; teacherFormState.role = 'MAIN'; - teacherFormState.isPrimary = false; editingTeacherId.value = null; }; @@ -626,7 +634,6 @@ const handleAddTeacher = async () => { const dto: AddClassTeacherDto = { teacherId: teacherFormState.teacherId, role: teacherFormState.role, - isPrimary: teacherFormState.isPrimary, }; await addClassTeacher(currentClass.value!.id, dto); message.success('添加成功'); @@ -644,7 +651,6 @@ const handleUpdateTeacherRole = async (teacher: ClassTeacher) => { try { await updateClassTeacher(currentClass.value!.id, teacher.teacherId, { role: teacher.role, - isPrimary: teacher.isPrimary, }); message.success('更新成功'); loadClassTeachers(currentClass.value!.id); @@ -1265,6 +1271,7 @@ onMounted(() => { .teachers-list .teacher-info { display: flex; align-items: center; + gap: 8px; } .teachers-list .teacher-name { @@ -1272,6 +1279,43 @@ onMounted(() => { color: #2D3436; } +.teacher-role-badge { + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; +} + +.teacher-role-badge.role-main { + background: #E3F2FD; + color: #1976D2; +} + +.teacher-role-badge.role-assist { + background: #E8F5E9; + color: #388E3C; +} + +.teacher-role-badge.role-care { + background: #FFF3E0; + color: #F57C00; +} + +.principal-badge { + padding: 2px 6px; + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + color: white; + border-radius: 4px; + font-size: 11px; + font-weight: 500; +} + +.teacher-actions { + display: flex; + align-items: center; + gap: 8px; +} + .empty-teachers { text-align: center; padding: 40px; diff --git a/reading-platform-frontend/src/views/school/parents/ParentListView.vue b/reading-platform-frontend/src/views/school/parents/ParentListView.vue index 6809838..c3c541b 100644 --- a/reading-platform-frontend/src/views/school/parents/ParentListView.vue +++ b/reading-platform-frontend/src/views/school/parents/ParentListView.vue @@ -335,7 +335,7 @@ import type { Parent, CreateParentDto, ParentChild, Student } from '@/api/school // 状态辅助函数 const getParentStatusText = (status: string): string => { - if (status === 'ACTIVE') return '活跃'; + if (status === 'ACTIVE') return '启用'; if (status === 'INACTIVE') return '停用'; return translateGenericStatus(status); }; diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/enums/ClassTeacherRole.java b/reading-platform-java/src/main/java/com/reading/platform/common/enums/ClassTeacherRole.java new file mode 100644 index 0000000..88b5fd5 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/common/enums/ClassTeacherRole.java @@ -0,0 +1,53 @@ +package com.reading.platform.common.enums; + +import lombok.Getter; + +/** + * 班级教师角色枚举 + */ +@Getter +public enum ClassTeacherRole { + + /** + * 主班/班主任 + */ + MAIN("MAIN", "主班"), + + /** + * 配班 + */ + ASSIST("ASSIST", "配班"), + + /** + * 保育员 + */ + CARE("CARE", "保育员"); + + private final String code; + private final String description; + + ClassTeacherRole(String code, String description) { + this.code = code; + this.description = description; + } + + public static ClassTeacherRole fromCode(String code) { + if (code == null) { + return null; + } + for (ClassTeacherRole role : values()) { + if (role.getCode().equalsIgnoreCase(code)) { + return role; + } + } + return null; + } + + /** + * 判断是否为主班/班主任角色 + */ + public static boolean isPrimaryRole(String code) { + return MAIN.getCode().equalsIgnoreCase(code); + } + +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolClassController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolClassController.java index 788eb76..e105aba 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolClassController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolClassController.java @@ -3,6 +3,7 @@ package com.reading.platform.controller.school; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.reading.platform.common.annotation.Log; +import com.reading.platform.common.enums.ClassTeacherRole; import com.reading.platform.common.enums.LogModule; import com.reading.platform.common.enums.LogOperationType; import com.reading.platform.common.mapper.ClassMapper; @@ -11,6 +12,8 @@ import com.reading.platform.common.response.Result; import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.dto.request.ClassCreateRequest; import com.reading.platform.dto.request.ClassUpdateRequest; +import com.reading.platform.dto.request.AddClassTeacherRequest; +import com.reading.platform.dto.request.UpdateClassTeacherRequest; import com.reading.platform.common.mapper.StudentMapper; import com.reading.platform.dto.response.ClassResponse; import com.reading.platform.dto.response.ClassTeacherResponse; @@ -29,12 +32,14 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; @Tag(name = "School - Class", description = "Class Management APIs for School") +@Slf4j @RestController @RequestMapping("/api/v1/school/classes") @RequiredArgsConstructor @@ -71,7 +76,14 @@ public class SchoolClassController { public Result getClass(@PathVariable Long id) { Long tenantId = SecurityUtils.getCurrentTenantId(); Clazz clazz = classService.getClassByIdWithTenantCheck(id, tenantId); - return Result.success(classMapper.toVO(clazz)); + ClassResponse vo = classMapper.toVO(clazz); + + // 填充教师列表 + List classTeachers = classTeacherMapper.selectList( + new LambdaQueryWrapper().eq(ClassTeacher::getClassId, id)); + vo.setTeachers(buildClassTeacherResponseList(classTeachers)); + + return Result.success(vo); } @Operation(summary = "Get class page") @@ -97,24 +109,32 @@ public class SchoolClassController { List classTeachers = classTeacherMapper.selectList( new LambdaQueryWrapper().eq(ClassTeacher::getClassId, vo.getId())); - List teacherList = new ArrayList<>(); - for (ClassTeacher ct : classTeachers) { - Teacher t = teacherService.findTeacherById(ct.getTeacherId()); - teacherList.add(ClassTeacherResponse.builder() - .id(ct.getId()) - .classId(ct.getClassId()) - .teacherId(ct.getTeacherId()) - .role(ct.getRole()) - .teacherName(t != null ? t.getName() : null) - .isPrimary("班主任".equals(ct.getRole()) || "主班".equals(ct.getRole())) - .createdAt(ct.getCreatedAt()) - .build()); - } + List teacherList = buildClassTeacherResponseList(classTeachers); vo.setTeachers(teacherList); } return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); } + /** + * 构建班级教师响应列表 + */ + private List buildClassTeacherResponseList(List classTeachers) { + List teacherList = new ArrayList<>(); + for (ClassTeacher ct : classTeachers) { + Teacher t = teacherService.findTeacherById(ct.getTeacherId()); + teacherList.add(ClassTeacherResponse.builder() + .id(ct.getId()) + .classId(ct.getClassId()) + .teacherId(ct.getTeacherId()) + .role(ct.getRole()) + .teacherName(t != null ? t.getName() : null) + .isPrimary(ClassTeacherRole.isPrimaryRole(ct.getRole())) + .createdAt(ct.getCreatedAt()) + .build()); + } + return teacherList; + } + @Operation(summary = "Delete class") @Log(module = LogModule.CLASS, type = LogOperationType.DELETE, description = "删除班级") @DeleteMapping("/{id}") @@ -124,12 +144,44 @@ public class SchoolClassController { return Result.success(); } - @Operation(summary = "Assign teachers to class") - @Log(module = LogModule.CLASS, type = LogOperationType.UPDATE, description = "分配教师到班级") + @Operation(summary = "Add teacher to class") + @Log(module = LogModule.CLASS, type = LogOperationType.CREATE, description = "添加教师到班级") @PostMapping("/{id}/teachers") - public Result assignTeachers(@PathVariable Long id, @RequestBody List teacherIds) { + public Result addClassTeacher(@PathVariable Long id, @Valid @RequestBody AddClassTeacherRequest request) { Long tenantId = SecurityUtils.getCurrentTenantId(); - classService.assignTeachersWithTenantCheck(id, tenantId, teacherIds); + classService.getClassByIdWithTenantCheck(id, tenantId); + + // 如果设置为新主班,先将原主班降级为配班 + if (ClassTeacherRole.isPrimaryRole(request.getRole())) { + List allTeachers = classTeacherMapper.selectList( + new LambdaQueryWrapper().eq(ClassTeacher::getClassId, id)); + for (ClassTeacher ct : allTeachers) { + if (ClassTeacherRole.isPrimaryRole(ct.getRole())) { + ct.setRole(ClassTeacherRole.ASSIST.getCode()); + classTeacherMapper.updateById(ct); + log.info("班级 {} 的原主班教师 {} 已降级为配班", id, ct.getTeacherId()); + } + } + } + + // 查询是否已存在关联 + ClassTeacher existing = classTeacherMapper.selectOne(new LambdaQueryWrapper() + .eq(ClassTeacher::getClassId, id) + .eq(ClassTeacher::getTeacherId, request.getTeacherId())); + + if (existing != null) { + // 更新现有角色 + existing.setRole(request.getRole()); + classTeacherMapper.updateById(existing); + } else { + // 创建新关联 + ClassTeacher classTeacher = new ClassTeacher(); + classTeacher.setClassId(id); + classTeacher.setTeacherId(request.getTeacherId()); + classTeacher.setRole(request.getRole()); + classTeacherMapper.insert(classTeacher); + } + return Result.success(); } @@ -163,20 +215,7 @@ public class SchoolClassController { classService.getClassByIdWithTenantCheck(id, tenantId); List classTeachers = classTeacherMapper.selectList( new LambdaQueryWrapper().eq(ClassTeacher::getClassId, id)); - List teacherList = new ArrayList<>(); - for (ClassTeacher ct : classTeachers) { - Teacher t = teacherService.findTeacherById(ct.getTeacherId()); - teacherList.add(ClassTeacherResponse.builder() - .id(ct.getId()) - .classId(ct.getClassId()) - .teacherId(ct.getTeacherId()) - .role(ct.getRole()) - .teacherName(t != null ? t.getName() : null) - .isPrimary("班主任".equals(ct.getRole()) || "主班".equals(ct.getRole())) - .createdAt(ct.getCreatedAt()) - .build()); - } - return Result.success(teacherList); + return Result.success(buildClassTeacherResponseList(classTeachers)); } @Operation(summary = "Update class teacher role") @@ -185,11 +224,13 @@ public class SchoolClassController { public Result updateClassTeacher( @PathVariable Long id, @PathVariable Long teacherId, - @RequestBody Object request) { + @Valid @RequestBody UpdateClassTeacherRequest request) { // 验证班级属于当前租户 Long tenantId = SecurityUtils.getCurrentTenantId(); classService.getClassByIdWithTenantCheck(id, tenantId); - // TODO: 实现更新班级教师 + + // 调用 Service 方法更新 + classService.updateClassTeacherRole(id, teacherId, request.getRole(), null); return Result.success(); } @@ -202,7 +243,12 @@ public class SchoolClassController { // 验证班级属于当前租户 Long tenantId = SecurityUtils.getCurrentTenantId(); classService.getClassByIdWithTenantCheck(id, tenantId); - // TODO: 实现移除班级教师 + + // 删除班级教师关联记录 + classTeacherMapper.delete(new LambdaQueryWrapper() + .eq(ClassTeacher::getClassId, id) + .eq(ClassTeacher::getTeacherId, teacherId)); + return Result.success(); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/AddClassTeacherRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/AddClassTeacherRequest.java new file mode 100644 index 0000000..8dcf930 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/AddClassTeacherRequest.java @@ -0,0 +1,23 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 添加教师到班级请求 + */ +@Data +@Schema(description = "添加教师到班级请求") +public class AddClassTeacherRequest { + + @NotNull(message = "教师 ID 不能为空") + @Schema(description = "教师 ID", required = true, example = "1") + private Long teacherId; + + @NotBlank(message = "角色不能为空") + @Schema(description = "角色:MAIN(主班教师)|ASSIST(配班教师)|CARE(保育员)", required = true, + example = "MAIN", allowableValues = {"MAIN", "ASSIST", "CARE"}) + private String role; +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassCreateRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassCreateRequest.java index 6004774..d813679 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassCreateRequest.java +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassCreateRequest.java @@ -21,4 +21,7 @@ public class ClassCreateRequest { @Schema(description = "容量") private Integer capacity; + @Schema(description = "班主任教师 ID") + private Long teacherId; + } diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassTeacherUpdateRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassTeacherUpdateRequest.java new file mode 100644 index 0000000..84856e8 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassTeacherUpdateRequest.java @@ -0,0 +1,19 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 班级教师角色更新请求 + */ +@Data +@Schema(description = "班级教师角色更新请求") +public class ClassTeacherUpdateRequest { + + @Schema(description = "角色:MAIN-主班,ASSIST-配班,CARE-保育员") + private String role; + + @Schema(description = "是否班主任") + private Boolean isPrimary; + +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassUpdateRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassUpdateRequest.java index 3fd2f3b..7e23e24 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassUpdateRequest.java +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ClassUpdateRequest.java @@ -22,4 +22,7 @@ public class ClassUpdateRequest { @Schema(description = "状态") private String status; + @Schema(description = "班主任教师 ID") + private Long teacherId; + } diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/UpdateClassTeacherRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/UpdateClassTeacherRequest.java new file mode 100644 index 0000000..5e0465f --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/UpdateClassTeacherRequest.java @@ -0,0 +1,18 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 更新班级教师角色请求 + */ +@Data +@Schema(description = "更新班级教师角色请求") +public class UpdateClassTeacherRequest { + + @NotBlank(message = "角色不能为空") + @Schema(description = "角色:MAIN(主班教师)|ASSIST(配班教师)|CARE(保育员)", required = true, + example = "MAIN", allowableValues = {"MAIN", "ASSIST", "CARE"}) + private String role; +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/ClassService.java b/reading-platform-java/src/main/java/com/reading/platform/service/ClassService.java index fcc96d8..2766a2f 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/ClassService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/ClassService.java @@ -84,10 +84,15 @@ public interface ClassService extends com.baomidou.mybatisplus.extension.service List getTeacherIdsByClassId(Long classId); /** - * 获取教师在班级中的角色信息(role 字段) + * 获取学生在班级中的角色信息(role 字段) */ String getTeacherRoleInClass(Long classId, Long teacherId); + /** + * 更新班级教师角色 + */ + void updateClassTeacherRole(Long classId, Long teacherId, String role, Boolean isPrimary); + /** * 获取租户下活跃班级列表 */ diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/TeacherServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/TeacherServiceImpl.java index 25f133f..56bee76 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/TeacherServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/TeacherServiceImpl.java @@ -61,6 +61,9 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi throw new BusinessException(ErrorCode.DATA_ALREADY_EXISTS, "用户名已存在"); } + // 检查手机号是否已存在 + checkPhoneUnique(tenantId, request.getPhone(), null); + Teacher teacher = new Teacher(); teacher.setTenantId(tenantId); teacher.setUsername(request.getUsername()); @@ -97,6 +100,9 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi Teacher teacher = getTeacherById(id); + // 检查手机号是否已存在(排除当前教师) + checkPhoneUnique(teacher.getTenantId(), request.getPhone(), teacher.getId()); + if (StringUtils.hasText(request.getName())) { teacher.setName(request.getName()); } @@ -326,4 +332,33 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi response.setLessonCount((int) lessonCount); } + /** + * 检查手机号唯一性 + * + * @param tenantId 租户 ID + * @param phone 手机号 + * @param excludeTeacherId 排除的教师 ID(编辑时使用,新增时为 null) + * @throws BusinessException 手机号已存在时抛出 + */ + private void checkPhoneUnique(Long tenantId, String phone, Long excludeTeacherId) { + if (!StringUtils.hasText(phone)) { + return; // 手机号为空时不校验(由 @NotBlank 校验) + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Teacher::getTenantId, tenantId) + .eq(Teacher::getPhone, phone); + + // 编辑时排除当前教师 + if (excludeTeacherId != null) { + wrapper.ne(Teacher::getId, excludeTeacherId); + } + + Teacher existing = teacherMapper.selectOne(wrapper); + if (existing != null) { + log.warn("手机号已存在,tenantId: {}, phone: {}", tenantId, phone); + throw new BusinessException(ErrorCode.DATA_ALREADY_EXISTS, "手机号已存在"); + } + } + }