三级联动选择器/树形组件封装(vue2/uniapp版)

416 阅读4分钟

要封装的选择器效果:


这种选择器在app中也是非常常见的,跟树形组件很像,主要用到的是vue中的递归组件。
递归组件:组件是可以在它们自己的模板中调用自身的。在三级联动选择器的情况下,每个级别的选择器都是相同的组件类型,可以通过递归来实现。不过它们只能通过 name 选项来做这件事:
数据结构:

大概过程:

●默认展示全部和第一级数据,点击上一级的时候再展示下一级的数据,再次点击时就关闭下级数据
●切换上级数据时,要把之前的下级数据全部重置,更新下一级的选项
注意事项:
●使用递归组件时,确保递归终止条件正确设置。如果没有子级选项可供渲染,应该终止递归并显示最后一级的选项
●在处理选项改变时,避免无限循环。当父级选项改变时,子级选项也会随之改变,这可能导致无限循环。确保在更新选项时使用合适的条件进行判断,避免无限递归
●如果数据量较大,可以考虑使用异步加载选项,只在需要时请求下一级选项的数据,以提高性能和用户体验(下述代码未实现)\

实现的过程中遇见的问题:

1. 下级组件无法刚好展示在上级组件的右边,而是竖着展示的

解决方法:给组件设置flex属性即可

image.png

2. 在切换一级数据时,二级数据会更新,但三级数据不会更新

原因1:因为在更新二级数据时,没有重新设置三级数据。
在 Vue.js 中,当父组件的数据更新时,会触发子组件的重新渲染。但是,当子组件接收到新的 props 数据时,并不会自动重新渲染子组件的子组件。这意味着,即使您通过 props 将新的二级数据传递给了子组件,但子组件的子组件仍然保留着旧的三级数据。
解决1: 在父组件更新二级数据时,同时更新三级数据
原因2: 在 setSubCategoryData 方法中切换子类别数据并更新 subCategoryData,但是当切换一级数据时并没有重置 subCategoryData。
解决2: 在切换一级数据时通过重置 subCategoryData 和 currentActiveIndex 将子类别数据重置为初始状态。
递归组件 IotCategoryTree 使用了 v-if 条件来判断是否需要渲染子组件。当 subTreeData 发生变化时,v-if 条件会重新计算,从而触发子组件的重新渲染。
所以当切换一级数据时,subTreeData 应该被重置为空数组,并且递归组件 IotCategoryTree 应该自动重新渲染子组件及其子组件。

3. 滑动遮罩层,遮罩层下面的元素也会滑动

解决1: 在父组件中监听遮罩层的显隐,手动设置overflow的值
eg:

watch: {
    filterListVisible(val) {
      const el = document.getElementsByClassName('dropdownFilter')
        if (val) {
            el[0].style.overflow = 'hidden'
        } else {
            el[0].style.overflow = 'scroll'
          }
        }
    },

但是这段代码在H5生效,在APP不生效,后来发现是因为 uniapp是没有document(window也一样)的,这样写获取不到元素。
于是我就想着能不能通过refs来获取,于是我写了 .$refs.dropdownFilterRef.$el
但是它没有生效,问了chatGPT,它说是因为:
在 Vue 中,直接使用this.$refs.myElement.$el是无法直接修改样式的,因为 $el 返回的是普通的 DOM 元素,不具备 Vue 的响应式能力。
既然这样,那我们就用符合vue响应式的方式,利用数据绑定的优势来动态控制节点的样式,于是我又写了
并给相应的元素进行绑定 :style="elementStyle" 👏终于生效!
设置完这个之后,我又想给CategoryTree组件设置内部滚动(BCD单独滚动),并且每一列不会互相影响,大概就是这个意思,如图:

image.png

这个就比较简单了,给.category元素加上overflow-y: scroll即可(注⚠️:是给.category加,不是给.category_content元素加,给.category_content加的话就会变成整个A一起滚动)\

优化后代码:

CategoryTree.vue

<template>
    <view class="flex category-tree box-border">
        <view
            v-if="withTree(categoryList)"
            class="category box-border"
            :style="{
                backgroundColor: bgColorShow ? '#f5f8f7' : '#fff',
                borderLeft: !borderShow ? 'none' : '1px solid #CEDBD5'
            }"
            v-bind="$attrs"
        >
            <view
                class="category_content"
                v-for="(item, index) in [
                    { name: '全部', id: null, path: '' }
                ].concat(categoryList)"
                :key="item.id"
            >
                <view
                    @click="setSubCategoryData(item, index)"
                    :style="{
                        color:
                            currentActiveIndex === index ? '#2CD181' : '#1D201E'
                    }"
                    >{{ item.name }}</view
                >
            </view>
        </view>
        <CategoryTree
            ref="subCategory"
            class="subCategory"
            v-if="withTree(subCategoryData)"
            :originCategoryList="subCategoryData"
        >
        </CategoryTree>
    </view>
</template>
<script>
export default {
    name: 'CategoryTree',
    props: {
        originCategoryList: {
            type: Array
        },
        getTreeDataApi: {
            type: Function
        }
    },
    inject: ['confirmCategoryValue'],
    data() {
        return {
            categoryList: [],
            subCategoryData: [],
            currentActiveIndex: 0,
            showSubCategory: false,
            bgColorShow: false,
            borderShow: false,
            currentActiveId: ''
        }
    },
    watch: {
        originCategoryList: {
            handler() {
                if (this.getTreeDataApi) {
                    this.getCategoryList()
                } else if (this.originCategoryList) {
                    this.categoryList = this.originCategoryList
                    if (
                        this.categoryList[0] &&
                        this.categoryList[0].path.split('/').length - 1 === 3
                    ) {
                        this.borderShow = true
                    }
                } else {
                    this.categoryList = []
                }
                this.subCategoryData = []
                this.setCurrentIndexValue(0)
            },
            deep: true,
            immediate: true
        }
    },
    methods: {
        getCategoryList() {
            this.getTreeDataApi().then((data) => {
                this.categoryList = data
                if (
                    this.categoryList[0] &&
                    this.categoryList[0].path.split('/').length - 1 === 1
                ) {
                    this.bgColorShow = true
                }
            })
        },
        setSubCategoryData(data, index) {
            if (index === this.currentActiveIndex && index !== 0) {
                this.subCategoryData = []
                this.setCurrentIndexValue('')
            } else if (data.children) {
                this.subCategoryData = data.children
                this.setCurrentIndexValue(index)
            } else {
                this.resetSubCategory()
            }
            this.currentActiveId = data.id
            this.confirmCategoryValue('deviceTypeId', this.currentActiveId)
        },
        resetSubCategory() {
            this.confirmCategoryValue('deviceTypeId', null)
            this.subCategoryData = []
            this.setCurrentIndexValue(0)
        },
        setCurrentIndexValue(value) {
            this.currentActiveIndex = value
        },
        withTree(data) {
            return data && data.length
        }
    }
}
</script>
<style lang="scss" scoped>
.category-tree {
    .category {
        width: 250rpx;
        height: 800rpx;
        /* padding-bottom: 190rpx; */
        background-color: #f5f8f7;
        overflow-y: scroll;
        &_content {
            margin: 30rpx 30rpx 60rpx;
        }
        &_content:last-child {
            padding-bottom: 190rpx;
            margin-bottom: 30rpx;
        }
    }
}
</style>

这样一个三级联动选择器就实现成功啦,如有什么问题和可以改进的地方欢迎大家进行指正!