跳转到内容

useSwap

一个在 Vue 中使用的组合式函数(hook),用于封装 SwapDrag,以响应式的方式在组件中启用拖拽排序。

  • 通过 ref 传入容器元素,onMounted 时自动初始化,onUnmounted 时自动销毁
  • 提供 isDragging 响应式状态,方便在拖拽期间控制 UI
  • 暴露 init / destroy / reinitialize 方法,便于手动管理生命周期
  • 支持 autoInit 选项控制是否自动初始化

示例

1
2
3
4
5
6
7
8
9
10

randomsleep

示例代码
vue
<script setup lang="ts">
import { nextTick, reactive, ref } from 'vue'
import { useSwap } from './code'
import { random } from '../../../utils/random/code'
import { sleep } from '../../../utils/sleep/code'

defineOptions({ name: 'Example6' })

const list = reactive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
const el = ref<HTMLElement>()
useSwap(el, {
  async onSort({ draggedIndex, targetIndex }, swap) {
    console.log('onSort', draggedIndex, targetIndex)
    await sleep(random(50, 300)) // 模拟请求
    const [tmp] = list.splice(draggedIndex, 1)
    list.splice(targetIndex, 0, tmp!)
    await nextTick()
    await swap()
  },
})
</script>

<template>
  <div ref="el" class="container">
    <div v-for="item in list" :key="item" :data-id="item" class="item">{{ item }}</div>
  </div>
</template>

<style lang="scss" scoped>
.container {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

:deep(.item) {
  position: relative;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  background-color: var(--vp-c-bg-soft);

  &.swap-chosen {
    background-color: var(--vp-c-brand-3);
  }

  &.swap-before::after,
  &.swap-after::after {
    content: '';
    position: absolute;
    left: 0;
    width: 100%;
    height: 3px;
    background-color: plum;
    pointer-events: none;
  }

  &.swap-before::after {
    bottom: calc(100% + 8px);
    transform: translateY(50%);
  }

  &.swap-after::after {
    top: calc(100% + 8px);
    transform: translateY(-50%);
  }
}
</style>

code

SwapDrag

ts
import { onMounted, onUnmounted, ref, Ref } from 'vue'
import { SwapDrag } from '../../../utils/browser/swap-drag/code'
import type { SwapDragOptions } from '../../../utils/browser/swap-drag/code'

export interface UseSwapOptions extends SwapDragOptions {
  autoInit?: boolean
}

export function useSwap(
  el: Ref<HTMLElement | undefined>,
  options: UseSwapOptions = {},
) {
  const { autoInit = true, ...restOptions } = options

  const instance = ref<SwapDrag>()
  const isDragging = ref(false)

  const init = () => {
    if (!el.value) return
    instance.value = new SwapDrag(el.value, {
      ...restOptions,
      onStart(e) {
        isDragging.value = true
        restOptions.onStart?.(e)
      },
      onEnd() {
        isDragging.value = false
        restOptions.onEnd?.()
      },
    })
  }

  const destroy = () => instance.value?.destroy()

  const reinitialize = () => {
    destroy()
    init()
  }

  if (autoInit) onMounted(init)
  onUnmounted(destroy)

  return { instance, isDragging, init, destroy, reinitialize }
}