Vue自定义组件之穿梭框 --可适应较多数据

穿梭框

该组件是个穿梭框,因为使用了 滚动加载,所以支持数据量较大的情况。
基于 Vue 、element-ui 的 【Checkbox 多选框】& 【Input 输入框】& 【InfiniteScroll 无限滚动】
在这里插入图片描述

代码分析

<template>
    <div class="transfer-wrapper">
        <div class="flex-dir-column padd-all-15">
            <el-input v-model="query" :placeholder="placeholder" prefix-icon="el-icon-search" class="mar-b-15" clearable></el-input>
            <el-checkbox v-if="allCheck" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
            <div class="over-auto flex-auto" v-infinite-scroll="load_l_d" infinite-scroll-distance="10">
                <el-checkbox-group v-model="r_d" class="flex-dir-column">
                    <el-checkbox @change="checkHandle($event, item.key)" v-for="(item, index) in l_d_s" :key="index" :label="item.key" class="checkbox">{{item.label}}</el-checkbox>
                </el-checkbox-group>
            </div>
        </div>
        <div class="fgx"></div>
        <div class="flex-dir-column padd-all-15 flex-auto">
            <div class="empty-tips" v-if="!r_d.length">{{emptyText}}</div>
            <div v-else class="select-tips">已选择 <span class="color-e6a441">{{r_d.length}}</span>{{checkAddTips}}</div>
            <div class="over-auto flex-auto" v-infinite-scroll="load_r_d" infinite-scroll-distance="10">
                <div class="dis-flex select-item flex-cross-center" v-for="(val, key) in r_d_s" :key="key">
                    <div class="flex-auto">{{labelByKey[val]}}</div>
                    <i class="el-icon-error pointer" @click="del(val)"></i>
                </div>
            </div>
        </div>
        <!-- 这里隐藏渲染一个 l_d_set 主要是为了触发 computed 中的 l_d_set 执行 -->
        <span style="display: none;">{{l_d_set}}</span>  

    </div>
</template>
<style scoped>
.transfer-wrapper {
    border: 1px solid #dddddd;
    border-radius: 2px 0 2px 2px 2px;
    display: flex;
    width: 500px;
    height: 300px;
}
.fgx {
    width: 1px;
    background-color: #dddddd;
}
.checkbox {
    line-height: 30px;
    margin-left: 0 !important;
}
.select-item {
    line-height: 20px;
    padding: 7px 10px 7px 0;
}
.empty-tips {
    font-size: 12px;
    color: #91a1a9;
}
.select-tips {
    font-size: 12px;
    color: #91a1a9;
    line-height: 34px;
}
.color-e6a441 {
    color: #e6a441;
}
.el-form-item.is-error .transfer-wrapper {
    border-color: #f56c6c;
}
.el-form-item.is-error .transfer-wrapper >>> .el-input__inner {
    border-color: #dcdfe6;
}
.flex-dir-column {
    display: flex;
    flex-direction: column;
}
.padd-all-15 {
    padding: 15px;
}
.mar-b-15 {
    margin-bottom: 15px;
}
.over-auto {
    overflow: auto;
}
.flex-auto {
    flex: 1 1 auto;
}
.dis-flex {
    display: flex;
}
.flex-cross-center {
    align-items: center !important;
}
.pointer {
    cursor: pointer;
}
</style>
<script>
export default {
    name: 'transfer',
    props: {
        value: {
            type: Array,
            default: function () {
                return []
            }
        },
        allCheck: {
            type: Boolean
        },
        placeholder: {
            type: String,
            default: '请输入'
        },
        data: {
            required: true,
            type: Array,
            default: function () {
                return []
            }
        },
        emptyText: {
            type: String,
            default: '请选择'
        },
        checkAddTips: {
            required: false,
            type: String,
            default: ''
        }
    },
    watch: {
        query(n) {
            clearTimeout(this.timer)
            this.timer = setTimeout(() => {
                this.l_d = [...this.l_d_set].filter(
                    v => v.label.indexOf(n) != -1
                )
            }, 300)
        },
        l_d() {
            this.l_d_s = []
            this.load_l_d()
        },
        r_d() {
            this.r_d_s = []
            this.load_r_d()
        }
    },
    computed: {
        checkAll: {
            get: function () {
                if (!this.l_d.length) {
                    return false
                } else if (this.r_d.length >= this.l_d.length) {
                    let ret = true
                    this.l_d.forEach(v => {
                        if (!this.r_d.includes(v.key)) ret = false
                    })
                    return ret
                } else {
                    return false
                }
            },
            set: function (val) {}
        },
        labelByKey() {
            return [...this.l_d_set].reduce((a, b) => {
                a[b.key] = b.label
                return a
            }, {})
        },
        l_d_set() {
            this.l_d = [...this.data]
            return new Set(this.data)
        },
        r_d: {
            get() {
                return [...this.value]
            },
            set(n) {
                this.$emit('input', n)
                this.$nextTick(() => {
                    this.$emit('change', n)
                    // 以下代码可以触发  el-form-item 的change 校验
                    try {
                        this.$parent.validate('change')
                    } catch (error) {}
                })
            }
        }
    },

    data() {
        return {
            timer: null,
            query: '',
            l_d: [],
            l_d_s: [],
            r_d_s: [],
            step: 10	//这里可以设置每次加载项的个数
        }
    },
    methods: {
        handleCheckAllChange(selected) {
            const opearIds = this.l_d.map(v => v.key)
            if (selected) {
                this.r_d = [...new Set([...this.r_d, ...opearIds])]
            } else {
                this.r_d_s = this.r_d_s.filter(v => !opearIds.includes(v))
                this.r_d = this.r_d.filter(v => !opearIds.includes(v))
            }
        },
        del(key) {
            this.r_d = this.r_d.filter(v => v !== key)
            this.r_d_s = this.r_d_s.filter(v => v !== key)
        },
        load_l_d() {
            const start = this.l_d_s.length
            const end = Math.min(start + this.step, this.l_d.length)
            ;[...this.l_d].slice(start, end).forEach(e => {
                this.l_d_s.push(e)
            })
        },
        load_r_d() {
            const start = this.r_d_s.length
            const end = Math.min(start + this.step, this.r_d.length)
            ;[...this.r_d].slice(start, end).forEach(e => {
                this.r_d_s.push(e)
            })
        },
        checkHandle(isAdd, key) {
            if (!isAdd) this.del(key)
        }
    }
}
</script>


