vue3.0 + TS + AntD 项目开发学习笔记:transfer 穿梭框的使用

204 阅读1分钟

在用 vue3.0 + TS + Antd 开发前端项目时,需要用到 Transfer 穿梭框,于是去 antd 官方网站查看,它的基本用法如下:

<template>
  <div>
    <a-transfer
      v-model:target-keys="targetKeys"
      v-model:selected-keys="selectedKeys"
      :data-source="mockData"
      :titles="['Source', 'Target']"
      :render="item => item.title"
      :disabled="disabled"
      @change="handleChange"
      @selectChange="handleSelectChange"
      @scroll="handleScroll"
    />
  </div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
interface MockData {
  key: string;
  title: string;
  description: string;
  disabled: boolean;
}
const mockData: MockData[] = [];
for (let i = 0; i < 20; i++) {
  mockData.push({
    key: i.toString(),
    title: `content${i + 1}`,
    description: `description of content${i + 1}`,
    disabled: i % 3 < 1,
  });
}

const oriTargetKeys = mockData.filter(item => +item.key % 3 > 1).map(item => item.key);
const disabled = ref<boolean>(false);

const targetKeys = ref<string[]>(oriTargetKeys);

const selectedKeys = ref<string[]>(['1', '4']);

const handleChange = (nextTargetKeys: string[], direction: string, moveKeys: string[]) => {
  console.log('targetKeys: ', nextTargetKeys);
  console.log('direction: ', direction);
  console.log('moveKeys: ', moveKeys);
};
const handleSelectChange = (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => {
  console.log('sourceSelectedKeys: ', sourceSelectedKeys);
  console.log('targetSelectedKeys: ', targetSelectedKeys);
};
const handleScroll = (direction: string, e: Event) => {
  console.log('direction:', direction);
  console.log('target:', e.target);
};
</script>

基本代码适配项目之后发现一个问题,穿梭框右侧添加的顺序是倒插的,从左边添加了一部分内容到右边,然后想要编辑修改再次添加,相关数据就会在最上面,项目要求右边列表有先后顺序,目前官方的代码没有给出自由排序的方法。于是我就去查看网友有没有相似的问题。发现可以自定义穿梭框,实现右侧数据自由拖拽功能。

实现原理为:

@dragstart 拖拽开始时在被拖拽元素上触发此事件
@drop.prevent 被拖拽的元素在目标元素上同时鼠标放开触发的事件,此事件作用在目标元素上
@dragover.prevent 拖拽元素在目标元素上移动的时候触发的事件,此事件作用在目标元素上

HTML 代码如下:

<template>
  <div id="export-demo-edit">
    <a-transfer v-model:target-keys="keys" :data-source="drawerData.mockData" :selected-keys="drawerData.selectedKeys"
      show-search :filter-option="exportFilterOption" @change="handleChange" 
      @selectChange="onSelectChange" :list-style="{
        width: '50%',
        textAlign: 'left',
        height: '350px',
      }">
      <template #children="{ direction, filteredItems, onItemSelect }">
        <div v-if="direction === 'right'" class="transfer-left">
          <div draggable="true" v-for="(item, index) in filteredItems" :key="item.key" @mouseenter="isTarget(item)"
            @mouseleave="isTarget(item)" @dragstart="handleDragstart(index)" @drop.prevent="handleDrop()"
            @dragover.prevent="handleDragover(index)" @click="() => checkChange(item.chosen, item.key, onItemSelect)"
            class="transfer-left-item">
            <a-checkbox  v-model:checked="item.chosen"></a-checkbox>
            <div class="content">
              <span> &nbsp;{{ item.title }}</span>
              <a-input-number v-if="item.key === 'progress'" :prefix="$t('setup.export.process')"
              size="small" style="width: 60%; margin-left: 10px;" v-model:value="value" :min="1" :max="8"
              onkeyup="value=value.replace(/^(0+)|[^\d]+/g,'')" @change="onchange" />
            </div>
          </div>
        </div>
        <div v-if="direction === 'left'" class="transfer-left">
          <div v-for="item in filteredItems" :key="item.key" class="transfer-left-item"  @click="() => checkChange(item.chosen, item.key, onItemSelect)">
            <a-checkbox  v-model:checked="item.chosen"></a-checkbox>
            <span> &nbsp; {{ item.title }} </span>
            <a-input-number v-if="item.key === 'progress'" :prefix="$t('setup.export.process')"
              size="small" style="width: 60%; margin-left: 10px;" v-model:value="value" :min="1" :max="8"
              onkeyup="value=value.replace(/^(0+)|[^\d]+/g,'')" @change="onchange" />
          </div>
        </div>
      </template>
    </a-transfer>
  </div>
