Web前端小站Web

vue封装一个多选级联选择器组件

此级联选择器有三种状态:未选,半选,全选。半选就是此树结构的下方有选中的,全选就是此树结构的下方全部选中。实现上下级联动。

3435 热度
1145 浏览

手写一个多选级联选择器,我们首先要创建一个外壳组件,用来处理默认数据增加一些显示隐藏,选中属性和回显功能。<\/h2>
我们把外壳组件起名为multipleChoice.vue,这里先放上template代码,已选显示的列表我没做,已选的ID会放在数据selected里,你们可以自行寻找。<\/blockquote>
    <div v-click-outside=\"close\" class=\"department-option\" @click.stop=\"show = true\">\n        <div class=\"now-text\">\n            选择部门\n        <\/div>\n        <transition name=\"transition-drop\">\n            <div class=\"department-box\" v-show=\"show\">\n                <MultipleChoiceItem v-model=\"listData\" @child-change=\"childChange\" :selectKey=\"selectKey\" \/>\n            <\/div>\n        <\/transition>\n    <\/div>\n<\/pre>


<\/p>

接下来就是script代码。我们要引入组件 MultipleChoiceItem 组件,这个组件会放在下面。这是用来递归显示的组件。<\/blockquote>
注意:一下代码是multipleChoice.vue的script代码。内部实现了对数据的处理,给已选中的定义选中状态,给原始数据增加是否选中字段,显示隐藏字段,给每条数据都赋值一个父级原型链。为的是当前状态修改后,所有父级需要变更状态如(半选,全选,未选)。<\/blockquote>
props主要传入列表数据,是否需要自定义key包括返回的id,要显示的name,树结构的子级字段,v-model需要绑定的数据。<\/blockquote>
<script>\nimport MultipleChoiceItem from \".\/multipleChoiceItem.vue\"\nexport default {\n    name: 'multipleChoice',\n    data() {\n        return {\n            show: false,\n            listData: [], \/\/ 列表数据\n            selected: [], \/\/ 最终选中\n        }\n    },\n    props: {\n        data: {\n            type: Array,\n            default: () => []\n        },\n        \/\/ 未选提示\n        placeholder: {\n            type: String,\n            default: '请选择'\n        },\n        \/**\n         * 用到的key\n         * 默认 id    name    child\n         *\/\n        selectKey: {\n            type: Object,\n            default: () => {\n                return { value: 'id', lable: 'name', child: 'child' }\n            }\n        },\n        value: {\n            type: Array,\n            default: () => []\n        }\n    },\n    watch: {\n        show(newVal) {\n            if(!newVal) {\n                this.updateData()   \n            }\n        }\n    },\n    created() {\n        this.init();\n    },\n    methods: {\n        childChange(val) {\n            \/\/ console.log(val)\n        },\n        openCascader() {\n            this.show = true;\n            this.init()\n        },\n        init() {\n            if (this.data && this.data.length) {\n                let arr = JSON.parse(JSON.stringify(this.data))\n                this.washData(arr, null); \/\/ 洗数据\n                this.listData = arr;\n            }\n        },\n        washData(treeList, parent, flag) {\n            for (let i = 0; i < treeList.length; i++) {\n                let flag_n = false; \/\/ 父级有选中子级不显示 标识符\n                let jsonTree = JSON.parse(JSON.stringify(treeList[i])); \/\/ 赋值已选\n                const tree = treeList[i];\n                tree.parent = parent;\n                if(this.value.includes(tree[this.selectKey.value])) {\n                    flag_n = true;\n                    \/\/ 默认回选\n                    if (tree.parent) {\n                        const diffParnet = (child_item) => {\n                            if(child_item.check != 1) { \/\/ 只有没有选中的父级才能是半选状态\n                                child_item.check = 2;\n                            }\n                            if(child_item.parent) {\n                                diffParnet(child_item.parent);\n                            }\n                        }\n                        diffParnet(tree.parent)\n                    }\n                    tree.check = 1;\n                    tree.show = true;\n                    if(!flag) {\n                        this.selected_item.push(jsonTree); \/\/ 回源默认选中\n                    }\n                } else {\n                    \/\/ 默认为0\n                    tree.check = 0;\n                    tree.show = false;\n                }\n                if (tree[this.selectKey.child]) {\n                    this.washData(tree[this.selectKey.child], tree, flag_n);\n                }\n            }\n        },\n        \/\/ 弹窗关闭\n        updateData() {\n            this.selected = [];\n            this.findSelected(this.listData);\n            this.$emit('input', this.selected);\n            this.$emit('on-change', this.selected);\n        },\n        close() {\n            this.show = false;\n        },\n        \/\/ 查询已被选中的数据\n        findSelected(arr) {\n            arr.forEach(v => {\n                if (v.check == 1) {\n                    this.selected.push(v.id)\n                }\n                if (v[this.selectKey.child] && v[this.selectKey.child].length) {\n                    this.findSelected(v[this.selectKey.child])\n                }\n            })\n        }\n    },\n    components: {\n        MultipleChoiceItem\n    }\n}\n<\/script>\n<\/pre>


