175 lines
3.7 KiB
Vue
175 lines
3.7 KiB
Vue
<template>
|
|
<n-layout has-sider class="app-layout">
|
|
<!-- 侧边栏 -->
|
|
<n-layout-sider
|
|
bordered
|
|
collapse-mode="width"
|
|
:collapsed-width="64"
|
|
:width="240"
|
|
v-model:collapsed="collapsed"
|
|
:show-trigger="!isMobile"
|
|
:responsive="true"
|
|
:breakpoint="768"
|
|
@collapse="onSidebarCollapse"
|
|
@expand="onSidebarExpand"
|
|
>
|
|
<AppSidebar :collapsed="collapsed" @menu-select="onMenuSelect" />
|
|
</n-layout-sider>
|
|
|
|
<!-- 主内容区 -->
|
|
<n-layout>
|
|
<!-- 顶部导航 -->
|
|
<n-layout-header bordered>
|
|
<AppHeader @toggle-sidebar="toggleSidebar" />
|
|
</n-layout-header>
|
|
|
|
<!-- 内容区域 -->
|
|
<n-layout-content>
|
|
<div class="content-wrapper">
|
|
<router-view />
|
|
</div>
|
|
</n-layout-content>
|
|
</n-layout>
|
|
|
|
<!-- 移动端遮罩层 -->
|
|
<div
|
|
v-if="isMobile && !collapsed"
|
|
class="mobile-overlay"
|
|
@click="toggleSidebar"
|
|
></div>
|
|
|
|
<!-- 返回顶部按钮 -->
|
|
<BackToTop />
|
|
</n-layout>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
|
import { NLayout, NLayoutSider, NLayoutHeader, NLayoutContent } from 'naive-ui'
|
|
import AppSidebar from './AppSidebar.vue'
|
|
import AppHeader from './AppHeader.vue'
|
|
import BackToTop from './BackToTop.vue'
|
|
|
|
const SIDEBAR_COLLAPSE_KEY = 'app.sidebar.collapsed'
|
|
const collapsed = ref(localStorage.getItem(SIDEBAR_COLLAPSE_KEY) === 'true')
|
|
|
|
// 移动端检测
|
|
const isMobile = ref(false)
|
|
|
|
// 检测屏幕尺寸
|
|
const checkIsMobile = () => {
|
|
isMobile.value = window.innerWidth < 768
|
|
// 移动端时强制隐藏侧边栏
|
|
if (isMobile.value) {
|
|
collapsed.value = true
|
|
}
|
|
}
|
|
|
|
// 切换侧边栏
|
|
const toggleSidebar = () => {
|
|
collapsed.value = !collapsed.value
|
|
}
|
|
|
|
// 侧边栏折叠事件
|
|
const onSidebarCollapse = () => {
|
|
collapsed.value = true
|
|
}
|
|
|
|
// 侧边栏展开事件
|
|
const onSidebarExpand = () => {
|
|
collapsed.value = false
|
|
}
|
|
|
|
// 菜单选择事件 - 移动端自动关闭
|
|
const onMenuSelect = () => {
|
|
// 移动端时关闭侧边栏
|
|
if (isMobile.value) {
|
|
collapsed.value = true
|
|
}
|
|
}
|
|
|
|
// 监听窗口大小变化
|
|
const handleResize = () => {
|
|
checkIsMobile()
|
|
}
|
|
|
|
onMounted(() => {
|
|
checkIsMobile()
|
|
window.addEventListener('resize', handleResize)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('resize', handleResize)
|
|
})
|
|
|
|
watch(collapsed, (val) => {
|
|
localStorage.setItem(SIDEBAR_COLLAPSE_KEY, String(val))
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.app-layout {
|
|
height: 100vh;
|
|
}
|
|
|
|
.content-wrapper {
|
|
padding: 20px;
|
|
min-height: calc(100vh - 64px);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* 使用Naive UI内置的sticky功能 */
|
|
:deep(.n-layout-header) {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1000;
|
|
}
|
|
|
|
/* 移动端遮罩层 */
|
|
.mobile-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
z-index: 999;
|
|
}
|
|
|
|
/* 移动端样式 */
|
|
@media (max-width: 767px) {
|
|
/* 移动端时完全隐藏侧边栏 */
|
|
:deep(.n-layout-sider) {
|
|
position: fixed !important;
|
|
top: 0;
|
|
left: 0;
|
|
height: 100vh;
|
|
z-index: 1000;
|
|
width: 280px !important;
|
|
max-width: 80vw;
|
|
transform: translateX(-100%);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
/* 移动端侧边栏打开时的样式 */
|
|
:deep(.n-layout-sider:not(.n-layout-sider--collapsed)) {
|
|
transform: translateX(0) !important;
|
|
}
|
|
|
|
/* 移动端时隐藏侧边栏触发器 - 通过show-trigger属性控制 */
|
|
|
|
/* 移动端主内容区域占满全宽 */
|
|
:deep(.n-layout) {
|
|
margin-left: 0 !important;
|
|
}
|
|
}
|
|
|
|
/* 桌面端保持原有样式 */
|
|
@media (min-width: 768px) {
|
|
:deep(.n-layout-sider) {
|
|
position: relative !important;
|
|
transform: none !important;
|
|
}
|
|
}
|
|
</style>
|