这两天对树形结构菜单感兴趣,弄了一个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');
const child = this.find__node(this.menulist, params.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'
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);
menutabs.forEach((v, i) => {
if (v.name === name) {
this.menutabs.splice(i, 1);
if (menutabs.length <= 1) i = 0, activeIndex = 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: '/' },
]
})
不喜直喷,转载注明!!!