el-tree是一个经常用到的组件,但是它不支持v-model,使用起来很麻烦,这篇教程封装了el-tree,使得它使用起来很简单,并且支持搜索,支持叶子节点横向排列,这样就算数据多了,也会显的很紧凑,同时它支持提交halfCheck节点,这点在做菜单管理的时候很有用,如果数据中不保存halfCheck,你需要向上遍历父节点,但是保存了父节点在回显的时候会有问题,因为只要父节点选中子节点都会选中,这些在组件封装中都做了处理
组件的封装
<template>
<div>
<el-input v-model="filterText" placeholder="请输入搜索条件" v-if="filter" clearable/>
<el-tree v-bind="allProps" ref="treeRef" :filter-node-method="filterNode"
@check="handleCheck"/>
</div>
</template>
<script lang="ts">
// @ts-nocheck
export default {
name: "ui-tree",
props: { // 参考 https://element-plus.org/zh-CN/component/tree.html#%E5%B1%9E%E6%80%A7
modelValue: {default: () => []}, //要提交的表单值
nodeKey: {default: 'id'}, //每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
defaultExpandAll: {default: true}, //是否默认展开所有节点
showCheckbox: {default: true}, //是否显示选择框
data: {default: null}, //树的数据
filter: {default: true}, //是否显示过滤框
leafInline: {default: true}, //叶子节点是否显示成一行
props: { //参考:https://element-plus.org/zh-CN/component/tree.html#props
type: Object, default: () => {
return {}
}
}
},
data() {
return {
textValue: '', //view模式显示的内容
userChecked: false, //是否用户引起的变化
filterText: '',
allProps: {
...this.$attrs,
...this.$props
}
}
},
created() {
if (this.leafInline) {
this.props.class = this.customNodeClass
}
},
mounted() {
this.setChecked()
},
watch: {
'modelValue': {
handler(val) {
this.setChecked()
},
},
// 过滤
filterText(val) {
this.$refs.treeRef.filter(val)
},
},
computed: {
labelField() {
if (!this.props || !this.props.label) return 'label'
return this.props.label
},
childrenField() {
if (!this.props || !this.props.children) return 'children'
return this.props.children
},
},
methods: {
// 设置选中的节点
setChecked() {
let val = this.modelValue
//如果是用户点击则不设置
if (this.userChecked) {
this.userChecked = false
return
}
if (!val || !this.$refs.treeRef) return
let leafNode = []
// 如果后台保存了half节点,需要过滤掉
this.filterLeafNode(leafNode, this.data, val)
this.$refs.treeRef.setCheckedKeys(leafNode)
},
//用户选择后回调
handleCheck(data, check) {
this.updateModelValue(check.halfCheckedKeys, check.checkedKeys)
},
updateModelValue(halfCheckedKeys, checkedKeys) {
this.userChecked = true
let checkIds = []
checkIds.push(...halfCheckedKeys)
checkIds.push(...checkedKeys)
this.$emit('update:modelValue', checkIds)
},
//根据搜索框过滤节点
filterNode(value: string, data) {
// 该方法会遍历所有节点,显示返回为true的节点
if (!value) return true
return data[this.labelField].includes(value)
},
// 过滤父节点,只返回叶子节点
filterLeafNode(leafNode, children, checkedArray) {
if (!children) return []
children.forEach(item => {
if (!item[this.childrenField] || item[this.childrenField].length == 0) {
if (checkedArray.indexOf(item[this.nodeKey]) > -1) {
leafNode.push(item[this.nodeKey])
}
} else {
this.filterLeafNode(leafNode, item[this.childrenField], checkedArray)
}
})
},
customNodeClass(data, node) {
if (node.isLeaf) return ''
let addClass = true
for (const key in node.childNodes) {
if (!node.childNodes[key].isLeaf) {
addClass = false
}
}
let levelClass = 'level-' + node.level
return addClass ? `penultimate-node ${levelClass}` : ''
},
}
}
</script>
<style>
.penultimate-node .el-tree-node__children {
line-height: 12px;
}
.el-tree-node.penultimate-node > .el-tree-node__children {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.el-tree-node.penultimate-node.level-1 > .el-tree-node__children {
padding-left: 30px;
}
.el-tree-node.penultimate-node.level-2 > .el-tree-node__children {
padding-left: 48px;
}
.el-tree-node.penultimate-node.level-3 > .el-tree-node__children {
padding-left: 64px;
}
.el-tree-node.penultimate-node.level-4 > .el-tree-node__children {
padding-left: 84px;
}
.penultimate-node .el-tree-node__children > .el-tree-node .el-tree-node__content {
padding-left: 12px !important;
}
.penultimate-node .el-tree-node__children .el-tree-node__content .el-tree-node__expand-icon {
display: none;
}
</style>
组件的使用
node-key就是绑定值,如果要form绑定id就传id,组件默认显示label,子节点保存在children里面,如果要变更可以通过:props="{label:'title',children:'children'}"来实现
<template>
<div style="width: 400px">
<ui-tree :data="data" node-key="label" v-model="form"></ui-tree>
</div>
</template>
<script>
import UiTree from "@/components/ui-tree.vue";
export default {
name: "tree",
components: {UiTree},
data() {
return {
form: ['菜单管理'],
data: [{label: '系统管理',children:[{label:'用户管理', children:[{label:'菜单管理'},{label:'按钮管理'},{label:'权限管理'}]},{label:'角色管理'}]},
{label:'文档管理',children:[{label:'目录管理'},{label:'图片管理'},{label:'文件管理'}]}]
}
},
}
</script>