<template>
<div class="MultipleTree">
<div v-show="isShowSelect" class="mask" @blur="popoverHide" />
<el-popover
ref="dcPopover"
v-clickoutside="closePopover"
placement="bottom-start"
:width="popoverWidth"
trigger="click"
:popper-options="{ boundariesElement: 'viewport', removeOnDestroy: true }"
@hide="popoverHide"
>
<el-tree
ref="tree"
class="common-tree"
:width="width"
:data="treeData"
:props="obj"
:show-checkbox="multiple"
:node-key="obj.id"
:check-strictly="checkStrictly"
:default-expanded-keys="multiple ? defaultKey : []"
:default-checked-keys="multiple ? defaultKey : []"
:expand-on-click-node="multiple && expandClickNode"
:check-on-click-node="checkClickNode"
:highlight-current="true"
@check-change="nodeClick"
@node-click="nodeClick"
@check="checkClick"
@node-expand="nodeExpand"
@node-collapse="nodeExpand"
/>
<el-select
slot="reference"
ref="select"
v-model="returnDataKeys"
:size="size"
:multiple="multiple"
:clearable="clearable"
:collapse-tags="collapseTags"
:placeholder="placeholder"
class="tree-select"
@click.native="selectClick"
@remove-tag="removeTag"
@clear="clean"
>
<el-option
v-for="(item, index) in options"
:key="index"
:label="item && item.label"
:value="item && item.value"
/>
</el-select>
</el-popover>
</div>
</template>
<script>
export default {
name: 'MultipleTree',
props: {
// 树结构数据
data: {
type: Array,
default() {
return []
}
},
// 树属性
obj: {
type: Object,
required: false,
default: () => {
return {
id: 'id', // ID
label: 'name', // 显示名称
children: 'children', // 子级字段名
path: 'path', // 路径
content: 'content', // 描述
pid: 'pid' // 父id
}
}
},
// 配置是否可多选
multiple: {
type: Boolean,
default() {
return false
}
},
// 配置是否可清空选择
clearable: {
type: Boolean,
default() {
return false
}
},
// 配置多选时是否将选中值按文字的形式展示
collapseTags: {
type: Boolean,
default() {
return true
}
},
// 显示复选框情况下,是否严格遵循父子不互相关联
checkStrictly: {
type: Boolean,
default() {
return false
}
},
// 多选是设置点击节点是否可以选中
checkClickNode: {
type: Boolean,
default() {
return false
}
},
// 多选时:点击节点展开还是点三角标
expandClickNode: {
type: Boolean,
default() {
return true
}
},
// 默认选中的节点key
defaultKey: {
type: [Number, String, Array, Object],
default() {
return []
}
},
// 只获取叶子节点
leafOnly: {
type: Boolean,
default() {
return true
}
},
size: {
type: String,
default() {
return 'mini'
}
},
width: {
type: String,
default() {
return '100%'
}
},
height: {
type: String,
default() {
return '300px'
}
},
placeholder: {
type: String,
default() {
return '请选择'
}
}
},
// 上面是父组件可传入参数
data() {
return {
popoverWidth: '0px', // 下拉框大小
isShowSelect: false, // 是否显示树状选择器
options: [], // select option选项
returnDatas: [], // 返回给父组件数组对象
returnDataKeys: this.defaultKey // 返回父组件数组主键值
}
},
computed: {
treeData() {
// 若非树状结构,则转化为树状结构数据
return JSON.stringify(this.data).indexOf(this.obj.children) !== -1
? this.data
: this.switchTree()
}
},
watch: {
treeData: {
handler() {
this.$nextTick(() => {
this.init()
})
},
immediate: true,
deep: true
},
defaultKey: {
handler(val) {
this.returnDataKeys = val
},
immediate: true,
deep: true
}
},
methods: {
init() {
if (this.defaultKey !== undefined && this.defaultKey.length > 0) {
if (this.multiple) {
// 多选
if (Object.prototype.toString.call(this.defaultKey).indexOf('Array') != -1) {
if (Object.prototype.toString.call(this.defaultKey[0]).indexOf('Object') != -1) {
// 对象
this.setDatas(this.defaultKey)
} else if (
Object.prototype.toString.call(this.defaultKey[0]).indexOf('Number') != -1 ||
Object.prototype.toString.call(this.defaultKey[0]).indexOf('String') != -1
) {
this.setKeys(this.defaultKey)
} else {
return
}
} else {
return
}
} else {
// 单选
if (
Object.prototype.toString.call(this.defaultKey).indexOf('Number') != -1 ||
Object.prototype.toString.call(this.defaultKey).indexOf('String') != -1 ||
Object.prototype.toString.call(this.defaultKey).indexOf('Object') != -1
) {
this.setKey(this.defaultKey)
} else {
return
}
}
}
},
// 下拉框select点击[入口]
selectClick() {
var element = document.querySelector('body > div.el-select-dropdown.el-popper.is-multiple')
element.style.display = 'none'
this.$nextTick(function() {
// 设置下拉框自适应宽度
this.popoverWidth = this.$refs.select.$el.clientWidth - 26
})
// 显示下拉框
return (this.isShowSelect = !this.isShowSelect)
},
nodeExpand(data, node) {
this.$refs.dcPopover.updatePopper()
},
checkClick(data, node) {
// 文中,已严格遵循了父子不关联,这里需要判断,如果是父节点勾选,则将判断check节点中有无子节点,如果没有也进行勾选
if (!this.multiple) {
// 单选
this.isShowSelect = false
this.setKey(node.key)
} else {
const node1 = this.$refs.tree.getNode(data.id)
if (!node) {
return
}
if (!node1) {
return
}
if (!node1.checked) {
// 如果有子级,则同步清空
if (data.children) {
let checks = []
const checkedKeys = data.id
checks.push(checkedKeys)
const arr = this.getChildrenKeys(data)
checks = checks.concat(arr)
this.returnDataKeys = this.returnDataKeys.filter(
(x) => !checks.some((item) => x === item)
)
} else {
const index = this.returnDataKeys.findIndex((e) => e === data.id)
this.returnDataKeys.splice(index, 1)
}
} else {
let checksArr = [...this.returnDataKeys]
const checkedKeys = data.id
checksArr.push(checkedKeys)
const arr = this.getChildrenKeys(data)
checksArr = checksArr.concat(arr)
this.returnDataKeys = [...new Set(checksArr)]
}
this.$refs.tree.setCheckedKeys(this.returnDataKeys)
const t = []
this.options = this.returnDataKeys.map((item) => {
// 设置option选项
const node = this.$refs.tree.getNode(item) // 所有被选中的节点对应的node
if (!node) {
return
} else {
t.push(node.data)
return { label: node.label, value: node.key }
}
})
this.returnDatas = t
}
},
// 获取子级及孙子级value
getChildrenKeys(node) {
let ids = []
if (node.children) {
node.children.forEach((e) => {
ids.push(e.id)
if (e.children) {
ids = ids.concat(this.getChildrenKeys(e))
}
})
}
return ids
},
// 树点击方法
nodeClick(data, node) {
if (!this.multiple) {
// 单选
this.isShowSelect = false
this.setKey(node.key)
} else {
// 多选
const checkedKeys = this.$refs.tree.getCheckedKeys(this.leafOnly) // 所有被选中的节点的 key 所组成的数组数据
const t = []
this.options = checkedKeys.map((item) => {
// 设置option选项
const node = this.$refs.tree.getNode(item) // 所有被选中的节点对应的node
if (!node) {
return
} else {
t.push(node.data)
return { label: node.label, value: node.key }
}
})
this.returnDataKeys = this.options.map((item) => {
return item.value
})
this.returnDatas = t
this.$emit('getValue', this.returnDataKeys, this.returnDatas)
}
},
// 单选:清空选中
clean() {
// this.$refs.tree.setCurrentKey(null) //清除树选中key
this.$refs.tree.setCurrentKey([]) // 清除树选中key
this.returnDatas = []
this.returnDataKeys = []
this.clearSelectedNodes()
this.popoverHide()
},
// 单选:设置、初始化值 key
setKey(thisKey) {
this.$refs.tree.setCurrentKey(thisKey)
const node = this.$refs.tree.getNode(thisKey)
this.setData(node.data)
},
// 单选:设置、初始化对象
setData(data) {
this.options = []
this.options.push({
label: data[this.obj.label],
value: data[this.obj.id]
})
this.returnDatas = data
this.returnDataKeys = data[this.obj.id]
},
// 多选:设置、初始化值 keys
setKeys(thisKeys) {
this.$refs.tree.setCheckedKeys(thisKeys)
this.returnDataKeys = thisKeys
const t = []
this.options = thisKeys.map((item) => {
// 设置option选项
const node = this.$refs.tree.getNode(item) // 所有被选中的节点对应的node
if (!node) {
return
} else {
t.push(node.data)
return { label: node.label, value: node.key }
}
})
this.returnDatas = t
this.popoverHide()
},
// 多选:设置、初始化对象
setDatas(data) {
this.$refs.tree.setCheckedNodes(data)
this.returnDatas = data
const t = []
data.map((item) => {
// 设置option选项
t.push(item[this.obj.id])
})
this.returnDataKeys = t
this.popoverHide()
},
// 多选,删除任一select选项的回调
removeTag(val) {
this.$refs.tree.setChecked(val, false) // 设置为未选中
const node = this.$refs.tree.getNode(val) // 获取节点
if (!this.checkStrictly && node.childNodes.length > 0) {
this.treeToList(node).map((item) => {
if (item.childNodes.length <= 0) {
this.$refs.tree.setChecked(item, false)
}
})
}
this.nodeClick()
this.popoverHide()
},
// 点击别处时关闭下拉框
closePopover() {
// if (this.isShowSelect) {
this.isShowSelect = false
if (this.returnDataKeys) {
this.$emit('getValue', this.returnDataKeys, this.returnDatas)
}
// }
},
// 下拉框关闭执行
popoverHide() {
this.$emit('getValue', this.returnDataKeys, this.returnDatas)
},
// 多选,清空所有勾选
clearSelectedNodes() {
const checkedKeys = this.$refs.tree.getCheckedKeys(this.leafOnly) // 所有被选中的节点的 key 所组成的数组数据
for (let i = 0; i < checkedKeys.length; i++) {
this.$refs.tree.setChecked(checkedKeys[i], false)
}
},
// 树形转为集合
treeToList(tree) {
let queen = []
const out = []
queen = queen.concat(tree)
while (queen.length) {
const first = queen.shift()
if (first.childNodes) {
queen = queen.concat(first.childNodes)
}
out.push(first)
}
return out
},
switchTree() {
return this.buildTree(this.data, this.defaultValue)
},
// 将一维的扁平数组转换为多层级对象
buildTree(data, id) {
const fa = (id) => {
const temp = []
for (let i = 0; i < data.length; i++) {
const n = data[i]
if (n[this.obj.pid] === id) {
n[this.obj.children] = fa(n[this.obj.id])
temp.push(n)
}
}
return temp
}
return fa(id)
}
}
}
</script>
<style lang="scss">
.MultipleTree {
.el-tooltip {
overflow: initial;
}
.popoverTag {
text-align: center;
max-height: 200px;
overflow-y: scroll;
.el-tag {
margin-right: 10px;
margin-bottom: 10px;
cursor: pointer;
}
}
}
</style>
效果图: