feat: 教师手机号唯一性校验 & 班级相关功能优化
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 <noreply@anthropic.com>
This commit is contained in:
parent
c436f9f384
commit
bf03bc30af
@ -88,12 +88,10 @@ export interface ClassTeacher {
|
|||||||
export interface AddClassTeacherDto {
|
export interface AddClassTeacherDto {
|
||||||
teacherId: number;
|
teacherId: number;
|
||||||
role: 'MAIN' | 'ASSIST' | 'CARE';
|
role: 'MAIN' | 'ASSIST' | 'CARE';
|
||||||
isPrimary?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateClassTeacherDto {
|
export interface UpdateClassTeacherDto {
|
||||||
role?: 'MAIN' | 'ASSIST' | 'CARE';
|
role?: 'MAIN' | 'ASSIST' | 'CARE';
|
||||||
isPrimary?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransferStudentDto {
|
export interface TransferStudentDto {
|
||||||
|
|||||||
@ -257,7 +257,6 @@
|
|||||||
<a-select-option value="ASSIST">配班</a-select-option>
|
<a-select-option value="ASSIST">配班</a-select-option>
|
||||||
<a-select-option value="CARE">保育员</a-select-option>
|
<a-select-option value="CARE">保育员</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-checkbox v-model:checked="teacherFormState.isPrimary">班主任</a-checkbox>
|
|
||||||
<a-button type="primary" size="small" :loading="teachersSubmitting" @click="handleAddTeacher">
|
<a-button type="primary" size="small" :loading="teachersSubmitting" @click="handleAddTeacher">
|
||||||
添加
|
添加
|
||||||
</a-button>
|
</a-button>
|
||||||
@ -269,18 +268,20 @@
|
|||||||
<div v-for="teacher in classTeachers" :key="teacher.teacherId" class="teacher-item">
|
<div v-for="teacher in classTeachers" :key="teacher.teacherId" class="teacher-item">
|
||||||
<div class="teacher-info">
|
<div class="teacher-info">
|
||||||
<span class="teacher-name">{{ teacher.teacherName }}</span>
|
<span class="teacher-name">{{ teacher.teacherName }}</span>
|
||||||
<a-select v-model:value="teacher.role" size="small" style="width: 80px; margin-left: 8px;"
|
<span class="teacher-role-badge" :class="'role-' + teacher.role.toLowerCase()">{{ getRoleLabel(teacher.role) }}</span>
|
||||||
|
<span v-if="teacher.role === 'MAIN'" class="principal-badge">班主任</span>
|
||||||
|
</div>
|
||||||
|
<div class="teacher-actions">
|
||||||
|
<a-select v-model:value="teacher.role" size="small" style="width: 80px;"
|
||||||
@change="handleUpdateTeacherRole(teacher)">
|
@change="handleUpdateTeacherRole(teacher)">
|
||||||
<a-select-option value="MAIN">主班</a-select-option>
|
<a-select-option value="MAIN">主班</a-select-option>
|
||||||
<a-select-option value="ASSIST">配班</a-select-option>
|
<a-select-option value="ASSIST">配班</a-select-option>
|
||||||
<a-select-option value="CARE">保育员</a-select-option>
|
<a-select-option value="CARE">保育员</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-checkbox v-model:checked="teacher.isPrimary"
|
<a-button type="link" danger size="small" @click="handleRemoveTeacher(teacher.teacherId)">
|
||||||
@change="handleUpdateTeacherRole(teacher)">班主任</a-checkbox>
|
移除
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
<a-button type="link" danger size="small" @click="handleRemoveTeacher(teacher.teacherId)">
|
|
||||||
移除
|
|
||||||
</a-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -494,10 +495,20 @@ const handleEdit = (record: ClassInfo) => {
|
|||||||
formState.id = record.id;
|
formState.id = record.id;
|
||||||
formState.name = record.name;
|
formState.name = record.name;
|
||||||
formState.grade = record.grade;
|
formState.grade = record.grade;
|
||||||
formState.teacherId = record.teacherId || null;
|
// 从 teachers 数组中提取班主任 ID
|
||||||
|
formState.teacherId = getPrimaryTeacherId(record.teachers) || null;
|
||||||
modalVisible.value = true;
|
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 () => {
|
const handleModalOk = async () => {
|
||||||
try {
|
try {
|
||||||
await formRef.value?.validate();
|
await formRef.value?.validate();
|
||||||
@ -583,11 +594,9 @@ const handleStudentsTableChange = (pag: any) => {
|
|||||||
const teacherFormState = reactive<{
|
const teacherFormState = reactive<{
|
||||||
teacherId: number | null;
|
teacherId: number | null;
|
||||||
role: 'MAIN' | 'ASSIST' | 'CARE';
|
role: 'MAIN' | 'ASSIST' | 'CARE';
|
||||||
isPrimary: boolean;
|
|
||||||
}>({
|
}>({
|
||||||
teacherId: null,
|
teacherId: null,
|
||||||
role: 'MAIN',
|
role: 'MAIN',
|
||||||
isPrimary: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const editingTeacherId = ref<number | null>(null);
|
const editingTeacherId = ref<number | null>(null);
|
||||||
@ -611,7 +620,6 @@ const loadClassTeachers = async (classId: number) => {
|
|||||||
const resetTeacherForm = () => {
|
const resetTeacherForm = () => {
|
||||||
teacherFormState.teacherId = null;
|
teacherFormState.teacherId = null;
|
||||||
teacherFormState.role = 'MAIN';
|
teacherFormState.role = 'MAIN';
|
||||||
teacherFormState.isPrimary = false;
|
|
||||||
editingTeacherId.value = null;
|
editingTeacherId.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -626,7 +634,6 @@ const handleAddTeacher = async () => {
|
|||||||
const dto: AddClassTeacherDto = {
|
const dto: AddClassTeacherDto = {
|
||||||
teacherId: teacherFormState.teacherId,
|
teacherId: teacherFormState.teacherId,
|
||||||
role: teacherFormState.role,
|
role: teacherFormState.role,
|
||||||
isPrimary: teacherFormState.isPrimary,
|
|
||||||
};
|
};
|
||||||
await addClassTeacher(currentClass.value!.id, dto);
|
await addClassTeacher(currentClass.value!.id, dto);
|
||||||
message.success('添加成功');
|
message.success('添加成功');
|
||||||
@ -644,7 +651,6 @@ const handleUpdateTeacherRole = async (teacher: ClassTeacher) => {
|
|||||||
try {
|
try {
|
||||||
await updateClassTeacher(currentClass.value!.id, teacher.teacherId, {
|
await updateClassTeacher(currentClass.value!.id, teacher.teacherId, {
|
||||||
role: teacher.role,
|
role: teacher.role,
|
||||||
isPrimary: teacher.isPrimary,
|
|
||||||
});
|
});
|
||||||
message.success('更新成功');
|
message.success('更新成功');
|
||||||
loadClassTeachers(currentClass.value!.id);
|
loadClassTeachers(currentClass.value!.id);
|
||||||
@ -1265,6 +1271,7 @@ onMounted(() => {
|
|||||||
.teachers-list .teacher-info {
|
.teachers-list .teacher-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.teachers-list .teacher-name {
|
.teachers-list .teacher-name {
|
||||||
@ -1272,6 +1279,43 @@ onMounted(() => {
|
|||||||
color: #2D3436;
|
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 {
|
.empty-teachers {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
|
|||||||
@ -335,7 +335,7 @@ import type { Parent, CreateParentDto, ParentChild, Student } from '@/api/school
|
|||||||
|
|
||||||
// 状态辅助函数
|
// 状态辅助函数
|
||||||
const getParentStatusText = (status: string): string => {
|
const getParentStatusText = (status: string): string => {
|
||||||
if (status === 'ACTIVE') return '活跃';
|
if (status === 'ACTIVE') return '启用';
|
||||||
if (status === 'INACTIVE') return '停用';
|
if (status === 'INACTIVE') return '停用';
|
||||||
return translateGenericStatus(status);
|
return translateGenericStatus(status);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package com.reading.platform.controller.school;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.reading.platform.common.annotation.Log;
|
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.LogModule;
|
||||||
import com.reading.platform.common.enums.LogOperationType;
|
import com.reading.platform.common.enums.LogOperationType;
|
||||||
import com.reading.platform.common.mapper.ClassMapper;
|
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.common.security.SecurityUtils;
|
||||||
import com.reading.platform.dto.request.ClassCreateRequest;
|
import com.reading.platform.dto.request.ClassCreateRequest;
|
||||||
import com.reading.platform.dto.request.ClassUpdateRequest;
|
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.common.mapper.StudentMapper;
|
||||||
import com.reading.platform.dto.response.ClassResponse;
|
import com.reading.platform.dto.response.ClassResponse;
|
||||||
import com.reading.platform.dto.response.ClassTeacherResponse;
|
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 io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Tag(name = "School - Class", description = "Class Management APIs for School")
|
@Tag(name = "School - Class", description = "Class Management APIs for School")
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/school/classes")
|
@RequestMapping("/api/v1/school/classes")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -71,7 +76,14 @@ public class SchoolClassController {
|
|||||||
public Result<ClassResponse> getClass(@PathVariable Long id) {
|
public Result<ClassResponse> getClass(@PathVariable Long id) {
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
Clazz clazz = classService.getClassByIdWithTenantCheck(id, tenantId);
|
Clazz clazz = classService.getClassByIdWithTenantCheck(id, tenantId);
|
||||||
return Result.success(classMapper.toVO(clazz));
|
ClassResponse vo = classMapper.toVO(clazz);
|
||||||
|
|
||||||
|
// 填充教师列表
|
||||||
|
List<ClassTeacher> classTeachers = classTeacherMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getClassId, id));
|
||||||
|
vo.setTeachers(buildClassTeacherResponseList(classTeachers));
|
||||||
|
|
||||||
|
return Result.success(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "Get class page")
|
@Operation(summary = "Get class page")
|
||||||
@ -97,24 +109,32 @@ public class SchoolClassController {
|
|||||||
|
|
||||||
List<ClassTeacher> classTeachers = classTeacherMapper.selectList(
|
List<ClassTeacher> classTeachers = classTeacherMapper.selectList(
|
||||||
new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getClassId, vo.getId()));
|
new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getClassId, vo.getId()));
|
||||||
List<ClassTeacherResponse> teacherList = new ArrayList<>();
|
List<ClassTeacherResponse> teacherList = buildClassTeacherResponseList(classTeachers);
|
||||||
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());
|
|
||||||
}
|
|
||||||
vo.setTeachers(teacherList);
|
vo.setTeachers(teacherList);
|
||||||
}
|
}
|
||||||
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建班级教师响应列表
|
||||||
|
*/
|
||||||
|
private List<ClassTeacherResponse> buildClassTeacherResponseList(List<ClassTeacher> classTeachers) {
|
||||||
|
List<ClassTeacherResponse> 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")
|
@Operation(summary = "Delete class")
|
||||||
@Log(module = LogModule.CLASS, type = LogOperationType.DELETE, description = "删除班级")
|
@Log(module = LogModule.CLASS, type = LogOperationType.DELETE, description = "删除班级")
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@ -124,12 +144,44 @@ public class SchoolClassController {
|
|||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "Assign teachers to class")
|
@Operation(summary = "Add teacher to class")
|
||||||
@Log(module = LogModule.CLASS, type = LogOperationType.UPDATE, description = "分配教师到班级")
|
@Log(module = LogModule.CLASS, type = LogOperationType.CREATE, description = "添加教师到班级")
|
||||||
@PostMapping("/{id}/teachers")
|
@PostMapping("/{id}/teachers")
|
||||||
public Result<Void> assignTeachers(@PathVariable Long id, @RequestBody List<Long> teacherIds) {
|
public Result<Void> addClassTeacher(@PathVariable Long id, @Valid @RequestBody AddClassTeacherRequest request) {
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
classService.assignTeachersWithTenantCheck(id, tenantId, teacherIds);
|
classService.getClassByIdWithTenantCheck(id, tenantId);
|
||||||
|
|
||||||
|
// 如果设置为新主班,先将原主班降级为配班
|
||||||
|
if (ClassTeacherRole.isPrimaryRole(request.getRole())) {
|
||||||
|
List<ClassTeacher> allTeachers = classTeacherMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<ClassTeacher>().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<ClassTeacher>()
|
||||||
|
.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();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,20 +215,7 @@ public class SchoolClassController {
|
|||||||
classService.getClassByIdWithTenantCheck(id, tenantId);
|
classService.getClassByIdWithTenantCheck(id, tenantId);
|
||||||
List<ClassTeacher> classTeachers = classTeacherMapper.selectList(
|
List<ClassTeacher> classTeachers = classTeacherMapper.selectList(
|
||||||
new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getClassId, id));
|
new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getClassId, id));
|
||||||
List<ClassTeacherResponse> teacherList = new ArrayList<>();
|
return Result.success(buildClassTeacherResponseList(classTeachers));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "Update class teacher role")
|
@Operation(summary = "Update class teacher role")
|
||||||
@ -185,11 +224,13 @@ public class SchoolClassController {
|
|||||||
public Result<Void> updateClassTeacher(
|
public Result<Void> updateClassTeacher(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@PathVariable Long teacherId,
|
@PathVariable Long teacherId,
|
||||||
@RequestBody Object request) {
|
@Valid @RequestBody UpdateClassTeacherRequest request) {
|
||||||
// 验证班级属于当前租户
|
// 验证班级属于当前租户
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
classService.getClassByIdWithTenantCheck(id, tenantId);
|
classService.getClassByIdWithTenantCheck(id, tenantId);
|
||||||
// TODO: 实现更新班级教师
|
|
||||||
|
// 调用 Service 方法更新
|
||||||
|
classService.updateClassTeacherRole(id, teacherId, request.getRole(), null);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +243,12 @@ public class SchoolClassController {
|
|||||||
// 验证班级属于当前租户
|
// 验证班级属于当前租户
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
classService.getClassByIdWithTenantCheck(id, tenantId);
|
classService.getClassByIdWithTenantCheck(id, tenantId);
|
||||||
// TODO: 实现移除班级教师
|
|
||||||
|
// 删除班级教师关联记录
|
||||||
|
classTeacherMapper.delete(new LambdaQueryWrapper<ClassTeacher>()
|
||||||
|
.eq(ClassTeacher::getClassId, id)
|
||||||
|
.eq(ClassTeacher::getTeacherId, teacherId));
|
||||||
|
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
@ -21,4 +21,7 @@ public class ClassCreateRequest {
|
|||||||
@Schema(description = "容量")
|
@Schema(description = "容量")
|
||||||
private Integer capacity;
|
private Integer capacity;
|
||||||
|
|
||||||
|
@Schema(description = "班主任教师 ID")
|
||||||
|
private Long teacherId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -22,4 +22,7 @@ public class ClassUpdateRequest {
|
|||||||
@Schema(description = "状态")
|
@Schema(description = "状态")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "班主任教师 ID")
|
||||||
|
private Long teacherId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
@ -84,10 +84,15 @@ public interface ClassService extends com.baomidou.mybatisplus.extension.service
|
|||||||
List<Long> getTeacherIdsByClassId(Long classId);
|
List<Long> getTeacherIdsByClassId(Long classId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取教师在班级中的角色信息(role 字段)
|
* 获取学生在班级中的角色信息(role 字段)
|
||||||
*/
|
*/
|
||||||
String getTeacherRoleInClass(Long classId, Long teacherId);
|
String getTeacherRoleInClass(Long classId, Long teacherId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新班级教师角色
|
||||||
|
*/
|
||||||
|
void updateClassTeacherRole(Long classId, Long teacherId, String role, Boolean isPrimary);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取租户下活跃班级列表
|
* 获取租户下活跃班级列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -61,6 +61,9 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
|
|||||||
throw new BusinessException(ErrorCode.DATA_ALREADY_EXISTS, "用户名已存在");
|
throw new BusinessException(ErrorCode.DATA_ALREADY_EXISTS, "用户名已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查手机号是否已存在
|
||||||
|
checkPhoneUnique(tenantId, request.getPhone(), null);
|
||||||
|
|
||||||
Teacher teacher = new Teacher();
|
Teacher teacher = new Teacher();
|
||||||
teacher.setTenantId(tenantId);
|
teacher.setTenantId(tenantId);
|
||||||
teacher.setUsername(request.getUsername());
|
teacher.setUsername(request.getUsername());
|
||||||
@ -97,6 +100,9 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
|
|||||||
|
|
||||||
Teacher teacher = getTeacherById(id);
|
Teacher teacher = getTeacherById(id);
|
||||||
|
|
||||||
|
// 检查手机号是否已存在(排除当前教师)
|
||||||
|
checkPhoneUnique(teacher.getTenantId(), request.getPhone(), teacher.getId());
|
||||||
|
|
||||||
if (StringUtils.hasText(request.getName())) {
|
if (StringUtils.hasText(request.getName())) {
|
||||||
teacher.setName(request.getName());
|
teacher.setName(request.getName());
|
||||||
}
|
}
|
||||||
@ -326,4 +332,33 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
|
|||||||
response.setLessonCount((int) lessonCount);
|
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<Teacher> wrapper = new LambdaQueryWrapper<Teacher>()
|
||||||
|
.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, "手机号已存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user