Skip to content
Return to top
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`来进行禁止拖拽 我们也可以针对`FatDragItem`的`disabled`来禁止单个元素的拖拽行为

WARNING

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

## 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

`FatDropList` 和`FatDragItem`都支持`preview`和`placeholder`插槽; 优先级为`FatDragItem` > `FatDropList`;

DANGER

`preview`和`placeholder`插槽使用的数据源为当前响应拖拽事件的`FatDragItem`上的`props`的`data`属性;

## 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`，该模式下`FatDropList`的`props.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