element自定义下拉树

776 阅读2分钟

在项目中我们可能会遇到需要下拉然后出现一颗树的需求,类似下图

在这里插入图片描述
那么我们就着手来实现这个功能吧!

首先我们先来实现最基本的下拉树

<template>
	<el-select 
		class="ld-select"
		clearable
		:value="valueTitle"
		filterable
		:filter-method="remoteMethod"
		@clear="clearHandle"
		placeholder="请输入"
		popper-class="ld-select_tree-drop"
		@focus="changeDefaultCheck"
	>
		<el-option :value="valueTitle">
			<el-tree 
				show-checkbox
				accordion
				node-key="id"
				:check-strictly="true"
				ref="selectTree"
				:data="options"
				:props="defaultProps"
				:node-key="defaultProps.value"
				@check="checkNode"
				:default-checked-keys="defaultCheckNodes"
				:filter-node-method="filterNode">
			</el-tree>
		</el-option>
		<div class="ld-select_tree" disabled>
			<el-button type="primary" @click="allotMenuConfirm" size="mini">确 定</el-button>
		</div>
	</el-select>
</template>

el-select的:valu="valueTitle",valueTitle我只是定义了一下,我这边并不需要展示他勾选了什么,因为树选用了showBox及一个确认按钮来确认最终选择。ps:如果不写value会报错

接下来是各个方法

remoteMethod

remoteMethod(val){
	this.valueTitle = val
	this.$refs.selectTree.filter(val) // 过滤搜索树中节点
}

clearHandle

clearHandle(){
	this.valueTitle = ''
	this.remoteMethod(this.valueTitle)
}

因为组件会涉及到反显的问题,所以使用:check-strictly="true"来取消掉了父子级间的关联,然后写了一些方法来实现点击勾选的效果

changeDefaultCheck 此方法写在select的focus事件触发时,主要是用来解决,回显时候,父被勾选子未全被勾选时,父依旧为全选状态的问题

changeDefaultCheck(){ // 改变默认选中的时候 子级未全选父级全选状态的问题(改为半选)
	const defaultNodes = this.$refs.selectTree.getCheckedNodes()
	defaultNodes.map((item)=>{
		if(item.childList && item.childList.length > 0){
			const currentNodes = this.$refs.selectTree.getNode(item)
			this.changeParentStatus(currentNodes)		
		}
	})	
}

checkNode树的checkNode方法

checkNode(node, treeStatus){
	const selected = treeStatus.checkedKeys.indexOf(node.id) // -1未选中
	// 选中
	if(selected === -1) {
		this.changeParentAndChild(node) // 取消节点时的方法					
	} else {
		// 勾子改变父
		this.selectedParent(node)
		// 勾父全选子
		this.uniteChildSame(node, true)
	}
}

changeParentAndChild取消节点的方法

changeParentAndChild(currentObj){
	const currentNode = this.$refs.selectTree.getNode(currentObj)
	if(currentNode.childNodes.length > 0){ // 被取消的如果有子  则走uniteChildSame方法去取消所有的子的选中状态
		this.uniteChildSame(currentObj, false)
	}
	if(currentNode.parent.key){
		this.changeParentStatus(currentNode.parent)
	}
}

uniteChildSame

// 勾父全选子 取消父取消子
uniteChildSame(treeList, isSelected) {			
	this.$refs.selectTree.setChecked(treeList.id, isSelected)
	if(treeList.childList !== null){
		for(let i = 0; i < treeList.childList.length; i++) {
			this.uniteChildSame(treeList.childList[i], isSelected)
		}
	}
}

selectedParent

// 统一处理父节点为选中
selectedParent(currentObj) {
	const currentNode = this.$refs.selectTree.getNode(currentObj)
	if(currentNode.parent.key) {
		const childCheckedStatus = currentNode.parent.childNodes.every((item, index)=>{
			return item.checked === true
		})
		if(childCheckedStatus){
			this.$refs.selectTree.setChecked(currentNode.parent, true)
		}
		else{
			currentNode.parent.indeterminate = true						
		}
		this.selectedParent(currentNode.parent)
	}
}

