注意父组件用的vue3语法,子组件用的vue2的语法
封装treeList子组件,直接上代码
<template>
<el-select
:title="multiple? optionData.name : ''"
ref="select"
:value="value"
:placeholder="placeholder"
size="small"
clearable
:disabled="disabled"
:filterable="filterable"
:filter-method="filterMethod"
style="width: 100%;"
@clear="clear"
@visible-change="visibleChange"
>
<el-option
ref="option"
class="tree-select__option"
:value="optionData.id"
:label="optionData.name"
>
<el-tree
ref="tree"
class="tree-select__tree"
:class="`tree-select__tree--${multiple ? 'checked' : 'radio'}`"
:node-key="nodeKey"
:data="data"
:props="props"
:default-expanded-keys="[value]"
:show-checkbox="multiple"
:highlight-current="!multiple"
:expand-on-click-node="multiple"
:filter-node-method="filterNode"
@node-click="handleNodeClick"
@check-change="handleCheckChange"
></el-tree>
</el-option>
</el-select>
</template>
<script>
export default {
name: 'TreeSelect',
props: {
// v-model绑定
value: {
type: [String, Number],
default: ''
},
multiple: {
type: Boolean,
default: false
},
placeholder:{
type: [String, Number],
default: '请选择'
},
// 树形的数据
data: {
type: Array,
default: function () {
return []
}
},
// 每个树节点用来作为唯一标识的属性
nodeKey: {
type: [String, Number],
default: 'id'
},
filterable: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
// tree的props配置
props: {
type: Object,
default: function () {
return {
label: 'label',
children: 'children'
}
}
}
},
data() {
return {
optionData: {
id: '',
name: ''
},
filterFlag: false
}
},
watch: {
value: {
handler(val) {
if (!this.isEmpty(this.data)) {
this.init(val)
}
},
immediate: true
},
data: function (val) {
if (!this.isEmpty(val)) {
this.init(this.value)
}
}
},
created() {},
methods: {
// 是否为空
isEmpty(val) {
for (let key in val) {
return false
}
return true
},
handleNodeClick(data) {
if (this.multiple) {
return
}
this.$emit('input', data[this.nodeKey])
this.$refs.select.visible = false
},
handleCheckChange() {
const nodes = this.$refs.tree.getCheckedNodes()
const value = nodes.map((item) => item[this.nodeKey]).join(',')
this.$emit('myinput', value)
},
init(val) {
// 多选
if (this.multiple) {
const arr = val.toString().split(',')
this.$nextTick(() => {
this.$refs.tree.setCheckedKeys(arr)
const nodes = this.$refs.tree.getCheckedNodes()
this.optionData.id = val
this.optionData.name = nodes
.map((item) => item[this.props.label])
.join(',')
})
}
// 单选
else {
val = val === '' ? null : val
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(val)
if (val === null) {
return
}
const label = this.props.label || 'name'
const node = this.$refs.tree.getNode(val)
this.optionData.id = val
this.optionData[label] = node.label
})
}
},
visibleChange(e) {
if (e) {
const tree = this.$refs.tree
this.filterFlag && tree.filter('')
this.filterFlag = false
let selectDom = null
if(this.multiple) {
selectDom = tree.$el.querySelector('.el-tree-node.is-checked')
} else {
selectDom = tree.$el.querySelector('.is-current')
}
setTimeout(() => {
this.$refs.select.scrollToOption({ $el: selectDom })
}, 0)
}
},
clear() {
this.$emit('input', '')
},
filterMethod(val) {
this.filterFlag = true
this.$refs.tree.filter(val)
},
filterNode(value, data) {
if (!value) return true
const label = this.props.label || 'name'
return data[label].indexOf(value) !== -1
}
}
}
</script>
<style lang="scss">
.tree-select__option {
&.el-select-dropdown__item {
height: auto;
line-height: 1;
padding: 0;
background-color: #fff;
}
}
.tree-select__tree {
padding: 4px 20px;
font-weight: 400;
&.tree-select__tree--radio {
.el-tree-node.is-current > .el-tree-node__content {
// color: $mainColor;
font-weight: 700;
}
}
}
</style>
父组件调用
<treeSelect
class="select-input-btn m-2"
:placeholder="'选择部门'"
:disabled="searchData.TenantId == null ? true : false"
@nodeinput="handleGetSeveralDepartment"
:multiple="false"
v-model="searchDataLabel.DepartmentName"
:filterable="true"
:role="'depart'"
:data="selectData.DepartSelectData"
clearable
/>
</div>
<div class="m-r-10 w-500">
<treeSelect
class="select-input-btn m-2"
:placeholder="'选择人员'"
:disabled="searchData.TenantId == null ? true : false"
@myinput="handleGetSeveralOperationPeople"
:multiple="true"
v-model="searchDataLabel.UsersName"
:filterable="true"
:role="'user'"
:data="selectData.peopleSelectData"
clearable
/>
传递给子组件的下拉列表数组需要组装
// 获取部门方法
function getdeptsFunc(TenantId: number) {
api.myFunction.getdepts({ TenantId: TenantId }).then(res => {
if (res != null && res[0] != null && res[0].status == 200) {
let initDepartArray = res[0].data
// 修改属性为label和id;需要将数据组装成子组件需要的样子,也可以将递归方法放在子组件中,这样无论传什么数据都可以处理
const modifiedArray = initDepartArray.map(item => {
return { label: item.DeptName, id: item.DeptId, ParentId: item.ParentId}
})
// 递归
selectData.DepartSelectData = buildTree(modifiedArray)
cloneDepartData.value = selectData.DepartSelectData
}
})
}
//递归方法
function buildTree(items) {
const result = [] // 树的根节点,即没有父节点的节点
const itemMap = {} // 将每个节点的 id 与节点本身映射起来
// 将每个节点的 id 与节点本身映射起来
items.forEach(item => {
itemMap[item.id] = item
})
// 遍历每个节点,将其添加到父节点的 children 属性中
items.forEach(item => {
const parentItem = itemMap[item.ParentId]
if (parentItem != null) {
// 如果父节点存在,则将当前节点添加到父节点的 children 数组中
;(parentItem.children || (parentItem.children = [])).push(item)
} else {
// 如果父节点不存在,则将当前节点添加到树的根节点中
result.push(item)
}
})
return result
}
// 获取treeSelect选取的值
const handleGetSeveralValue = (value) => {
console.log(value)
//此处通过获取的value得到中文,并赋值即可
searchData.severalValue = value
}
筛选补充: (实现筛选拼音和不区分大小写)
修改treeList子组件,改装筛选函数filterMethod
上代码:注意这里1区分了单层和多层(只需要父组件在调用treeList子组件时传相应的不同的props就可以了,我这里传的是role这个字段,所以判断的是role)
// 筛选
filterTree(node, val) {
if (this.role == 'user') {//只有一层节点
const deptNameMatch = node.data.label.toLowerCase().includes(val.toLowerCase())
const pyNameMatch = node.data.PYName.toLowerCase().includes(val.toLowerCase())
if (deptNameMatch || pyNameMatch) {
node.visible = true
} else {
node.visible = false
}
} else if (this.role == 'depart') {//不知道有多少层节点
const deptNameMatch = node.label.toLowerCase().includes(val.toLowerCase())
const pyNameMatch = node.data.PYName.toLowerCase().includes(val.toLowerCase())
const hasChildNodes = Array.isArray(node.childNodes) && node.childNodes.length > 0
if (deptNameMatch || pyNameMatch) {
node.visible = true
if (hasChildNodes) {
return node.childNodes.forEach(childNode => {
childNode.visible = true
})
}
} else {
node.visible = false
}
if (hasChildNodes) {
node.childNodes.forEach(childNode => {
this.filterTree(childNode, val)
if (childNode.visible) {
node.visible = true // 如果子节点可见,则将父节点也设置为可见
}
})
}
}
},
filterMethod(val) {
this.filterFlag = true
this.$refs.tree.store.root.childNodes.forEach(node => {
this.filterTree(node, val)
})
},
// 筛选end
父组件:在组装传递给子组件的数据时,需要将后端给的label和拼音名都传递给子组件,以下是完整代码
// 获取部门方法
function getdeptsFunc(TenantId: number) {
api.myFunction.getdepts({ TenantId: TenantId }).then(res => {
if (res != null && res[0] != null && res[0].status == 200) {
let initDepartArray = res[0].data
// 修改属性为label和id,PYName为拼音数据;需要将数据组装成子组件需要的样子,也可以将递归方法放在子组件中,这样无论传什么数据都可以处理
const modifiedArray = initDepartArray.map(item => {
return { label: item.DeptName, id: item.DeptId, ParentId: item.ParentId, PYName: item.PYName }
})
// 递归
selectData.DepartSelectData = buildTree(modifiedArray)
cloneDepartData.value = selectData.DepartSelectData
}
})
}
//递归方法
function buildTree(items) {
const result = [] // 树的根节点,即没有父节点的节点
const itemMap = {} // 将每个节点的 id 与节点本身映射起来
// 将每个节点的 id 与节点本身映射起来
items.forEach(item => {
itemMap[item.id] = item
})
// 遍历每个节点,将其添加到父节点的 children 属性中
items.forEach(item => {
const parentItem = itemMap[item.ParentId]
if (parentItem != null) {
// 如果父节点存在,则将当前节点添加到父节点的 children 数组中
;(parentItem.children || (parentItem.children = [])).push(item)
} else {
// 如果父节点不存在,则将当前节点添加到树的根节点中
result.push(item)
}
// 将PYName属性传递给子节点
if (item.children && item.children.length > 0) {
item.children.forEach(child => {
child.PYName = item.PYName
})
}
})
return result
}