Skip to content
On this page

Fat Drag Drop

FatDragDrop 用于处理拖拽操作。





1. 创建一个自定义拖拽元素



查看代码
vue
<template>
  <div>
    <FatDragItem class="inline-block">
      <span> 愿天上人间,占得欢娱 </span>
    </FatDragItem>
  </div>
</template>
<script setup lang="ts">
  import { FatDragItem } from '@wakeadmin/components';
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
</style>



2. 自定义触发元素

默认情况下,整个元素都是可以响应拖拽事件的,我们也支持通过特定元素来进行触发拖拽,比如我们只允许通过点击🐇触发拖拽

查看代码
vue
<template>
  <div>
    <FatDragItem class="inline-block">
      <FatDragHandler class="handler"> 🐇 </FatDragHandler>
      <span>点火樱桃,照一架、荼蘼如雪 </span>
    </FatDragItem>
  </div>
</template>
<script setup lang="ts">
  import { FatDragItem, FatDragHandler } from '@wakeadmin/components';
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
  }
  .handler {
    cursor: move;
  }
</style>



3. 限制拖拽范围

默认情况下,我们不会对拖拽元素进行任何限制。但是我们也提供一些props用来限制元素的移动

查看代码
vue
<template>
  <div>
    <div>
      <h4>只能X轴移动</h4>
      <FatDragItem class="inline-block" lock-axis="x">
        <FatDragHandler class="handler"> 🐇 </FatDragHandler>
        <span>落花人独立 </span>
      </FatDragItem>
      <h4>只能Y轴移动</h4>
      <FatDragItem class="inline-block" lock-axis="y">
        <FatDragHandler class="handler"> 🦌 </FatDragHandler>
        <span>微雨燕双飞 </span>
      </FatDragItem>
    </div>
    <div>
      <h4>限制范围</h4>
      <div id="boundary" class="drag-boundary">
        <FatDragItem class="drag-boundary-item" drag-boundary="#boundary"> </FatDragItem>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
  import { FatDragItem, FatDragHandler } from '@wakeadmin/components';
</script>
<style lang="scss" scoped>
  h4 {
    margin: 12px;
  }
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    margin-right: 20px;
  }
  .handler {
    cursor: move;
  }
  .drag-boundary {
    display: inline-block;
    width: 662px;
    height: 662px;
    border: 1px solid #ccc;
    margin-left: 3vw;
  }
  .drag-boundary-item {
    display: inline-block;
    width: 83px;
    height: 83px;
    background-color: bisque;
    cursor: move;
  }
</style>

TIP

drag-boundary 如果传入一个string的话,那么会调用document.querySelector去获取对应的元素,请确保该元素已经被添加到页面上。 当然,我们也可以直接传入一个HTMLElement元素给它




4. 禁用拖拽

通过disabled来禁用拖拽

查看代码
vue
<template>
  <div>
    <div>
      <ElButton @click="toggleAllowDrag">切换状态</ElButton>
    </div>
    <FatDragItem class="inline-block" :class="{ disabled: allowDrag }" :disabled="allowDrag">
      <span> 始围寸而高尺,今连拱而九成 </span>
    </FatDragItem>
  </div>
</template>
<script setup lang="ts">
  import { ElButton } from 'element-plus';
  import { FatDragItem } from '@wakeadmin/components';
  import { ref } from 'vue';

  const allowDrag = ref(false);
  const toggleAllowDrag = () => {
    allowDrag.value = !allowDrag.value;
  };
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    margin: 12px;
    box-sizing: border-box;
    cursor: move;
    &.disabled {
      cursor: not-allowed;
    }
  }
</style>



5. 拖拽延迟

在一些特殊场合,我们的宿主元素可能需要同时监听click事件,这个时候我们可以添加拖拽延迟来防止元素错误的响应拖拽事件。比如下面的例子,我们需要等待鼠标按下500ms不动才会响应拖拽事件

查看代码
vue
<template>
  <div>
    <FatDragItem class="inline-block" :drag-delay="500">
      <span> 一星如月看多时 </span>
    </FatDragItem>
  </div>
</template>
<script setup lang="ts">
  import { FatDragItem } from '@wakeadmin/components';
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
</style>



6. 列表排序

查看代码
vue
<template>
  <div>
    <FatDropList :data="dataSource1" class="list" @dropped="move($event)">
      <FatDragItem v-for="item of dataSource1" :key="item" class="inline-block w-300" :data="item">
        <span> {{ item }} </span>
      </FatDragItem>
    </FatDropList>
  </div>
