基于vue 封装的tree插件

基于vue 封装的tree插件
实现思想:组件递归实现思想,自己调用自己,因为事件只能传递一层,所以需要每一层子组件触发上一层父组件的事件。然后通过Vue.set(对象,属性,值)的方式动态给对象添加属性;支持选中回填

子节点键值名称为 children
在这里插入图片描述
在这里插入图片描述

event
@selectCheck=“selectCheck” //获得当前选中的节点数据

@select-check=“selectCheck” //只要树形结构有变化,就会推送当前选中的数据

props

名称描述类型默认值必须
echo由唯一键位组成的字符串,多个用西文逗号分隔string
label树结构每一层显示的内容的数据节点名称stringdmmc
checkKey数据中的唯一键节点名称stringdm
scoped是否子父级联动booleanfalse
value
parent父节点数据Objectnull
halfCheck半选开关booleantrue
multipleChoice单多选开关 true 多选booleanfalse
treeDataList树结构渲染数据array[]
export default {
    name: "tree",
    template: '<ul style="text-align: left;" ref="ul">' +
    '<template v-for="item in treeDataList">' +
    '<li>' +
    '<svg v-if="item.isLeaf&&!item.expend" @click="switchExpend(item,true)" t="1619510924655" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2923" width="26" height="26"><path d="M461.024 665.856c-17.696 14.08-45.024 2.4-45.024-19.2v-269.312c0-21.6 27.328-33.28 45.024-19.2l169.44 134.656a24.096 24.096 0 0 1 0 38.4l-169.44 134.656z" fill="#8a8a8a" p-id="2924"></path></svg>' +
    '<svg v-if="item.isLeaf&&item.expend" @click="switchExpend(item,false)" t="1619511142075" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3433" width="26" height="26"><path d="M358.144 461.024c-14.08-17.696-2.4-45.024 19.2-45.024h269.312c21.6 0 33.28 27.328 19.2 45.024L531.2 630.464a24.096 24.096 0 0 1-38.4 0l-134.656-169.44z" fill="#8a8a8a" p-id="3434"></path></svg>' +
    '<div v-if="multipleChoice"  style="position: relative;float: left;margin-top: 2px;"><div @click="clickCheck(item,$event)" :class="[item.checked?(item.halfCheck?\'halfCheck\':\'checkDiv\'):\'checkDiv1\']"></div></div>' +
    '<div :style="{\'padding-left\':multipleChoice?\'20px\':\'0px\'  }" @click="clickCheck(item,$event)"  :class="[item.checked?(item.halfCheck?\'\':\'activeNode\'):\'\']">{{item.dmmc}}</div>' +
    '</li>' +
    '<tree v-show="item.expend" ' +
    ':scoped="scoped" ' +
    ':halfCheck="halfCheck" ' +
    '@node-check="nodeCheck"  ' +
    'v-model="arr" ' +
    ':parent="item" ' +
    ':echo="echo" ' +
    ':multipleChoice="multipleChoice" ' +
    'style="margin-left: 20px" ' +
    ':treeDataList="item.children">' +
    '</tree>' +
    '</template>' +
    '</ul>',
    props: {
        echo: { // 回显的内容
            type: String,
            default: ''
        },
        label: { // 显示的字段
            type: String,
            default: 'dmmc'
        },
        checkKey: { // 唯一键节点
            type: String,
            default: 'dm'
        },
        scoped: { // 是否子父集关联
            type: Boolean,
            default: false
        },
        value: {},
        parent: { // 父级
            type: Object,
            default: () => null
        },
        halfCheck: { // 是否半选
            type: Boolean,
            default: true
        },
        multipleChoice: { // 单选多选标记 true 多选
            type: Boolean,
            default: () => {
                return false
            }
        },
        treeDataList: { // 传入的treeDataList循环的值
            type: Array,
            default: () => {
                return []
            }
        },
    },
    model: {
        prop: 'value',
        event: 'setValue'
    },
    data() {
        return {
            arr: []
        }
    },
    computed: {},
    mounted() {
        /*
         * @Author GyYu
         * @Description //TODO 事件分发 通知修改子节点属性
         * @Date 17:13 2021/4/29
         **/
        this.$on('childChecked', (node, checked) => {
            if (node.children && node.children.length) {
                for (const child of node.children) {
                    this.$set(child, 'checked', checked)
                    this.$emit('node-check', child, checked)
                }
            }
        })
        /*
         * @Author GyYu
         * @Description //TODO 事件分发,通知修改父节点属性
         * @Date 17:13 2021/4/29
         **/
        this.$on('parentChecked', (node, checked, type) => {
            this.$set(node, 'checked', checked)
            if (!node.parent) { // 当前节点没有父节点,推送父组件选中数据,退出函数
                this.$emit("select-check", this.getNodes({checked: true}, this.treeDataList))
                return false
            }
            // 判定是否有满足条件的
            const someChildNodeChecked = node.parent.children.some(node => node.checked)
            // 判定所有的是否都满足条件
            const everyChildNodeChecked = node.parent.children.every(node => node.checked)
            // 如果是半选开关打开
            if (this.halfCheck) {
                // 修改数据节点中半选开关标记,用于控制标签的类
                everyChildNodeChecked ? this.$set(node.parent, 'halfCheck', false) : someChildNodeChecked ? this.$set(node.parent, 'halfCheck', true) : this.$set(node.parent, 'halfCheck', false)
                // type 当为1的时候,是回填选中状态调用的,需要将父节点左右的展开开关打开
                if (type == 1) everyChildNodeChecked ? this.$set(node.parent, 'expend', true) : someChildNodeChecked ? this.$set(node.parent, 'expend', true) : this.$set(node.parent, 'expend', false)
                // 更新父节点的值
                if (!checked && someChildNodeChecked) {
                    this.$set(node.parent, 'halfCheck', true)
                    this.$set(node.parent, 'expend', true)
                    return false
                }
                // 发送通知,通知递归中的父组件,需要处理的内容
                this.$emit('parentChecked', node.parent, checked, type)
            } else {
                if (checked && everyChildNodeChecked) this.$emit('parentChecked', node.parent, checked)
                if (!checked) this.$emit('parentChecked', node.parent, checked, type)
            }
        })
        // 选中与反选处理
        this.$on('node-check', (node, checked, type) => {
            if (node.isLeaf) { // 折叠开关  只有有折叠的才改
                // 修改数据节点中展开开关
                this.$set(node, 'expend', checked ? true : false)
            }
            // 加一个控制,选中回填才有效,这里注意一下
            this.$nextTick(function () {
                if (!this.scoped) {
                    this.$emit('parentChecked', node, checked, type)
                    this.$emit('childChecked', node, checked)
                    this.$nextTick(function () {
                        this.$emit('selectCheck', this.getNodes({checked: true}, this.treeDataList, false))
                    })
                } else {
                    this.$set(node, 'checked', checked)
                }
            })
        })
        this.initData();
    },
    methods: {
        /*
         * @Author GyYu
         * @Description //TODO 切换展开标记
         * @Date 13:40 2021/4/28
         **/
        switchExpend(item, flag) {
            this.$set(item, 'expend', flag)
        },
        /*
         * @Author GyYu
         * @Description //TODO 外部调用选中数据方法
         * 调用方式 @selectCheck="selectCheck"
         * @Date 17:19 2021/4/29
         **/
        getCheckData() {
            return this.getNodes({checked: true}, this.treeDataList)
        },
        /*
         * @Author GyYu
         * @Description //TODO 递归查找每个节点中的checked是否选中
         *
         * @Date 17:19 2021/4/29
         **/
        getNodes(opt, data) {
            data = data || this.treeDataList
            let res = []
            for (const node of data) {
                for (const [key, value] of Object.entries(opt)) {
                    if (node[key] === value) {
                        let n = Object.assign({}, node)
                        delete n['children']
                        delete n['parent']
                        res.push(n)
                    }
                }
                if (node.children && node.children.length) {
                    res = res.concat(this.getNodes(opt, node.children))
                }
            }
            return res
        },
        /*
         * @Author GyYu
         * @Description //TODO 事件发生器入口,所有事件分发全部从这里走
         * @Date 17:20 2021/4/29
         **/
        nodeCheck(node, checked, type) {
            this.$emit('node-check', node, checked, type)
        },
        /*
         * @Author GyYu
         * @Description //TODO 选中与反选
         * @Date 17:08 2021/4/29
         * @Param
         * @return
         **/
        clickCheck(node, $event) {
            if (this.multipleChoice) { // 多选开关 是多选
                node.checked = !node.checked
                this.$emit('node-check', node, node.checked)
            } else {
				if(node.children&&node.children.length>0){
					return false;
				}
                // 不是多选,根据组件父子链,将所有数据组装后,进行递归,修改上次选中的数据,全部处理成未选中
                const getRoot = (el) => {
                    if (el.$parent.$el.nodeName === 'UL') {
                        el = el.$parent
                        return getRoot(el)
                    }
                    return el
                }
                let root = getRoot(this)
                const checkNo = (list) => {
                    for (let n of list || []) {
                        this.$set(n, 'checked', false)
                        this.$set(n, 'halfCheck', false)
                        if (n.children) checkNo(n.children)
                    }
                }
                checkNo(root.treeDataList) //开始递归
                node.checked = !node.checked
                this.$emit('node-check', node, node.checked) // 本次选中的业务开始  本次,找父级,找子集
            }
        },
        /*
         * @Author GyYu
         * @Description //TODO 初始化数据,增加一些开关
         * @Date 17:33 2021/4/29
         * @Param
         * @return
         **/
        initData() {
            let _self = this;
            let arr = this.echo.split(',');
            for (const node of this.treeDataList) {
                this.$set(node, 'parent', this.parent)
            }
            this.treeDataList.forEach(function (item) {
                if (!item.children) {
                    _self.$set(item, "children", []) // 添加一个空的子集
                }
                _self.$set(item, "halfCheck", false) // 添加半选标记
                _self.$set(item, "isLeaf", item.children.length > 0 ? true : false) // 添加折叠图标标记
                _self.$set(item, "expend", false) // 添加展开标记
                _self.$set(item, "checked", arr.length > 0 && arr.includes(item[_self.checkKey]) ? _self.nodeCheck(item, true, 1) : false) // 添加全选标记
            })
        }
    },
    created() {

    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值