菜单导航设置支持纯顶部

This commit is contained in:
RuoYi
2025-12-16 11:40:06 +08:00
parent 5579b57bef
commit 49f62e565a
10 changed files with 344 additions and 96 deletions

View File

@ -130,6 +130,18 @@
border-radius: 4px; border-radius: 4px;
} }
/* el menu */
.el-menu-item,
.el-sub-menu {
.svg-icon + span {
margin-left: 5px;
}
}
.el-menu--horizontal .el-menu--popup {
min-width: 120px !important;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.pagination-container .el-pagination > .el-pagination__jump { .pagination-container .el-pagination > .el-pagination__jump {
display: none !important; display: none !important;

View File

@ -61,7 +61,7 @@
} }
.svg-icon { .svg-icon {
margin-right: 16px; margin-right: 10px !important;
} }
.el-menu { .el-menu {

View File

@ -94,7 +94,6 @@ export default {
display: inline-block; display: inline-block;
font-size: 14px; font-size: 14px;
line-height: 50px; line-height: 50px;
margin-left: 8px;
.no-redirect { .no-redirect {
color: #97a8be; color: #97a8be;
cursor: text; cursor: text;

View File

@ -162,7 +162,7 @@ export default {
this.$store.dispatch('app/toggleSideBarHide', true) this.$store.dispatch('app/toggleSideBarHide', true)
} }
} }
}, }
} }
</script> </script>
@ -171,7 +171,7 @@ export default {
float: left; float: left;
height: 50px !important; height: 50px !important;
line-height: 50px !important; line-height: 50px !important;
color: #999093 !important; color: #303133 !important;
padding: 0 5px !important; padding: 0 5px !important;
margin: 0 10px !important; margin: 0 10px !important;
} }
@ -186,7 +186,7 @@ export default {
float: left; float: left;
height: 50px !important; height: 50px !important;
line-height: 50px !important; line-height: 50px !important;
color: #999093 !important; color: #303133 !important;
padding: 0 5px !important; padding: 0 5px !important;
margin: 0 10px !important; margin: 0 10px !important;
} }

View File

