文章目录
- 功能描述
- 代码
-
- base-tree.vue:
- treeDemo.vue:
功能描述
基础功能同el-tree



代码
base-tree.vue:
<!--* @Description: 公共树组件* @Author: HMM* @Date: 2021-01-11 16:23:23* @FilePath: base-tree.vue
变量:expandNode:是否展开所有节点isShowFilter: 是否需要节点过滤treeProps: 树形结构配置项
事件:@handleNodeClick: 节点被点击时的回调@handleCheck:当复选框被点击的时候触发@handleCheckChange:节点选中状态发生变化时的回调
方法:同element-ui
getCurrentKey
getCurrentNode
setCurrentKey
getCheckedKeys
getCheckedNodes-->
<template><div class="baseTree"><el-input v-if="isShowFilter" size="small" :placeholder="placeholder" v-model="filterText"></el-input><el-tree ref="baseTree":data="treeList":node-key="treeProps.id":props="treeProps":highlight-current="highlight":accordion="accordion":default-expand-all="expand":default-expanded-keys="expandedKeys":auto-expand-parent="expandParent":expand-on-click-node="expandNode":show-checkbox="multiple":check-strictly="checkStrictly":filter-node-method="filterNode"@node-click="handleNodeClick"@node-expand="handleNodeExpand"@node-collapse="handleNodeCollapse"@check="handleCheck"@check-change="handleCheckChange"@node-contextmenu="handleNodeContextMenu"></el-tree></div>
</template><script>
export default {props: {// treeList: {// type: Array,// default () {// return [];// }// },// 默认树形结构配置项treeProps: {type: Object,default() {return {id: 'id', // Number类型:树组件ID(node-key)label: 'label', // String类型 : 树组件显示名称pid: 'parentId', // Number类型:父级IDchildren: 'children' // String类型:子节点};}},// 自动收起accordion:{type:Boolean,default:() => { return false }},// 是否展开所有节点,默认展开expand: {type: Boolean,default() {return true;}},// 展开子节点的时候是否自动展开父节点 默认值为 trueexpandParent:{type: Boolean,default() {return true;}},// 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。expandNode: {type: Boolean,default() {return true;}},// 默认展开的节点的 key 的数组expandedKeys:{type: Array,default() {return [];}},// 是否高亮当前选中节点,默认值是 truehighlight: {type: Boolean,default() {return true;}},// 是否可多选,默认单选multiple: {type: Boolean,default() {return false;}},// 显示复选框情况下,是否严格遵循父子不互相关联checkStrictly: {type: Boolean,default() {return false;}},// 图标urliconUrl: {type: String,default() {return '';}},placeholder: {type: String,default: () => {return '检索关键字';}},// 是否需要关键字过滤isShowFilter: {type: Boolean,default: () => {return false;}}},data() {return {filterText: '',visible: false};},methods: {/*** @description: 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏* @param {*} value* @param {*} data* @return {*}*/filterNode(value, data) {if(!value) return true;return data[this.treeProps.label].indexOf(value) !== -1;},/*** @description: 节点被展开时触发的事件* @param {*} data 该节点所对应的数据对象* @param {*} node 节点对应的Node对象* @param {*} vueComponent 节点组件本身* @return {*}*/handleNodeExpand(data, node, vueComponent){this.$emit('handleNodeExpand', data);},/*** @description: 节点被关闭时触发的事件* @param {*} data 该节点所对应的数据对象* @param {*} node 节点对应的Node对象* @param {*} vueComponent 节点组件本身* @return {*}*/handleNodeCollapse(data, node, vueComponent){this.$emit('handleNodeCollapse', data);},/*** @description: [事件] - 节点被点击时的回调* @param {*} data 该节点所对应的数据对象* @param {*} node 节点对应的Node对象* @param {*} vueComponent 节点组件本身* @return {*}*/handleNodeClick(data, node, vueComponent) {this.$emit('handleNodeClick', data);},/*** @description: 事件 - 当某一节点被鼠标右键点击时会触发该事件* @param {*} event* @param {*} data 传递给 data 属性的数组中该节点所对应的对象* @param {*} node 节点对应的Node对象* @param {*} vueComponent 节点组件本身* @return {*}*/handleNodeContextMenu(event, data, node, vueComponent){this.$emit('handleNodeContextMenu', event, data);},/*** @description: [事件] - 当复选框被点击的时候触发* @param {*} checkedNodes 该节点所对应的对象* @param {*} checkedKeys 树目前的选中状态对象* @param {*} halfCheckedNodes* @param {*} halfCheckedKeys* @return {*}*/handleCheck(node, checkedData) {// console.log('handleCheck: ', node, checkedData)this.$emit('handleCheck', node, checkedData);},/*** @description: 事件 - 节点选中状态发生变化时的回调* @param {*}* @return {*}*/handleCheckChange(data, checked) {let currentNode = this.$refs.baseTree.getNode(data);// console.log('handleCheckChange: ', data, checked)if(this.checkStrictly){// 用于:父子节点严格互不关联时,父节点勾选变化时通知子节点同步变化,实现单向关联if(checked) {// 选中 子节点只要被选中父节点就被选中this.parentNodeChange(currentNode);} else {// 未选中 处理子节点全部未选中this.childNodeChange(currentNode);}}this.$emit('handleCheckChange', data, checked);},// ------------------------------------------------------------------------/*** @description: 获取当前被选中节点的 key,使用此方法必须设置 node-key 属性,若没有节点被选中则返回 null* @param {*}* @return {*}*/getCurrentKey: function () {return this.$refs.baseTree.getCurrentKey();},/*** @description: 获取当前被选中节点的 data,若没有节点被选中则返回 null* @param {*}* @return {*}*/getCurrentNode: function () {return this.$refs.baseTree.getCurrentNode();},/*** @description: 通过 key 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性* @param {*} key 待被选节点的 key,若为 null 则取消当前高亮的节点* @return {*}*/setCurrentKey:function(key){// $nextTick 是确保DOM渲染结束之后执行的this.$nextTick(() => {this.$refs.baseTree.setCurrentKey(key);})},setCurrentNode:function(node){// $nextTick 是确保DOM渲染结束之后执行的this.$nextTick(() => {this.$refs.baseTree.setCurrentNode(node);})},/*** @description: 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点的 key 所组成的数组* @param {*}* @return {*}*/getCheckedKeys: function () {return this.$refs.baseTree.getCheckedKeys();},/*** @description: 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点所组成的数组* @param {*}* @return {*}*/getCheckedNodes: function () {return this.$refs.baseTree.getCheckedNodes();},/*** @description: 通过 keys 设置目前勾选的节点,使用此方法必须设置 node-key 属性* @param {*} keys 勾选节点的 key 的数组* @param {*} leafOnly boolean 类型的参数,若为 true 则仅设置叶子节点的选中状态,默认值为 false* @return {*}*/setCheckedKeys: function(keys){// if(!keys)keys = [];const leafOnly = false;this.$nextTick(() => {this.$refs.baseTree.setCheckedKeys(keys, leafOnly);})},/*** @description: 通过 key / data 设置某个节点的勾选状态,使用此方法必须设置 node-key 属性* @param {*} val 勾选节点的 key 或者 data* @param {*} checked boolean 类型,节点是否选中* @param {*} deep boolean 类型,是否设置子节点 ,默认为 false* @return {*}*/setChecked: function(val, checked, deep){this.$refs.baseTree.setChecked(val, checked);},// --------------------------------------------------------------------------// 统一处理子节点为不选中childNodeChange (node) {for(let i = 0; i < node.childNodes.length; i++) {node.childNodes[i].checked = false;this.childNodeChange(node.childNodes[i]);}},// 统一处理父节点为选中parentNodeChange (node) {if(node.parent.key !== undefined) {node.parent.checked = true;this.parentNodeChange(node.parent);}}},computed: {/*** @description: 树形结构数据(非标准的转换为标准结构)* @param {*}* @return {*}*/treeList() {return this.$attrs.treeList || [];}},watch: {filterText(val) {this.$refs.baseTree.filter(val);}}
};
</script><style lang="scss">
// 字体和大小
.custom-tree-node {font-family:"Microsoft YaHei";font-size: $size14;position: relative;
}// 选中状态背景色
.baseTree .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {background-color: #F5F7FA !important;
}// 原生el-tree-node的div是块级元素,需要改为inline-block,才能显示滚动条
.baseTree .el-tree >.el-tree-node {display: inline-block;min-width: 100%;
}</style>
treeDemo.vue:
<!--* @Description: 树形组件 - 使用示例* @Author: HMM* @Date: 2021-01-14 16:04:43* @FilePath: treeDemo.vue
--><template><div class="treeDemo"><el-container><el-main><baseTreeref="tree":treeProps="props":treeList="treeList":accordion="isAccordion":expandNode="false":isShowFilter="true"@handleNodeClick="handleNodeClick"@handleNodeContextMenu="handleNodeContextMenu"/></el-main><el-main><span>父子不互相关联:check-strictly=true</span><br /><br /><baseTree:multiple="true":collapse="false":checkStrictly="true":treeProps="props":treeList="treeList":accordion="isAccordion":expandNode="false":isShowFilter="true"@handleCheck="handleCheck"@handleCheckChange="handleCheckChange"/></el-main><el-main><span>父子互相关联:check-strictly=false</span><br /><br /><baseTree:multiple="true":collapse="false":treeProps="props":treeList="treeList":accordion="isAccordion":expandNode="false":isShowFilter="true"/></el-main><!-- 树右键 - 弹出菜单项 --><div v-show="menuVisible"><ul id="menu" class="menu" @mouseleave="foo"><li class="menu_item" >新增</li><li class="menu_item" >删除</li><li class="menu_item" >上移</li><li class="menu_item" >下移</li></ul></div></el-container></div>
</template><!-- JS -->
<script>
import baseTree from '_c/basics/base-tree';
export default {name:'demo',components: {baseTree},data() {return {props:{ // 配置项(必选)id: 'id',label: 'name',pid: 'parentId',children: 'children'// disabled:true},// 数组list: [{id:1, parentId:0, name:'一级菜单A', rank:1},{id:2, parentId:0, name:'一级菜单B', rank:1},{id:3, parentId:0, name:'一级菜单C', rank:1},{id:4, parentId:1, name:'二级菜单A-A', rank:2},{id:5, parentId:1, name:'二级菜单A-B', rank:2},{id:6, parentId:2, name:'二级菜单B-A', rank:2},{id:7, parentId:4, name:'三级菜单A-A-A', rank:3},{id:15, parentId:0, name:'一级菜单C', rank:1},{id:16, parentId:0, name:'一级菜单C', rank:1},{id:17, parentId:0, name:'一级菜单C', rank:1},{id:18, parentId:0, name:'一级菜单C', rank:1}],treeList:[],isClearable:true, // 可清空(可选)isAccordion:false, // 可收起(可选)menuVisible: false}},created(){this.initData();},methods: {initData(){this.treeList = this.listToTree(this.list, this.props);console.log(this.treeList);},handleNodeClick(data){console.log('handleNodeClick', data)},handleCheck(node, checkedData){console.log('handleCheck: ', node, checkedData)},handleCheckChange(data, checked){console.log('handleCheckChange: ', data, checked)},// ---------------------------------------------------------------/*** @description: 树节点右键事件* @param {*} event* @param {*} data* @return {*}*/handleNodeContextMenu(event, data){// 设置当前节点this.$refs.tree.setCurrentNode(data);this.currentNode = data;// 弹出this.menuVisible = true;let menu = document.querySelector('#menu');/* 菜单定位基于鼠标点击位置 */document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法menu.style.left = event.clientX + 20 + 'px';menu.style.top = event.clientY - 10 + 'px';},/*** @description: 取消鼠标监听事件 菜单栏* @param {*}* @return {*}*/foo() {this.menuVisible = false;// 要及时关掉监听,不关掉的是一个坑,不信你试试,虽然前台显示的时候没有啥毛病,加一个alert你就知道了document.removeEventListener('click', this.foo);},// ---------------------------------------------------------------/*** @description 数组转树形数据* @param {数据数组} list* @param {树结构配置} config*/listToTree(list, config) {let conf = {};Object.assign(conf, config);const nodeMap = new Map();const result = [];const { id, children, pid } = conf;for(const node of list) {// node[children] = node[children] || [];nodeMap.set(node[id], node);}for(const node of list) {const parent = nodeMap.get(node[pid]);(parent ? (parent.children ? parent.children : parent.children = []) : result).push(node);}return result;}}
}
</script><style lang="scss" scoped>
// 树节点右键 - 弹出菜单栏
.treeDemo .menu {height: auto;width: 120px;color: #606266;position: absolute;padding: 0px;box-sizing: border-box;/*border: 1px solid #999999;*/text-align: center;background-color: #fff;border-radius: 0.25rem;box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}.treeDemo .menu_item {line-height: 20px;text-align: center;
}
.treeDemo li:hover {background-color: rgba(232, 237, 250, 0.6);color: darkslategrey;cursor: pointer;
}
.treeDemo li {position: relative;display: flex;font-size: 14px;align-items: center;height: 34px;line-height: 34px;outline: none;margin-top: 0px;padding-left: 20px;padding-right: 5px;
}</style>
根据树节点右键事件:弹出菜单项:
