递归组件 - 树菜单 - vue2

128 阅读1分钟

业务需要封装递归组件

注意点

  1. 需要递归的组件必须有name属性
  2. 自调用时必须有出口(不满足条件时停止调用),否则内存溢出。
  3. 在父组件修改或重组传入的数据时,父组件调用$set更新引用类型数据。
  4. 在递归组件中修改父组件传入的数据需要通过computed做双向绑定。如果视图不更新,调用$forceUpdate()
  5. 自调用时也需要给自己绑定自定义方法(就像在父组件使用子组件时的绑定。):值得一提的是:不可以直接绑定子组件的方法,否则点击递归的内层时会多次触发该事件,需要改写为@你的自定义事件="(参数)=>$emit(你的自定义事件,参数)的形式。

核心代码:简易版

//子组件
<style lang="less" scoped>
.my-tree {

    .tree-wrapper {
        padding-left: 20px;
    }

    .tree-item {
        height: 30px;
        line-height: 30px;
        overflow: hidden;
        cursor: pointer;
        transition: all .1s ease;

        .tree-item-children {}

        .on {
            display: block;
        }

        .off {
            display: none;
        }
    }

    .expand {
        height: auto;
    }

    .selected {
        background: rgb(216, 224, 238);
        font-weight: bold;
        color: #000;
    }

    .loading {
        text-align: center;
    }
}
</style>

<template>
    <div class="my-tree">
        <!-- 内容 -->
        <div v-if="data?.length" class="tree-wrapper">
            <div @click.stop="onCurrentNode($event, { item, data, index })" v-for="(item, index) in data" :key="index"
                :class="['tree-item', item.expand && 'expand']">
                <template v-if="item.children?.length && !customHeader">
                    <Icon :type="item.expand ? 'ios-arrow-down' : 'ios-arrow-forward'" />
                </template>
                <span v-if="!customHeader" :class="[item.selected && 'selected']"> {{ item.title }}</span>
                <slot name="header" :index="index" :item="item" />
                <WyxTree :class="['tree-item-children', item.expand ? 'on' : 'off']" v-if="item.children?.length"
                    :data.sync="item.children" :expandOne="expandOne"
                    @on-current-node="(e, v) => $emit('on-current-node', e, v)"/>
            </div>
        </div>
        <!-- loading -->
        <div v-else class="loading">{{ emptyText }}</div>
    </div>
</template>

<script>
export default {
    name: 'MyTree',
    data() {
        return {
        }
    },
    props: {
        data: {
            type: Array,
            default: []
        },
        customHeader: {
            type: Boolean,
            default: false
        },
        expandOne: {
            type: Boolean,
            default: false
        },
        emptyText: {
            type: String,
            default: '加载中 ... '
        }
    },
    computed: {
        data: {
            get() {
                return this.$props.data
            },
            set(v) {
                this.$emit('update:data', v)
            }
        }
    },
    methods: {
        // 控制展开收起
        handleExpand(nodeList, node) {
            // 允许随意展开 默认状态
            node.expand = !node.expand
            node.selected = !node.selected
            // 只允许展开一项
            if (this.expandOne) {
                nodeList = nodeList.map(item => {
                    if (item.title !== node.title) {
                        item.expand = false
                        item.selected = false
                    }
                    return item
                })
            }
        },
        onCurrentNode(e, v) {
            const { item, data } = v
            this.handleExpand(data, item)
            this.$forceUpdate()
            this.$emit('on-current-node', e, v)
        }
    },
}
</script>