@ -1,10 +1,13 @@
<template> <template>
<div class="navbar"> <div class="navbar" :class="'nav' + navType">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb v-if="!topNav" id="breadcrumb-container" class="breadcrumb-container" /> <breadcrumb v-if="navType == 1" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="topNav" id="topmenu-container" class="topmenu-container" /> <top-nav v-if="navType == 2" id="topmenu-container" class="topmenu-container" />
<template v-if="navType == 3">
<logo v-show="showLogo" :collapse="false"></logo>
<top-bar id="topbar-container" class="topbar-container" />
</template>
<div class="right-menu"> <div class="right-menu">
<template v-if="device!=='mobile'"> <template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" /> <search id="header-search" class="right-menu-item" />
@ -50,6 +53,8 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb' import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav' import TopNav from '@/components/TopNav'
import TopBar from './TopBar'
import Logo from './Sidebar/Logo'
import Hamburger from '@/components/Hamburger' import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull' import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect' import SizeSelect from '@/components/SizeSelect'
@ -61,7 +66,9 @@ export default {
emits: ['setLayout'], emits: ['setLayout'],
components: { components: {
Breadcrumb, Breadcrumb,
Logo,
TopNav, TopNav,
TopBar,
Hamburger, Hamburger,
Screenfull, Screenfull,
SizeSelect, SizeSelect,
@ -81,9 +88,14 @@ export default {
return this.$store.state.settings.showSettings return this.$store.state.settings.showSettings
} }
}, },
topNav: { navType: {
get() { get() {
return this.$store.state.settings.topNav return this.$store.state.settings.navType
}
},
showLogo: {
get() {
return this.$store.state.settings.sidebarLogo
} }
} }
}, },
@ -110,20 +122,33 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.navbar.nav3 {
.hamburger-container {
display: none !important;
}
}
.navbar { .navbar {
height: 50px; height: 50px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: #fff; background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08); box-shadow: 0 1px 4px rgba(0,21,41,.08);
display: flex;
align-items: center;
// padding: 0 8px;
box-sizing: border-box;
.hamburger-container { .hamburger-container {
line-height: 46px; line-height: 46px;
height: 100%; height: 100%;
float: left;
cursor: pointer; cursor: pointer;
transition: background .3s; transition: background .3s;
-webkit-tap-highlight-color:transparent; -webkit-tap-highlight-color:transparent;
display: flex;
align-items: center;
flex-shrink: 0;
margin-right: 8px;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, .025)
@ -131,7 +156,7 @@ export default {
} }
.breadcrumb-container { .breadcrumb-container {
float: left; flex-shrink: 0;
} }
.topmenu-container { .topmenu-container {
@ -139,15 +164,26 @@ export default {
left: 50px; left: 50px;
} }
.topbar-container {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
overflow: hidden;
margin-left: 8px;
}
.errLog-container { .errLog-container {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
} }
.right-menu { .right-menu {
float: right;
height: 100%; height: 100%;
line-height: 50px; line-height: 50px;
display: flex;
align-items: center;
margin-left: auto;
&:focus { &:focus {
outline: none; outline: none;

View File

@ -3,6 +3,27 @@
<div class="drawer-container"> <div class="drawer-container">
<div> <div>
<div class="setting-drawer-content"> <div class="setting-drawer-content">
<div class="setting-drawer-title">
<h3 class="drawer-title">菜单导航设置</h3>
</div>
<div class="nav-wrap">
<el-tooltip content="左侧菜单" placement="bottom">
<div class="item left" @click="handleNavType(1)" :style="{'--theme': theme}" :class="{ activeItem: navType == 1 }">
<b></b><b></b>
</div>
</el-tooltip>
<el-tooltip content="混合菜单" placement="bottom">
<div class="item mix" @click="handleNavType(2)" :style="{'--theme': theme}" :class="{ activeItem: navType == 2 }">
<b></b><b></b>
</div>
</el-tooltip>
<el-tooltip content="顶部菜单" placement="bottom">
<div class="item top" @click="handleNavType(3)" :style="{'--theme': theme}" :class="{ activeItem: navType == 3 }">
<b></b><b></b>
</div>
</el-tooltip>
</div>
<div class="setting-drawer-title"> <div class="setting-drawer-title">
<h3 class="drawer-title">主题风格设置</h3> <h3 class="drawer-title">主题风格设置</h3>
</div> </div>
@ -39,11 +60,6 @@
<h3 class="drawer-title">系统布局配置</h3> <h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>开启 TopNav</span>
<el-switch v-model="topNav" class="drawer-switch" />
</div>
<div class="drawer-item"> <div class="drawer-item">
<span>开启 Tags-Views</span> <span>开启 Tags-Views</span>
<el-switch v-model="tagsView" class="drawer-switch" /> <el-switch v-model="tagsView" class="drawer-switch" />
@ -93,6 +109,7 @@ export default {
return { return {
theme: this.$store.state.settings.theme, theme: this.$store.state.settings.theme,
sideTheme: this.$store.state.settings.sideTheme, sideTheme: this.$store.state.settings.sideTheme,
navType: this.$store.state.settings.navType,
showSettings: false showSettings: false
} }
}, },
@ -108,21 +125,6 @@ export default {
}) })
} }
}, },
topNav: {
get() {
return this.$store.state.settings.topNav
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'topNav',
value: val
})
if (!val) {
this.$store.dispatch('app/toggleSideBarHide', false)
this.$store.commit("SET_SIDEBAR_ROUTERS", this.$store.state.permission.defaultRoutes)
}
}
},
tagsView: { tagsView: {
get() { get() {
return this.$store.state.settings.tagsView return this.$store.state.settings.tagsView
@ -180,6 +182,25 @@ export default {
} }
} }
}, },
watch: {
navType: {
handler(val) {
if (val == 1) {
this.$store.dispatch("app/toggleSideBarHide", false)
}
if (val == 2) {
}
if (val == 3) {
this.$store.dispatch("app/toggleSideBarHide", true)
}
if ([1, 3].includes(val)) {
this.$store.commit("SET_SIDEBAR_ROUTERS",this.$store.state.permission.defaultRoutes)
}
},
immediate: true,
deep: true
}
},
methods: { methods: {
themeChange(val) { themeChange(val) {
this.$store.dispatch('settings/changeSetting', { this.$store.dispatch('settings/changeSetting', {
@ -195,6 +216,13 @@ export default {
}) })
this.sideTheme = val this.sideTheme = val
}, },
handleNavType(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'navType',
value: val
})
this.navType = val
},
openSetting() { openSetting() {
this.showSettings = true this.showSettings = true
}, },
@ -206,7 +234,7 @@ export default {
this.$cache.local.set( this.$cache.local.set(
"layout-setting", "layout-setting",
`{ `{
"topNav":${this.topNav}, "navType":${this.navType},
"tagsView":${this.tagsView}, "tagsView":${this.tagsView},
"tagsIcon":${this.tagsIcon}, "tagsIcon":${this.tagsIcon},
"fixedHeader":${this.fixedHeader}, "fixedHeader":${this.fixedHeader},
@ -229,7 +257,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.setting-drawer-content { .setting-drawer-content {
.setting-drawer-title { .setting-drawer-title {
margin-bottom: 12px; margin-bottom: 12px;
color: rgba(0, 0, 0, .85); color: rgba(0, 0, 0, .85);
@ -270,9 +298,9 @@ export default {
} }
} }
} }
} }
.drawer-container { .drawer-container {
padding: 20px; padding: 20px;
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
@ -294,5 +322,68 @@ export default {
.drawer-switch { .drawer-switch {
float: right float: right
} }
}
// 导航模式
.nav-wrap {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 10px;
margin-bottom: 20px;
.activeItem {
border: 2px solid #{'var(--theme)'} !important;
} }
.item {
position: relative;
margin-right: 16px;
cursor: pointer;
width: 56px;
height: 48px;
border-radius: 4px;
background: #f0f2f5;
border: 2px solid transparent;
}
.left {
b:first-child {
display: block;
height: 30%;
background: #fff;
}
b:last-child {
width: 30%;
background: #1b2a47;
position: absolute;
height: 100%;
top: 0;
border-radius: 4px 0 0 4px;
}
}
.mix {
b:first-child {
border-radius: 4px 4px 0 0;
display: block;
height: 30%;
background: #1b2a47;
}
b:last-child {
width: 30%;
background: #1b2a47;
position: absolute;
height: 70%;
border-radius: 0 0 0 4px;
}
}
.top {
b:first-child {
display: block;
height: 30%;
background: #1b2a47;
border-radius: 4px 4px 0 0;
}
}
}
</style> </style>

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"> <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' && navType !== 3 ? variables.menuBackground : variables.menuLightBackground }">
<transition name="sidebarLogoFade"> <transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/"> <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" /> <img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1> <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' && navType !== 3 ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
</router-link> </router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/"> <router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" /> <img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1> <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' && navType !== 3 ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
</router-link> </router-link>
</transition> </transition>
</div> </div>
@ -31,6 +31,9 @@ export default {
}, },
sideTheme() { sideTheme() {
return this.$store.state.settings.sideTheme return this.$store.state.settings.sideTheme
},
navType() {
return this.$store.state.settings.navType
} }
}, },
data() { data() {
@ -54,7 +57,6 @@ export default {
.sidebar-logo-container { .sidebar-logo-container {
position: relative; position: relative;
width: 100%;
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;
background: #2b2f3a; background: #2b2f3a;

View File

@ -0,0 +1,108 @@
<template>
<el-menu class="topbar-menu" :default-active="activeMenu" :active-text-color="theme" mode="horizontal">
<sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" />
<el-submenu index="more" class="el-submenu__hide-arrow" v-if="moreRoutes.length > 0">
<template slot="title">更多菜单</template>
<sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" />
</el-submenu>
</el-menu>
</template>
<script>
import SidebarItem from '../Sidebar/SidebarItem'
export default {
components: { SidebarItem },
data() {
return {
// 顶部栏初始数
visibleNumber: 5
}
},
computed: {
theme() {
return this.$store.state.settings.theme
},
topMenus() {
return this.$store.state.permission.sidebarRouters.filter((f) => !f.hidden).slice(0, this.visibleNumber)
},
moreRoutes() {
const sidebarRouters = this.$store.state.permission.sidebarRouters;
return sidebarRouters.filter((f) => !f.hidden).slice(this.visibleNumber, sidebarRouters.length - this.visibleNumber)
},
// 默认激活的菜单
activeMenu() {
const { meta, path } = this.$route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
},
beforeMount() {
window.addEventListener('resize', this.setVisibleNumber)
},
beforeDestroy() {
window.removeEventListener('resize', this.setVisibleNumber)
},
mounted() {
this.setVisibleNumber()
},
methods: {
// 根据宽度计算设置显示栏数
setVisibleNumber() {
const width = document.body.getBoundingClientRect().width / 3
this.visibleNumber = parseInt(width / 85)
}
}
}
</script>
<style lang="scss">
.topbar-menu.el-menu--horizontal > .el-menu-item {
float: left;
height: 50px !important;
line-height: 50px !important;
color: #303133 !important;
padding: 0 5px !important;
margin: 0 10px !important;
}
.el-menu-item.is-active .svg-icon + span, .el-submenu.is-active .svg-icon + span{
color: v-bind(theme);
}
.el-menu--horizontal .el-menu .el-menu-item, .el-menu--horizontal .el-menu .el-submenu__title {
color: #303133 !important;
}
/* submenu item */
.topbar-menu.el-menu--horizontal > .el-submenu .el-submenu__title {
float: left;
height: 50px !important;
line-height: 50px !important;
color: #303133 !important;
padding: 0 5px !important;
margin: 0 10px !important;
}
/* 图标右间距 */
.topbar-menu .svg-icon {
margin-right: 4px;
}
/* topbar more arrow */
.topbar-menu .el-submenu .el-submenu__icon-arrow {
position: static;
vertical-align: middle;
margin-left: 8px;
margin-top: 0px;
}
/* menu__title el-menu-item */
.topbar-menu.el-menu--horizontal .el-submenu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
height: 55px;
padding: 0 15px;
}
</style>

View File

@ -15,9 +15,9 @@ module.exports = {
showSettings: true, showSettings: true,
/** /**
* 是否显示顶部导航 * 菜单导航模式 1、纯左侧 2、混合左侧+顶部) 3、纯顶部
*/ */
topNav: false, navType: 1,
/** /**
* 是否显示 tagsView * 是否显示 tagsView

View File

@ -1,7 +1,7 @@
import defaultSettings from '@/settings' import defaultSettings from '@/settings'
import { useDynamicTitle } from '@/utils/dynamicTitle' import { useDynamicTitle } from '@/utils/dynamicTitle'
const { sideTheme, showSettings, topNav, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings const { sideTheme, showSettings, navType, tagsView, tagsIcon, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '' const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
const state = { const state = {
@ -9,7 +9,7 @@ const state = {
theme: storageSetting.theme || '#409EFF', theme: storageSetting.theme || '#409EFF',
sideTheme: storageSetting.sideTheme || sideTheme, sideTheme: storageSetting.sideTheme || sideTheme,
showSettings: showSettings, showSettings: showSettings,
topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, navType: storageSetting.navType === undefined ? navType : storageSetting.navType,
tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon, tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,