使用element-ui制作侧边导航多级菜单

这两天对树形结构菜单感兴趣,弄了一个tree 的树形菜单。总体还是不错的,感谢el-ui 提供的支持与便捷。

在这里插入图片描述

使用递归关键字查找操作数据,在一个功能上加入lodash查找索引,支持标签页拖拽及清除。如果代码不能使用,仅尽参考。
为了代码整体结构清晰度,将代码都缩进了,不合适请自行格式化。
<template>
  <el-scrollbar class="Container">
    <el-menu :default-active="activePath" class="el-menu-vertical-demo" router unique-opened :collapse-transition="false" background-color="#333744" :collapse="Collapse" text-color="#fff" active-text-color="#409bff" @select="handleSelect">
      <!-- 1级菜单,if 判断两类型 -->
      <div v-for="item in menulist" :key="this">
      <el-menu-item :index="'/' + item.path" v-if="!item.children" ><i :class="item.icon"></i><span slot="title">{{item.name}}</span></el-menu-item><!-- 无子级渲染文本 -->
      <el-submenu v-if="item.children" :index="item.path"><!-- 有子级渲染菜单 -->
        <i :class="item.icon" slot="title"></i><span :slot="!Collapse ? 'title': ''">{{item.name}}</span>
        <!-- 二级菜单 if 判断两类型 -->
        <div v-for="leveltwo in item.children">
          <el-menu-item :index="item.path +'/'+ leveltwo.path" v-if="!leveltwo.children"><i :class="leveltwo.icon"></i><span slot="title">{{leveltwo.name}}</span></el-menu-item><!-- 无子级渲染文本 -->
          <el-submenu v-if="leveltwo.children" :index="item.path + leveltwo.path"><!-- 有子级渲染菜单 -->
            <template slot="title"><i :class="leveltwo.icon"></i><span>{{leveltwo.name}}</span></template>
            <!-- 三级菜单 if 渲染 判断文件类型 -->
            <div v-for="levelthree in leveltwo.children"><!-- 无子级渲染文本 -->
              <el-menu-item :index="leveltwo.path +'/'+ levelthree.path" v-if="!levelthree.children"><i :class="levelthree.icon"></i><span slot="title">{{levelthree.name}}</span></el-menu-item>
            </div>
          </el-submenu>
        </div>
      </el-submenu>
      </div>
    </el-menu>
  </el-scrollbar>
