From 7fe2c319eead56bd2a2ee3aff3362f2a456f61db Mon Sep 17 00:00:00 2001 From: zhonghua Date: Wed, 18 Mar 2026 10:14:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=A6=E6=A0=A1=E8=AF=BE=E7=A8=8B=E7=AE=A1?= =?UTF-8?q?=E7=90=86:=20=E5=89=8D=E5=90=8E=E7=AB=AF=E5=AF=B9=E9=BD=90?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=8E=B0=E6=90=9C=E7=B4=A2=E4=B8=8E=E5=B9=B4?= =?UTF-8?q?=E7=BA=A7=E7=AD=9B=E9=80=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- reading-platform-frontend/src/api/school.ts | 9 +- .../views/school/courses/CourseListView.vue | 85 +++++++++---------- .../school/SchoolCourseController.java | 8 +- .../platform/service/CourseService.java | 6 +- .../service/impl/CourseServiceImpl.java | 50 ++++++++++- 5 files changed, 107 insertions(+), 51 deletions(-) diff --git a/reading-platform-frontend/src/api/school.ts b/reading-platform-frontend/src/api/school.ts index bbd5cf0..d66e526 100644 --- a/reading-platform-frontend/src/api/school.ts +++ b/reading-platform-frontend/src/api/school.ts @@ -380,8 +380,13 @@ export interface Course { publishedAt?: string; } -export const getSchoolCourses = () => - http.get('/v1/school/courses'); +export interface SchoolCourseQueryParams { + keyword?: string; + grade?: string; // 小班|中班|大班 或 small|middle|big +} + +export const getSchoolCourses = (params?: SchoolCourseQueryParams) => + http.get('/v1/school/courses', { params }); export const getSchoolCourse = (id: number) => http.get(`/v1/school/courses/${id}`); diff --git a/reading-platform-frontend/src/views/school/courses/CourseListView.vue b/reading-platform-frontend/src/views/school/courses/CourseListView.vue index d88f475..4aaadce 100644 --- a/reading-platform-frontend/src/views/school/courses/CourseListView.vue +++ b/reading-platform-frontend/src/views/school/courses/CourseListView.vue @@ -31,7 +31,7 @@ 年级筛选
+ :class="{ active: selectedGrade === grade.value }" @click="handleGradeChange(grade.value)"> {{ grade.label }}
@@ -49,8 +49,8 @@ -
-
+
@@ -69,11 +69,11 @@

《{{ course.pictureBookName }}》

- {{ translateGradeTag(tag) }} - {{ translateDomainTag(tag) }} @@ -112,11 +112,11 @@
-
+
-

{{ searchKeyword ? '未找到匹配的课程' : '暂无课程数据' }}

+

{{ (searchKeyword || selectedGrade) ? '未找到匹配的课程' : '暂无课程数据' }}

授权第一门课程 @@ -214,6 +214,21 @@ const searchKeyword = ref(''); const selectedCourseIds = ref([]); const selectedGrade = ref(''); // 选中的年级 +// 解析标签(后端返回 JSON 字符串,需解析为数组) +const parseTags = (val: any): string[] => { + if (!val) return []; + if (Array.isArray(val)) return val; + if (typeof val === 'string') { + try { + const parsed = JSON.parse(val); + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } + } + return []; +}; + // 年级选项 const gradeOptions = [ { label: '全部', value: '' }, @@ -223,35 +238,17 @@ const gradeOptions = [ ]; const authorizedCount = computed(() => courses.value.filter(c => c.authorized).length); -const totalUsage = computed(() => courses.value.reduce((sum, c) => sum + c.usageCount, 0)); +const totalUsage = computed(() => courses.value.reduce((sum, c) => sum + (c.usageCount || 0), 0)); -// 过滤后的课程列表(用于搜索和年级筛选) -const filteredCourses = computed(() => { - let result = courses.value; +// 年级切换:请求后端筛选 +const handleGradeChange = (value: string) => { + selectedGrade.value = value; + loadCourses(); +}; - // 按年级筛选 - if (selectedGrade.value) { - result = result.filter(c => { - const gradeTags = c.gradeTags || []; - return gradeTags.some((tag: string) => translateGradeTag(tag) === selectedGrade.value); - }); - } - - // 按关键词搜索 - if (searchKeyword.value) { - const keyword = searchKeyword.value.toLowerCase(); - result = result.filter(c => - c.name.toLowerCase().includes(keyword) || - c.pictureBookName.toLowerCase().includes(keyword) - ); - } - - return result; -}); - -// 年级切换处理 -const handleGradeChange = () => { - // 年级切换时自动触发筛选 +// 搜索:请求后端筛选 +const handleSearch = () => { + loadCourses(); }; const columns = [ @@ -283,15 +280,21 @@ const pagination = reactive({ const courses = ref([]); const availableCourses = ref([]); -// 加载课程列表 +// 加载课程列表(支持后端搜索与年级筛选) const loadCourses = async () => { loading.value = true; try { - const data = await schoolApi.getSchoolCourses(); - courses.value = data.map((course: any) => ({ + const params: { keyword?: string; grade?: string } = {}; + if (searchKeyword.value?.trim()) params.keyword = searchKeyword.value.trim(); + if (selectedGrade.value) params.grade = selectedGrade.value; + const data = await schoolApi.getSchoolCourses(params); + courses.value = (data || []).map((course: any) => ({ ...course, - gradeTags: course.gradeTags || [], - domainTags: course.domainTags || [], + gradeTags: parseTags(course.gradeTags), + domainTags: parseTags(course.domainTags), + duration: course.duration ?? course.durationMinutes ?? 0, + pictureUrl: course.pictureUrl ?? course.coverImagePath ?? course.coverUrl, + authorized: course.authorized ?? true, })); pagination.total = courses.value.length; } catch (error: any) { @@ -301,10 +304,6 @@ const loadCourses = async () => { } }; -const handleSearch = () => { - // 搜索功能已通过 filteredCourses computed 实现 -}; - const handleTableChange = (pag: any) => { pagination.current = pag.current; pagination.pageSize = pag.pageSize; diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCourseController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCourseController.java index 6ef66c3..c28584d 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCourseController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCourseController.java @@ -27,10 +27,12 @@ public class SchoolCourseController { @GetMapping @Operation(summary = "获取学校课程包列表") - public Result> getSchoolCourses() { - log.info("获取学校课程包列表"); + public Result> getSchoolCourses( + @RequestParam(required = false) String keyword, + @RequestParam(required = false) String grade) { + log.info("获取学校课程包列表,keyword={}, grade={}", keyword, grade); Long tenantId = SecurityUtils.getCurrentTenantId(); - List courses = courseService.getTenantPackageCourses(tenantId); + List courses = courseService.getTenantPackageCourses(tenantId, keyword, grade); return Result.success(courses); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/CourseService.java b/reading-platform-java/src/main/java/com/reading/platform/service/CourseService.java index e12a497..17c29e0 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/CourseService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/CourseService.java @@ -53,7 +53,11 @@ public interface CourseService extends com.baomidou.mybatisplus.extension.servic /** * 查询租户套餐下的课程 + * + * @param tenantId 租户 ID + * @param keyword 关键词(课程名称、绘本名称,可选) + * @param grade 年级筛选(小班/中班/大班 或 small/middle/big,可选) */ - List getTenantPackageCourses(Long tenantId); + List getTenantPackageCourses(Long tenantId, String keyword, String grade); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java index 7c62b2c..7d9a076 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java @@ -15,6 +15,7 @@ import com.reading.platform.dto.response.CourseResponse; import com.reading.platform.dto.response.LessonStepResponse; import com.reading.platform.entity.Course; import com.reading.platform.entity.CourseLesson; +import com.alibaba.fastjson2.JSON; import com.reading.platform.entity.CoursePackage; import com.reading.platform.entity.CoursePackageCourse; import com.reading.platform.entity.LessonStep; @@ -479,8 +480,8 @@ public class CourseServiceImpl extends ServiceImpl } @Override - public List getTenantPackageCourses(Long tenantId) { - log.info("查询租户套餐下的课程,tenantId={}", tenantId); + public List getTenantPackageCourses(Long tenantId, String keyword, String grade) { + log.info("查询租户套餐下的课程,tenantId={}, keyword={}, grade={}", tenantId, keyword, grade); // 1. 查询租户的套餐关联 List tenantPackages = tenantPackageMapper.selectList( @@ -538,10 +539,55 @@ public class CourseServiceImpl extends ServiceImpl .ne(Course::getStatus, CourseStatus.ARCHIVED.getCode()) ); + // 7. 按关键词和年级筛选 + String kw = StringUtils.hasText(keyword) ? keyword.trim().toLowerCase() : null; + List gradeKeys = parseGradeFilter(grade); + if (kw != null || !gradeKeys.isEmpty()) { + courses = courses.stream() + .filter(c -> matchKeyword(c, kw)) + .filter(c -> matchGrade(c, gradeKeys)) + .collect(Collectors.toList()); + } + log.info("查询租户套餐下的课程包成功,tenantId={}, count={}", tenantId, courses.size()); return courses; } + /** 解析年级筛选:小班/中班/大班 -> small,middle,big(含大小写) */ + private List parseGradeFilter(String grade) { + if (!StringUtils.hasText(grade)) return List.of(); + String g = grade.trim(); + return switch (g) { + case "小班" -> List.of("small", "SMALL"); + case "中班" -> List.of("middle", "MIDDLE"); + case "大班" -> List.of("big", "BIG"); + case "small", "SMALL" -> List.of("small", "SMALL"); + case "middle", "MIDDLE" -> List.of("middle", "MIDDLE"); + case "big", "BIG" -> List.of("big", "BIG"); + default -> List.of(); + }; + } + + private boolean matchKeyword(Course c, String keyword) { + if (keyword == null) return true; + String name = c.getName() != null ? c.getName().toLowerCase() : ""; + String book = c.getPictureBookName() != null ? c.getPictureBookName().toLowerCase() : ""; + return name.contains(keyword) || book.contains(keyword); + } + + private boolean matchGrade(Course c, List gradeKeys) { + if (gradeKeys.isEmpty()) return true; + String tags = c.getGradeTags(); + if (!StringUtils.hasText(tags)) return false; + try { + List list = JSON.parseArray(tags, String.class); + if (list == null) return false; + return list.stream().anyMatch(gradeKeys::contains); + } catch (Exception e) { + return false; + } + } + /** * 将空字符串转为 null,避免 MySQL JSON 列报错(空串不是有效 JSON) */