硬老板要求非要将菜单改成左右两侧垂直显示,找了一圈市场上没有合适的,只能在现有的框架上进行调整,现调整后如下图:
在原有的框架组件路由下新建一个组件SidebarItemNew
<!-- html -->
<template>
<div class="menu-container">
<!-- 左侧一级菜单 -->
<el-menu
class="left-menu"
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="getMenuBackground"
:text-color="getMenuTextColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="vertical"
:class="sideTheme"
:style="{borderRight: toggleSideBarType ? '1px solid #e6e6e6' : 'none'}"
@select="handlePrimarySelects_">
<div v-for="subItem in primaryMenu" :key="subItem.path" :index="subItem.path" v-if="toggleSideBarType">
<el-menu-item v-if="subItem.meta" :key="subItem.path" :index="resolvePath(subItem.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="subItem.meta.icon || (subItem.meta && subItem.meta.icon)"/>
<template #title><span class="menu-title" :title="hasTitle(subItem.meta.title)">{{ subItem.meta.title }}</span></template>
</el-menu-item>
</div>
<div v-else v-for="subItem in primaryMenu" :key="subItem.path" :index="subItem.path" >
<el-sub-menu ref="subMenu" :index="resolvePath(subItem.path)" :key="subItem.path"
@mouseenter="handleSubMenuEnter(subItem)" v-if="subItem.meta"
@click="handleSubMenuClick(subItem)" teleported >
<template #title>
<svg-icon :icon-class="subItem.meta && subItem.meta.icon" />
<span class="menu-title" :title="hasTitle(subItem.meta.title)">{{ subItem.meta.title }}</span>
</template>
<sidebar-item
v-for="(child, index) in subItem.children"
:key="child.path + index"
:is-nest="true"
:item="child"
:base-path="resolvePath(subItem.path + '/' + child.path)"
class="nest-menu"
/>
</el-sub-menu>
</div>
</el-menu>
<!-- 右侧二级菜单 -->
<el-menu class="right-menu" mode="vertical" :default-active="activeSecondary" v-if="toggleSideBarType">
<div v-for="(subItem , index) in currentSecondary" :key="subItem.path" :base-path="resolvePath(subItem.path)">
<div v-if="subItem.meta && !subItem.hidden">
<!-- 二级菜单项 -->
<app-link v-if="!subItem.children || subItem.children.length === 0" :to="resolvePath(subItem.parentPath + subItem.path, subItem.query)">
<el-menu-item :index="subItem.path" :class="{ 'submenu-title-noDropdown': !isNest }" @click="handleSecondarySelect(subItem)">
<svg-icon :icon-class="subItem.meta && subItem.meta.icon" />
<template #title><span class="menu-title" :title="hasTitle(subItem.meta.title)">{{ subItem.meta.title }}</span></template>
</el-menu-item>
</app-link>
<!-- 三级菜单 -->
<el-sub-menu v-else :index="subItem.path">
<template #title>
<svg-icon :icon-class="subItem.meta && subItem.meta.icon" />
<span class="menu-title">{{ subItem.meta.title }}</span>
</template>
<el-menu-item
v-for="child in subItem.children"
:key="child.path"
:index="child.path"
@click="handleThirdSelect(subItem.parentPath + subItem.path + '/' + child.path)"
style="display: flow;">
<svg-icon :icon-class="child.meta && child.meta.icon" />
<template #title>{{ child.meta.title }}</template>
</el-menu-item>
</el-sub-menu>
</div>
</div>
</el-menu>
</div>
</template>
// js部分
<script setup>
import SidebarItem from './SidebarItem'
import {
computed,
onMounted,
onUnmounted,
ref, watch
} from 'vue'
import { useRoute, useRouter } from 'vue-router'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import variables from '@/assets/styles/variables.module.scss'
import usePermissionStore from '@/store/modules/permission'
const route = useRoute()
const router = useRouter()
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const theme = computed(() => settingsStore.theme)
const permissionStore = usePermissionStore()
const toggleSideBarType = computed(() => appStore.sidebar.opened)
const primaryMenu = computed(() => permissionStore.sidebarRouters)
const childsRouters = computed(() => appStore.dataRouters)
const isCollapse = computed(() => !appStore.sidebar.opened)
const sideTheme = computed(() => settingsStore.sideTheme)
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/ruoyi'
const onlyOneChild = ref({})
const activePrimary = ref(''); // 默认激活第一个一级菜单
const activeSecondary = ref(''); // 当前激活的二级菜单
const currentSecondary = ref(); // 初始二级菜单数据
const props = defineProps({
// primaryMenu:{
// type:Array,
// default:[]
// },
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
})
function getRouterLoaction(){
let arr = []
let defaultChilds = []
currentSecondary.value = []
permissionStore.sidebarRouters.map(item =>{
if(item.children){
item.children.map(obj =>{
if(!obj.hidden){
arr.push(obj)
}
})
}
if(item.children && !item.hidden){
defaultChilds.push(item)
}
})
currentSecondary.value = defaultChilds[0].children
currentSecondary.value.map(item =>item.parentPath = defaultChilds[0].path + '/')
activeSecondary.value = currentSecondary.value[0].path // 默认第一个高亮
hasOneShowingChild(arr, permissionStore.sidebarRouters)
}
onMounted(() =>{
if(childsRouters.value == null) return getRouterLoaction()
currentSecondary.value = childsRouters.value
activeSecondary.value = ''
currentSecondary.value.map(item =>{
if(item.selectPath){
activeSecondary.value = item.selectPath
}
})
hasOneShowingChild(childsRouters.value)
})
onUnmounted(() =>{
window.addEventListener('beforeunload', (event) => {
// 窗口关闭时清除本地路由数据
localStorage.removeItem('dataRouters')
})
})
// 获取菜单背景色
const getMenuBackground = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-bg)';
}
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg;
})
// 获取菜单文字颜色
const getMenuTextColor = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-text)';
}
return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText;
})
const activeMenu = computed(() => {
const { meta, path } = route;
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
})
function handlePrimarySelects_ (index) {
const primaryItem = permissionStore.sidebarRouters.find(item => item.path === index)
if(primaryItem.meta.title == '官网') return window.open(primaryItem.path, '_blank')
handlePrimarySelect(index)
}
// 处理一级菜单选中
const handlePrimarySelect = (index) => {
currentSecondary.value = []
activePrimary.value = index
const primaryItem = permissionStore.sidebarRouters.find(item => item.path === index)
currentSecondary.value = primaryItem ? primaryItem.children : []
if(currentSecondary.value){
currentSecondary.value.map(item =>item.parentPath = index + '/')
appStore.refachRouter(currentSecondary.value)
}
activeSecondary.value = '' // 重置二级选中状态
}
// 处理二级菜单点击
const handleSecondarySelect = (subItem) => {
activeSecondary.value = subItem.path
if(currentSecondary.value && currentSecondary.value.length > 0){
currentSecondary.value.map(item =>{
if(subItem.path === item.path){
item.selectPath = subItem.path
}
})
appStore.refachRouter(currentSecondary.value)
}
}
function hasOneShowingChild(children = [], parent) {
if (!children) {
children = [];
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
}
onlyOneChild.value = item
return true
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
};
function resolvePath(routePath, routeQuery) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(props.basePath)) {
return props.basePath
}
if (routeQuery) {
let query = JSON.parse(routeQuery);
return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
}
// console.log(props.basePath + '/' + routePath)
return getNormalPath(props.basePath + '/' + routePath)
}
function hasTitle(title){
if (title.length > 5) {
return title;
} else {
return "";
}
}
const handleSubMenuEnter = (subItem) => {
handlePrimarySelect(subItem.path)
}
const handleSubMenuClick = (subItem) => {
if( subItem.meta.title == '官网') return window.open( subItem.meta.link, '_blank')
}
const handleThirdSelect = (path) => {
const resolvedPath = resolvePath(path)
if (typeof resolvedPath === 'string' && isExternal(resolvedPath)) {
window.open(resolvedPath, '_blank')
}
else if (typeof resolvedPath === 'object') {
router.push(resolvedPath)
}
else {
router.push(resolvedPath)
}
}
</script>
// css
<style scoped>
.menu-container {
display: flex;
height: 100vh;
/* 根据实际容器高度调整 */
}
.el-menu {
border-right: none;
width: 100%;
}
.left-menu {
width: 56px;
border-right: 1px solid #e6e6e6;
}
:deep(.el-menu-item){
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
:deep(.el-menu){
/* width: 60px !important; */
}
:deep(.submenu-title-noDropdown){
/* display: flow; */
margin-top: 10px;
.svg-icon{
margin-right: 0px !important;
}
}
:deep(.right-menu){
.submenu-title-noDropdown{
display: flow;
.svg-icon{
margin-right: 10px !important;
}
}
}
</style>
最后修改父组件内容,将原有的html隐藏或删除,如下图
引入关键组件
<SidebarItemNew></SidebarItemNew>
import SidebarItemNew from './SidebarItemNew'