</template>
<script>
  var menu2 = [
  { id: 1, name: "首页",     pid: 0, icon: 'el-icon-s-home', path: 'home' },
  { id: 2, name: "内容创作", pid: 0, icon: 'el-icon-edit-outline', path: 'producectxs' },
    { id: 20, name: "Map",   pid: 2, icon: 'el-icon-document', path: 'amap' },
    { id: 21, name: "发表博文",   pid: 2, icon: 'el-icon-document', path: 'publish_article' },
    { id: 22, name: "上传资源",   pid: 2, icon: 'el-icon-document', path:'upfile',},
    { id: 23, name: "上传视频",   pid: 2, icon: 'el-icon-document', path: 'upvideo' },
    { id: 24, name: "发布问题",   pid: 2, icon: 'el-icon-document', path: 'issue_questions' },
  { id: 3, name: "内容管理", pid: 0, icon: 'el-icon-document', path: 'managectxs' },
    { id: 31, name: "文章管理",   pid: 3, icon: 'el-icon-document' },
    { id: 32, name: "资源管理",   pid: 3, icon: 'el-icon-document' },
    { id: 33, name: "评论管理",   pid: 3, icon: 'el-icon-document' },
    { id: 34, name: "分类专栏",   pid: 3, icon: 'el-icon-document' },
    { id: 35, name: "视频管理",   pid: 3, icon: 'el-icon-document' },
    { id: 36, name: "自定义模块", pid: 3, icon: 'el-icon-document' },
    { id: 37, name: "订阅专栏",   pid: 3, icon: 'el-icon-document' },
    { id: 38, name: "问答管理",   pid: 3, icon: 'el-icon-document' },
  { id: 4, name: "数据观星", pid: 0, icon: 'el-icon-pie-chart', path: 'datestargazers' },
    { id: 41, name: "博文数据", pid: 4, icon: 'el-icon-document' },
      { id: 411, name: "博文数据2", pid: 41, icon: 'el-icon-document' },
    { id: 42, name: "下载数据", pid: 4, icon: 'el-icon-document' },
    { id: 43, name: "收益数据", pid: 4, icon: 'el-icon-document' },
    { id: 44, name: "粉丝数据", pid: 4, icon: 'el-icon-document' },
    { id: 45, name: "粉丝数据", pid: 4, icon: 'el-icon-document' },
    { id: 46, name: "一周小结", pid: 4, icon: 'el-icon-document' },
  { id: 5, name: "收益中心", pid: 0, icon: 'el-icon-coin', path: 'profitcenter' },
  { id: 6, name: "创作活动", pid: 0, icon: 'el-icon-ship', path: 'activity' },
    { id: 61, name: "活动列表", pid: 6, icon: 'el-icon-document' },
    { id: 62, name: "投稿管理", pid: 6, icon: 'el-icon-document' },
  { id: 7, name: "工具",     pid: 0, icon: 'el-icon-setting', path: 'tools' },
    { id: 71, name: "搬家",         pid: 7, icon: 'el-icon-document' },
    { id: 72, name: "博客打赏",     pid: 7, icon: 'el-icon-document' },
    { id: 73, name: "开头付费资源", pid: 7, icon: 'el-icon-document' },
  { id: 8, name: "创作权益", pid: 0, icon: 'el-icon-trophy', path: 'interests' },
    { id: 81, name: "等级权益",   pid: 8, icon: 'el-icon-document' },
    { id: 82, name: "自定义域名", pid: 8, icon: 'el-icon-document' },
  { id: 9, name: "设置",     pid: 0, icon: 'el-icon-s-tools', path: 'setting' },
    { id: 91, name: "博客设置", pid: 9, icon: 'el-icon-document' },
];
        
  export default {
    data() {
      return {
        menulist: [],
        activePath: '/home',
        str: ''
      }
    },
    props:{
      Collapse: {type: Boolean, default: false},
      msg: {type: String, default: ''},
      managers: {type: String, default: ''}
    },
    mounted() {
      this.menulist = this.handleTree(menu2, 'id', "pid");
      this.$bus.$on('callback', params => {
      let parent;
      if (params.pid === '0' || params.pid) parent = this.find__node(this.menulist, params.pid, 'id');  // 通过pid查找id
      const child = this.find__node(this.menulist, params.path, 'path');  // 通过path查找path
      this.activePath = parent ? parent.path + '/' + child.path : '/' + child.path;
      })
    },
    methods: {
      Change(e) {this.$emit('update:managers', e.target.value) },
      handleTree(data, id, parentId, children, rootId) {
		id = id || 'id'
		parentId = parentId || 'parentId'
		children = children || 'children'
		rootId = rootId || 0
		//对源数据深度克隆
		const cloneData = JSON.parse(JSON.stringify(data))
		//循环所有项
		const treeData =  cloneData.filter(parent => {
		  const branchArr = cloneData.filter(child => parent[id] === child[parentId]);
		  branchArr.length > 0 && (parent.children = branchArr);
		  return parent[parentId] === rootId;
		});
		return treeData != '' ? treeData : data;
	  },
      handleSelect(key, keyPath) {
        const keywords = key.substring(key.indexOf('/')+1, key.length);
        this.activePath = key;
        const result = this.find__node(this.menulist, keywords, 'path');
        if (result) this.$bus.$emit('handler_menu', result)
      },
      find__node(data, key, method) {
        for (var i = 0; i < data.length; i++) {
          if(data[i][method] === key) return data[i];
          if (data[i].children) return this.find__node(data[i].children, key, method)
        };
      }
    },
    watch: {
      managers(news) {
        this.str = news;
      }
    }

  }
</script>

