Compare commits

...

4 Commits

8 changed files with 1105 additions and 221 deletions

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 490 B

After

Width:  |  Height:  |  Size: 490 B

View File

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 712 B

View File

@ -127,6 +127,23 @@ export interface QueryCourseSectionParams {
*/ */
export class TeachCourseApi { export class TeachCourseApi {
/**
*
*/
static async getTeacherMenuList(): Promise<ApiResponse<any>> {
try{
const response = await ApiRequest.get<any>('/aiol/aiolMenu/getTeacherMenus')
console.log('📝 获取教师菜单响应:', response);
return response
} catch(error) {
console.error('❌ 获取教师菜单失败:', error)
throw error
}
}
/** /**
* *
*/ */

View File

@ -0,0 +1,929 @@
<template>
<div>
<!-- 课程筛选标签 -->
<div class="text-wrapper_1 flex-row">
<span
class="text_12"
:class="{ active: activeCourseTab === 'all' }"
@click="handleCourseTabChange('all')"
>全部课程</span
>
<span
class="text_13"
:class="{ active: activeCourseTab === 'learning' }"
@click="handleCourseTabChange('learning')"
>学习中</span
>
<span
class="text_14"
:class="{ active: activeCourseTab === 'completed' }"
@click="handleCourseTabChange('completed')"
>已完结</span
>
</div>
<!-- 分割线 -->
<div class="course-divider"></div>
<!-- 课程列表 -->
<div class="course-list">
<!-- 加载状态 -->
<div v-if="loading" class="loading-wrapper">
<span>正在加载课程数据...</span>
</div>
<!-- 课程卡片 -->
<div
v-else-if="filteredCourses.length > 0"
v-for="course in filteredCourses"
:key="course.id"
class="box_2 flex-row justify-between"
>
<div class="block_4 flex-col">
<div class="box_3 flex-row justify-between">
<div class="status-image-container">
<img
class="status-image"
referrerpolicy="no-referrer"
:src="
getCourseStatusClass(course) === 'learning'
? '/images/icon/learning.png'
: '/images/icon/finish.png'
"
:alt="getStatusText(course)"
/>
</div>
<img
class="thumbnail_4"
referrerpolicy="no-referrer"
:src="course.thumbnail"
:alt="course.name"
/>
<span :class="['status-text', getCourseStatusClass(course)]">{{
getStatusText(course)
}}</span>
</div>
</div>
<div class="block_5 flex-col">
<div class="group_6 flex-row">
<span class="text_16">{{ course.title }}</span>
<n-icon size="1.04vw" class="thumbnail_5">
<PersonOutline />
</n-icon>
<span class="text_17">{{ course.enrollmentCount || 0 }}</span>
</div>
<span class="text_18"
>讲师{{
getInstructorNames(course.teacherList) || "暂无讲师"
}}</span
>
<span class="text_19">{{
formatDescription(course.description)
}}</span>
<div class="group_7 flex-row">
<img
class="thumbnail_6"
referrerpolicy="no-referrer"
src="/images/profile/11.png"
/>
<span class="text_20"
>{{ course.chapterCount || 0 }}{{
course.sectionCount || 0
}}</span
>
<img
class="thumbnail_7"
referrerpolicy="no-referrer"
src="/images/profile/22.png"
/>
<span class="text_21"
>{{
course.startDate
? new Date(course.startDate).toLocaleDateString()
: "时间待定"
}}
-
{{
course.endDate
? new Date(course.endDate).toLocaleDateString()
: "时间待定"
}}</span
>
<img
class="thumbnail_8"
referrerpolicy="no-referrer"
src="/images/profile/33.png"
/>
<!-- <span class="text_22">报名{{ course.enrollCount }}/{{ course.maxEnroll }}</span> -->
<div
class="text-wrapper_2 flex-col"
@click="goToCourseDetail(course)"
>
<span class="text_23">{{
getCourseStatusClass(course) === "learning"
? "去学习"
: getCourseStatusClass(course) === "completed"
? "去复习"
: "去报名"
}}</span>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-else class="empty-state">
<span>暂无课程数据</span>
</div>
</div>
<!-- 分页器 -->
<div class="pagination-wrapper" v-if="totalPages > 1">
<div class="pagination">
<span
class="pagination-item nav-button"
:class="{ disabled: currentPage === 1 }"
@click="goToPage('first')"
>
首页
</span>
<span
class="pagination-item nav-button"
:class="{ disabled: currentPage === 1 }"
@click="goToPage('prev')"
>
上一页
</span>
<span
v-for="page in totalPages"
:key="page"
class="pagination-item page-number"
:class="{ active: page === currentPage }"
@click="goToPage(page)"
>
{{ page }}
</span>
<span
class="pagination-item nav-button"
:class="{ disabled: currentPage === totalPages }"
@click="goToPage('next')"
>
下一页
</span>
<span
class="pagination-item nav-button"
:class="{ disabled: currentPage === totalPages }"
@click="goToPage('last')"
>
尾页
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
// import { useI18n } from "vue-i18n";
import { useMessage } from "naive-ui";
import { useRouter } from "vue-router";
import { PersonOutline } from "@vicons/ionicons5";
import { CourseApi } from "@/api/modules/course";
import { TeachCourseApi } from "@/api/modules/teachCourse";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
const router = useRouter();
// const { locale } = useI18n();
const message = useMessage();
//
interface Section {
id: string;
name: string;
level: number; // 12
parentId?: string;
courseId: string;
sortOrder?: number;
}
//
interface Teacher {
id: string;
name: string;
avatar: string;
title: string;
tag: string;
sortOrder: number;
}
//
interface Course {
id: string;
name: string;
cover: string;
video: string;
school: string;
description: string;
type: number;
target: string;
difficulty: number;
subject: string;
outline: string;
prerequisite: string;
reference: string;
arrangement: string;
startTime: string;
endTime: string;
enrollCount: number;
maxEnroll: number;
status: number;
question: string;
izAi: number;
pauseExit: number;
allowSpeed: number;
showSubtitle: number;
publishStatus: number;
semester: string | null;
allowDownload: number;
createBy: string;
createTime: string;
updateBy: string;
updateTime: string;
teacherList: Teacher[];
isEnrolled: boolean;
//
chapterCount?: number; //
sectionCount?: number; //
sections?: Section[]; //
}
//
const activeCourseTab = ref("all");
//
const currentPage = ref(1);
const pageSize = ref(10); // 5
//
const courses = ref<any[]>([]);
const loading = ref(false);
const total = ref(0);
// banner
// const bannerImage = computed(() => {
// return locale.value === "zh"
// ? "/banners/banner8.png"
// : "/banners/banner1-en.png";
// });
// const bannerAlt = computed(() => {
// return t('home.banner.alt')
// })
//
const getCourseSections = async (courseId: string) => {
try {
const response = await TeachCourseApi.getCourseSections(courseId);
return response.data.result || [];
} catch (error) {
console.error(`获取课程${courseId}章节信息失败:`, error);
return [];
}
};
//
const calculateChapterAndSectionCount = (sections: any) => {
const chapterCount = sections.filter(
(section: any) => section.level === 1
).length;
const sectionCount = sections.filter(
(section: any) => section.level === 2
).length;
return { chapterCount, sectionCount };
};
//
const fetchCourses = async () => {
try {
loading.value = true;
//
const response = await CourseApi.getCourses({}, true);
console.log("Fetched courses:", response.data);
if (response.data && Array.isArray(response.data)) {
//
const coursesWithSections = await Promise.all(
response.data.map(async (course: any) => {
//
const sections = await getCourseSections(course.id);
const { chapterCount, sectionCount } =
calculateChapterAndSectionCount(sections);
return {
...course,
sections,
chapterCount,
sectionCount,
};
})
);
courses.value = coursesWithSections;
total.value = coursesWithSections.length;
console.log("Courses with sections:", coursesWithSections);
} else {
courses.value = [];
total.value = 0;
}
} catch (error) {
message.error("获取课程数据失败,请稍后重试。");
console.error("Error fetching courses:", error);
courses.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
//
const allFilteredCourses = computed(() => {
if (activeCourseTab.value === "learning") {
//
return courses.value.filter(
(course) => getCourseStatusClass(course) === "learning"
);
} else if (activeCourseTab.value === "completed") {
//
return courses.value.filter(
(course) => getCourseStatusClass(course) === "completed"
);
}
return courses.value;
});
//
const totalPages = computed(() => {
return Math.ceil(allFilteredCourses.value.length / pageSize.value);
});
//
const filteredCourses = computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
const end = start + pageSize.value;
return allFilteredCourses.value.slice(start, end);
});
//
const getStatusText = (course: Course) => {
const now = new Date();
const endTime = new Date(course.endTime);
if (course.isEnrolled) {
if (endTime < now) {
return "已完结";
} else {
return "学习中";
}
}
return "未报名";
};
//
const getCourseStatusClass = (course: Course) => {
const now = new Date();
const endTime = new Date(course.endTime);
if (course.isEnrolled) {
if (endTime < now) {
return "completed";
} else {
return "learning";
}
}
return "not-enrolled";
};
//
const getInstructorNames = (teacherList: Teacher[]) => {
return teacherList.map((teacher) => teacher.name).join(" ");
};
// HTML
const formatDescription = (description: string) => {
return description.replace(/<[^>]*>/g, "").substring(0, 200) + "...";
};
//
const goToCourseDetail = async (course: Course) => {
try {
//
if (!userStore.isLoggedIn) {
console.log("用户未登录跳转到AI伴学页面");
router.push(`/ai-companion?courseId=${course.id}`);
return;
}
console.log("检查课程报名状态课程ID:", course.id);
//
const response = await CourseApi.checkEnrollmentStatus(String(course.id));
if ((response.code === 0 || response.code === 200) && response.data) {
const isEnrolled = response.data.result;
if (isEnrolled) {
//
console.log("用户已报名,跳转到已兑换页面");
router.push(`/course/${course.id}/exchanged`);
} else {
// AI
console.log("用户未报名跳转到AI伴学页面");
router.push(`/ai-companion?courseId=${course.id}`);
}
} else {
// AI
console.warn("查询报名状态失败跳转到AI伴学页面");
router.push(`/ai-companion?courseId=${course.id}`);
}
} catch (error) {
console.error("检查报名状态时发生错误:", error);
// AI
router.push(`/ai-companion?courseId=${course.id}`);
}
};
//
const handleCourseTabChange = (tab: string) => {
activeCourseTab.value = tab;
currentPage.value = 1; // tab
};
// API
const goToPage = (page: number | string) => {
let newPage = currentPage.value;
if (typeof page === "number") {
if (page >= 1 && page <= totalPages.value) {
newPage = page;
}
} else {
switch (page) {
case "first":
if (currentPage.value > 1) {
newPage = 1;
}
break;
case "prev":
if (currentPage.value > 1) {
newPage = currentPage.value - 1;
}
break;
case "next":
if (currentPage.value < totalPages.value) {
newPage = currentPage.value + 1;
}
break;
case "last":
if (currentPage.value < totalPages.value) {
newPage = totalPages.value;
}
break;
}
}
if (newPage !== currentPage.value) {
currentPage.value = newPage;
// fetchCourses()
}
};
onMounted(async () => {
await fetchCourses();
});
</script>
<style scoped>
.text-wrapper_1 {
display: flex;
gap: 2.08vw;
padding-bottom: 1.04vh;
}
.text_12,
.text_13,
.text_14,
.text_15 {
font-size: 0.94vw;
color: #000;
font-family: "Microsoft YaHei", Arial, sans-serif;
font-weight: normal;
cursor: pointer;
padding: 0.42vh 0;
transition: color 0.3s ease;
border-radius: 0.31vw;
}
.text_12.active,
.text_13.active,
.text_14.active,
.text_15.active {
color: rgba(2, 134, 206, 1);
}
.text_12:hover,
.text_13:hover,
.text_14:hover,
.text_15:hover {
color: rgba(2, 134, 206, 1);
}
.course-divider {
width: 100%;
height: 1.5px;
background: #e6e6e6;
margin-bottom: 1.67vh;
}
.course-list {
display: flex;
flex-direction: column;
gap: 1.25vh;
}
.box_2 {
width: 100%;
min-height: 10.42vh;
background: rgba(255, 255, 255, 1);
border: none;
border-radius: 0.6vw;
padding: 1.04vh 1.04vw;
transition: all 0.3s ease;
}
.box_2:hover {
box-shadow: 0 0.21vh 1.04vh rgba(0, 0, 0, 0.1);
transform: translateY(-0.1vh);
}
.block_4 {
margin-right: 1.04vw;
}
.box_3 {
width: 202px;
height: 156px;
position: relative;
border-radius: 5px;
overflow: hidden;
background: rgba(243, 243, 243, 1);
box-shadow: 0 0.1vh 0.31vh rgba(0, 0, 0, 0.1);
}
.thumbnail_4 {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 5px;
}
.status-image-container {
position: absolute;
top: 0;
left: 0;
z-index: 10;
}
.status-image {
width: 66px;
height: 22px;
}
.status-text {
position: absolute;
top: 26vh;
right: 0.63vw;
color: #fff;
font-size: 0.6vw;
font-family: "Microsoft YaHei", Arial, sans-serif;
font-weight: normal;
z-index: 12;
padding: 0.21vh 0.52vw;
border-radius: 0.21vw;
background: rgba(0, 0, 0, 0.5);
}
.status-text.learning {
background: rgba(2, 134, 206, 0.8);
}
.status-text.completed {
background: rgba(76, 175, 80, 0.8);
}
.status-text.not-enrolled {
background: rgba(158, 158, 158, 0.8);
}
.block_5 {
flex: 1;
display: flex;
flex-direction: column;
}
.group_6 {
align-items: center;
margin-bottom: 0.42vh;
}
.text_16 {
font-size: 1.04vw;
font-weight: bold;
color: #333;
font-family: "Microsoft YaHei", Arial, sans-serif;
flex: 1;
margin-right: 0.52vw;
}
.thumbnail_5 {
width: 1.04vw;
height: 1.04vw;
margin-right: 0.26vw;
}
.text_17 {
font-size: 0.78vw;
color: #666;
font-family: "Microsoft YaHei", Arial, sans-serif;
}
.text_18 {
font-size: 0.78vw;
color: #999;
font-family: "Microsoft YaHei", Arial, sans-serif;
margin-bottom: 0.42vh;
}
.text_19 {
font-size: 0.73vw;
color: #666;
font-family: "Microsoft YaHei", Arial, sans-serif;
margin-bottom: 0.63vh;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.group_7 {
align-items: center;
gap: 0.52vw;
}
.thumbnail_6,
.thumbnail_7,
.thumbnail_8 {
width: 0.83vw;
height: 0.83vw;
}
.text_20,
.text_21,
.text_22 {
font-size: 0.68vw;
color: #999;
font-family: "Microsoft YaHei", Arial, sans-serif;
}
.text-wrapper_2 {
margin-left: auto;
background: rgba(2, 134, 206, 1);
border-radius: 0.31vw;
padding: 0.31vh 0.78vw;
cursor: pointer;
transition: all 0.3s ease;
}
.text-wrapper_2:hover {
background: rgba(1, 120, 185, 1);
}
.text_23 {
font-size: 0.73vw;
color: #fff;
font-family: "Microsoft YaHei", Arial, sans-serif;
font-weight: normal;
}
.pagination-wrapper {
width: 100%;
display: flex;
justify-content: center;
margin-top: 4.17vh;
padding: 1.04vh 0;
}
.pagination {
display: flex;
align-items: center;
gap: 0.52vw;
}
.pagination-item {
padding: 0.52vh 0.78vw;
font-size: 0.73vw;
color: #333;
cursor: pointer;
border-radius: 0.26vw;
transition: all 0.3s ease;
border: 1px solid #ddd;
background: #fff;
font-family: "Microsoft YaHei", Arial, sans-serif;
}
.pagination-item:hover {
color: rgba(2, 134, 206, 1);
border-color: rgba(2, 134, 206, 1);
}
.pagination-item.active {
background: rgba(2, 134, 206, 1);
color: #fff;
border-color: rgba(2, 134, 206, 1);
}
.pagination-item.disabled {
color: #ccc;
cursor: not-allowed;
border-color: #f0f0f0;
}
.pagination-item.disabled:hover {
color: #ccc;
border-color: #f0f0f0;
}
.nav-button {
padding: 0.52vh 1.04vw;
}
.page-number {
min-width: 2.08vw;
text-align: center;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.box_2 {
flex-direction: column;
}
.block_4 {
width: 100%;
margin-right: 0;
margin-bottom: 0.83vh;
}
.box_3 {
width: 100%;
height: 18vh;
}
.text_16 {
font-size: 1.25vw;
}
.text_17,
.text_18,
.text_19 {
font-size: 1vw;
}
.text_20,
.text_21,
.text_22,
.text_23 {
font-size: 0.9vw;
}
.thumbnail_5,
.thumbnail_6,
.thumbnail_7,
.thumbnail_8 {
width: 1.25vw;
height: 1.25vw;
}
}
@media (max-width: 768px) {
.text_12,
.text_13,
.text_14 {
font-size: 2.16px;
padding: 10px 0;
}
.course-divider {
width: 100%;
}
.pagination-wrapper {
margin-top: 6vh;
}
.pagination {
gap: 2vw;
}
.pagination-item {
font-size: 3vw;
padding: 1vh 2vw;
}
.nav-button {
padding: 1vh 3vw;
}
.page-number {
min-width: 8vw;
}
.box_2 {
padding: 0.83vh 0.83vw;
}
.block_4 {
margin-bottom: 0.63vh;
}
.box_3 {
height: 20vh;
}
}
@media (max-width: 480px) {
.text_12,
.text_13,
.text_14 {
font-size: 0.83vw;
padding: 0.31vh 0.63vw;
}
.box_2 {
padding: 0.63vh 0.63vw;
}
.text_16 {
font-size: 0.83vw;
}
.text_19 {
font-size: 0.73vw;
}
}
/* 通用样式类 */
.flex-row {
display: flex;
flex-direction: row;
}
.flex-col {
display: flex;
flex-direction: column;
}
.justify-between {
justify-content: space-between;
}
/* 加载状态样式 */
.loading-wrapper {
display: flex;
justify-content: center;
align-items: center;
padding: 2vh 0;
font-size: 0.83vw;
color: #666;
font-family: "Microsoft YaHei", Arial, sans-serif;
}
/* 空状态样式 */
.empty-state {
display: flex;
justify-content: center;
align-items: center;
padding: 4vh 0;
font-size: 0.94vw;
color: #999;
font-family: "Microsoft YaHei", Arial, sans-serif;
}
</style>

View File

@ -93,8 +93,7 @@
<!-- 右侧内容区域 --> <!-- 右侧内容区域 -->
<div class="group_5 flex-col"> <div class="group_5 flex-col">
<!-- 课程内容 --> <!-- 课程内容 -->
<div v-if="isCoursesTab"> <!-- <div>
<!-- 课程筛选标签 -->
<div class="text-wrapper_1 flex-row"> <div class="text-wrapper_1 flex-row">
<span class="text_12" :class="{ active: activeCourseTab === 'all' }" <span class="text_12" :class="{ active: activeCourseTab === 'all' }"
@click="handleCourseTabChange('all')">全部课程</span> @click="handleCourseTabChange('all')">全部课程</span>
@ -104,12 +103,9 @@
@click="handleCourseTabChange('completed')">已完结</span> @click="handleCourseTabChange('completed')">已完结</span>
</div> </div>
<!-- 分割线 -->
<div class="course-divider"></div> <div class="course-divider"></div>
<!-- 课程列表 -->
<div class="course-list"> <div class="course-list">
<!-- 学习中的课程 -->
<div v-for="course in filteredCourses" :key="course.id" class="box_2 flex-row justify-between"> <div v-for="course in filteredCourses" :key="course.id" class="box_2 flex-row justify-between">
<div class="block_4 flex-col"> <div class="block_4 flex-col">
<div class="box_3 flex-row justify-between"> <div class="box_3 flex-row justify-between">
@ -149,7 +145,6 @@
</div> </div>
</div> </div>
<!-- 分页器 -->
<div class="pagination-wrapper" v-if="totalPages > 1"> <div class="pagination-wrapper" v-if="totalPages > 1">
<div class="pagination"> <div class="pagination">
<span class="pagination-item nav-button" :class="{ disabled: currentPage === 1 }" <span class="pagination-item nav-button" :class="{ disabled: currentPage === 1 }"
@ -176,7 +171,9 @@
</span> </span>
</div> </div>
</div> </div>
</div> </div> -->
<CourseContent v-if="isCoursesTab"></CourseContent>
<!-- 作业内容 --> <!-- 作业内容 -->
@ -1191,6 +1188,7 @@ import QuillEditor from '@/components/common/QuillEditor.vue'
import InstantMessage from '@/components/InstantMessage.vue' import InstantMessage from '@/components/InstantMessage.vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { MessageApi, type BackendMessageItem } from '@/api' import { MessageApi, type BackendMessageItem } from '@/api'
import CourseContent from '@/components/profile/CourseContent.vue'
const { t, locale } = useI18n() const { t, locale } = useI18n()
const router = useRouter() const router = useRouter()

View File

@ -21,79 +21,44 @@
<!-- 导航栏 --> <!-- 导航栏 -->
<div class="nav-container"> <div class="nav-container">
<router-link to="/teacher/course-management" class="nav-item" :class="{ active: activeNavItem === 0 }" <!-- 动态菜单渲染 -->
@click="setActiveNavItem(0)"> <template v-for="menu in menuList" :key="menu.id">
<img :src="activeNavItem === 0 ? '/images/teacher/课程管理(选中).png' : '/images/teacher/课程管理.png'" alt=""> <!-- 有子菜单的父级菜单 -->
<span>课程管理</span> <div v-if="menu.children && menu.children.length > 0"
</router-link> class="nav-item"
:class="{ active: activeNavItem === menu.id }"
<!-- 考试管理 - 可展开菜单 --> @click="toggleMenu(menu)">
<div class="nav-item" :class="{ active: activeNavItem === 4 }" @click="toggleExamMenu"> <img :src="getMenuIcon(menu)" :alt="menu.name" v-if="menu.icon">
<img <span>{{ menu.name }}</span>
:src="activeNavItem === 4 ? '/images/teacher/examination-active.png' : '/images/teacher/examination.png'" <n-icon class="expand-icon" :class="{ expanded: isMenuExpanded(menu.id) }">
alt="">
<span>考试管理</span>
<n-icon class="expand-icon" :class="{ expanded: examMenuExpanded }">
<ChevronDownOutline /> <ChevronDownOutline />
</n-icon> </n-icon>
</div> </div>
<!-- 考试管理子菜单 --> <!-- 子菜单容器 -->
<div class="submenu-container" :class="{ expanded: examMenuExpanded }"> <div v-if="menu.children && menu.children.length > 0"
<router-link to="/teacher/exam-management/question-bank" class="submenu-item" class="submenu-container"
:class="{ active: activeSubNavItem === 'question-bank' }" @click="setActiveSubNavItem('question-bank')"> :class="{ expanded: isMenuExpanded(menu.id) }">
<span>题库管理</span> <router-link v-for="subMenu in menu.children"
</router-link> :key="subMenu.id"
<router-link to="/teacher/exam-management/exam-library" class="submenu-item" :to="subMenu.path"
:class="{ active: activeSubNavItem === 'exam-library' }" @click="setActiveSubNavItem('exam-library')"> class="submenu-item"
<span>试卷管理</span> :class="{ active: activeSubNavItem === subMenu.id }"
</router-link> @click="handleSubMenuClick(subMenu, menu)">
<router-link to="/teacher/exam-management/marking-center/list" class="submenu-item" <span>{{ subMenu.name }}</span>
:class="{ active: activeSubNavItem === 'marking-center' }" @click="setActiveSubNavItem('marking-center')">
<span>阅卷中心</span>
</router-link> </router-link>
</div> </div>
<!-- 没有子菜单的直接链接 -->
<!-- 学员中心 - 可展开菜单 --> <router-link v-else
<div class="nav-item" :class="{ active: activeNavItem === 1 }" :to="menu.path"
@click="toggleStudentMenu('/teacher/student-management/student-library')"> class="nav-item"
<img :src="activeNavItem === 1 ? '/images/teacher/学院管理(选中).png' : '/images/teacher/学员管理.png'" alt=""> :class="{ active: activeNavItem === menu.id }"
<span>学员中心</span> @click="toggleMenu(menu)">
<n-icon class="expand-icon" :class="{ expanded: studentMenuExpanded }"> <img :src="getMenuIcon(menu)" :alt="menu.name" v-if="menu.icon">
<ChevronDownOutline /> <span>{{ menu.name }}</span>
</n-icon>
</div>
<!-- 学员中心子菜单 -->
<div class="submenu-container" :class="{ expanded: studentMenuExpanded }">
<router-link to="/teacher/student-management/student-library" class="submenu-item"
:class="{ active: activeSubNavItem === 'student-library' }"
@click="setActiveSubNavItem('student-library')">
<span>学员库</span>
</router-link>
<router-link to="/teacher/student-management/class-management" class="submenu-item"
:class="{ active: activeSubNavItem === 'class-management' }"
@click="setActiveSubNavItem('class-management')">
<span>班级管理</span>
</router-link>
</div>
<!-- <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 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'"
alt="">
<span>消息中心</span>
</router-link>
<router-link to="/teacher/personal-center" class="nav-item" :class="{ active: activeNavItem === 3 }"
@click="setActiveNavItem(3)">
<img :src="activeNavItem === 3 ? '/images/teacher/个人中心(选中).png' : '/images/teacher/个人中心.png'" alt="">
<span>个人中心</span>
</router-link> </router-link>
</template>
</div> </div>
<!-- ai助教 - 已注释 --> <!-- ai助教 - 已注释 -->
@ -188,6 +153,7 @@ import { useRoute, useRouter } from 'vue-router'
import { ChevronDownOutline } from '@vicons/ionicons5' import { ChevronDownOutline } from '@vicons/ionicons5'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { ChevronBackSharp } from '@vicons/ionicons5' import { ChevronBackSharp } from '@vicons/ionicons5'
import { TeachCourseApi } from '@/api/modules/teachCourse'
const userStore = useUserStore() const userStore = useUserStore()
@ -197,11 +163,9 @@ const height = window.innerHeight;
console.log(`当前屏幕宽度: ${width}px, 高度: ${height}px`); console.log(`当前屏幕宽度: ${width}px, 高度: ${height}px`);
// //
const activeNavItem = ref(0); // 0: , 1: , 2: , 3: , 4: , 5: , 6: const activeNavItem = ref(''); // ID
const activeSubNavItem = ref(''); // const activeSubNavItem = ref(''); //
const examMenuExpanded = ref(false); // const expandedMenus = ref(new Set()); // ID
const studentMenuExpanded = ref(false); //
const orchestrationMenuExpanded = ref(false); //
const showTopImage = ref(false); // / const showTopImage = ref(false); // /
// //
@ -227,96 +191,77 @@ const breadcrumbDisplay = computed(() => {
const isCourseEditor = computed(() => route.path.includes('course-editor') || route.path.includes('chapter-editor-teacher')); const isCourseEditor = computed(() => route.path.includes('course-editor') || route.path.includes('chapter-editor-teacher'));
const setActiveNavItem = (index: number) => { //
activeNavItem.value = index; const isMenuExpanded = (menuId) => {
// return expandedMenus.value.has(menuId)
if (index !== 4) {
examMenuExpanded.value = false;
} }
//
if (index !== 1) { //
studentMenuExpanded.value = false; const toggleMenu = (menu) => {
} if (menu.children && menu.children.length > 0) {
// if (expandedMenus.value.has(menu.id)) {
if (index !== 6) { expandedMenus.value.delete(menu.id)
orchestrationMenuExpanded.value = false; } else {
} expandedMenus.value.add(menu.id)
//
if (index !== 4 && index !== 1 && index !== 6) {
activeSubNavItem.value = '';
} }
} }
// //
const toggleExamMenu = () => { activeNavItem.value = menu.id
//
if (!examMenuExpanded.value) {
studentMenuExpanded.value = false;
orchestrationMenuExpanded.value = false;
}
examMenuExpanded.value = !examMenuExpanded.value; //
activeNavItem.value = 4; if (menu.path && (!menu.children || menu.children.length === 0)) {
router.push(menu.path)
//
if (examMenuExpanded.value && !activeSubNavItem.value) {
activeSubNavItem.value = 'question-management';
} }
} }
// //
const toggleStudentMenu = (path: string) => { const handleSubMenuClick = (subMenu, parentMenu) => {
// activeSubNavItem.value = subMenu.id
if (!studentMenuExpanded.value) { activeNavItem.value = parentMenu.id
examMenuExpanded.value = false; if (subMenu.path) {
orchestrationMenuExpanded.value = false; router.push(subMenu.path)
}
studentMenuExpanded.value = !studentMenuExpanded.value;
activeNavItem.value = 1;
//
if (studentMenuExpanded.value && !activeSubNavItem.value) {
activeSubNavItem.value = 'student-library';
router.push(path);
} }
} }
// //
const toggleOrchestrationMenu = () => { const getMenuIcon = (menu) => {
// if (!menu.icon) return null
if (!orchestrationMenuExpanded.value) {
examMenuExpanded.value = false; //
studentMenuExpanded.value = false; if (activeNavItem.value === menu.id) {
return menu.icon.replace('.png', '-active.png')
} }
orchestrationMenuExpanded.value = !orchestrationMenuExpanded.value; return menu.icon
activeNavItem.value = 6;
//
if (orchestrationMenuExpanded.value && !activeSubNavItem.value) {
activeSubNavItem.value = 'app-management';
}
} }
// //
const setActiveSubNavItem = (subItem: string) => { const updateActiveNavItem = () => {
activeSubNavItem.value = subItem; const path = route.path;
console.log('当前路径:', path);
// //
if (subItem === 'question-bank' || subItem === 'exam-library' || subItem === 'marking-center') { const findActiveMenu = (menus, parentId = null) => {
// for (const menu of menus) {
activeNavItem.value = 4; if (menu.path && path.includes(menu.path.replace('/teacher/', ''))) {
examMenuExpanded.value = true; activeNavItem.value = parentId || menu.id
} else if (subItem === 'student-library' || subItem === 'class-management') { if (parentId) {
// activeSubNavItem.value = menu.id
activeNavItem.value = 1; expandedMenus.value.add(parentId)
studentMenuExpanded.value = true;
} else if (subItem === 'app-management' || subItem === 'knowledge-base' || subItem === 'process-design' || subItem === 'model-config' || subItem === 'ocr-recognition') {
//
activeNavItem.value = 6;
orchestrationMenuExpanded.value = true;
} }
return true
}
if (menu.children && menu.children.length > 0) {
if (findActiveMenu(menu.children, menu.id)) {
return true
}
}
}
return false
}
findActiveMenu(menuList.value)
} }
// //
@ -343,6 +288,50 @@ const hideSidebar = computed(() => {
return currentPath.includes('course-editor') || currentPath.includes('chapter-editor-teacher') || currentPath.includes('certificate') return currentPath.includes('course-editor') || currentPath.includes('chapter-editor-teacher') || currentPath.includes('certificate')
}) })
//
const menuList = ref([])
//
const processMenuData = (menuData) => {
const menuMap = new Map()
const rootMenus = []
//
menuData.forEach(menu => {
menuMap.set(menu.id, {
...menu,
children: []
})
})
//
menuData.forEach(menu => {
if (menu.parentId === null) {
//
rootMenus.push(menuMap.get(menu.id))
} else {
//
const parent = menuMap.get(menu.parentId)
if (parent) {
parent.children.push(menuMap.get(menu.id))
}
}
})
// sortOrder
const sortMenus = (menus) => {
menus.sort((a, b) => b.sortOrder - a.sortOrder)
menus.forEach(menu => {
if (menu.children.length > 0) {
sortMenus(menu.children)
}
})
}
sortMenus(rootMenus)
return rootMenus
}
// //
const breadcrumbPathItems = computed(() => { const breadcrumbPathItems = computed(() => {
const currentPath = route.path; const currentPath = route.path;
@ -803,7 +792,20 @@ const breadcrumbPathItems = computed(() => {
}); });
// //
onMounted(() => { onMounted(async () => {
try {
//
const menuResponse = await TeachCourseApi.getTeacherMenuList()
const rawMenuData = menuResponse.data.result
console.log('原始菜单数据:', rawMenuData);
//
menuList.value = processMenuData(rawMenuData)
console.log('处理后的菜单数据:', menuList.value);
} catch (error) {
console.error('获取菜单数据失败:', error)
}
// //
updateActiveNavItem(); updateActiveNavItem();
@ -840,68 +842,6 @@ const updateTopImageVisibility = () => {
console.log('顶部图片显示状态:', showTopImage.value); console.log('顶部图片显示状态:', showTopImage.value);
}; };
//
const updateActiveNavItem = () => {
const path = route.path;
console.log('当前路径:', path); //
if (path.includes('course-management')) {
activeNavItem.value = 0; //
} else if (path.includes('student-management')) {
activeNavItem.value = 1; //
studentMenuExpanded.value = true;
//
if (path.includes('student-library')) {
activeSubNavItem.value = 'student-library';
} else if (path.includes('class-management')) {
activeSubNavItem.value = 'class-management';
}
} else if (path.includes('my-resources')) {
activeNavItem.value = 2; //
} else if (path.includes('personal-center')) {
activeNavItem.value = 3; //
} else if (path.includes('exam-management')) {
activeNavItem.value = 4; //
examMenuExpanded.value = true;
const arr = ['question-bank', 'exam-library', 'marking-center'];
const found = arr.find(item => path.includes(item));
activeSubNavItem.value = found || '';
} else if (path.includes('message-center')) {
activeNavItem.value = 5; //
} else if (path.includes('ai-assistant')) {
// AI
console.log('检测到AI助教页面清空导航选中状态');
activeNavItem.value = -1;
activeSubNavItem.value = '';
examMenuExpanded.value = false;
studentMenuExpanded.value = false;
orchestrationMenuExpanded.value = false;
} else if (path.includes('airag')) {
//
activeNavItem.value = 6; //
orchestrationMenuExpanded.value = true;
//
if (path.includes('aiapp')) {
activeSubNavItem.value = 'app-management';
} else if (path.includes('aiknowledge')) {
activeSubNavItem.value = 'knowledge-base';
} else if (path.includes('aiflow')) {
activeSubNavItem.value = 'process-design';
} else if (path.includes('aimodel')) {
activeSubNavItem.value = 'model-config';
} else if (path.includes('ocr')) {
activeSubNavItem.value = 'ocr-recognition';
}
} else if (path.includes('/teacher/ai/app')) {
// AI
activeNavItem.value = 6; //
orchestrationMenuExpanded.value = true;
activeSubNavItem.value = 'app-management';
}
}
</script> </script>
<style scoped> <style scoped>