- 后端:theme 表增加 color 字段;主题创建/更新/课程响应返回 themeColor - 前端:主题管理页颜色选择器与列表;管理端课程列表与详情主题 Tag - 课程中心/课程包卡片展示主题 Tag,course-center 规范化接口字段 - 隐藏管理端课程配置列与筛选;课程详情关联主题使用 themeName/color Made-with: Cursor
149 lines
3.8 KiB
TypeScript
149 lines
3.8 KiB
TypeScript
import { http } from './index';
|
|
|
|
// ============= 类型定义 =============
|
|
|
|
/** 套餐信息 */
|
|
export interface CourseCollection {
|
|
id: number;
|
|
name: string;
|
|
description?: string;
|
|
packageCount: number;
|
|
gradeLevels?: string[];
|
|
status: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
}
|
|
|
|
/** 课程包中的课程项(用于提取课程配置) */
|
|
export interface CoursePackageCourseItem {
|
|
id?: number;
|
|
name?: string;
|
|
lessonType?: string;
|
|
}
|
|
|
|
/** 课程包信息 */
|
|
export interface CoursePackage {
|
|
id: number;
|
|
name: string;
|
|
description?: string;
|
|
coverImagePath?: string;
|
|
pictureBookName?: string;
|
|
gradeTags: string[];
|
|
domainTags?: string[]; // 不再展示,由课程配置替代
|
|
courses?: CoursePackageCourseItem[]; // 用于课程配置展示
|
|
themeId?: number;
|
|
themeName?: string;
|
|
/** 主题颜色(hex),来自主题字典 */
|
|
themeColor?: string;
|
|
durationMinutes?: number;
|
|
usageCount?: number;
|
|
avgRating?: number;
|
|
sortOrder?: number;
|
|
}
|
|
|
|
/** 筛选元数据 - 年级选项 */
|
|
export interface GradeOption {
|
|
label: string;
|
|
count: number;
|
|
}
|
|
|
|
/** 筛选元数据 - 课程配置选项 */
|
|
export interface LessonTypeOption {
|
|
lessonType: string;
|
|
name: string;
|
|
count: number;
|
|
}
|
|
|
|
/** 筛选元数据 - 课程包主题选项 */
|
|
export interface ThemeOption {
|
|
themeId: number;
|
|
name: string;
|
|
count: number;
|
|
}
|
|
|
|
/** 筛选元数据响应 */
|
|
export interface FilterMetaResponse {
|
|
grades: GradeOption[];
|
|
lessonTypes: LessonTypeOption[];
|
|
themes?: ThemeOption[];
|
|
}
|
|
|
|
// ============= 响应规范化(学校端/教师端共用同一接口,兼容字段形态) =============
|
|
|
|
function normalizeGradeTags(raw: unknown): string[] {
|
|
if (raw == null) return [];
|
|
if (Array.isArray(raw)) return raw.map(String).filter(Boolean);
|
|
if (typeof raw === 'string' && raw.trim()) {
|
|
const s = raw.trim();
|
|
if (s.startsWith('[')) {
|
|
try {
|
|
const j = JSON.parse(s);
|
|
return Array.isArray(j) ? j.map(String).filter(Boolean) : [];
|
|
} catch {
|
|
/* fallthrough */
|
|
}
|
|
}
|
|
return s.split(',').map((x) => x.trim()).filter(Boolean);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/** 统一课程包卡片所需字段,避免学校端/代理层返回 snake_case 或嵌套 theme 时主题不显示 */
|
|
export function normalizeCoursePackage(raw: any): CoursePackage {
|
|
if (!raw || typeof raw !== 'object') {
|
|
return raw as CoursePackage;
|
|
}
|
|
const nested =
|
|
raw.theme && typeof raw.theme === 'object' ? (raw.theme as Record<string, unknown>) : null;
|
|
const themeName = String(
|
|
raw.themeName ?? raw.theme_name ?? nested?.name ?? ''
|
|
).trim();
|
|
const themeColor = (raw.themeColor ?? raw.theme_color ?? nested?.color) as string | undefined;
|
|
const themeId = (raw.themeId ?? raw.theme_id ?? nested?.id) as number | undefined;
|
|
|
|
return {
|
|
...raw,
|
|
themeId: themeId ?? raw.themeId,
|
|
themeName: themeName || undefined,
|
|
themeColor: themeColor || undefined,
|
|
gradeTags: normalizeGradeTags(raw.gradeTags ?? raw.grade_tags),
|
|
};
|
|
}
|
|
|
|
// ============= API 接口 =============
|
|
|
|
/**
|
|
* 获取租户的课程套餐列表
|
|
*/
|
|
export function getCollections(): Promise<CourseCollection[]> {
|
|
return http.get<CourseCollection[]>('/v1/school/packages');
|
|
}
|
|
|
|
/**
|
|
* 获取套餐下的课程包列表(支持筛选)
|
|
*/
|
|
export function getPackages(
|
|
collectionId: number,
|
|
params?: {
|
|
grade?: string;
|
|
lessonType?: string;
|
|
themeId?: number;
|
|
keyword?: string;
|
|
}
|
|
): Promise<CoursePackage[]> {
|
|
return http
|
|
.get<any[]>(`/v1/school/packages/${collectionId}/packages`, {
|
|
params,
|
|
})
|
|
.then((list) =>
|
|
Array.isArray(list) ? list.map((item) => normalizeCoursePackage(item)) : []
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 获取套餐的筛选元数据
|
|
*/
|
|
export function getFilterMeta(collectionId: number): Promise<FilterMetaResponse> {
|
|
return http.get<FilterMetaResponse>(`/v1/school/packages/${collectionId}/filter-meta`);
|
|
}
|