<\/p>

接下来是multipleChoice.vue的style代码<\/blockquote>
<style lang=\"less\" scoped>\n.department-option {\n    width: 300px;\n    height: 32px;\n    margin-top: 20px;\n    position: relative;\n    border: 1px solid #d9d9d9;\n    cursor: pointer;\n\n\n    .department-box {\n        width: 298px;\n        max-height: 180px;\n        overflow-y: auto;\n    }\n\n\n    .select-list {\n        display: inline-block;\n        background-color: #f5f5f5;\n        color: rgba(0, 0, 0, .85);\n        margin: 3px 0 3px 3px;\n        height: 24px;\n        line-height: 24px;\n        padding: 0 10px;\n\n\n        .icon-close {\n            margin-left: 6px;\n            font-size: 10px;\n            color: rgba(0, 0, 0, .45);\n        }\n    }\n\n\n    .now-text {\n        overflow: hidden;\n        color: #bfbfbf;\n        white-space: nowrap;\n        text-overflow: ellipsis;\n        pointer-events: none;\n        padding: 0 6px;\n        line-height: 30px;\n    }\n\n\n    input {\n        position: absolute;\n        width: 0;\n        border: 0;\n        left: 6px;\n        top: 0;\n        height: 30px;\n        padding: 6px 6px;\n    }\n}\n<\/style>\n<\/pre>


<\/p>

我们写好multipleChoice.vue组件后,要继续创建multipleChoiceItem.vue组件<\/h2>
multipleChoiceItem.vue组件是为了递归显示每条数据的,里面包括了多选,上级下级联动的逻辑。<\/blockquote>
下方是template代码,重复使用组件multipleChoiceItem(自己)来实现递归。<\/blockquote>
<template>\n    <div>\n        <div class=\"list\" :class=\"{ 'child-list': is_child }\" v-for=\"(item, index) in dataList\">\n            <div class=\"main-body\">\n                <span class=\"more-icon\" :class=\"{ 'body-icon': item[selectKey.child], 'active': item.show }\"\n                    @click=\"changeShow(item, index)\"><\/span>\n                <span class=\"check-box\" @click=\"changeCheckStatus(item, index)\">\n                    <span class=\"chech-item\" :class=\"isCheckClass(item.check)\"><\/span>\n                    {{ item[selectKey.lable] }}\n                <\/span>\n            <\/div>\n            <multipleChoiceItem v-if=\"item[selectKey.child] && item.show\" v-model=\"item[selectKey.child]\" :is_child=\"true\" \/>\n        <\/div>\n    <\/div>\n<\/template>\n<\/pre>


<\/p>

