bonus-ui/src/layout/components/Navbar.vue

547 lines
15 KiB
Vue
Raw Normal View History

2024-06-26 15:11:05 +08:00
<template>
2026-01-20 17:40:38 +08:00
<div>
<div class="navbar">
<div class="app-title">
<span>{{ title }}</span>
</div>
<div class="menus" @mouseleave="handleMenuLeave">
<div
v-for="(item, index) in menuList"
:key="index"
class="menu-item"
@mouseenter="handleMenuEnter(item)"
@click="handleMenuClick(item)"
>
<div class="menu-btn">
<span v-if="item.redirect == 'index' || item.redirect == 'index1'">
{{ item.children[0].meta.title }}
</span>
2026-01-22 09:24:00 +08:00
<span v-else-if="item.meta && item.meta.title">{{ item.meta.title }}</span>
2026-01-20 17:40:38 +08:00
<div class="tab-item-btn"></div>
</div>
</div>
</div>
<div class="right-menu">
<template v-if="device !== 'mobile'">
<search id="header-search" class="right-menu-item" />
<!-- <screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip> -->
</template>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<div>
<img :src="avatar" class="user-avatar" />
<span class="nick-name">{{ user.nickName }}</span>
<i class="el-icon-arrow-down" style="color: #fff"></i>
</div>
<el-dropdown-menu slot="dropdown">
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item @click.native="setting = true">
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item divided @click.native="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<div class="menu-box" v-show="showMenuBox" @mouseenter="handleBoxEnter" @mouseleave="handleBoxLeave">
<div class="menu-list">
<div class="list-box">
<!-- 顶部标题 -->
<div class="menu-title-top" v-if="currentMenu && currentMenu.meta">
<div class="title-tip"></div>
<span style="font-weight: bold; font-size: 16px">{{ currentMenu.meta.title }}</span>
</div>
<!-- 一级 -->
<div v-for="(child, i) in currentMenu.children || []" :key="i" class="sub-menu-item" v-if="!child.hidden">
<!-- 一级有子级 -->
<div v-if="child.children && child.children.length">
<!-- 一级父节点点击展开 -->
<div class="menu-title" @click="toggleLevel1(child)">
<div class="children-title-tip"></div>
<span>
<span style="margin-right: 5px">{{ child.meta.title }}</span>
<img
v-if="openLevel1 == child.path"
src="@/assets/images/up.png"
style="width: 14px; height: 14px"
alt=""
/>
<img v-else src="@/assets/images/down.png" style="width: 14px; height: 14px" alt="" />
</span>
<span class="menu-star">
<img src="@/assets/images/star.png" style="width: 10px; height: 9px" alt="" />
</span>
</div>
<!-- 二级区域只在当前一级展开时显示 -->
<div v-show="openLevel1 === child.path">
<!-- 二级 -->
<div v-for="(c2, j) in child.children" :key="j" class="sub-menu-item" v-if="!c2.hidden">
<!-- 二级还有子级 -->
<div v-if="c2.children && c2.children.length">
<!-- 二级父节点 -->
<div class="menu-title" @click="toggleLevel2(c2)">
{{ c2.meta.title }}
</div>
<!-- 三级只展开当前二级 -->
<div v-show="openLevel2 === c2.path">
<div
v-for="(c3, k) in c2.children"
:key="k"
class="sub-menu-item"
v-if="!c3.hidden"
@click="goRoute(c3)"
>
{{ c3.meta.title }}
</div>
</div>
</div>
<!-- 二级叶子节点 -->
<div v-else class="menu-title" @click="goRoute(c2)">
<span class="child-left">- {{ c2.meta.title }}</span>
<span class="menu-star">
<img src="@/assets/images/star.png" style="width: 10px; height: 9px" alt="" />
</span>
</div>
</div>
</div>
</div>
<!-- 一级就是叶子节点 -->
<div v-else class="menu-title" @click="goRoute(child)">
<div class="children-title-tip"></div>
<span>{{ child.meta.title }}</span>
<span class="menu-star">
<img src="@/assets/images/star.png" style="width: 10px; height: 9px" alt="" />
</span>
</div>
</div>
</div>
</div>
2024-06-26 15:11:05 +08:00
</div>
</div>
</template>
<script>
2026-01-20 17:40:38 +08:00
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import bonusGit from '@/components/bonus/Git'
import bonusDoc from '@/components/bonus/Doc'
import { getUserProfile } from '@/api/system/user'
2024-06-26 15:11:05 +08:00
export default {
components: {
Breadcrumb,
TopNav,
Hamburger,
Screenfull,
SizeSelect,
Search,
bonusGit,
bonusDoc,
2024-06-26 15:11:05 +08:00
},
2026-01-20 17:40:38 +08:00
data() {
return {
title: process.env.VUE_APP_TITLE,
user: {},
onlyOneChild: null,
basePath: null,
basePath2: null,
menuList: [],
showMenuBox: false,
currentMenu: {}, // 当前 hover 的一级菜单
openLevel1: '', // 当前展开的一级菜单 path
openLevel2: '', // 当前展开的二级菜单 path
}
},
2024-06-26 15:11:05 +08:00
computed: {
2026-01-20 17:40:38 +08:00
...mapGetters(['sidebarRouters', 'sidebar', 'avatar', 'device']),
2024-06-26 15:11:05 +08:00
setting: {
get() {
2026-01-20 17:40:38 +08:00
return this.$store.state.settings.showSettings
2024-06-26 15:11:05 +08:00
},
set(val) {
2026-01-20 17:40:38 +08:00
this.$store.dispatch('settings/changeSetting', {
key: 'showSettings',
value: val,
2026-01-20 17:40:38 +08:00
})
},
2024-06-26 15:11:05 +08:00
},
topNav: {
get() {
2026-01-20 17:40:38 +08:00
return this.$store.state.settings.topNav
},
},
2024-06-26 15:11:05 +08:00
},
2026-01-20 17:40:38 +08:00
created() {
this.getUser()
// 过滤掉 sidebarRouters 中隐藏的路由
this.menuList = this.sidebarRouters.filter((route) => !route.hidden)
},
mounted() {},
2024-06-26 15:11:05 +08:00
methods: {
2026-01-20 17:40:38 +08:00
getUser() {
getUserProfile().then((response) => {
this.user = response.data
})
},
2024-06-26 15:11:05 +08:00
toggleSideBar() {
2026-01-20 17:40:38 +08:00
this.$store.dispatch('app/toggleSideBar')
2024-06-26 15:11:05 +08:00
},
async logout() {
2026-01-20 17:40:38 +08:00
this.$confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
2026-01-20 17:40:38 +08:00
this.$store.dispatch('LogOut').then((res) => {
2025-10-29 08:56:04 +08:00
console.log('🚀 ~ res-退出登录:', res)
2026-01-20 17:40:38 +08:00
console.log('logout', process.env.VUE_APP_BASE_API)
2026-01-05 18:07:00 +08:00
this.$store.dispatch('tagsView/delAllViews')
2025-04-21 13:20:14 +08:00
if (process.env.VUE_APP_BASE_API == '/iws/jxhzb-api') {
window.location.href = 'http://sgwpdm.ah.sgcc.com.cn/iws'
} else {
2026-01-20 17:40:38 +08:00
this.$router.push({ path: '/login' })
2025-04-21 13:20:14 +08:00
}
2026-01-20 17:40:38 +08:00
})
2024-06-26 15:11:05 +08:00
})
2025-10-29 08:56:04 +08:00
.catch((err) => {
console.log('🚀 ~ err-退出登录:', err)
2026-01-20 17:40:38 +08:00
})
},
handleMenuClick(item) {
console.log('🚀 ~ item.redirect:', item.redirect)
if (item.redirect == 'index' || item.redirect == 'index1') {
this.$router.push({
path: '/' + item.redirect,
})
}
},
handleMenuEnter(item) {
if (item.redirect == 'index' || item.redirect == 'index1') {
this.showMenuBox = false
return
}
this.showMenuBox = true
// 构建带 fullPath 的菜单树
const buildPath = (nodes, parentPath = '') => {
return nodes.map((n) => {
let currentPath = n.path || ''
// 子路由不带 /,拼父路径
if (!currentPath.startsWith('/')) {
currentPath = parentPath.replace(/\/$/, '') + '/' + currentPath
}
const newNode = {
...n,
fullPath: currentPath,
}
if (n.children && n.children.length) {
newNode.children = buildPath(n.children, currentPath)
}
return newNode
})
}
this.currentMenu = {
...item,
fullPath: item.path,
children: buildPath(item.children || [], item.path),
}
},
handleMenuLeave() {
this.showMenuBox = false
},
handleBoxEnter() {
this.showMenuBox = true
},
handleBoxLeave() {
this.showMenuBox = false
},
goRoute(route) {
console.log('🚀 ~ route:', route)
if (route.fullPath) {
this.$router.push({
path: route.fullPath,
query: route.query,
})
this.showMenuBox = false
}
},
// 点击一级父节点
toggleLevel1(item) {
console.log('🚀 ~ item:', item)
if (this.openLevel1 === item.path) {
// 已展开 → 收起
this.openLevel1 = ''
this.openLevel2 = ''
} else {
// 展开当前,关闭其它
this.openLevel1 = item.path
this.openLevel2 = ''
}
},
// 点击二级父节点
toggleLevel2(item) {
console.log('🚀 ~ item:', item)
if (this.openLevel2 === item.path) {
this.openLevel2 = ''
} else {
this.openLevel2 = item.path
}
},
},
2026-01-20 17:40:38 +08:00
}
2024-06-26 15:11:05 +08:00
</script>
<style lang="scss" scoped>
.navbar {
height: 50px;
overflow: hidden;
position: relative;
2026-01-20 17:40:38 +08:00
display: flex;
align-items: center;
justify-content: space-between;
2025-12-11 18:36:05 +08:00
// background: #fff;
2026-01-20 17:40:38 +08:00
// background-image: url('../../assets/images/titleGif.gif');
// background-size: cover;
// background-repeat: no-repeat;
// background-position: center;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
2026-01-20 17:40:38 +08:00
background: linear-gradient(90deg, #00d2be 0%, #4eacff 100%);
.app-title {
width: 314px;
height: 50px;
padding-left: 14px;
font-family: Microsoft YaHei, Microsoft YaHei;
font-weight: bold;
font-size: 20px;
color: #fff;
text-align: center;
display: flex;
align-items: center;
}
.menus {
min-width: 998px;
height: 50px;
line-height: 50px;
flex: 1;
display: flex;
align-items: center;
justify-content: space-around;
margin: 0 20px;
.menu-item {
height: 50px;
cursor: pointer;
font-family: Microsoft YaHei, Microsoft YaHei;
font-weight: bold;
font-size: 16px;
color: #ffffff;
text-align: center;
font-style: normal;
text-transform: none;
.menu-btn {
width: 100%;
text-align: center;
}
// 移入下方横条
&:hover {
.tab-item-btn {
margin: 5px auto 0;
width: 15px;
height: 2px;
margin-top: -8px;
background: #fff;
}
}
}
}
2024-06-26 15:11:05 +08:00
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
2024-06-26 15:11:05 +08:00
&:hover {
background: rgba(0, 0, 0, 0.025);
2024-06-26 15:11:05 +08:00
}
}
.breadcrumb-container {
float: left;
}
.topmenu-container {
position: absolute;
left: 50px;
}
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
2025-12-11 18:36:05 +08:00
color: #bfbfbf; /* 纯黑色,比#262626更黑 */
2024-06-26 15:11:05 +08:00
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
2024-06-26 15:11:05 +08:00
&:hover {
background: rgba(0, 0, 0, 0.025);
2024-06-26 15:11:05 +08:00
}
}
}
.avatar-container {
2026-01-20 17:40:38 +08:00
margin-right: 16px;
2024-06-26 15:11:05 +08:00
2025-12-11 18:36:05 +08:00
.user-avatar {
cursor: pointer;
width: 18px;
height: 18px;
vertical-align: text-bottom;
2026-01-20 17:40:38 +08:00
color: #262626;
2025-12-11 18:36:05 +08:00
/* 移除了opacity属性使用color属性实现颜色调整 */
2024-06-26 15:11:05 +08:00
}
2026-01-20 17:40:38 +08:00
.nick-name {
margin: 0 3px;
font-family: Microsoft YaHei, Microsoft YaHei;
font-weight: 400;
font-size: 14px;
color: #ffffff;
text-align: center;
font-style: normal;
text-transform: none;
}
}
}
}
.menu-box {
position: absolute;
z-index: 99999999;
width: 100%;
background: linear-gradient(90deg, #00d2be 0%, #4eacff 100%);
.menu-list {
padding: 16px 20px;
width: 100%;
min-height: 287px;
background: #f8fdfc;
border-radius: 16px;
font-family: Microsoft YaHei, Microsoft YaHei;
font-weight: 400;
font-size: 14px;
color: #2cbab2;
line-height: 30px;
.list-box {
padding: 16px;
width: 290px;
height: 100%;
background: linear-gradient(0deg, rgba(44, 186, 178, 0.1) 0%, rgba(44, 186, 178, 0) 100%);
border-radius: 8px;
2024-06-26 15:11:05 +08:00
}
}
2026-01-20 17:40:38 +08:00
.menu-title-top {
width: 100%;
display: flex;
align-items: center;
}
.menu-title {
width: 100%;
display: flex;
align-items: center;
cursor: pointer;
// 移入效果
&:hover {
background: rgba(44, 186, 178, 0.1);
border-radius: 4px;
}
}
.menu-star {
padding-right: 5px;
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
opacity: 0; // 默认不显示
transition: opacity 0.2s;
}
/* hover 当前行时,显示星标 */
.menu-title:hover .menu-star {
opacity: 1;
}
.title-tip {
margin-right: 4px;
width: 8px;
height: 16px;
background: #2cbab2;
border-radius: 4px 4px 4px 4px;
}
.children-title-tip {
margin-right: 4px;
width: 8px;
height: 8px;
background: #2cbab2;
border-radius: 50%;
}
.child-left {
// 与父节点左边距2个字符
margin-left: 1rem;
}
2024-06-26 15:11:05 +08:00
}
2026-01-20 17:40:38 +08:00
</style>