This commit is contained in:
Leo
2026-01-05 18:32:29 +08:00
parent 1a4e56bfed
commit a3291f7a31
22 changed files with 3180 additions and 23 deletions

View File

@@ -0,0 +1,174 @@
<template>
<div class="mobile-bottom-nav" v-if="isMobile && show">
<div
v-for="item in navItems"
:key="item.path"
class="nav-item"
:class="{ 'active': isActive(item.path) }"
@click="handleNavClick(item)"
>
<div class="nav-icon">
<i :class="item.icon" v-if="item.icon"></i>
<svg-icon :icon-class="item.iconClass" v-else-if="item.iconClass" />
<el-badge :value="item.badge" :hidden="!item.badge" v-if="item.badge">
<div class="icon-placeholder"></div>
</el-badge>
</div>
<div class="nav-label">{{ item.label }}</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'MobileBottomNav',
props: {
items: {
type: Array,
default: () => []
},
show: {
type: Boolean,
default: true
}
},
computed: {
...mapGetters(['device']),
isMobile() {
return this.device === 'mobile' || window.innerWidth < 768
},
navItems() {
if (this.items && this.items.length > 0) {
return this.items
}
// 默认导航项 - 从路由中获取
const routes = this.$store?.state?.permission?.routes || []
const mainRoutes = routes.filter(route => route.meta && route.meta.title && !route.hidden)
if (mainRoutes.length > 0) {
return mainRoutes.slice(0, 5).map(route => ({
path: route.path,
label: route.meta.title,
icon: route.meta.icon || 'el-icon-menu',
iconClass: route.meta.icon
}))
}
// 如果没有路由,返回默认导航
return [
{
path: '/index',
label: '首页',
icon: 'el-icon-s-home'
}
]
}
},
methods: {
isActive(path) {
return this.$route.path.startsWith(path)
},
handleNavClick(item) {
if (item.handler) {
item.handler()
} else if (item.path) {
this.$router.push(item.path)
}
this.$emit('nav-click', item)
}
}
}
</script>
<style lang="scss" scoped>
.mobile-bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background: #fff;
border-top: 1px solid #e4e7ed;
display: flex;
justify-content: space-around;
align-items: center;
z-index: 1000;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
padding-bottom: env(safe-area-inset-bottom);
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 6px 0;
cursor: pointer;
transition: all 0.3s;
-webkit-tap-highlight-color: transparent;
.nav-icon {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 4px;
position: relative;
i, .svg-icon {
font-size: 22px;
color: #909399;
transition: all 0.3s;
}
.icon-placeholder {
width: 22px;
height: 22px;
}
}
.nav-label {
font-size: 11px;
color: #909399;
transition: all 0.3s;
}
&.active {
.nav-icon {
i, .svg-icon {
color: #409eff;
font-size: 24px;
}
}
.nav-label {
color: #409eff;
font-weight: 600;
}
}
&:active {
transform: scale(0.95);
opacity: 0.8;
}
}
}
// 桌面端隐藏
@media (min-width: 769px) {
.mobile-bottom-nav {
display: none;
}
}
// 为底部导航预留空间
@media (max-width: 768px) {
.app-main {
padding-bottom: 60px !important;
}
}
</style>