changeParentStatus

changeParentStatus(parentNodes){	//	取消子以后改变父  然后继续看父的父应该的状态
	const childNodes = parentNodes.childNodes
	
	const childCheckedStatus = childNodes.every((item, index)=>{ // 子全部未选中
		return item.checked === false && item.indeterminate === false
	})
	
	const childCheckedAllChecked = childNodes.every((item, index)=>{ // 字全部完全选中
		return item.checked === true
	})
	
	if(childCheckedStatus){
		parentNodes.checked = false
		parentNodes.indeterminate = false
	}
	
	if(childCheckedAllChecked){
		parentNodes.checked = true
		parentNodes.indeterminate = false
	}
	
	if(!childCheckedStatus && !childCheckedAllChecked){
		parentNodes.checked = false
		parentNodes.indeterminate = true
	}

	if(parentNodes.parent.key){
		const parentNode = parentNodes.parent
		this.changeParentStatus(parentNode)
	}
}

到此本组件的核心方法就结束了,获取最后被勾选的节点的话,使用下面的方法就ok了

this.chooseNodes = this.refs.selectTree.getCheckedKeys().concat(this.refs.selectTree.getHalfCheckedKeys())

然后贴上整个组件的全部代码(还没有优化过,没啥时间去看,后续有更改会过来修改)

<template>
	<el-select 
		class="ld-select"
		clearable
		:value="valueTitle"
		filterable
		:filter-method="remoteMethod"
		@clear="clearHandle"
		placeholder="请输入"
		popper-class="ld-select_tree-drop"
		@focus="changeDefaultCheck"
	>
		<el-option :value="valueTitle">
			<el-tree 
				show-checkbox
				accordion
				node-key="id"
				:check-strictly="true"
				ref="selectTree"
				:data="options"
				:props="defaultProps"
				:node-key="defaultProps.value"
				@check="checkNode"
				:default-checked-keys="defaultCheckNodes"
				:filter-node-method="filterNode">
			</el-tree>
		</el-option>
		<div class="ld-select_tree" disabled>
			<el-button type="primary" @click="allotMenuConfirm" size="mini">确 定</el-button>
		</div>
	</el-select>
</template>

