feat:作业编辑接口替换;章节编辑新增字段替换,回显功能完善

This commit is contained in:
yuk255 2025-09-22 22:26:58 +08:00
parent b2ec1e2015
commit f80fcb7025
13 changed files with 461 additions and 411 deletions

View File

@ -505,6 +505,63 @@ export class TeachCourseApi {
throw error
}
}
/**
*
*/
static async getSectionVideo(courseId:string,sectionId: string): Promise<ApiResponseWithResult<any>> {
try {
const response = await ApiRequest.get<{ result: any }>(`/aiol/aiolCourse/${courseId}/section_video/${sectionId}`)
console.log('📑 查询小节绑定的视频响应:', response)
return response
} catch (error) {
console.error('❌ 查询小节绑定的视频失败:', error)
throw error
}
}
/**
*
*/
static async getSectionDocument(courseId:string,sectionId: string): Promise<ApiResponseWithResult<any>> {
try {
const response = await ApiRequest.get<{ result: any }>(`/aiol/aiolCourse/${courseId}/section_document/${sectionId}`)
console.log('📑 查询小节绑定的文档响应:', response)
return response
} catch (error) {
console.error('❌ 查询小节绑定的文档失败:', error)
throw error
}
}
/**
*
*/
static async getSectionExam(courseId:string,sectionId: string): Promise<ApiResponseWithResult<any>> {
try {
const response = await ApiRequest.get<{ result: any }>(`/aiol/aiolCourse/${courseId}/section_exam/${sectionId}`)
console.log('📑 查询小节绑定的考试响应:', response)
return response
} catch (error) {
console.error('❌ 查询小节绑定的考试失败:', error)
throw error
}
}
/**
*
*/
static async getSectionHomeWork(courseId:string,sectionId: string): Promise<ApiResponseWithResult<any>> {
try {
const response = await ApiRequest.get<{ result: any }>(`/aiol/aiolCourse/${courseId}/section_homework/${sectionId}`)
console.log('📑 查询小节绑定的作业响应:', response)
return response
} catch (error) {
console.error('❌ 查询小节绑定的作业失败:', error)
throw error
}
}
}
// 作业相关类型定义
@ -603,9 +660,9 @@ export class HomeworkApi {
*/
static async editHomework(data: EditHomeworkRequest): Promise<ApiResponse<any>> {
try {
console.log('🚀 发送编辑作业请求:', { url: '/aiol/aiolHomework/edit', data })
console.log('🚀 发送编辑作业请求:', { url: '/aiol/aiolHomework/teacher_edit', data })
const response = await ApiRequest.put<any>('/aiol/aiolHomework/edit', data)
const response = await ApiRequest.post<any>('/aiol/aiolHomework/teacher_edit', data)
console.log('✏️ 编辑作业响应:', response)
return response
@ -1079,4 +1136,21 @@ class DiscussionApi {
throw error
}
}
/**
*
*/
static async untopComment(commentId: string): Promise<ApiResponse> {
try {
console.log('🚀 发送评论取消置顶请求:', { url: `/aiol/aiolComment/untop/${commentId}`, commentId })
const response = await ApiRequest.get<ApiResponse>(`/aiol/aiolComment/untop/${commentId}`)
console.log('👍 评论取消置顶响应:', response)
return response
} catch (error) {
console.error('❌ 评论取消置顶失败:', error)
throw error
}
}
}

View File

@ -16,7 +16,7 @@
<n-select v-model:value="selectedType" :options="typeOptions" placeholder="全部" class="type-select" />
</div> -->
<div class="search-item">
<n-input v-model:value="searchKeyword" placeholder="搜索试卷" />
<n-input v-model:value="searchKeyword" type="text" placeholder="搜索试卷" />
</div>
<div class="info-text">
已全部加载,{{ totalCount }}份考试/练习
@ -70,14 +70,14 @@
</div>
<!-- 分页 -->
<div class="pagination-section">
<!-- <div class="pagination-section">
<CustomPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="totalCount"
:page-sizes="[10, 20, 50]"
/>
</div>
</div> -->
</div>
<template #footer>
@ -94,7 +94,7 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { NModal, NButton, NTag } from 'naive-ui'
import CustomPagination from './CustomPagination.vue'
// import CustomPagination from './CustomPagination.vue'
import { TeachCourseApi } from '@/api/modules/teachCourse'
interface ExamPaper {
@ -123,7 +123,7 @@ const props = defineProps<Props>()
const selectedType = ref('全部')
const searchKeyword = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
// const pageSize = ref(10)
const selectedExamId = ref<number | null>(null)
//

View File

@ -77,7 +77,7 @@ defineExpose({
}
.fade-enter-active, .fade-leave-active {
transition: all 0.3s ease;
transition: all 0.1s ease;
}
.fade-enter-from {

View File

@ -78,11 +78,11 @@
<span>班级管理</span>
</router-link>
</div>
<router-link to="/teacher/my-resources" class="nav-item" :class="{ active: activeNavItem === 2 }"
<!-- <router-link to="/teacher/my-resources" class="nav-item" :class="{ active: activeNavItem === 2 }"
@click="setActiveNavItem(2)">
<img :src="activeNavItem === 2 ? '/images/teacher/我的资源(选中).png' : '/images/teacher/我的资源.png'" alt="">
<span>我的资源</span>
</router-link>
</router-link> -->
<router-link to="/teacher/message-center" class="nav-item" :class="{ active: activeNavItem === 5 }"
@click="setActiveNavItem(5)">
<img :src="activeNavItem === 5 ? '/images/profile/message-active.png' : '/images/profile/message.png'"
@ -150,8 +150,19 @@
<span class="breadcrumb-side"></span>
<div class="custom-breadcrumb">
<!-- 左侧课程管理 -->
<span class="breadcrumb-item clickable first-item"
@click="handleBreadcrumbClick('/teacher/course-management')">课程管理</span>
<div class="breadcrumb-item clickable first-item" v-if="isCourseEditor">
<n-button text @click="handleBreadcrumbClick('/teacher/course-management')">
<template #icon>
<n-icon>
<ChevronBackSharp />
</n-icon>
</template>
课程管理
</n-button>
</div>
<!-- <span class="breadcrumb-item clickable first-item"
@click="handleBreadcrumbClick('/teacher/course-management')" v-else>课程管理</span> -->
<!-- 右侧路径 -->
<div class="breadcrumb-path">
<span v-for="(item, index) in breadcrumbPathItems" :key="index" class="breadcrumb-item" :class="{
@ -177,6 +188,7 @@ import { ref, onMounted, computed, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ChevronDownOutline } from '@vicons/ionicons5'
import { useUserStore } from '@/stores/user'
import { ChevronBackSharp } from '@vicons/ionicons5'
const userStore = useUserStore()
@ -214,6 +226,8 @@ const breadcrumbDisplay = computed(() => {
return true;
});
const isCourseEditor = computed(() => route.path.includes('course-editor') || route.path.includes('chapter-editor-teacher'));
const setActiveNavItem = (index: number) => {
activeNavItem.value = index;
//
@ -615,18 +629,13 @@ const breadcrumbPathItems = computed(() => {
);
}
console.log('课程编辑器页面面包屑:', breadcrumbs);
return breadcrumbs;
}
// >>>>
// >>/
if (currentPath.includes('chapter-editor-teacher')) {
const courseId = route.params.courseId;
let breadcrumbs = [
{
title: '课程管理',
path: '/teacher/course-management'
},
{
title: '课程管理',
path: '/teacher/course-management'
@ -636,22 +645,16 @@ const breadcrumbPathItems = computed(() => {
path: `/teacher/course-editor/${courseId}`
},
{
title: '章节',
title: route.query.mode === 'add' ? '新建章节' : '编辑章节',
path: `/teacher/course-editor/${courseId}/chapters`
},
{
title: '章节名称',
path: currentPath
}
];
console.log('章节编辑页面面包屑:', breadcrumbs);
return breadcrumbs;
}
//
if (currentPath.includes('student-management')) {
console.log('学员管理页面路径:', currentPath);
let breadcrumbs: Array<{ title: string, path: string }> = [];
if (currentPath.includes('student-library')) {
@ -667,7 +670,6 @@ const breadcrumbPathItems = computed(() => {
}
];
} else if (currentPath.includes('class-management')) {
console.log('匹配到班级管理页面');
breadcrumbs = [
{
title: '学员中心',
@ -679,7 +681,6 @@ const breadcrumbPathItems = computed(() => {
}
];
} else if (currentPath.endsWith('/student-management')) {
console.log('匹配到学员管理主页面');
breadcrumbs = [
{
title: '学员中心',
@ -688,7 +689,6 @@ const breadcrumbPathItems = computed(() => {
];
}
console.log('学员管理页面面包屑:', breadcrumbs);
return breadcrumbs;
}
@ -1030,10 +1030,10 @@ const updateActiveNavItem = () => {
/* Firefox */
-ms-overflow-style: none;
/* IE and Edge */
}
}
/* 隐藏Webkit浏览器的滚动条 */
.sidebar-container::-webkit-scrollbar {
/* 隐藏Webkit浏览器的滚动条 */
.sidebar-container::-webkit-scrollbar {
display: none;
}
@ -1135,6 +1135,7 @@ const updateActiveNavItem = () => {
.orchestration-nav {
margin-top: 10px;
}
.nav-container .nav-item {
margin-left: 15px;
width: 210px;
@ -1244,6 +1245,7 @@ const updateActiveNavItem = () => {
width: 20px;
height: 20px;
}
.router-view-container {
flex: 1;
padding: 10px 25px;
@ -1316,6 +1318,7 @@ const updateActiveNavItem = () => {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
}
.breadcrumb-item {
@ -1355,5 +1358,3 @@ const updateActiveNavItem = () => {
font-weight: normal;
}
</style>

View File

@ -655,7 +655,7 @@ onMounted(() => {
/* 搜索过渡效果 */
.fade-enter-active,
.fade-leave-active {
transition: all 0.3s ease;
transition: all 0.1s ease;
}
.fade-enter-from {

View File

@ -651,7 +651,7 @@ const handleIssuanceConfirm = (selectedExams: any[]) => {
/* 表格过渡动画 */
.table-fade-enter-active,
.table-fade-leave-active {
transition: all 0.3s ease;
transition: all 0.1s ease;
}
.table-fade-enter-from {
@ -665,7 +665,7 @@ const handleIssuanceConfirm = (selectedExams: any[]) => {
}
.table-fade-move {
transition: transform 0.3s ease;
transition: transform 0.1s ease;
}
/* ========== 空状态样式 ========== */

View File

@ -843,7 +843,7 @@ document.addEventListener('click', closeFileMenu)
/* 过渡动画 */
.certificate-fade-enter-active {
transition: all 0.5s ease;
transition: all 0.1s ease;
}
.certificate-fade-leave-active {
@ -861,7 +861,7 @@ document.addEventListener('click', closeFileMenu)
}
.certificate-fade-move {
transition: transform 0.5s ease;
transition: transform 0.1s ease;
}
/* 确保动画可见 */

View File

@ -125,8 +125,7 @@
<div class="lesson-section flex-row justify-end">
<span class="lesson-label-text required">小节名称</span>
<div class="lesson-input-container">
<n-input v-model:value="section.lessonName" placeholder="请输入小节名称"
@blur="() => handleLessonBlur(section)" @focus="handleLessonFocus" />
<n-input v-model:value="section.lessonName" placeholder="请输入小节名称" @focus="handleLessonFocus" />
</div>
</div>
@ -493,7 +492,7 @@
<!-- 删除小节确认对话框 -->
<n-modal v-model:show="showDeleteSectionModal" :mask-closable="false" preset="dialog" title="确认删除"
:positive-text="'确定删除'" :negative-text="'取消'" @positive-click="confirmDeleteSection"
:positive-text="'确定删除'" :negative-text="'取消'" :loading="deleting" @positive-click="confirmDeleteSection"
@negative-click="cancelDeleteSection">
<template #header>
<span>确认删除小节</span>
@ -611,8 +610,7 @@ const chapter = ref<Chapter>({
id: 1,
lessonName: '开课彩蛋新开始',
sectionType: 'video', //
targetType: null, // 0=,1=,2=,3=,4=,5=
targetId: null, // ID/ID
targetId: null, // IDtype/ID
//
videoUploadOption: '',
videoFiles: [],
@ -844,72 +842,72 @@ const saveSection = async (section: any, sortOrder: number = 1) => {
return false;
}
// targetTypetargetId
const getTargetTypeAndId = (section: any) => {
console.log('🔍 确定小节的targetType和targetId', section);
// typetargetId
const getTypeAndTargetId = (section: any) => {
console.log('🔍 确定小节的type和targetId', section);
// targetTypetargetId
let targetType = null;
// typetargetId
let type = null;
let targetId = null;
switch (section.sectionType) {
case 'video':
if (section.selectedVideoResource && section.selectedVideoResource.id) {
targetType = 'resource'; //
type = 0; // type=0
targetId = String(section.selectedVideoResource.id);
} else if (section.targetType === 'resource' && section.targetId) {
// targetTypetargetId
targetType = section.targetType;
} else if (section.targetId) {
// targetId
type = 0; // type=0
targetId = String(section.targetId);
}
break;
case 'material':
if (section.selectedMaterialResource && section.selectedMaterialResource.id) {
targetType = 2; //
type = 1; // type=1
targetId = String(section.selectedMaterialResource.id);
} else if (section.targetType === 2 && section.targetId) {
// targetTypetargetId
targetType = section.targetType;
} else if (section.targetId) {
// targetId
type = 1; // type=1
targetId = String(section.targetId);
}
break;
case 'exercise':
if (section.selectedExercise && section.selectedExercise.id) {
targetType = 3; //
type = 4; // type=4
targetId = String(section.selectedExercise.id);
} else if (section.targetType === 3 && section.targetId) {
// targetTypetargetId
targetType = section.targetType;
} else if (section.targetId) {
// targetId
type = 4; // type=4
targetId = String(section.targetId);
}
break;
case 'homework':
if (section.selectedHomework && section.selectedHomework.id) {
targetType = 'homework'; //
type = 3; // type=3
targetId = String(section.selectedHomework.id);
} else if (section.targetType === 'homework' && section.targetId) {
// targetTypetargetId
targetType = section.targetType;
} else if (section.targetId) {
// targetId
type = 3; // type=3
targetId = String(section.targetId);
}
break;
case 'exam':
if (section.selectedExam && section.selectedExam.id) {
targetType = 'exam'; //
type = 2; // type=2
targetId = String(section.selectedExam.id);
} else if (section.targetType === 'exam' && section.targetId) {
// targetTypetargetId
targetType = section.targetType;
} else if (section.targetId) {
// targetId
type = 2; // type=2
targetId = String(section.targetId);
}
break;
case 'discussion':
if (section.selectedDiscussion && section.selectedDiscussion.id) {
targetType = 6; //
type = 5; // type=5
targetId = String(section.selectedDiscussion.id);
} else if (section.targetType === 6 && section.targetId) {
// targetTypetargetId
targetType = section.targetType;
} else if (section.targetId) {
// targetId
type = 5; // type=5
targetId = String(section.targetId);
}
break;
@ -917,19 +915,18 @@ const saveSection = async (section: any, sortOrder: number = 1) => {
break;
}
return { targetType, targetId };
return { type, targetId };
};
const { targetType, targetId } = getTargetTypeAndId(section);
const { type, targetId } = getTypeAndTargetId(section);
const sectionData = {
courseId: courseId,
name: section.lessonName.trim(),
type: 0, //
type: type, // type(0-5)
sortOrder: sortOrder, // 使
parentId: String(currentChapter.value.id), // ID
level: 2, // 2=
targetType: targetType, //
targetId: targetId // ID
};
@ -1055,6 +1052,13 @@ const expandSection = (sectionId: number) => {
console.log('展开小节:', sectionId);
};
//
// const loadSelectedResources = async (sectionId: number | string) => {
// console.log('🔍 sectionId:', sectionId);
// // sectionIdAPI
// return
// };
//
const loadChapters = async () => {
try {
@ -1125,30 +1129,32 @@ const loadChapters = async () => {
//
if (editChapterData.sections && Array.isArray(editChapterData.sections)) {
console.log('🔍 原始小节数据:', editChapterData.sections);
editChapterData.sections.forEach((sectionData: any) => {
const sectionId = sectionData.id; // ID
console.log('🔍 处理小节:', { originalId: sectionData.id, name: sectionData.name, targetType: sectionData.targetType, targetId: sectionData.targetId });
// targetType
// 使 for...of
for (const sectionData of editChapterData.sections) {
const sectionId = sectionData.id; // ID
console.log('🔍 处理小节:', { originalId: sectionData.id, name: sectionData.name, type:sectionData.type, targetId: sectionData.targetId });
// type
let sectionType = 'video'; //
if (sectionData.targetType !== null && sectionData.targetType !== undefined) {
switch (sectionData.targetType) {
if (sectionData.type !== null && sectionData.type !== undefined) {
switch (sectionData.type) {
case 0:
sectionType = 'video';
break;
case 2:
case 1:
sectionType = 'material';
break;
case 3:
sectionType = 'exercise';
break;
case 4:
sectionType = 'homework';
break;
case 5:
case 2:
sectionType = 'exam';
break;
case 6:
case 3:
sectionType = 'homework';
break;
case 4:
sectionType = 'exercise';
break;
case 5:
sectionType = 'discussion';
break;
default:
@ -1161,7 +1167,6 @@ const loadChapters = async () => {
id: sectionId, // 使ID
lessonName: sectionData.name || '未命名小节',
sectionType: sectionType,
targetType: sectionData.targetType || null, // targetType
targetId: sectionData.targetId || null, // targetId
//
videoUploadOption: '',
@ -1179,69 +1184,70 @@ const loadChapters = async () => {
// contentDescription: sectionData.description || ''
};
// targetTypetargetId
if (sectionData.targetType !== null && sectionData.targetId) {
if (sectionData.targetType === 0) {
// -
const dataRes = await getSelectedResources(sectionData);
// type
if (dataRes) {
if (sectionData.type === 0) {
//
newSection.videoUploadOption = '从课件库选择';
//
newSection.selectedVideoResource = {
id: sectionData.targetId,
name: `视频资源 (ID: ${sectionData.targetId})`,
id: dataRes.id || sectionData.targetId,
name: dataRes.name || dataRes.fileName || `视频资源 (ID: ${sectionData.targetId})`,
type: 0,
fileSize: 0,
createTime: '',
fileUrl: ''
fileSize: dataRes.fileSize || 0,
createTime: dataRes.createTime || '',
fileUrl: dataRes.fileUrl || dataRes.url || ''
};
} else if (sectionData.targetType === 2) {
} else if (sectionData.type === 1) {
//
newSection.materialUploadOption = '从资源库选择';
//
newSection.selectedMaterialResource = {
id: sectionData.targetId,
name: `资料文档 (ID: ${sectionData.targetId})`,
id: dataRes.id || sectionData.targetId,
name: dataRes.name || dataRes.fileName || `资料文档 (ID: ${sectionData.targetId})`,
type: 2,
fileSize: 0,
createTime: '',
fileUrl: ''
fileSize: dataRes.fileSize || 0,
createTime: dataRes.createTime || '',
fileUrl: dataRes.fileUrl || dataRes.url || ''
};
} else if (sectionData.targetType === 3) {
//
newSection.selectedExercise = {
id: sectionData.targetId,
title: `练习 (ID: ${sectionData.targetId})`,
createTime: '',
updateTime: ''
};
} else if (sectionData.targetType === 4) {
//
newSection.selectedHomework = {
id: sectionData.targetId,
title: `作业 (ID: ${sectionData.targetId})`,
createTime: '',
updateTime: ''
};
} else if (sectionData.targetType === 5) {
} else if (sectionData.type === 2) {
//
newSection.selectedExam = {
id: sectionData.targetId,
title: `考试 (ID: ${sectionData.targetId})`,
createTime: '',
updateTime: ''
id: dataRes.id || sectionData.targetId,
title: dataRes.title || dataRes.name || `考试 (ID: ${sectionData.targetId})`,
createTime: dataRes.createTime || '',
updateTime: dataRes.updateTime || ''
};
} else if (sectionData.targetType === 6) {
} else if (sectionData.type === 3) {
//
newSection.selectedHomework = {
id: dataRes.id || sectionData.targetId,
title: dataRes.title || dataRes.name || `作业 (ID: ${sectionData.targetId})`,
createTime: dataRes.createTime || '',
updateTime: dataRes.updateTime || ''
};
} else if (sectionData.type === 4) {
//
newSection.selectedExercise = {
id: dataRes.id || sectionData.targetId,
title: dataRes.title || dataRes.name || `练习 (ID: ${sectionData.targetId})`,
createTime: dataRes.createTime || '',
updateTime: dataRes.updateTime || ''
};
} else if (sectionData.type === 5) {
//
newSection.selectedDiscussion = {
id: sectionData.targetId,
title: `讨论 (ID: ${sectionData.targetId})`,
createTime: '',
updateTime: ''
id: dataRes.id || sectionData.targetId,
title: dataRes.title || dataRes.name || `讨论 (ID: ${sectionData.targetId})`,
createTime: dataRes.createTime || '',
updateTime: dataRes.updateTime || ''
};
}
}
newChapter.sections.push(newSection);
});
}
console.log('🔍 转换后的小节数据:', newChapter.sections);
}
@ -1273,214 +1279,7 @@ const loadChapters = async () => {
}
}
// URL
const editChapterId = route.query.chapterId as string;
if (editChapterId) {
console.log('✏️ 编辑特定章节模式章节ID:', editChapterId);
} else {
console.log('✏️ 编辑模式:加载第一个章节数据');
}
// 使
const response = await TeachCourseApi.getCourseSections(courseId);
console.log('🔍 完整API响应:', response);
console.log('🔍 响应数据:', response.data);
// API
let chapterData = null;
if (response.data && response.data.result) {
chapterData = response.data.result;
console.log('✅ 从服务器加载的章节数据 (result):', chapterData);
} else if (Array.isArray(response.data)) {
chapterData = response.data;
console.log('✅ 从服务器加载的章节数据 (array):', chapterData);
}
if (chapterData && Array.isArray(chapterData)) {
console.log('✅ 章节数据数量:', chapterData.length);
// level(level=1)(level=2)
const chaptersData = chapterData.filter((item: any) => item.level === 1);
const sectionsData = chapterData.filter((item: any) => item.level === 2);
console.log('📚 章数据:', chaptersData);
console.log('📖 节数据:', sectionsData);
// - ID
if (chaptersData.length > 0) {
let targetChapterData = null;
if (editChapterId) {
// ID
targetChapterData = chaptersData.find((ch: any) => ch.id === editChapterId);
if (!targetChapterData) {
console.error('❌ 未找到指定ID的章节:', editChapterId);
message.error('未找到指定的章节');
return;
}
console.log('🎯 找到目标章节:', targetChapterData);
} else {
//
targetChapterData = chaptersData[0];
console.log('📚 加载第一个章节:', targetChapterData);
}
const newChapter: Chapter = {
id: parseInt(targetChapterData.id || '0'),
name: targetChapterData.name || '未命名章节',
sections: []
};
//
const chapterSections = sectionsData.filter((section: any) => section.parent_id === targetChapterData.id);
chapterSections.forEach((sectionData: any) => {
// targetType
let sectionType = 'video'; //
if (sectionData.targetType !== null && sectionData.targetType !== undefined) {
switch (sectionData.targetType) {
case 0:
sectionType = 'video';
break;
case 2:
sectionType = 'material';
break;
case 3:
sectionType = 'exercise';
break;
case 4:
sectionType = 'homework';
break;
case 5:
sectionType = 'exam';
break;
case 6:
sectionType = 'discussion';
break;
default:
sectionType = 'video';
break;
}
}
const newSection: any = {
id: parseInt(sectionData.id || '0'),
lessonName: sectionData.name || '未命名小节',
sectionType: sectionType,
targetType: sectionData.targetType || null, // targetType
targetId: sectionData.targetId || null, // targetId
//
videoUploadOption: '',
videoFiles: [],
selectedVideoResource: null,
//
materialUploadOption: '',
materialFiles: [],
selectedMaterialResource: null,
//
coursewareName: '课件准备PPT',
coursewareUploadOption: '',
coursewareFiles: [],
contentTitle: sectionData.name || '未命名小节',
// contentDescription: ''
};
// targetTypetargetId
if (sectionData.targetType !== null && sectionData.targetId) {
if (sectionData.targetType === 0) {
//
newSection.videoUploadOption = '从课件库选择';
//
newSection.selectedVideoResource = {
id: sectionData.targetId,
name: `视频资源 (ID: ${sectionData.targetId})`,
type: 0,
fileSize: 0,
createTime: '',
fileUrl: ''
};
} else if (sectionData.targetType === 2) {
//
newSection.materialUploadOption = '从资源库选择';
//
newSection.selectedMaterialResource = {
id: sectionData.targetId,
name: `资料文档 (ID: ${sectionData.targetId})`,
type: 2,
fileSize: 0,
createTime: '',
fileUrl: ''
};
} else if (sectionData.targetType === 3) {
//
newSection.selectedExercise = {
id: sectionData.targetId,
title: `练习 (ID: ${sectionData.targetId})`,
createTime: '',
updateTime: ''
};
} else if (sectionData.targetType === 4) {
//
newSection.selectedHomework = {
id: sectionData.targetId,
title: `作业 (ID: ${sectionData.targetId})`,
createTime: '',
updateTime: ''
};
} else if (sectionData.targetType === 5) {
//
newSection.selectedExam = {
id: sectionData.targetId,
title: `考试 (ID: ${sectionData.targetId})`,
createTime: '',
updateTime: ''
};
} else if (sectionData.targetType === 6) {
//
newSection.selectedDiscussion = {
id: sectionData.targetId,
title: `讨论 (ID: ${sectionData.targetId})`,
createTime: '',
updateTime: ''
};
}
}
newChapter.sections.push(newSection);
});
//
chapter.value = newChapter;
//
autoExpandFirstSection();
message.success(`成功加载章节:${chapter.value.name}`);
} else {
//
console.log('📝 没有找到章节数据,创建默认章节');
const defaultChapter: Chapter = {
id: 0,
name: '',
sections: []
};
chapter.value = defaultChapter;
message.info('当前课程还没有章节,请添加第一个章节');
}
} else {
console.warn('⚠️ 没有获取到章节数据');
console.warn('⚠️ 响应结构:', response);
console.warn('⚠️ 响应数据:', response.data);
//
const defaultChapter: Chapter = {
id: 0,
name: '',
sections: []
};
chapter.value = defaultChapter;
message.info('当前课程还没有章节,请添加第一个章节');
}
} catch (error) {
console.error('❌ 加载章节失败:', error);
console.error('❌ 错误详情:', error);
@ -1598,27 +1397,27 @@ const openDiscussionCreator = (section: any) => {
// };
//
const handleLessonBlur = async (section: any) => {
console.log('课节名称已更新:', section.lessonName);
if (section.lessonName && section.lessonName.trim()) {
// contentTitle使
section.contentTitle = section.lessonName;
// const handleLessonBlur = async (section: any) => {
// console.log(':', section.lessonName);
// if (section.lessonName && section.lessonName.trim()) {
// // contentTitle使
// section.contentTitle = section.lessonName;
// (ID)
if (currentChapter.value.id && currentChapter.value.id !== 0) {
console.log('📝 章节已存在,自动保存小节');
//
const sectionIndex = currentChapter.value.sections.findIndex((s: any) => s.id === section.id);
const sortOrder = sectionIndex >= 0 ? sectionIndex + 1 : 1;
const saved = await saveSection(section, sortOrder);
if (saved) {
message.success('小节保存成功');
}
} else {
console.log('📝 章节尚未保存,小节将在章节保存时一并保存');
}
}
};
// // (ID)
// if (currentChapter.value.id && currentChapter.value.id !== 0) {
// console.log('📝 ');
// //
// const sectionIndex = currentChapter.value.sections.findIndex((s: any) => s.id === section.id);
// const sortOrder = sectionIndex >= 0 ? sectionIndex + 1 : 1;
// const saved = await saveSection(section, sortOrder);
// if (saved) {
// message.success('');
// }
// } else {
// console.log('📝 ');
// }
// }
// };
const handleLessonFocus = () => {
console.log('开始编辑课节名称');
@ -1679,13 +1478,11 @@ const handleExamLibraryConfirm = (selectedExam: any) => {
if (examLibraryType.value === '0') {
//
currentEditingSection.value.selectedExercise = selectedExam;
currentEditingSection.value.targetType = 3; //
currentEditingSection.value.targetId = selectedExam.id;
message.success(`已选择练习:${selectedExam.title}`);
} else if (examLibraryType.value === '1') {
//
currentEditingSection.value.selectedExam = selectedExam;
currentEditingSection.value.targetType = 5; //
currentEditingSection.value.targetId = selectedExam.id;
message.success(`已选择考试:${selectedExam.title}`);
}
@ -1701,7 +1498,6 @@ const handleHomeworkLibraryConfirm = (selectedHomework: any) => {
if (currentEditingSection.value && selectedHomework) {
//
currentEditingSection.value.selectedHomework = selectedHomework;
currentEditingSection.value.targetType = 4; //
currentEditingSection.value.targetId = selectedHomework.id;
console.log('已设置作业到小节:', currentEditingSection.value);
@ -1719,7 +1515,6 @@ const handleDiscussionLibraryConfirm = (selectedDiscussion: any) => {
if (currentEditingSection.value && selectedDiscussion) {
//
currentEditingSection.value.selectedDiscussion = selectedDiscussion;
currentEditingSection.value.targetType = 6; //
currentEditingSection.value.targetId = selectedDiscussion.id;
console.log('已设置讨论到小节:', currentEditingSection.value);
@ -1733,7 +1528,6 @@ const handleDiscussionLibraryConfirm = (selectedDiscussion: any) => {
//
const clearSelectedDiscussion = (section: any) => {
section.selectedDiscussion = null;
section.targetType = null;
section.targetId = null;
message.success('已移除选择的讨论');
};
@ -1741,7 +1535,6 @@ const clearSelectedDiscussion = (section: any) => {
//
const clearSelectedHomework = (section: any) => {
section.selectedHomework = null;
section.targetType = null;
section.targetId = null;
message.success('已移除选择的作业');
};
@ -1749,14 +1542,12 @@ const clearSelectedHomework = (section: any) => {
//
const clearSelectedExercise = (section: any) => {
section.selectedExercise = null;
section.targetType = null;
section.targetId = null;
message.success('已移除选择的练习');
};
//
const clearSelectedExam = (section: any) => {
section.selectedExam = null;
section.targetType = null;
section.targetId = null;
message.success('已移除选择的考试');
};
@ -1783,26 +1574,60 @@ const removeLessonSection = (sectionId: number) => {
};
//
const confirmDeleteSection = () => {
const confirmDeleteSection = async () => {
if (!sectionToDelete.value) {
message.error('删除信息丢失');
return;
}
const index = currentChapter.value.sections.findIndex((section: any) => section.id === sectionToDelete.value!.sectionId);
const sectionId = sectionToDelete.value.sectionId;
const section = currentChapter.value.sections.find((s: any) => s.id === sectionId);
if (!section) {
message.error('未找到要删除的小节');
return;
}
try {
deleting.value = true;
console.log(section,sectionId);
// ID
if (section.id && !section.id.includes('temp')) {
//
console.log('🗑️ 删除已保存的小节ID:', section.id);
await TeachCourseApi.deleteCourseSection(section.id.toString());
console.log('✅ 小节删除接口调用成功');
message.success('小节删除成功');
} else {
//
console.log('🗑️ 删除本地新增的小节临时ID:', section.id);
message.success('小节删除成功');
}
//
const index = currentChapter.value.sections.findIndex((s: any) => s.id === sectionId);
if (index > -1) {
currentChapter.value.sections.splice(index, 1);
message.success('小节删除成功');
console.log('📝 已从页面数组中移除小节');
}
//
if (activeCollapsePanels.value.includes(sectionToDelete.value.sectionId.toString())) {
if (activeCollapsePanels.value.includes(sectionId.toString())) {
autoExpandFirstSection();
}
}
} catch (error) {
console.error('❌ 删除小节失败:', error);
message.error('删除小节失败,请重试');
} finally {
deleting.value = false;
//
showDeleteSectionModal.value = false;
sectionToDelete.value = null;
}
};
//
@ -1825,8 +1650,7 @@ const clearSelectedResource = (section: any, resourceType: number) => {
section.materialFiles = [];
}
// ID
section.targetType = null;
// ID
section.targetId = null;
message.success(`已清除${resourceType === 0 ? '视频' : '资料'}选择`);
@ -1891,13 +1715,11 @@ const handleResourceSelect = (selectedResources: any[]) => {
//
currentEditingSection.value.videoUploadOption = '从课件库选择';
currentEditingSection.value.selectedVideoResource = resource;
currentEditingSection.value.targetType = 0; //
currentEditingSection.value.targetId = resource.id;
} else if (currentResourceType.value === 2) {
//
currentEditingSection.value.materialUploadOption = '从资源库选择';
currentEditingSection.value.selectedMaterialResource = resource;
currentEditingSection.value.targetType = 2; //
currentEditingSection.value.targetId = resource.id;
}
@ -1913,13 +1735,11 @@ const handleResourceUpload = (resource: any) => {
//
currentEditingSection.value.videoUploadOption = '本地上传';
currentEditingSection.value.selectedVideoResource = resource;
currentEditingSection.value.targetType = 0; //
currentEditingSection.value.targetId = resource.id;
} else if (currentResourceType.value === 2) {
//
currentEditingSection.value.materialUploadOption = '本地上传';
currentEditingSection.value.selectedMaterialResource = resource;
currentEditingSection.value.targetType = 2; //
currentEditingSection.value.targetId = resource.id;
}
@ -1961,11 +1781,37 @@ const updateSectionSort = async () => {
console.log(`📝 更新小节 "${section.lessonName}" 排序为: ${index + 1}`);
// sectionTypetype
let type = 0; //
switch (section.sectionType) {
case 'video':
type = 0;
break;
case 'material':
type = 1;
break;
case 'exam':
type = 2;
break;
case 'homework':
type = 3;
break;
case 'exercise':
type = 4;
break;
case 'discussion':
type = 5;
break;
default:
type = 0;
break;
}
const sectionData = {
id: String(section.id),
courseId: courseId,
name: section.lessonName.trim(),
type: 0,
type: type, // type
sortOrder: index + 1, // 1
parentId: String(currentChapter.value.id),
level: 2
@ -2045,6 +1891,119 @@ const getSectionTypeStyle = (value: string) => {
return typeStyles[value] || 'default';
};
//
const getSelectedResources = async (sectionData: any) => {
if (!sectionData?.id || !courseId.value) {
console.warn('⚠️ sectionData.id 或 courseId 为空,无法查询资源');
return;
}
const { id: sectionId, type } = sectionData;
let data:any
try {
console.log('🔍 开始查询小节资源回显数据sectionId:', sectionId, 'type:', type, 'courseId:', courseId.value);
// type
switch (type) {
case 0: {
//
console.log('📹 查询视频资源...');
const videoResult = await TeachCourseApi.getSectionVideo(courseId.value, sectionId);
console.log(videoResult);
if (videoResult.data?.result) {
console.log('📹 视频资源查询成功:', videoResult.data.result);
// TODO:
data = videoResult.data.result;
} else {
console.log('📹 视频资源查询无数据');
}
break;
}
case 1: {
//
console.log('📄 查询文档资源...');
const documentResult = await TeachCourseApi.getSectionDocument(courseId.value, sectionId);
if (documentResult.data?.result) {
console.log('📄 文档资源查询成功:', documentResult.data.result);
// TODO:
data = documentResult.data.result;
} else {
console.log('📄 文档资源查询无数据');
}
break;
}
case 2: {
//
console.log('📝 查询考试资源...');
const examResult = await TeachCourseApi.getSectionExam(courseId.value, sectionId);
if (examResult.data?.result) {
console.log('📝 考试资源查询成功:', examResult.data.result);
// TODO:
data = examResult.data.result;
} else {
console.log('📝 考试资源查询无数据');
}
break;
}
case 3: {
//
console.log('📋 查询作业资源...');
const homeworkResult = await TeachCourseApi.getSectionHomeWork(courseId.value, sectionId);
if (homeworkResult.data?.result) {
console.log('📋 作业资源查询成功:', homeworkResult.data.result);
// TODO:
data = homeworkResult.data.result
} else {
console.log('📋 作业资源查询无数据');
}
break;
}
case 4: {
//
console.log('🏃 练习资源接口暂未实现');
// TODO:
// const exerciseResult = await TeachCourseApi.getSectionExercise(courseId.value, sectionId);
break;
}
case 5: {
//
console.log('💬 讨论资源接口暂未实现');
// TODO:
// const discussionResult = await TeachCourseApi.getSectionDiscussion(courseId.value, sectionId);
break;
}
default: {
console.warn('⚠️ 未知的小节类型:', type);
break;
}
}
//
if (Array.isArray(data) && data.length > 0) {
data = data[0];
console.log('📦 检测到数组格式数据,已取第一项:', data);
}
console.log('✅ 小节资源回显查询完成');
return data
} catch (error:any) {
message.error(`❌ 查询小节资源回显失败:${error.message}`)
console.error('❌ 查询小节资源回显失败:', error);
}
};
const goBack = () => {
router.back();
}

View File

@ -605,10 +605,11 @@ const columns: DataTableColumns<Chapter> = [
const getTypeText = (type: string) => {
const typeMap: Record<string, string> = {
'0': '视频',
'1': '文档',
'2': '音频',
'3': '测试',
'4': '作业'
'1': '资料',
'2': '考试',
'3': '作业',
'4': '练习',
'5': '讨论',
}
return typeMap[type] || '-'
}

View File

@ -779,7 +779,7 @@ onMounted(async () => {
/* Tab切换过渡动画 */
.tab-fade-enter-active,
.tab-fade-leave-active {
transition: all 0.3s ease;
transition: all 0.1s ease;
}
.tab-fade-enter-from {
@ -793,7 +793,7 @@ onMounted(async () => {
}
.tab-pane {
animation: fadeIn 0.3s ease;
animation: fadeIn 0.1s ease;
}
@keyframes fadeIn {

View File

@ -319,7 +319,7 @@ const hideSidebar = computed(() => {
/* 内容区域过渡动画 */
.content-transition-enter-active,
.content-transition-leave-active {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.content-transition-enter-from {

View File

@ -85,7 +85,7 @@
</template>
点赞 ({{ reply.likes }})
</n-button>
<n-button quaternary @click="topReply(reply)" v-if="reply.isTop">
<n-button quaternary @click="unTopReply(reply)" v-if="reply.isTop">
<template #icon>
<n-icon size="20">
<svg xmlns="http://www.w3.org/2000/svg"
@ -450,11 +450,26 @@ const topReply = (item: any) => {
DiscussionApi.topComment(item.id).then(() => {
item.isPinned = true
message.success('已置顶')
loadDiscussionInfo()
loadRepliesList()
}).catch(() => {
message.error('置顶失败,请重试')
})
}
const unTopReply = (item: any) => {
DiscussionApi.untopComment(item.id).then(() => {
item.isPinned = false
message.success('已取消置顶')
loadDiscussionInfo()
loadRepliesList()
}).catch(() => {
message.error('取消置顶失败,请重试')
})
}
const replyToReply = (reply: any) => {
replyTarget.value = reply
showReplyToReplyModal.value = true

View File

@ -390,7 +390,7 @@ console.log('CourseStatistics component loaded with ECharts and Tab functionalit
/* Tab切换过渡动画 */
.tab-fade-enter-active,
.tab-fade-leave-active {
transition: all 0.3s ease;
transition: all 0.1s ease;
}
.tab-fade-enter-from {