jcloud/frontend/src/app/layouts/AppLayout.vue
2025-12-27 21:35:18 +08:00

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>