Files
schoolNews/schoolNewsWeb/src/layouts/NavigationLayout.vue
2025-11-12 16:10:34 +08:00

250 lines
5.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="navigation-layout">
<!-- 顶部导航栏 -->
<TopNavigation />
<!-- 主内容区域 -->
<div class="layout-content">
<!-- 面包屑 -->
<!-- <div class="breadcrumb-wrapper" v-if="breadcrumbItems.length > 0">
<Breadcrumb :items="breadcrumbItems" />
</div> -->
<!-- 显示导航栏的侧边栏和内容 -->
<div class="content-wrapper" v-if="hasSidebarMenus">
<!-- 侧边栏 -->
<aside class="sidebar" :class="{ collapsed: sidebarCollapsed }">
<div class="sidebar-toggle-btn" @click="toggleSidebar">
<i class="toggle-icon">{{ sidebarCollapsed ? '▶' : '◀' }}</i>
</div>
<nav class="sidebar-nav">
<MenuSidebar
:menus="sidebarMenus"
:collapsed="sidebarCollapsed"
@menu-click="handleMenuClick"
/>
</nav>
</aside>
<!-- 页面内容 -->
<main class="main-content">
<AIAgent/>
<router-view />
</main>
</div>
<!-- 没有侧边栏时直接显示内容 -->
<div class="content-wrapper-full" v-else>
<main class="main-content-full">
<AIAgent/>
<router-view />
</main>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import type { SysMenu } from '@/types';
import { MenuType } from '@/types/enums';
// import { getMenuPath } from '@/utils/route-generator';
import { TopNavigation, MenuSidebar } from '@/components';
import { AIAgent } from '@/views/public/ai';
const route = useRoute();
const router = useRouter();
const store = useStore();
const sidebarCollapsed = ref(false);
// 获取所有菜单
const allMenus = computed(() => store.getters['auth/menuTree']);
// 获取当前激活的顶层导航菜单
const activeTopMenu = computed(() => {
const path = route.path;
// 找到匹配的顶层菜单
for (const menu of allMenus.value) {
if (menu.type === MenuType.NAVIGATION) {
if (isPathUnderMenu(path, menu)) {
return menu;
}
}
}
return null;
});
// 获取当前页面的侧边栏菜单SIDEBAR类型的子菜单
const sidebarMenus = computed(() => {
if (!activeTopMenu.value || !activeTopMenu.value.children) {
return [];
}
// 返回SIDEBAR类型的子菜单
return activeTopMenu.value.children.filter((child: SysMenu) => child.type === MenuType.SIDEBAR);
});
// 是否有侧边栏菜单
const hasSidebarMenus = computed(() => sidebarMenus.value.length > 0);
// 面包屑数据(暂时未使用)
// const breadcrumbItems = computed(() => {
// if (!route.meta?.menuId) return [];
//
// const menuPath = getMenuPath(allMenus.value, route.meta.menuId as string);
// return menuPath.map((menu) => ({
// title: menu.name || '',
// path: menu.url || '',
// }));
// });
// 判断路径是否在菜单下
function isPathUnderMenu(path: string, menu: SysMenu): boolean {
if (menu.url === path) return true;
if (menu.children) {
for (const child of menu.children) {
if (isPathUnderMenu(path, child)) {
return true;
}
}
}
return false;
}
// 切换侧边栏
function toggleSidebar() {
sidebarCollapsed.value = !sidebarCollapsed.value;
localStorage.setItem('sidebarCollapsed', String(sidebarCollapsed.value));
}
// 处理菜单点击
function handleMenuClick(menu: SysMenu) {
if (menu.url && menu.url !== route.path) {
router.push(menu.url);
}
}
// 恢复侧边栏状态
const savedState = localStorage.getItem('sidebarCollapsed');
if (savedState !== null) {
sidebarCollapsed.value = savedState === 'true';
}
// 监听路由变化
watch(
() => route.path,
() => {
// 路由变化时可以做一些处理
}
);
</script>
<style lang="scss" scoped>
.navigation-layout {
display: flex;
flex-direction: column;
background: #f0f2f5;
}
.layout-content {
flex: 1;
display: flex;
flex-direction: column;
max-height: calc(100vh - 76px);
overflow-y: auto;
}
.breadcrumb-wrapper {
background: white;
padding: 16px 24px;
margin: 16px 16px 0 16px;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
}
.content-wrapper {
flex: 1;
display: flex;
margin: 16px;
gap: 16px;
}
.sidebar {
width: 260px;
background: white;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
transition: width 0.3s ease;
flex-shrink: 0;
display: flex;
flex-direction: column;
position: relative;
&.collapsed {
width: 80px;
}
}
.sidebar-toggle-btn {
position: absolute;
top: 50%;
right: -12px;
width: 24px;
height: 48px;
background: white;
border: 1px solid #e8e8e8;
border-radius: 0 12px 12px 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
transition: all 0.3s;
&:hover {
background: #f0f2f5;
}
.toggle-icon {
font-size: 12px;
color: #666;
}
}
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 16px 0;
}
.main-content {
flex: 1;
background: white;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
// padding: 24px;
overflow-y: auto;
min-width: 0;
}
.content-wrapper-full {
flex: 1;
// margin: 16px;
}
.main-content-full {
background: white;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
min-height: calc(100vh - 76px);
}
</style>