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

View File

@ -19,8 +19,11 @@
<template #title>
<span>{{ schedule.scheduledTime || '待定' }}</span>
</template>
<div class="course-name">{{ schedule.courseName }}</div>
<div class="class-name">{{ schedule.className }}</div>
<div class="course-name">{{ schedule.courseName || schedule.coursePackageName || '-' }}</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">
<a-button
v-if="schedule.hasLesson && schedule.lessonStatus === 'PLANNED'"
@ -41,7 +44,7 @@
<a-button
v-else
size="small"
@click="startLessonFromSchedule(schedule)"
@click="handleStartLessonFromSchedule(schedule)"
>
创建课堂
</a-button>
@ -102,8 +105,11 @@
@click="showScheduleDetail(schedule)"
>
<div class="schedule-time">{{ schedule.scheduledTime || '待定' }}</div>
<div class="schedule-course">{{ schedule.courseName }}</div>
<div class="schedule-class">{{ schedule.className }}</div>
<div class="schedule-course">{{ schedule.courseName || schedule.coursePackageName || '-' }}</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">
<a-tag v-if="schedule.source === 'SCHOOL'" color="orange" size="small">学校排课</a-tag>
<a-tag v-else color="purple" size="small">自主预约</a-tag>
@ -193,8 +199,14 @@
>
<template v-if="selectedSchedule">
<a-descriptions :column="1" bordered>
<a-descriptions-item label="班级">{{ selectedSchedule.className }}</a-descriptions-item>
<a-descriptions-item label="课程">{{ selectedSchedule.courseName }}</a-descriptions-item>
<a-descriptions-item label="班级">{{ selectedSchedule.className || '-' }}</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="时间段">{{ selectedSchedule.scheduledTime || '待定' }}</a-descriptions-item>
<a-descriptions-item label="重复方式">
@ -223,7 +235,7 @@
<a-button
v-else-if="selectedSchedule.status === 'ACTIVE'"
type="primary"
@click="startLessonFromSchedule(selectedSchedule)"
@click="handleStartLessonFromSchedule(selectedSchedule)"
>
开始上课
</a-button>
@ -261,7 +273,8 @@ import {
type TeacherSchedule,
type CreateTeacherScheduleDto,
} 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();
@ -450,14 +463,10 @@ const handleCancelSchedule = async (id: number) => {
}
};
//
const startLessonFromSchedule = async (schedule: TeacherSchedule) => {
// teacherIdtitlelessonDate
const handleStartLessonFromSchedule = async (schedule: TeacherSchedule) => {
try {
const lesson = await createLesson({
courseId: schedule.courseId,
classId: schedule.classId,
plannedDatetime: schedule.scheduledDate,
});
const lesson = await startLessonFromSchedule(schedule.id);
message.success('课堂创建成功');
router.push(`/teacher/lessons/${lesson.id}`);
} catch (error) {
@ -540,6 +549,10 @@ onMounted(() => {
.class-name {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.today-lesson-type {
margin-bottom: 8px;
}
@ -671,6 +684,10 @@ onMounted(() => {
margin-bottom: 4px;
}
.schedule-lesson-type {
margin-bottom: 4px;
}
.schedule-source {
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.reading.platform.common.annotation.RequireRole;
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.Result;
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.TimetableResponse;
import com.reading.platform.entity.SchedulePlan;
import com.reading.platform.service.SchoolScheduleService;
import com.reading.platform.service.TeacherScheduleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -34,7 +34,7 @@ import java.util.List;
public class TeacherScheduleController {
private final TeacherScheduleService teacherScheduleService;
private final SchedulePlanMapper schedulePlanMapper;
private final SchoolScheduleService schoolScheduleService;
@GetMapping
@Operation(summary = "获取教师排课列表")
@ -45,7 +45,9 @@ public class TeacherScheduleController {
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
Long teacherId = SecurityUtils.getCurrentUserId();
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()));
}
@ -64,7 +66,9 @@ public class TeacherScheduleController {
public Result<List<SchedulePlanResponse>> getTodaySchedules() {
Long teacherId = SecurityUtils.getCurrentUserId();
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);
}
@ -72,7 +76,7 @@ public class TeacherScheduleController {
@Operation(summary = "获取排课详情")
public Result<SchedulePlanResponse> getSchedule(@PathVariable Long id) {
SchedulePlan schedule = teacherScheduleService.getScheduleById(id);
return Result.success(schedulePlanMapper.toVO(schedule));
return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
}
@PostMapping
@ -81,7 +85,7 @@ public class TeacherScheduleController {
Long teacherId = SecurityUtils.getCurrentUserId();
Long tenantId = SecurityUtils.getCurrentTenantId();
SchedulePlan schedule = teacherScheduleService.createSchedule(tenantId, teacherId, request);
return Result.success(schedulePlanMapper.toVO(schedule));
return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
}
@PutMapping("/{id}")
@ -90,7 +94,7 @@ public class TeacherScheduleController {
@PathVariable Long id,
@RequestBody SchedulePlanUpdateRequest request) {
SchedulePlan schedule = teacherScheduleService.updateSchedule(id, request);
return Result.success(schedulePlanMapper.toVO(schedule));
return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
}
@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.entity.SchedulePlan;
import com.reading.platform.mapper.SchedulePlanMapper;
import com.reading.platform.service.SchoolScheduleService;
import com.reading.platform.service.TeacherScheduleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -33,6 +34,7 @@ public class TeacherScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper,
implements TeacherScheduleService {
private final SchedulePlanMapper schedulePlanMapper;
private final SchoolScheduleService schoolScheduleService;
@Override
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);
// 按日期分组
// 按日期分组使用 schoolScheduleService.toSchedulePlanResponse 填充 classNamecourseName 等展示字段
return schedules.stream()
.collect(Collectors.groupingBy(SchedulePlan::getScheduledDate))
.entrySet().stream()
@ -82,20 +84,7 @@ public class TeacherScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper,
.date(date)
.weekDay(date != null ? date.getDayOfWeek().getValue() : null)
.schedules(daySchedules.stream()
.map(schedule -> SchedulePlanResponse.builder()
.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())
.map(schoolScheduleService::toSchedulePlanResponse)
.collect(Collectors.toList()))
.build();
})