</template>
<script setup lang="ts">
  import { FatDragItem, FatDropList, moveItemInRefArray, type FatDragItemEventPayload } from '@wakeadmin/components';
  import { ref } from 'vue';

  const dataSource1 = ref([
    '蘅皋向晚舣轻航',
    '卸云帆、水驿鱼乡',
    '当暮天、霁色如晴昼',
    '江练静、皎月飞光',
    '那堪听、远村羌管,引离人断肠',
    '此际浪萍风梗,度岁茫茫',
    '堪伤',
    '朝欢暮宴,被多情、赋与凄凉',
    '别来最苦,襟袖依约,尚有馀香',
    '算得伊、鸳衾凤枕,夜永争不思量',
    '牵情处,惟有临歧,一句难忘',
  ]);
  const move = (obj: FatDragItemEventPayload['dropped']) => {
    moveItemInRefArray(dataSource1, obj.previousIndex, obj.currentIndex);
  };
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
  .w-300 {
    width: 300px;
  }
  .list {
    display: flex;
    flex-direction: column;
    width: max-content;
    margin-left: 10vw;
    margin-top: 12px;
    &.row {
      flex-direction: row;

      .inline-block + .inline-block {
        border: 1px solid #ccc;
        border-left: none;
      }
    }
    .inline-block {
      border: 1px solid #ccc;
    }
    .inline-block + .inline-block {
      border-top: none;
    }
  }
</style>

TIP

FatDropList 也可以通过disabled来进行禁止拖拽 我们也可以针对FatDragItemdisabled来禁止单个元素的拖拽行为

WARNING

FatDropList 不会修改任何数据,因此,使用者需要监听对应的事件来修改对应的数据源; 我们也提供了moveItemInRefArraytransferArrayItem这两个方法来方便使用者对数据源进行修改




7. 水平列表排序

查看代码
vue
<template>
  <div>
    <FatDropList :data="dataSource1" class="list row" orientation="horizontal" @dropped="move($event)">
      <FatDragItem v-for="item of dataSource1" :key="item" class="inline-block w-100" :data="item">
        <span> {{ item }} </span>
      </FatDragItem>
    </FatDropList>
  </div>
</template>
<script setup lang="ts">
  import { FatDragItem, FatDropList, moveItemInRefArray, type FatDragItemEventPayload } from '@wakeadmin/components';
  import { ref } from 'vue';

  const dataSource1 = ref(['明月如霜', '好风如水', '清景无限', '曲港跳鱼']);
  const move = (obj: FatDragItemEventPayload['dropped']) => {
    moveItemInRefArray(dataSource1, obj.previousIndex, obj.currentIndex);
  };
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
  .w-100 {
    width: 100px;
  }
  .list {
    display: flex;
    flex-direction: column;
    width: max-content;
    margin-left: 10vw;
    margin-top: 12px;
    &.row {
      flex-direction: row;

      .inline-block + .inline-block {
        border: 1px solid #ccc;
        border-left: none;
      }
    }
    .inline-block {
      border: 1px solid #ccc;
    }
    .inline-block + .inline-block {
      border-top: none;
    }
  }
</style>



8. 自定义预览

默认情况下,我们会针对原元素进行一次复制,然后将其作为预览元素使用。用户也可以传入对应的渲染函数或者插槽来自定义预览;

查看代码
vue
<template>
  <div>
    <FatDropList :data="dataSource1" class="list" @dropped="move($event)">
      <FatDragItem v-for="item of dataSource1" :key="item.name" class="inline-block w-300" :data="item">
        <span> {{ item.name }} </span>
      </FatDragItem>
      <template #preview="{ cover }">
        <picture class="cover">
          <img :src="cover" />
        </picture>
      </template>
    </FatDropList>
  </div>
