基于vue 封装的tree插件
实现思想:组件递归实现思想,自己调用自己,因为事件只能传递一层,所以需要每一层子组件触发上一层父组件的事件。然后通过Vue.set(对象,属性,值)的方式动态给对象添加属性;支持选中回填
子节点键值名称为 children
event
@selectCheck=“selectCheck” //获得当前选中的节点数据
@select-check=“selectCheck” //只要树形结构有变化,就会推送当前选中的数据
props
名称 | 描述 | 类型 | 默认值 | 必须 |
---|---|---|---|---|
echo | 由唯一键位组成的字符串,多个用西文逗号分隔 | string | – | 是 |
label | 树结构每一层显示的内容的数据节点名称 | string | dmmc | 是 |
checkKey | 数据中的唯一键节点名称 | string | dm | 是 |
scoped | 是否子父级联动 | boolean | false | 否 |
value | – | – | – | – |
parent | 父节点数据 | Object | null | 否 |
halfCheck | 半选开关 | boolean | true | 否 |
multipleChoice | 单多选开关 true 多选 | boolean | false | 是 |
treeDataList | 树结构渲染数据 | array | [] | 是 |
export default {
name: "tree",
template: '<ul style="text-align: left;" ref="ul">' +
'<template v-for="item in treeDataList">' +
'<li>' +
'<svg v-if="item.isLeaf&&!item.expend" @click="switchExpend(item,true)" t="1619510924655" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2923" width="26" height="26"><path d="M461.024 665.856c-17.696 14.08-45.024 2.4-45.024-19.2v-269.312c0-21.6 27.328-33.28 45.024-19.2l169.44 134.656a24.096 24.096 0 0 1 0 38.4l-169.44 134.656z" fill="#8a8a8a" p-id="2924"></path></svg>' +
'<svg v-if="item.isLeaf&&item.expend" @click="switchExpend(item,false)" t="1619511142075" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3433" width="26" height="26"><path d="M358.144 461.024c-14.08-17.696-2.4-45.024 19.2-45.024h269.312c21.6 0 33.28 27.328 19.2 45.024L531.2 630.464a24.096 24.096 0 0 1-38.4 0l-134.656-169.44z" fill="#8a8a8a" p-id="3434"></path></svg>' +
'<div v-if="multipleChoice" style="position: relative;float: left;margin-top: 2px;"><div @click="clickCheck(item,$event)" :class="[item.checked?(item.halfCheck?\'halfCheck\':\'checkDiv\'):\'checkDiv1\']"></div></div>' +
'<div :style="{\'padding-left\':multipleChoice?\'20px\':\'0px\' }" @click="clickCheck(item,$event)" :class="[item.checked?(item.halfCheck?\'\':\'activeNode\'):\'\']">{{item.dmmc}}</div>' +
'</li>' +
'<tree v-show="item.expend" ' +
':scoped="scoped" ' +
':halfCheck="halfCheck" ' +
'@node-check="nodeCheck" ' +
'v-model="arr" ' +
':parent="item" ' +
':echo="echo" ' +
':multipleChoice="multipleChoice" ' +
'style="margin-left: 20px" ' +
':treeDataList="item.children">' +
'</tree>' +
'</template>' +
'</ul>',
props: {
echo: { // 回显的内容
type: String,
default: ''
},
label: { // 显示的字段
type: String,
default: 'dmmc'
},
checkKey: { // 唯一键节点
type: String,
default: 'dm'
},
scoped: { // 是否子父集关联
type: Boolean,
default: false
},
value: {},
parent: { // 父级
type: Object,
default: () => null
},
halfCheck: { // 是否半选
type: Boolean,
default: true
},
multipleChoice: { // 单选多选标记 true 多选
type: Boolean,
default: () => {
return false
}
},
treeDataList: { // 传入的treeDataList循环的值
type: Array,
default: () => {
return []
}
},
},
model: {
prop: 'value',
event: 'setValue'
},
data() {
return {
arr: []
}
},
computed: {},
mounted() {
/*
* @Author GyYu
* @Description //TODO 事件分发 通知修改子节点属性
* @Date 17:13 2021/4/29
**/
this.$on('childChecked', (node, checked) => {
if (node.children && node.children.length) {
for (const child of node.children) {
this.$set(child, 'checked', checked)
this.$emit('node-check', child, checked)
}
}
})
/*
* @Author GyYu
* @Description //TODO 事件分发,通知修改父节点属性
* @Date 17:13 2021/4/29
**/
this.$on('parentChecked', (node, checked, type) => {
this.$set(node, 'checked', checked)
if (!node.parent) { // 当前节点没有父节点,推送父组件选中数据,退出函数
this.$emit("select-check", this.getNodes({checked: true}, this.treeDataList))
return false
}
// 判定是否有满足条件的
const someChildNodeChecked = node.parent.children.some(node => node.checked)
// 判定所有的是否都满足条件
const everyChildNodeChecked = node.parent.children.every(node => node.checked)
// 如果是半选开关打开
if (this.halfCheck) {
// 修改数据节点中半选开关标记,用于控制标签的类
everyChildNodeChecked ? this.$set(node.parent, 'halfCheck', false) : someChildNodeChecked ? this.$set(node.parent, 'halfCheck', true) : this.$set(node.parent, 'halfCheck', false)
// type 当为1的时候,是回填选中状态调用的,需要将父节点左右的展开开关打开
if (type == 1) everyChildNodeChecked ? this.$set(node.parent, 'expend', true) : someChildNodeChecked ? this.$set(node.parent, 'expend', true) : this.$set(node.parent, 'expend', false)
// 更新父节点的值
if (!checked && someChildNodeChecked) {
this.$set(node.parent, 'halfCheck', true)
this.$set(node.parent, 'expend', true)
return false
}
// 发送通知,通知递归中的父组件,需要处理的内容
this.$emit('parentChecked', node.parent, checked, type)
} else {
if (checked && everyChildNodeChecked) this.$emit('parentChecked', node.parent, checked)
if (!checked) this.$emit('parentChecked', node.parent, checked, type)
}
})
// 选中与反选处理
this.$on('node-check', (node, checked, type) => {
if (node.isLeaf) { // 折叠开关 只有有折叠的才改
// 修改数据节点中展开开关
this.$set(node, 'expend', checked ? true : false)
}
// 加一个控制,选中回填才有效,这里注意一下
this.$nextTick(function () {
if (!this.scoped) {
this.$emit('parentChecked', node, checked, type)
this.$emit('childChecked', node, checked)
this.$nextTick(function () {
this.$emit('selectCheck', this.getNodes({checked: true}, this.treeDataList, false))
})
} else {
this.$set(node, 'checked', checked)
}
})
})
this.initData();
},
methods: {
/*
* @Author GyYu
* @Description //TODO 切换展开标记
* @Date 13:40 2021/4/28
**/
switchExpend(item, flag) {
this.$set(item, 'expend', flag)
},
/*
* @Author GyYu
* @Description //TODO 外部调用选中数据方法
* 调用方式 @selectCheck="selectCheck"
* @Date 17:19 2021/4/29
**/
getCheckData() {
return this.getNodes({checked: true}, this.treeDataList)
},
/*
* @Author GyYu
* @Description //TODO 递归查找每个节点中的checked是否选中
*
* @Date 17:19 2021/4/29
**/
getNodes(opt, data) {
data = data || this.treeDataList
let res = []
for (const node of data) {
for (const [key, value] of Object.entries(opt)) {
if (node[key] === value) {
let n = Object.assign({}, node)
delete n['children']
delete n['parent']
res.push(n)
}
}
if (node.children && node.children.length) {
res = res.concat(this.getNodes(opt, node.children))
}
}
return res
},
/*
* @Author GyYu
* @Description //TODO 事件发生器入口,所有事件分发全部从这里走
* @Date 17:20 2021/4/29
**/
nodeCheck(node, checked, type) {
this.$emit('node-check', node, checked, type)
},
/*
* @Author GyYu
* @Description //TODO 选中与反选
* @Date 17:08 2021/4/29
* @Param
* @return
**/
clickCheck(node, $event) {
if (this.multipleChoice) { // 多选开关 是多选
node.checked = !node.checked
this.$emit('node-check', node, node.checked)
} else {
if(node.children&&node.children.length>0){
return false;
}
// 不是多选,根据组件父子链,将所有数据组装后,进行递归,修改上次选中的数据,全部处理成未选中
const getRoot = (el) => {
if (el.$parent.$el.nodeName === 'UL') {
el = el.$parent
return getRoot(el)
}
return el
}
let root = getRoot(this)
const checkNo = (list) => {
for (let n of list || []) {
this.$set(n, 'checked', false)
this.$set(n, 'halfCheck', false)
if (n.children) checkNo(n.children)
}
}
checkNo(root.treeDataList) //开始递归
node.checked = !node.checked
this.$emit('node-check', node, node.checked) // 本次选中的业务开始 本次,找父级,找子集
}
},
/*
* @Author GyYu
* @Description //TODO 初始化数据,增加一些开关
* @Date 17:33 2021/4/29
* @Param
* @return
**/
initData() {
let _self = this;
let arr = this.echo.split(',');
for (const node of this.treeDataList) {
this.$set(node, 'parent', this.parent)
}
this.treeDataList.forEach(function (item) {
if (!item.children) {
_self.$set(item, "children", []) // 添加一个空的子集
}
_self.$set(item, "halfCheck", false) // 添加半选标记
_self.$set(item, "isLeaf", item.children.length > 0 ? true : false) // 添加折叠图标标记
_self.$set(item, "expend", false) // 添加展开标记
_self.$set(item, "checked", arr.length > 0 && arr.includes(item[_self.checkKey]) ? _self.nodeCheck(item, true, 1) : false) // 添加全选标记
})
}
},
created() {
}
}