322 lines
7.2 KiB
Vue
322 lines
7.2 KiB
Vue
<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 = (navId) => {
|
||
const idx = expandedSubMenus.value.indexOf(navId)
|
||
if (idx > -1) {
|
||
expandedSubMenus.value.splice(idx, 1)
|
||
} else {
|
||
expandedSubMenus.value.push(navId)
|
||
}
|
||
}
|
||
|
||
const getTargetUrl = (nav) => {
|
||
return nav.mobileUrl || nav.url
|
||
}
|
||
|
||
const handleNavClick = (nav) => {
|
||
if (nav.hasChildren || nav.children?.length > 0) {
|
||
toggleSubMenu(nav.id)
|
||
return
|
||
}
|
||
if (nav.open) {
|
||
window.open(nav.url)
|
||
sidebarVisible.value = false
|
||
return
|
||
}
|
||
const targetUrl = getTargetUrl(nav)
|
||
navStore.changeNavIndex(targetUrl)
|
||
activeIndex.value = targetUrl
|
||
router.push(targetUrl)
|
||
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.id)}"
|
||
@click="handleNavClick(nav)"
|
||
>
|
||
<span class="nav-label">{{ nav.label }}</span>
|
||
<el-icon class="nav-arrow" :size="12">
|
||
<ArrowDown v-if="expandedSubMenus.includes(nav.id)" />
|
||
<ArrowRight v-else />
|
||
</el-icon>
|
||
</div>
|
||
<div v-show="expandedSubMenus.includes(nav.id)" class="nav-sub">
|
||
<div
|
||
v-for="child in nav.children"
|
||
:key="child.id"
|
||
class="nav-item nav-item--child"
|
||
:class="{'nav-item--active': activeIndex === getTargetUrl(child)}"
|
||
@click="handleNavClick(child)"
|
||
>
|
||
{{ child.label }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 无子菜单 -->
|
||
<div
|
||
v-else
|
||
class="nav-item"
|
||
:class="{'nav-item--active': activeIndex === getTargetUrl(nav)}"
|
||
@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>
|