diff --git a/reading-platform-frontend/src/api/school-course.ts b/reading-platform-frontend/src/api/school-course.ts index add1320..9783ce9 100644 --- a/reading-platform-frontend/src/api/school-course.ts +++ b/reading-platform-frontend/src/api/school-course.ts @@ -232,8 +232,8 @@ export function getSchoolCourseFullDetail(id: number) { return api.schoolCourseControllerGetFullDetail(id) as any; } -export function getTeacherSchoolCourseFullDetail(id: number) { - return api.teacherSchoolCourseControllerGetFullDetail(id) as any; +export function getTeacherSchoolCourseFullDetail(id: number | string) { + return api.teacherSchoolCourseControllerGetFullDetail(id as any) as any; } // 更新校本课程包完整数据 diff --git a/reading-platform-frontend/src/api/teacher.ts b/reading-platform-frontend/src/api/teacher.ts index e501a48..a05ca17 100644 --- a/reading-platform-frontend/src/api/teacher.ts +++ b/reading-platform-frontend/src/api/teacher.ts @@ -57,23 +57,26 @@ export function getTeacherCourses(params: TeacherCourseQueryParams): Promise<{ pageSize: number; }> { // 使用 http 直接调用 API,后端返回 list 字段,需要转换为 items - return http.get<{ list: TeacherCourse[]; total: number; pageNum: number; pageSize: number }>('/v1/teacher/courses', { + return http.get<{ list?: TeacherCourse[]; records?: TeacherCourse[]; total?: number | string; pageNum?: number; pageSize?: number }>('/v1/teacher/courses', { params: { pageNum: params.pageNum, pageSize: params.pageSize, keyword: params.keyword, category: params.grade, }, - }).then(res => ({ - items: res.list || [], - total: res.total || 0, - page: res.pageNum || 1, - pageSize: res.pageSize || 10, - })); + }).then(res => { + const list = res.list ?? res.records ?? []; + return { + items: Array.isArray(list) ? list : [], + total: typeof res.total === 'string' ? parseInt(res.total, 10) || 0 : (res.total || 0), + page: res.pageNum || 1, + pageSize: res.pageSize || 10, + }; + }); } -// 获取课程详情 -export function getTeacherCourse(id: number): Promise { +// 获取课程详情(id 使用 string 避免 Long 精度丢失) +export function getTeacherCourse(id: number | string): Promise { return http.get(`/v1/teacher/courses/${id}`) as any; } @@ -153,9 +156,14 @@ export function getClassTeachers(classId: number): Promise { +// 获取单个授课记录详情(id 使用 string 避免 Long 精度丢失) +export function getLesson(id: number | string): Promise { return http.get(`/v1/teacher/lessons/${id}`) as any; } @@ -205,24 +213,24 @@ export function createLesson(data: CreateLessonDto): Promise { return http.post('/v1/teacher/lessons', data) as any; } -// 开始上课 -export function startLesson(id: number): Promise { +// 开始上课(id 使用 string 避免 Long 精度丢失) +export function startLesson(id: number | string): Promise { return http.post(`/v1/teacher/lessons/${id}/start`) as any; } -// 结束上课 -export function finishLesson(id: number, data: FinishLessonDto): Promise { +// 结束上课(id 使用 string 避免 Long 精度丢失) +export function finishLesson(id: number | string, data: FinishLessonDto): Promise { return http.post(`/v1/teacher/lessons/${id}/complete`, data) as any; } -// 取消课程 -export function cancelLesson(id: number): Promise { +// 取消课程(id 使用 string 避免 Long 精度丢失) +export function cancelLesson(id: number | string): Promise { return http.post(`/v1/teacher/lessons/${id}/cancel`) as any; } -// 保存学生评价记录 +// 保存学生评价记录(lessonId 使用 string 避免 Long 精度丢失) export function saveStudentRecord( - lessonId: number, + lessonId: number | string, studentId: number, data: StudentRecordDto ): Promise { @@ -253,13 +261,13 @@ export interface StudentRecordsResponse { students: StudentWithRecord[]; } -export function getStudentRecords(lessonId: number): Promise { +export function getStudentRecords(lessonId: number | string): Promise { return http.get(`/v1/teacher/lessons/${lessonId}/students/records`) as any; } -// 批量保存学生评价记录 +// 批量保存学生评价记录(lessonId 使用 string 避免 Long 精度丢失) export function batchSaveStudentRecords( - lessonId: number, + lessonId: number | string, records: Array<{ studentId: number } & StudentRecordDto> ): Promise<{ count: number; records: any[] }> { return http.post(`/v1/teacher/lessons/${lessonId}/students/batch-records`, { records }) as any; @@ -491,13 +499,13 @@ export interface SaveLessonProgressDto { progressData?: any; } -// 保存课程进度 -export function saveLessonProgress(lessonId: number, data: SaveLessonProgressDto): Promise { +// 保存课程进度(lessonId 使用 string 避免 Long 精度丢失) +export function saveLessonProgress(lessonId: number | string, data: SaveLessonProgressDto): Promise { return http.put(`/v1/teacher/lessons/${lessonId}/progress`, data) as any; } -// 获取课程进度 -export function getLessonProgress(lessonId: number): Promise { +// 获取课程进度(lessonId 使用 string 避免 Long 精度丢失) +export function getLessonProgress(lessonId: number | string): Promise { return http.get(`/v1/teacher/lessons/${lessonId}/progress`) as any; } diff --git a/reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue b/reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue index 5aa1b09..deb220a 100644 --- a/reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue +++ b/reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue @@ -803,7 +803,7 @@ const loadCourseDetail = async () => { loading.value = true; try { - const data = await teacherApi.getTeacherCourse(parseInt(courseId)); + const data = await teacherApi.getTeacherCourse(courseId); course.value = data; // 获取课程列表 diff --git a/reading-platform-frontend/src/views/teacher/courses/CourseListView.vue b/reading-platform-frontend/src/views/teacher/courses/CourseListView.vue index 1d9466a..ea6c1fb 100644 --- a/reading-platform-frontend/src/views/teacher/courses/CourseListView.vue +++ b/reading-platform-frontend/src/views/teacher/courses/CourseListView.vue @@ -63,11 +63,7 @@ placeholder="搜索课程名称..." style="width: 240px;" @search="handleFilterChange" - > - - + />
精彩绘本
-
+
- {{ course.avgRating.toFixed(1) }} + {{ (course.avgRating ?? 0).toFixed(1) }}
@@ -158,7 +154,7 @@
- +

暂无符合条件的课程

试试调整筛选条件吧

@@ -185,7 +181,7 @@ import { message } from 'ant-design-vue'; import { ClockCircleOutlined, TeamOutlined, - SearchOutlined, + InboxOutlined, BookOutlined, BookFilled, StarOutlined, @@ -236,6 +232,21 @@ const domainMap: Record = { MATH: '数学', math: '数学', }; +// 解析标签(后端可能返回 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 []; +}; + // 获取图片完整URL const getImageUrl = (path: string) => { if (!path) return ''; @@ -281,12 +292,19 @@ const loadCourses = async () => { } const data = await teacherApi.getTeacherCourses(params); - courses.value = (data.items || []).map((item: any) => ({ - ...item, - gradeTags: translateGradeTags(item.gradeTags || []), - domainTags: translateDomainTags(item.domainTags || []), - pictureUrl: item.coverImagePath, - })); + courses.value = (data.items || []).map((item: any) => { + const gradeTags = parseTags(item.gradeTags); + const domainTags = parseTags(item.domainTags); + return { + ...item, + gradeTags: translateGradeTags(gradeTags), + domainTags: translateDomainTags(domainTags), + duration: item.duration ?? item.durationMinutes ?? 0, + usageCount: item.usageCount ?? 0, + avgRating: item.avgRating ?? 0, + pictureUrl: item.coverImagePath ?? item.pictureUrl, + }; + }); pagination.total = data.total || 0; } catch (error: any) { message.error(error.message || '获取课程列表失败'); diff --git a/reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue b/reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue index 15a632d..6df2ad4 100644 --- a/reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue +++ b/reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue @@ -131,6 +131,7 @@ import { BookOpen } from 'lucide-vue-next'; import { message, Modal } from 'ant-design-vue'; import dayjs from 'dayjs'; import * as teacherApi from '@/api/teacher'; +import { useUserStore } from '@/stores/user'; import { getTeacherSchoolCourseFullDetail } from '@/api/school-course'; import { translateGradeTags } from '@/utils/tagMaps'; import FilePreviewModal from '@/components/FilePreviewModal.vue'; @@ -139,6 +140,7 @@ import PreparePreview from './components/PreparePreview.vue'; const router = useRouter(); const route = useRoute(); +const userStore = useUserStore(); const loading = ref(false); // 导航状态 @@ -152,7 +154,7 @@ const previewModalVisible = ref(false); const previewFileUrl = ref(''); const previewFileName = ref(''); -const courseId = ref(0); +const courseId = ref(''); const course = ref({}); const lessons = ref([]); const classes = ref([]); @@ -163,8 +165,23 @@ const scheduleModalVisible = ref(false); const scheduleLoading = ref(false); const scheduleDate = ref(undefined); +// 解析标签(后端可能返回 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 translatedGradeTags = computed(() => { - return translateGradeTags(course.value.gradeTags || []); + return translateGradeTags(parseTags(course.value.gradeTags)); }); const totalDuration = computed(() => { @@ -193,7 +210,7 @@ const getFileUrl = (filePath: string | null | undefined): string => { }; const loadCourseData = async () => { - courseId.value = parseInt(route.params.id as string); + courseId.value = (route.params.id as string) || ''; if (!courseId.value) return; loading.value = true; @@ -203,8 +220,8 @@ const loadCourseData = async () => { let data: any; if (isSchoolCourse) { - // 加载校本课程包 - const res = await getTeacherSchoolCourseFullDetail(courseId.value); + // 加载校本课程包(id 传 string 避免 Long 精度丢失) + const res = await getTeacherSchoolCourseFullDetail(courseId.value as any); data = res.data || res; // 转换校本课程包数据结构 @@ -259,11 +276,13 @@ const loadCourseData = async () => { }; }); } else { - // 加载标准课程包 + // 加载标准课程包(id 传 string 避免 Long 精度丢失) data = await teacherApi.getTeacherCourse(courseId.value); course.value = { ...data, courseLessons: data.courseLessons || [], + gradeTags: parseTags(data.gradeTags), + domainTags: parseTags(data.domainTags), }; // 转换课程数据格式以匹配前端组件期望 @@ -358,10 +377,20 @@ const startTeaching = async () => { cancelText: '取消', onOk: async () => { try { - // 创建授课记录 + const now = dayjs(); + const teacherId = userStore.user?.id; + if (!teacherId) { + message.error('未获取到教师信息,请重新登录'); + return; + } + // 创建授课记录(后端要求 teacherId、title、lessonDate) const lesson = await teacherApi.createLesson({ courseId: courseId.value, classId: selectedClassId.value!, + teacherId, + title: course.value.name || '授课', + lessonDate: now.format('YYYY-MM-DD'), + startTime: now.format('HH:mm'), }); // 开始上课 @@ -393,10 +422,20 @@ const confirmSchedule = async () => { scheduleLoading.value = true; try { + const teacherId = userStore.user?.id; + if (!teacherId) { + message.error('未获取到教师信息,请重新登录'); + scheduleLoading.value = false; + return; + } + const dt = scheduleDate.value!; await teacherApi.createLesson({ courseId: courseId.value, classId: selectedClassId.value!, - plannedDatetime: scheduleDate.value.toISOString(), + teacherId, + title: course.value.name || '授课', + lessonDate: dt.format('YYYY-MM-DD'), + startTime: dt.format('HH:mm'), }); message.success('预约成功,可在"上课记录"中查看'); diff --git a/reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue b/reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue index ac85e06..2afda7a 100644 --- a/reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue +++ b/reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue @@ -183,7 +183,7 @@ const loadData = async () => { error.value = ''; try { - const data = await teacherApi.getLesson(parseInt(lessonId)); + const data = await teacherApi.getLesson(lessonId); // 解析课程中的路径数组 const parsePathArray = (paths: any) => { diff --git a/reading-platform-frontend/src/views/teacher/lessons/LessonRecordsView.vue b/reading-platform-frontend/src/views/teacher/lessons/LessonRecordsView.vue index b36137b..15da6cb 100644 --- a/reading-platform-frontend/src/views/teacher/lessons/LessonRecordsView.vue +++ b/reading-platform-frontend/src/views/teacher/lessons/LessonRecordsView.vue @@ -237,7 +237,7 @@ import { const router = useRouter(); const route = useRoute(); -const lessonId = computed(() => Number(route.params.id)); +const lessonId = computed(() => (route.params.id as string) || ''); const loading = ref(false); const saving = ref(false); diff --git a/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue b/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue index 77e5e25..c392031 100644 --- a/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue +++ b/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue @@ -473,7 +473,7 @@ const showTimer = ref(false); const showNotesDrawer = ref(false); const currentLessonIndex = ref(0); const currentStepIndex = ref(0); -const lessonId = ref(0); +const lessonId = ref(''); // 文件预览相关 const previewModalVisible = ref(false); @@ -692,7 +692,7 @@ const getLessonStatus = (index: number): string => { }; const loadLessonData = async () => { - lessonId.value = parseInt(route.params.id as string); + lessonId.value = (route.params.id as string) || ''; if (!lessonId.value) return; loading.value = true;