</template>
<script setup lang="ts">
  import { FatDragItem, FatDropList, moveItemInRefArray, type FatDragItemEventPayload } from '@wakeadmin/components';
  import { ref } from 'vue';

  import img1 from './images/1.jpg';
  import img2 from './images/2.jpg';
  import img3 from './images/3.jpg';
  import img4 from './images/4.jpg';
  import img5 from './images/5.jpg';
  import img6 from './images/6.jpg';

  const dataSource1 = ref([
    {
      name: 'Rain',
      cover: img1,
    },
    {
      name: '月 ~Main Theme',
      cover: img2,
    },
    {
      name: 'It through all eternity',
      cover: img3,
    },
    {
      name: '真紅の翼',
      cover: img4,
    },
    {
      name: '王都アウルリウム',
      cover: img5,
    },
    {
      name: '久遠寺有珠',
      cover: img6,
    },
  ]);
  const move = (obj: FatDragItemEventPayload['dropped']) => {
    moveItemInRefArray(dataSource1, obj.previousIndex, obj.currentIndex);
  };
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
  .w-300 {
    width: 300px;
  }
  .list {
    display: flex;
    flex-direction: column;
    width: max-content;
    margin-left: 10vw;
    margin-top: 12px;
    &.row {
      flex-direction: row;

      .inline-block + .inline-block {
        border: 1px solid #ccc;
        border-left: none;
      }
    }
    .inline-block {
      border: 1px solid #ccc;
    }
    .inline-block + .inline-block {
      border-top: none;
    }
  }
</style>

<!-- eslint-disable-next-line wkvue/no-style-scoped -->
<style lang="scss">
  .cover {
    display: inline-block;
    width: 130px;
    height: 130px;
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }
</style>

TIP

FatDropListFatDragItem都支持previewplaceholder插槽; 优先级为FatDragItem > FatDropList;

DANGER

previewplaceholder插槽使用的数据源为当前响应拖拽事件的FatDragItem上的propsdata属性;




9. 自定义占位

查看代码
vue
<template>
  <div>
    <FatDropList :data="dataSource1" class="list custom-drop-list" :drop-sort-threshold="1" @dropped="move($event)">
      <FatDragItem v-for="item of dataSource1" :key="item.name" class="inline-block w-300" :data="item">
        <span> {{ item.name }} </span>
      </FatDragItem>
      <template #placeholder="{ cover }">
        <picture class="cover">
          <img :src="cover" />
        </picture>
      </template>
    </FatDropList>
  </div>
</template>
<script setup lang="ts">
  import { FatDragItem, FatDropList, moveItemInRefArray, type FatDragItemEventPayload } from '@wakeadmin/components';
  import { ref } from 'vue';

  import img1 from './images/1.jpg';
  import img2 from './images/2.jpg';
  import img3 from './images/3.jpg';
  import img4 from './images/4.jpg';
  import img5 from './images/5.jpg';
  import img6 from './images/6.jpg';

  const dataSource1 = ref([
    {
      name: 'Rain',
      cover: img1,
    },
    {
      name: '月 ~Main Theme',
      cover: img2,
    },
    {
      name: 'It through all eternity',
      cover: img3,
    },
    {
      name: '真紅の翼',
      cover: img4,
    },
    {
      name: '王都アウルリウム',
      cover: img5,
    },
    {
      name: '久遠寺有珠',
      cover: img6,
    },
  ]);
  const move = (obj: FatDragItemEventPayload['dropped']) => {
    moveItemInRefArray(dataSource1, obj.previousIndex, obj.currentIndex);
  };
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
  .w-300 {
    width: 300px;
  }
  .list {
    display: flex;
    flex-direction: column;
    width: max-content;
    margin-left: 10vw;
    margin-top: 12px;
    &.row {
      flex-direction: row;

      .inline-block + .inline-block {
        border: 1px solid #ccc;
        border-left: none;
      }
    }
    .inline-block {
      border: 1px solid #ccc;
    }
    .inline-block + .inline-block {
      border-top: none;
    }
  }
  .cover {
    display: inline-block;
    width: 130px;
    height: 130px;
    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }
  :deep(.custom-drop-list) {
    .fat-drag-placeholder {
      opacity: 1;
    }
  }
</style>

WARNING

默认情况下,我们会在拖拽行为开始之前缓存当前宿主元素的位置信息,并且只允许鼠标在该宿主元素附近时才会执行排序操作。 在这里,因为我们使用了自定义占位图,从而导致宿主元素的高度进行了变化,因此我们需要加大其判断阈值(drop-sort-threshold)从而使得排序操作可以正确进行响应

10. 不同列表直接的数据拖拽

查看代码
vue
<template>
  <div class="drop-group">
    <FatDropList ref="dropListRef1" class="list drop-item" :data="dataSource1">
      <FatDragItem
        v-for="item of dataSource1"
        :key="item"
        class="inline-block w-300"
        :data="item"
        @dropped="dropListGroupDropHandler"
      >
        <span> {{ item }} </span>
      </FatDragItem>
    </FatDropList>
    <FatDropList class="list drop-item" :connect-to="connectTo" :data="dataSource2">
      <FatDragItem
        v-for="item of dataSource2"
        :key="item"
        class="inline-block w-300"
        :data="item"
        @dropped="dropListGroupDropHandler"
      >
        <span> {{ item }} </span>
      </FatDragItem>
    </FatDropList>
  </div>
