feat: 移动端响应式侧边栏抽屉布局
- 检测移动端设备(<768px)自动切换为抽屉式侧边栏 - 添加半透明遮罩层和滑入滑出动画 - 路由切换时自动关闭侧边栏 - 优化移动端导航体验
This commit is contained in:
@@ -1,12 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view v-if="route.meta.fullPage" />
|
<router-view v-if="route.meta.fullPage" />
|
||||||
<el-container v-else class="app-layout">
|
<el-container v-else class="app-layout">
|
||||||
<el-aside :width="isCollapse ? 'var(--sidebar-collapsed)' : 'var(--sidebar-width)'" class="sidebar" :class="{ collapsed: isCollapse }">
|
<!-- Mobile backdrop -->
|
||||||
<AppSidebar :is-collapse="isCollapse" />
|
<div v-if="isMobile && !isCollapse" class="mobile-backdrop" @click="isCollapse = true"></div>
|
||||||
|
<el-aside
|
||||||
|
:width="isCollapse && !isMobile ? 'var(--sidebar-collapsed)' : 'var(--sidebar-width)'"
|
||||||
|
class="sidebar"
|
||||||
|
:class="{ collapsed: isCollapse, 'mobile-drawer': isMobile, 'mobile-open': isMobile && !isCollapse }"
|
||||||
|
>
|
||||||
|
<AppSidebar :is-collapse="isCollapse && !isMobile" />
|
||||||
</el-aside>
|
</el-aside>
|
||||||
<el-container class="main-container">
|
<el-container class="main-container">
|
||||||
<el-header class="app-header-bar" height="60px">
|
<el-header class="app-header-bar" height="60px">
|
||||||
<AppHeader :is-collapse="isCollapse" @toggle="isCollapse = !isCollapse" />
|
<AppHeader :is-collapse="isCollapse" @toggle="handleToggle" />
|
||||||
</el-header>
|
</el-header>
|
||||||
<el-main class="app-main">
|
<el-main class="app-main">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
@@ -20,13 +26,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import AppSidebar from './AppSidebar.vue'
|
import AppSidebar from './AppSidebar.vue'
|
||||||
import AppHeader from './AppHeader.vue'
|
import AppHeader from './AppHeader.vue'
|
||||||
|
|
||||||
const isCollapse = ref(false)
|
const isCollapse = ref(false)
|
||||||
|
const isMobile = ref(false)
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
function checkMobile() {
|
||||||
|
isMobile.value = window.innerWidth < 768
|
||||||
|
if (isMobile.value) isCollapse.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToggle() {
|
||||||
|
isCollapse.value = !isCollapse.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close drawer on route change (mobile)
|
||||||
|
watch(() => route.path, () => {
|
||||||
|
if (isMobile.value) isCollapse.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkMobile()
|
||||||
|
window.addEventListener('resize', checkMobile)
|
||||||
|
})
|
||||||
|
onUnmounted(() => window.removeEventListener('resize', checkMobile))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -89,4 +116,34 @@ const route = useRoute()
|
|||||||
.fade-leave-to {
|
.fade-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.mobile-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.mobile-drawer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 100;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.mobile-drawer.mobile-open {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.app-header-bar {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user