feat: 教师端排课优化

- 排课详情/课表:显示班级、课程、课程类型(使用 toSchedulePlanResponse 填充)
- 开始上课:改用 from-schedule API,避免 teacherId/title/lessonDate 校验失败
- 前端:TeacherSchedule 增加 lessonType/coursePackageName,课程展示兼容 coursePackageName

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-19 17:59:43 +08:00
parent 69d00d7650
commit 3f6696d7bb
4 changed files with 58 additions and 40 deletions

View File

@ -250,6 +250,11 @@ export function createLesson(data: CreateLessonDto): Promise<any> {
return http.post('/v1/teacher/lessons', data) as any; return http.post('/v1/teacher/lessons', data) as any;
} }
/** 从排课开始上课:创建课时并开始,适用于课表/排课详情的「开始上课」按钮 */
export function startLessonFromSchedule(schedulePlanId: number): Promise<{ id: number }> {
return http.post(`/v1/teacher/lessons/from-schedule/${schedulePlanId}/start`) as any;
}
// 开始上课id 使用 string 避免 Long 精度丢失) // 开始上课id 使用 string 避免 Long 精度丢失)
export function startLesson(id: number | string): Promise<any> { export function startLesson(id: number | string): Promise<any> {
return http.post(`/v1/teacher/lessons/${id}/start`) as any; return http.post(`/v1/teacher/lessons/${id}/start`) as any;
@ -551,9 +556,12 @@ export function getLessonProgress(lessonId: number | string): Promise<LessonProg
export interface TeacherSchedule { export interface TeacherSchedule {
id: number; id: number;
classId: number; classId: number;
className: string; className?: string;
courseId: number; courseId: number;
courseName: string; courseName?: string;
coursePackageName?: string; // 课程包名称,与 courseName 二选一展示
lessonType?: string; // 课程类型代码
lessonTypeName?: string; // 课程类型名称(后端已翻译)
teacherId?: number; teacherId?: number;
scheduledDate?: string; scheduledDate?: string;
scheduledTime?: string; scheduledTime?: string;

View File

@ -19,8 +19,11 @@
<template #title> <template #title>
<span>{{ schedule.scheduledTime || '待定' }}</span> <span>{{ schedule.scheduledTime || '待定' }}</span>
</template> </template>
<div class="course-name">{{ schedule.courseName }}</div> <div class="course-name">{{ schedule.courseName || schedule.coursePackageName || '-' }}</div>
<div class="class-name">{{ schedule.className }}</div> <div class="class-name">{{ schedule.className || '-' }}</div>
<a-tag v-if="schedule.lessonType" size="small" class="today-lesson-type" :style="getLessonTagStyle(schedule.lessonType)">
{{ getLessonTypeName(schedule.lessonType) }}
</a-tag>
<div class="card-actions"> <div class="card-actions">
<a-button <a-button
v-if="schedule.hasLesson && schedule.lessonStatus === 'PLANNED'" v-if="schedule.hasLesson && schedule.lessonStatus === 'PLANNED'"
@ -41,7 +44,7 @@
<a-button <a-button
v-else v-else
size="small" size="small"
@click="startLessonFromSchedule(schedule)" @click="handleStartLessonFromSchedule(schedule)"
> >
创建课堂 创建课堂
</a-button> </a-button>
@ -102,8 +105,11 @@
@click="showScheduleDetail(schedule)" @click="showScheduleDetail(schedule)"
> >
<div class="schedule-time">{{ schedule.scheduledTime || '待定' }}</div> <div class="schedule-time">{{ schedule.scheduledTime || '待定' }}</div>
<div class="schedule-course">{{ schedule.courseName }}</div> <div class="schedule-course">{{ schedule.courseName || schedule.coursePackageName || '-' }}</div>
<div class="schedule-class">{{ schedule.className }}</div> <div class="schedule-class">{{ schedule.className || '-' }}</div>
<a-tag v-if="schedule.lessonType" size="small" class="schedule-lesson-type" :style="getLessonTagStyle(schedule.lessonType)">
{{ getLessonTypeName(schedule.lessonType) }}
</a-tag>
<div class="schedule-source"> <div class="schedule-source">
<a-tag v-if="schedule.source === 'SCHOOL'" color="orange" size="small">学校排课</a-tag> <a-tag v-if="schedule.source === 'SCHOOL'" color="orange" size="small">学校排课</a-tag>
<a-tag v-else color="purple" size="small">自主预约</a-tag> <a-tag v-else color="purple" size="small">自主预约</a-tag>
@ -193,8 +199,14 @@
> >
<template v-if="selectedSchedule"> <template v-if="selectedSchedule">
<a-descriptions :column="1" bordered> <a-descriptions :column="1" bordered>
<a-descriptions-item label="班级">{{ selectedSchedule.className }}</a-descriptions-item> <a-descriptions-item label="班级">{{ selectedSchedule.className || '-' }}</a-descriptions-item>
<a-descriptions-item label="课程">{{ selectedSchedule.courseName }}</a-descriptions-item> <a-descriptions-item label="课程">{{ selectedSchedule.courseName || selectedSchedule.coursePackageName || '-' }}</a-descriptions-item>
<a-descriptions-item label="课程类型">
<a-tag v-if="selectedSchedule.lessonType" size="small" :style="getLessonTagStyle(selectedSchedule.lessonType)">
{{ getLessonTypeName(selectedSchedule.lessonType) }}
</a-tag>
<span v-else>-</span>
</a-descriptions-item>
<a-descriptions-item label="排课日期">{{ formatDate(selectedSchedule.scheduledDate) }}</a-descriptions-item> <a-descriptions-item label="排课日期">{{ formatDate(selectedSchedule.scheduledDate) }}</a-descriptions-item>
<a-descriptions-item label="时间段">{{ selectedSchedule.scheduledTime || '待定' }}</a-descriptions-item> <a-descriptions-item label="时间段">{{ selectedSchedule.scheduledTime || '待定' }}</a-descriptions-item>
<a-descriptions-item label="重复方式"> <a-descriptions-item label="重复方式">
@ -223,7 +235,7 @@
<a-button <a-button
v-else-if="selectedSchedule.status === 'ACTIVE'" v-else-if="selectedSchedule.status === 'ACTIVE'"
type="primary" type="primary"
@click="startLessonFromSchedule(selectedSchedule)" @click="handleStartLessonFromSchedule(selectedSchedule)"
> >
开始上课 开始上课
</a-button> </a-button>
@ -261,7 +273,8 @@ import {
type TeacherSchedule, type TeacherSchedule,
type CreateTeacherScheduleDto, type CreateTeacherScheduleDto,
} from '@/api/teacher'; } from '@/api/teacher';
import { getTeacherClasses, getTeacherCourses, createLesson } from '@/api/teacher'; import { getTeacherClasses, getTeacherCourses, startLessonFromSchedule } from '@/api/teacher';
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
const router = useRouter(); const router = useRouter();
@ -450,14 +463,10 @@ const handleCancelSchedule = async (id: number) => {
} }
}; };
// // teacherIdtitlelessonDate
const startLessonFromSchedule = async (schedule: TeacherSchedule) => { const handleStartLessonFromSchedule = async (schedule: TeacherSchedule) => {
try { try {
const lesson = await createLesson({ const lesson = await startLessonFromSchedule(schedule.id);
courseId: schedule.courseId,
classId: schedule.classId,
plannedDatetime: schedule.scheduledDate,
});
message.success('课堂创建成功'); message.success('课堂创建成功');
router.push(`/teacher/lessons/${lesson.id}`); router.push(`/teacher/lessons/${lesson.id}`);
} catch (error) { } catch (error) {
@ -540,6 +549,10 @@ onMounted(() => {
.class-name { .class-name {
font-size: 12px; font-size: 12px;
color: #666; color: #666;
margin-bottom: 4px;
}
.today-lesson-type {
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -671,6 +684,10 @@ onMounted(() => {
margin-bottom: 4px; margin-bottom: 4px;
} }
.schedule-lesson-type {
margin-bottom: 4px;
}
.schedule-source { .schedule-source {
margin-bottom: 4px; margin-bottom: 4px;
} }

View File

@ -3,7 +3,6 @@ package com.reading.platform.controller.teacher;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.annotation.RequireRole; import com.reading.platform.common.annotation.RequireRole;
import com.reading.platform.common.enums.UserRole; import com.reading.platform.common.enums.UserRole;
import com.reading.platform.common.mapper.SchedulePlanMapper;
import com.reading.platform.common.response.PageResult; import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result; import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.common.security.SecurityUtils;
@ -12,6 +11,7 @@ import com.reading.platform.dto.request.SchedulePlanUpdateRequest;
import com.reading.platform.dto.response.SchedulePlanResponse; import com.reading.platform.dto.response.SchedulePlanResponse;
import com.reading.platform.dto.response.TimetableResponse; import com.reading.platform.dto.response.TimetableResponse;
import com.reading.platform.entity.SchedulePlan; import com.reading.platform.entity.SchedulePlan;
import com.reading.platform.service.SchoolScheduleService;
import com.reading.platform.service.TeacherScheduleService; import com.reading.platform.service.TeacherScheduleService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -34,7 +34,7 @@ import java.util.List;
public class TeacherScheduleController { public class TeacherScheduleController {
private final TeacherScheduleService teacherScheduleService; private final TeacherScheduleService teacherScheduleService;
private final SchedulePlanMapper schedulePlanMapper; private final SchoolScheduleService schoolScheduleService;
@GetMapping @GetMapping
@Operation(summary = "获取教师排课列表") @Operation(summary = "获取教师排课列表")
@ -45,7 +45,9 @@ public class TeacherScheduleController {
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
Long teacherId = SecurityUtils.getCurrentUserId(); Long teacherId = SecurityUtils.getCurrentUserId();
Page<SchedulePlan> page = teacherScheduleService.getTeacherSchedules(teacherId, pageNum, pageSize, startDate, endDate); Page<SchedulePlan> page = teacherScheduleService.getTeacherSchedules(teacherId, pageNum, pageSize, startDate, endDate);
List<SchedulePlanResponse> voList = schedulePlanMapper.toVO(page.getRecords()); List<SchedulePlanResponse> voList = page.getRecords().stream()
.map(schoolScheduleService::toSchedulePlanResponse)
.collect(java.util.stream.Collectors.toList());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
} }
@ -64,7 +66,9 @@ public class TeacherScheduleController {
public Result<List<SchedulePlanResponse>> getTodaySchedules() { public Result<List<SchedulePlanResponse>> getTodaySchedules() {
Long teacherId = SecurityUtils.getCurrentUserId(); Long teacherId = SecurityUtils.getCurrentUserId();
List<SchedulePlan> schedules = teacherScheduleService.getTodaySchedules(teacherId); List<SchedulePlan> schedules = teacherScheduleService.getTodaySchedules(teacherId);
List<SchedulePlanResponse> voList = schedulePlanMapper.toVO(schedules); List<SchedulePlanResponse> voList = schedules.stream()
.map(schoolScheduleService::toSchedulePlanResponse)
.collect(java.util.stream.Collectors.toList());
return Result.success(voList); return Result.success(voList);
} }
@ -72,7 +76,7 @@ public class TeacherScheduleController {
@Operation(summary = "获取排课详情") @Operation(summary = "获取排课详情")
public Result<SchedulePlanResponse> getSchedule(@PathVariable Long id) { public Result<SchedulePlanResponse> getSchedule(@PathVariable Long id) {
SchedulePlan schedule = teacherScheduleService.getScheduleById(id); SchedulePlan schedule = teacherScheduleService.getScheduleById(id);
return Result.success(schedulePlanMapper.toVO(schedule)); return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
} }
@PostMapping @PostMapping
@ -81,7 +85,7 @@ public class TeacherScheduleController {
Long teacherId = SecurityUtils.getCurrentUserId(); Long teacherId = SecurityUtils.getCurrentUserId();
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
SchedulePlan schedule = teacherScheduleService.createSchedule(tenantId, teacherId, request); SchedulePlan schedule = teacherScheduleService.createSchedule(tenantId, teacherId, request);
return Result.success(schedulePlanMapper.toVO(schedule)); return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
} }
@PutMapping("/{id}") @PutMapping("/{id}")
@ -90,7 +94,7 @@ public class TeacherScheduleController {
@PathVariable Long id, @PathVariable Long id,
@RequestBody SchedulePlanUpdateRequest request) { @RequestBody SchedulePlanUpdateRequest request) {
SchedulePlan schedule = teacherScheduleService.updateSchedule(id, request); SchedulePlan schedule = teacherScheduleService.updateSchedule(id, request);
return Result.success(schedulePlanMapper.toVO(schedule)); return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")

View File

@ -11,6 +11,7 @@ import com.reading.platform.dto.response.SchedulePlanResponse;
import com.reading.platform.dto.response.TimetableResponse; import com.reading.platform.dto.response.TimetableResponse;
import com.reading.platform.entity.SchedulePlan; import com.reading.platform.entity.SchedulePlan;
import com.reading.platform.mapper.SchedulePlanMapper; import com.reading.platform.mapper.SchedulePlanMapper;
import com.reading.platform.service.SchoolScheduleService;
import com.reading.platform.service.TeacherScheduleService; import com.reading.platform.service.TeacherScheduleService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -33,6 +34,7 @@ public class TeacherScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper,
implements TeacherScheduleService { implements TeacherScheduleService {
private final SchedulePlanMapper schedulePlanMapper; private final SchedulePlanMapper schedulePlanMapper;
private final SchoolScheduleService schoolScheduleService;
@Override @Override
public Page<SchedulePlan> getTeacherSchedules(Long teacherId, Integer pageNum, Integer pageSize, public Page<SchedulePlan> getTeacherSchedules(Long teacherId, Integer pageNum, Integer pageSize,
@ -71,7 +73,7 @@ public class TeacherScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper,
List<SchedulePlan> schedules = schedulePlanMapper.selectList(wrapper); List<SchedulePlan> schedules = schedulePlanMapper.selectList(wrapper);
// 按日期分组 // 按日期分组使用 schoolScheduleService.toSchedulePlanResponse 填充 classNamecourseName 等展示字段
return schedules.stream() return schedules.stream()
.collect(Collectors.groupingBy(SchedulePlan::getScheduledDate)) .collect(Collectors.groupingBy(SchedulePlan::getScheduledDate))
.entrySet().stream() .entrySet().stream()
@ -82,20 +84,7 @@ public class TeacherScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper,
.date(date) .date(date)
.weekDay(date != null ? date.getDayOfWeek().getValue() : null) .weekDay(date != null ? date.getDayOfWeek().getValue() : null)
.schedules(daySchedules.stream() .schedules(daySchedules.stream()
.map(schedule -> SchedulePlanResponse.builder() .map(schoolScheduleService::toSchedulePlanResponse)
.id(schedule.getId())
.name(schedule.getName())
.classId(schedule.getClassId())
.courseId(schedule.getCourseId())
.teacherId(schedule.getTeacherId())
.scheduledDate(schedule.getScheduledDate())
.scheduledTime(schedule.getScheduledTime())
.weekDay(schedule.getWeekDay())
.repeatType(schedule.getRepeatType())
.source(schedule.getSource())
.note(schedule.getNote())
.status(schedule.getStatus())
.build())
.collect(Collectors.toList())) .collect(Collectors.toList()))
.build(); .build();
}) })