feat: 课程中心增加课程包主题筛选,过滤导入课/集体课
- 后端: 套餐筛选元数据新增 themes,getPackages 支持 themeId - 前端: 学校端/教师端课程中心增加课程包主题下拉筛选 - 课程配置筛选和卡片展示均过滤导入课、集体课 Made-with: Cursor
This commit is contained in:
parent
212559fd9f
commit
2d9856edac
@ -52,10 +52,18 @@ export interface LessonTypeOption {
|
|||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 筛选元数据 - 课程包主题选项 */
|
||||||
|
export interface ThemeOption {
|
||||||
|
themeId: number;
|
||||||
|
name: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
/** 筛选元数据响应 */
|
/** 筛选元数据响应 */
|
||||||
export interface FilterMetaResponse {
|
export interface FilterMetaResponse {
|
||||||
grades: GradeOption[];
|
grades: GradeOption[];
|
||||||
lessonTypes: LessonTypeOption[];
|
lessonTypes: LessonTypeOption[];
|
||||||
|
themes?: ThemeOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============= API 接口 =============
|
// ============= API 接口 =============
|
||||||
@ -75,6 +83,7 @@ export function getPackages(
|
|||||||
params?: {
|
params?: {
|
||||||
grade?: string;
|
grade?: string;
|
||||||
lessonType?: string;
|
lessonType?: string;
|
||||||
|
themeId?: number;
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
}
|
}
|
||||||
): Promise<CoursePackage[]> {
|
): Promise<CoursePackage[]> {
|
||||||
|
|||||||
@ -59,19 +59,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-row">
|
<div class="filter-row">
|
||||||
|
<!-- 课程包主题筛选 -->
|
||||||
|
<div class="filter-group">
|
||||||
|
<span class="filter-label">课程包主题:</span>
|
||||||
|
<a-select v-model:value="selectedThemeId" placeholder="全部课程包主题" style="width: 180px" allowClear
|
||||||
|
@change="loadPackages">
|
||||||
|
<a-select-option :value="undefined">全部课程包主题</a-select-option>
|
||||||
|
<a-select-option v-for="opt in (filterMeta.themes || [])" :key="opt.themeId" :value="opt.themeId">
|
||||||
|
{{ opt.name }} ({{ opt.count }})
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
<!-- 课程配置筛选 -->
|
<!-- 课程配置筛选 -->
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<span class="filter-label">课程配置:</span>
|
<span class="filter-label">课程配置:</span>
|
||||||
<a-select v-model:value="selectedLessonType" placeholder="全部课程配置" style="width: 180px" allowClear
|
<a-select v-model:value="selectedLessonType" placeholder="全部课程配置" style="width: 180px" allowClear
|
||||||
@change="loadPackages">
|
@change="loadPackages">
|
||||||
<a-select-option :value="undefined">全部课程配置</a-select-option>
|
<a-select-option :value="undefined">全部课程配置</a-select-option>
|
||||||
<a-select-option v-for="opt in (filterMeta.lessonTypes || [])" :key="opt.lessonType"
|
<a-select-option v-for="opt in filteredLessonTypes" :key="opt.lessonType"
|
||||||
:value="opt.lessonType">
|
:value="opt.lessonType">
|
||||||
{{ opt.name }} ({{ opt.count }})
|
{{ opt.name }} ({{ opt.count }})
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- 搜索 -->
|
<!-- 搜索 -->
|
||||||
<div class="filter-group search-group">
|
<div class="filter-group search-group">
|
||||||
<a-input-search v-model:value="searchKeyword" placeholder="搜索课程包..." style="width: 220px" allowClear
|
<a-input-search v-model:value="searchKeyword" placeholder="搜索课程包..." style="width: 220px" allowClear
|
||||||
@ -137,7 +149,14 @@ const selectedCollection = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 筛选元数据
|
// 筛选元数据
|
||||||
const filterMeta = ref<FilterMetaResponse>({ grades: [], themes: [] });
|
const filterMeta = ref<FilterMetaResponse>({ 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<CoursePackage[]>([]);
|
const packages = ref<CoursePackage[]>([]);
|
||||||
@ -176,6 +195,7 @@ const selectCollection = async (collection: CourseCollection) => {
|
|||||||
// 重置筛选条件
|
// 重置筛选条件
|
||||||
selectedGrade.value = '';
|
selectedGrade.value = '';
|
||||||
selectedLessonType.value = undefined;
|
selectedLessonType.value = undefined;
|
||||||
|
selectedThemeId.value = undefined;
|
||||||
searchKeyword.value = '';
|
searchKeyword.value = '';
|
||||||
descExpanded.value = false;
|
descExpanded.value = false;
|
||||||
|
|
||||||
@ -205,7 +225,7 @@ const loadFilterMeta = async () => {
|
|||||||
filterMeta.value = await getFilterMeta(selectedCollectionId.value);
|
filterMeta.value = await getFilterMeta(selectedCollectionId.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取筛选元数据失败', 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, {
|
packages.value = await getPackages(selectedCollectionId.value, {
|
||||||
grade: selectedGrade.value || undefined,
|
grade: selectedGrade.value || undefined,
|
||||||
lessonType: selectedLessonType.value,
|
lessonType: selectedLessonType.value,
|
||||||
|
themeId: selectedThemeId.value,
|
||||||
keyword: searchKeyword.value || undefined,
|
keyword: searchKeyword.value || undefined,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -227,8 +248,8 @@ const loadPackages = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听年级、课程配置变化
|
// 监听年级、课程配置、主题变化
|
||||||
watch([selectedGrade, selectedLessonType], () => {
|
watch([selectedGrade, selectedLessonType, selectedThemeId], () => {
|
||||||
loadPackages();
|
loadPackages();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -88,13 +88,14 @@ const gradeText = computed(() => {
|
|||||||
return grades.join(' · ');
|
return grades.join(' · ');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 从 courses 提取课程类型列表(去重,与管理端一致)
|
// 从 courses 提取课程类型列表(去重,过滤导入课、集体课)
|
||||||
|
const EXCLUDED_LESSON_TYPES = new Set(['INTRODUCTION', 'INTRO', 'COLLECTIVE']);
|
||||||
const lessonTypes = computed(() => {
|
const lessonTypes = computed(() => {
|
||||||
const courses = props.pkg.courses || [];
|
const courses = props.pkg.courses || [];
|
||||||
const types = new Set<string>();
|
const types = new Set<string>();
|
||||||
for (const c of courses) {
|
for (const c of courses) {
|
||||||
const t = c.lessonType;
|
const t = c.lessonType;
|
||||||
if (t) types.add(t);
|
if (t && !EXCLUDED_LESSON_TYPES.has(t.toUpperCase())) types.add(t);
|
||||||
}
|
}
|
||||||
return Array.from(types);
|
return Array.from(types);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,12 +7,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<a-spin :spinning="loadingCollections">
|
<a-spin :spinning="loadingCollections">
|
||||||
<div class="collection-list">
|
<div class="collection-list">
|
||||||
<div
|
<div v-for="collection in collections" :key="collection.id"
|
||||||
v-for="collection in collections"
|
|
||||||
:key="collection.id"
|
|
||||||
:class="['collection-item', { active: selectedCollectionId === collection.id }]"
|
:class="['collection-item', { active: selectedCollectionId === collection.id }]"
|
||||||
@click="selectCollection(collection)"
|
@click="selectCollection(collection)">
|
||||||
>
|
|
||||||
<div class="collection-name">{{ collection.name }}</div>
|
<div class="collection-name">{{ collection.name }}</div>
|
||||||
<div class="collection-count">{{ collection.packageCount || 0 }}个课程包</div>
|
<div class="collection-count">{{ collection.packageCount || 0 }}个课程包</div>
|
||||||
</div>
|
</div>
|
||||||
@ -34,11 +31,7 @@
|
|||||||
<div ref="descRef" :class="['desc-text', { expanded: descExpanded }]">
|
<div ref="descRef" :class="['desc-text', { expanded: descExpanded }]">
|
||||||
{{ selectedCollection.description }}
|
{{ selectedCollection.description }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button v-if="showExpandBtn" class="expand-btn" @click="descExpanded = !descExpanded">
|
||||||
v-if="showExpandBtn"
|
|
||||||
class="expand-btn"
|
|
||||||
@click="descExpanded = !descExpanded"
|
|
||||||
>
|
|
||||||
{{ descExpanded ? '收起' : '展开更多' }}
|
{{ descExpanded ? '收起' : '展开更多' }}
|
||||||
<DownOutlined :class="{ rotated: descExpanded }" />
|
<DownOutlined :class="{ rotated: descExpanded }" />
|
||||||
</button>
|
</button>
|
||||||
@ -52,18 +45,12 @@
|
|||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<span class="filter-label">年级:</span>
|
<span class="filter-label">年级:</span>
|
||||||
<div class="grade-tags">
|
<div class="grade-tags">
|
||||||
<span
|
<span :class="['grade-tag', { active: !selectedGrade }]" @click="selectedGrade = ''">
|
||||||
:class="['grade-tag', { active: !selectedGrade }]"
|
|
||||||
@click="selectedGrade = ''"
|
|
||||||
>
|
|
||||||
全部
|
全部
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span v-for="grade in filterMeta.grades" :key="grade.label"
|
||||||
v-for="grade in filterMeta.grades"
|
|
||||||
:key="grade.label"
|
|
||||||
:class="['grade-tag', { active: selectedGrade === grade.label }]"
|
:class="['grade-tag', { active: selectedGrade === grade.label }]"
|
||||||
@click="selectedGrade = grade.label"
|
@click="selectedGrade = grade.label">
|
||||||
>
|
|
||||||
{{ grade.label }}
|
{{ grade.label }}
|
||||||
<span class="count">({{ grade.count }})</span>
|
<span class="count">({{ grade.count }})</span>
|
||||||
</span>
|
</span>
|
||||||
@ -71,37 +58,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-row">
|
<div class="filter-row"><!-- 课程包主题筛选 -->
|
||||||
|
<div class="filter-group">
|
||||||
|
<span class="filter-label">课程包主题:</span>
|
||||||
|
<a-select v-model:value="selectedThemeId" placeholder="全部课程包主题" style="width: 180px" allowClear
|
||||||
|
@change="loadPackages">
|
||||||
|
<a-select-option :value="undefined">全部课程包主题</a-select-option>
|
||||||
|
<a-select-option v-for="opt in (filterMeta.themes || [])" :key="opt.themeId" :value="opt.themeId">
|
||||||
|
{{ opt.name }} ({{ opt.count }})
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
<!-- 课程配置筛选 -->
|
<!-- 课程配置筛选 -->
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<span class="filter-label">课程配置:</span>
|
<span class="filter-label">课程配置:</span>
|
||||||
<a-select
|
<a-select v-model:value="selectedLessonType" placeholder="全部课程配置" style="width: 180px" allowClear
|
||||||
v-model:value="selectedLessonType"
|
@change="loadPackages">
|
||||||
placeholder="全部课程配置"
|
|
||||||
style="width: 180px"
|
|
||||||
allowClear
|
|
||||||
@change="loadPackages"
|
|
||||||
>
|
|
||||||
<a-select-option :value="undefined">全部课程配置</a-select-option>
|
<a-select-option :value="undefined">全部课程配置</a-select-option>
|
||||||
<a-select-option
|
<a-select-option v-for="opt in filteredLessonTypes" :key="opt.lessonType"
|
||||||
v-for="opt in (filterMeta.lessonTypes || [])"
|
:value="opt.lessonType">
|
||||||
:key="opt.lessonType"
|
|
||||||
:value="opt.lessonType"
|
|
||||||
>
|
|
||||||
{{ opt.name }} ({{ opt.count }})
|
{{ opt.name }} ({{ opt.count }})
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 搜索 -->
|
<!-- 搜索 -->
|
||||||
<div class="filter-group search-group">
|
<div class="filter-group search-group">
|
||||||
<a-input-search
|
<a-input-search v-model:value="searchKeyword" placeholder="搜索课程包..." style="width: 220px" allowClear
|
||||||
v-model:value="searchKeyword"
|
@search="loadPackages" />
|
||||||
placeholder="搜索课程包..."
|
|
||||||
style="width: 220px"
|
|
||||||
allowClear
|
|
||||||
@search="loadPackages"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -110,13 +96,8 @@
|
|||||||
<section class="packages-section">
|
<section class="packages-section">
|
||||||
<a-spin :spinning="loadingPackages">
|
<a-spin :spinning="loadingPackages">
|
||||||
<div v-if="packages.length > 0" class="packages-grid">
|
<div v-if="packages.length > 0" class="packages-grid">
|
||||||
<CoursePackageCard
|
<CoursePackageCard v-for="pkg in packages" :key="pkg.id" :pkg="pkg" @click="handlePackageClick"
|
||||||
v-for="pkg in packages"
|
@prepare="handlePrepare" />
|
||||||
:key="pkg.id"
|
|
||||||
:pkg="pkg"
|
|
||||||
@click="handlePackageClick"
|
|
||||||
@prepare="handlePrepare"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="empty-packages">
|
<div v-else class="empty-packages">
|
||||||
<InboxOutlined class="empty-icon" />
|
<InboxOutlined class="empty-icon" />
|
||||||
@ -166,7 +147,14 @@ const selectedCollection = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 筛选元数据
|
// 筛选元数据
|
||||||
const filterMeta = ref<FilterMetaResponse>({ grades: [], lessonTypes: [] });
|
const filterMeta = ref<FilterMetaResponse>({ 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<CoursePackage[]>([]);
|
const packages = ref<CoursePackage[]>([]);
|
||||||
@ -175,6 +163,7 @@ const loadingPackages = ref(false);
|
|||||||
// 筛选条件
|
// 筛选条件
|
||||||
const selectedGrade = ref('');
|
const selectedGrade = ref('');
|
||||||
const selectedLessonType = ref<string | undefined>(undefined);
|
const selectedLessonType = ref<string | undefined>(undefined);
|
||||||
|
const selectedThemeId = ref<number | undefined>(undefined);
|
||||||
const searchKeyword = ref('');
|
const searchKeyword = ref('');
|
||||||
|
|
||||||
// 描述展开
|
// 描述展开
|
||||||
@ -205,6 +194,7 @@ const selectCollection = async (collection: CourseCollection) => {
|
|||||||
// 重置筛选条件
|
// 重置筛选条件
|
||||||
selectedGrade.value = '';
|
selectedGrade.value = '';
|
||||||
selectedLessonType.value = undefined;
|
selectedLessonType.value = undefined;
|
||||||
|
selectedThemeId.value = undefined;
|
||||||
searchKeyword.value = '';
|
searchKeyword.value = '';
|
||||||
descExpanded.value = false;
|
descExpanded.value = false;
|
||||||
|
|
||||||
@ -234,7 +224,7 @@ const loadFilterMeta = async () => {
|
|||||||
filterMeta.value = await getFilterMeta(selectedCollectionId.value);
|
filterMeta.value = await getFilterMeta(selectedCollectionId.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取筛选元数据失败', 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, {
|
packages.value = await getPackages(selectedCollectionId.value, {
|
||||||
grade: selectedGrade.value || undefined,
|
grade: selectedGrade.value || undefined,
|
||||||
lessonType: selectedLessonType.value,
|
lessonType: selectedLessonType.value,
|
||||||
|
themeId: selectedThemeId.value,
|
||||||
keyword: searchKeyword.value || undefined,
|
keyword: searchKeyword.value || undefined,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -266,8 +257,8 @@ const handlePrepare = (pkg: CoursePackage) => {
|
|||||||
router.push(`/teacher/courses/${pkg.id}/prepare`);
|
router.push(`/teacher/courses/${pkg.id}/prepare`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听年级、课程配置变化
|
// 监听年级、课程配置、主题变化
|
||||||
watch([selectedGrade, selectedLessonType], () => {
|
watch([selectedGrade, selectedLessonType, selectedThemeId], () => {
|
||||||
loadPackages();
|
loadPackages();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -432,7 +423,7 @@ onMounted(() => {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-row + .filter-row {
|
.filter-row+.filter-row {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -88,13 +88,14 @@ const gradeText = computed(() => {
|
|||||||
return grades.join(' · ');
|
return grades.join(' · ');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 从 courses 提取课程类型列表(去重,与管理端一致)
|
// 从 courses 提取课程类型列表(去重,过滤导入课、集体课)
|
||||||
|
const EXCLUDED_LESSON_TYPES = new Set(['INTRODUCTION', 'INTRO', 'COLLECTIVE']);
|
||||||
const lessonTypes = computed(() => {
|
const lessonTypes = computed(() => {
|
||||||
const courses = props.pkg.courses || [];
|
const courses = props.pkg.courses || [];
|
||||||
const types = new Set<string>();
|
const types = new Set<string>();
|
||||||
for (const c of courses) {
|
for (const c of courses) {
|
||||||
const t = c.lessonType;
|
const t = c.lessonType;
|
||||||
if (t) types.add(t);
|
if (t && !EXCLUDED_LESSON_TYPES.has(t.toUpperCase())) types.add(t);
|
||||||
}
|
}
|
||||||
return Array.from(types);
|
return Array.from(types);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -51,8 +51,9 @@ public class SchoolPackageController {
|
|||||||
@PathVariable Long collectionId,
|
@PathVariable Long collectionId,
|
||||||
@RequestParam(required = false) String grade,
|
@RequestParam(required = false) String grade,
|
||||||
@RequestParam(required = false) String lessonType,
|
@RequestParam(required = false) String lessonType,
|
||||||
|
@RequestParam(required = false) Long themeId,
|
||||||
@RequestParam(required = false) String keyword) {
|
@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")
|
@GetMapping("/{collectionId}/filter-meta")
|
||||||
|
|||||||
@ -25,6 +25,9 @@ public class PackageFilterMetaResponse {
|
|||||||
@Schema(description = "课程配置选项列表(导入课、集体课、健康、科学等)")
|
@Schema(description = "课程配置选项列表(导入课、集体课、健康、科学等)")
|
||||||
private List<LessonTypeOption> lessonTypes;
|
private List<LessonTypeOption> lessonTypes;
|
||||||
|
|
||||||
|
@Schema(description = "课程包主题选项列表")
|
||||||
|
private List<ThemeOption> themes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 年级选项
|
* 年级选项
|
||||||
*/
|
*/
|
||||||
@ -59,4 +62,23 @@ public class PackageFilterMetaResponse {
|
|||||||
@Schema(description = "包含该类型环节的课程包数量")
|
@Schema(description = "包含该类型环节的课程包数量")
|
||||||
private Integer count;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,10 +40,11 @@ public interface CourseCollectionService extends IService<CourseCollection> {
|
|||||||
* @param collectionId 套餐ID
|
* @param collectionId 套餐ID
|
||||||
* @param grade 年级筛选
|
* @param grade 年级筛选
|
||||||
* @param lessonType 课程配置筛选(INTRODUCTION、COLLECTIVE、HEALTH、LANGUAGE、SCIENCE、SOCIAL、ART)
|
* @param lessonType 课程配置筛选(INTRODUCTION、COLLECTIVE、HEALTH、LANGUAGE、SCIENCE、SOCIAL、ART)
|
||||||
|
* @param themeId 课程包主题筛选
|
||||||
* @param keyword 关键词搜索
|
* @param keyword 关键词搜索
|
||||||
* @return 课程包列表
|
* @return 课程包列表
|
||||||
*/
|
*/
|
||||||
List<CoursePackageResponse> getPackagesByCollection(Long collectionId, String grade, String lessonType, String keyword);
|
List<CoursePackageResponse> getPackagesByCollection(Long collectionId, String grade, String lessonType, Long themeId, String keyword);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取套餐的筛选元数据(年级、课程配置选项)
|
* 获取套餐的筛选元数据(年级、课程配置选项)
|
||||||
|
|||||||
@ -213,8 +213,8 @@ public class CourseCollectionServiceImpl extends ServiceImpl<CourseCollectionMap
|
|||||||
* 获取课程套餐下的课程包列表(支持筛选)
|
* 获取课程套餐下的课程包列表(支持筛选)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<CoursePackageResponse> getPackagesByCollection(Long collectionId, String grade, String lessonType, String keyword) {
|
public List<CoursePackageResponse> getPackagesByCollection(Long collectionId, String grade, String lessonType, Long themeId, String keyword) {
|
||||||
log.info("获取课程套餐的课程包列表(筛选),collectionId={}, grade={}, lessonType={}, keyword={}", collectionId, grade, lessonType, keyword);
|
log.info("获取课程套餐的课程包列表(筛选),collectionId={}, grade={}, lessonType={}, themeId={}, keyword={}", collectionId, grade, lessonType, themeId, keyword);
|
||||||
|
|
||||||
// 查询关联关系
|
// 查询关联关系
|
||||||
List<CourseCollectionPackage> associations = collectionPackageMapper.selectList(
|
List<CourseCollectionPackage> associations = collectionPackageMapper.selectList(
|
||||||
@ -252,6 +252,11 @@ public class CourseCollectionServiceImpl extends ServiceImpl<CourseCollectionMap
|
|||||||
wrapper.apply("JSON_CONTAINS(grade_tags, {0})", "\"" + grade + "\"");
|
wrapper.apply("JSON_CONTAINS(grade_tags, {0})", "\"" + grade + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 课程包主题筛选
|
||||||
|
if (themeId != null) {
|
||||||
|
wrapper.eq(CoursePackage::getThemeId, themeId);
|
||||||
|
}
|
||||||
|
|
||||||
// 关键词搜索
|
// 关键词搜索
|
||||||
if (StringUtils.hasText(keyword)) {
|
if (StringUtils.hasText(keyword)) {
|
||||||
wrapper.and(w -> w
|
wrapper.and(w -> w
|
||||||
@ -315,6 +320,7 @@ public class CourseCollectionServiceImpl extends ServiceImpl<CourseCollectionMap
|
|||||||
return PackageFilterMetaResponse.builder()
|
return PackageFilterMetaResponse.builder()
|
||||||
.grades(new ArrayList<>())
|
.grades(new ArrayList<>())
|
||||||
.lessonTypes(new ArrayList<>())
|
.lessonTypes(new ArrayList<>())
|
||||||
|
.themes(new ArrayList<>())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,9 +379,32 @@ public class CourseCollectionServiceImpl extends ServiceImpl<CourseCollectionMap
|
|||||||
.build())
|
.build())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 统计课程包主题分布
|
||||||
|
Map<Long, Integer> themeCountMap = new HashMap<>();
|
||||||
|
Map<Long, String> 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<PackageFilterMetaResponse.ThemeOption> 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()
|
return PackageFilterMetaResponse.builder()
|
||||||
.grades(grades)
|
.grades(grades)
|
||||||
.lessonTypes(lessonTypes)
|
.lessonTypes(lessonTypes)
|
||||||
|
.themes(themes)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user