vue3封装虚拟列表穿梭框组件

490 阅读2分钟

基于虚拟列表封装穿梭组件

element-plus的穿梭组件列表不支持虚拟列表,数据量大时会出现卡顿

因此封装了基于虚拟列表的穿梭框组件

虚拟列表组件:juejin.cn/post/739031…

组件参数:

  1. data: 全量列表数据
  2. values:穿梭框右侧的数据
  3. toLeftText:向左穿梭按钮的文本
  4. toRightText:向右穿梭按钮的文本
<template>
  <div class="flex-container">
    <VList 
        :data="leftList"
        :listHeight="200"
        :itemHeight="30"
    >
      <template #header>
        <div class="header">
          <el-checkbox
            v-model="checkAllLeft"
            @change="handleCheckAllChangeLeft"
          >全选</el-checkbox>
          <div>{{checkedLeft}}/{{leftList.length}}</div>
        </div>
      </template>
      <template #default="{item}">
          <div class="item">
              <el-checkbox 
                  v-model="item.checked" 
                  :label="item.label" size="large" 
                  @change="handleChangeChecked(item)" 
                  :disabled="item.disabled"
              />
          </div>
      </template>
    </VList>
    <div class="flex-button">
      <el-button type="primary" :disabled="disabledToLeft" @click="handleToLeft">{{toLeftText}}</el-button>
      <el-button type="primary" :disabled="disabledToRight" @click="handleToRight">{{toRightText}}</el-button>
    </div>
    <VList 
      :data="rightList"
      :listHeight="200"
      :itemHeight="30"
    >
      <template #header>
        <div class="header">
          <el-checkbox
            v-model="checkAllRight"
            @change="handleCheckAllChangeRight"
          >全选</el-checkbox>
          <div>{{checkedRight}}/{{rightList.length}}</div>
        </div>
      </template>
      <template #default="{item}">
          <div class="item">
              <el-checkbox 
                  v-model="item.checked" 
                  :label="item.label" size="large" 
                  @change="handleChangeChecked(item)" 
                  :disabled="item.disabled"
              />
          </div>
      </template>
    </VList>
  </div>
</template>

<script setup lang='ts'>
import { ref, computed } from 'vue'
import VList from './list-v.vue'
interface item{
  key: number|string,
  label: string,
  disabled: boolean,
  checked: boolean,
  isRight: boolean
}
interface PropsType{
  data:item[],
  values: (number|string)[],
  toLeftText?: string,
  toRightText?: string
}
const props = withDefaults(defineProps<PropsType>(),{
  data: ()=>[],
  values: ()=>[],
  toLeftText:'<',
  toRightText:'>'
})
const emits = defineEmits(['update:values'])
props.data.forEach(i=>{ i.checked = false }) // 全量数据,初始化checked为false
const dataList = computed(()=> props.data.map(item=>{ // 全量数据,根据values设置isRight属性
  return {
      ...item,
      isRight:props.values.includes(item.key)
  }
}));
const leftList = computed(() => dataList.value.filter(i => !i.isRight))
const rightList = computed(() => dataList.value.filter(i => i.isRight));
const disabledToLeft = computed(() => rightList.value.filter(i => i.checked).length === 0)
const disabledToRight = computed(() => leftList.value.filter(i => i.checked).length === 0)
const checkAllLeft = computed(() => leftList.value.filter(i=>!i.checked && !i.disabled && !props.values.includes(i.key)).length === 0)
const checkAllRight = computed(() => rightList.value.filter(i=>!i.checked && !i.disabled && props.values.includes(i.key)).length === 0)
const checkedLeft = computed(() => leftList.value.filter(i=>i.checked && !props.values.includes(i.key)).length)
const checkedRight = computed(() => rightList.value.filter(i=>i.checked && props.values.includes(i.key)).length)
// 勾选
const handleChangeChecked = (item)=>{
  props.data.forEach(i=>{
    if(i.key===item.key){
        i.checked = item.checked
    }
  })
}
// 穿梭
const handleToLeft = ()=>{
  props.data.forEach(i=>{
    if(i.checked && props.values.includes(i.key)){
      props.values.splice(props.values.indexOf(i.key),1)
      i.checked = false
    }
  })
}
const handleToRight = ()=>{
  props.data.forEach(i=>{
    if(i.checked && !props.values.includes(i.key)){
      props.values.push(i.key)
      i.checked = false
    }
  })
}
// 全选
const handleCheckAllChangeLeft = (val)=>{
  console.log(val)
  props.data.forEach(item=>{
    if(!item.disabled && !props.values.includes(item.key)){
      item.checked = val
    }
  })
}
const handleCheckAllChangeRight = (val)=>{
  console.log(val)
  props.data.forEach(item=>{
    if(!item.disabled && props.values.includes(item.key)){
      item.checked = val
    }
  })
}
</script>

<style lang="scss" scoped>
.flex-container{
  display: flex;
  justify-content: space-around;
  align-items: center;
  margin: 0 10px;
  .header{
    display: flex;
    justify-content: space-between;
    color: #303133;
  }
  .item{
    .el-checkbox{
        padding: 0 10px;
        width: 100%;
        &:hover{
            background: rgba(40, 147, 248, 0.2);
        }
    }
  }
}
.flex-button{
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 0 10px;
}
</style>

使用穿梭组件

    <transferV v-model:values="values" :data="data" />
const values = ref([1, 4])
const data = ref([
  {
    key: 1,
    label: '备选项1',
    disabled: false,
  },
  {
    key: 2,
    label: '备选项2',
    disabled: true,
  },
  {
    key: 3,
    label: '备选项3',
    disabled: false,
  },
  {
    key: 4,
    label: '备选项4',
    disabled: false,
  },
  {
    key: 5,
    label: '备选项5',
    disabled: false,
  },
  {
    key: 6,
    label: '备选项6',
    disabled: false,
  },
  {
    key: 7,
    label: '备选项7',
    disabled: false,
  },
  {
    key: 8,
    label: '备选项8',
    disabled: false,
  },
])

组件参数配置

参数说明类型默认值是否必传
values选中值Array[]
data数据Array[]
toLeftText向左穿梭按钮文本String'>'
toRightText向右穿梭按钮文本String'<'