diff --git a/lesingle-edu-reading-platform-frontend/src/api/generated/mutator.ts b/lesingle-edu-reading-platform-frontend/src/api/generated/mutator.ts index e050dbe..c5ffd42 100644 --- a/lesingle-edu-reading-platform-frontend/src/api/generated/mutator.ts +++ b/lesingle-edu-reading-platform-frontend/src/api/generated/mutator.ts @@ -1,4 +1,4 @@ -import axios, {type AxiosRequestConfig, type AxiosResponse} from "axios"; +import axios, { type AxiosRequestConfig, type AxiosResponse } from "axios"; /** * 创建默认 Axios 实例 @@ -14,6 +14,7 @@ const axiosInstance = axios.create({ */ axiosInstance.interceptors.response.use( async (response) => { + // console.error("请求响应-1:", response); const { data, config } = response; // 处理 Blob 响应(Orval 默认使用 blob) @@ -33,7 +34,7 @@ axiosInstance.interceptors.response.use( return jsonData.data; } // 业务错误码,抛出错误 - const error: any = new Error(jsonData.message || '请求失败'); + const error: any = new Error(jsonData.message || "请求失败"); error.response = { data: jsonData, status: 200 }; error.code = jsonData.code; return Promise.reject(error); @@ -54,7 +55,7 @@ axiosInstance.interceptors.response.use( return data.data; } // 业务错误码,抛出错误 - const error: any = new Error(data.message || '请求失败'); + const error: any = new Error(data.message || "请求失败"); error.response = { data, status: 200 }; error.code = data.code; return Promise.reject(error); @@ -62,10 +63,32 @@ axiosInstance.interceptors.response.use( return data; }, - (error) => { + async (error) => { const status = error.response?.status; - const message = error.response?.data?.message || error.message; + // axios 在 responseType=blob 且非 2xx 时,error.response.data 往往是 Blob(即使实际是 JSON) + const maybeBlob = error.response?.data; + if (maybeBlob instanceof Blob) { + try { + const text = await maybeBlob.text(); + const jsonData = JSON.parse(text); + if (jsonData && typeof jsonData === "object") { + // 统一把解析后的 JSON 写回,便于业务侧 error.response.data.message 读取 + error.response.data = jsonData; + // 同步 Error.message,便于直接用 error.message 展示 + if ( + typeof (jsonData as any).message === "string" && + (jsonData as any).message + ) { + error.message = (jsonData as any).message; + } + } + } catch { + // ignore: 不是 JSON 或解析失败 + } + } + const message = error.response?.data?.message || error.message; + // console.error("请求失败-1:", error); switch (status) { case 401: localStorage.removeItem("token"); diff --git a/lesingle-edu-reading-platform-frontend/src/api/index.ts b/lesingle-edu-reading-platform-frontend/src/api/index.ts index 60322b7..67d1310 100644 --- a/lesingle-edu-reading-platform-frontend/src/api/index.ts +++ b/lesingle-edu-reading-platform-frontend/src/api/index.ts @@ -1,19 +1,19 @@ -import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; -import { message } from 'ant-design-vue'; +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; +import { message } from "ant-design-vue"; // 创建axios实例 const request: AxiosInstance = axios.create({ - baseURL: '/api', // 使用 /api 作为统一前缀,超管端路径包含 /v1 + baseURL: "/api", // 使用 /api 作为统一前缀,超管端路径包含 /v1 timeout: 30000, headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, }); // 请求拦截器 request.interceptors.request.use( (config) => { - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); if (token) { config.headers.Authorization = `Bearer ${token}`; } @@ -21,18 +21,19 @@ request.interceptors.request.use( }, (error) => { return Promise.reject(error); - } + }, ); // 响应拦截器 request.interceptors.response.use( (response: AxiosResponse) => { + // console.error("响应拦截器-1:", response); const { data } = response; // 如果是标准响应格式 { code, message, data } - if (typeof data === 'object' && data !== null && 'code' in data) { + if (typeof data === "object" && data !== null && "code" in data) { // 业务错误码非 200 时抛出错误 if (data.code !== 200) { - const error: any = new Error(data.message || '请求失败'); + const error: any = new Error(data.message || "请求失败"); error.response = response; error.code = data.code; return Promise.reject(error); @@ -44,6 +45,7 @@ request.interceptors.response.use( return data; }, (error) => { + // console.error("响应拦截器错误-1:", error); const { response } = error; if (response) { @@ -51,49 +53,49 @@ request.interceptors.response.use( switch (status) { case 401: - message.error('登录已过期,请重新登录'); - localStorage.removeItem('token'); - localStorage.removeItem('user'); - localStorage.removeItem('role'); - localStorage.removeItem('name'); - window.location.href = '/login'; + message.error("登录已过期,请重新登录"); + localStorage.removeItem("token"); + localStorage.removeItem("user"); + localStorage.removeItem("role"); + localStorage.removeItem("name"); + window.location.href = "/login"; break; case 403: // 区分 token 过期/无效和权限不足的场景 // 如果是 token 问题导致的 403,跳转到登录页 - if (data && typeof data === 'object' && 'code' in data) { + if (data && typeof data === "object" && "code" in data) { const errorCode = data.code; // token 过期或无效时跳转到登录页 if (errorCode === 401 || errorCode === 403) { - message.error(data.message || '登录已过期,请重新登录'); - localStorage.removeItem('token'); - localStorage.removeItem('user'); - localStorage.removeItem('role'); - localStorage.removeItem('name'); - window.location.href = '/login'; + message.error(data.message || "登录已过期,请重新登录"); + localStorage.removeItem("token"); + localStorage.removeItem("user"); + localStorage.removeItem("role"); + localStorage.removeItem("name"); + window.location.href = "/login"; break; } } // 其他情况视为权限不足,显示提示但不跳转 - message.error(data?.message || '没有权限访问'); + message.error(data?.message || "没有权限访问"); break; case 404: - message.error('请求的资源不存在'); + message.error("请求的资源不存在"); break; case 500: - message.error(data?.message || '服务器错误'); + message.error(data?.message || "服务器错误"); break; default: - message.error(data?.message || '请求失败'); + message.error(data?.message || "请求失败"); } - } else if (error.code === 'ECONNABORTED') { - message.error('请求超时'); + } else if (error.code === "ECONNABORTED") { + message.error("请求超时"); } else { - message.error('网络错误'); + message.error("网络错误"); } return Promise.reject(error); - } + }, ); // 导出请求方法 @@ -105,11 +107,19 @@ export const http = { return request.get(url, config); }, - post(url: string, data?: any, config?: AxiosRequestConfig): Promise { + post( + url: string, + data?: any, + config?: AxiosRequestConfig, + ): Promise { return request.post(url, data, config); }, - put(url: string, data?: any, config?: AxiosRequestConfig): Promise { + put( + url: string, + data?: any, + config?: AxiosRequestConfig, + ): Promise { return request.put(url, data, config); }, @@ -117,7 +127,11 @@ export const http = { return request.delete(url, config); }, - patch(url: string, data?: any, config?: AxiosRequestConfig): Promise { + patch( + url: string, + data?: any, + config?: AxiosRequestConfig, + ): Promise { return request.patch(url, data, config); }, }; diff --git a/lesingle-edu-reading-platform-frontend/src/views/admin/courses/CourseEditView.vue b/lesingle-edu-reading-platform-frontend/src/views/admin/courses/CourseEditView.vue index 4fd40d4..bbb0c85 100644 --- a/lesingle-edu-reading-platform-frontend/src/views/admin/courses/CourseEditView.vue +++ b/lesingle-edu-reading-platform-frontend/src/views/admin/courses/CourseEditView.vue @@ -23,48 +23,46 @@ - - - - - - - - - - + + + + + + + + + + - -
- - + +
+ + - - + + - - + + - - + + - - + + - - + + - - -
- + + +
+
@@ -283,8 +281,19 @@ const handleSaveDraft = async () => { // 保存 const handleSave = async (isDraft = false) => { - // 正式保存才校验当前步骤;保存草稿允许未完成配置,不校验 - if (!isDraft) { + // 校验逻辑: + // - 保存草稿:仅校验「基本信息」(Step1),其余环节不校验(按原来逻辑允许未完成) + // - 正式保存:仍按原逻辑校验当前步骤 + if (isDraft) { + const step1 = step1Ref.value; + if (step1?.validate) { + const result = await step1.validate(); + if (!result?.valid) { + message.warning(result?.errors?.[0] || '请完成基本信息'); + return; + } + } + } else { const ok = await validateCurrentStep(); if (!ok) return; } @@ -332,18 +341,57 @@ const handleSave = async (isDraft = false) => { console.log('Saving course data...', { isDraft, isEdit: isEdit.value }); + let savedCourse: any = null; if (isEdit.value) { - await updateCourse(courseId.value, courseData); + const res = await updateCourse(courseId.value, courseData) as any; + savedCourse = res?.data ?? res ?? null; console.log('Course updated successfully'); } else { const res = await createCourse(courseData) as any; - savedCourseId = res?.id ?? res?.data?.id; // 响应拦截器已返回 data.data,但也兼容直接返回完整响应 + savedCourse = res?.data ?? res ?? null; + savedCourseId = savedCourse?.id ?? res?.id ?? res?.data?.id; // 兼容多种返回形态 } if (!savedCourseId) { throw new Error('无法获取课程ID'); } + // 草稿保存:接口若返回实体与 ID,则填充到当前编辑,并切换到编辑路由 + // 这样后续环节(导入课/集体课/领域课等)能拿到 courseId 进行关联保存 + if (isDraft && !isEdit.value) { + // 回填(尽量不破坏当前填写内容;这里只补齐后端回写字段) + if (savedCourse && typeof savedCourse === 'object') { + formData.basic.name = savedCourse.name ?? formData.basic.name; + formData.basic.themeId = savedCourse.themeId ?? formData.basic.themeId; + formData.basic.pictureBookName = savedCourse.pictureBookName ?? formData.basic.pictureBookName; + formData.basic.coreContent = savedCourse.coreContent ?? formData.basic.coreContent; + formData.basic.duration = (savedCourse.durationMinutes ?? savedCourse.duration) ?? formData.basic.duration; + formData.basic.coverImagePath = savedCourse.coverImagePath ?? formData.basic.coverImagePath; + + const gradeTags = savedCourse.gradeTags; + if (gradeTags !== undefined && gradeTags !== null) { + formData.basic.grades = Array.isArray(gradeTags) ? gradeTags : (gradeTags ? JSON.parse(gradeTags) : []); + } + const domainTags = savedCourse.domainTags; + if (domainTags !== undefined && domainTags !== null) { + formData.basic.domainTags = Array.isArray(domainTags) ? domainTags : (domainTags ? JSON.parse(domainTags || '[]') : []); + } + + formData.intro.introSummary = savedCourse.introSummary ?? formData.intro.introSummary; + formData.intro.introHighlights = savedCourse.introHighlights ?? formData.intro.introHighlights; + formData.intro.introGoals = savedCourse.introGoals ?? formData.intro.introGoals; + formData.intro.introSchedule = savedCourse.introSchedule ?? formData.intro.introSchedule; + formData.intro.introKeyPoints = savedCourse.introKeyPoints ?? formData.intro.introKeyPoints; + formData.intro.introMethods = savedCourse.introMethods ?? formData.intro.introMethods; + formData.intro.introEvaluation = savedCourse.introEvaluation ?? formData.intro.introEvaluation; + formData.intro.introNotes = savedCourse.introNotes ?? formData.intro.introNotes; + formData.scheduleRefData = savedCourse.scheduleRefData ?? formData.scheduleRefData; + formData.environmentConstruction = savedCourse.environmentConstruction ?? formData.environmentConstruction; + } + + await router.replace(`/admin/packages/${savedCourseId}/edit`); + } + // 2. 保存导入课 try { const introLessonData = step4Ref.value?.getSaveData(); diff --git a/lesingle-edu-reading-platform-frontend/src/views/admin/themes/ThemeListView.vue b/lesingle-edu-reading-platform-frontend/src/views/admin/themes/ThemeListView.vue index 2014d8c..6c58dc9 100644 --- a/lesingle-edu-reading-platform-frontend/src/views/admin/themes/ThemeListView.vue +++ b/lesingle-edu-reading-platform-frontend/src/views/admin/themes/ThemeListView.vue @@ -173,6 +173,7 @@ const handleEdit = (record: any) => { editingId.value = record.id; form.name = record.name; form.description = record.description || ''; + form.color = (record.color || '').toString(); form.sortOrder = record.sortOrder; modalVisible.value = true; };