feat:学员中心菜单对接接口;教师端菜单添加一个显示的判断

This commit is contained in:
yuk255 2025-10-10 09:50:46 +08:00
parent 49a55c6c26
commit 949d5f439d
5 changed files with 169 additions and 68 deletions

View File

@ -1,6 +1,6 @@
// 菜单相关API // 菜单相关API
import { ApiRequest } from '../request' import { ApiRequest } from '../request'
import type { ApiResponse } from '../types' import type { ApiResponse, ApiResponseWithResult } from '../types'
// 菜单项接口定义 // 菜单项接口定义
export interface MenuItem { export interface MenuItem {
@ -27,7 +27,7 @@ export class MenuApi {
/** /**
* *
*/ */
static async getStudentMenus(): Promise<ApiResponse<MenuItem[]>> { static async getStudentMenus(): Promise<ApiResponseWithResult<MenuItem[]>> {
return await ApiRequest.get('/aiol/aiolMenu/getStudentMenus') return await ApiRequest.get('/aiol/aiolMenu/getStudentMenus')
} }
} }

View File

@ -13,6 +13,7 @@ export interface ApiResponseWithResult<T = any> {
code: number code: number
message: string message: string
data: { data: {
code: number
result: T result: T
} }
timestamp?: string timestamp?: string

View File

@ -3406,6 +3406,9 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 40px 20px; padding: 40px 20px;
position: relative;
height: 100%;
width: 100%;
} }
.loading-text { .loading-text {

View File

@ -23,70 +23,27 @@
<!-- 分割线 --> <!-- 分割线 -->
<div class="menu-divider"></div> <div class="menu-divider"></div>
<!-- 我的课程 --> <!-- 动态菜单 -->
<div :class="['image-text_19', { active: activeTab === 'courses' }]" @click="handleMenuSelect('courses')"> <div
<img class="image_8 default-icon" referrerpolicy="no-referrer" src="/images/profile/course.png" /> v-for="(menu, index) in visibleMenuItems"
<img class="image_8 hover-icon" referrerpolicy="no-referrer" src="/images/profile/course-active.png" /> :key="menu.id"
<span class="text-group_19">我的课程</span> :class="[`menu-item-${index}`, { active: activeTab === getMenuTabKey(menu.path) }]"
@click="handleMenuSelect(getMenuTabKey(menu.path))"
>
<img
class="menu-icon default-icon"
referrerpolicy="no-referrer"
:src="menu.icon"
:alt="`${menu.name}图标`"
/>
<img
class="menu-icon hover-icon"
referrerpolicy="no-referrer"
:src="getActiveIcon(menu.icon)"
:alt="`${menu.name}激活图标`"
/>
<span class="menu-text">{{ menu.name }}</span>
</div> </div>
<!-- 我的作业 -->
<div :class="['image-text_20', { active: activeTab === 'homework' }]" @click="handleMenuSelect('homework')">
<img class="label_4 default-icon" referrerpolicy="no-referrer" src="/images/profile/grade.png" />
<img class="label_4 hover-icon" referrerpolicy="no-referrer" src="/images/profile/grade-active.png" />
<span class="text-group_20">我的作业</span>
</div>
<!-- 我的考试 -->
<div :class="['image-text_21', { active: activeTab === 'exam' }]" @click="handleMenuSelect('exam')">
<img class="label_5 default-icon" referrerpolicy="no-referrer" src="/images/profile/checklist.png" />
<img class="label_5 hover-icon" referrerpolicy="no-referrer" src="/images/profile/checklist-active.png" />
<span class="text-group_21">我的考试</span>
</div>
<!-- 我的练习 -->
<div :class="['image-text_22', { active: activeTab === 'practice' }]" @click="handleMenuSelect('practice')">
<img class="label_6 default-icon" referrerpolicy="no-referrer" src="/images/profile/bookmark.png" />
<img class="label_6 hover-icon" referrerpolicy="no-referrer" src="/images/profile/bookmark-active.png" />
<span class="text-group_22">我的练习</span>
</div>
<!-- 我的活动 -->
<div :class="['image-text_23', { active: activeTab === 'activity' }]" @click="handleMenuSelect('activity')">
<img class="thumbnail_40 default-icon" referrerpolicy="no-referrer" src="/images/profile/gift.png" />
<img class="thumbnail_40 hover-icon" referrerpolicy="no-referrer" src="/images/profile/gift-active.png" />
<span class="text-group_23">我的活动</span>
</div>
<!-- 我的关注 -->
<div :class="['image-text_27', { active: activeTab === 'follows' }]" @click="handleMenuSelect('follows')">
<img class="thumbnail_42 default-icon" referrerpolicy="no-referrer" src="/images/profile/concern.png" />
<img class="thumbnail_42 hover-icon" referrerpolicy="no-referrer"
src="/images/profile/concern-active.png" />
<span class="text-group_27">我的关注</span>
</div>
<!-- 我的消息 -->
<div :class="['image-text_24', { active: activeTab === 'message' }]" @click="handleMenuSelect('message')">
<img class="label_7 default-icon" referrerpolicy="no-referrer" src="/images/profile/message.png" />
<img class="label_7 hover-icon" referrerpolicy="no-referrer" src="/images/profile/message-active.png" />
<span class="text-group_24">我的消息</span>
</div>
<!-- 我的资料 -->
<div :class="['image-text_25', { active: activeTab === 'materials' }]" @click="handleMenuSelect('materials')">
<img class="image_9 default-icon" referrerpolicy="no-referrer" src="/images/profile/profile.png" />
<img class="image_9 hover-icon" referrerpolicy="no-referrer" src="/images/profile/profile-active.png" />
<span class="text-group_25">我的资料</span>
</div>
<!-- 我的下载 -->
<!-- <div :class="['image-text_26', { active: activeTab === 'download' }]" @click="handleMenuSelect('download')">
<img class="thumbnail_41 default-icon" referrerpolicy="no-referrer" src="/images/profile/download.png" />
<img class="thumbnail_41 hover-icon" referrerpolicy="no-referrer"
src="/images/profile/download-active.png" />
<span class="text-group_26">我的下载</span>
</div> -->
</div> </div>
</div> </div>
@ -1042,6 +999,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 MenuApi from '@/api/modules/menu'
import CourseContent from '@/components/profile/CourseContent.vue' import CourseContent from '@/components/profile/CourseContent.vue'
import PracticeContent from '@/components/profile/PracticeContent.vue' import PracticeContent from '@/components/profile/PracticeContent.vue'
import ActivityContent from '@/components/profile/ActivityContent.vue' import ActivityContent from '@/components/profile/ActivityContent.vue'
@ -3394,7 +3352,47 @@ const submitAssignment = () => {
closeUploadModal() closeUploadModal()
} }
onMounted(() => { const menuItems = ref<any[]>([])
const getMenu = async () => {
try {
const response = await MenuApi.getStudentMenus()
if (response.data && response.data.code === 200) {
menuItems.value = response.data.result
console.log('✅ 获取菜单成功:', menuItems.value)
} else {
menuItems.value = []
}
} catch (error) {
console.error('❌ 获取菜单失败:', error)
menuItems.value = []
}
}
//
const visibleMenuItems = computed(() => {
return menuItems.value
.filter(menu => menu.izVisible === 1)
})
// tab key
const getMenuTabKey = (path: string): TabType => {
const pathMatch = path.match(/\/profile\/(.+)/)
return pathMatch ? pathMatch[1] as TabType : 'courses'
}
//
const getActiveIcon = (iconPath: string) => {
if (!iconPath) return iconPath
const lastDotIndex = iconPath.lastIndexOf('.')
if (lastDotIndex === -1) return iconPath + '-active'
const fileName = iconPath.substring(0, lastDotIndex)
const extension = iconPath.substring(lastDotIndex)
return fileName + '-active' + extension
}
onMounted(async () => {
// //
// //
const shouldRefresh = sessionStorage.getItem('refreshProfile') const shouldRefresh = sessionStorage.getItem('refreshProfile')
@ -3408,6 +3406,9 @@ onMounted(() => {
}, 100) }, 100)
} }
//
await getMenu()
const tabKey = <TabType>route.params.tabKey || 'courses' const tabKey = <TabType>route.params.tabKey || 'courses'
handleMenuSelect(tabKey) handleMenuSelect(tabKey)
@ -9269,4 +9270,97 @@ onActivated(() => {
.form-input::placeholder { .form-input::placeholder {
color: #999; color: #999;
} }
/* 动态菜单样式 */
[class^="menu-item-"] {
width: 11vw;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
cursor: pointer;
padding: 1.5vh 0 1.5vh 1.8vw;
border-radius: 0.31vw;
transition: all 0.3s ease;
background: transparent;
}
[class^="menu-item-"]:hover {
background: #eff7fc;
}
[class^="menu-item-"].active {
background: #eff7fc;
}
/* 动态菜单图标 */
[class^="menu-item-"] .menu-icon {
width: 1.04vw;
height: 1.04vw;
margin-right: 0.78vw;
object-fit: contain;
flex-shrink: 0;
}
/* 动态菜单文字 */
[class^="menu-item-"] .menu-text {
font-size: 16px;
color: rgba(102, 102, 102, 1);
font-family: 'Microsoft YaHei', Arial, sans-serif;
font-weight: normal;
line-height: 1.46vh;
transition: color 0.3s ease;
}
/* 激活状态的菜单文字颜色 */
[class^="menu-item-"].active .menu-text {
color: rgba(2, 134, 206, 1);
}
/* 悬停状态的菜单文字颜色 */
[class^="menu-item-"]:hover .menu-text {
color: rgba(2, 134, 206, 1);
}
/* 动态菜单图标悬停效果 */
[class^="menu-item-"] .hover-icon {
display: none;
}
[class^="menu-item-"]:hover .default-icon {
display: none;
}
[class^="menu-item-"]:hover .hover-icon {
display: block;
}
/* 激活状态的图标显示 */
[class^="menu-item-"].active .default-icon {
display: none;
}
[class^="menu-item-"].active .hover-icon {
display: block;
}
/* 移动端适配 */
@media (max-width: 768px) {
[class^="menu-item-"] {
width: 80%;
margin: 1.5vh 0;
padding: 2vh 0 2vh 3vw;
}
[class^="menu-item-"] .menu-icon {
width: 6vw;
height: 6vw;
margin-right: 4vw;
}
[class^="menu-item-"] .menu-text {
font-size: 4.5vw;
line-height: 3vh;
}
}
</style> </style>

View File

@ -296,8 +296,11 @@ const processMenuData = (menuData) => {
const menuMap = new Map() const menuMap = new Map()
const rootMenus = [] const rootMenus = []
// izVisible 1
const visibleMenuData = menuData.filter(menu => menu.izVisible === 1)
// //
menuData.forEach(menu => { visibleMenuData.forEach(menu => {
menuMap.set(menu.id, { menuMap.set(menu.id, {
...menu, ...menu,
children: [] children: []
@ -305,7 +308,7 @@ const processMenuData = (menuData) => {
}) })
// //
menuData.forEach(menu => { visibleMenuData.forEach(menu => {
if (menu.parentId === null) { if (menu.parentId === null) {
// //
rootMenus.push(menuMap.get(menu.id)) rootMenus.push(menuMap.get(menu.id))