一个完整的后台管理系统需要一个树形结构的目录,方便用户切换页面。
因为使用的是element-plus的ui库,所以首选el-menu组件,点击查看文档。
因为此组件不是树形结构的,所以需要封装成系统需要的树形结构组件。可以使用vue的递归组件。
menu.vue代码如下
<template>
<div class="menu-container">
<el-scrollbar
height="100%">
<el-menu
ref="ElMenuRef"
:collapse="false"
:default-active="route.path"
:router="false">
<MenuItem
v-for="item,index in dataContainer.dataList"
:key="item.path"
:dataInfo="item"></MenuItem>
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import {
defineComponent,
ref,
reactive,
computed,
onMounted,
watch,
toRef,
onUnmounted,
} from 'vue';
import SvgIcon from "@/components/svgIcon/index.vue";
import { useRouter,useRoute } from "vue-router";
import MenuItem from "./MenuItem.vue";
export default {
name: 'Menu',
components: {
SvgIcon,
MenuItem,
},
props:{
/** 所显示的数据列表 */
dataList:{
type:Array,
default:()=>{
return [];
},
},
},
setup(props) {
const router = useRouter();
const route = useRoute();
const ElMenuRef = ref(null);
const dataContainer = reactive({
dataList:toRef(props,'dataList'),
});
/**
* 当页面加载后设置设置当前打开的父节点,因为如果父节点可点击的话不会自动打开
* */
onMounted(()=>{
if(!ElMenuRef.value) return;
let el = ElMenuRef.value.$el;
let hasActiveSubEl = el.querySelector('.el-sub-menu .is-sub-defin-active');
if(!hasActiveSubEl) return;
ElMenuRef.value.open(route.path);
});
return {
dataContainer,
route,
ElMenuRef,
};
},
};
</script>
<style scoped lang="scss">
.menu-container {
height: 100%;
width: 100%;
/** 基础目录配置 */
--local-active-text-color:#ffffff;
--local-active-bg-color:#5240ff96;
--local-active-sub-bg-color:#3634ac57;
--local-hover-color:#3634ac57;
--local-font-size:15px;
--local-text-color:#b6cce2;
--local-box-shadow: 0 1px 4px #001247;
--local-border-radius:8px;
:deep(.el-menu){
border:none !important;
--el-menu-active-color:var(--local-active-text-color) !important;
--el-menu-item-font-size:var(--local-font-size) !important;
--el-menu-text-color:var(--local-text-color) !important;
--el-menu-hover-bg-color:var(--local-hover-color) !important;
--active-item-bg-color:var(--local-active-bg-color) !important;
--el-menu-bg-color:transparent !important;
--el-menu-base-level-padding:15px !important;
--el-menu-level-padding:20px !important;
--el-menu-icon-width:calc(15px + 0) !important;
--el-menu-item-height:55px !important;
--el-menu-sub-item-height:55px !important;
--active-sub-bg-color:transparent !important;
padding: 10px;
box-sizing: border-box;
.el-sub-menu__icon-arrow{
margin-top: 0 !important;
top:initial !important;
}
.el-menu{
padding: 0;
}
.el-sub-menu{
>.el-sub-menu__title{
border-radius: var(--local-border-radius);
}
&.is-active{
background-color: var(--active-sub-bg-color);
>.el-sub-menu__title{
background-color: var(--local-active-sub-bg-color);
}
}
.el-sub-menu__icon-arrow{
font-size: 17px !important;
}
&.is-sub-defin-active{
>.el-sub-menu__title{
background-color: var(--active-item-bg-color);
// font-weight: bold;
color: var(--el-menu-active-color);
box-shadow: var(--local-box-shadow);
}
}
/** 表示有已经活动的sub目录 */
&:has(.is-sub-defin-active){
background-color: var(--active-sub-bg-color);
>.el-sub-menu__title{
background-color: var(--local-active-sub-bg-color);
}
}
}
.el-menu-item{
border-radius: var(--local-border-radius);
&.is-active{
background-color: var(--active-item-bg-color);
// font-weight: bold;
box-shadow: var(--local-box-shadow);
}
}
}
:deep(.el-scrollbar){
.el-scrollbar__bar{
.el-scrollbar__thumb{
background-color: rgba(194, 194, 194, 0.51) !important;
}
}
}
}
</style>
其中子组件MenuItem.vue代码如下
<template>
<div class="menu-item-container">
<!-- 没有子目录的 -->
<el-menu-item
v-if="!dataContainer.dataInfo.childs || dataContainer.dataInfo.childs.length==0"
:index="dataContainer.dataInfo.path"
:class="{
'is-active':dataContainer.dataInfo.path==route.path,
}"
@click="handleClick(dataContainer.dataInfo)">
<div class="item-target">
<SvgIcon
v-if="dataContainer.dataInfo.iconName"
:style="'width: 17px;min-width:17px;height: 17px;'"
:name="dataContainer.dataInfo.iconName"></SvgIcon>
{{dataContainer.dataInfo.title}}
<div
v-if="dataContainer.dataInfo.content"
class="content">
{{dataContainer.dataInfo.content}}
</div>
<div
v-if="dataContainer.dataInfo.number"
class="sign">
{{dataContainer.dataInfo.number}}
</div>
</div>
</el-menu-item>
<!-- 有子目录且父节点可点击 -->
<el-sub-menu
v-else-if="dataContainer.dataInfo.path"
:class="{
'is-sub-defin-active':route.path==dataContainer.dataInfo.path,
}"
:index="dataContainer.dataInfo.path">
<template #title>
<div
class="item-target"
@click.stop="handleClick(dataContainer.dataInfo)">
<SvgIcon
v-if="dataContainer.dataInfo.iconName"
:style="'width: 17px;min-width:17px;height: 17px;'"
:name="dataContainer.dataInfo.iconName"></SvgIcon>
{{dataContainer.dataInfo.title}}
<div
v-if="dataContainer.dataInfo.content"
class="content">
{{dataContainer.dataInfo.content}}
</div>
<div
v-if="dataContainer.dataInfo.number"
class="sign">
{{dataContainer.dataInfo.number}}
</div>
</div>
</template>
<MenuItem
v-for="item,index in dataContainer.dataInfo.childs"
:key="item.path"
:dataInfo="item"></MenuItem>
</el-sub-menu>
<!-- 有子目录且父节点不可点击 -->
<el-sub-menu
v-else
:index="dataContainer.dataInfo.sign">
<template #title>
<div class="item-target">
<SvgIcon
v-if="dataContainer.dataInfo.iconName"
:style="'width: 17px;min-width:17px;height: 17px;'"
:name="dataContainer.dataInfo.iconName"></SvgIcon>
{{dataContainer.dataInfo.title}}
<div
v-if="dataContainer.dataInfo.content"
class="content">
{{dataContainer.dataInfo.content}}
</div>
<div
v-if="dataContainer.dataInfo.number"
class="sign">
{{dataContainer.dataInfo.number}}
</div>
</div>
</template>
<MenuItem
v-for="item,index in dataContainer.dataInfo.childs"
:key="item.path"
:dataInfo="item"></MenuItem>
</el-sub-menu>
</div>
</template>
<script>
import {
defineComponent,
ref,
reactive,
computed,
onMounted,
watch,
toRef,
onUnmounted,
} from 'vue';
import SvgIcon from "@/components/svgIcon/index.vue";
import { useRouter,useRoute } from "vue-router";
export default {
name: 'MenuItem',
components: {
SvgIcon,
},
props:{
/** 所显示的数据列表 */
dataInfo:{
type:Object,
default:()=>{
return {};
},
},
},
setup(props) {
const router = useRouter();
const route = useRoute();
const dataContainer = reactive({
dataInfo:toRef(props,'dataInfo'),
});
/** 跳转相应链接 */
function handleClick(params){
if(!params.path) return;
/** 如果是一个链接的话直接跳转 */
if(params.isLink){
window.open(params.path);
}else{
router.push(params.path);
}
}
return {
dataContainer,
handleClick,
route,
};
},
};
</script>
<style scoped lang="scss">
.menu-item-container {
height: fit-content;
width: 100%;
:deep(.item-target){
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
position: relative;
>*{
margin-right: 10px;
}
>.sign{
right: 0;
position: absolute;
width: fit-content;
background-color: #ffe4e4;
color: #f56c6c;
border-radius: 999px;
padding: 5px 10px;
box-sizing: border-box;
line-height: 1;
font-size: 12px;
margin: 0;
font-weight: bold;
}
>.content{
font-size: 12px;
margin-left: 5px;
opacity: 0.8;
font-weight: 400 !important;
}
}
}
</style>
这样就可以实现一个树形结构的系统目录了。
DEMO,源码