<script>
	export default {
		name: "el-tree-select",
		// props: ['options', 'defaultProps', 'value'],
		props: {
			options: {
				type: Array, // 必须是树形结构的对象数组
				default: () => {
					return []
				}
			},
			defaultProps: {
				type: Object,
				default: () => {
					return {
						value: 'id', // ID字段名
						label: 'resourceName', // 显示名称
						children: 'childList' // 子级字段名
					}
				}
			},
			value: {
				type: Number,
				default: () => {
					return null
				}
			},
			defaultCheckNodes: {
				type: Array, // 已经分配的资源
				default: () => {
					return []
				}
			}
		},
		data() {
			return {
				valueId: this.value, // 初始值
				valueTitle: '',
				chooseNodes: []
			}
		},
		created() {
			this.initHandle()
		},
		methods: {
			changeDefaultCheck(){ // 改变默认选中的时候 子级未全选父级全选状态的问题(改为半选)
				const defaultNodes = this.$refs.selectTree.getCheckedNodes()
				defaultNodes.map((item)=>{
					if(item.childList && item.childList.length > 0){
						const currentNodes = this.$refs.selectTree.getNode(item)
						this.changeParentStatus(currentNodes)		
					}
				})	
			},
			// 初始化值
			initHandle() {
				this.chooseNodes = []
//				if(this.valueId){
//					this.valueTitle = this.$refs.selectTree.getNode(this.valueId).data.title // 初始化显示
//					this.$refs.selectTree.setCurrentKey(this.valueId) // 设置默认选中
//					this.defaultExpandedKey = [this.valueId] // 设置默认展开
//				}
			},
			checkNode(node, treeStatus){
				const selected = treeStatus.checkedKeys.indexOf(node.id) // -1未选中
				// 选中
				if(selected === -1) {
					this.changeParentAndChild(node) // 取消节点时的方法					
				} else {
					// 勾子改变父
					this.selectedParent(node)
					// 勾父全选子
					this.uniteChildSame(node, true)
				}
				
			},
			// 勾父全选子 取消父取消子
			uniteChildSame(treeList, isSelected) {			
				this.$refs.selectTree.setChecked(treeList.id, isSelected)
				if(treeList.childList !== null){
					for(let i = 0; i < treeList.childList.length; i++) {
						this.uniteChildSame(treeList.childList[i], isSelected)
					}
				}
			},
			// 统一处理父节点为选中
			selectedParent(currentObj) {
				const currentNode = this.$refs.selectTree.getNode(currentObj)
				if(currentNode.parent.key) {
					const childCheckedStatus = currentNode.parent.childNodes.every((item, index)=>{
						return item.checked === true
					})
					if(childCheckedStatus){
						this.$refs.selectTree.setChecked(currentNode.parent, true)
					}
					else{
						currentNode.parent.indeterminate = true						
					}
					this.selectedParent(currentNode.parent)
				}
			},
			changeParentAndChild(currentObj){
				const currentNode = this.$refs.selectTree.getNode(currentObj)
				if(currentNode.childNodes.length > 0){ // 被取消的如果有子  则走uniteChildSame方法去取消所有的子的选中状态
					this.uniteChildSame(currentObj, false)
				}
				if(currentNode.parent.key){
					this.changeParentStatus(currentNode.parent)
				}
			},
			changeParentStatus(parentNodes){	//	取消子以后改变父  然后继续看父的父应该的状态
				const childNodes = parentNodes.childNodes
				
				const childCheckedStatus = childNodes.every((item, index)=>{ // 子全部未选中
					return item.checked === false && item.indeterminate === false
				})
				
				const childCheckedAllChecked = childNodes.every((item, index)=>{ // 字全部完全选中
					return item.checked === true
				})
				
				if(childCheckedStatus){
					parentNodes.checked = false
					parentNodes.indeterminate = false
				}
				
				if(childCheckedAllChecked){
					parentNodes.checked = true
					parentNodes.indeterminate = false
				}
				
				if(!childCheckedStatus && !childCheckedAllChecked){
					parentNodes.checked = false
					parentNodes.indeterminate = true
				}

				if(parentNodes.parent.key){
					const parentNode = parentNodes.parent
					this.changeParentStatus(parentNode)
				}
			},
			// 清除选中
			clearHandle(){
				this.valueTitle = ''
				this.remoteMethod(this.valueTitle)
			},
			remoteMethod(val){
				this.valueTitle = val
				this.$refs.selectTree.filter(val)
			},
			filterNode(value, data) { // 2019/04/10 删除自定义的过滤
				if (!value) return true
        		return data.resourceName.indexOf(value) !== -1
		    },
		   	allotMenuConfirm(){ // 确认分配
		   		this.chooseNodes = this.$refs.selectTree.getCheckedKeys().concat(this.$refs.selectTree.getHalfCheckedKeys())
		   		this.$emit('getValue', this.chooseNodes)
		   	}
		}
	}
</script>

<style scoped>
	.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
		height: auto;
		padding: 0;
	}
	
	.el-select-dropdown__item.selected {
		font-weight: normal;
	}
	
	ul li>>>.el-tree .el-tree-node__content {
		height: auto;
	}
	
	.el-tree-node__label {
		font-weight: normal;
	}
	
	.el-tree>>>.is-current .el-tree-node__label {
		color: #409EFF;
		font-weight: 700;
	}
	
	.el-tree>>>.is-current .el-tree-node__children .el-tree-node__label {
		color: #606266;
		font-weight: normal;
	}
	
	.ld-select_tree {
		text-align: right;
		padding-right: 20px;
		position: absolute;
		bottom: 10px;
		right: -4px;
	}
</style>

<style lang="scss">
	.ld-select {
		.el-input__icon {
			line-height: 32px!important;
		}
	}
</style>

此组件目前已实现,下拉树,全选,半选,点击节点时父子级联,回显时父子不级联的功能