fix: 课程包详情页标题改为「详情」; 优化课程包编辑页相关逻辑

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-23 15:34:26 +08:00
parent 877acf33b8
commit 1783706d63
2 changed files with 72 additions and 125 deletions

View File

@ -1,36 +1,27 @@
<template>
<div class="course-detail-view">
<!-- 顶部导航 -->
<div class="detail-header">
<div class="header-left">
<a-button type="text" @click="router.back()">
<ArrowLeftOutlined />
</a-button>
<div class="course-title">
<h2>{{ course.name || '课程包详情' }}</h2>
<a-tag :style="getStatusStyle(course.status)" style="margin-left: 12px;">
{{ translateStatus(course.status) }}
</a-tag>
</div>
</div>
<div class="header-actions">
<a-button v-if="course.status !== 'PUBLISHED'" @click="editCourse">
<EditOutlined /> 编辑
</a-button>
<a-button @click="viewStats">
<BarChartOutlined /> 数据
</a-button>
<a-popconfirm
v-if="course.status === 'DRAFT' || course.status === 'ARCHIVED'"
title="确定删除此课程包吗?"
@confirm="deleteCourse"
>
<a-button danger>
<DeleteOutlined /> 删除
<a-page-header title="课程包详情" :sub-title="course.name || ''" @back="() => router.back()">
<template #extra>
<div class="header-actions">
<a-button v-if="canEdit" @click="editCourse">
<EditOutlined /> 编辑
</a-button>
</a-popconfirm>
</div>
</div>
<a-button @click="viewStats">
<BarChartOutlined /> 数据
</a-button>
<a-popconfirm v-if="course.status === 'DRAFT' || course.status === 'ARCHIVED'" title="确定删除此课程包吗?"
@confirm="deleteCourse">
<a-button danger>
<DeleteOutlined /> 删除
</a-button>
</a-popconfirm>
</div>
</template>
<template #tags>
<a-tag :style="getStatusStyle(course.status)">{{ translateStatus(course.status) }}</a-tag>
</template>
</a-page-header>
<a-spin :spinning="loading">
<div class="detail-content">
@ -223,12 +214,8 @@
</div>
<div class="section-body">
<div class="lesson-cards">
<div
v-for="lesson in courseLessons"
:key="lesson.id"
class="lesson-card"
:class="'lesson-type-' + lesson.lessonType?.toLowerCase()"
>
<div v-for="lesson in courseLessons" :key="lesson.id" class="lesson-card"
:class="'lesson-type-' + lesson.lessonType?.toLowerCase()">
<div class="lesson-header">
<div class="lesson-type-badge" :style="{ background: getLessonTypeBgColor(lesson.lessonType) }">
{{ translateLessonType(lesson.lessonType) }}
@ -254,29 +241,20 @@
<div class="lesson-section" v-if="hasLessonResources(lesson)">
<div class="lesson-section-title">核心资源</div>
<div class="resource-grid">
<div
v-if="lesson.videoPath"
class="resource-item"
@click="previewFile(lesson.videoPath, lesson.videoName || '绘本动画')"
>
<div v-if="lesson.videoPath" class="resource-item"
@click="previewFile(lesson.videoPath, lesson.videoName || '绘本动画')">
<VideoCameraOutlined class="resource-icon video" />
<span class="resource-name">{{ lesson.videoName || '绘本动画' }}</span>
<EyeOutlined class="resource-action" />
</div>
<div
v-if="lesson.pptPath"
class="resource-item"
@click="previewFile(lesson.pptPath, lesson.pptName || '教学课件')"
>
<div v-if="lesson.pptPath" class="resource-item"
@click="previewFile(lesson.pptPath, lesson.pptName || '教学课件')">
<FilePptOutlined class="resource-icon ppt" />
<span class="resource-name">{{ lesson.pptName || '教学课件' }}</span>
<EyeOutlined class="resource-action" />
</div>
<div
v-if="lesson.pdfPath"
class="resource-item"
@click="previewFile(lesson.pdfPath, lesson.pdfName || '电子绘本')"
>
<div v-if="lesson.pdfPath" class="resource-item"
@click="previewFile(lesson.pdfPath, lesson.pdfName || '电子绘本')">
<FilePdfOutlined class="resource-icon pdf" />
<span class="resource-name">{{ lesson.pdfName || '电子绘本' }}</span>
<EyeOutlined class="resource-action" />
@ -333,12 +311,8 @@
<VideoCameraOutlined style="color: #722ed1;" /> 视频资源
</div>
<div class="resource-list">
<div
v-for="(item, index) in allVideos"
:key="'video-' + index"
class="resource-item-card"
@click="previewFile(item.path, item.name)"
>
<div v-for="(item, index) in allVideos" :key="'video-' + index" class="resource-item-card"
@click="previewFile(item.path, item.name)">
<VideoCameraOutlined class="item-icon" style="color: #722ed1;" />
<span class="item-name">{{ item.name }}</span>
<PlayCircleOutlined class="item-action" />
@ -352,12 +326,8 @@
<AudioOutlined style="color: #52c41a;" /> 音频资源
</div>
<div class="resource-list">
<div
v-for="(item, index) in allAudios"
:key="'audio-' + index"
class="resource-item-card"
@click="previewFile(item.path, item.name)"
>
<div v-for="(item, index) in allAudios" :key="'audio-' + index" class="resource-item-card"
@click="previewFile(item.path, item.name)">
<AudioOutlined class="item-icon" style="color: #52c41a;" />
<span class="item-name">{{ item.name }}</span>
<PlayCircleOutlined class="item-action" />
@ -371,12 +341,8 @@
<FileTextOutlined style="color: #1890ff;" /> 文档资源
</div>
<div class="resource-list">
<div
v-for="(item, index) in allDocuments"
:key="'doc-' + index"
class="resource-item-card"
@click="previewFile(item.path, item.name)"
>
<div v-for="(item, index) in allDocuments" :key="'doc-' + index" class="resource-item-card"
@click="previewFile(item.path, item.name)">
<FilePdfOutlined v-if="item.type === 'pdf'" class="item-icon" style="color: #f5222d;" />
<FilePptOutlined v-else-if="item.type === 'ppt'" class="item-icon" style="color: #fa8c16;" />
<FileTextOutlined v-else class="item-icon" style="color: #1890ff;" />
@ -392,14 +358,8 @@
<PictureOutlined style="color: #13c2c2;" /> 图片资源
</div>
<div class="image-grid">
<img
v-for="(item, index) in allImages"
:key="'img-' + index"
:src="getFileUrl(item.path)"
:alt="item.name"
class="image-thumbnail"
@click="previewImage(getFileUrl(item.path))"
/>
<img v-for="(item, index) in allImages" :key="'img-' + index" :src="getFileUrl(item.path)"
:alt="item.name" class="image-thumbnail" @click="previewImage(getFileUrl(item.path))" />
</div>
</div>
</div>
@ -414,11 +374,7 @@
</a-modal>
<!-- 文件预览弹窗 -->
<FilePreviewModal
v-model:open="previewModalVisible"
:file-url="previewFileUrl"
:file-name="previewFileName"
/>
<FilePreviewModal v-model:open="previewModalVisible" :file-url="previewFileUrl" :file-name="previewFileName" />
</div>
</template>
@ -543,12 +499,18 @@ const domainTags = computed(() => {
}
});
// 稿//
const canEdit = computed(() => {
const s = course.value.status;
return s === 'DRAFT' || s === 'REJECTED' || s === 'ARCHIVED';
});
//
const hasIntroContent = computed(() => {
return course.value.introSummary || course.value.introHighlights ||
course.value.introGoals || course.value.introSchedule ||
course.value.introKeyPoints || course.value.introMethods ||
course.value.introEvaluation || course.value.introNotes;
course.value.introGoals || course.value.introSchedule ||
course.value.introKeyPoints || course.value.introMethods ||
course.value.introEvaluation || course.value.introNotes;
});
//
@ -587,7 +549,7 @@ const allVideos = computed(() => {
paths.forEach((item: any) => {
videos.push({ path: item.path, name: item.name || '视频', source: '资源库' });
});
} catch {}
} catch { }
}
return videos;
});
@ -601,7 +563,7 @@ const allAudios = computed(() => {
paths.forEach((item: any) => {
audios.push({ path: item.path, name: item.name || '音频', source: '资源库' });
});
} catch {}
} catch { }
}
return audios;
});
@ -628,7 +590,7 @@ const allDocuments = computed(() => {
paths.forEach((item: any) => {
docs.push({ path: item.path, name: item.name || '电子绘本', type: 'pdf', source: '资源库' });
});
} catch {}
} catch { }
}
return docs;
});
@ -642,16 +604,16 @@ const allImages = computed(() => {
paths.forEach((item: any) => {
images.push({ path: item.path, name: item.name || '挂图' });
});
} catch {}
} catch { }
}
return images;
});
const hasAnyResources = computed(() => {
return allVideos.value.length > 0 ||
allAudios.value.length > 0 ||
allDocuments.value.length > 0 ||
allImages.value.length > 0;
allAudios.value.length > 0 ||
allDocuments.value.length > 0 ||
allImages.value.length > 0;
});
const totalResourcesCount = computed(() => {
@ -823,38 +785,9 @@ const fetchCourseDetail = async () => {
background: #f0f2f5;
}
.detail-header {
.header-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
background: white;
border-bottom: 1px solid #f0f0f0;
position: sticky;
top: 0;
z-index: 100;
.header-left {
display: flex;
align-items: center;
gap: 12px;
.course-title {
display: flex;
align-items: center;
h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
}
}
.header-actions {
display: flex;
gap: 8px;
}
gap: 8px;
}
.detail-content {
@ -1124,9 +1057,17 @@ const fetchCourseDetail = async () => {
font-size: 18px;
margin-right: 8px;
&.video { color: #722ed1; }
&.ppt { color: #fa8c16; }
&.pdf { color: #f5222d; }
&.video {
color: #722ed1;
}
&.ppt {
color: #fa8c16;
}
&.pdf {
color: #f5222d;
}
}
.resource-name {

View File

@ -186,6 +186,12 @@ const fetchCourseDetail = async () => {
router.push(`/admin/packages/${courseId.value}`);
return;
}
//
if (course?.status === 'PENDING') {
message.warning('审核中的课程包不允许编辑,请等待审核完成或先撤销审核');
router.push(`/admin/packages/${courseId.value}`);
return;
}
//
formData.basic.name = course.name;