This commit is contained in:
yiqiuyang
2026-07-30 16:54:55 +08:00
parent 2b9a322335
commit 661ee7d7a3
6 changed files with 380 additions and 11 deletions

View File

@ -0,0 +1,316 @@
<script setup>
import {ref, onMounted, watch} from 'vue'
import {useRouter} from 'vue-router'
import {useNavStore} from '@/store/nav'
import {storeToRefs} from 'pinia'
import {Menu, ArrowDown, ArrowRight} from '@element-plus/icons-vue'
const navStore = useNavStore()
const {navIndex} = storeToRefs(navStore)
const router = useRouter()
const sidebarVisible = ref(false)
const navList = ref([])
const activeIndex = ref('/')
const expandedSubMenus = ref([])
const toggleSidebar = () => {
sidebarVisible.value = !sidebarVisible.value
}
const closeSidebar = () => {
sidebarVisible.value = false
}
const toggleSubMenu = (url) => {
const idx = expandedSubMenus.value.indexOf(url)
if (idx > -1) {
expandedSubMenus.value.splice(idx, 1)
} else {
expandedSubMenus.value.push(url)
}
}
const handleNavClick = (nav) => {
if (nav.hasChildren || nav.children?.length > 0) {
toggleSubMenu(nav.url)
return
}
if (nav.open) {
window.open(nav.url)
sidebarVisible.value = false
return
}
navStore.changeNavIndex(nav.url)
activeIndex.value = nav.url
router.push(nav.url)
sidebarVisible.value = false
}
watch(
navIndex,
(newVal) => {
activeIndex.value = newVal
},
{immediate: true}
)
onMounted(() => {
navList.value = window.nav?.header || []
})
const toMIIF = () => {
window.open('https://beian.miit.gov.cn')
}
</script>
<template>
<div class="mobile-layout">
<!-- ==================== 顶部 Header ==================== -->
<div class="mobile-header">
<el-icon class="header-menu-icon" :size="22" @click="toggleSidebar">
<Menu />
</el-icon>
<div class="header-logo" @click="router.push('/')"></div>
</div>
<!-- ==================== 侧边栏 ==================== -->
<transition name="sidebar-slide">
<div v-show="sidebarVisible" class="sidebar">
<div class="sidebar-nav">
<template v-for="nav in navList" :key="nav.id">
<!-- 有子菜单 -->
<div v-if="nav.hasChildren || nav.children?.length > 0" class="nav-group">
<div
class="nav-item nav-item--parent"
:class="{'nav-item--expanded': expandedSubMenus.includes(nav.url)}"
@click="handleNavClick(nav)"
>
<span class="nav-label">{{ nav.label }}</span>
<el-icon class="nav-arrow" :size="12">
<ArrowDown v-if="expandedSubMenus.includes(nav.url)" />
<ArrowRight v-else />
</el-icon>
</div>
<div v-show="expandedSubMenus.includes(nav.url)" class="nav-sub">
<div
v-for="child in nav.children"
:key="child.id"
class="nav-item nav-item--child"
:class="{'nav-item--active': activeIndex === child.url}"
@click="handleNavClick(child)"
>
{{ child.label }}
</div>
</div>
</div>
<!-- 无子菜单 -->
<div
v-else
class="nav-item"
:class="{'nav-item--active': activeIndex === nav.url}"
@click="handleNavClick(nav)"
>
<span class="nav-label">{{ nav.label }}</span>
</div>
</template>
</div>
</div>
</transition>
<!-- ==================== Content 内容区域 ==================== -->
<div class="mobile-content">
<router-view v-slot="{Component}">
<keep-alive include="New">
<component :is="Component" />
</keep-alive>
</router-view>
</div>
<!-- ==================== Footer 底部 ==================== -->
<div class="mobile-footer">
<div class="footer-info">
<span>Copyright © 2023.Rangu Technology Co.,Ltd.</span>
<span>燃谷科技南京有限公司</span>
</div>
<div class="footer-beian" @click="toMIIF">苏ICP备2023030548号-1</div>
</div>
</div>
</template>
<style lang="scss" scoped>
// ==================== 布局容器 ====================
.mobile-layout {
width: 100%;
max-width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: $bg_color;
overflow-x: hidden;
}
// ==================== Header ====================
.mobile-header {
position: fixed;
top: 0;
left: 0;
width: 100%; /* no */
max-width: 100vw;
height: 44px; /* no */
background-color: $bg_color;
z-index: 110;
display: flex;
align-items: center;
padding: 0 10px; /* no */
box-sizing: border-box;
.header-menu-icon {
color: $white;
cursor: pointer;
flex-shrink: 0;
}
.header-logo {
background-image: url('/static/images/header/logo.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 100px;
height: 32px; /* no */
cursor: pointer;
flex-shrink: 0;
}
}
// ==================== 侧边栏遮罩层 ====================
.sidebar-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 100;
}
.mask-fade-enter-active,
.mask-fade-leave-active {
transition: opacity 0.3s ease;
}
.mask-fade-enter-from,
.mask-fade-leave-to {
opacity: 0;
}
// ==================== 侧边栏(浮动层) ====================
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 160px; /* no */
height: 100%;
background-color: $bg_color;
z-index: 105;
overflow-y: auto;
padding-top: 56px; /* no */
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3); /* no */
}
.sidebar-slide-enter-active,
.sidebar-slide-leave-active {
transition: transform 0.3s ease;
}
.sidebar-slide-enter-from,
.sidebar-slide-leave-to {
transform: translateX(-100%);
}
.sidebar-nav {
padding: 0;
}
.nav-group {
border-bottom: 1px solid rgba(255, 255, 255, 0.08); /* no */
}
.nav-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px; /* no */
cursor: pointer;
color: #999999;
font-size: 14px;
transition: all 0.2s ease;
user-select: none;
&:active {
background-color: rgba(255, 255, 255, 0.05);
}
&--active {
color: $white;
}
&--parent {
font-weight: 500;
}
&--expanded {
color: $white;
}
&--child {
padding-left: 32px; /* no */
font-size: 13px;
}
.nav-label {
flex: 1;
}
.nav-arrow {
color: #999999;
flex-shrink: 0;
}
}
.nav-sub {
overflow: hidden;
}
// ==================== Content 内容区域 ====================
.mobile-content {
margin-top: 44px; /* no */
flex: 1;
width: 100%;
max-width: 100vw;
overflow-x: hidden;
}
// ==================== Footer 底部 ====================
.mobile-footer {
width: 100%;
max-width: 100vw;
padding: 20px 16px; /* no */
box-sizing: border-box;
text-align: center;
background-color: $bg_color;
.footer-info {
display: flex;
flex-direction: column;
gap: 4px;
color: #999999;
font-size: 12px;
margin-bottom: 8px;
}
.footer-beian {
color: #0056b3;
font-size: 11px;
cursor: pointer;
}
}
</style>