我一人全干!之二,vue3后台管理系统树形目录的实现。

一个完整的后台管理系统需要一个树形结构的目录,方便用户切换页面。

我一人全干!之二,vue3后台管理系统树形目录的实现。_第1张图片
因为使用的是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,源码

你可能感兴趣的:(前端,vue,毒蘑菇后台管理,javascript,vue.js,前端)