fix(admin): 课程包编辑页问题修复
- 封面回显与保存:使用 getFileUrl 统一处理,修复 watch 逻辑 - 课程介绍/排课参考/环创建设回显:修复 API 字段映射和解析 - 测评内容 JSON 格式:新增 parseAssessmentDataForDisplay 前后端统一 - 保存后跳转列表:修复新建/编辑流程的 router.replace - 表单校验:导入课、集体课、领域课各必填一条,下一步时校验 - 保存按钮:修复 @click 将 event 误传为 isDraft 导致不跳转 - Lesson API:updateLesson/updateStep 传入正确的 courseId Made-with: Cursor
This commit is contained in:
parent
36b8621060
commit
877acf33b8
@ -211,11 +211,18 @@ export const fileApi = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文件URL
|
* 获取文件URL
|
||||||
|
* 支持:完整 OSS URL、以 / 开头的路径、相对路径
|
||||||
*/
|
*/
|
||||||
getFileUrl: (filePath: string): string => {
|
getFileUrl: (filePath: string | null | undefined): string => {
|
||||||
// filePath 格式: /uploads/courses/covers/xxx.png
|
if (!filePath) return '';
|
||||||
// 直接返回相对路径,由 nginx 或后端静态服务处理
|
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
|
||||||
return filePath;
|
return filePath;
|
||||||
|
}
|
||||||
|
const SERVER_BASE = import.meta.env.VITE_SERVER_BASE_URL || '/api';
|
||||||
|
if (filePath.startsWith('/')) {
|
||||||
|
return `${SERVER_BASE}${filePath}`;
|
||||||
|
}
|
||||||
|
return `${SERVER_BASE}/uploads/${filePath}`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -105,8 +105,8 @@ export function createLesson(courseId: number, data: CreateLessonData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新课程
|
// 更新课程
|
||||||
export function updateLesson(lessonId: number, data: Partial<CreateLessonData>) {
|
export function updateLesson(courseId: number, lessonId: number, data: Partial<CreateLessonData>) {
|
||||||
return http.put(`/v1/admin/courses/0/lessons/${lessonId}`, data);
|
return http.put(`/v1/admin/courses/${courseId}/lessons/${lessonId}`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除课程
|
// 删除课程
|
||||||
@ -132,8 +132,8 @@ export function createStep(courseId: number, lessonId: number, data: CreateStepD
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新环节
|
// 更新环节
|
||||||
export function updateStep(stepId: number, data: Partial<CreateStepData>) {
|
export function updateStep(courseId: number, stepId: number, data: Partial<CreateStepData>) {
|
||||||
return http.put(`/v1/admin/courses/0/lessons/steps/${stepId}`, data);
|
return http.put(`/v1/admin/courses/${courseId}/lessons/steps/${stepId}`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除环节
|
// 删除环节
|
||||||
|
|||||||
@ -126,7 +126,7 @@ import { ref, reactive, watch, onMounted } from 'vue';
|
|||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import { getThemeList } from '@/api/theme';
|
import { getThemeList } from '@/api/theme';
|
||||||
import { uploadFile } from '@/api/file';
|
import { uploadFile, getFileUrl } from '@/api/file';
|
||||||
import type { Theme } from '@/api/theme';
|
import type { Theme } from '@/api/theme';
|
||||||
|
|
||||||
interface BasicInfoData {
|
interface BasicInfoData {
|
||||||
@ -206,19 +206,16 @@ watch(
|
|||||||
if (newVal) {
|
if (newVal) {
|
||||||
Object.assign(formData, newVal);
|
Object.assign(formData, newVal);
|
||||||
|
|
||||||
// 处理封面图片
|
// 处理封面图片回显
|
||||||
if (newVal.coverImagePath && coverImages.value.length === 0) {
|
if (newVal.coverImagePath) {
|
||||||
// 构建正确的图片URL
|
|
||||||
let imageUrl = newVal.coverImagePath;
|
|
||||||
if (!imageUrl.startsWith('http') && !imageUrl.startsWith('/uploads') && !imageUrl.includes('/uploads/')) {
|
|
||||||
imageUrl = `/uploads/${imageUrl}`;
|
|
||||||
}
|
|
||||||
coverImages.value = [{
|
coverImages.value = [{
|
||||||
uid: '-1',
|
uid: '-1',
|
||||||
name: 'cover',
|
name: 'cover',
|
||||||
status: 'done',
|
status: 'done',
|
||||||
url: imageUrl,
|
url: getFileUrl(newVal.coverImagePath),
|
||||||
}];
|
}];
|
||||||
|
} else {
|
||||||
|
coverImages.value = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -255,16 +252,11 @@ const beforeCoverUpload = async (file: any) => {
|
|||||||
try {
|
try {
|
||||||
const result = await uploadFile(file, 'cover');
|
const result = await uploadFile(file, 'cover');
|
||||||
formData.coverImagePath = result.filePath;
|
formData.coverImagePath = result.filePath;
|
||||||
// 构建正确的图片URL - 后端返回的filePath已经包含完整路径
|
|
||||||
let imageUrl = result.filePath;
|
|
||||||
if (!imageUrl.startsWith('http') && !imageUrl.startsWith('/uploads') && !imageUrl.includes('/uploads/')) {
|
|
||||||
imageUrl = `/uploads/${imageUrl}`;
|
|
||||||
}
|
|
||||||
coverImages.value = [{
|
coverImages.value = [{
|
||||||
uid: file.uid,
|
uid: file.uid,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
status: 'done',
|
status: 'done',
|
||||||
url: imageUrl,
|
url: getFileUrl(result.filePath),
|
||||||
}];
|
}];
|
||||||
handleChange();
|
handleChange();
|
||||||
message.success('封面上传成功');
|
message.success('封面上传成功');
|
||||||
|
|||||||
@ -133,10 +133,14 @@ const tableData = ref<ScheduleRow[]>([]);
|
|||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) {
|
if (!newVal || typeof newVal !== 'string') {
|
||||||
|
tableData.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(newVal);
|
const parsed = JSON.parse(newVal);
|
||||||
tableData.value = parsed.map((row: any, index: number) => ({
|
const rows = Array.isArray(parsed) ? parsed : [];
|
||||||
|
tableData.value = rows.map((row: any, index: number) => ({
|
||||||
...row,
|
...row,
|
||||||
key: row.key || `row_${index}`,
|
key: row.key || `row_${index}`,
|
||||||
}));
|
}));
|
||||||
@ -144,7 +148,6 @@ watch(
|
|||||||
console.error('解析排课数据失败', e);
|
console.error('解析排课数据失败', e);
|
||||||
tableData.value = [];
|
tableData.value = [];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@ -60,6 +60,7 @@ import { PlusOutlined } from '@ant-design/icons-vue';
|
|||||||
import LessonConfigPanel from '@/components/course/LessonConfigPanel.vue';
|
import LessonConfigPanel from '@/components/course/LessonConfigPanel.vue';
|
||||||
import type { LessonData } from '@/components/course/LessonConfigPanel.vue';
|
import type { LessonData } from '@/components/course/LessonConfigPanel.vue';
|
||||||
import { getLessonByType, createLesson, updateLesson, deleteLesson as deleteLessonApi } from '@/api/lesson';
|
import { getLessonByType, createLesson, updateLesson, deleteLesson as deleteLessonApi } from '@/api/lesson';
|
||||||
|
import { parseAssessmentDataForDisplay } from '@/utils/assessmentData';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
courseId: number;
|
courseId: number;
|
||||||
@ -100,7 +101,7 @@ const fetchLesson = async () => {
|
|||||||
preparation: lesson.preparation || '',
|
preparation: lesson.preparation || '',
|
||||||
extension: lesson.extension || '',
|
extension: lesson.extension || '',
|
||||||
reflection: lesson.reflection || '',
|
reflection: lesson.reflection || '',
|
||||||
assessmentData: lesson.assessmentData || '',
|
assessmentData: parseAssessmentDataForDisplay(lesson.assessmentData),
|
||||||
useTemplate: lesson.useTemplate || false,
|
useTemplate: lesson.useTemplate || false,
|
||||||
steps: lesson.steps || [],
|
steps: lesson.steps || [],
|
||||||
isNew: false,
|
isNew: false,
|
||||||
@ -159,10 +160,10 @@ const handleLessonChange = () => {
|
|||||||
emit('change');
|
emit('change');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 验证:若配置了导入课,则通过 formRules 校验
|
// 验证:导入课为必填,至少配置一条
|
||||||
const validate = async () => {
|
const validate = async () => {
|
||||||
if (!lessonData.value) {
|
if (!lessonData.value) {
|
||||||
return { valid: true, errors: [] as string[], warnings: ['未配置导入课'] };
|
return { valid: false, errors: ['请配置导入课(至少一条)'] };
|
||||||
}
|
}
|
||||||
return configPanelRef.value?.validate() ?? { valid: true, errors: [] as string[] };
|
return configPanelRef.value?.validate() ?? { valid: true, errors: [] as string[] };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -60,6 +60,7 @@ import { PlusOutlined } from '@ant-design/icons-vue';
|
|||||||
import LessonConfigPanel from '@/components/course/LessonConfigPanel.vue';
|
import LessonConfigPanel from '@/components/course/LessonConfigPanel.vue';
|
||||||
import type { LessonData } from '@/components/course/LessonConfigPanel.vue';
|
import type { LessonData } from '@/components/course/LessonConfigPanel.vue';
|
||||||
import { getLessonByType, createLesson as createLessonApi, updateLesson, deleteLesson as deleteLessonApi } from '@/api/lesson';
|
import { getLessonByType, createLesson as createLessonApi, updateLesson, deleteLesson as deleteLessonApi } from '@/api/lesson';
|
||||||
|
import { parseAssessmentDataForDisplay } from '@/utils/assessmentData';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
courseId: number;
|
courseId: number;
|
||||||
@ -101,7 +102,7 @@ const fetchLesson = async () => {
|
|||||||
preparation: lesson.preparation || '',
|
preparation: lesson.preparation || '',
|
||||||
extension: lesson.extension || '',
|
extension: lesson.extension || '',
|
||||||
reflection: lesson.reflection || '',
|
reflection: lesson.reflection || '',
|
||||||
assessmentData: lesson.assessmentData || '',
|
assessmentData: parseAssessmentDataForDisplay(lesson.assessmentData),
|
||||||
useTemplate: lesson.useTemplate || false,
|
useTemplate: lesson.useTemplate || false,
|
||||||
steps: lesson.steps || [],
|
steps: lesson.steps || [],
|
||||||
isNew: false,
|
isNew: false,
|
||||||
@ -160,10 +161,10 @@ const handleLessonChange = () => {
|
|||||||
emit('change');
|
emit('change');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 验证:若配置了集体课,则通过 formRules 校验
|
// 验证:集体课为必填,至少配置一条
|
||||||
const validate = async () => {
|
const validate = async () => {
|
||||||
if (!lessonData.value) {
|
if (!lessonData.value) {
|
||||||
return { valid: true, errors: [] as string[], warnings: ['未配置集体课'] };
|
return { valid: false, errors: ['请配置集体课(至少一条)'] };
|
||||||
}
|
}
|
||||||
return configPanelRef.value?.validate() ?? { valid: true, errors: [] as string[] };
|
return configPanelRef.value?.validate() ?? { valid: true, errors: [] as string[] };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -93,6 +93,7 @@ import {
|
|||||||
import LessonConfigPanel from '@/components/course/LessonConfigPanel.vue';
|
import LessonConfigPanel from '@/components/course/LessonConfigPanel.vue';
|
||||||
import type { LessonData } from '@/components/course/LessonConfigPanel.vue';
|
import type { LessonData } from '@/components/course/LessonConfigPanel.vue';
|
||||||
import { getLessonList, createLesson, updateLesson, deleteLesson } from '@/api/lesson';
|
import { getLessonList, createLesson, updateLesson, deleteLesson } from '@/api/lesson';
|
||||||
|
import { parseAssessmentDataForDisplay } from '@/utils/assessmentData';
|
||||||
|
|
||||||
interface DomainConfig {
|
interface DomainConfig {
|
||||||
type: string;
|
type: string;
|
||||||
@ -204,7 +205,7 @@ const fetchLessons = async () => {
|
|||||||
preparation: lesson.preparation || '',
|
preparation: lesson.preparation || '',
|
||||||
extension: lesson.extension || '',
|
extension: lesson.extension || '',
|
||||||
reflection: lesson.reflection || '',
|
reflection: lesson.reflection || '',
|
||||||
assessmentData: lesson.assessmentData || '',
|
assessmentData: parseAssessmentDataForDisplay(lesson.assessmentData),
|
||||||
useTemplate: lesson.useTemplate || false,
|
useTemplate: lesson.useTemplate || false,
|
||||||
steps: lesson.steps || [],
|
steps: lesson.steps || [],
|
||||||
isNew: false,
|
isNew: false,
|
||||||
@ -266,8 +267,13 @@ const handleLessonChange = () => {
|
|||||||
emit('change');
|
emit('change');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 验证:若启用某领域,则通过 formRules 校验各领域
|
// 验证:领域课为必填,至少配置一条,且已启用的领域需通过 formRules 校验
|
||||||
const validate = async () => {
|
const validate = async () => {
|
||||||
|
const saveData = getSaveData();
|
||||||
|
if (!saveData || saveData.length === 0) {
|
||||||
|
return { valid: false, errors: ['请配置领域课(至少一条)'] };
|
||||||
|
}
|
||||||
|
|
||||||
const enabledDomains = domains.filter((d) => d.enabled);
|
const enabledDomains = domains.filter((d) => d.enabled);
|
||||||
const allErrors: string[] = [];
|
const allErrors: string[] = [];
|
||||||
|
|
||||||
|
|||||||
19
reading-platform-frontend/src/utils/assessmentData.ts
Normal file
19
reading-platform-frontend/src/utils/assessmentData.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 测评内容(assessmentData)前后端格式统一
|
||||||
|
* 后端将纯文本存储为 JSON 字符串(如 "核心内容"),加载时需解析为明文展示
|
||||||
|
*/
|
||||||
|
export function parseAssessmentDataForDisplay(value: string | null | undefined): string {
|
||||||
|
if (value == null || value === '') return '';
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return '';
|
||||||
|
// 若是 JSON 字符串格式(如 "核心内容"),解析后返回明文
|
||||||
|
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(trimmed);
|
||||||
|
return typeof parsed === 'string' ? parsed : trimmed;
|
||||||
|
} catch {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
@ -1,21 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="course-edit-view">
|
<div class="course-edit-view">
|
||||||
<div class="sticky-header">
|
<div class="sticky-header">
|
||||||
<a-page-header
|
<a-page-header :title="isEdit ? '编辑课程包' : '创建课程包'" @back="() => router.back()">
|
||||||
:title="isEdit ? '编辑课程包' : '创建课程包'"
|
|
||||||
@back="() => router.back()"
|
|
||||||
>
|
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button @click="handleSaveDraft" :loading="saving">保存草稿</a-button>
|
<a-button @click="handleSaveDraft" :loading="saving">保存草稿</a-button>
|
||||||
<a-button v-if="currentStep > 0" @click="prevStep">上一步</a-button>
|
<a-button v-if="currentStep > 0" @click="prevStep">上一步</a-button>
|
||||||
<a-button v-if="currentStep < 6" type="primary" @click="nextStep">下一步</a-button>
|
<a-button v-if="currentStep < 6" type="primary" @click="nextStep">下一步</a-button>
|
||||||
<a-button
|
<a-button v-if="currentStep === 6" type="primary" :loading="saving" @click="() => handleSave(false)">
|
||||||
v-if="currentStep === 6"
|
|
||||||
type="primary"
|
|
||||||
:loading="saving"
|
|
||||||
@click="handleSave"
|
|
||||||
>
|
|
||||||
{{ isEdit ? '保存' : '创建' }}
|
{{ isEdit ? '保存' : '创建' }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button v-else-if="isEdit" type="primary" @click="handleSaveAndSubmit" :loading="saving">
|
<a-button v-else-if="isEdit" type="primary" @click="handleSaveAndSubmit" :loading="saving">
|
||||||
@ -48,62 +40,32 @@
|
|||||||
<!-- 步骤内容 -->
|
<!-- 步骤内容 -->
|
||||||
<div class="step-content">
|
<div class="step-content">
|
||||||
<!-- 步骤1:基本信息 -->
|
<!-- 步骤1:基本信息 -->
|
||||||
<Step1BasicInfo
|
<Step1BasicInfo v-show="currentStep === 0" ref="step1Ref" v-model="formData.basic"
|
||||||
v-show="currentStep === 0"
|
@change="handleDataChange" />
|
||||||
ref="step1Ref"
|
|
||||||
v-model="formData.basic"
|
|
||||||
@change="handleDataChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 步骤2:课程介绍 -->
|
<!-- 步骤2:课程介绍 -->
|
||||||
<Step2CourseIntro
|
<Step2CourseIntro v-show="currentStep === 1" ref="step2Ref" v-model="formData.intro"
|
||||||
v-show="currentStep === 1"
|
@change="handleDataChange" />
|
||||||
ref="step2Ref"
|
|
||||||
v-model="formData.intro"
|
|
||||||
@change="handleDataChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 步骤3:排课参考 -->
|
<!-- 步骤3:排课参考 -->
|
||||||
<Step3ScheduleRef
|
<Step3ScheduleRef v-show="currentStep === 2" ref="step3Ref" v-model="formData.scheduleRefData"
|
||||||
v-show="currentStep === 2"
|
@change="handleDataChange" />
|
||||||
ref="step3Ref"
|
|
||||||
v-model="formData.scheduleRefData"
|
|
||||||
@change="handleDataChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 步骤4:导入课 -->
|
<!-- 步骤4:导入课 -->
|
||||||
<Step4IntroLesson
|
<Step4IntroLesson v-show="currentStep === 3" ref="step4Ref" :course-id="courseId"
|
||||||
v-show="currentStep === 3"
|
@change="handleDataChange" />
|
||||||
ref="step4Ref"
|
|
||||||
:course-id="courseId"
|
|
||||||
@change="handleDataChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 步骤5:集体课 -->
|
<!-- 步骤5:集体课 -->
|
||||||
<Step5CollectiveLesson
|
<Step5CollectiveLesson v-show="currentStep === 4" ref="step5Ref" :course-id="courseId"
|
||||||
v-show="currentStep === 4"
|
:course-name="formData.basic.name" @change="handleDataChange" />
|
||||||
ref="step5Ref"
|
|
||||||
:course-id="courseId"
|
|
||||||
:course-name="formData.basic.name"
|
|
||||||
@change="handleDataChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 步骤6:领域课 -->
|
<!-- 步骤6:领域课 -->
|
||||||
<Step6DomainLessons
|
<Step6DomainLessons v-show="currentStep === 5" ref="step6Ref" :course-id="courseId"
|
||||||
v-show="currentStep === 5"
|
:course-name="formData.basic.name" @change="handleDataChange" />
|
||||||
ref="step6Ref"
|
|
||||||
:course-id="courseId"
|
|
||||||
:course-name="formData.basic.name"
|
|
||||||
@change="handleDataChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 步骤7:环创建设 -->
|
<!-- 步骤7:环创建设 -->
|
||||||
<Step7Environment
|
<Step7Environment v-show="currentStep === 6" ref="step7Ref" v-model="formData.environmentConstruction"
|
||||||
v-show="currentStep === 6"
|
@change="handleDataChange" />
|
||||||
ref="step7Ref"
|
|
||||||
v-model="formData.environmentConstruction"
|
|
||||||
@change="handleDataChange"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@ -231,8 +193,8 @@ const fetchCourseDetail = async () => {
|
|||||||
formData.basic.grades = Array.isArray(course.gradeTags) ? course.gradeTags : (course.gradeTags ? JSON.parse(course.gradeTags) : []);
|
formData.basic.grades = Array.isArray(course.gradeTags) ? course.gradeTags : (course.gradeTags ? JSON.parse(course.gradeTags) : []);
|
||||||
formData.basic.pictureBookName = course.pictureBookName || '';
|
formData.basic.pictureBookName = course.pictureBookName || '';
|
||||||
formData.basic.coreContent = course.coreContent || '';
|
formData.basic.coreContent = course.coreContent || '';
|
||||||
formData.basic.duration = course.duration || 25;
|
formData.basic.duration = course.durationMinutes ?? course.duration ?? 25;
|
||||||
formData.basic.domainTags = course.domainTags ? JSON.parse(course.domainTags) : [];
|
formData.basic.domainTags = Array.isArray(course.domainTags) ? course.domainTags : (course.domainTags ? JSON.parse(course.domainTags || '[]') : []);
|
||||||
formData.basic.coverImagePath = course.coverImagePath || '';
|
formData.basic.coverImagePath = course.coverImagePath || '';
|
||||||
|
|
||||||
// 课程介绍
|
// 课程介绍
|
||||||
@ -280,18 +242,26 @@ const nextStep = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 校验:导入课、集体课、领域课至少配置一种
|
// 校验:导入课、集体课、领域课各必填一条
|
||||||
const validateAtLeastOneLesson = (): boolean => {
|
const validateAllThreeLessons = (): boolean => {
|
||||||
const hasIntro = !!step4Ref.value?.lessonData;
|
const hasIntro = !!step4Ref.value?.lessonData;
|
||||||
const hasCollective = !!step5Ref.value?.lessonData;
|
const hasCollective = !!step5Ref.value?.lessonData;
|
||||||
const domainData = step6Ref.value?.getSaveData?.() || [];
|
const domainData = step6Ref.value?.getSaveData?.() || [];
|
||||||
const hasDomain = Array.isArray(domainData) && domainData.length > 0;
|
const hasDomain = Array.isArray(domainData) && domainData.length > 0;
|
||||||
|
|
||||||
if (hasIntro || hasCollective || hasDomain) {
|
if (!hasIntro) {
|
||||||
return true;
|
message.warning('请配置导入课(至少一节)');
|
||||||
}
|
|
||||||
message.warning('请至少配置一种课程:导入课、集体课或领域课(至少完成一个领域)');
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasCollective) {
|
||||||
|
message.warning('请配置集体课(至少一节)');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasDomain) {
|
||||||
|
message.warning('请配置领域课(至少一节)');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 验证当前步骤(覆盖全部 7 步)
|
// 验证当前步骤(覆盖全部 7 步)
|
||||||
@ -308,9 +278,9 @@ const validateCurrentStep = async (): Promise<boolean> => {
|
|||||||
];
|
];
|
||||||
const ref = stepRefs[step]?.value;
|
const ref = stepRefs[step]?.value;
|
||||||
if (!ref?.validate) {
|
if (!ref?.validate) {
|
||||||
// 步骤 5(领域课)、步骤 6(环创建设)需额外校验「至少一种课程」
|
// 步骤 5(领域课)、步骤 6(环创建设)需额外校验「导入课、集体课、领域课各必填一条」
|
||||||
if (step === 5 || step === 6) {
|
if (step === 5 || step === 6) {
|
||||||
return validateAtLeastOneLesson();
|
return validateAllThreeLessons();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -321,9 +291,9 @@ const validateCurrentStep = async (): Promise<boolean> => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 步骤 5(领域课)、步骤 6(环创建设)需额外校验「至少一种课程」
|
// 步骤 5(领域课)、步骤 6(环创建设)需额外校验「导入课、集体课、领域课各必填一条」
|
||||||
if (step === 5 || step === 6) {
|
if (step === 5 || step === 6) {
|
||||||
return validateAtLeastOneLesson();
|
return validateAllThreeLessons();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@ -384,13 +354,7 @@ const handleSave = async (isDraft = false) => {
|
|||||||
console.log('Course updated successfully');
|
console.log('Course updated successfully');
|
||||||
} else {
|
} else {
|
||||||
const res = await createCourse(courseData) as any;
|
const res = await createCourse(courseData) as any;
|
||||||
console.log('🔍 创建课程返回结果:', JSON.stringify(res, null, 2));
|
savedCourseId = res?.id ?? res?.data?.id; // 响应拦截器已返回 data.data,但也兼容直接返回完整响应
|
||||||
savedCourseId = res?.id || res?.data?.id; // 响应拦截器已返回 data.data,但也兼容直接返回完整响应
|
|
||||||
console.log('Course created with ID:', savedCourseId);
|
|
||||||
// 更新路由以支持后续保存
|
|
||||||
if (savedCourseId) {
|
|
||||||
router.replace(`/admin/packages/${savedCourseId}/edit`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!savedCourseId) {
|
if (!savedCourseId) {
|
||||||
@ -433,18 +397,10 @@ const handleSave = async (isDraft = false) => {
|
|||||||
// 继续保存其他内容,不中断
|
// 继续保存其他内容,不中断
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ 所有课程数据保存完成,准备显示成功提示...');
|
|
||||||
message.success(isDraft ? '草稿保存成功' : (isEdit.value ? '保存成功' : '创建成功'));
|
message.success(isDraft ? '草稿保存成功' : (isEdit.value ? '保存成功' : '创建成功'));
|
||||||
console.log('✅ 成功提示已显示,准备跳转...');
|
|
||||||
|
|
||||||
if (!isDraft) {
|
if (!isDraft) {
|
||||||
console.log('🚀 准备跳转到课程列表页面...');
|
await router.replace('/admin/packages');
|
||||||
console.log('🚀 isDraft =', isDraft, ', isEdit =', isEdit.value);
|
|
||||||
// 确保所有异步操作完成后再跳转
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
console.log('🚀 即将执行 router.push 跳转...');
|
|
||||||
await router.push('/admin/packages');
|
|
||||||
console.log('✅ 已执行 router.push 跳转');
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Save failed:', error);
|
console.error('Save failed:', error);
|
||||||
@ -457,7 +413,8 @@ const handleSave = async (isDraft = false) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 保存单个课程
|
// 保存单个课程
|
||||||
const saveLesson = async (courseId: number, lessonData: any, lessonType: string) => {
|
const saveLesson = async (courseId: number | string, lessonData: any, lessonType: string) => {
|
||||||
|
const cid = Number(courseId);
|
||||||
if (!lessonData) {
|
if (!lessonData) {
|
||||||
console.log('No lesson data to save for type:', lessonType);
|
console.log('No lesson data to save for type:', lessonType);
|
||||||
return;
|
return;
|
||||||
@ -486,15 +443,10 @@ const saveLesson = async (courseId: number, lessonData: any, lessonType: string)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (lessonData.isNew || !lessonData.id) {
|
if (lessonData.isNew || !lessonData.id) {
|
||||||
// 创建新课程
|
const res = await createLesson(cid, lessonPayload) as any;
|
||||||
console.log('Creating new lesson:', lessonType);
|
|
||||||
const res = await createLesson(courseId, lessonPayload) as any;
|
|
||||||
lessonId = res.data?.id || res.id;
|
lessonId = res.data?.id || res.id;
|
||||||
console.log('Lesson created with ID:', lessonId);
|
|
||||||
} else {
|
} else {
|
||||||
// 更新现有课程
|
await updateLesson(cid, lessonData.id, lessonPayload);
|
||||||
console.log('Updating lesson:', lessonId);
|
|
||||||
await updateLesson(lessonData.id, lessonPayload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存教学环节
|
// 保存教学环节
|
||||||
@ -509,9 +461,9 @@ const saveLesson = async (courseId: number, lessonData: any, lessonType: string)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (step.isNew || !step.id) {
|
if (step.isNew || !step.id) {
|
||||||
await createStep(courseId, lessonId, stepPayload);
|
await createStep(cid, lessonId, stepPayload);
|
||||||
} else {
|
} else {
|
||||||
await updateStep(step.id, stepPayload);
|
await updateStep(cid, step.id, stepPayload);
|
||||||
}
|
}
|
||||||
} catch (stepError: any) {
|
} catch (stepError: any) {
|
||||||
console.error('Failed to save step:', stepError);
|
console.error('Failed to save step:', stepError);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user