注释:代码仅供参考
<el-form-item label="设备" prop="deviceCodeList">
<selectTree class="parentC" :value="'groupCode'" :data="options" v-model="ruleForm.deviceCodeList"
:props="props" :show-checkbox="true" :filterable="true" :collapse-tags="true" :placeholder="'请选择设备'"
:lazy="true" :default-expand-all="true" :width="287" />
</el-form-item>
import selectTree from './selectTree.vue';
<!--
* @description: 通用树型下拉框
* @fileName: treeSelect.vue
* @author: t
* @date: 2023-03-07 17:15:03
* @Attributes: data 展示数据 array
props 配置选项,具体配置可以参照element ui库中el-tree的配置 object
show-checkbox 节点是否可被选择 boolean
check-strictly 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false boolean
icon-class 自定义树节点的图标 string
load 加载子树数据的方法,仅当 lazy 属性为true 时生效 function(node, resolve)
lazy 是否懒加载子节点,需与 load 方法结合使用 boolean
disabled 下拉框是否禁用
getCheckedKeys 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点的 key 所组成的数组
getCurrentNode 获取当前被选中节点的 data,若没有节点被选中则返回 null
collapse-tags 多选时是否将选中值按文字的形式展示
select-last-node 单选时是否只能选择最后一个节点
Scoped Slot 自定义树节点的内容,参数为 { node, data }
show-count 若节点中存在children 则在父节点展示所属children的数量,注意但设置插槽时 show-count将失效!
clearable 单选时是否可以清空选项
filterable 属性即可启用搜索功
!!!!!必传!!!!!! props-value 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 !!!!!!
!-->
<!--:clearable="$attrs['show-checkbox']==undefined?$attrs['clearable']:false"-->
<template>
<el-select class="selectTree" :style="`width:${width}px;overflow-y: auto`" :value="valueFilter(value)"
:placeholder="$attrs['placeholder']" :multiple="$attrs['show-checkbox']" :disabled="$attrs['disabled']"
:filterable="$attrs['filterable']" :clearable="$attrs['clearable']" :collapse-tags="$attrs['collapse-tags']"
@change="selectChange" @clear="selectClear" ref="mySelect" :filter-method="remoteMethod">
<template slot="empty">
<div class="selecTree">
<el-tree :load="loadRegionNode" :data="treeData" :props="props" @node-click="handleNodeClick"
:show-checkbox="$attrs['show-checkbox']" :check-strictly="$attrs['check-strictly']"
:icon-class="$attrs['icon-class']" :lazy="$attrs['lazy']" :node-key="props.value"
:filter-node-method="filterNode" @check-change="handleCheckChange"
:default-expanded-keys="defaultExpandedKeys" :default-expand-all="$attrs['default-expand-all']"
ref="myTree">
<!-- highlight-current -->
<template slot-scope="{ node, data }">
<slot :node="node" :data="data">
<span class="slotSpan">
<span>
{{ data[props.label] }}
<b v-if="$attrs['show-count'] != undefined && data[props.children]">({{
data[props.children].length }})</b>
</span>
</span>
</slot>
</template>
</el-tree>
</div>
</template>
</el-select>
</template>
<script>
import { getTreeList, getDeviceList } from '@/api/aiproductmgt/quipment';
export default {
props: {
width: {
type: Number,
default: 200,
},
value: {
type: undefined,
default: null,
},
data: {
type: Array,
default: null,
},
props: {
type: Object,
default: null,
},
},
data() {
return {
searchVal: '',
treeData: [],
defaultExpandedKeys: [],
};
},
created() {
this.queryRegionGroupList()
this.propsInit();
},
mounted() {
setTimeout(this.initData, 10);
},
beforeUpdate() {
this.propsInit();
this.initData();
},
methods: {
/**
* 懒加载树节点
* @param {Obejct} node 用户点击的树节点
* @param {Function} resolve 异步数据后的回调
*/
loadRegionNode(node, resolve) {
// 层级为0时
if (node.level === 0) {
return resolve(this.treeData);
} else {
this.queryRegionGroupList(node?.data, resolve);
}
},
/**
* 查询设备分组列表
* @param {String} loadRegionNode 异步展开的父节点的属性 首次查询时不传
* @param {Function} resolve 懒加载时需要resolve的数据
*/
queryRegionGroupList(loadRegionNode, resolve) {
const data = {
pageSize: 200000,
pageNo: 1,
groupCode: loadRegionNode ? loadRegionNode?.groupCode : '0',
getGroupOnlineDeviceCount: '1',
};
const list = []
if (data.groupCode == undefined || data.groupCode == '') {
return;
} else {
getTreeList(data).then((res) => {
const groupList = res.data.groupList || [];
const deviceList = res.data.deviceList || [];
groupList.forEach((item) => {
item.label = item.groupName;
item.value = item.groupCode;
});
// deviceList.forEach((item) => {
// item.label = item.deviceName;
// item.value = item.deviceGroupCode;
// groupName = item.deviceName;
// groupCode = item.deviceGroupCode;
// // item.isEquipment = true;
// // item.isLeaf = true;
// });
let arr = []
for (let i = 0; i < deviceList.length; i++) {
arr.push({
label: deviceList[i].deviceName,
value: deviceList[i].deviceGroupCode,
groupName: deviceList[i].deviceName,
groupCode: deviceList[i].deviceUuid,
deviceGroupCode: deviceList[i].deviceGroupCode,
})
}
const list = groupList.concat(arr);
// const list = groupList;
resolve && resolve(list);
if (loadRegionNode) {
loadRegionNode.children = list;
} else {
// 第一次加载或者搜索加载
this.treeData = this.searchVal ? this.arrayToTree(list) : list;
}
});
}
},
/**
* 扁平化数据转换为想要的树结构
*/
// arrayToTree(arr) {
// if (!Array.isArray(arr)) {
// return [];
// }
// const obj = {};
// arr.forEach((item) => {
// // 做字典
// obj[item.regionCode] = item;
// });
// const targetArr = [];
// arr.forEach((item) => {
// const parent = obj[item.parentGroupCode];
// if (parent) {
// parent.children = parent.children || [];
// parent.children.push(item);
// } else {
// targetArr.push(item);
// }
// });
// return targetArr;
// },
initData() {
if (this.$attrs['show-checkbox'] === undefined) {
let newItem = this.recurrenceQuery(this.treeData, this.props.value, this.value);
if (newItem.length) {
if (this.props.value && newItem[0][this.props.value]) {
this.defaultExpandedKeys = [newItem[0][this.props.value]];
}
this.$nextTick(() => {
this.$refs.myTree.setCurrentNode(newItem[0]);
});
}
} else {
let newValue = JSON.parse(JSON.stringify(this.value));
if (!(newValue instanceof Array)) {
newValue = [newValue];
}
if (newValue?.length) {
let checkList = newValue.map(key => {
if (key) {
let newItem = this.recurrenceQuery(this.treeData, this.props.value, key);
return newItem[0] || '';
}
});
if (checkList?.length) {
let defaultExpandedKeys = checkList.map(item => item?.[this.props.value || '']);
if (defaultExpandedKeys.length) this.defaultExpandedKeys = defaultExpandedKeys;
this.$nextTick(() => {
this.$refs.myTree.setCheckedNodes(checkList);
});
}
}
}
this.$forceUpdate();
},
// 多选
handleCheckChange(data, e, ev) {
let checkList = this.$refs.myTree.getCheckedNodes();
let setList = null;
if (checkList.length) {
setList = checkList.map(item => item[this.props.value]);
}
this.$emit('input', setList);
// 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点
this.$emit('change', data, e, ev);
},
// 单选事件
handleNodeClick(data, e) {
if (!(this.$attrs['select-last-node'] === undefined)) {
if (data[this.props.children] && data[this.props.children]?.length) {
return false;
}
}
if (this.$attrs['show-checkbox'] === undefined) {
this.$emit('input', data[this.props.value]);
this.$refs.mySelect.blur();
}
this.$emit('change', data, e);
},
// 递归查找通用方法
recurrenceQuery(list, key, value) {
if (!list || !key || !value) return [];
let queryData = [];
list.map(item => {
if (item[this.props.children] && item[this.props.children].length) {
queryData.push(...this.recurrenceQuery(item[this.props.children], key, value));
}
if (item[key] == value) {
queryData.push(item);
}
return item;
});
return queryData;
},
selectChange(e) {
if (this.$attrs['show-checkbox'] !== undefined) {
let checkList = e.map(key => {
let newItem = this.recurrenceQuery(this.treeData, this.props.label, key);
return newItem[0] || '';
});
this.$refs.myTree.setCheckedNodes(checkList);
this.$emit('input', e);
}
},
selectClear(flag) {
if (this.$attrs['show-checkbox'] === undefined) {
if (!flag) this.$emit('input', '');
this.$refs.myTree.setCurrentKey(null);
} else {
if (!flag) this.$emit('input', []);
this.$refs.myTree.setCheckedKeys([]);
}
this.remoteMethod('');
},
getCheckedNodes() {
if (this.value !== null && this.value !== undefined && this.value !== '') {
return this.$refs.myTree.getCheckedNodes();
}
return [];
},
getCurrentNode() {
if (this.value !== null && this.value !== undefined && this.value !== '') {
return this.$refs.myTree.getCurrentNode();
}
return null;
},
valueFilter(val) {
if (this.$attrs['show-checkbox'] === undefined) {
let res = '';
[res] = this.recurrenceQuery(this.treeData, this.props.value, val);
return res?.[this.props.label] || '';
} else {
if (!val?.length) return [];
let res = val.map(item => {
let [newItem] = this.recurrenceQuery(this.treeData, this.props.value, item);
return newItem?.[this.props.label] || '';
});
if (!res?.length) return [];
res = res.filter(item => item);
return res;
}
},
propsInit() {
this.props.label = this.props.label || 'label';
this.props.value = this.props.value || 'value';
this.props.children = this.props.children || 'children';
if (this.$attrs['select-last-node'] !== undefined && !this.props.disabled) {
this.props.disabled = data => data?.[this.props.children]?.length;
this.$attrs['check-strictly'] = true;
}
},
remoteMethod(query) {
this.$refs.myTree.filter(query);
},
filterNode(value, data) {
if (!value) return true;
return data[this.props.label].indexOf(value) !== -1;
},
},
watch: {
value: {
deep: true,
handler(val) {
if (!val || !val?.length) {
this.selectClear(true);
}
},
},
},
};
</script>
<style scoped lang="scss">
.selectTree {
::v-deep {
.el-select__tags {
max-width: 250px !important;
display: inline-block;
span {
display: flex !important;
}
}
.el-select .el-select__tags span {
display: flex !important;
}
}
}
.selecTree {
min-height: 250px;
overflow: auto;
padding: 0px;
::v-deep {
.el-tree {
width: 290px;
max-height: 250px;
}
.el-tree-node__content {
width: 100%;
}
}
}
// .el-select {
// width: 100%;
// }
.slotSpan {
font-size: 14px;
b {
font-weight: normal;
font-size: 12px;
color: #999;
}
}
.selecTree ::v-deep .el-tree-node__content {
font-size: 14px;
}
</style>