Vue3入门 - ElementPlus中左侧菜单和Tabs菜单组合联动效果
在Vue3中,ElementPlus是使用比较广泛的UI组件库,提供了丰富的界面元素支持项目开发需求。在后台管理系统中,左侧或顶部的菜单栏通常包含多个子菜单项,通过菜单的展开和收缩功能,用户可以方便地查看或隐藏不需要的菜单项,从而优化界面布局,提供用户体验。
在ElementPlus中,可通过修改属性default-openeds和unique-opened来实现菜单项的展开和收缩.、默认展开等功能;并使用JS数组中reduce方法来完成default-openeds数组读取。
一、界面搭建
Vue3的项目创建之前篇幅已讲过,这里就不再讲Vue3项目创建相关基础内容了。
先在项目中创建一个菜单组件(Navigation.vue),代码如下:
<template>
<el-row>
<el-col :span="5">
<el-menu
:default-active="defaultActive"
:default-openeds="defaultOpeneds"
class="el-menu-vertical-demo">
<el-sub-menu :index="item.id+''" v-for="(item, index) in menuList" :key="item.id">
<template #title>
<span>{{item.name}}</span>
</template>
<el-sub-menu :index="sub.id+''" v-for="(sub, idx) in item.children" :key="sub.id">
<template #title>{{sub.name}}</template>
<el-menu-item :index="third.id+''"
v-for="(third, i) in sub.children"
:key="third.id"
@click="selectItemEvent(third)">
{{third.name}}
</el-menu-item>
</el-sub-menu>
</el-sub-menu>
</el-menu>
</el-col>
<el-col :span="1"> </el-col>
<el-col :span="18">
<el-tabs v-model="defaultActive" class="demo-tabs">
<el-tab-pane :label="item.name" :name="item.id+''" v-for="(item, index) in tabsList" :key="item.id">
{{item.name}}
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
type MenuType = {
id: number
name: string
path?: string
children?: Array<MenuType>
}
const menuList = reactive([
{
"id": 1000,
"name": "商品模块",
"path": "goods/index",
"children": [
{
"id": 10001000,
"name": "基本信息",
"path": "goods/base/index",
"children": [
{
"id": 1000100101,
"name": "年份信息",
"path": "goods/base/year"
},
{
"id": 1000100102,
"name": "颜色信息",
"path": "goods/base/color"
},
{
"id": 1000100103,
"name": "商品信息",
"path": "goods/base/info"
}
]
}
]
},
{
"id": 2000,
"name": "人事信息",
"path": "personnel/index",
"children": [
{
"id": 20001000,
"name": "基本信息",
"path": "personnel/base/index",
"children": [
{
"id": 2000100101,
"name": "部门信息",
"path": "personnel/base/unit"
},
{
"id": 2000100102,
"name": "社保信息",
"path": "personnel/base/social"
},
{
"id": 2000100103,
"name": "人事信息",
"path": "personnel/base/info"
}
]
}
]
},
{
"id": 3000,
"name": "会员信息",
"path": "account/index",
"children": [
{
"id": 30001000,
"name": "基础信息",
"path": "account/base/index",
"children": [
{
"id": 3000100101,
"name": "会员等级",
"path": "account/base/level"
},
{
"id": 3000100102,
"name": "会员信息",
"path": "account/base/info"
}
]
}
]
}
])
const defaultOpeneds = reactive([]) // 默认展开项
const defaultActive = ref('1000100101') // 当前选择菜单的index
const tabsList = reactive([]) // tab菜单数据列表
// 菜单点击事件
const selectItemEvent = (item: MenuType) => {
}
</script>
组件完成后,将其在App.vue中引入注入到html代码中。
<script setup lang="ts">
import { RouterView } from 'vue-router'
import Navigation from '@/components/Navigation.vue'
</script>
<template>
<Navigation></Navigation>
<RouterView />
</template>
二、保持一个子菜单的展开
当将Menu菜单的属性unique-opened设置为true(是否只保持一个子菜单的展开,默认为false),则始终保持一个分类的展开,其他分类会自动收缩的效果。
代码如下:
<template>
<el-row>
<el-col :span="5">
<el-menu
:default-active="defaultActive"
:default-openeds="defaultOpeneds"
:unique-opened="true"
class="el-menu-vertical-demo">
<el-sub-menu :index="item.id+''" v-for="(item, index) in menuList" :key="item.id">
<template #title>
<span>{{item.name}}</span>
</template>
<el-sub-menu :index="sub.id+''" v-for="(sub, idx) in item.children" :key="sub.id">
<template #title>{{sub.name}}</template>
<el-menu-item :index="third.id+''"
v-for="(third, i) in sub.children"
:key="third.id"
@click="selectItemEvent(third)">
{{third.name}}
</el-menu-item>
</el-sub-menu>
</el-sub-menu>
</el-menu>
</el-col>
<el-col :span="1"> </el-col>
<el-col :span="18">
<el-tabs v-model="defaultActive" class="demo-tabs">
<el-tab-pane :label="item.name" :name="item.id+''" v-for="(item, index) in tabsList" :key="item.id">
{{item.name}}
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</template>
三、Tabs菜单追加数据
当点击左侧菜单里,判断右侧tabList是否存在该项,不存在追加即可。并修正当前选中项的index,代码如下:
<script lang="ts" setup>
import { reactive, ref } from 'vue'
type MenuType = {
id: number
name: string
path?: string
children?: Array<MenuType>
}
const menuList = reactive([
// 略....
])
let defaultOpeneds = reactive([]) // 默认展开项
const defaultActive = ref('') // 当前选择菜单的index
const tabsList = reactive([]) // tab菜单数据列表
// 菜单点击事件
const selectItemEvent = (item: MenuType) => {
defaultActive.value = item.id + ''
// tabList中不存在则追加
if(!tabsList.some((sub) => sub.id == item.id)) {
tabsList.push(item)
}
}
</script>
上述代码完成后,点击左侧菜单,则横向tabs菜单就显示出来了。并且则于el-tabs组件上是通过v-model="defaultActive"进行双向数据绑定的,所以tabs组件点击后,左侧菜单也会跟期一起联动显示出对应的菜单项。
三、默认展开菜单项
在系统加载完毕后,可以通过default-openeds属性默认展开某个菜单分类;另外,系统一般会缓存上次菜单选择项的index数据,此时我们就可以读取该数据,通过它向上反查要默认打开菜单分类。
如下图,要是默认展开第一个分类”商品模块“,则default-openeds的值应为['1000', '10001000'];上次打开如是”年份信息“项菜单,如何通过它获取到['1000', '10001000']呢?这个就使用到万能的Array.reduce()方法了,下面将通过它提取出default-openeds的值。
通过当前菜单信息id,反查所有上级id数据;在setup中,增加Recursive()弟归函数,用于反查上级id数组并返回default-openeds属性需要的数组index。代码如下:
<script lang="ts" setup>
import { reactive, ref } from 'vue'
type MenuType = {
id: number
name: string
path?: string
children?: Array<MenuType>
}
const menuList = reactive([
// 略...
])
const defaultOpeneds = ref<string[]>([]) // 默认展开项
const defaultActive = ref('') // 当前选择菜单的index
const tabsList = reactive([]) // tab菜单数据列表
// 递归数据
const Recursive = (menu: Array<MenuType>, key: number) : string[] => {
return menu.reduce((alls: string[], item) => {
// 判断其是否为数组,并且存在子项开始查询
if(Array.isArray(item.children) && item.children.length > 0) {
// 查询是否有符合其id项,有则为本元素id追加进数组中
if(item.children.some(sub => sub.id == key)) {
return alls.concat([item.id+''])
}
// 未查询到,则递归继续查询
else {
const data: string[] = Recursive(item.children, key)
// 递归查询到数组存在,则将本元素id追加进数组,并放在首位
if(data.length>0) return [item.id+''].concat(data)
}
}
return alls
}, [])
}
// 默认展开项(这里1000100101先写,实际项目中在缓存中读取)
defaultOpeneds.value = Recursive(menuList, 1000100101)
// 菜单点击事件
const selectItemEvent = (item: MenuType) => {
// 修正当前选中的index标识(通过vuex或pinia维护该数据时,可将其缓存到本地)
defaultActive.value = item.id + ''
// tabList中不存在则追加
if(!tabsList.some((sub) => sub.id == item.id)) {
tabsList.push(item)
}
}
</script>
此时,则可以通过当前菜单id(即html渲染时的index)向上反查上级菜单的index数据了,如下图:
页面加载完毕后,则会显示默认展开项了,如下图:
当然,这里只是演示demo。项目中需要将菜单数据、tabs菜单数据、默认展开的菜单、默认选中等,写入Vuex或Pinia中,根据自己项目需求进行调整即可。