fix:修复课程删除报错,修复课程编辑字段保存回显问题

This commit is contained in:
yuk255 2025-09-16 19:42:33 +08:00
parent 568d8fdf47
commit bff716f6b6
4 changed files with 439 additions and 361 deletions

View File

@ -193,9 +193,7 @@ export class TeachCourseApi {
try {
console.log('🚀 发送删除课程请求:', { url: '/aiol/aiolCourse/delete', id })
const response = await ApiRequest.delete<any>('/aiol/aiolCourse/delete', {
params: { id }
})
const response = await ApiRequest.delete<any>(`/aiol/aiolCourse/delete?id=${id}`, )
console.log('🗑️ 删除课程响应:', response)
return response

View File

@ -2,7 +2,7 @@
<div class="course-category">
<!-- 顶部 -->
<div class="top">
<n-tabs v-model:value="activeTab" size="large">
<n-tabs v-model:value="activeTab" size="large">
<n-tab-pane name="ongoing" tab="进行中" />
<n-tab-pane name="draft" tab="草稿箱" />
<n-tab-pane name="finished" tab="已结束" />
@ -27,11 +27,7 @@
<!-- 错误状态 -->
<div v-else-if="error" class="error-container">
<n-result
status="error"
title="加载失败"
:description="error"
>
<n-result status="error" title="加载失败" :description="error">
<template #footer>
<n-space justify="center">
<n-button type="primary" @click="getCourseList(true)">
@ -49,10 +45,7 @@
<!-- 空状态 -->
<div v-else-if="courseList.length === 0" class="empty-container">
<n-empty
description="暂无课程数据"
size="large"
>
<n-empty description="暂无课程数据" size="large">
<template #extra>
<n-button type="primary" @click="navigateToCreateCourse">
创建课程
@ -67,13 +60,8 @@
<div class="course-image-container">
<div class="section-title" :class="{ 'offline': course.status === 0 }">{{ course.statusText }}
</div>
<n-popselect
:options="getOptionsForCourse(course)"
trigger="hover"
placement="bottom-end"
:render-label="renderOptionLabel"
@update:value="(value: string) => handleOptionSelect(value, course)"
>
<n-popselect :options="getOptionsForCourse(course)" trigger="hover" placement="bottom-end"
:render-label="renderOptionLabel" @update:value="(value: string) => handleOptionSelect(value, course)">
<div class="more-options">
<span class="more-icon">
<n-icon size="20">
@ -117,8 +105,17 @@
<script setup lang="ts">
import { ref, h, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { EllipsisVerticalSharp, Refresh } from '@vicons/ionicons5';
import { useMessage, useDialog } from 'naive-ui';
import {
EllipsisVerticalSharp,
Refresh,
CloudOfflineOutline, //
CreateOutline, //
CopyOutline, //
TrashOutline, //
CloudUploadOutline, //
AddOutline // /
} from '@vicons/ionicons5';
import { useMessage, useDialog, NIcon } from 'naive-ui';
import TeachCourseApi, { type TeachCourse } from '@/api/modules/teachCourse';
import { useCourseStore } from '@/stores/course';
@ -159,9 +156,9 @@ const getCourseList = async (forceRefresh: boolean = false) => {
const response = await TeachCourseApi.getTeacherCourseList(params);
loading.value = false;
if (response && response.data) {
const courseData = response.data.result.map((course: TeachCourse): CourseDisplayItem => ({
...course,
//
const courseData = response.data.result.map((course: TeachCourse): CourseDisplayItem => ({
...course,
//
statusText: getStatusText(course.status),
image: course.cover || '/images/teacher/fj.png'
}));
@ -190,7 +187,7 @@ const getCourseList = async (forceRefresh: boolean = false) => {
//
message.error(error.value);
} finally {
loading.value = false;
loading.value = false;
}
};
@ -265,21 +262,24 @@ const handleSearch = async () => {
const getOptionsForCourse = (course: CourseDisplayItem) => {
if (course.status === 1) { //
return [
{ label: '下架', value: 'offline', icon: '/images/teacher/下架.png' },
{ label: '编辑', value: 'edit', icon: '/images/teacher/小编辑.png' },
{ label: '删除', value: 'delete', icon: '/images/teacher/删除.png' }
{ label: '下架', value: 'offline', icon: CloudOfflineOutline },
{ label: '编辑', value: 'edit', icon: CreateOutline },
{ label: '复制', value: 'copy', icon: CopyOutline },
{ label: '删除', value: 'delete', icon: TrashOutline }
];
} else if (course.status === 0) { // /稿
return [
{ label: '发布', value: 'publish', icon: '/images/teacher/加号.png' },
{ label: '编辑', value: 'edit', icon: '/images/teacher/小编辑.png' },
{ label: '删除', value: 'delete', icon: '/images/teacher/删除.png' }
{ label: '发布', value: 'publish', icon: CloudUploadOutline },
{ label: '编辑', value: 'edit', icon: CreateOutline },
{ label: '复制', value: 'copy', icon: CopyOutline },
{ label: '删除', value: 'delete', icon: TrashOutline }
];
} else if (course.status === 2) { //
return [
{ label: '发布', value: 'publish', icon: '/images/teacher/加号.png' },
{ label: '编辑', value: 'edit', icon: '/images/teacher/小编辑.png' },
{ label: '删除', value: 'delete', icon: '/images/teacher/删除.png' }
{ label: '发布', value: 'publish', icon: CloudUploadOutline },
{ label: '编辑', value: 'edit', icon: CreateOutline },
{ label: '复制', value: 'copy', icon: CopyOutline },
{ label: '删除', value: 'delete', icon: TrashOutline }
];
}
return [];
@ -294,13 +294,9 @@ const renderOptionLabel = (option: any) => {
gap: '6px'
}
}, [
h('img', {
src: option.icon,
alt: '',
style: {
width: '13px',
height: '13px'
}
h(NIcon, {
size: 16,
component: option.icon
}),
option.label
]);
@ -312,12 +308,8 @@ const handleOptionSelect = (value: string, course: any) => {
// value
switch (value) {
case 'edit':
// - store
console.log('✏️ 编辑课程,准备数据:', course);
// store
courseStore.setCourseEditData(course);
// ID
router.push(`/teacher/course-create/${course.id}`);
break;
@ -333,6 +325,12 @@ const handleOptionSelect = (value: string, course: any) => {
//
handlePublishCourse(course);
break;
case 'copy':
//
courseStore.setCourseEditData(course);
// ID
router.push(`/teacher/course-create/${course.id}?type=copy`);
break;
default:
break;
}
@ -475,206 +473,206 @@ onMounted(async () => {
overflow-x: hidden;
/* 防止水平滚动 */
display: flex;
flex-direction: column;
}
flex-direction: column;
}
.top {
padding: 10px 20px;
white-space: nowrap;
/* 进一步减少左右padding */
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
width: 100%;
/* 确保宽度充满父容器 */
box-sizing: border-box;
}
.top {
padding: 10px 20px;
white-space: nowrap;
/* 进一步减少左右padding */
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
width: 100%;
/* 确保宽度充满父容器 */
box-sizing: border-box;
}
.nav-links {
flex: 1;
}
.nav-links {
flex: 1;
}
.actions {
display: flex;
align-items: center;
flex-shrink: 0;
gap: 12px;
}
.actions {
display: flex;
align-items: center;
flex-shrink: 0;
gap: 12px;
}
.search-container {
display: flex;
align-items: center;
}
.search-container {
display: flex;
align-items: center;
}
.course-container {
width: 100%;
padding: 20px;
box-sizing: border-box;
flex: 1;
display: flex;
flex-direction: column;
}
.course-container {
width: 100%;
padding: 20px;
box-sizing: border-box;
flex: 1;
display: flex;
flex-direction: column;
}
.loading-container {
display: contents;
}
.loading-container {
display: contents;
}
.error-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
width: 100%;
padding: 40px 20px;
}
.error-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
width: 100%;
padding: 40px 20px;
}
.empty-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
width: 100%;
padding: 40px 20px;
}
.empty-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
width: 100%;
padding: 40px 20px;
}
.course-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
/* 默认4列确保100%缩放下不被截断 */
gap: 12px;
/* 进一步减少间距 */
margin-bottom: 20px;
width: 100%;
max-width: 100%;
box-sizing: border-box;
padding: 0 3px;
/* 减少内边距 */
}
.course-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
/* 默认4列确保100%缩放下不被截断 */
gap: 12px;
/* 进一步减少间距 */
margin-bottom: 20px;
width: 100%;
max-width: 100%;
box-sizing: border-box;
padding: 0 3px;
/* 减少内边距 */
}
.course-card {
width: 100%;
height: auto;
/* aspect-ratio: 190 / 201; */
background: #FFFFFF;
border: 1px solid #D8D8D8;
box-sizing: border-box;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
}
.course-card {
width: 100%;
height: auto;
/* aspect-ratio: 190 / 201; */
background: #FFFFFF;
border: 1px solid #D8D8D8;
box-sizing: border-box;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
}
.course-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
/* transform: translateY(-5px); */
}
.course-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
/* transform: translateY(-5px); */
}
.course-image-container {
position: relative;
padding: 0;
width: 100%;
height: 30px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.course-image-container {
position: relative;
padding: 0;
width: 100%;
height: 30px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.section-title {
width: 50px;
height: 20px;
background: #0288D1;
color: white;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
}
.section-title {
width: 50px;
height: 20px;
background: #0288D1;
color: white;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
}
.section-title.offline {
background: #FF6C17;
}
.section-title.offline {
background: #FF6C17;
}
.more-options {
cursor: pointer;
margin: 0;
padding: 0;
}
.more-options {
cursor: pointer;
margin: 0;
padding: 0;
}
.more-icon {
font-size: 18px;
font-weight: bold;
color: #333;
display: flex;
line-height: 1;
width: 32px;
height: 32px;
justify-content: center;
align-items: center;
}
.more-icon {
font-size: 18px;
font-weight: bold;
color: #333;
display: flex;
line-height: 1;
width: 32px;
height: 32px;
justify-content: center;
align-items: center;
}
.course-image-container img {
width: 13px;
height: 13px;
}
.course-image-container img {
width: 13px;
height: 13px;
}
.course-info {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
flex: 1;
box-sizing: border-box;
padding: 0;
}
.course-info {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
flex: 1;
box-sizing: border-box;
padding: 0;
}
.course-info img {
width: 80%;
height: auto;
aspect-ratio: 150 / 105;
/* 居中 */
display: block;
margin: 8px auto 10px;
position: relative;
top: -5px;
object-fit: cover;
object-position: center;
}
.course-info img {
width: 80%;
height: auto;
aspect-ratio: 150 / 105;
/* 居中 */
display: block;
margin: 8px auto 10px;
position: relative;
top: -5px;
object-fit: cover;
object-position: center;
}
.course-name {
width: 80%;
font-family: AppleSystemUIFont;
font-size: 14px;
color: #333333;
line-height: 1.5;
text-align: center;
font-style: normal;
text-transform: none;
white-space: nowrap;
/* 强制文本在一行显示 */
overflow: hidden;
/* 隐藏超出容器的文本 */
text-overflow: ellipsis;
/* 溢出部分用省略号表示 */
margin-top: 0px;
/* 文字往上移动:添加负上边距 */
margin-bottom: 10px;
/* 减小底部边距从15px到10px */
}
.course-name {
width: 80%;
font-family: AppleSystemUIFont;
font-size: 14px;
color: #333333;
line-height: 1.5;
text-align: center;
font-style: normal;
text-transform: none;
white-space: nowrap;
/* 强制文本在一行显示 */
overflow: hidden;
/* 隐藏超出容器的文本 */
text-overflow: ellipsis;
/* 溢出部分用省略号表示 */
margin-top: 0px;
/* 文字往上移动:添加负上边距 */
margin-bottom: 10px;
/* 减小底部边距从15px到10px */
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: center;
position: relative;
z-index: 100;
margin: 0;
margin-top: auto;
flex-shrink: 0;
/* 分页样式 */
.pagination {
display: flex;
justify-content: center;
position: relative;
z-index: 100;
margin: 0;
margin-top: auto;
flex-shrink: 0;
}
.pagination-content {
@ -749,7 +747,7 @@ onMounted(async () => {
border-color: #1890ff;
}
:deep(.n-tabs.n-tabs--top .n-tab-pane){
:deep(.n-tabs.n-tabs--top .n-tab-pane) {
padding: 0;
}

View File

@ -3,16 +3,16 @@
<div class="form-container">
<!-- 表单内容 -->
<div class="form-content">
<div class="header-left">
<n-button quaternary circle size="large" @click="goBack">
<template #icon>
<n-icon>
<ArrowBackOutline />
</n-icon>
</template>
</n-button>
<h2 class="page-title">{{ pageTitle }}</h2>
</div>
<div class="header-left">
<n-button quaternary circle size="large" @click="goBack">
<template #icon>
<n-icon>
<ArrowBackOutline />
</n-icon>
</template>
</n-button>
<h2 class="page-title">{{ pageTitle }}</h2>
</div>
<!-- 上半部分两列布局 -->
<div class="form-row">
@ -24,7 +24,7 @@
<n-input v-model:value="formData.courseName" placeholder="请输入课程名称" class="form-input" />
</div>
<!-- 课程开始时间 -->
<!-- 课程开始时间 -->
<div class="form-item">
<label class="form-label required">课程开始时间:</label>
<n-date-picker v-model:value="formData.startTime" type="datetime" placeholder="选择时间" class="form-input" />
@ -58,7 +58,7 @@
<div class="form-item" v-show="formData.studentType === 'partial'">
<label class="form-label required">选择班级:</label>
<n-select v-model:value="formData.selectedClasses" multiple :options="classOptions"
placeholder="选择班级(可多选)" class="form-input" />
placeholder="选择班级(可多选)" class="form-input" :disabled="isEditMode && !isCopyMode" />
</div>
</div>
@ -200,9 +200,19 @@ const courseStore = useCourseStore()
//
const isEditMode = computed(() => !!route.params.id)
const courseId = computed(() => route.params.id as string)
//
const isCopyMode = computed(() => route.query.type === 'copy')
//
const pageTitle = computed(() => isEditMode.value ? '编辑课程' : '创建课程')
const pageTitle = computed(() => {
if (isCopyMode.value) {
return '复制课程'
} else if (isEditMode.value && !isCopyMode.value) {
return '编辑课程'
} else {
return '新建课程'
}
})
// shallowRef
const editorRef = shallowRef()
@ -214,6 +224,9 @@ const previewUrl = ref('')
//
const pendingCourseDescription = ref('')
//
const pendingSelectedClasses = ref<string[]>([])
const toolbarConfig = {}
const editorConfig = { placeholder: '请输入内容...' }
const mode = 'default'
@ -326,7 +339,19 @@ const loadCourseData = async () => {
formData.startTime = storeCourseData.start_time || storeCourseData.startTime || null;
formData.endTime = storeCourseData.end_time || storeCourseData.endTime || null;
formData.studentType = (storeCourseData.type === 1 || storeCourseData.studentType === 'partial') ? 'partial' : 'all';
formData.selectedClasses = storeCourseData.target ? storeCourseData.target.split(',') : (storeCourseData.selectedClasses || []);
console.log('huixian:',storeCourseData);
//
const classData = storeCourseData.classId ? storeCourseData.classId.split(',') : (storeCourseData.selectedClasses || []);
if (classOptions.value.length > 0) {
//
formData.selectedClasses = classData;
console.log('直接设置班级数据:', classData);
} else {
//
pendingSelectedClasses.value = classData;
console.log('保存班级数据到待回显变量:', classData);
}
const tempCourseDescription = storeCourseData.description || storeCourseData.courseDescription || '';
if (tempCourseDescription) {
@ -389,7 +414,18 @@ const loadCourseData = async () => {
formData.startTime = courseData.start_time || courseData.startTime || null;
formData.endTime = courseData.end_time || courseData.endTime || null;
formData.studentType = (courseData.type === 1 || courseData.studentType === 'partial') ? 'partial' : 'all';
formData.selectedClasses = courseData.target ? courseData.target.split(',') : (courseData.selectedClasses || []);
//
const classData = courseData.target ? courseData.target.split(',') : (courseData.selectedClasses || []);
if (classOptions.value.length > 0) {
//
formData.selectedClasses = classData;
console.log('直接设置班级数据:', classData);
} else {
//
pendingSelectedClasses.value = classData;
console.log('保存班级数据到待回显变量:', classData);
}
//
const tempCourseDescription = courseData.description || courseData.courseDescription || '';
@ -676,7 +712,7 @@ const handleSubmit = async () => {
question: null
}
if (isEditMode.value) {
if (isEditMode.value && !isCopyMode.value) {
//
const editData = {
id: courseId.value,
@ -702,7 +738,7 @@ const handleSubmit = async () => {
if (response.data.code === 200) {
// ID
const newCourseId = response.data.data?.id || response.data.id;
const newCourseId = response.data.result;
if (newCourseId && formData.instructors.length > 0) {
await handleTeacherChanges(newCourseId.toString(), true);
}
@ -730,8 +766,9 @@ const goBack = () => {
}
//
const getCourseList = () => {
CourseApi.getCategories().then(response => {
const getCourseList = async () => {
try {
const response = await CourseApi.getCategories()
categoryOptions.value = response.data.map((category: any) => ({
label: category.name,
value: category.id
@ -740,50 +777,71 @@ const getCourseList = () => {
if (formData.courseCategory.length === 0 && categoryOptions.value.length > 0) {
formData.courseCategory = [];
}
}).catch(error => {
console.log('课程分类选项加载完成:', categoryOptions.value);
} catch (error) {
console.error('获取课程列表失败:', error)
})
}
}
//
const getTeacherList = () => {
TeachCourseApi.getTeacherList().then(response => {
const getTeacherList = async () => {
try {
const response = await TeachCourseApi.getTeacherList()
instructorOptions.value = response.data.result.map((teacher: any) => ({
label: teacher.realname,
value: teacher.id
}))
}).catch(error => {
console.log('老师选项加载完成:', instructorOptions.value);
} catch (error) {
console.error('获取老师列表失败:', error)
})
}
}
//
const getClassList = () => {
ClassApi.queryClassList({course_id:null}).then(response => {
const getClassList = async () => {
try {
const response = await ClassApi.queryClassList({ course_id: null })
console.log('班级列表:', response.data.result);
//
classOptions.value = []
response.data.result.forEach((cls: any) => {
classOptions.value.push({
label: cls.name,
value: cls.id
})
})
}).catch(error => {
console.log('班级选项加载完成:', classOptions.value);
//
if (pendingSelectedClasses.value.length > 0) {
console.log('开始回显班级数据:', pendingSelectedClasses.value);
formData.selectedClasses = [...pendingSelectedClasses.value];
pendingSelectedClasses.value = []; //
console.log('班级回显完成:', formData.selectedClasses);
}
} catch (error) {
console.error('获取班级列表失败:', error)
})
}
}
onMounted(() => {
// courseIdstore
onMounted(async () => {
//
await Promise.all([
getCourseList(),
getTeacherList(),
getClassList() //
]);
//
if (isEditMode.value && courseId.value) {
loadCourseData()
await loadCourseData()
} else if (route.query.courseData || courseStore.courseEditData) {
// 使IDstore
loadCourseData()
await loadCourseData()
}
getCourseList()
getTeacherList()
getClassList()
})
</script>
@ -804,28 +862,28 @@ onMounted(() => {
.header-left {
display: flex;
align-items: center;
gap: 16px;
display: flex;
align-items: center;
gap: 16px;
}
.page-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.title {
margin: 0;
font-size: 20px;
font-weight: 500;
color: #333;
margin: 0;
font-size: 20px;
font-weight: 500;
color: #333;
}
.header-actions {
display: flex;
gap: 12px;
display: flex;
gap: 12px;
}
.form-row {

View File

@ -151,6 +151,14 @@ const activeSubNavItem = ref(''); // 子菜单激活状态
const examMenuExpanded = ref(false); //
const studentMenuExpanded = ref(false); //
const showTopImage = ref(true); // /
//
const hideTopImageRoutes = [
'teacher/chapter-editor-teacher',
'teacher/course-editor'
//
];
const route = useRoute();
const router = useRouter();
const isAiHovered = ref(false);
@ -654,13 +662,29 @@ onMounted(() => {
} else {
document.documentElement.style.setProperty('--top-height', '0px');
}
updateTopImageVisibility();
});
// 使watch
watch(route, () => {
updateActiveNavItem();
updateTopImageVisibility();
});
//
const updateTopImageVisibility = () => {
const currentPath = route.path;
console.log('检查路径是否需要隐藏顶部图片:', currentPath);
//
const shouldHideTopImage = hideTopImageRoutes.some(routePath =>
currentPath.includes(routePath)
);
showTopImage.value = !shouldHideTopImage;
console.log('顶部图片显示状态:', showTopImage.value);
};
//
const updateActiveNavItem = () => {
const path = route.path;
@ -704,7 +728,7 @@ const updateActiveNavItem = () => {
<style scoped>
.admin-dashboard {
min-height: 100vh;
/* min-height: 100vh; */
}
.top-image-container {