</template>
<script setup lang="ts">
  import {
    FatDragItem,
    FatDropList,
    moveItemInRefArray,
    transferArrayItem,
    type FatDragItemEventPayload,
  } from '@wakeadmin/components';
  import { ref, computed } from 'vue';

  const dropListRef1 = ref();
  const connectTo = computed(() => [dropListRef1.value?.instance].filter(val => !!val));

  const dataSource1 = ref([
    '町、時の流れ、人',
    'nostalgia',
    'Dearly Beloved',
    '蒼崎青子',
    '谁が为に',
    'Sorrow',
    "Dead's dream",
  ]);
  const dataSource2 = ref([
    'グーラ領/森林',
    'ザナルカンドにて',
    'The Final Battle',
    'Blood Upon the Snow',
    'Old Soldiers Die Hard',
    'Lost Again',
    'I Really Want to Stay At Your House',
  ]);
  const dropListGroupDropHandler = (event: FatDragItemEventPayload['dropped']) => {
    const { container, previousContainer, previousIndex, currentIndex } = event;
    if (container !== previousContainer) {
      transferArrayItem(previousContainer!.data, container!.data, previousIndex, currentIndex);
    } else {
      moveItemInRefArray(container!.data as any, previousIndex, currentIndex);
    }
  };
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
  .w-300 {
    width: 300px;
  }
  .list {
    display: flex;
    flex-direction: column;
    width: max-content;
    margin-top: 12px;
    & + & {
      margin-left: 10vw;
    }
    &.row {
      flex-direction: row;

      .inline-block + .inline-block {
        border: 1px solid #ccc;
        border-left: none;
      }
    }
    .inline-block {
      border: 1px solid #ccc;
    }
    .inline-block + .inline-block {
      border-top: none;
    }
  }

  .drop-group {
    display: flex;
  }
  .drop-item {
    margin-bottom: 3vw;
  }
</style>

TIP

默认情况下,只允许指定的FatDropList的数据进入。上面的例子中,我们只允许右边的内容进入左边,但是左边的无法进入右边

当然, 我们也提供FatDropListGroup组件来允许FatDropList直接的数据交互

查看代码
vue
<template>
  <FatDropListGroup class="drop-group">
    <FatDropList class="list drop-item" :data="dataSource1">
      <FatDragItem
        v-for="item of dataSource1"
        :key="item"
        class="inline-block w-300"
        :data="item"
        @dropped="dropListGroupDropHandler"
      >
        <span> {{ item }} </span>
      </FatDragItem>
    </FatDropList>
    <FatDropList class="list drop-item" :data="dataSource2">
      <FatDragItem
        v-for="item of dataSource2"
        :key="item"
        class="inline-block w-300"
        :data="item"
        @dropped="dropListGroupDropHandler"
      >
        <span> {{ item }} </span>
      </FatDragItem>
    </FatDropList>
  </FatDropListGroup>
</template>
<script setup lang="ts">
  import {
    FatDragItem,
    FatDropList,
    FatDropListGroup,
    moveItemInRefArray,
    transferArrayItem,
    type FatDragItemEventPayload,
  } from '@wakeadmin/components';
  import { ref } from 'vue';

  const dataSource1 = ref([
    '町、時の流れ、人',
    'nostalgia',
    'Dearly Beloved',
    '蒼崎青子',
    '谁が为に',
    'Sorrow',
    "Dead's dream",
  ]);
  const dataSource2 = ref([
    'グーラ領/森林',
    'ザナルカンドにて',
    'The Final Battle',
    'Blood Upon the Snow',
    'Old Soldiers Die Hard',
    'Lost Again',
    'I Really Want to Stay At Your House',
  ]);
  const dropListGroupDropHandler = (event: FatDragItemEventPayload['dropped']) => {
    const { container, previousContainer, previousIndex, currentIndex } = event;
    if (container !== previousContainer) {
      transferArrayItem(previousContainer!.data, container!.data, previousIndex, currentIndex);
    } else {
      moveItemInRefArray(container!.data as any, previousIndex, currentIndex);
    }
  };
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
  .w-300 {
    width: 300px;
  }
  .list {
    display: flex;
    flex-direction: column;
    width: max-content;
    margin-top: 12px;
    & + & {
      margin-left: 10vw;
    }
    &.row {
      flex-direction: row;

      .inline-block + .inline-block {
        border: 1px solid #ccc;
        border-left: none;
      }
    }
    .inline-block {
      border: 1px solid #ccc;
    }
    .inline-block + .inline-block {
      border-top: none;
    }
  }

  .drop-group {
    display: flex;
  }
  .drop-item {
    margin-bottom: 3vw;
  }
