RuoYi-Vue3若依微服务框架+element plus 左侧菜单改写

硬老板要求非要将菜单改成左右两侧垂直显示,找了一圈市场上没有合适的,只能在现有的框架上进行调整,现调整后如下图:

 

在原有的框架组件路由下新建一个组件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'

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值