文章目录
- 功能描述
-
- 单选:
- 多选:
- 代码
-
- treeSelect.vue
- treeSelectDemo.vue
功能描述
单选:
- 默认初始值;
- select输入框清空功能;
- value数据双向绑定;

多选:
- 默认初始值;
- select输入框清空功能;
- select输入框中的×删除勾选;
- 支持角色菜单控制功能:check-strictly=true
- 当点击勾选复选框时候,若状态为 选中:
- 其所有父节点 (父节点、父节点的父节点以此类推)全部统一跟随当前节点变化为选中;
- 其所有子节点不跟随当前节点变化
- 当点击勾选复选框时候,若状态为 未选中:
- 其所有父节点 不跟随当前节点变化;
- 其所有子节点 全部统一跟随当前节点变化为 未选中;
- 当点击勾选复选框时候,若状态为 选中:
check-strictly=false:
从check-strictly=false父子互相关联的基础入手,需要解决的问题就是:
将尚未全部勾选的子节点对应的父节点改为半勾选状态,查找文档良久无果;
只有getHalfCheckedKeys和getHalfCheckedNodes,并没有设置成半勾选。


代码
treeSelect.vue
<!--* @Description: 树形选择器* @Author: HMM* @Date: 2021-05-24 10:27:34* @FilePath: \components\tree-select.vue
-->
<template><div class="treeSelect"><el-select:value="valueTitle":multiple="multiple":collapse-tags="collapse":clearable="clearable":disabled="disabled":size="size":style="selectStyle"@input="$emit('input',$event)"@clear="handleClear"@remove-tag="handleRemoveTag"><el-option :value="valueId" :label="label"><el-treeid="tree-option"ref="selectTree":accordion="accordion":show-checkbox="multiple":data="options":props="treeProps":node-key="treeProps.id":check-strictly="checkStrictly":default-expand-all="expand":auto-expand-parent="expandParent":expand-on-click-node="expandNode":default-expanded-keys="defaultExpandedKeys"@node-click="handleNodeClick"@check-change="handleCheckChange"></el-tree></el-option></el-select></div>
</template><style lang="scss" scoped>.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {height: auto;// max-height: 274px;padding: 0;overflow: hidden;// overflow: hidden;// overflow-y: auto;
}.el-scrollbar .el-select-dropdown__item.selected {font-weight: normal;
}// 横向滚动条
.el-scrollbar__bar.is-horizontal {height: 6px;left: 2px;
}
// 纵向滚动条
.el-scrollbar__bar.is-vertical {width: 6px;top: 2px;
}// 字体和大小
.custom-tree-node {font-family:"Microsoft YaHei";font-size: 14px;position: relative;
}// 原生el-tree-node的div是块级元素,需要改为inline-block,才能显示滚动条
.treeSelect .el-tree >.el-tree-node {display: inline-block;min-width: 100%;
}// ul li ::v-deep .el-tree .el-tree-node__content {
// height: auto;
// padding: 0 20px;
// }// .el-tree-node__label {
// font-weight: normal;
// }.el-tree ::v-deep .is-current .el-tree-node__label {color: #1B65B9;font-weight: 700;
}.el-tree ::v-deep .is-current .el-tree-node__children .el-tree-node__label {color: #606266;font-weight: normal;
}
</style><script>
export default {name: 'tree-select',props:{// 是否可多选,默认单选multiple: {type: Boolean,default: false},// 可清空选项clearable:{type:Boolean,default:() => { return true }},// -------------------- el-tree --------------------// 配置项treeProps:{type: Object,default:() => {return {id:'id', // ID字段名label: 'title', // 显示名称children: 'children' // 子级字段名}}},// 选项列表数据(树形结构的对象数组)options:{type: Array,default: () => { return [] }},// 自动收起accordion:{type:Boolean,default:() => { return false }},// 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 falsecheckStrictly:{type:Boolean,default:() => { return false }},// 是否展开所有节点,默认展开expand: {type: Boolean,default() {return true;}},// 展开子节点的时候是否自动展开父节点 默认值为 trueexpandParent:{type: Boolean,default() {return true;}},// 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。expandNode: {type: Boolean,default() {return true;}},// -------------------- el-select --------------------// 初始值 - 绑定value是为了外面也可以传值改变到里面的值双向绑定value: {type: [String, Number, Boolean, Array],default: () => { return null }},// 多选时是否将选中值按文字的形式展示collapse: {type: Boolean,default: false},// 选择框大小size:{type:String,default:() => { return 'small' }},// 选择框 宽度width: {type: String,default: '270px'},// 是否禁用disabled:{type:Boolean,default:() => { return false }}},data() {return {valueId: this.value, // 初始值valueTitle: '',label:'', // 分组defaultExpandedKeys:[]}},mounted(){this.initHandle();},methods: {/*** @description: 初始化组件* @param {*}* @return {*}*/initHandle(){// 单选if(this.valueId && !this.multiple){var node = this.$refs.selectTree.getNode(this.valueId);if(node){this.valueTitle = node.data[this.treeProps.label]; // 初始化显示this.defaultExpandedKeys = [this.valueId]; // 设置默认展开this.$nextTick(() => {this.$refs.selectTree.setCurrentKey(this.valueId); // 设置默认选中})}}// 多选if(this.valueId && this.multiple){this.defaultExpandedKeys = this.valueId; // 设置默认展开this.$nextTick(() => {this.$refs.selectTree.setCheckedKeys(this.valueId); // 设置默认选中})}},/*** @description: 单选 - 节点被点击时的回调,返回被点击的节点数据* @param {*}* @return {*}*/handleNodeClick(node){this.valueTitle = node[this.treeProps.label];this.valueId = node[this.treeProps.id];this.defaultExpandedKey = [];this.$emit('getValue', this.valueId, this.valueTitle);},/*** @description: 多选,节点勾选状态发生变化时的回调* @param {*}* @return {*}*/handleCheckChange (data, checked) {let currentNode = this.$refs.selectTree.getNode(data);if(this.checkStrictly){// 用于:父子节点严格互不关联时,父节点勾选变化时通知子节点同步变化,实现单向关联if(checked) {// 选中 子节点只要被选中父节点就被选中this.parentNodeChange(currentNode);} else {// 未选中 处理子节点全部未选中this.childNodeChange(currentNode);}}// 用于:父子节点严格关联时// console.log(this.$refs.selectTree.getHalfCheckedKeys())this.valueId = this.$refs.selectTree.getCheckedKeys();var checkedNodes = this.$refs.selectTree.getCheckedNodes();this.valueTitle = checkedNodes.map((node) => {return node[this.treeProps.label];});this.defaultExpandedKeys = [];this.$emit('getValue', this.valueId, this.valueTitle);},/*** @description: 清除选中* @param {*}* @return {*}*/handleClear(){this.valueTitle = '';this.valueId = '';this.defaultExpandedKeys = [];// 清除树if(this.multiple){this.$refs.selectTree.setCheckedKeys(null);} else {this.$refs.selectTree.setCurrentKey(null);}this.$emit('getValue', null);},/*** @description: 多选 删除任一标签选项的回调* @param {*}* @return {*}*/handleRemoveTag(val){var checkedNodes = this.$refs.selectTree.getCheckedNodes();var node = checkedNodes.find(node => node[this.treeProps.label] === val);this.$refs.selectTree.setChecked(node[this.treeProps.id], false);},// 统一处理子节点为不选中childNodeChange (node) {for(let i = 0; i < node.childNodes.length; i++) {node.childNodes[i].checked = false;this.childNodeChange(node.childNodes[i]);}},// 统一处理父节点为选中parentNodeChange (node) {if(node.parent.key !== undefined) {node.parent.checked = true;this.parentNodeChange(node.parent);}}},watch: {value(){this.valueId = this.valuethis.initHandle()},// 父子组件双向绑定valuevalueId(){this.$emit('input', this.valueId);}},computed:{selectStyle() {return {width: `${this.width}`};}}
};
</script>
treeSelectDemo.vue
<!--* @Description: 树形下拉框 - 使用示例* @Author: HMM* @Date: 2021-01-14 16:04:43* @FilePath: \treeSelectDemo.vue
--><template><div class="messageDemo"><el-container><el-main><treeSelect:treeProps="props":options="treeSelectList"v-model="valueId":clearable="isClearable":accordion="isAccordion":expandNode="false"size="small"width="100%"@getValue="getValue($event)"/></el-main><el-main><span>父子不互相关联:check-strictly=true</span><treeSelect:multiple="true":collapse="false":checkStrictly="true":treeProps="props":options="treeSelectList"v-model="valueIds":clearable="isClearable":accordion="isAccordion":expandNode="false"size="small"width="360px"@getValue="getValue2($event)"/></el-main><el-main><span>父子互相关联:check-strictly=false</span><treeSelect:multiple="true":collapse="false":treeProps="props":options="treeSelectList"v-model="valueIds2":clearable="isClearable":accordion="isAccordion":expandNode="false"size="small"width="360px"@getValue="getValue2($event)"/></el-main></el-container></div>
</template><!-- JS -->
<script>
import treeSelect from '../components/tree-select';
export default {name:'demo',components: {treeSelect},data() {return {props:{ // 配置项(必选)id: 'id',label: 'name',pid: 'parentId',children: 'children'// disabled:true},// 数组list: [{id:1, parentId:0, name:'一级菜单A', rank:1},{id:2, parentId:0, name:'一级菜单B', rank:1},{id:3, parentId:0, name:'一级菜单C', rank:1},{id:4, parentId:1, name:'二级菜单A-A', rank:2},{id:5, parentId:1, name:'二级菜单A-B', rank:2},{id:6, parentId:2, name:'二级菜单B-A', rank:2},{id:7, parentId:4, name:'三级菜单A-A-A', rank:3},{id:15, parentId:0, name:'一级菜单C', rank:1},{id:16, parentId:0, name:'一级菜单C', rank:1},{id:17, parentId:0, name:'一级菜单C', rank:1},{id:18, parentId:0, name:'一级菜单C', rank:1}],treeSelectList:[],isClearable:true, // 可清空(可选)isAccordion:false, // 可收起(可选)valueId:null, // 初始ID(可选)valueIds:[],valueIds2:[]}},created(){this.initData();},methods: {initData(){this.treeSelectList = this.listToTree(this.list, this.props);console.log(this.treeSelectList);},/*** @description 数组转树形数据* @param {数据数组} list* @param {树结构配置} config*/listToTree(list, config) {let conf = {};Object.assign(conf, config);const nodeMap = new Map();const result = [];const { id, children, pid } = conf;for(const node of list) {// node[children] = node[children] || [];nodeMap.set(node[id], node);}for(const node of list) {const parent = nodeMap.get(node[pid]);(parent ? (parent.children ? parent.children : parent.children = []) : result).push(node);}return result;},// 树形选择器 - 取值getValue(value){// this.valueId = valueconsole.log('getValue', value, this.valueId);},// 树形选择器 - 取值getValue2(value){// this.valueId = valueconsole.log('getValue2', value, this.valueIds);}},watch:{valueId(){console.log('valueId', this.valueId);},valueIds(){console.log('valueIds', this.valueIds);}}}
</script>
参考:
基于Element-UI的组件改造的树形选择器(树形下拉框)
Element-UI二次封装实现TreeSelect 树形下拉选择组件
element ui的el-tree多选树(复选框)父子节点关联不关联的问题
vue+element-ui之tree树形控件有关子节点和父节点之间的各种选中关系详解