下方是script代码,内部实现了选中后递归所有子级选中,父级递归检测是全选还是半选状态。<\/blockquote>
<script>\n\/\/ 多选级联选择器\nexport default {\n    name: 'multipleChoiceItem',\n    data() {\n        return {\n            dataList: []\n        }\n    },\n    props: {\n        value: {\n            type: Array,\n            default: () => []\n        },\n        is_child: {\n            type: Boolean,\n            default: false\n        },\n        \/**\n         * 用到的key\n         * 默认 id    name    child\n         *\/\n         selectKey: {\n            type: Object,\n            default: () => {\n                return { value: 'id', lable: 'name', child: 'child' }\n            }\n        },\n    },\n    created() {\n        this.dataList = this.value;\n    },\n    watch: {\n\n\n    },\n    methods: {\n        isCheckClass(val) {\n            if(val == 0) { \/\/ 未选中\n                return ''\n            } else if(val == 1) {\n                return 'pitch-on'; \/\/ 选中\n            } else if (val == 2) {\n                return 'half-select'; \/\/ 半选中\n            }\n        },\n        \/\/ 修改选中状态\n        changeCheckStatus(item, i) {\n            if(item.check == 0 || item.check == 2) {\n                item.check = 1;\n            } else {\n                item.check = 0;\n            }\n            try {\n                this.changeStatus(item, i)\n            } catch(err) {\n                console.log('err=', err)\n            }\n        },\n        \/\/ 父级自己联动\n        changeStatus(item, i) {\n            \/\/ console.log(item)\n            \/\/ 子级全部修改相同\n            this.diffChangeStatus(item)\n            \/\/ 父级联动\n            if (item.parent) {\n                const diffParnet = (child_item) => {\n                    child_item.check = this.findParent(child_item);\n                    if(child_item.parent) {\n                        diffParnet(child_item.parent);\n                    }\n                }\n                diffParnet(item.parent)\n            }\n        },\n        diffChangeStatus(item, i) {\n            item[this.selectKey.child] && item[this.selectKey.child].forEach(v => {\n                v.check = item.check;\n                if (v[this.selectKey.child] && v[this.selectKey.child].length) {\n                    this.diffChangeStatus(v)\n                }\n            })\n        },\n        \/\/ 查找父级的选中的状态\n        findParent(item) {\n            let flag = 0;\n            let flag_arr = [];\n            let THIS = this;\n            function filterData(arr) {\n                arr.forEach(v => {\n                    if (v.check == 1) {\n                        flag = 1;\n                    }\n                    if(!flag_arr.includes(v.check)) {\n                        flag_arr.push(v.check)\n                    }\n                    \/\/ if (v[THIS.selectKey.child]) {\n                    \/\/     filterData(v[THIS.selectKey.child])\n                    \/\/ }\n                })\n            }\n            filterData(item[THIS.selectKey.child]);\n            if(flag_arr.length > 1) {\n                return 2\n            } else {\n                return flag\n            }\n        },\n        \/\/ 修改当前显示\n        changeShow(item, i) {\n            item.show = !item.show;\n        },\n    },\n}\n<\/script>\n<\/pre>


<\/p>

下方是style代码<\/blockquote>
<style lang=\"less\" scoped>\n.check-box {\n    display: flex;\n    align-items: center;\n}\n.chech-item {\n    display: inline-block;\n    width: 14px;\n    height: 14px;\n    border: 1px solid #D7D7D7;\n    position: relative;\n    margin-right: 4px;\n    transition: all .3s;\n    \/\/ 半选中\n    &.half-select {\n        border-color: #2d8cf0;\n        background-color: #2d8cf0;\n        color: #2d8cf0;\n        &::after {\n            content: \"\";\n            width: 8px;\n            height: 2px;\n            background-color: #fff;\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translate(-4px, -1px);\n        }\n    }\n    \/\/ 全选\n    &.pitch-on {\n        border-color: #2d8cf0;\n        background-color: #2d8cf0;\n        color: #2d8cf0;\n        &::after {\n            content: \"\";\n            width: 4px;\n            height: 8px;\n            position: absolute;\n            top: 1px;\n            left: 4px;\n            border: 2px solid #fff;\n            border-top: 0;\n            border-left: 0;\n            transition: all 0.2s ease-in-out;\n            transform: rotate(45deg) scale(1);\n        }\n    }\n}\n.list {\n    width: 100%;\n\n\n    &.child-list {\n        padding-left: 20px;\n    }\n\n\n    .more-icon {\n        display: inline-block;\n        position: relative;\n        width: 30px;\n        height: 100%;\n\n\n        &.body-icon::after {\n            content: \"\";\n            position: absolute;\n            left: 50%;\n            top: 50%;\n            margin-top: -4px;\n            margin-left: 0px;\n            width: 0;\n            height: 0;\n            border-left: 4px solid #787878;\n            border-right: 4px solid transparent;\n            border-top: 4px solid transparent;\n            border-bottom: 4px solid transparent;\n        }\n\n\n        &.active::after {\n            transform: rotate(90deg);\n        }\n    }\n\n\n    .main-body {\n        display: flex;\n        align-items: center;\n        height: 30px;\n    }\n}\n<\/style>\n<\/pre>


<\/p>


<\/h2>

vue封装一个多选级联选择器组件

声明:Web前端小站 - 前端博客 - 王搏的个人博客|版权所有,违者必究|如未注明,均为原创

转载:转载请注明原文链接 - vue封装一个多选级联选择器组件

评论 (0)

0/50
暂无评论,快来抢沙发吧~