<style lang="css" scoped>
.Container.el-scrollbar {
  height: calc(100vh - 84px);
  width: 100%;
  overflow-x: hidden;
}
>>> .el-scrollbar__wrap {
  overflow-x: hidden;
}
.el-menu {
  border-right: none;
}
</style>
home 组件 => 使用component 组件渲染对应组件
<template>
  <el-container>
    <el-header>
      <el-col :span="4" :offset="23">
        <el-button type="primary" @click="logout">退出</el-button>
      </el-col>
    </el-header>

    <el-container>
      <el-aside :width="isCollapse ? '64px' : '200px'">
        <div class="toggle-button" @click="isCollapse = !isCollapse">|||</div>
        <menus :Collapse="isCollapse"></menus>
      </el-aside>

      <el-main>
        <el-menu ref="menu_tab" :default-active="activePath" class="el-menu-demo" mode="horizontal" @select="handleSelect" background-color="#333744" text-color="#fff" active-text-color="#ffd04b">
          <el-menu-item  v-for="(item, index) in menutabs" :key="'/' + item.path" :index="'/'+ item.path">{{item.name}} <i  v-if="item.name !== '首页'" slot="title" class="el-icon-circle-close" @click.stop="close(item.name, index)"></i></el-menu-item>
        </el-menu>
        <div v-for="item in menutabs" :key="this" v-show="active === item.name">
          <component :is="item.path"/>
        </div>
      </el-main>

    </el-container>

  </el-container>
</template>

<script>
import Cookies from "js-cookie";
import menus from "@/components/aside/menu";
import weather from "@/components/header/weather";
import home from "@/main/home";
import amap from "@/main/producectxs/map";
import publish_article from "@/main/producectxs/publish_article";
import upfile from "@/main/producectxs/upfile";
import upvideo from "@/main/producectxs/upvideo";
import issue_questions from "@/main/producectxs/issue_questions";
import _ from 'lodash'  // lodash
import Sortable from 'sortablejs';  // 支持拖拽
export default {
  components: { menus, weather,
    home, publish_article, upfile, upvideo, issue_questions,amap
  },
  data() {
    return {
      isCollapse: false,
      menutabs: [{ name: "首页", path: "home", pid: "" } ],
      active: "首页",
      activePath: '/home'
    };
  },
  mounted() {
    this.$bus.$on("handler_menu", ({ name, path, pid }) => {
      this.activePath = '/' + path
      let component = this.menutabs.find(v => v.name === name);
      if (!component) this.menutabs.push({ name, path, pid });
      this.active = name;
    });
    this.DragDrop()
  },
  methods: {
    logout() {
      Cookies.remove("user");
      this.$router.push("/login");
    },
    handleSelect(key) {
      const filters = this.menutabs.filter(v => '/' + v.path === key ? v.name : undefined);
      const {name, path, pid} = filters[0];
      this.active = name;
      this.activePath = '/' + path;
      this.$bus.$emit('callback', filters[0])
    },
    close(name, index) {
      const { active, activePath, menutabs } = this;
      let activeIndex = _.findKey(menutabs, o => o.name === active);  // 使用lodash方法拿到选中激活的索引
      menutabs.forEach((v, i) => {
        if (v.name === name) {
        this.menutabs.splice(i, 1);
          if (menutabs.length <= 1) i = 0, activeIndex = 0; // 只剩下最后一个就不用遍历了,直接使i等于0
          if (parseInt(activeIndex) === i) {
            this.active = menutabs[i].name
            this.activePath = '/' + menutabs[i].path
          }
          this.$bus.$emit('callback', menutabs[i])
        }
      })
    },
    DragDrop() {
      const _this = this;
      Sortable.create(this.$refs.menu_tab.$el, {
        onEnd({ newIndex, oldIndex }) {
          let currRow = _this.menutabs.splice(oldIndex, 1)[0];
          _this.menutabs.splice(newIndex, 0, currRow);
        },
      });
    }
  },
};
</script>

<style lang="stylus">
body>#app>.el-container {
  height: 100%;
}

.el-header {
  background: #373d41;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.el-aside {
  background: #333744;
  color: #fff;
  position: relative;
}

.toggle-button {
  background: #4a5064;
  text-align: center;
  font-size: 10px;
  line-height: 24px;
  color: #fff;
  letter-spacing: 0.2em;
  cursor: pointer;
}

.el-main {
  background: #eaedf1;
  color: #333;
  padding: 0;
  margin: 0;
  border-bottom: none;
  & > div {
    height: calc(100% - 61px);
  }
  & > .el-menu {
    border-bottom: none;
  }
}
</style>

路由 => 阻止其他路由跳转
import Vue from 'vue'
import Router from 'vue-router'
import home from '@/views/home'
import login from '@/views/login'

Vue.use(Router)

export default new Router({
  routes: [
    { path: '/login', name: 'login', component: login },
    { path: '/', name: 'home', component: home},
    { path: '*',  hidden: true, redirect: '/' },
  ]
})
不喜直喷,转载注明!!!
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值