Files
vueadmin/src/layouts/components/sideMenu.vue
T
2026-01-16 23:17:20 +08:00

173 lines
4.0 KiB
Vue

<template>
<a-menu mode="inline" :theme="theme" :collapsed="collapsed" :selected-keys="selectedKeys" :open-keys="openKeys"
@select="handleSelect" @open-change="handleOpenChange" class="side-menu">
<template v-for="item in menuList">
<!-- 有子菜单 -->
<a-sub-menu v-if="item.children && item.children.length > 0" :key="item.path + '-submenu'">
<template #icon>
<component :is="item.meta?.icon || 'MenuOutlined'" />
</template>
<template #title>{{ item.meta?.title || item.name }}</template>
<a-menu-item v-for="child in item.children.filter(sub => !sub.children || sub.children.length === 0)"
:key="child.path">
<template #icon>
<component :is="child.meta?.icon || 'FileOutlined'" />
</template>
{{ child.meta?.title || child.name }}
</a-menu-item>
<a-sub-menu v-for="child in item.children.filter(sub => sub.children && sub.children.length > 0)"
:key="child.path">
<template #icon>
<component :is="child.meta?.icon || 'AppstoreOutlined'" />
</template>
<template #title>{{ child.meta?.title || child.name }}</template>
<a-menu-item v-for="grandChild in child.children" :key="grandChild.path">
<template #icon>
<component :is="grandChild.meta?.icon || 'FileOutlined'" />
</template>
{{ grandChild.meta?.title || grandChild.name }}
</a-menu-item>
</a-sub-menu>
</a-sub-menu>
<!-- 无子菜单 -->
<a-menu-item v-else :key="item.path + '-item'">
<template #icon>
<component :is="item.meta?.icon || 'MenuOutlined'" />
</template>
{{ item.meta?.title || item.name }}
</a-menu-item>
</template>
</a-menu>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getUserMenu } from '@/api/menu'
const props = defineProps({
collapsed: {
type: Boolean,
default: false
},
theme: {
type: String,
default: 'light'
}
})
const route = useRoute()
const router = useRouter()
const menuList = ref([])
const selectedKeys = ref([])
const openKeys = ref([])
// 获取菜单数据
const getMenuList = async () => {
try {
const res = await getUserMenu()
if (res.code === 200) {
menuList.value = res.data || []
}
} catch (error) {
console.error('获取菜单失败:', error)
// 模拟数据
menuList.value = [
{
path: '/home',
name: 'Home',
meta: { title: '首页', icon: 'HomeOutlined' }
},
{
path: '/system',
name: 'System',
meta: { title: '系统管理', icon: 'SettingOutlined' },
children: [
{
path: '/system/user',
name: 'User',
meta: { title: '用户管理', icon: 'UserOutlined' }
},
{
path: '/system/role',
name: 'Role',
meta: { title: '角色管理', icon: 'TeamOutlined' }
},
{
path: '/system/menu',
name: 'Menu',
meta: { title: '菜单管理', icon: 'MenuOutlined' }
}
]
}
]
}
}
// 更新选中的菜单
const updateSelectedKeys = () => {
selectedKeys.value = [route.path]
// 获取父级菜单路径
const matched = route.matched
.filter(item => item.path !== '/' && item.path !== route.path)
.map(item => item.path)
// 折叠时不自动展开
if (!props.collapsed) {
openKeys.value = matched
}
}
// 处理菜单选择
const handleSelect = ({ key }) => {
router.push(key)
}
// 处理菜单展开/收起
const handleOpenChange = (keys) => {
openKeys.value = keys
}
// 监听路由变化
watch(() => route.path, () => {
updateSelectedKeys()
}, { immediate: true })
// 监听折叠状态
watch(() => props.collapsed, (val) => {
if (val) {
openKeys.value = []
} else {
updateSelectedKeys()
}
})
onMounted(() => {
getMenuList()
})
</script>
<style scoped lang="scss">
.side-menu {
height: calc(100% - 60px);
border-right: none;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
</style>