foreign-admin-ui/src/components/layout/Layout.vue

163 lines
3.9 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>
<section class="layout">
<!-- 顶部导航栏固定在顶部 -->
<Header :isSmallScreen="screenWidth < COLLAPSE_BREAKPOINT" @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 }">
<!-- 使用 :key 确保每次路由变化都会创建新的 ErrorBoundary 实例 -->
<ErrorBoundary :key="route.path + '-' + refreshKey">
<KeepAlive v-if="route.meta.keepAlive">
<component :is="Component" :key="refreshKey" />
</KeepAlive>
<component v-else :is="Component" :key="refreshKey" />
</ErrorBoundary>
</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 ErrorBoundary from '@/components/common/ErrorBoundary.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 screenWidth = ref(window.innerWidth)
// 响应式收起侧边栏的阈值单位px
const COLLAPSE_BREAKPOINT = 992
// 监听屏幕宽度变化
const handleResize = () => {
screenWidth.value = window.innerWidth
// 当屏幕宽度小于阈值时自动收起侧边栏
if (screenWidth.value < COLLAPSE_BREAKPOINT) {
sidebarCollapsed.value = true
}else {
// sidebarCollapsed.value = false
}
}
onMounted(() => {
// 初始化时判断屏幕宽度
handleResize()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
// 切换侧边栏
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;
}
}
}
}
// 响应式侧边栏收起
@media screen and (max-width: 992px) {
.layout {
.main-container {
&.is-collapsed {
.content-container {
margin-left: 64px;
}
}
}
}
}
// 页面切换动画
.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>