组件使用(基础组件可进行全局注册,直接使用)

<ym-transfer :data="animalIdList" v-model="animalIds" allCheck checkAddTips="小动物"></ym-transfer>
<script>
export default {
	data(){
		return {
            animalIds: ['1','2','3','4'],
            animalIdList: [
                { label: '小猪', key: '1' },
                { label: '小鸭', key: '2' },
                { label: '小鱼', key: '3' },
                { label: '小鸡', key: '4' },
                { label: '小马', key: '5' },
                { label: '小狗', key: '6' },
                { label: '蚂蚁', key: '7' },
                { label: '小兔', key: '8' },
                { label: '大象', key: '9' },
                { label: '鳄鱼', key: '10' },
                { label: '鸽子', key: '11' },
                { label: '天鹅', key: '12' },
                { label: '蛤蟆', key: '13' },
                { label: '海马', key: '14' },
                { label: '企鹅', key: '15' },
                { label: '小驴', key: '16' }
            ],
		}
	}
}
</script>
属性
参数说明类型require默认值
v-model绑定值(当绑定值为空数组时,默认是全部选中)Arraytrue[]
allCheck是否展示全选按钮Booleanfalsefalse
data需要展示的列表,每项需要符合 { label: ‘小猪’, key: ‘1’ }形式Arraytrue[]
placeholder自定义 搜索框 placeholderStringfalse请输入
emptyText自定义 右侧 没有选择时的展示文字Stringfalse请选择
checkAddTips自定义 右侧 选择时的展示个数描述(如小动物)Stringfalse
组件使用注意事项
  1. v-mode 绑定时,[1] 和 [‘1’] 是不一样的,需要和 data 中的 key 保持一致
  2. 组件基于 element 用到的组件有:【Checkbox 多选框】& 【Input 输入框】& 【InfiniteScroll 无限滚动】,使用前请确保以上组件已注册
  3. 初始加载 step 不能过小,当元素不能撑满高度时,就不会出现滚动条,进而不能触发滚动事件 加载后续项
关于大数据量的思考
  1. 当数据量较大时,页面会出现卡顿。主要原因:页面渲染占用时间过长,其实数据的存储没什么问题。
  2. 本组件采取 滚动加载的形式,来解决大数据量的展示问题,亲测两千左右的数据不会有问题。
  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值