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

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

毒蘑菇后台管理
因为使用的是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,源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/222868.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++12.1

三种运算符重载&#xff0c;每个至少实现一个运算符的重载 #include <iostream>using namespace std;class Person {friend const Person operator- (const Person &L, const Person &R);friend bool operator<(const Person &L,const Person &R);f…

html刷题笔记

1 em 12 pt 16 px 100% source元素为audio、video、picture元素指定多个媒体文件 margin是用来隔开元素与元素的间距&#xff1b;padding是用来隔开元素与内容的间隔。 margin用于布局分开元素使元素与元素互不相干&#xff1b;padding用于元素与内容之间的间隔&#xff0c;…

vr建筑虚拟实景展厅漫游体验更直观全面

随着科技的不断进步&#xff0c;纯三维、可交互、轻量化的三维线上展览云平台&#xff0c;打破时间界限&#xff0c;以其独特的魅力&#xff0c;给予客户更多的自主性、趣味性和真实性&#xff0c;客户哪怕在天南地北&#xff0c;通过网络、手机即可随时随地参观企业线上立体化…

IDEA切换Python虚拟环境

前言 因为之前一直使用的IDEA开发&#xff0c;换到VSCODE之后各种不习惯&#xff0c;特别是DEBUG的操作&#xff0c;特别难受&#xff0c;因此决心换回IDEA 环境配置 已有项目调整 进入Project 选择SDKs&#xff0c;新建Python 配置Conda以及虚拟环境 有就选择一个虚拟环境…

DOCBOX dynamiccontent.properties.xhtml RCE漏洞复现

0x01 产品简介 DOCBOX是一款具有恢复保证的文档管理系统,高效、快如闪电、防审核、简单,从模拟切换到数字并使用 DOCBOX 归档您的文档。 0x02 漏洞概述 DOCBOX电子文档管理系统4.2.0版本中dynamiccontent.properties.xhtml接口存在远程代码执行漏洞,未经身份认证的攻击者可…

MS8051运算放大器可Pin to Pin兼容AD8051/AD8052/AD8054

MS805x 系列为轨到轨输出的电压反馈运算放大器&#xff0c;具有易用、低成本等特点。可Pin to Pin兼容AD8051/AD8052/AD8054。相比于典型的电流反馈放大器&#xff0c;在带宽和转换率有更大的优势&#xff0c;并同时具备宽的输入共模电压范围和大的输出电压摆幅&#xff0c;这使…

go这么好用的pprof包却引发了安全漏洞?规避pprof自动注册默认url

文章目录 背景介绍问题分析解决方案1.起一个新的http server,不使用默认对象2.修改http默认对象3.既然源码这么坑,把源码捞出来改成自己想要的。背景介绍 最近项目在线上被检测出有pprof有漏洞,原因是产品对外暴露的端口,可以通过http://ip:prot/debug/pprof/profile,获取…

java.lang.UnsupportedOperationException解决方法

问题描述 在实际开发中经常会有类似的这种代码&#xff0c;想要按类的某一个属性对列表中的元素分组。 例如&#xff1a; 有一些学生&#xff0c;然后根绝他们的年龄对他们进行分组。可以写出如下代码。 public class UnsupportedOperationExceptionDemo {DataNoArgsConstru…

vue 过滤器 (filters) ,实际开发中的使用

在实际开发中要判断不同的状态,要写很多重复的代码, 类似这样: 这样学麻烦, 不方便维护, 这时候可以用 filters 过滤器 在页面这样写就可以了

Magna EDI 项目案例

麦格纳Magna是加拿大的一家国际性汽车零配件制造商&#xff0c;主要为乘用车/轻型商用车制造商生产和销售外饰/结构件、动力总成系统、电子系统、座椅系统、底盘、ADAS、闭锁系统、后视镜、车灯、车顶等产品&#xff0c;此外还提供整车工程和代工生产服务。 Magna与其全球合作伙…

周大福传世杰作「裕世钻芳华」首次亮相“超越时光”天然钻石展

