119 lines
2.7 KiB
Vue
119 lines
2.7 KiB
Vue
<template>
|
||
<section class="layout">
|
||
<!-- 顶部导航栏:固定在顶部 -->
|
||
<Header @toggle-sidebar="handleToggleSidebar" />
|
||
|
||
<!-- 主体内容区域 -->
|
||
<section class="main-container" :class="{ 'is-collapsed': sidebarCollapsed }">
|
||
<!-- 左侧侧边菜单:固定在左侧 -->
|
||
<Sidebar :collapsed="sidebarCollapsed" />
|
||
|
||
<!-- 右侧内容区:页签 + 主体内容 -->
|
||
<section class="content-container">
|
||
<!-- 页签标签栏 -->
|
||
<Tabs />
|
||
|
||
<!-- 主体内容区 -->
|
||
<main class="main-content">
|
||
<transition
|
||
name="fade-transform"
|
||
mode="out-in"
|
||
style="height: 100%; display: flex; flex-direction: column"
|
||
>
|
||
<router-view v-slot="{ Component }">
|
||
<KeepAlive v-if="route.meta.keepAlive">
|
||
<component :is="Component" :key="refreshKey" />
|
||
</KeepAlive>
|
||
<component v-else :is="Component" :key="refreshKey" />
|
||
</router-view>
|
||
</transition>
|
||
</main>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
defineOptions({
|
||
name: 'Layout',
|
||
})
|
||
import Header from './Header.vue'
|
||
import Sidebar from './Sidebar.vue'
|
||
import Tabs from './Tabs.vue'
|
||
import { useTabsStore } from '@/stores/modules/tabs'
|
||
|
||
const route = useRoute()
|
||
const tabsStore = useTabsStore()
|
||
|
||
// 用于组件重新渲染的 key
|
||
const refreshKey = computed(() => {
|
||
// 当 refreshKey 包含当前路由时,重新渲染组件
|
||
if (tabsStore.refreshKey.startsWith(route.path)) {
|
||
return tabsStore.refreshKey
|
||
}
|
||
return route.path
|
||
})
|
||
|
||
// 侧边栏收缩状态
|
||
const sidebarCollapsed = ref(false)
|
||
|
||
// 切换侧边栏
|
||
const handleToggleSidebar = (collapsed: boolean) => {
|
||
sidebarCollapsed.value = collapsed
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.layout {
|
||
width: 100%;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
background: var(--el-bg-color-page);
|
||
|
||
.main-container {
|
||
display: flex;
|
||
height: calc(100vh - 50px);
|
||
margin-top: 50px;
|
||
transition: margin-left 0.3s ease;
|
||
|
||
&.is-collapsed {
|
||
.content-container {
|
||
margin-left: 64px;
|
||
}
|
||
}
|
||
|
||
.content-container {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
margin-left: 200px;
|
||
transition: margin-left 0.3s ease;
|
||
|
||
.main-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
// background: white;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 页面切换动画
|
||
.fade-transform-enter-active,
|
||
.fade-transform-leave-active {
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.fade-transform-enter-from {
|
||
opacity: 0;
|
||
transform: translateX(30px);
|
||
}
|
||
|
||
.fade-transform-leave-to {
|
||
opacity: 0;
|
||
transform: translateX(-30px);
|
||
}
|
||
</style>
|