diff --git a/reading-platform-frontend/src/api/course-center.ts b/reading-platform-frontend/src/api/course-center.ts index a31f656..83da8c0 100644 --- a/reading-platform-frontend/src/api/course-center.ts +++ b/reading-platform-frontend/src/api/course-center.ts @@ -52,10 +52,18 @@ export interface LessonTypeOption { count: number; } +/** 筛选元数据 - 课程包主题选项 */ +export interface ThemeOption { + themeId: number; + name: string; + count: number; +} + /** 筛选元数据响应 */ export interface FilterMetaResponse { grades: GradeOption[]; lessonTypes: LessonTypeOption[]; + themes?: ThemeOption[]; } // ============= API 接口 ============= @@ -75,6 +83,7 @@ export function getPackages( params?: { grade?: string; lessonType?: string; + themeId?: number; keyword?: string; } ): Promise { diff --git a/reading-platform-frontend/src/views/school/courses-new/CourseCenterView.vue b/reading-platform-frontend/src/views/school/courses-new/CourseCenterView.vue index 6214ffe..30027e8 100644 --- a/reading-platform-frontend/src/views/school/courses-new/CourseCenterView.vue +++ b/reading-platform-frontend/src/views/school/courses-new/CourseCenterView.vue @@ -59,19 +59,31 @@
+ +
+ 课程包主题: + + 全部课程包主题 + + {{ opt.name }} ({{ opt.count }}) + + +
课程配置: 全部课程配置 - {{ opt.name }} ({{ opt.count }})
+
); // 筛选元数据 -const filterMeta = ref({ grades: [], themes: [] }); +const filterMeta = ref({ grades: [], lessonTypes: [], themes: [] }); + +// 课程配置筛选选项(过滤导入课、集体课) +const EXCLUDED_LESSON_TYPES = new Set(['INTRODUCTION', 'INTRO', 'COLLECTIVE']); +const filteredLessonTypes = computed(() => { + const list = filterMeta.value.lessonTypes || []; + return list.filter(opt => !EXCLUDED_LESSON_TYPES.has((opt.lessonType || '').toUpperCase())); +}); // 课程包列表 const packages = ref([]); @@ -176,6 +195,7 @@ const selectCollection = async (collection: CourseCollection) => { // 重置筛选条件 selectedGrade.value = ''; selectedLessonType.value = undefined; + selectedThemeId.value = undefined; searchKeyword.value = ''; descExpanded.value = false; @@ -205,7 +225,7 @@ const loadFilterMeta = async () => { filterMeta.value = await getFilterMeta(selectedCollectionId.value); } catch (error) { console.error('获取筛选元数据失败', error); - filterMeta.value = { grades: [], lessonTypes: [] }; + filterMeta.value = { grades: [], lessonTypes: [], themes: [] }; } }; @@ -217,6 +237,7 @@ const loadPackages = async () => { packages.value = await getPackages(selectedCollectionId.value, { grade: selectedGrade.value || undefined, lessonType: selectedLessonType.value, + themeId: selectedThemeId.value, keyword: searchKeyword.value || undefined, }); } catch (error: any) { @@ -227,8 +248,8 @@ const loadPackages = async () => { } }; -// 监听年级、课程配置变化 -watch([selectedGrade, selectedLessonType], () => { +// 监听年级、课程配置、主题变化 +watch([selectedGrade, selectedLessonType, selectedThemeId], () => { loadPackages(); }); diff --git a/reading-platform-frontend/src/views/school/courses-new/components/CoursePackageCard.vue b/reading-platform-frontend/src/views/school/courses-new/components/CoursePackageCard.vue index 28cc633..75d5e7d 100644 --- a/reading-platform-frontend/src/views/school/courses-new/components/CoursePackageCard.vue +++ b/reading-platform-frontend/src/views/school/courses-new/components/CoursePackageCard.vue @@ -88,13 +88,14 @@ const gradeText = computed(() => { return grades.join(' · '); }); -// 从 courses 提取课程类型列表(去重,与管理端一致) +// 从 courses 提取课程类型列表(去重,过滤导入课、集体课) +const EXCLUDED_LESSON_TYPES = new Set(['INTRODUCTION', 'INTRO', 'COLLECTIVE']); const lessonTypes = computed(() => { const courses = props.pkg.courses || []; const types = new Set(); for (const c of courses) { const t = c.lessonType; - if (t) types.add(t); + if (t && !EXCLUDED_LESSON_TYPES.has(t.toUpperCase())) types.add(t); } return Array.from(types); }); diff --git a/reading-platform-frontend/src/views/teacher/courses-new/CourseCenterView.vue b/reading-platform-frontend/src/views/teacher/courses-new/CourseCenterView.vue index b5dadf9..9490aa5 100644 --- a/reading-platform-frontend/src/views/teacher/courses-new/CourseCenterView.vue +++ b/reading-platform-frontend/src/views/teacher/courses-new/CourseCenterView.vue @@ -7,12 +7,9 @@
-
+ @click="selectCollection(collection)">
{{ collection.name }}
{{ collection.packageCount || 0 }}个课程包
@@ -34,11 +31,7 @@
{{ selectedCollection.description }}
- @@ -52,18 +45,12 @@
年级:
- + 全部 - + @click="selectedGrade = grade.label"> {{ grade.label }} ({{ grade.count }}) @@ -71,37 +58,36 @@
-
+
+
+ 课程包主题: + + 全部课程包主题 + + {{ opt.name }} ({{ opt.count }}) + + +
课程配置: - + 全部课程配置 - + {{ opt.name }} ({{ opt.count }})
+ +
- +
@@ -110,13 +96,8 @@
- +
@@ -166,7 +147,14 @@ const selectedCollection = computed(() => ); // 筛选元数据 -const filterMeta = ref({ grades: [], lessonTypes: [] }); +const filterMeta = ref({ grades: [], lessonTypes: [], themes: [] }); + +// 课程配置筛选选项(过滤导入课、集体课) +const EXCLUDED_LESSON_TYPES = new Set(['INTRODUCTION', 'INTRO', 'COLLECTIVE']); +const filteredLessonTypes = computed(() => { + const list = filterMeta.value.lessonTypes || []; + return list.filter(opt => !EXCLUDED_LESSON_TYPES.has((opt.lessonType || '').toUpperCase())); +}); // 课程包列表 const packages = ref([]); @@ -175,6 +163,7 @@ const loadingPackages = ref(false); // 筛选条件 const selectedGrade = ref(''); const selectedLessonType = ref(undefined); +const selectedThemeId = ref(undefined); const searchKeyword = ref(''); // 描述展开 @@ -205,6 +194,7 @@ const selectCollection = async (collection: CourseCollection) => { // 重置筛选条件 selectedGrade.value = ''; selectedLessonType.value = undefined; + selectedThemeId.value = undefined; searchKeyword.value = ''; descExpanded.value = false; @@ -234,7 +224,7 @@ const loadFilterMeta = async () => { filterMeta.value = await getFilterMeta(selectedCollectionId.value); } catch (error) { console.error('获取筛选元数据失败', error); - filterMeta.value = { grades: [], lessonTypes: [] }; + filterMeta.value = { grades: [], lessonTypes: [], themes: [] }; } }; @@ -246,6 +236,7 @@ const loadPackages = async () => { packages.value = await getPackages(selectedCollectionId.value, { grade: selectedGrade.value || undefined, lessonType: selectedLessonType.value, + themeId: selectedThemeId.value, keyword: searchKeyword.value || undefined, }); } catch (error: any) { @@ -266,8 +257,8 @@ const handlePrepare = (pkg: CoursePackage) => { router.push(`/teacher/courses/${pkg.id}/prepare`); }; -// 监听年级、课程配置变化 -watch([selectedGrade, selectedLessonType], () => { +// 监听年级、课程配置、主题变化 +watch([selectedGrade, selectedLessonType, selectedThemeId], () => { loadPackages(); }); @@ -432,7 +423,7 @@ onMounted(() => { gap: 16px; } -.filter-row + .filter-row { +.filter-row+.filter-row { margin-top: 12px; } diff --git a/reading-platform-frontend/src/views/teacher/courses-new/components/CoursePackageCard.vue b/reading-platform-frontend/src/views/teacher/courses-new/components/CoursePackageCard.vue index 3fbb3f2..2670af6 100644 --- a/reading-platform-frontend/src/views/teacher/courses-new/components/CoursePackageCard.vue +++ b/reading-platform-frontend/src/views/teacher/courses-new/components/CoursePackageCard.vue @@ -88,13 +88,14 @@ const gradeText = computed(() => { return grades.join(' · '); }); -// 从 courses 提取课程类型列表(去重,与管理端一致) +// 从 courses 提取课程类型列表(去重,过滤导入课、集体课) +const EXCLUDED_LESSON_TYPES = new Set(['INTRODUCTION', 'INTRO', 'COLLECTIVE']); const lessonTypes = computed(() => { const courses = props.pkg.courses || []; const types = new Set(); for (const c of courses) { const t = c.lessonType; - if (t) types.add(t); + if (t && !EXCLUDED_LESSON_TYPES.has(t.toUpperCase())) types.add(t); } return Array.from(types); }); 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 d017ddb..dbadb59 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 @@ -51,8 +51,9 @@ public class SchoolPackageController { @PathVariable Long collectionId, @RequestParam(required = false) String grade, @RequestParam(required = false) String lessonType, + @RequestParam(required = false) Long themeId, @RequestParam(required = false) String keyword) { - return Result.success(collectionService.getPackagesByCollection(collectionId, grade, lessonType, keyword)); + return Result.success(collectionService.getPackagesByCollection(collectionId, grade, lessonType, themeId, keyword)); } @GetMapping("/{collectionId}/filter-meta") diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/response/PackageFilterMetaResponse.java b/reading-platform-java/src/main/java/com/reading/platform/dto/response/PackageFilterMetaResponse.java index 3ed02e1..204077d 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/dto/response/PackageFilterMetaResponse.java +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/response/PackageFilterMetaResponse.java @@ -25,6 +25,9 @@ public class PackageFilterMetaResponse { @Schema(description = "课程配置选项列表(导入课、集体课、健康、科学等)") private List lessonTypes; + @Schema(description = "课程包主题选项列表") + private List themes; + /** * 年级选项 */ @@ -59,4 +62,23 @@ public class PackageFilterMetaResponse { @Schema(description = "包含该类型环节的课程包数量") private Integer count; } + + /** + * 课程包主题选项 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "课程包主题选项") + public static class ThemeOption { + @Schema(description = "主题ID") + private Long themeId; + + @Schema(description = "主题名称") + private String name; + + @Schema(description = "该主题下的课程包数量") + private Integer count; + } } 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 01a0d87..1cf957d 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 @@ -40,10 +40,11 @@ public interface CourseCollectionService extends IService { * @param collectionId 套餐ID * @param grade 年级筛选 * @param lessonType 课程配置筛选(INTRODUCTION、COLLECTIVE、HEALTH、LANGUAGE、SCIENCE、SOCIAL、ART) + * @param themeId 课程包主题筛选 * @param keyword 关键词搜索 * @return 课程包列表 */ - List getPackagesByCollection(Long collectionId, String grade, String lessonType, String keyword); + List getPackagesByCollection(Long collectionId, String grade, String lessonType, Long themeId, String keyword); /** * 获取套餐的筛选元数据(年级、课程配置选项) diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseCollectionServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseCollectionServiceImpl.java index 026fdd6..b5c6d37 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseCollectionServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseCollectionServiceImpl.java @@ -213,8 +213,8 @@ public class CourseCollectionServiceImpl extends ServiceImpl getPackagesByCollection(Long collectionId, String grade, String lessonType, String keyword) { - log.info("获取课程套餐的课程包列表(筛选),collectionId={}, grade={}, lessonType={}, keyword={}", collectionId, grade, lessonType, keyword); + public List getPackagesByCollection(Long collectionId, String grade, String lessonType, Long themeId, String keyword) { + log.info("获取课程套餐的课程包列表(筛选),collectionId={}, grade={}, lessonType={}, themeId={}, keyword={}", collectionId, grade, lessonType, themeId, keyword); // 查询关联关系 List associations = collectionPackageMapper.selectList( @@ -252,6 +252,11 @@ public class CourseCollectionServiceImpl extends ServiceImpl w @@ -315,6 +320,7 @@ public class CourseCollectionServiceImpl extends ServiceImpl()) .lessonTypes(new ArrayList<>()) + .themes(new ArrayList<>()) .build(); } @@ -373,9 +379,32 @@ public class CourseCollectionServiceImpl extends ServiceImpl themeCountMap = new HashMap<>(); + Map themeNameMap = new HashMap<>(); + for (CoursePackage pkg : packages) { + Long tid = pkg.getThemeId(); + if (tid != null) { + themeCountMap.merge(tid, 1, Integer::sum); + if (!themeNameMap.containsKey(tid)) { + Theme theme = themeMapper.selectById(tid); + themeNameMap.put(tid, theme != null ? theme.getName() : "主题" + tid); + } + } + } + List themes = themeCountMap.entrySet().stream() + .map(e -> PackageFilterMetaResponse.ThemeOption.builder() + .themeId(e.getKey()) + .name(themeNameMap.getOrDefault(e.getKey(), "")) + .count(e.getValue()) + .build()) + .sorted((a, b) -> themeNameMap.getOrDefault(a.getThemeId(), "").compareTo(themeNameMap.getOrDefault(b.getThemeId(), ""))) + .collect(Collectors.toList()); + return PackageFilterMetaResponse.builder() .grades(grades) .lessonTypes(lessonTypes) + .themes(themes) .build(); }