</template>

JavaScript 代码如下:

<script setup lang="ts">

import { onMounted, PropType, reactive, ref, toRefs } from 'vue'
import { exportFilterOption } from '@/logic/useExport'
import { MockData } from '@/interface/export-set'
import { useSetupStore } from '@/store/modules/setup'
const useSetup = useSetupStore()

const props = defineProps({
  mockData: { type: Array as PropType<MockData[]>, default: null }
})
const { mockData } = toRefs(props)
const keys = ref()
const getKeys = () =>{
  const arr: any[] = []
  useSetup.exportTemplateInfo.fileds?.forEach((item:any)=>{
      arr.push(item.key)
 })
  keys.value = arr
}

onMounted(() => {
  getKeys()
})

const value = ref<number | undefined>(1)
value.value = useSetup.exportTemplateInfo.processNum ?? 1
const emits = defineEmits(['onchange', 'process'])
const onchange = (e: number) => {
  if (e === null) {
    value.value = 1
  }
  emits('process', value.value)
}

type drawData = {
  oldItemIndex: String
  newItemIndex: String
  mockData: MockData[]
  selectedKeys: String[]
}
const drawerData = reactive<drawData>({
  oldItemIndex: '',
  newItemIndex: '',
  mockData: mockData.value,
  selectedKeys: []
})

/**
 *  选项选中时触发
 */
const handleChange = (key: MockData[], direction: string, moveKeys: string[]) => { 
  emits('onchange', keys.value)
}



// 所有标签数组
const allKeys: any = []
drawerData.mockData.forEach((items) => {
  allKeys.push(items.key)
})
/**
 *  选项改变时触发
 */
const onSelectChange = (sourceSelectedKeys: string[], targetSelectedKeys: string[]) => {
  // 判断右边列表是否全选  是的话右边多选框全为true
  if (targetSelectedKeys.length === keys.value.length && keys.value.length!==0) {
    targetSelectedKeys.forEach((item:any) => {
      drawerData.mockData.forEach((items: any, index: any) => {
        if (item === items.key) { 
          items.chosen = true
        }
      });
    });  
  }
  // 判断右边列表是否全未选, 是的话左边多选框全为 false
  if (targetSelectedKeys.length === 0) {
    keys.value.forEach((item: any) => {
      drawerData.mockData.forEach((items: any, index: any) => {
        if (item === items.key) {
          items.chosen = false
        }
      });
    })
  }
  // 判断左边列表是否全选 是的话左边多选框全为 true
  if (sourceSelectedKeys.length === drawerData.mockData.length - keys.value.length) {
    sourceSelectedKeys.forEach((item: any) => {
      drawerData.mockData.forEach((items: any) => {
        if (item === items.key) {
          items.chosen = true;
        }
      });
    });
  }
  // 判断左边列表是否全未选, 是的话左边多选框全为 false
  if (sourceSelectedKeys.length === 0) {
    const newKeys = allKeys.filter((item: any) => keys.value.indexOf(item) === -1)
    if (newKeys.length !== 0) {  
      newKeys.forEach((item: any) => {
        drawerData.mockData.forEach((items: any) => {
          if (items.key === item) {
            items.chosen = false;
          }
        })
      });
    } else if (keys.value.length === 0)  {
      drawerData.mockData.forEach((items: any) => {
        items.chosen = false;
      })
    }
  }
  drawerData.selectedKeys = [...sourceSelectedKeys, ...targetSelectedKeys];
}

/**
 *  拖动结束后触发
 */
const handleDrop = () => {
  // 删除旧的
  const changeItem = keys.value.splice(drawerData.oldItemIndex, 1)[0];
  // 在列表中目标位置增加新的
  keys.value.splice(drawerData.newItemIndex, 0, changeItem);
  emits('onchange', keys.value)
};
/**
 *  拖动开始后触发
 */
const handleDragstart = (index: any) => { drawerData.oldItemIndex = index; };
/**
 *  拖动结束后触发
 */
const handleDragover = (index: any) => { drawerData.newItemIndex = index; };
// 用于判断选中了哪些多选框
const checkChange = (checked: any, key: any, onItemSelect: any) => {
  drawerData.mockData.forEach((items: any) => {
        if (key === items.key) {
          items.chosen = true;
        }
      });
  onItemSelect(key, !checked);
};

// 穿梭框列表图标显示与隐藏
const isTarget = (e: any) => {
  let key = false;
  keys.value.forEach((item: any) => { if (e.key === item) { key = item; } });
};

</script>