vue 自定义 Tree 组件

syTree.vue

<style lang="scss">
    label{font-size: 14px;}
.notetitle{cursor: pointer;}
.notetitle:hover{background-color: #eaf4fe;}//移上样式
.notetitle-selected{background-color: #d5e8fc;}//点击后样式
ul.tree-ul li{line-height:28px;}
.tree-square{font-size:17px;margin-right: 8px;margin-top:6px;color:$sy-default-color;margin-left: 5px;width:0.5em;height: 1em;}
.tree-folder{font-size:14px;margin-right: 8px;margin-top:8px;color:$sy-ioc-color;margin-left:0px}

<style>
    
<template>
    <ul class="tree-ul">
        <li v-for="nodeItem,index in dataset">
            <div class="notetitle" :class="{'notetitle-selected': cursor_node == nodeItem[value]}">
                <div style="float: left;">
				    <div v-if="(nodeItem.children && nodeItem.children.length > 0)">
						<i  v-if="!nodeItem.expand" @click="changeState(nodeItem)" class="tree-square fa fa-angle-right"></i>
						<i v-else @click="changeState(nodeItem)" class="tree-square fa fa-angle-down"></i>
					</div>
					<div v-else style="width:14px; height: 14px;"></div>
                </div>
				<div style="float: left;">
					<vxe-checkbox v-model="nodeItem.checked" v-if="multiple" @change="checkNode(nodeItem)" :true-value="true" :false-value="false"></vxe-checkbox>
					<i v-else @click="changeState(nodeItem)" class="tree-folder fa fa-file-text-o"></i>
				</div>
                <div>
                   
					<div style="cursor: pointer;" @click="childClick(nodeItem)">
						{{nodeItem[title]}}
					</div>
					<div class="clear"></div>
                </div>
                <div style="clear: both;"></div>
            </div>
            <div v-if="nodeItem.children && nodeItem.children.length > 0 && nodeItem.expand" class="childTree" style="margin-left:22px;">
                <syTree ref="syTree"
                        :dataset="nodeItem.children"
                        :title="title"
                        :father="nodeItem"
                        :multiple="multiple"
                        :auto-cascade="autoCascade"
                        :cursor-node.sync="cursor_node"
                        @on-select-change="onSelectChange"
                        @on-check-change="onCheckChange"
                ></syTree>
            </div>
        </li>
    </ul>
</template>

<script>

	export default {
		name: "syTree",
		data(){
			return {
				// 当前点击的节点
                cursor_node:'',
			}
		},
		// dataset: 树节点数据数组
		props:{
			// 树结构数据
            /*[{
                ...
                expand: false, // 是否展开
                checked: false,// 是否勾选
                children: []   // 子节点
            ]*/
			dataset: Array,

            // 树节点上显示的标签对象属性键名,对应dataset里的键名
            title:{
				type: String,
                default:'title'
            },
			// 树节点表示的值的键名,对应dataset里的键名
			value:{
				type: String,
				default:'value'
            },
            // 是否显示复选框,显示复选框表示可以勾选
			multiple:{
                type: Boolean,
                default: false
            },
            
            // 是否级联选择,即父级选择自动勾选子级节点
            autoCascade:{
                type: Boolean,
                default: true
            },
            
            // 当前树节点中被点击激活的节点的value值,组件类递归时使用,外部调用不需要配置该值
            cursorNode:{
                default:''
            },
            // 父级节点,组件类递归时使用,外部调用不需要配置该值
            father:{
				type: Object,
                default:()=>{
                	return {}
                }
            }
        },
		methods: {
			// 子组件节点点击回调事件
			onSelectChange({nodeItem}){
            	this.cursor_node = nodeItem[this.value];
				// 回调父组件
				this.$emit('update:cursor-node', this.cursor_node);
            	this.$emit('on-select-change', {nodeItem});
            },

			// 子组件节点复选框点击回调事件
			onCheckChange({nodeItem}){
				// 设置父级节点复选框状态
                if(this.autoCascade){
                    let nodes = this.getCheckedChildren();
                    if (nodes.length == this.dataset.length){
                        this.father.checked = true;
                    }else{
                    	this.father.checked = false
                    }
                }
                if(this.father !== {} || nodeItem !== null){
                    // 回调父组件
                    this.$emit('on-check-change', {nodeItem});
                }
			},

            // 当前组件节点点击事件
            childClick(nodeItem){
				this.changeState(nodeItem);
				this.onSelectChange({nodeItem})
            },

			// 展开和收起的切换
			changeState: function(nodeItem) {
				nodeItem.expand = !nodeItem.expand;
				//this.$forceUpdate();
			},

			// 点击复选框
			checkNode: function (nodeItem) {
				let allselect = nodeItem.checked;
                let that = this;
				let setCheck = function (childs, allselect) {
					for(let n=0; n< childs.length; n++){
						//childs[n].checked = allselect;
						// 此处改为用Vue.set来给数组赋值,这样Vue才能监听到数组的change事件,才会重新渲染Dom
						that.$set(childs[n], 'checked', allselect);
						setCheck(childs[n].children || [], allselect);
					}
				}
                
                // 设置为级联,才会自动设置子节点和父节点的勾选状态
                if(this.autoCascade){
                    // 设置子节点选中状态
                    setCheck(nodeItem.children || [], allselect);
                    // 设置父节点选中状态
                    this.onCheckChange({nodeItem})
                }else{
                    // 回调父组件
                    this.$emit('on-check-change', {nodeItem});
                }
			},

			// 获取所有选中的节点
			getCheckedNodes: function () {
				let ChoiceData = [];
				let getNodes = function (Dataset) {
					for(let n = 0; n < Dataset.length; n++){
						if (Dataset[n].checked){
							ChoiceData.push(Dataset[n]);
						}
						if (Dataset[n].children != null){
							getNodes(Dataset[n].children);
						}
					}
				}
				getNodes(this.dataset);
				return ChoiceData;
			},
            
            /****************************** 组件提供给外部调用的method定义 ***********************************/
            
            // 获取当前组件的dataset中checked = true 的nodes ,不包含子级
            getCheckedChildren(){
				let ChoiceData = [];
				for(let n = 0; n < this.dataset.length; n++){
					if (this.dataset[n].checked){
						ChoiceData.push(this.dataset[n]);
					}
				}
				return ChoiceData;
            },
			
            // 设置树节点全部展开或全部收起,option = true 表示全部展开
			setExpand(option){
				let a = function(arr){
					for(let n = 0; n < arr.length; n++){
						arr[n].expand = option;
						if(arr[n].children.length>0){
							a(arr[n].children);
						}
					}
				}
				for(let n = 0; n < this.dataset.length; n++){
					this.dataset[n].expand = option;
					a(this.dataset[n].children);
				}
			},
			
		},
		watch: {
            'cursorNode':{
				handler(newName, oldName) {
					this.cursor_node = newName;
				},
                immediate: false
			}
		}
	}
</script>

demo 使用

<template>
    <div>
        <div style="border: 1px solid #eee; padding: 15px; margin-bottom: 10px;">
            <div>
                级次:<input type="number" v-model="grade" style="width: 80px;"></input>
            </div>
            <div>
                每级条数:<input type="number" v-model="num" style="width: 80px;"></input>
            </div>
            <div>
                <Button @click="createList">生成</Button>
            </div>
        </div>
        <div style="width: 400px; height: 700px; padding: 10px; border: 1px solid #eee;">
            <syTree ref="listTree" :multiple="true" :dataset="datalist"></syTree>
        </div>
    </div>
</template>

<script>
	import syTree from '@/components/sy-tree/syTree.vue'

    export default {
        components: {
			syTree
        },
        data() {
            return {
            	num:5,
                grade:3,
				datalist:[],
            }
        },
        created() {
        	this.createList();
		},
        methods:{
        	createChildren(grade, f_title){
        		let children = [];
				for (let i=0; i<this.num; i++){
					this.$set(children, i, {
						'title': f_title + `-${i}`,
						'expand':false,
						'checked':false,
						'value': f_title + `-${i}`
					})
					if (grade > 1){
						children[i].children = this.createChildren( grade-1, children[i]['title'])
                    }
				}
				return children;
            },

            createList(){
        		let numbers =  1;
        		for (let i = 0; i<this.grade; i++){
					numbers = numbers * this.num;
                }
        		if (numbers > 100000){
        			this.$Message.info('记录条数,不能超过10万条!');
        			return;
                }

				this.datalist = [];
				for (let i=0; i<this.num; i++){
					this.$set(this.datalist, i, {});
					this.$set(this.datalist[i], 'title', `code-${i}`);
					this.$set(this.datalist[i], 'value', `code-${i}`);
					this.$set(this.datalist[i], 'expand', false);
					this.$set(this.datalist[i], 'checked', false);
					this.$set(this.datalist[i], 'children', this.createChildren(this.grade-1 , this.datalist[i].title));

				}
			}
        }
	}
</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值