Files
portal/src/views/layout/mobile/index.vue
2026-07-30 17:12:26 +08:00

322 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>