fix: 课程包详情页标题改为「详情」; 优化课程包编辑页相关逻辑
Made-with: Cursor
This commit is contained in:
parent
877acf33b8
commit
1783706d63
@ -1,36 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="course-detail-view">
|
<div class="course-detail-view">
|
||||||
<!-- 顶部导航 -->
|
<!-- 顶部导航 -->
|
||||||
<div class="detail-header">
|
<a-page-header title="课程包详情" :sub-title="course.name || ''" @back="() => router.back()">
|
||||||
<div class="header-left">
|
<template #extra>
|
||||||
<a-button type="text" @click="router.back()">
|
<div class="header-actions">
|
||||||
<ArrowLeftOutlined />
|
<a-button v-if="canEdit" @click="editCourse">
|
||||||
</a-button>
|
<EditOutlined /> 编辑
|
||||||
<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-button>
|
</a-button>
|
||||||
</a-popconfirm>
|
<a-button @click="viewStats">
|
||||||
</div>
|
<BarChartOutlined /> 数据
|
||||||
</div>
|
</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">
|
<a-spin :spinning="loading">
|
||||||
<div class="detail-content">
|
<div class="detail-content">
|
||||||
@ -223,12 +214,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="section-body">
|
<div class="section-body">
|
||||||
<div class="lesson-cards">
|
<div class="lesson-cards">
|
||||||
<div
|
<div v-for="lesson in courseLessons" :key="lesson.id" class="lesson-card"
|
||||||
v-for="lesson in courseLessons"
|
:class="'lesson-type-' + lesson.lessonType?.toLowerCase()">
|
||||||
:key="lesson.id"
|
|
||||||
class="lesson-card"
|
|
||||||
:class="'lesson-type-' + lesson.lessonType?.toLowerCase()"
|
|
||||||
>
|
|
||||||
<div class="lesson-header">
|
<div class="lesson-header">
|
||||||
<div class="lesson-type-badge" :style="{ background: getLessonTypeBgColor(lesson.lessonType) }">
|
<div class="lesson-type-badge" :style="{ background: getLessonTypeBgColor(lesson.lessonType) }">
|
||||||
{{ translateLessonType(lesson.lessonType) }}
|
{{ translateLessonType(lesson.lessonType) }}
|
||||||
@ -254,29 +241,20 @@
|
|||||||
<div class="lesson-section" v-if="hasLessonResources(lesson)">
|
<div class="lesson-section" v-if="hasLessonResources(lesson)">
|
||||||
<div class="lesson-section-title">核心资源</div>
|
<div class="lesson-section-title">核心资源</div>
|
||||||
<div class="resource-grid">
|
<div class="resource-grid">
|
||||||
<div
|
<div v-if="lesson.videoPath" class="resource-item"
|
||||||
v-if="lesson.videoPath"
|
@click="previewFile(lesson.videoPath, lesson.videoName || '绘本动画')">
|
||||||
class="resource-item"
|
|
||||||
@click="previewFile(lesson.videoPath, lesson.videoName || '绘本动画')"
|
|
||||||
>
|
|
||||||
<VideoCameraOutlined class="resource-icon video" />
|
<VideoCameraOutlined class="resource-icon video" />
|
||||||
<span class="resource-name">{{ lesson.videoName || '绘本动画' }}</span>
|
<span class="resource-name">{{ lesson.videoName || '绘本动画' }}</span>
|
||||||
<EyeOutlined class="resource-action" />
|
<EyeOutlined class="resource-action" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="lesson.pptPath" class="resource-item"
|
||||||
v-if="lesson.pptPath"
|
@click="previewFile(lesson.pptPath, lesson.pptName || '教学课件')">
|
||||||
class="resource-item"
|
|
||||||
@click="previewFile(lesson.pptPath, lesson.pptName || '教学课件')"
|
|
||||||
>
|
|
||||||
<FilePptOutlined class="resource-icon ppt" />
|
<FilePptOutlined class="resource-icon ppt" />
|
||||||
<span class="resource-name">{{ lesson.pptName || '教学课件' }}</span>
|
<span class="resource-name">{{ lesson.pptName || '教学课件' }}</span>
|
||||||
<EyeOutlined class="resource-action" />
|
<EyeOutlined class="resource-action" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="lesson.pdfPath" class="resource-item"
|
||||||
v-if="lesson.pdfPath"
|
@click="previewFile(lesson.pdfPath, lesson.pdfName || '电子绘本')">
|
||||||
class="resource-item"
|
|
||||||
@click="previewFile(lesson.pdfPath, lesson.pdfName || '电子绘本')"
|
|
||||||
>
|
|
||||||
<FilePdfOutlined class="resource-icon pdf" />
|
<FilePdfOutlined class="resource-icon pdf" />
|
||||||
<span class="resource-name">{{ lesson.pdfName || '电子绘本' }}</span>
|
<span class="resource-name">{{ lesson.pdfName || '电子绘本' }}</span>
|
||||||
<EyeOutlined class="resource-action" />
|
<EyeOutlined class="resource-action" />
|
||||||
@ -333,12 +311,8 @@
|
|||||||
<VideoCameraOutlined style="color: #722ed1;" /> 视频资源
|
<VideoCameraOutlined style="color: #722ed1;" /> 视频资源
|
||||||
</div>
|
</div>
|
||||||
<div class="resource-list">
|
<div class="resource-list">
|
||||||
<div
|
<div v-for="(item, index) in allVideos" :key="'video-' + index" class="resource-item-card"
|
||||||
v-for="(item, index) in allVideos"
|
@click="previewFile(item.path, item.name)">
|
||||||
:key="'video-' + index"
|
|
||||||
class="resource-item-card"
|
|
||||||
@click="previewFile(item.path, item.name)"
|
|
||||||
>
|
|
||||||
<VideoCameraOutlined class="item-icon" style="color: #722ed1;" />
|
<VideoCameraOutlined class="item-icon" style="color: #722ed1;" />
|
||||||
<span class="item-name">{{ item.name }}</span>
|
<span class="item-name">{{ item.name }}</span>
|
||||||
<PlayCircleOutlined class="item-action" />
|
<PlayCircleOutlined class="item-action" />
|
||||||
@ -352,12 +326,8 @@
|
|||||||
<AudioOutlined style="color: #52c41a;" /> 音频资源
|
<AudioOutlined style="color: #52c41a;" /> 音频资源
|
||||||
</div>
|
</div>
|
||||||
<div class="resource-list">
|
<div class="resource-list">
|
||||||
<div
|
<div v-for="(item, index) in allAudios" :key="'audio-' + index" class="resource-item-card"
|
||||||
v-for="(item, index) in allAudios"
|
@click="previewFile(item.path, item.name)">
|
||||||
:key="'audio-' + index"
|
|
||||||
class="resource-item-card"
|
|
||||||
@click="previewFile(item.path, item.name)"
|
|
||||||
>
|
|
||||||
<AudioOutlined class="item-icon" style="color: #52c41a;" />
|
<AudioOutlined class="item-icon" style="color: #52c41a;" />
|
||||||
<span class="item-name">{{ item.name }}</span>
|
<span class="item-name">{{ item.name }}</span>
|
||||||
<PlayCircleOutlined class="item-action" />
|
<PlayCircleOutlined class="item-action" />
|
||||||
@ -371,12 +341,8 @@
|
|||||||
<FileTextOutlined style="color: #1890ff;" /> 文档资源
|
<FileTextOutlined style="color: #1890ff;" /> 文档资源
|
||||||
</div>
|
</div>
|
||||||
<div class="resource-list">
|
<div class="resource-list">
|
||||||
<div
|
<div v-for="(item, index) in allDocuments" :key="'doc-' + index" class="resource-item-card"
|
||||||
v-for="(item, index) in allDocuments"
|
@click="previewFile(item.path, item.name)">
|
||||||
: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;" />
|
<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;" />
|
<FilePptOutlined v-else-if="item.type === 'ppt'" class="item-icon" style="color: #fa8c16;" />
|
||||||
<FileTextOutlined v-else class="item-icon" style="color: #1890ff;" />
|
<FileTextOutlined v-else class="item-icon" style="color: #1890ff;" />
|
||||||
@ -392,14 +358,8 @@
|
|||||||
<PictureOutlined style="color: #13c2c2;" /> 图片资源
|
<PictureOutlined style="color: #13c2c2;" /> 图片资源
|
||||||
</div>
|
</div>
|
||||||
<div class="image-grid">
|
<div class="image-grid">
|
||||||
<img
|
<img v-for="(item, index) in allImages" :key="'img-' + index" :src="getFileUrl(item.path)"
|
||||||
v-for="(item, index) in allImages"
|
:alt="item.name" class="image-thumbnail" @click="previewImage(getFileUrl(item.path))" />
|
||||||
:key="'img-' + index"
|
|
||||||
:src="getFileUrl(item.path)"
|
|
||||||
:alt="item.name"
|
|
||||||
class="image-thumbnail"
|
|
||||||
@click="previewImage(getFileUrl(item.path))"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -414,11 +374,7 @@
|
|||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- 文件预览弹窗 -->
|
<!-- 文件预览弹窗 -->
|
||||||
<FilePreviewModal
|
<FilePreviewModal v-model:open="previewModalVisible" :file-url="previewFileUrl" :file-name="previewFileName" />
|
||||||
v-model:open="previewModalVisible"
|
|
||||||
:file-url="previewFileUrl"
|
|
||||||
:file-name="previewFileName"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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(() => {
|
const hasIntroContent = computed(() => {
|
||||||
return course.value.introSummary || course.value.introHighlights ||
|
return course.value.introSummary || course.value.introHighlights ||
|
||||||
course.value.introGoals || course.value.introSchedule ||
|
course.value.introGoals || course.value.introSchedule ||
|
||||||
course.value.introKeyPoints || course.value.introMethods ||
|
course.value.introKeyPoints || course.value.introMethods ||
|
||||||
course.value.introEvaluation || course.value.introNotes;
|
course.value.introEvaluation || course.value.introNotes;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 排课参考数据
|
// 排课参考数据
|
||||||
@ -587,7 +549,7 @@ const allVideos = computed(() => {
|
|||||||
paths.forEach((item: any) => {
|
paths.forEach((item: any) => {
|
||||||
videos.push({ path: item.path, name: item.name || '视频', source: '资源库' });
|
videos.push({ path: item.path, name: item.name || '视频', source: '资源库' });
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch { }
|
||||||
}
|
}
|
||||||
return videos;
|
return videos;
|
||||||
});
|
});
|
||||||
@ -601,7 +563,7 @@ const allAudios = computed(() => {
|
|||||||
paths.forEach((item: any) => {
|
paths.forEach((item: any) => {
|
||||||
audios.push({ path: item.path, name: item.name || '音频', source: '资源库' });
|
audios.push({ path: item.path, name: item.name || '音频', source: '资源库' });
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch { }
|
||||||
}
|
}
|
||||||
return audios;
|
return audios;
|
||||||
});
|
});
|
||||||
@ -628,7 +590,7 @@ const allDocuments = computed(() => {
|
|||||||
paths.forEach((item: any) => {
|
paths.forEach((item: any) => {
|
||||||
docs.push({ path: item.path, name: item.name || '电子绘本', type: 'pdf', source: '资源库' });
|
docs.push({ path: item.path, name: item.name || '电子绘本', type: 'pdf', source: '资源库' });
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch { }
|
||||||
}
|
}
|
||||||
return docs;
|
return docs;
|
||||||
});
|
});
|
||||||
@ -642,16 +604,16 @@ const allImages = computed(() => {
|
|||||||
paths.forEach((item: any) => {
|
paths.forEach((item: any) => {
|
||||||
images.push({ path: item.path, name: item.name || '挂图' });
|
images.push({ path: item.path, name: item.name || '挂图' });
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch { }
|
||||||
}
|
}
|
||||||
return images;
|
return images;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasAnyResources = computed(() => {
|
const hasAnyResources = computed(() => {
|
||||||
return allVideos.value.length > 0 ||
|
return allVideos.value.length > 0 ||
|
||||||
allAudios.value.length > 0 ||
|
allAudios.value.length > 0 ||
|
||||||
allDocuments.value.length > 0 ||
|
allDocuments.value.length > 0 ||
|
||||||
allImages.value.length > 0;
|
allImages.value.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalResourcesCount = computed(() => {
|
const totalResourcesCount = computed(() => {
|
||||||
@ -823,38 +785,9 @@ const fetchCourseDetail = async () => {
|
|||||||
background: #f0f2f5;
|
background: #f0f2f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-header {
|
.header-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
gap: 8px;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-content {
|
.detail-content {
|
||||||
@ -1124,9 +1057,17 @@ const fetchCourseDetail = async () => {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|
||||||
&.video { color: #722ed1; }
|
&.video {
|
||||||
&.ppt { color: #fa8c16; }
|
color: #722ed1;
|
||||||
&.pdf { color: #f5222d; }
|
}
|
||||||
|
|
||||||
|
&.ppt {
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pdf {
|
||||||
|
color: #f5222d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-name {
|
.resource-name {
|
||||||
|
|||||||
@ -186,6 +186,12 @@ const fetchCourseDetail = async () => {
|
|||||||
router.push(`/admin/packages/${courseId.value}`);
|
router.push(`/admin/packages/${courseId.value}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 审核中(待审核)的课程包不允许编辑
|
||||||
|
if (course?.status === 'PENDING') {
|
||||||
|
message.warning('审核中的课程包不允许编辑,请等待审核完成或先撤销审核');
|
||||||
|
router.push(`/admin/packages/${courseId.value}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 基本信息
|
// 基本信息
|
||||||
formData.basic.name = course.name;
|
formData.basic.name = course.name;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user