可多选的级联选择器

业务需求需要使用级联选择器的多选功能,但是使用的UI组件库不支持,在网上寻找类似的也不能完全符合需求,所以自己简单封装了一个组件,主要使用了element-ui中的el-select组件和el-tree组件实现,支持表单校验。效果如下图所示
在这里插入图片描述
在这里插入图片描述

组件代码如下

<template>
    <el-select multiple clearable filterable value-key="value" ref="selectTree" :disabled="disabled"
        :placeholder="placeholder" v-model="selectedValue" :filter-method="filterMethod" @visible-change="visibleChange"
        @remove-tag="selectValueChangeHandler" @clear="selectValueChangeHandler">
        <el-option v-for="item in selectOptions" :key="item.value" :label="item.label" :value="item"
            style="display:none"></el-option>
        <el-tree ref="tree" :highlight-current="true" :node-key="treeConfig.key" :data="treeData" :props="treeConfig.props"
            :default-expanded-keys="expandNodesKey" :filter-node-method="filterNode" @node-click="handleNodeClick">
            <span class="custom-tree-node" slot-scope="{node,data}">
                <p :class="isCurrentNodeSelected(node, data) ? 'is-current-node-selected' : ''"
                    :style="`text-overflow:ellipsis;overflow:hidden;height:20px;line-height:20px;width:${treeWidth - 30}px`">
                    {{ node.label }}</p>
            </span>
        </el-tree>
    </el-select>
</template>
<script>
export default {
    name: 'selectTree',
    props: {
        treeData: Array,
        value: Array,
        treeWidth: Number,
        disabled: Boolean,
        placeholder: String,
        treeConfig: {
            type: Object,
            default() {
                return {
                    key: 'value',
                    parent: 'parent',
                    parentId: 'parentId',
                    props: {
                        label: 'label',
                        children: 'children'
                    }
                }
            }
        }
    },
    model: {
        prop: 'value',
        event: 'treeChange'
    },
    data() {
        return {
            selectedValue: [],
            expandNodesKey: []
        }
    },
    computed: {
        selectOptions() {
            let result = [];
            let that = this;
            function getChild(item) {
                item.forEach((i) => {
                    if (Array.isArray(i[that.treeConfig.props.children])) {
                        result.push(i)
                        getChild(i[that.treeConfig.props.children])
                    } else {
                        let child = {
                            label: i[that.treeConfig.parent] + '-' + i[that.treeConfig.props.label],
                            value: i[that.treeConfig.key]
                        }
                        result.push(child)
                    }
                })
            }
            getChild(this.treeData)
            return result
        }
    },
    watch: {
        value: {
            immediate: true,
            deep: true,
            handler: function (val) {
                if (Array.isArray(val)) {
                    this.$nextTick(() => {
                        this.selectedValue = val;
                    })
                } else {
                    this.$nextTick(() => {
                        this.selectedValue = [];
                    })
                }
            }
        }
    },
    methods: {
        filterMethod(query) {
            this.$refs.tree.filter(query)
        },

        filterNode(query, data) {
            if (!query || query.length === 0) return true;
            let queryLabel = query;
            if (Array.isArray(query) && query.length === 1) {
                queryLabel = query[0][this.treeConfig.props.label]
            }
            let flag = (data[this.treeConfig.props.label].indexOf(queryLabel) !== -1) || (data[this.treeConfig.parent] && data[this.treeConfig.parent].indexOf(queryLabel) !== -1)
            return flag;
        },
        getExpandNodesKey() {
            this.expandNodesKey = [];
            const value = this.selectedValue;
            for (let i = 0; i < value.length; i++) {
                this.expandNodesKey.push(value[i][this.treeConfig.parentId])
            }
            this.expandNodesKey = Array.from(new Set(this.expandNodesKey))
        },

        visibleChange(visible) {
            if (visible) {
                this.getExpandNodesKey()
            }
        },

        handleNodeClick(node) {
            if (node.children && node.children.length > 0) return
            const value = node[this.treeConfig.key]
            const index = this.selectedValue.findIndex(item => item[this.treeConfig.key] === value)
            if (index > -1) {
                this.selectedValue.splice(index, 1)
            } else {
                this.selectedValue.push(node)
            }
            this.selectValueChangeHandler()
        },

        selectValueChangeHandler() {
            this.$emit('treeChange', this.selectedValue)
            this.dispatch('ElFormItem', 'el.form.change', this.selectedValue)
        },

        dispatch(componentName, eventName, params) {
            var parent = this.$parent || this.$root;
            var name = parent.$options.componentName;
            while (parent && (!name || name !== componentName)) {
                parent = parent.$parent
                if (parent) {
                    name = parent.$oprions.componentName
                }
            }

            if (parent) {
                parent.$emit.apply(parent,[eventName].concat(params))
            }
        },
        isCurrentNodeSelected(node,data){
            const value = data[this.treeConfig.key]
            const index = this.selectedValue.findIndex(item=>item[this.treeConfig.key]===value)
            return index>-1
        }

    },
    created() {

    },
    mounted() {

    },
}
</script>
<style lang="less" scoped>
.is-current-node-selected{
    color:#21a6e6;
}
.is-current-node-selected::after{
    position: absolute;
    right: 20px;
    font-size: 12px;
    font-weight: 700;
    content: "";
    width: 6px;
    height: 10px;
    border: solid #21a6e6;
    border-width: 0 3px 3px 0;
    transform: rotate(45deg);
    -webkit-font-smoothing: antialiased;
}
</style>

组件引用和数据示例如下


<template>
    <div>
        <el-form ref="form" :model="form" :rules="rules" label-width="120px">
            <el-form-item label="数据" prop="data">
                <SelectTree placeholder="请选择数据" :treeWidth="500" :treeData="dataList" v-model="form.data"></SelectTree>
            </el-form-item>
        </el-form>
    </div>
</template>
<script>
import SelectTree from './select-tree.vue'

export default {
    name: 'SelectTreeTest',
    components:{SelectTree},
    data() {
        return {
            form: {
                data: []
            },
            dataList: [
                {
                    value: 'test1',
                    label: 'test1',
                    children: [
                        {
                            value: '1-v1',
                            label: 'v1',
                            parent:'test1',
                            parentId:'test1'
                        },
                        {
                            value: '1-v2',
                            label: 'v2',
                            parent:'test1',
                            parentId:'test1'
                        },
                    ]
                },
                {
                    value: 'test2',
                    label: 'test2',
                    children: [
                        {
                            value: '2-v1',
                            label: 'v1',
                            parent:'test2',
                            parentId:'test2'
                        },
                        {
                            value: '2-v2',
                            label: 'v2',
                            parent:'test2',
                            parentId:'test2'
                        },
                    ]
                }

            ],
            rules: {
                data: [{ required: true }]
            }
        }
    },
    methods: {

    },
    created() {

    },
    mounted() {

    },
}
</script>
<style lang="less" scoped></style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值