父组件使用
<template><div>{{ array }} 更多属性详见wgyTreeSelect组件<wgyTreeSelectv-model="array":list="list":multiple="true":disabled-ids="[111,113,2]"/></div>
</template><script>
/* 注意: 默认是这种结构id: 'id', // IDlabel: 'name', // 显示名称children: 'children', // 子级字段名path: 'path', // 路径content: 'content', // 描述pid: 'pid', // 父id如果不是这种结构传入obj定义
*/
export default {data() {return {array: [],//数据源list: [{id: '1', // IDname: '上海市', // 显示名称children: [{id: '2', // IDname: '嘉定区', // 显示名称children: [{id: '3', // IDname: '江桥镇11111111111111111111111111111', // 显示名称content: '嘉定', // 描述pid: '2', // 父id},{id: '4', // IDname: '安亭镇', // 显示名称content: '安亭', // 描述pid: '2', // 父id},], // 子级字段名content: '嘉定', // 描述pid: '1', // 父id},{id: '2000000', // IDname: '嘉定2区', // 显示名称children: [{id: '3876543', // IDname: '江桥2镇11111111111111111111111111111', // 显示名称content: '嘉定', // 描述pid: '2000000', // 父id},], // 子级字段名content: '嘉定', // 描述pid: '1', // 父id},], // 子级字段名content: '上海魔都', // 描述pid: '0', // 父id},{id: '11', // IDname: '北京市', // 显示名称children: [{id: '12', // IDname: '朝阳区', // 显示名称children: [{id: '13', // IDname: '三里屯', // 显示名称content: '三里屯', // 描述pid: '12', // 父id},{id: '111', // IDname: '四里屯--------------', // 显示名称pid: '12', // 父id},{id: '113', // IDname: '五里屯--------------', // 显示名称pid: '12', // 父id},{id: '123', // IDname: '六里屯--------------', // 显示名称pid: '12', // 父id},{id: '11111', // IDname: '七里屯--------------', // 显示名称pid: '12', // 父id},{id: '11311', // IDname: '八里屯--------------', // 显示名称pid: '12', // 父id},{id: '12312', // IDname: '九里屯--------------', // 显示名称pid: '12', // 父id},{id: '11113331', // IDname: '十里屯--------------', // 显示名称pid: '12', // 父id},{id: '11344411', // IDname: '十一里屯--------------', // 显示名称pid: '12', // 父id},{id: '12555312', // IDname: '十二里屯--------------', // 显示名称pid: '12', // 父id},{id: '14', // IDname: '左家庄--------------', // 显示名称content: '左家庄', // 描述pid: '12', // 父id},], // 子级字段名content: '朝阳', // 描述pid: '11', // 父id},], // 子级字段名content: '北京', // 描述pid: '0', // 父id},],};},
}
</script>
子组件
<template><div><el-popoverv-model="isShowSelect"placement="bottom-start":width="popoverWidth":close-on-click-modal="false"trigger="manual"@hide="popoverHide"><el-treeref="tree"v-bind="$attrs"class="common-tree":width="width":data="treeData":props="obj":show-checkbox="multiple":node-key="obj.id":check-strictly="checkStrictly":default-expanded-keys="defaultKeys":expand-on-click-node="multiple&&expandClickNode":check-on-click-node="checkClickNode":highlight-current="true"@check-change="nodeClick"@node-click="nodeClick"/><el-selectslot="reference"ref="select"v-bind="$attrs"v-model="returnDataKeys":size="size":width="width":multiple="multiple":clearable="clearable":collapse-tags="collapseTags"class="tree-select"@click.native="selectClick"@remove-tag="removeTag"@clear="clear"@mouseenter.native="showCloseIcon = true"@mouseleave.native="showCloseIcon = false"><el-optionv-for="item in options":key="item.value":label="item.label":value="item.value"/><!-- 这里是删除整个下拉框的内容,多选的时候,是没有这个功能的,所有自己弄了一个icon --><templateslot="prefix"><iv-if="multiple && clearable && returnDataKeys.length && showCloseIcon"class="el-icon-close"@click.stop="clearSelectedNodes"></i><i></i></template></el-select></el-popover></div>
</template><script>
export default {name: 'TreeSelect',props: {// 绑定的值value: {tyep: Object,},// 树结构数据list: {type: Array,default: () => ([]),},obj: {type: Object,required: false,default: () => ({id: 'id', // IDlabel: 'name', // 显示名称children: 'children', // 子级字段名path: 'path', // 路径content: 'content', // 描述pid: 'pid', // 父id}),},// 配置是否可多选multiple: {type: Boolean,default: false,},// 配置是否可清空选择,只对单选时生效, 多选时自定义清空按钮clearable: {type: Boolean,default: true,},// 配置多选时是否将选中值按文字的形式展示collapseTags: {type: Boolean,default: false,},// 显示复选框情况下,是否严格遵循父子不互相关联checkStrictly: {type: Boolean,default: false,},// 多选时设置点击节点是否可以选中,false是只有点击多选框才选中,点击元素不选中checkClickNode: {type: Boolean,default: false,},// 多选时:点击节点展开还是点三角标,true是点击节点展开,false是点击三角形展开expandClickNode: {type: Boolean,default: false,},size: {type: String,default: 'small',},width: {type: String,default: '200px',},// 父节点全选时, 是否只展示子节点onlyLeaf: {type: Boolean,default: false,},// 父节点全选时,是否只展示父节点mergeTag: {type: Boolean,default: false,},// 外部点击是否收起树形, 默认收起closeOnOutsideClick: {type: Boolean,default: true,},// 禁止选中的数据disabledIds: {type: Array,default: () => ([]),},},// 上面是父组件可传入参数data() {return {defaultKeys: [], // 默认展开的节点defaultKey: '',first: false, //popoverWidth: '0px', // 下拉框大小isShowSelect: false, // 是否显示树状选择器options: [], // select option选项returnDatas: [], // 返回给父组件数组对象returnDataKeys: [], // 返回父组件数组主键值showCloseIcon: false, // 清空icon};},computed: {treeData() {// 判断传入的数据是不是树形结构const isTreeStructure = JSON.stringify(this.list).indexOf(this.obj.children) !== -1;// 是树形结构,就返回传入的数据, 不是的话格式化成树形结构return isTreeStructure ? this.list : this.toTreeStructure(this.list);},},watch: {// 是否显示树状选择器isShowSelect() {// 隐藏select自带的下拉框this.$refs.select.blur();},// 监听tree数据treeData() {this.$nextTick(() => {this.init();});},// 监听value从新赋值value: {handler(val) {this.$nextTick(() => {if (this.multiple) {this.defaultKeys = val;} else {this.defaultKey = val;}this.init();});},immediate: true,},// 监听选中的值returnDataKeys: {handler(val) {if (this.first || val) {this.first = true;this.$emit('input', val);} else {this.first = true;}},},// 监听禁用数组disabledIds: {handler(val) {console.log(val);this.addDisabledProperty(this.list, val);},},},mounted() {if (this.closeOnOutsideClick) {document.addEventListener('click', this.handleClickOutside);}},beforeDestroy() {if (this.closeOnOutsideClick) {document.removeEventListener('click', this.handleClickOutside);}},methods: {// 传入数据类型不是树形结构,转成树形结构toTreeStructure(list) {console.log(list);const map = {}; let node; const roots = []; leti;for (i = 0; i < list.length; i += 1) {map[list[i].id] = i; // 初始化maplist[i].children = []; // 初始化children}for (i = 0; i < list.length; i += 1) {node = list[i];if (node.pid !== '0') {// 如果有父级list[map[node.pid]].children.push(node);} else {// 如果没有父级,则为根节点roots.push(node);}}return roots;},// 点击其他元素, 树隐藏handleClickOutside(event) {if (!this.$refs.select?.$el?.contains(event.target)) {this.isShowSelect = false;}},init() {// 如果是多选,if (this.multiple) {// 且默认展开的节点大于0if (Array.isArray(this.defaultKeys) && this.defaultKeys.length > 0) {// 检测this.defaultKeys[0]是否是一个对象。if (Object.prototype.toString.call(this.defaultKeys[0]).indexOf('Object') !== -1) { // 对象this.setDatas(this.defaultKeys);// 检测this.defaultKeys[0]是否是一个数字或者字符串。} else if (Object.prototype.toString.call(this.defaultKeys[0]).indexOf('Number') !== -1|| Object.prototype.toString.call(this.defaultKeys[0]).indexOf('String') !== -1) {this.setKeys(this.defaultKeys);} else {console.log('多选:传入参数类型不匹配');}}} 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 {console.log('单选:传入参数类型不匹配');}}},// 下拉框select点击[入口]selectClick() {this.isShowSelect = !this.isShowSelect;},// 节点被点击 nodeClick(a, node) {// 单选if (!this.multiple) {this.isShowSelect = false;this.setKey(node.key);// 多选} else {// 所有被选中的节点的 key 所组成的数组数据const checkedKeys = this.$refs.tree.getCheckedKeys();let selectedNodes = checkedKeys.map((item) => {// 所有被选中的节点对应的nodeconst { data, label, key } = this.$refs.tree.getNode(item);return { label, value: key, data };});// 如果onlyLeaf为true,mergeTag为false只保留叶子节点if (this.onlyLeaf && !this.mergeTag) {selectedNodes = selectedNodes.filter((n) => !n.data.children);}// 如果mergeTag为true,onlyLeaf为false只保留父节点if (this.mergeTag && !this.onlyLeaf) {selectedNodes = selectedNodes.filter((n) => {// 判断当前节点是否有父节点if (n.data.pid) {// 判断当前节点的父节点是否也在selectedNodes中const parentInSelectedNodes = selectedNodes.some((upN) => upN.data.id === n.data.pid);// 如果父节点也在selectedNodes中,就将当前节点从selectedNodes中移除return !parentInSelectedNodes;}return true;});}// 设置option选项this.options = selectedNodes;this.returnDataKeys = selectedNodes.map((item) => item.value);this.returnDatas = selectedNodes.map((n) => n.data);}},// 单选:清空选中clear() {this.$refs.tree.setCurrentKey(null);// 清除树选中keythis.returnDatas = null;this.returnDataKeys = '';this.popoverHide();this.isShowSelect = false;},// 单选:设置、初始化值 keysetKey(thisKey) {if (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];},// 多选:设置、初始化值 keyssetKeys(checkedKeys) {// 给树状选择器设置选中的节点。 给select赋值this.$refs.tree.setCheckedKeys(checkedKeys);this.returnDataKeys = checkedKeys;const selectedNodes = checkedKeys.map((item) => {// 所有被选中的节点对应的nodeconst node = this.$refs.tree.getNode(item);return { label: node.label, value: node.key, data: node.data };});this.returnDatas = selectedNodes.map((node) => node.data);this.popoverHide();},// 多选:设置、初始化对象 setDatas(data) {// 获取选中节点的主键值const checkedKeys = data.map((item) => item[this.obj.id]);// 设置树状选择器的选中节点this.$refs.tree.setCheckedKeys(checkedKeys);// 设置返回给父组件的数组对象this.returnDatas = data;this.returnDataKeys = checkedKeys;this.popoverHide();},// 多选模式下,当删除选中的节点时,将该节点及其子节点设置为未选中状态,并更新选中的节点。 okremoveTag(val) {// 获取删除的节点const node = this.$refs.tree.getNode(val);// 判断获取的节点是否为叶子节点const isLeafNode = node.childNodes.length === 0;// 是叶子节点,那么只需要将该节点设置为未选中状态// 不是叶子节点,那么需要将该节点及其所有子节点设置为未选中状态const nodesToUncheck = isLeafNode ? [node] : this.treeToList(node);// 将每个节点设置为未选中状态。nodesToUncheck.forEach((n) => this.$refs.tree.setChecked(n, false));// 更新选中的节点。this.nodeClick();this.popoverHide();},// 下拉框关闭执行popoverHide() {this.$emit('getValue', this.returnDataKeys, this.returnDatas);},// 多选,清空所有被选中的节点clearSelectedNodes() {this.$refs.tree.setCheckedKeys([]);},// 树形转为集合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;},// 禁用某个节点addDisabledProperty(data, ids) {data.forEach((item) => {if (ids.includes(+item.id)) {item.disabled = true;}if (item.children) {this.addDisabledProperty(item.children, ids);}});},},
};
</script><style scoped lang="scss">
::v-deep .el-input__prefix{position: absolute;height: 100%;right: 5px;.el-icon-close {position: absolute;right: 6px;top: 11px;z-index: 1;border-radius:50%;color: #fff;background-color: #B0B3B8;}
}.mask{height: 100%;position: fixed;top: 0;left: 0;opacity: 0;z-index: 11;}.common-tree{overflow: auto;}.tree-select{position: relative;z-index: 111;}.ok{float: right;}.el-row{padding-top: 0px !important;}
</style>