&#xff08;2023年12月6日&#xff0c;北京&#xff09;天然钻石&#xff0c;是自地球深处历经数十亿年时光形成的自然奇迹&#xff0c;在悠长的岁月中见证了无数真挚情感的珍贵瞬间。12月6日&#xff0c;“超越时光”周大福天然钻石展于北京凤凰国际传媒中心启幕&#xff0c;…

烟感监控:这个技巧是真的香,后悔没早点知道!

在现代社会&#xff0c;安全意识的提升成为各行各业关注的焦点之一。特别是在建筑、工业和住宅领域&#xff0c;火灾作为一种突发性、破坏性极强的灾害&#xff0c;给人们的生命和财产安全带来了巨大的威胁。 因此&#xff0c;为了有效预防和应对火灾&#xff0c;烟感监控系统逐…

Day51力扣打卡

打卡记录 Plus and Multiply&#xff08;模拟&#xff09; 链接 要满足 a x b ∗ y n a^x b * y n axb∗yn 的关系&#xff0c;可以枚举满足 b ∗ y n − a x b * y n - a ^ x b∗yn−ax 的可余条件。 t int(input()) for _ in range(t):n, a, b map(int, input().…

轮询分区的设置

终于可以写MPI了&#xff0c;没想到&#xff0c;刚开始就当头一棒&#xff0c;我按照之前的配置MPI环境&#xff0c;配置完成就报错 好家伙&#xff0c;仔细检查了每一个步骤都没找到问题&#xff0c;上网搜索了一些解决方案&#xff0c;也没有解决。所幸&#xff0c;在配置MPI…

netty07-粘包半包以及解决方案

粘包指的是发送方在发送数据时&#xff0c;多个数据包被合并成一个大的数据包发送到接收方&#xff0c;接收方在接收时无法准确地区分各个数据包的边界&#xff0c;从而导致数据粘在一起。 半包指的是发送方发送的数据包被拆分成了多个小的数据包&#xff0c;在接收方接收时&a…

C# Onnx 阿里达摩院开源DAMO-YOLO目标检测

效果 模型信息 Inputs ------------------------- name&#xff1a;images tensor&#xff1a;Float[1, 3, 192, 320] --------------------------------------------------------------- Outputs ------------------------- name&#xff1a;output tensor&#xff1a;Float…

批发订货系统一般有哪几种形式

批发订货系统一般有三种方式&#xff1a; 第一种是SaaS&#xff0c;这种方式软件厂商开一个账号&#xff0c;使用的企业仅使用里面的一个账号&#xff0c;给客户进行订货&#xff0c;有的甚至没有独立的小程序&#xff0c;需要进入软件厂商的APP进行订货&#xff0c;这种方法的…

从钓鱼邮件溯源到反制上线

背景 某天下午紧急接到一个溯源的活儿&#xff1a;客户收到一封可疑邮件&#xff0c;要求判断是否为钓鱼邮件&#xff0c;如果是钓鱼邮件&#xff0c;则要求尽可能找到人员信息。由于保密要求&#xff0c;所以部分信息必须厚码&#xff0c;请各位师傅见谅。 邮件内容如下&…

识别和修复网站上损坏链接的最佳实践

如果您有一个网站&#xff0c;我们知道您花了很多时间在它上面&#xff0c;以使其成为最好的资源。如果你的链接不起作用&#xff0c;你的努力可能是徒劳的。您网站上的断开链接可能会以两种方式损害您的业务&#xff1a; 它们对企业来说是可怕的&#xff0c;因为当消费者点击…

​DeepMind:开发出可以向人类学习的人工智能

Nature发表了一篇Google DeepMind的研究成果&#xff1a;研究人员在3D模拟环境中使用神经网络和强化学习&#xff0c;展示了AI智能体如何在没有直接从人类那里获取数据的情况下&#xff0c;通过观察来学习和模仿人类的行为。 这项研究被视为向人工通用智能&#xff08;AGI&…