</style>

TIP

两者可以一起使用

DANGER

无论是connectTo还是FatDropListGroup,该模式下FatDropListprops.data请务必跟v-for的数据源保持一致




11. 不同列表直接的数据拖拽进入判断

在某些情况下,我们可能需要对进入的数据进行判断,看是否符合要求。 可以传入一个函数来进行处理。 比如下面的例子中,我们只允许町、時の流れ、人在两者之间进行数据传递

查看代码
vue
<template>
  <FatDropListGroup class="drop-group">
    <FatDropList class="list drop-item" :data="dataSource1" :enter-predicate="enterPredicate">
      <FatDragItem
        v-for="item of dataSource1"
        :key="item"
        class="inline-block w-300"
        :data="item"
        @dropped="dropListGroupDropHandler"
      >
        <span> {{ item }} </span>
      </FatDragItem>
    </FatDropList>
    <FatDropList class="list drop-item" :data="dataSource2" :enter-predicate="enterPredicate">
      <FatDragItem
        v-for="item of dataSource2"
        :key="item"
        class="inline-block w-300"
        :data="item"
        @dropped="dropListGroupDropHandler"
      >
        <span> {{ item }} </span>
      </FatDragItem>
    </FatDropList>
  </FatDropListGroup>
</template>
<script setup lang="ts">
  import {
    FatDragItem,
    FatDropList,
    FatDropListGroup,
    moveItemInRefArray,
    transferArrayItem,
    type FatDragItemEventPayload,
  } from '@wakeadmin/components';
  import { DragRef } from '@wakeadmin/components/dist/fat-drag-drop/dragRef';
  import { ref } from 'vue';

  const dataSource1 = ref([
    '町、時の流れ、人',
    'nostalgia',
    'Dearly Beloved',
    '蒼崎青子',
    '谁が为に',
    'Sorrow',
    "Dead's dream",
  ]);
  const dataSource2 = ref([
    'グーラ領/森林',
    'ザナルカンドにて',
    'The Final Battle',
    'Blood Upon the Snow',
    'Old Soldiers Die Hard',
    'Lost Again',
    'I Really Want to Stay At Your House',
  ]);

  const enterPredicate = (drag: DragRef) => {
    return drag.data === '町、時の流れ、人';
  };

  const dropListGroupDropHandler = (event: FatDragItemEventPayload['dropped']) => {
    const { container, previousContainer, previousIndex, currentIndex } = event;
    if (container !== previousContainer) {
      transferArrayItem(previousContainer!.data, container!.data, previousIndex, currentIndex);
    } else {
      moveItemInRefArray(container!.data as any, previousIndex, currentIndex);
    }
  };
</script>
<style lang="scss" scoped>
  .inline-block {
    display: inline-block;
    background: #fff;
    padding: 6px 12px;
    box-sizing: border-box;
    cursor: move;
  }
  .w-300 {
    width: 300px;
  }
  .list {
    display: flex;
    flex-direction: column;
    width: max-content;
    margin-top: 12px;
    & + & {
      margin-left: 10vw;
    }
    &.row {
      flex-direction: row;

      .inline-block + .inline-block {
        border: 1px solid #ccc;
        border-left: none;
      }
    }
    .inline-block {
      border: 1px solid #ccc;
    }
    .inline-block + .inline-block {
      border-top: none;
    }
  }

  .drop-group {
    display: flex;
  }
  .drop-item {
    margin-bottom: 3vw;
  }
</style>

12. API

12.1 FatDragItem 属性





12.2 FatDragItem 事件





12.3 FatDragItem 实例方法

  • reset: () => void 重置拖拽状态




12.4 FatDragItem 插槽

  • preview 自定义拖拽预览
  • placeholder 自定义拖拽占位




12.5 FatDropList 属性





12.6 FatDropList 事件





12.7 FatDropList 实例方法

  • instance: DropListRef DropListRef 实例




12.8 FatDropList 插槽

  • preview 自定义拖拽预览
  • placeholder 自定义拖拽占位




12.9 FatDragHandler 属性

  • disabled?: boolean 是否禁止拖拽
  • tag?: string 宿主元素 tag 默认为 span