feat: 教师课程中心 - 年级/领域/课程类型筛选与 lessonTags 展示
- 新增 lessonType 筛选参数,支持 SOCIAL/SOCIETY/DOMAIN_* 等格式兼容 - 列表接口返回 lessonTags(name,lessonType) 供 tag 展示 - 新增 LessonTagResponse DTO - 完善 tagMaps 与 LESSON_TYPE_NAMES 映射(INTRO/DOMAIN_*) - 修复筛选参数未传递到接口的问题 Made-with: Cursor
This commit is contained in:
parent
c652cda7a8
commit
20c500e921
@ -13,9 +13,17 @@ export interface TeacherCourseQueryParams {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
grade?: string;
|
||||
domain?: string;
|
||||
lessonType?: string;
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
/** 课程环节标签(列表展示) */
|
||||
export interface LessonTag {
|
||||
name: string;
|
||||
lessonType: string;
|
||||
}
|
||||
|
||||
export interface TeacherCourse {
|
||||
id: number;
|
||||
name: string;
|
||||
@ -23,6 +31,7 @@ export interface TeacherCourse {
|
||||
coverImagePath?: string;
|
||||
gradeTags: string[];
|
||||
domainTags: string[];
|
||||
lessonTags?: LessonTag[];
|
||||
duration: number;
|
||||
avgRating: number;
|
||||
usageCount: number;
|
||||
@ -62,7 +71,9 @@ export function getTeacherCourses(params: TeacherCourseQueryParams): Promise<{
|
||||
pageNum: params.pageNum,
|
||||
pageSize: params.pageSize,
|
||||
keyword: params.keyword,
|
||||
category: params.grade,
|
||||
grade: params.grade,
|
||||
domain: params.domain,
|
||||
lessonType: params.lessonType,
|
||||
},
|
||||
}).then(res => {
|
||||
const list = res.list ?? res.records ?? [];
|
||||
|
||||
@ -6,114 +6,121 @@
|
||||
// 年级标签映射(英文 → 中文)
|
||||
export const GRADE_TAG_MAP: Record<string, string> = {
|
||||
// 大写格式
|
||||
SMALL: '小班',
|
||||
MIDDLE: '中班',
|
||||
BIG: '大班',
|
||||
SMALL: "小班",
|
||||
MIDDLE: "中班",
|
||||
BIG: "大班",
|
||||
// 小写格式
|
||||
small: '小班',
|
||||
middle: '中班',
|
||||
big: '大班',
|
||||
small: "小班",
|
||||
middle: "中班",
|
||||
big: "大班",
|
||||
};
|
||||
|
||||
// 领域标签映射(英文 → 中文)- 导出供其他模块使用
|
||||
export const DOMAIN_TAG_MAP: Record<string, string> = {
|
||||
// 旧格式(大写)
|
||||
LANGUAGE: '语言',
|
||||
SCIENCE: '科学',
|
||||
SOCIAL: '社会',
|
||||
ART: '艺术',
|
||||
HEALTH: '健康',
|
||||
MATH: '数学',
|
||||
LANGUAGE: "语言",
|
||||
SCIENCE: "科学",
|
||||
SOCIAL: "社会",
|
||||
ART: "艺术",
|
||||
HEALTH: "健康",
|
||||
MATH: "数学",
|
||||
|
||||
// 课程环节/领域 DOMAIN_ 前缀格式(后端返回)
|
||||
DOMAIN_HEALTH: "健康",
|
||||
DOMAIN_LANGUAGE: "语言",
|
||||
DOMAIN_SOCIAL: "社会",
|
||||
DOMAIN_SCIENCE: "科学",
|
||||
DOMAIN_ART: "艺术",
|
||||
|
||||
// 活动类型作为领域的兼容映射(历史数据兼容)
|
||||
family: '亲子活动',
|
||||
FAMILY: '亲子活动',
|
||||
class: '课堂活动',
|
||||
CLASS: '课堂活动',
|
||||
outdoor: '户外活动',
|
||||
OUTDOOR: '户外活动',
|
||||
handicraft: '手工活动',
|
||||
HANDICRAFT: '手工活动',
|
||||
game: '游戏活动',
|
||||
GAME: '游戏活动',
|
||||
music: '音乐活动',
|
||||
MUSIC: '音乐活动',
|
||||
exploration: '探索活动',
|
||||
EXPLORATION: '探索活动',
|
||||
sports: '运动活动',
|
||||
SPORTS: '运动活动',
|
||||
art: '艺术活动',
|
||||
family: "亲子活动",
|
||||
FAMILY: "亲子活动",
|
||||
class: "课堂活动",
|
||||
CLASS: "课堂活动",
|
||||
outdoor: "户外活动",
|
||||
OUTDOOR: "户外活动",
|
||||
handicraft: "手工活动",
|
||||
HANDICRAFT: "手工活动",
|
||||
game: "游戏活动",
|
||||
GAME: "游戏活动",
|
||||
music: "音乐活动",
|
||||
MUSIC: "音乐活动",
|
||||
exploration: "探索活动",
|
||||
EXPLORATION: "探索活动",
|
||||
sports: "运动活动",
|
||||
SPORTS: "运动活动",
|
||||
art: "艺术活动",
|
||||
|
||||
// 新格式(细分领域)
|
||||
// 健康领域
|
||||
health_motor: '身体动作发展',
|
||||
health_hygiene: '生活习惯与能力',
|
||||
HEALTH_MOTOR: '身体动作发展',
|
||||
HEALTH_HYGIENE: '生活习惯与能力',
|
||||
health_motor: "身体动作发展",
|
||||
health_hygiene: "生活习惯与能力",
|
||||
HEALTH_MOTOR: "身体动作发展",
|
||||
HEALTH_HYGIENE: "生活习惯与能力",
|
||||
|
||||
// 语言领域
|
||||
lang_listen: '倾听与表达',
|
||||
lang_read: '早期阅读',
|
||||
LANG_LISTEN: '倾听与表达',
|
||||
LANG_READ: '早期阅读',
|
||||
language_communication: '语言',
|
||||
LANGUAGE_COMMUNICATION: '语言',
|
||||
lang_listen: "倾听与表达",
|
||||
lang_read: "早期阅读",
|
||||
LANG_LISTEN: "倾听与表达",
|
||||
LANG_READ: "早期阅读",
|
||||
language_communication: "语言",
|
||||
LANGUAGE_COMMUNICATION: "语言",
|
||||
|
||||
// 社会领域
|
||||
social_interact: '人际交往',
|
||||
social_adapt: '社会适应',
|
||||
SOCIAL_INTERACT: '人际交往',
|
||||
SOCIAL_ADAPT: '社会适应',
|
||||
social_emotional: '社会',
|
||||
SOCIAL_EMOTIONAL: '社会',
|
||||
social_interact: "人际交往",
|
||||
social_adapt: "社会适应",
|
||||
SOCIAL_INTERACT: "人际交往",
|
||||
SOCIAL_ADAPT: "社会适应",
|
||||
social_emotional: "社会",
|
||||
SOCIAL_EMOTIONAL: "社会",
|
||||
|
||||
// 科学领域
|
||||
science_explore: '科学探究',
|
||||
math_cog: '数学认知',
|
||||
SCIENCE_EXPLORE: '科学探究',
|
||||
MATH_COG: '数学认知',
|
||||
science_exploration: '科学',
|
||||
SCIENCE_EXPLORATION: '科学',
|
||||
science_explore: "科学探究",
|
||||
math_cog: "数学认知",
|
||||
SCIENCE_EXPLORE: "科学探究",
|
||||
MATH_COG: "数学认知",
|
||||
science_exploration: "科学",
|
||||
SCIENCE_EXPLORATION: "科学",
|
||||
|
||||
// 艺术领域
|
||||
art_music: '音乐表现',
|
||||
art_create: '美术创作',
|
||||
ART_MUSIC: '音乐表现',
|
||||
ART_CREATE: '美术创作',
|
||||
art_creativity: '艺术',
|
||||
ART_CREATIVITY: '艺术',
|
||||
art_music: "音乐表现",
|
||||
art_create: "美术创作",
|
||||
ART_MUSIC: "音乐表现",
|
||||
ART_CREATE: "美术创作",
|
||||
art_creativity: "艺术",
|
||||
ART_CREATIVITY: "艺术",
|
||||
};
|
||||
|
||||
// 年级标签颜色配置
|
||||
export const GRADE_TAG_COLORS: Record<string, { bg: string; text: string }> = {
|
||||
'小班': { bg: '#FFE4E8', text: '#E85A71' },
|
||||
'中班': { bg: '#E3F2FD', text: '#1976D2' },
|
||||
'大班': { bg: '#FFF8E1', text: '#F9A825' },
|
||||
小班: { bg: "#FFE4E8", text: "#E85A71" },
|
||||
中班: { bg: "#E3F2FD", text: "#1976D2" },
|
||||
大班: { bg: "#FFF8E1", text: "#F9A825" },
|
||||
};
|
||||
|
||||
// 领域标签颜色配置
|
||||
export const DOMAIN_TAG_COLORS: Record<string, { bg: string; text: string }> = {
|
||||
'语言': { bg: '#F3E5F5', text: '#8E24AA' },
|
||||
'倾听与表达': { bg: '#F3E5F5', text: '#8E24AA' },
|
||||
'早期阅读': { bg: '#EDE7F6', text: '#7B1FA2' },
|
||||
语言: { bg: "#F3E5F5", text: "#8E24AA" },
|
||||
倾听与表达: { bg: "#F3E5F5", text: "#8E24AA" },
|
||||
早期阅读: { bg: "#EDE7F6", text: "#7B1FA2" },
|
||||
|
||||
'科学': { bg: '#E8F5E9', text: '#43A047' },
|
||||
'科学探究': { bg: '#E8F5E9', text: '#43A047' },
|
||||
'数学认知': { bg: '#F1F8E9', text: '#558B2F' },
|
||||
科学: { bg: "#E8F5E9", text: "#43A047" },
|
||||
科学探究: { bg: "#E8F5E9", text: "#43A047" },
|
||||
数学认知: { bg: "#F1F8E9", text: "#558B2F" },
|
||||
|
||||
'社会': { bg: '#E0F7FA', text: '#0097A7' },
|
||||
'人际交往': { bg: '#E0F7FA', text: '#0097A7' },
|
||||
'社会适应': { bg: '#E0F2F1', text: '#00695C' },
|
||||
社会: { bg: "#E0F7FA", text: "#0097A7" },
|
||||
人际交往: { bg: "#E0F7FA", text: "#0097A7" },
|
||||
社会适应: { bg: "#E0F2F1", text: "#00695C" },
|
||||
|
||||
'艺术': { bg: '#FFF3E0', text: '#FB8C00' },
|
||||
'音乐表现': { bg: '#FFF3E0', text: '#FB8C00' },
|
||||
'美术创作': { bg: '#FBE9E7', text: '#E64A19' },
|
||||
艺术: { bg: "#FFF3E0", text: "#FB8C00" },
|
||||
音乐表现: { bg: "#FFF3E0", text: "#FB8C00" },
|
||||
美术创作: { bg: "#FBE9E7", text: "#E64A19" },
|
||||
|
||||
'健康': { bg: '#FFEBEE', text: '#E53935' },
|
||||
'身体动作发展': { bg: '#FFEBEE', text: '#E53935' },
|
||||
'生活习惯与能力': { bg: '#FFCDD2', text: '#C62828' },
|
||||
健康: { bg: "#FFEBEE", text: "#E53935" },
|
||||
身体动作发展: { bg: "#FFEBEE", text: "#E53935" },
|
||||
生活习惯与能力: { bg: "#FFCDD2", text: "#C62828" },
|
||||
|
||||
'数学': { bg: '#FFF8E1', text: '#F9A825' },
|
||||
数学: { bg: "#FFF8E1", text: "#F9A825" },
|
||||
};
|
||||
|
||||
/**
|
||||
@ -147,24 +154,32 @@ export function translateDomainTags(tags: string[]): string[] {
|
||||
/**
|
||||
* 获取年级标签样式
|
||||
*/
|
||||
export function getGradeTagStyle(tag: string): { background: string; color: string; border: string } {
|
||||
const colors = GRADE_TAG_COLORS[tag] || { bg: '#F0F0F0', text: '#666' };
|
||||
export function getGradeTagStyle(tag: string): {
|
||||
background: string;
|
||||
color: string;
|
||||
border: string;
|
||||
} {
|
||||
const colors = GRADE_TAG_COLORS[tag] || { bg: "#F0F0F0", text: "#666" };
|
||||
return {
|
||||
background: colors.bg,
|
||||
color: colors.text,
|
||||
border: 'none',
|
||||
border: "none",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取领域标签样式
|
||||
*/
|
||||
export function getDomainTagStyle(tag: string): { background: string; color: string; border: string } {
|
||||
const colors = DOMAIN_TAG_COLORS[tag] || { bg: '#F0F0F0', text: '#666' };
|
||||
export function getDomainTagStyle(tag: string): {
|
||||
background: string;
|
||||
color: string;
|
||||
border: string;
|
||||
} {
|
||||
const colors = DOMAIN_TAG_COLORS[tag] || { bg: "#F0F0F0", text: "#666" };
|
||||
return {
|
||||
background: colors.bg,
|
||||
color: colors.text,
|
||||
border: 'none',
|
||||
border: "none",
|
||||
};
|
||||
}
|
||||
|
||||
@ -173,45 +188,48 @@ export function getDomainTagStyle(tag: string): { background: string; color: str
|
||||
// 活动类型映射(英文 → 中文)
|
||||
export const ACTIVITY_TYPE_MAP: Record<string, string> = {
|
||||
// 大写格式
|
||||
HANDICRAFT: '手工活动',
|
||||
GAME: '游戏活动',
|
||||
MUSIC: '音乐活动',
|
||||
EXPLORATION: '探索活动',
|
||||
SPORTS: '运动活动',
|
||||
OUTDOOR: '户外活动',
|
||||
FAMILY: '家庭延伸',
|
||||
ART: '美工活动',
|
||||
OTHER: '其他',
|
||||
HANDICRAFT: "手工活动",
|
||||
GAME: "游戏活动",
|
||||
MUSIC: "音乐活动",
|
||||
EXPLORATION: "探索活动",
|
||||
SPORTS: "运动活动",
|
||||
OUTDOOR: "户外活动",
|
||||
FAMILY: "家庭延伸",
|
||||
ART: "美工活动",
|
||||
OTHER: "其他",
|
||||
|
||||
// 小写格式
|
||||
handicraft: '手工活动',
|
||||
game: '游戏活动',
|
||||
music: '音乐活动',
|
||||
exploration: '探索活动',
|
||||
sports: '运动活动',
|
||||
outdoor: '户外活动',
|
||||
family: '家庭延伸',
|
||||
art: '美工活动',
|
||||
other: '其他',
|
||||
handicraft: "手工活动",
|
||||
game: "游戏活动",
|
||||
music: "音乐活动",
|
||||
exploration: "探索活动",
|
||||
sports: "运动活动",
|
||||
outdoor: "户外活动",
|
||||
family: "家庭延伸",
|
||||
art: "美工活动",
|
||||
other: "其他",
|
||||
|
||||
// 其他格式
|
||||
class: '课堂活动',
|
||||
class: "课堂活动",
|
||||
};
|
||||
|
||||
// 活动类型颜色配置
|
||||
export const ACTIVITY_TYPE_COLORS: Record<string, { bg: string; text: string }> = {
|
||||
'手工活动': { bg: '#F3E5F5', text: '#8E24AA' },
|
||||
'美工活动': { bg: '#F3E5F5', text: '#8E24AA' },
|
||||
'游戏活动': { bg: '#FFF3E0', text: '#FB8C00' },
|
||||
'音乐活动': { bg: '#E3F2FD', text: '#1976D2' },
|
||||
'运动活动': { bg: '#E8F5E9', text: '#43A047' },
|
||||
'探索活动': { bg: '#E0F7FA', text: '#0097A7' },
|
||||
'户外活动': { bg: '#F1F8E9', text: '#558B2F' },
|
||||
'亲子活动': { bg: '#FCE4EC', text: '#C2185B' },
|
||||
'家庭延伸': { bg: '#E3F2FD', text: '#1976D2' },
|
||||
'课堂活动': { bg: '#FFF8E1', text: '#F9A825' },
|
||||
'艺术活动': { bg: '#FBE9E7', text: '#E64A19' },
|
||||
'其他': { bg: '#F5F5F5', text: '#666666' },
|
||||
export const ACTIVITY_TYPE_COLORS: Record<
|
||||
string,
|
||||
{ bg: string; text: string }
|
||||
> = {
|
||||
手工活动: { bg: "#F3E5F5", text: "#8E24AA" },
|
||||
美工活动: { bg: "#F3E5F5", text: "#8E24AA" },
|
||||
游戏活动: { bg: "#FFF3E0", text: "#FB8C00" },
|
||||
音乐活动: { bg: "#E3F2FD", text: "#1976D2" },
|
||||
运动活动: { bg: "#E8F5E9", text: "#43A047" },
|
||||
探索活动: { bg: "#E0F7FA", text: "#0097A7" },
|
||||
户外活动: { bg: "#F1F8E9", text: "#558B2F" },
|
||||
亲子活动: { bg: "#FCE4EC", text: "#C2185B" },
|
||||
家庭延伸: { bg: "#E3F2FD", text: "#1976D2" },
|
||||
课堂活动: { bg: "#FFF8E1", text: "#F9A825" },
|
||||
艺术活动: { bg: "#FBE9E7", text: "#E64A19" },
|
||||
其他: { bg: "#F5F5F5", text: "#666666" },
|
||||
};
|
||||
|
||||
/**
|
||||
@ -224,12 +242,16 @@ export function translateActivityType(type: string): string {
|
||||
/**
|
||||
* 获取活动类型样式
|
||||
*/
|
||||
export function getActivityTypeStyle(type: string): { background: string; color: string; border: string } {
|
||||
const colors = ACTIVITY_TYPE_COLORS[type] || { bg: '#F0F0F0', text: '#666' };
|
||||
export function getActivityTypeStyle(type: string): {
|
||||
background: string;
|
||||
color: string;
|
||||
border: string;
|
||||
} {
|
||||
const colors = ACTIVITY_TYPE_COLORS[type] || { bg: "#F0F0F0", text: "#666" };
|
||||
return {
|
||||
background: colors.bg,
|
||||
color: colors.text,
|
||||
border: 'none',
|
||||
border: "none",
|
||||
};
|
||||
}
|
||||
|
||||
@ -245,50 +267,50 @@ export function translateActivityDomain(domain: string): string {
|
||||
// 步骤类型映射(英文 → 中文)
|
||||
export const STEP_TYPE_MAP: Record<string, string> = {
|
||||
// 大写格式
|
||||
TEACHING: '教学',
|
||||
READING: '共读',
|
||||
DISCUSSION: '讨论',
|
||||
ACTIVITY: '活动',
|
||||
GAME: '游戏',
|
||||
SUMMARY: '总结',
|
||||
WARMUP: '热身',
|
||||
PRACTICE: '练习',
|
||||
INTERACTION: '互动',
|
||||
TEACHING: "教学",
|
||||
READING: "共读",
|
||||
DISCUSSION: "讨论",
|
||||
ACTIVITY: "活动",
|
||||
GAME: "游戏",
|
||||
SUMMARY: "总结",
|
||||
WARMUP: "热身",
|
||||
PRACTICE: "练习",
|
||||
INTERACTION: "互动",
|
||||
// 新增环节类型
|
||||
INTRODUCTION: '导入',
|
||||
CREATIVE: '创作',
|
||||
CUSTOM: '自定义',
|
||||
INTRODUCTION: "导入",
|
||||
CREATIVE: "创作",
|
||||
CUSTOM: "自定义",
|
||||
|
||||
// 小写格式
|
||||
teaching: '教学',
|
||||
reading: '共读',
|
||||
discussion: '讨论',
|
||||
activity: '活动',
|
||||
game: '游戏',
|
||||
summary: '总结',
|
||||
warmup: '热身',
|
||||
practice: '练习',
|
||||
interaction: '互动',
|
||||
introduction: '导入',
|
||||
creative: '创作',
|
||||
custom: '自定义',
|
||||
teaching: "教学",
|
||||
reading: "共读",
|
||||
discussion: "讨论",
|
||||
activity: "活动",
|
||||
game: "游戏",
|
||||
summary: "总结",
|
||||
warmup: "热身",
|
||||
practice: "练习",
|
||||
interaction: "互动",
|
||||
introduction: "导入",
|
||||
creative: "创作",
|
||||
custom: "自定义",
|
||||
};
|
||||
|
||||
// 步骤类型颜色配置
|
||||
export const STEP_TYPE_COLORS: Record<string, { bg: string; text: string }> = {
|
||||
'教学': { bg: '#E3F2FD', text: '#1976D2' },
|
||||
'共读': { bg: '#F3E5F5', text: '#8E24AA' },
|
||||
'阅读': { bg: '#F3E5F5', text: '#8E24AA' },
|
||||
'讨论': { bg: '#E0F7FA', text: '#0097A7' },
|
||||
'活动': { bg: '#FFF3E0', text: '#FB8C00' },
|
||||
'游戏': { bg: '#FCE4EC', text: '#C2185B' },
|
||||
'总结': { bg: '#E8F5E9', text: '#43A047' },
|
||||
'热身': { bg: '#FFF8E1', text: '#F9A825' },
|
||||
'练习': { bg: '#EDE7F6', text: '#673AB7' },
|
||||
'互动': { bg: '#FBE9E7', text: '#E64A19' },
|
||||
'导入': { bg: '#E8F5E9', text: '#4CAF50' },
|
||||
'创作': { bg: '#FCE4EC', text: '#E91E63' },
|
||||
'自定义': { bg: '#ECEFF1', text: '#607D8B' },
|
||||
教学: { bg: "#E3F2FD", text: "#1976D2" },
|
||||
共读: { bg: "#F3E5F5", text: "#8E24AA" },
|
||||
阅读: { bg: "#F3E5F5", text: "#8E24AA" },
|
||||
讨论: { bg: "#E0F7FA", text: "#0097A7" },
|
||||
活动: { bg: "#FFF3E0", text: "#FB8C00" },
|
||||
游戏: { bg: "#FCE4EC", text: "#C2185B" },
|
||||
总结: { bg: "#E8F5E9", text: "#43A047" },
|
||||
热身: { bg: "#FFF8E1", text: "#F9A825" },
|
||||
练习: { bg: "#EDE7F6", text: "#673AB7" },
|
||||
互动: { bg: "#FBE9E7", text: "#E64A19" },
|
||||
导入: { bg: "#E8F5E9", text: "#4CAF50" },
|
||||
创作: { bg: "#FCE4EC", text: "#E91E63" },
|
||||
自定义: { bg: "#ECEFF1", text: "#607D8B" },
|
||||
};
|
||||
|
||||
/**
|
||||
@ -301,12 +323,16 @@ export function translateStepType(type: string): string {
|
||||
/**
|
||||
* 获取步骤类型样式
|
||||
*/
|
||||
export function getStepTypeStyle(type: string): { background: string; color: string; border: string } {
|
||||
const colors = STEP_TYPE_COLORS[type] || { bg: '#F0F0F0', text: '#666' };
|
||||
export function getStepTypeStyle(type: string): {
|
||||
background: string;
|
||||
color: string;
|
||||
border: string;
|
||||
} {
|
||||
const colors = STEP_TYPE_COLORS[type] || { bg: "#F0F0F0", text: "#666" };
|
||||
return {
|
||||
background: colors.bg,
|
||||
color: colors.text,
|
||||
border: 'none',
|
||||
border: "none",
|
||||
};
|
||||
}
|
||||
|
||||
@ -314,29 +340,32 @@ export function getStepTypeStyle(type: string): { background: string; color: str
|
||||
|
||||
// 课程状态映射(英文 → 中文)
|
||||
export const COURSE_STATUS_MAP: Record<string, string> = {
|
||||
DRAFT: '草稿',
|
||||
PENDING: '审核中',
|
||||
REJECTED: '已驳回',
|
||||
PUBLISHED: '已发布',
|
||||
ARCHIVED: '已下架',
|
||||
REVIEWING: '审核中',
|
||||
DRAFT: "草稿",
|
||||
PENDING: "审核中",
|
||||
REJECTED: "已驳回",
|
||||
PUBLISHED: "已发布",
|
||||
ARCHIVED: "已下架",
|
||||
REVIEWING: "审核中",
|
||||
|
||||
// 小写格式
|
||||
draft: '草稿',
|
||||
pending: '审核中',
|
||||
rejected: '已驳回',
|
||||
published: '已发布',
|
||||
archived: '已下架',
|
||||
reviewing: '审核中',
|
||||
draft: "草稿",
|
||||
pending: "审核中",
|
||||
rejected: "已驳回",
|
||||
published: "已发布",
|
||||
archived: "已下架",
|
||||
reviewing: "审核中",
|
||||
};
|
||||
|
||||
// 课程状态颜色配置
|
||||
export const COURSE_STATUS_COLORS: Record<string, { bg: string; text: string }> = {
|
||||
'草稿': { bg: '#F5F5F5', text: '#666666' },
|
||||
'审核中': { bg: '#E3F2FD', text: '#1976D2' },
|
||||
'已驳回': { bg: '#FFEBEE', text: '#E53935' },
|
||||
'已发布': { bg: '#E8F5E9', text: '#43A047' },
|
||||
'已下架': { bg: '#FFF8E1', text: '#F9A825' },
|
||||
export const COURSE_STATUS_COLORS: Record<
|
||||
string,
|
||||
{ bg: string; text: string }
|
||||
> = {
|
||||
草稿: { bg: "#F5F5F5", text: "#666666" },
|
||||
审核中: { bg: "#E3F2FD", text: "#1976D2" },
|
||||
已驳回: { bg: "#FFEBEE", text: "#E53935" },
|
||||
已发布: { bg: "#E8F5E9", text: "#43A047" },
|
||||
已下架: { bg: "#FFF8E1", text: "#F9A825" },
|
||||
};
|
||||
|
||||
/**
|
||||
@ -349,13 +378,20 @@ export function translateCourseStatus(status: string): string {
|
||||
/**
|
||||
* 获取课程状态样式
|
||||
*/
|
||||
export function getCourseStatusStyle(status: string): { background: string; color: string; border: string } {
|
||||
export function getCourseStatusStyle(status: string): {
|
||||
background: string;
|
||||
color: string;
|
||||
border: string;
|
||||
} {
|
||||
const chineseStatus = COURSE_STATUS_MAP[status] || status;
|
||||
const colors = COURSE_STATUS_COLORS[chineseStatus] || { bg: '#F0F0F0', text: '#666' };
|
||||
const colors = COURSE_STATUS_COLORS[chineseStatus] || {
|
||||
bg: "#F0F0F0",
|
||||
text: "#666",
|
||||
};
|
||||
return {
|
||||
background: colors.bg,
|
||||
color: colors.text,
|
||||
border: 'none',
|
||||
border: "none",
|
||||
};
|
||||
}
|
||||
|
||||
@ -363,22 +399,22 @@ export function getCourseStatusStyle(status: string): { background: string; colo
|
||||
|
||||
// 资源类型映射
|
||||
export const RESOURCE_TYPE_MAP: Record<string, string> = {
|
||||
EBOOK: '电子绘本',
|
||||
AUDIO: '音频',
|
||||
VIDEO: '视频',
|
||||
PPT: 'PPT课件',
|
||||
POSTER: '教学挂图',
|
||||
OTHER: '其他资源',
|
||||
IMAGE: '图片',
|
||||
EBOOK: "电子绘本",
|
||||
AUDIO: "音频",
|
||||
VIDEO: "视频",
|
||||
PPT: "PPT课件",
|
||||
POSTER: "教学挂图",
|
||||
OTHER: "其他资源",
|
||||
IMAGE: "图片",
|
||||
|
||||
// 已中文的不转换
|
||||
'电子绘本': '电子绘本',
|
||||
'音频': '音频',
|
||||
'视频': '视频',
|
||||
'PPT课件': 'PPT课件',
|
||||
'教学挂图': '教学挂图',
|
||||
'其他资源': '其他资源',
|
||||
'图片': '图片',
|
||||
电子绘本: "电子绘本",
|
||||
音频: "音频",
|
||||
视频: "视频",
|
||||
PPT课件: "PPT课件",
|
||||
教学挂图: "教学挂图",
|
||||
其他资源: "其他资源",
|
||||
图片: "图片",
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -22,13 +22,8 @@
|
||||
<div class="filter-bar">
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">年级</span>
|
||||
<a-select
|
||||
v-model:value="filters.grade"
|
||||
placeholder="全部年级"
|
||||
style="width: 120px;"
|
||||
allowClear
|
||||
@change="handleFilterChange"
|
||||
>
|
||||
<a-select v-model:value="filters.grade" placeholder="全部年级" style="width: 120px;" allowClear
|
||||
@change="handleFilterChange">
|
||||
<a-select-option value="小班">
|
||||
小班
|
||||
</a-select-option>
|
||||
@ -38,42 +33,36 @@
|
||||
<a-select-option value="大班">
|
||||
大班
|
||||
</a-select-option>
|
||||
<a-select-option value="混合">混合</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<span class="filter-label">领域</span>
|
||||
<a-select
|
||||
v-model:value="filters.domain"
|
||||
placeholder="全部领域"
|
||||
style="width: 120px;"
|
||||
allowClear
|
||||
@change="handleFilterChange"
|
||||
>
|
||||
<a-select-option value="健康">健康</a-select-option>
|
||||
<a-select-option value="语言">语言</a-select-option>
|
||||
<a-select-option value="社会">社会</a-select-option>
|
||||
<a-select-option value="科学">科学</a-select-option>
|
||||
<a-select-option value="艺术">艺术</a-select-option>
|
||||
<span class="filter-label">课程类型</span>
|
||||
<a-select v-model:value="filters.lessonType" placeholder="全部类型" style="width: 130px;" allowClear
|
||||
@change="handleFilterChange">
|
||||
<a-select-option value="INTRODUCTION">导入课</a-select-option>
|
||||
<a-select-option value="COLLECTIVE">集体课</a-select-option>
|
||||
<a-select-option value="LANGUAGE">语言</a-select-option>
|
||||
<a-select-option value="HEALTH">健康</a-select-option>
|
||||
<a-select-option value="SCIENCE">科学</a-select-option>
|
||||
<a-select-option value="SOCIAL">社会</a-select-option>
|
||||
<a-select-option value="ART">艺术</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="filter-item search-box">
|
||||
<a-input-search
|
||||
v-model:value="filters.keyword"
|
||||
placeholder="搜索课程名称..."
|
||||
style="width: 240px;"
|
||||
@search="handleFilterChange"
|
||||
/>
|
||||
<a-input-search v-model:value="filters.keyword" placeholder="搜索课程名称..." style="width: 240px;"
|
||||
@search="handleFilterChange" />
|
||||
</div>
|
||||
<div class="filter-item filter-right">
|
||||
<a-select
|
||||
v-model:value="filters.sort"
|
||||
style="width: 130px;"
|
||||
@change="handleFilterChange"
|
||||
>
|
||||
<a-select-option value="popular"><FireOutlined /> 最受欢迎</a-select-option>
|
||||
<a-select-option value="newest"><StarOutlined /> 最新发布</a-select-option>
|
||||
<a-select-option value="rating"><StarFilled /> 评分最高</a-select-option>
|
||||
<a-select v-model:value="filters.sort" style="width: 130px;" @change="handleFilterChange">
|
||||
<a-select-option value="popular">
|
||||
<FireOutlined /> 最受欢迎
|
||||
</a-select-option>
|
||||
<a-select-option value="newest">
|
||||
<StarOutlined /> 最新发布
|
||||
</a-select-option>
|
||||
<a-select-option value="rating">
|
||||
<StarFilled /> 评分最高
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</div>
|
||||
@ -81,26 +70,21 @@
|
||||
<!-- 课程列表 -->
|
||||
<a-spin :spinning="loading">
|
||||
<div class="course-grid">
|
||||
<div
|
||||
v-for="course in courses"
|
||||
:key="course.id"
|
||||
class="course-card"
|
||||
@click="viewCourseDetail(course)"
|
||||
>
|
||||
<div v-for="course in courses" :key="course.id" class="course-card" @click="viewCourseDetail(course)">
|
||||
<!-- 封面区域 -->
|
||||
<div class="course-cover">
|
||||
<img
|
||||
v-if="course.pictureUrl"
|
||||
:src="getImageUrl(course.pictureUrl)"
|
||||
class="cover-image"
|
||||
/>
|
||||
<img v-if="course.pictureUrl" :src="getImageUrl(course.pictureUrl)" class="cover-image" />
|
||||
<div v-else class="cover-placeholder">
|
||||
<div class="placeholder-icon"><BookFilled /></div>
|
||||
<div class="placeholder-icon">
|
||||
<BookFilled />
|
||||
</div>
|
||||
<div class="placeholder-text">精彩绘本</div>
|
||||
</div>
|
||||
<!-- 评分徽章 -->
|
||||
<div class="rating-badge" v-if="(course.avgRating ?? 0) > 0">
|
||||
<span class="rating-star"><StarFilled /></span>
|
||||
<span class="rating-star">
|
||||
<StarFilled />
|
||||
</span>
|
||||
<span class="rating-value">{{ (course.avgRating ?? 0).toFixed(1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -112,21 +96,15 @@
|
||||
<BookOutlined /> {{ course.pictureBookName }}
|
||||
</p>
|
||||
|
||||
<!-- 标签区域 -->
|
||||
<!-- 标签区域:年级 + 领域 + 课程环节(lessonTags) -->
|
||||
<div class="course-tags">
|
||||
<a-tag
|
||||
v-for="tag in course.gradeTags"
|
||||
:key="'g-' + tag"
|
||||
:style="getGradeTagStyle(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</a-tag>
|
||||
<a-tag
|
||||
v-for="tag in course.domainTags"
|
||||
:key="'d-' + tag"
|
||||
:style="getDomainTagStyle(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
<a-tag v-for="tag in (course.gradeTags || [])" :key="'g-' + tag" size="small"
|
||||
:style="getGradeTagStyle(tag)">{{ tag }}</a-tag>
|
||||
<a-tag v-for="tag in (course.domainTags || [])" :key="'d-' + tag" size="small"
|
||||
:style="getDomainTagStyle(tag)">{{ tag }}</a-tag>
|
||||
<a-tag v-for="(lt, idx) in (course.lessonTags || [])" :key="'l-' + idx" size="small"
|
||||
:style="getLessonTagStyle(lt.lessonType)">
|
||||
{{ getLessonTypeName(lt.lessonType) }}
|
||||
</a-tag>
|
||||
</div>
|
||||
|
||||
@ -144,7 +122,9 @@
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<button class="prepare-btn" @click.stop="prepareCourse(course)">
|
||||
<span class="btn-icon"><EditOutlined /></span>
|
||||
<span class="btn-icon">
|
||||
<EditOutlined />
|
||||
</span>
|
||||
开始备课
|
||||
</button>
|
||||
</div>
|
||||
@ -162,13 +142,8 @@
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper" v-if="pagination.total > pagination.pageSize">
|
||||
<a-pagination
|
||||
v-model:current="pagination.current"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
show-less-items
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
<a-pagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize"
|
||||
:total="pagination.total" show-less-items @change="handlePageChange" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
@ -203,6 +178,7 @@ const loading = ref(false);
|
||||
const filters = reactive({
|
||||
grade: undefined as string | undefined,
|
||||
domain: undefined as string | undefined,
|
||||
lessonType: undefined as string | undefined,
|
||||
keyword: '',
|
||||
sort: 'popular',
|
||||
});
|
||||
@ -215,23 +191,6 @@ const pagination = reactive({
|
||||
|
||||
const courses = ref<any[]>([]);
|
||||
|
||||
// 年级映射(用于筛选)
|
||||
const gradeMap: Record<string, string> = {
|
||||
SMALL: '小班', small: '小班',
|
||||
MIDDLE: '中班', middle: '中班',
|
||||
BIG: '大班', big: '大班',
|
||||
};
|
||||
|
||||
// 领域映射(用于筛选)
|
||||
const domainMap: Record<string, string> = {
|
||||
LANGUAGE: '语言', language: '语言', lang_listen: '语言', lang_read: '语言',
|
||||
SCIENCE: '科学', science: '科学', science_explore: '科学', math_cog: '数学',
|
||||
SOCIAL: '社会', social: '社会', social_interact: '社会', social_adapt: '社会',
|
||||
ART: '艺术', art: '艺术', art_music: '艺术', art_create: '艺术',
|
||||
HEALTH: '健康', health: '健康', health_motor: '健康', health_hygiene: '健康',
|
||||
MATH: '数学', math: '数学',
|
||||
};
|
||||
|
||||
// 解析标签(与套餐管理 parseGradeLevels 对齐,兼容多种格式)
|
||||
const parseTags = (val: any): string[] => {
|
||||
if (!val) return [];
|
||||
@ -273,32 +232,79 @@ const handlePageChange = () => {
|
||||
loadCourses();
|
||||
};
|
||||
|
||||
// 课程环节类型:英文码 -> 中文展示名(兼容 INTRODUCTION/INTRO、DOMAIN_* 等后端格式)
|
||||
const LESSON_TYPE_NAMES: Record<string, string> = {
|
||||
INTRODUCTION: '导入课',
|
||||
INTRO: '导入课',
|
||||
COLLECTIVE: '集体课',
|
||||
LANGUAGE: '语言',
|
||||
HEALTH: '健康',
|
||||
SCIENCE: '科学',
|
||||
SOCIAL: '社会',
|
||||
ART: '艺术',
|
||||
DOMAIN_HEALTH: '健康',
|
||||
DOMAIN_LANGUAGE: '语言',
|
||||
DOMAIN_SOCIAL: '社会',
|
||||
DOMAIN_SCIENCE: '科学',
|
||||
DOMAIN_ART: '艺术',
|
||||
};
|
||||
const getLessonTypeName = (type: string) => LESSON_TYPE_NAMES[type] || type;
|
||||
|
||||
// 课程环节标签样式(按类型区分颜色)
|
||||
const getLessonTagStyle = (type: string) => {
|
||||
const colors: Record<string, { background: string; color: string }> = {
|
||||
INTRODUCTION: { background: '#E8F5E9', color: '#2E7D32' },
|
||||
INTRO: { background: '#E8F5E9', color: '#2E7D32' },
|
||||
COLLECTIVE: { background: '#E3F2FD', color: '#1565C0' },
|
||||
LANGUAGE: { background: '#F3E5F5', color: '#7B1FA2' },
|
||||
HEALTH: { background: '#FFEBEE', color: '#C62828' },
|
||||
SCIENCE: { background: '#E8F5E9', color: '#388E3C' },
|
||||
SOCIAL: { background: '#E0F7FA', color: '#00838F' },
|
||||
ART: { background: '#FFF3E0', color: '#E65100' },
|
||||
DOMAIN_HEALTH: { background: '#FFEBEE', color: '#C62828' },
|
||||
DOMAIN_LANGUAGE: { background: '#F3E5F5', color: '#7B1FA2' },
|
||||
DOMAIN_SOCIAL: { background: '#E0F7FA', color: '#00838F' },
|
||||
DOMAIN_SCIENCE: { background: '#E8F5E9', color: '#388E3C' },
|
||||
DOMAIN_ART: { background: '#FFF3E0', color: '#E65100' },
|
||||
};
|
||||
const c = colors[type] || { background: '#F5F5F5', color: '#666' };
|
||||
return { background: c.background, color: c.color, border: 'none' };
|
||||
};
|
||||
|
||||
// 五大领域:中文 -> 后端 domainTags 存储的英文码(用于筛选)
|
||||
const DOMAIN_TO_CODE: Record<string, string> = {
|
||||
健康: 'HEALTH',
|
||||
语言: 'LANGUAGE',
|
||||
社会: 'SOCIAL',
|
||||
科学: 'SCIENCE',
|
||||
艺术: 'ART',
|
||||
};
|
||||
|
||||
const loadCourses = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params: any = {
|
||||
const params: teacherApi.TeacherCourseQueryParams = {
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
};
|
||||
|
||||
if (filters.keyword) {
|
||||
params.keyword = filters.keyword;
|
||||
if (filters.keyword?.trim()) {
|
||||
params.keyword = filters.keyword.trim();
|
||||
}
|
||||
|
||||
// 年级筛选映射
|
||||
// 年级筛选:直接传小班/中班/大班,后端匹配 gradeTags
|
||||
if (filters.grade) {
|
||||
const gradeKey = Object.keys(gradeMap).find((key) => gradeMap[key] === filters.grade);
|
||||
if (gradeKey) {
|
||||
params.grade = gradeKey;
|
||||
}
|
||||
params.grade = filters.grade;
|
||||
}
|
||||
|
||||
// 领域筛选映射
|
||||
// 领域筛选:传英文码,后端匹配 domainTags(五大领域课配置)
|
||||
if (filters.domain) {
|
||||
const domainKey = Object.keys(domainMap).find((key) => domainMap[key] === filters.domain);
|
||||
if (domainKey) {
|
||||
params.domain = domainKey;
|
||||
}
|
||||
params.domain = DOMAIN_TO_CODE[filters.domain] ?? filters.domain;
|
||||
}
|
||||
|
||||
// 课程类型筛选:传 lessonType,后端匹配 course_lesson
|
||||
if (filters.lessonType) {
|
||||
params.lessonType = filters.lessonType;
|
||||
}
|
||||
|
||||
const data = await teacherApi.getTeacherCourses(params);
|
||||
@ -309,6 +315,7 @@ const loadCourses = async () => {
|
||||
...item,
|
||||
gradeTags: translateGradeTags(gradeTags),
|
||||
domainTags: translateDomainTags(domainTags),
|
||||
lessonTags: item.lessonTags || [],
|
||||
duration: item.duration ?? item.durationMinutes ?? 0,
|
||||
usageCount: item.usageCount ?? 0,
|
||||
avgRating: item.avgRating ?? 0,
|
||||
|
||||
@ -36,7 +36,7 @@ public class SchoolCourseController {
|
||||
@RequestParam(required = false) String grade) {
|
||||
log.info("获取学校课程包列表,keyword={}, grade={}", keyword, grade);
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
List<CoursePackage> courses = courseService.getTenantPackageCourses(tenantId, keyword, grade);
|
||||
List<CoursePackage> courses = courseService.getTenantPackageCourses(tenantId, keyword, grade, null);
|
||||
List<SchoolCourseResponse> list = courses.stream()
|
||||
.map(this::toSchoolCourseResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@ -11,6 +11,7 @@ import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.common.security.SecurityUtils;
|
||||
import com.reading.platform.dto.response.ClassResponse;
|
||||
import com.reading.platform.dto.response.CourseResponse;
|
||||
import com.reading.platform.dto.response.LessonTagResponse;
|
||||
import com.reading.platform.dto.response.StudentResponse;
|
||||
import com.reading.platform.dto.response.TeacherResponse;
|
||||
import com.reading.platform.entity.ClassTeacher;
|
||||
@ -18,7 +19,9 @@ import com.reading.platform.entity.Clazz;
|
||||
import com.reading.platform.entity.CoursePackage;
|
||||
import com.reading.platform.entity.Student;
|
||||
import com.reading.platform.entity.Teacher;
|
||||
import com.reading.platform.entity.CourseLesson;
|
||||
import com.reading.platform.service.ClassService;
|
||||
import com.reading.platform.service.CourseLessonService;
|
||||
import com.reading.platform.service.CoursePackageService;
|
||||
import com.reading.platform.service.StudentService;
|
||||
import com.reading.platform.service.TeacherService;
|
||||
@ -39,6 +42,7 @@ import java.util.stream.Collectors;
|
||||
public class TeacherCourseController {
|
||||
|
||||
private final CoursePackageService courseService;
|
||||
private final CourseLessonService courseLessonService;
|
||||
private final ClassService classService;
|
||||
private final StudentService studentService;
|
||||
private final TeacherService teacherService;
|
||||
@ -67,12 +71,24 @@ public class TeacherCourseController {
|
||||
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String category) {
|
||||
@RequestParam(required = false) String grade,
|
||||
@RequestParam(required = false) String domain,
|
||||
@RequestParam(required = false) String lessonType) {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
// 按 学校 -> 套餐 -> 课程包 层级查询教师可用课程
|
||||
// 按 学校 -> 套餐 -> 课程包 层级查询,支持 grade/domain/lessonType 筛选
|
||||
Page<CoursePackage> page = courseService.getTenantPackageCoursePage(
|
||||
tenantId, pageNum, pageSize, keyword, category, CourseStatus.PUBLISHED.getCode());
|
||||
tenantId, pageNum, pageSize, keyword, grade, domain, lessonType, CourseStatus.PUBLISHED.getCode());
|
||||
List<CourseResponse> voList = courseMapper.toVO(page.getRecords());
|
||||
// 填充 lessonTags(仅 name、lessonType)供列表 tag 展示
|
||||
for (CourseResponse vo : voList) {
|
||||
List<CourseLesson> lessons = courseLessonService.findByCourseId(vo.getId());
|
||||
vo.setLessonTags(lessons.stream()
|
||||
.map(l -> LessonTagResponse.builder()
|
||||
.name(l.getName())
|
||||
.lessonType(l.getLessonType())
|
||||
.build())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
|
||||
}
|
||||
|
||||
@ -80,10 +96,11 @@ public class TeacherCourseController {
|
||||
@GetMapping("/courses/all")
|
||||
public Result<List<CourseResponse>> getAllCourses(
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String category) {
|
||||
@RequestParam(required = false) String grade,
|
||||
@RequestParam(required = false) String domain) {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
// 按 学校 -> 套餐 -> 课程包 层级查询
|
||||
List<CoursePackage> courses = courseService.getTenantPackageCourses(tenantId, keyword, category);
|
||||
List<CoursePackage> courses = courseService.getTenantPackageCourses(tenantId, keyword, grade, domain);
|
||||
return Result.success(courseMapper.toVO(courses));
|
||||
}
|
||||
|
||||
|
||||
@ -197,4 +197,7 @@ public class CourseResponse {
|
||||
|
||||
@Schema(description = "关联的课程环节")
|
||||
private List<CourseLessonResponse> courseLessons;
|
||||
|
||||
@Schema(description = "课程环节标签(列表展示用,仅 name 和 lessonType)")
|
||||
private List<LessonTagResponse> lessonTags;
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.reading.platform.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 课程环节标签(列表展示用,仅 name 和 lessonType)
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "课程环节标签")
|
||||
public class LessonTagResponse {
|
||||
|
||||
@Schema(description = "环节名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "环节类型:INTRODUCTION、COLLECTIVE、LANGUAGE、HEALTH、SCIENCE、SOCIAL、ART")
|
||||
private String lessonType;
|
||||
}
|
||||
@ -14,7 +14,10 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 课程环节服务
|
||||
@ -57,6 +60,49 @@ public class CourseLessonService extends ServiceImpl<CourseLessonMapper, CourseL
|
||||
return lesson;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 lessonType 查询包含该类型环节的课程 ID 列表
|
||||
* 兼容多种存储格式:SOCIAL/SOCIETY/DOMAIN_SOCIAL、INTRODUCTION/INTRO 等
|
||||
*/
|
||||
public List<Long> findCourseIdsByLessonType(String lessonType) {
|
||||
List<String> typesToMatch = resolveLessonTypeVariants(lessonType);
|
||||
if (typesToMatch.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
LambdaQueryWrapper<CourseLesson> wrapper = new LambdaQueryWrapper<CourseLesson>()
|
||||
.select(CourseLesson::getCourseId);
|
||||
if (typesToMatch.size() == 1) {
|
||||
wrapper.eq(CourseLesson::getLessonType, typesToMatch.get(0));
|
||||
} else {
|
||||
wrapper.in(CourseLesson::getLessonType, typesToMatch);
|
||||
}
|
||||
return courseLessonMapper.selectList(wrapper).stream()
|
||||
.map(CourseLesson::getCourseId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将前端传入的 lessonType 解析为数据库中可能存储的多种格式
|
||||
*/
|
||||
private List<String> resolveLessonTypeVariants(String lessonType) {
|
||||
if (lessonType == null || lessonType.isBlank()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return switch (lessonType.toUpperCase()) {
|
||||
case "SOCIAL" -> Arrays.asList("SOCIAL", "SOCIETY", "DOMAIN_SOCIAL");
|
||||
case "SOCIETY" -> Arrays.asList("SOCIAL", "SOCIETY", "DOMAIN_SOCIAL");
|
||||
case "SCIENCE" -> Arrays.asList("SCIENCE", "DOMAIN_SCIENCE");
|
||||
case "LANGUAGE" -> Arrays.asList("LANGUAGE", "DOMAIN_LANGUAGE");
|
||||
case "HEALTH" -> Arrays.asList("HEALTH", "DOMAIN_HEALTH");
|
||||
case "ART" -> Arrays.asList("ART", "DOMAIN_ART");
|
||||
case "INTRODUCTION" -> Arrays.asList("INTRODUCTION", "INTRO");
|
||||
case "INTRO" -> Arrays.asList("INTRODUCTION", "INTRO");
|
||||
case "COLLECTIVE" -> Collections.singletonList("COLLECTIVE");
|
||||
default -> Collections.singletonList(lessonType);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类型查询课程环节
|
||||
*/
|
||||
|
||||
@ -61,22 +61,25 @@ public interface CoursePackageService extends com.baomidou.mybatisplus.extension
|
||||
*
|
||||
* @param tenantId 租户 ID
|
||||
* @param keyword 关键词(课程名称、绘本名称,可选)
|
||||
* @param grade 年级筛选(小班/中班/大班 或 small/middle/big,可选)
|
||||
* @param grade 年级筛选(小班/中班/大班,可选)
|
||||
* @param domain 领域筛选(健康/语言/社会/科学/艺术 或对应英文码,可选)
|
||||
*/
|
||||
List<CoursePackage> getTenantPackageCourses(Long tenantId, String keyword, String grade);
|
||||
List<CoursePackage> getTenantPackageCourses(Long tenantId, String keyword, String grade, String domain);
|
||||
|
||||
/**
|
||||
* 按 学校 -> 套餐 -> 课程包 层级分页查询教师可用课程
|
||||
* 教师端课程中心应使用此方法,通过租户已购买的套餐获取课程包
|
||||
*
|
||||
* @param tenantId 租户(学校)ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @param keyword 关键词(课程名称、绘本名称,可选)
|
||||
* @param grade 年级/领域筛选(可选)
|
||||
* @param status 课程状态(如 PUBLISHED)
|
||||
* @param tenantId 租户(学校)ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @param keyword 关键词(课程名称、绘本名称,可选)
|
||||
* @param grade 年级筛选(小班/中班/大班 或 SMALL/MIDDLE/BIG,可选)
|
||||
* @param domain 领域筛选(健康/语言/社会/科学/艺术 或 HEALTH/LANGUAGE/SOCIAL/SCIENCE/ART,可选)
|
||||
* @param lessonType 课程环节类型筛选(INTRODUCTION、COLLECTIVE、LANGUAGE、HEALTH、SCIENCE、SOCIAL、ART,可选)
|
||||
* @param status 课程状态(如 PUBLISHED)
|
||||
*/
|
||||
Page<CoursePackage> getTenantPackageCoursePage(Long tenantId, Integer pageNum, Integer pageSize,
|
||||
String keyword, String grade, String status);
|
||||
String keyword, String grade, String domain, String lessonType, String status);
|
||||
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ import org.springframework.util.StringUtils;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -226,7 +227,7 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CoursePackage> getTenantPackageCourses(Long tenantId, String keyword, String grade) {
|
||||
public List<CoursePackage> getTenantPackageCourses(Long tenantId, String keyword, String grade, String domain) {
|
||||
List<Long> collectionIds = tenantPackageMapper.selectList(
|
||||
new LambdaQueryWrapper<TenantPackage>()
|
||||
.eq(TenantPackage::getTenantId, tenantId)
|
||||
@ -271,13 +272,21 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
||||
.or().like(CoursePackage::getGradeTags, "\"" + grade + "\"")
|
||||
.or().like(CoursePackage::getGradeTags, "\"" + gradeLower + "\""));
|
||||
}
|
||||
if (StringUtils.hasText(domain)) {
|
||||
String domainLower = domain.toLowerCase();
|
||||
wrapper.and(w -> w
|
||||
.like(CoursePackage::getDomainTags, domain)
|
||||
.or().like(CoursePackage::getDomainTags, domainLower)
|
||||
.or().like(CoursePackage::getDomainTags, "\"" + domain + "\"")
|
||||
.or().like(CoursePackage::getDomainTags, "\"" + domainLower + "\""));
|
||||
}
|
||||
wrapper.orderByDesc(CoursePackage::getUsageCount);
|
||||
return coursePackageMapper.selectList(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<CoursePackage> getTenantPackageCoursePage(Long tenantId, Integer pageNum, Integer pageSize,
|
||||
String keyword, String grade, String status) {
|
||||
String keyword, String grade, String domain, String lessonType, String status) {
|
||||
int current = pageNum != null && pageNum > 0 ? pageNum : 1;
|
||||
int size = pageSize != null && pageSize > 0 ? pageSize : 10;
|
||||
|
||||
@ -309,7 +318,17 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
||||
return new Page<>(current, size, 0);
|
||||
}
|
||||
|
||||
// 3. 分页查询课程包
|
||||
// 2.5 lessonType 筛选:仅保留包含该类型环节的课程包
|
||||
if (StringUtils.hasText(lessonType)) {
|
||||
Set<Long> courseIdsWithLesson = courseLessonService.findCourseIdsByLessonType(lessonType).stream()
|
||||
.collect(Collectors.toSet());
|
||||
packageIds = packageIds.stream().filter(courseIdsWithLesson::contains).collect(Collectors.toList());
|
||||
if (packageIds.isEmpty()) {
|
||||
return new Page<>(current, size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 分页查询课程包(gradeTags 年级筛选,domainTags 领域筛选)
|
||||
Page<CoursePackage> page = new Page<>(current, size);
|
||||
LambdaQueryWrapper<CoursePackage> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.in(CoursePackage::getId, packageIds)
|
||||
@ -321,15 +340,23 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
|
||||
.or().like(CoursePackage::getPictureBookName, keyword)
|
||||
.or().like(CoursePackage::getCode, keyword));
|
||||
}
|
||||
// 年级筛选:匹配 gradeTags(支持中文小班/中班/大班 或英文 SMALL/MIDDLE/BIG)
|
||||
if (StringUtils.hasText(grade)) {
|
||||
String gradeLower = grade.toLowerCase();
|
||||
wrapper.and(w -> w
|
||||
.like(CoursePackage::getGradeTags, grade)
|
||||
.or().like(CoursePackage::getGradeTags, gradeLower)
|
||||
.or().like(CoursePackage::getGradeTags, "\"" + grade + "\"")
|
||||
.or().like(CoursePackage::getGradeTags, "\"" + gradeLower + "\"")
|
||||
.or().like(CoursePackage::getDomainTags, grade)
|
||||
.or().like(CoursePackage::getDomainTags, gradeLower));
|
||||
.or().like(CoursePackage::getGradeTags, "\"" + gradeLower + "\""));
|
||||
}
|
||||
// 领域筛选:匹配 domainTags(支持五大领域中文或英文码 HEALTH/LANGUAGE/SOCIAL/SCIENCE/ART)
|
||||
if (StringUtils.hasText(domain)) {
|
||||
String domainLower = domain.toLowerCase();
|
||||
wrapper.and(w -> w
|
||||
.like(CoursePackage::getDomainTags, domain)
|
||||
.or().like(CoursePackage::getDomainTags, domainLower)
|
||||
.or().like(CoursePackage::getDomainTags, "\"" + domain + "\"")
|
||||
.or().like(CoursePackage::getDomainTags, "\"" + domainLower + "\""));
|
||||
}
|
||||
wrapper.orderByDesc(CoursePackage::getUsageCount);
|
||||
return coursePackageMapper.selectPage(page, wrapper);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user