feat:教师端菜单栏替换为接口获取动态渲染

This commit is contained in:
yuk255 2025-09-29 21:26:56 +08:00
parent a7d8b00fe9
commit 4c43226c91
6 changed files with 171 additions and 200 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 {
/**
*
*/
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

@ -21,79 +21,44 @@
<!-- 导航栏 -->
<div class="nav-container">
<router-link to="/teacher/course-management" class="nav-item" :class="{ active: activeNavItem === 0 }"
@click="setActiveNavItem(0)">
<img :src="activeNavItem === 0 ? '/images/teacher/课程管理(选中).png' : '/images/teacher/课程管理.png'" alt="">
<span>课程管理</span>
</router-link>
<!-- 动态菜单渲染 -->
<template v-for="menu in menuList" :key="menu.id">
<!-- 有子菜单的父级菜单 -->
<div v-if="menu.children && menu.children.length > 0"
class="nav-item"
:class="{ active: activeNavItem === menu.id }"
@click="toggleMenu(menu)">
<img :src="getMenuIcon(menu)" :alt="menu.name" v-if="menu.icon">
<span>{{ menu.name }}</span>
<n-icon class="expand-icon" :class="{ expanded: isMenuExpanded(menu.id) }">
<ChevronDownOutline />
</n-icon>
</div>
<!-- 考试管理 - 可展开菜单 -->
<div class="nav-item" :class="{ active: activeNavItem === 4 }" @click="toggleExamMenu">
<img
:src="activeNavItem === 4 ? '/images/teacher/examination-active.png' : '/images/teacher/examination.png'"
alt="">
<span>考试管理</span>
<n-icon class="expand-icon" :class="{ expanded: examMenuExpanded }">
<ChevronDownOutline />
</n-icon>
</div>
<!-- 子菜单容器 -->
<div v-if="menu.children && menu.children.length > 0"
class="submenu-container"
:class="{ expanded: isMenuExpanded(menu.id) }">
<router-link v-for="subMenu in menu.children"
:key="subMenu.id"
:to="subMenu.path"
class="submenu-item"
:class="{ active: activeSubNavItem === subMenu.id }"
@click="handleSubMenuClick(subMenu, menu)">
<span>{{ subMenu.name }}</span>
</router-link>
</div>
<!-- 考试管理子菜单 -->
<div class="submenu-container" :class="{ expanded: examMenuExpanded }">
<router-link to="/teacher/exam-management/question-bank" class="submenu-item"
:class="{ active: activeSubNavItem === 'question-bank' }" @click="setActiveSubNavItem('question-bank')">
<span>题库管理</span>
<!-- 没有子菜单的直接链接 -->
<router-link v-else
:to="menu.path"
class="nav-item"
:class="{ active: activeNavItem === menu.id }"
@click="toggleMenu(menu)">
<img :src="getMenuIcon(menu)" :alt="menu.name" v-if="menu.icon">
<span>{{ menu.name }}</span>
</router-link>
<router-link to="/teacher/exam-management/exam-library" class="submenu-item"
:class="{ active: activeSubNavItem === 'exam-library' }" @click="setActiveSubNavItem('exam-library')">
<span>试卷管理</span>
</router-link>
<router-link to="/teacher/exam-management/marking-center/list" class="submenu-item"
:class="{ active: activeSubNavItem === 'marking-center' }" @click="setActiveSubNavItem('marking-center')">
<span>阅卷中心</span>
</router-link>
</div>
<!-- 学员中心 - 可展开菜单 -->
<div class="nav-item" :class="{ active: activeNavItem === 1 }"
@click="toggleStudentMenu('/teacher/student-management/student-library')">
<img :src="activeNavItem === 1 ? '/images/teacher/学院管理(选中).png' : '/images/teacher/学员管理.png'" alt="">
<span>学员中心</span>
<n-icon class="expand-icon" :class="{ expanded: studentMenuExpanded }">
<ChevronDownOutline />
</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>
</template>
</div>
<!-- ai助教 - 已注释 -->
@ -188,6 +153,7 @@ import { useRoute, useRouter } from 'vue-router'
import { ChevronDownOutline } from '@vicons/ionicons5'
import { useUserStore } from '@/stores/user'
import { ChevronBackSharp } from '@vicons/ionicons5'
import { TeachCourseApi } from '@/api/modules/teachCourse'
const userStore = useUserStore()
@ -197,11 +163,9 @@ const height = window.innerHeight;
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 examMenuExpanded = ref(false); //
const studentMenuExpanded = ref(false); //
// const orchestrationMenuExpanded = ref(false); // -
const expandedMenus = ref(new Set()); // ID
const showTopImage = ref(false); // /
//
@ -227,88 +191,77 @@ const breadcrumbDisplay = computed(() => {
const isCourseEditor = computed(() => route.path.includes('course-editor') || route.path.includes('chapter-editor-teacher'));
const setActiveNavItem = (index: number) => {
activeNavItem.value = index;
//
if (index !== 4) {
examMenuExpanded.value = false;
//
const isMenuExpanded = (menuId) => {
return expandedMenus.value.has(menuId)
}
//
const toggleMenu = (menu) => {
if (menu.children && menu.children.length > 0) {
if (expandedMenus.value.has(menu.id)) {
expandedMenus.value.delete(menu.id)
} else {
expandedMenus.value.add(menu.id)
}
}
//
if (index !== 1) {
studentMenuExpanded.value = false;
}
// -
// if (index !== 6) {
// orchestrationMenuExpanded.value = false;
// }
//
if (index !== 4 && index !== 1) { // index !== 6
activeSubNavItem.value = '';
//
activeNavItem.value = menu.id
//
if (menu.path && (!menu.children || menu.children.length === 0)) {
router.push(menu.path)
}
}
//
const toggleExamMenu = () => {
//
if (!examMenuExpanded.value) {
studentMenuExpanded.value = false;
}
examMenuExpanded.value = !examMenuExpanded.value;
activeNavItem.value = 4;
//
if (examMenuExpanded.value && !activeSubNavItem.value) {
activeSubNavItem.value = 'question-management';
//
const handleSubMenuClick = (subMenu, parentMenu) => {
activeSubNavItem.value = subMenu.id
activeNavItem.value = parentMenu.id
if (subMenu.path) {
router.push(subMenu.path)
}
}
//
const toggleStudentMenu = (path: string) => {
//
if (!studentMenuExpanded.value) {
examMenuExpanded.value = false;
//
const getMenuIcon = (menu) => {
if (!menu.icon) return null
//
if (activeNavItem.value === menu.id) {
return menu.icon.replace('.png', '-active.png')
}
studentMenuExpanded.value = !studentMenuExpanded.value;
activeNavItem.value = 1;
//
if (studentMenuExpanded.value && !activeSubNavItem.value) {
activeSubNavItem.value = 'student-library';
router.push(path);
}
return menu.icon
}
// -
// const toggleOrchestrationMenu = () => {
// orchestrationMenuExpanded.value = !orchestrationMenuExpanded.value;
// activeNavItem.value = 6;
//
const updateActiveNavItem = () => {
const path = route.path;
console.log('当前路径:', path);
// //
// if (orchestrationMenuExpanded.value && !activeSubNavItem.value) {
// activeSubNavItem.value = 'app-management';
// }
// }
//
const setActiveSubNavItem = (subItem: string) => {
activeSubNavItem.value = subItem;
//
if (subItem === 'question-bank' || subItem === 'exam-library' || subItem === 'marking-center') {
//
activeNavItem.value = 4;
examMenuExpanded.value = true;
} else if (subItem === 'student-library' || subItem === 'class-management') {
//
activeNavItem.value = 1;
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;
//
const findActiveMenu = (menus, parentId = null) => {
for (const menu of menus) {
if (menu.path && path.includes(menu.path.replace('/teacher/', ''))) {
activeNavItem.value = parentId || menu.id
if (parentId) {
activeSubNavItem.value = menu.id
expandedMenus.value.add(parentId)
}
return true
}
if (menu.children && menu.children.length > 0) {
if (findActiveMenu(menu.children, menu.id)) {
return true
}
}
}
return false
}
findActiveMenu(menuList.value)
}
//
@ -335,6 +288,50 @@ const hideSidebar = computed(() => {
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 currentPath = route.path;
@ -795,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();
@ -832,62 +842,6 @@ const updateTopImageVisibility = () => {
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;
// } 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';
// }
}
}
</script>
<style scoped>