diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2876312..eaf27e4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,65 @@ ## [Unreleased] +### 排课计划参考示例数据添加 ✅ (2026-03-18) + +**添加了课程排课计划参考示例数据:** + +**数据库迁移**: +- Flyway 迁移脚本:`V29__add_schedule_ref_data.sql` +- 为小猪佩奇绘本阅读、好饿的毛毛虫、三只小猪等课程添加排课计划参考 + +**数据内容**: +- 导入课:课程导入,激发兴趣(每周1次,连续2周) +- 集体课:集体教学活动(每周2次,连续4周) +- 五大领域课:语言、社会、科学、艺术、健康领域活动 + +**数据结构**: +```json +[ + { + "lessonType": "INTRODUCTION", + "title": "导入课", + "suggestedOrder": 1, + "durationMinutes": 15, + "frequency": "每周1次,连续2周", + "keyPoints": ["展示绘本封面,引导观察"], + "tips": "建议配合实物教具或多媒体资源" + }, + ... +] +``` + +**后端 API**: +- 新增 `GET /api/v1/school/packages/{packageId}/courses` 获取课程包课程列表 +- Git 提交:`commit 4072b21` + +--- + +### 排课计划参考功能 ✅ (2026-03-17 下午) + +**添加了课程包排课计划参考数据的返回和显示功能:** + +**问题背景**: +- 学校老师需要排课计划参考作为指引 +- 课程包下有导入课、集体课、五大领域课,需要告诉老师如何安排才能发挥最大价值 + +**后端实现**: +- `CoursePackageResponse.CoursePackageCourseItem` 添加 `scheduleRefData` 和 `lessonType` 字段 +- `CoursePackageService.toResponse()` 填充 `scheduleRefData` 和 `lessonType` +- `CourseCollectionService.toPackageResponse()` 重写以包含课程列表和排课计划参考 +- 添加必要的 mapper 依赖注入 + +**前端实现**: +- `school.ts` `CoursePackage` 接口添加 `scheduleRefData` 字段 +- `CreateScheduleModal.vue` 优化 `selectPackage()` 方法,直接从响应数据中提取排课计划参考 + +**API验证**:`GET /api/v1/school/packages/5/packages` 返回包含 `courses` 数组的完整响应 + +**Git提交**: `commit 9f89ce7` + +--- + ### 排课功能两层结构重构完成 ✅ (2026-03-17) **实现了课程套餐→课程包→课程的两层结构架构:** diff --git a/docs/dev-logs/2026-03-17.md b/docs/dev-logs/2026-03-17.md index a927e80..4eb9c58 100644 --- a/docs/dev-logs/2026-03-17.md +++ b/docs/dev-logs/2026-03-17.md @@ -224,3 +224,162 @@ mvn clean compile -DskipTests | AdminPackageController | ✅ 已规范 | | AdminThemeController | ✅ 已规范 | | AdminSettingsController | 🟡 可选(设置类接口允许使用 Map) | + +--- + +## 排课计划参考功能实现 + +### 背景 + +学校端排课功能需要显示排课计划参考数据,帮助老师了解课程包下的课程安排建议。用户说明:"因为我们一个套餐是一个课程体系,在这个课程体系下,有多个课程包,每个课程包下,有导入课、集体课、五大领域课,我们提供的排课计划参考是为了给学校老师指引,告诉他们课程包下的这三类课如何上,才能发挥最大的价值。" + +### 新建排课流程 + +1. 选择课程套餐 → 选择课程包 → 显示排课计划参考 +2. 单选课程类型(导入课/集体课/五大领域课) +3. 选择班级 → 指定授课教师 +4. 设置授课时间 + +### 后端修改 + +#### 1. CoursePackageResponse.java + +**文件路径**: `reading-platform-java/src/main/java/com/reading/platform/dto/response/CoursePackageResponse.java` + +**修改内容**: 在 `CoursePackageCourseItem` 内部类中添加 `scheduleRefData` 和 `lessonType` 字段 + +```java +@Schema(description = "排课计划参考数据(JSON)") +private String scheduleRefData; + +@Schema(description = "课程类型") +private String lessonType; +``` + +#### 2. CoursePackageService.java + +**文件路径**: `reading-platform-java/src/main/java/com/reading/platform/service/CoursePackageService.java` + +**修改内容**: 修改 `toResponse()` 方法,填充 `scheduleRefData` 和 `lessonType` + +```java +if (course != null) { + item.setId(course.getId()); + item.setName(course.getName()); + item.setScheduleRefData(course.getScheduleRefData()); + item.setLessonType(course.getLessonType()); +} +``` + +#### 3. CourseCollectionService.java + +**文件路径**: `reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java` + +**修改内容**: +- 添加依赖注入:`CoursePackageCourseMapper`, `CourseMapper` +- 添加导入:`Course`, `CoursePackageCourse` +- 完全重写 `toPackageResponse()` 方法,包含课程列表查询逻辑 + +```java +private CoursePackageResponse toPackageResponse(CoursePackage pkg) { + // 查询课程包关联的课程 + List packageCourses = packageCourseMapper.selectList( + new LambdaQueryWrapper() + .eq(CoursePackageCourse::getPackageId, pkg.getId()) + .orderByAsc(CoursePackageCourse::getSortOrder) + ); + + List courseItems = ...; + // 构建 courseItems 列表,包含 scheduleRefData +} +``` + +### 前端修改 + +#### 1. school.ts + +**文件路径**: `reading-platform-frontend/src/api/school.ts` + +**修改内容**: 在 `CoursePackage` 接口的 `courses` 数组项中添加 `scheduleRefData` 字段 + +```typescript +courses?: Array<{ + id: number; + name: string; + gradeLevel: string; + sortOrder: number; + scheduleRefData?: string; // 新增字段 +}>; +``` + +#### 2. CreateScheduleModal.vue + +**文件路径**: `reading-platform-frontend/src/views/school/schedule/components/CreateScheduleModal.vue` + +**修改内容**: 优化 `selectPackage()` 方法,直接从响应数据中提取排课计划参考 + +```typescript +const selectPackage = async (packageId: number) => { + formData.packageId = packageId; + formData.courseId = undefined; + + // 加载排课计划参考(从选中的课程包中获取) + if (selectedCollection.value?.packages) { + const selectedPkg = selectedCollection.value.packages.find((p: any) => p.id === packageId); + if (selectedPkg?.courses && selectedPkg.courses.length > 0) { + const firstCourse = selectedPkg.courses[0]; + if (firstCourse.scheduleRefData) { + try { + const parsedData = JSON.parse(firstCourse.scheduleRefData); + scheduleRefData.value = Array.isArray(parsedData) ? parsedData : []; + } catch (e) { + scheduleRefData.value = []; + } + } + } + } + + await loadLessonTypes(packageId); +}; +``` + +### API 验证 + +**请求**: +``` +GET /api/v1/school/packages/5/packages +Authorization: Bearer {token} +``` + +**响应**: +```json +{ + "code": 200, + "data": [{ + "id": "5", + "name": "完整阅读能力培养套餐", + "courses": [ + { + "id": "6", + "name": "小猪佩奇绘本阅读", + "gradeLevel": "小班", + "sortOrder": 1, + "scheduleRefData": null, + "lessonType": null + } + ] + }] +} +``` + +### 下一步工作 + +1. 在数据库中添加示例排课计划参考数据(`course.schedule_ref_data` 字段) +2. 考虑添加 `collectionId` 存储(需要数据库迁移和 DTO 更新) + +### Git 提交 + +``` +commit 9f89ce7 +feat: 添加课程包排课计划参考数据返回 +``` diff --git a/docs/dev-logs/2026-03-18.md b/docs/dev-logs/2026-03-18.md new file mode 100644 index 0000000..de4ffe5 --- /dev/null +++ b/docs/dev-logs/2026-03-18.md @@ -0,0 +1,82 @@ +# 开发日志 - 2026-03-18 + +## 完成事项 + +### 1. 提交昨天的变更代码 + +**提交内容**: +- `SchoolPackageController.java` - 新增 `GET /api/v1/school/packages/{packageId}/courses` 接口 +- 文档更新(CHANGELOG.md, dev-logs/2026-03-17.md) + +**Git 提交**: +``` +commit 4072b21 +feat: 添加课程包课程列表查询API +``` + +### 2. 添加排课计划参考示例数据 + +**Flyway 迁移脚本**:`V29__add_schedule_ref_data.sql` + +**为以下课程添加了排课计划参考数据**: + +#### 小猪佩奇绘本阅读 +- 导入课:通过图片、视频等形式导入课程主题 +- 集体课:全班集体参与的教学活动 +- 五大领域课:语言、社会、科学、艺术、健康 + +#### 好饿的毛毛虫 +- 导入课:通过毛毛虫玩偶导入课程,激发好奇心 +- 集体课:绘本共读,了解毛毛虫的成长过程 +- 五大领域课:各领域活动安排 + +#### 三只小猪 +- 导入课:通过小猪玩偶和房子图片导入课程主题 +- 集体课:绘本共读,理解故事情节和寓意 +- 五大领域课:各领域活动安排 + +**排课计划参考数据结构**: +```json +[ + { + "lessonType": "INTRODUCTION", + "title": "导入课", + "description": "...", + "suggestedOrder": 1, + "durationMinutes": 15, + "frequency": "每周1次,连续2周", + "keyPoints": ["..."], + "tips": "..." + }, + { + "lessonType": "COLLECTIVE", + "title": "集体课", + "description": "...", + "suggestedOrder": 2, + "durationMinutes": 25, + "frequency": "每周2次,连续4周", + "keyPoints": ["..."], + "tips": "..." + }, + // ... 五大领域课 +] +``` + +### 排课计划参考功能说明 + +**功能目的**: +帮助学校老师了解课程包下的三类课程(导入课、集体课、五大领域课)如何安排才能发挥最大价值。 + +**数据位置**: +- 数据库:`course.schedule_ref_data` 字段(JSON 格式) +- 实体类:`Course.scheduleRefData` +- 响应 DTO:`CoursePackageResponse.CoursePackageCourseItem.scheduleRefData` + +**前端显示**: +学校端排课功能中,选择课程包后会自动显示该课程包的排课计划参考,帮助老师了解课程安排建议。 + +### 后续工作 + +- [ ] 考虑添加 `collectionId` 存储(需要数据库迁移和 DTO 更新) + +--- diff --git a/reading-platform-frontend/src/api/school.ts b/reading-platform-frontend/src/api/school.ts index 627e664..bbd5cf0 100644 --- a/reading-platform-frontend/src/api/school.ts +++ b/reading-platform-frontend/src/api/school.ts @@ -301,6 +301,7 @@ export interface CoursePackage { name: string; gradeLevel: string; sortOrder: number; + scheduleRefData?: string; }>; } diff --git a/reading-platform-frontend/src/views/school/schedule/components/CreateScheduleModal.vue b/reading-platform-frontend/src/views/school/schedule/components/CreateScheduleModal.vue index 44f4515..8b54ab1 100644 --- a/reading-platform-frontend/src/views/school/schedule/components/CreateScheduleModal.vue +++ b/reading-platform-frontend/src/views/school/schedule/components/CreateScheduleModal.vue @@ -405,7 +405,29 @@ const handleCollectionChange = async (collectionId: number) => { const selectPackage = async (packageId: number) => { formData.packageId = packageId; formData.courseId = undefined; - scheduleRefData.value = []; + + // 加载排课计划参考(从选中的课程包中获取) + if (selectedCollection.value?.packages) { + const selectedPkg = selectedCollection.value.packages.find((p: any) => p.id === packageId); + if (selectedPkg?.courses && selectedPkg.courses.length > 0) { + // 取第一门课程的排课计划参考作为模板 + const firstCourse = selectedPkg.courses[0]; + if (firstCourse.scheduleRefData) { + try { + const parsedData = JSON.parse(firstCourse.scheduleRefData); + scheduleRefData.value = Array.isArray(parsedData) ? parsedData : []; + console.log('✅ 排课计划参考数据:', scheduleRefData.value); + } catch (e) { + console.error('解析排课数据失败:', e); + scheduleRefData.value = []; + } + } else { + scheduleRefData.value = []; + } + } else { + scheduleRefData.value = []; + } + } // 加载课程类型列表 await loadLessonTypes(packageId); @@ -414,31 +436,6 @@ const selectPackage = async (packageId: number) => { // 选择课程 const selectCourse = (courseId: number) => { formData.courseId = courseId; - // 加载该课程的排课计划参考 - loadScheduleRefData(courseId); -}; - -// 加载排课计划参考数据 -const loadScheduleRefData = async (courseId: number) => { - try { - // TODO: 调用获取课程详情的API - // const course = await getCourseDetail(courseId); - // if (course?.scheduleRefData) { - // try { - // const parsedData = JSON.parse(course.scheduleRefData); - // scheduleRefData.value = Array.isArray(parsedData) ? parsedData : []; - // } catch (e) { - // console.error('Failed to parse scheduleRefData:', e); - // scheduleRefData.value = []; - // } - // } else { - // scheduleRefData.value = []; - // } - scheduleRefData.value = []; - } catch (error) { - console.error('Failed to load course detail:', error); - scheduleRefData.value = []; - } }; // 加载课程类型列表 diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolPackageController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolPackageController.java index dde26ed..08be07b 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolPackageController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolPackageController.java @@ -48,6 +48,13 @@ public class SchoolPackageController { return Result.success(collectionService.getPackagesByCollection(collectionId)); } + @GetMapping("/packages/{packageId}/courses") + @Operation(summary = "获取课程包下的课程列表(包含排课计划参考)") + @RequireRole(UserRole.SCHOOL) + public Result getPackageCourses(@PathVariable Long packageId) { + return Result.success(packageService.findOnePackage(packageId)); + } + @GetMapping("/legacy") @Operation(summary = "查询租户套餐(旧版API,已废弃)") @Deprecated diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/response/CoursePackageResponse.java b/reading-platform-java/src/main/java/com/reading/platform/dto/response/CoursePackageResponse.java index dd4b5a0..54cb60c 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/dto/response/CoursePackageResponse.java +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/response/CoursePackageResponse.java @@ -98,5 +98,11 @@ public class CoursePackageResponse { @Schema(description = "排序号") private Integer sortOrder; + + @Schema(description = "排课计划参考数据(JSON)") + private String scheduleRefData; + + @Schema(description = "课程类型") + private String lessonType; } } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java b/reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java index ee59818..f99cd2b 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java @@ -8,12 +8,16 @@ import com.reading.platform.common.enums.TenantPackageStatus; import com.reading.platform.common.response.PageResult; import com.reading.platform.dto.response.CourseCollectionResponse; import com.reading.platform.dto.response.CoursePackageResponse; +import com.reading.platform.entity.Course; import com.reading.platform.entity.CourseCollection; import com.reading.platform.entity.CourseCollectionPackage; import com.reading.platform.entity.CoursePackage; +import com.reading.platform.entity.CoursePackageCourse; import com.reading.platform.entity.TenantPackage; import com.reading.platform.mapper.CourseCollectionMapper; import com.reading.platform.mapper.CourseCollectionPackageMapper; +import com.reading.platform.mapper.CourseMapper; +import com.reading.platform.mapper.CoursePackageCourseMapper; import com.reading.platform.mapper.CoursePackageMapper; import com.reading.platform.mapper.TenantPackageMapper; import lombok.RequiredArgsConstructor; @@ -39,6 +43,8 @@ public class CourseCollectionService extends ServiceImpl courseItems = new ArrayList<>(); + + // 查询课程包关联的课程 + List packageCourses = packageCourseMapper.selectList( + new LambdaQueryWrapper() + .eq(CoursePackageCourse::getPackageId, pkg.getId()) + .orderByAsc(CoursePackageCourse::getSortOrder) + ); + + if (!packageCourses.isEmpty()) { + courseItems = packageCourses.stream() + .map(pkc -> { + CoursePackageResponse.CoursePackageCourseItem item = new CoursePackageResponse.CoursePackageCourseItem(); + // 从 CoursePackageCourse 获取课程信息 + Course course = courseMapper.selectById(pkc.getCourseId()); + if (course != null) { + item.setId(course.getId()); + item.setName(course.getName()); + item.setScheduleRefData(course.getScheduleRefData()); + } + item.setGradeLevel(pkc.getGradeLevel()); + item.setSortOrder(pkc.getSortOrder()); + return item; + }) + .collect(Collectors.toList()); + } + return CoursePackageResponse.builder() .id(pkg.getId()) .name(pkg.getName()) @@ -333,7 +376,7 @@ public class CourseCollectionService extends ServiceImpl