You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
104 lines
3.5 KiB
104 lines
3.5 KiB
<template>
|
|
<aside class="sidebar" :class="{ collapsed: collapsed && !mobileOpen, open: mobileOpen }">
|
|
<div class="sidebar-inner">
|
|
<t-menu
|
|
:value="activeMenu"
|
|
:default-expanded="expandedKeys"
|
|
:collapsed="collapsed"
|
|
@change="onMenuChange"
|
|
@expand="onExpand"
|
|
>
|
|
<template v-for="item in visibleMenuItems" :key="item.id">
|
|
<t-menu-item v-if="!item.children" :value="item.path || item.id">
|
|
<template #icon><span>{{ item.icon }}</span></template>
|
|
{{ item.label }}
|
|
</t-menu-item>
|
|
<t-submenu v-else :value="item.id" :title="item.label">
|
|
<template #icon><span>{{ item.icon }}</span></template>
|
|
<t-menu-item
|
|
v-for="child in item.children.filter(c => isMenuVisible(c))"
|
|
:key="child.id"
|
|
:value="child.path || child.id"
|
|
>
|
|
<template #icon><span>{{ child.icon }}</span></template>
|
|
{{ child.label }}
|
|
</t-menu-item>
|
|
</t-submenu>
|
|
</template>
|
|
</t-menu>
|
|
<div class="sidebar-bottom">
|
|
<t-button variant="text" size="small" @click="$emit('toggleCollapse')" :title="collapsed ? '展开侧边栏' : '折叠侧边栏'">
|
|
<span :style="{ display: 'inline-block', transform: collapsed ? 'rotate(180deg)' : '' }">◀</span>
|
|
</t-button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { MENU_ITEMS, type MenuItem } from '@/stores/navigation'
|
|
|
|
const props = defineProps<{ collapsed: boolean; mobileOpen: boolean }>()
|
|
const emit = defineEmits<{ toggleCollapse: []; closeMobileMenu: [] }>()
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const auth = useAuthStore()
|
|
|
|
const activeMenu = computed(() => route.path)
|
|
|
|
const expandedKeys = computed(() => {
|
|
const keys: string[] = []
|
|
for (const item of MENU_ITEMS) {
|
|
if (item.children && item.children.some(c => c.path === route.path)) {
|
|
keys.push(item.id)
|
|
}
|
|
}
|
|
return keys
|
|
})
|
|
|
|
/** 隐藏无权限的 admin 菜单 */
|
|
const visibleMenuItems = computed(() => {
|
|
return MENU_ITEMS.filter(item => {
|
|
if (!auth.isAdmin) {
|
|
// 检查子菜单是否全部隐藏
|
|
if (item.children) {
|
|
const visibleChildren = item.children.filter(c => isMenuVisible(c))
|
|
if (visibleChildren.length === 0) return false
|
|
// 注:此处不能直接修改 item.children,需要返回过滤后的
|
|
return true
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
})
|
|
|
|
function isMenuVisible(item: MenuItem): boolean {
|
|
if (!item.roles || !item.roles.length) return true
|
|
return item.roles.some(r => auth.hasRole(r))
|
|
}
|
|
|
|
function onMenuChange(value: string) {
|
|
router.push(value)
|
|
emit('closeMobileMenu')
|
|
}
|
|
|
|
function onExpand(_value: string[]) {}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.sidebar {
|
|
width: 220px; background: var(--td-bg-color-container); border-right: 1px solid var(--td-border-level-1-color);
|
|
flex-shrink: 0; display: flex; flex-direction: column; transition: width .25s; overflow: hidden;
|
|
}
|
|
.sidebar.collapsed { width: 60px; }
|
|
.sidebar-inner { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
|
.sidebar-bottom { padding: 8px; border-top: 1px solid var(--td-border-level-1-color); text-align: center; }
|
|
@media (max-width: 768px) {
|
|
.sidebar { position: fixed; left: -260px; top: 48px; bottom: 0; z-index: 99; width: 260px; transition: left .25s; }
|
|
.sidebar.open { left: 0; }
|
|
}
|
|
</style>
|