本地 RAG 知识库
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

<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>