Vue.js 递归组件实现树形菜单
最近看了 Vue.js 的递归组件,实现了一个最基本的树形菜单。
main.js 作为入口:
import Vue from 'vue' import main from './components/main.vue' new Vue({ el: '#app', render: h => h(main) })
它引入了一个组件 main.vue:
<template> <div> <my-tree :data="theData" :name="menuName" :loading="loading" @getSubMenu="getSubMenu"></my-tree> </div> </template> <script> const myData = [ { id: '1', menuName: '基础管理', menuCode: '10' }, { id: '2', menuName: '商品管理', menuCode: '' }, { id: '3', menuName: '订单管理', menuCode: '30', children: [ { menuName: '订单列表', menuCode: '31' }, { menuName: '退货列表', menuCode: '32', children: [] } ] }, { id: '4', menuName: '商家管理', menuCode: '', children: [] } ]; const subMenuData1 = { parentId: '1', children: [ { menuName: '用户管理', menuCode: '11' }, { id: '12', menuName: '角色管理', menuCode: '12', children: [ { menuName: '管理员', menuCode: '121' }, { menuName: 'CEO', menuCode: '122' }, { menuName: 'CFO', menuCode: '123' }, { menuName: 'COO', menuCode: '124' }, { menuName: '普通人', menuCode: '124' } ] }, { menuName: '权限管理', menuCode: '13' } ] }; const subMenuData2 = { parentId: '2', children: [ { menuName: '商品一', menuCode: '21' }, { id: '22', menuName: '商品二', menuCode: '22', children: [ { menuName: '子类商品1', menuCode: '221' }, { menuName: '子类商品2', menuCode: '222' } ] } ] }; import myTree from './common/treeMenu.vue' export default { components: { myTree }, data () { return { theData: myData, menuName: 'menuName', // 显示菜单名称的属性 loading: false } }, methods: { getSubMenu (menuItem, callback) { this.loading = true; if (menuItem.id === subMenuData1.parentId) { this.loading = false; menuItem.children = subMenuData1.children; callback(menuItem.children); } setTimeout(() => { if (menuItem.id === subMenuData2.parentId) { this.loading = false; menuItem.children = subMenuData2.children; callback(menuItem.children); } }, 2000); } } } </script>
subMenuData1, subMenuData2 存放子菜单数据,可以从服务器获取,以实现动态加载。
该文件引入了树形组件 treeMenu.vue:
<template> <ul class="tree-menu"> <li v-for="(item, index) in data"> <span @click="toggle(item, index)"> <i :class="['icon', item.children && item.children.length ? folderIconList[index] : 'file-text', loading ? loadingIconList[index] : '']"></i> {{ item[name] || item.menuName }} </span> <tree-menu v-if="scope[index]" :data="item.children"></tree-menu> </li> </ul> </template> <script> export default { name: 'treeMenu', props: { data: Array, name: String, loading: Boolean }, data () { return { folderIconList: [], loadingIconList: [], scope: {} } }, created () { this.data.forEach((item, index) => { if (item.children && item.children.length) { this.folderIconList[index] = 'folder'; } }); }, methods: { doTask (index) { this.$set(this.scope, index, !this.scope[index]); this.folderIconList[index] = this.scope[index] ? 'folder-open' : 'folder'; }, toggle (item, index) { this.loadingIconList = []; if (item.children && item.children.length) { this.doTask(index); } else { this.loadingIconList[index] = 'loading'; this.$emit('getSubMenu', item, (subMenuList) => { if (subMenuList && subMenuList.length) { this.doTask(index); } }); } } } } </script> <style scoped> .tree-menu { list-style: none; } .tree-menu li { line-height: 2; } .tree-menu li span { cursor: default; } .icon { display: inline-block; width: 15px; height: 15px; background-repeat: no-repeat; vertical-align: -2px; } .icon.folder { background-image: url(/src/assets/folder.png); } .icon.folder-open { background-image: url(/src/assets/folder-open.png); } .icon.file-text { background-image: url(/src/assets/file-text.png); } .icon.loading { background-image: url(/src/assets/loading.gif); background-size: 15px; } </style>
效果图:
示例代码放在这里。