Fat Drag Drop
FatDragDrop
用于处理拖拽操作。
1. 创建一个自定义拖拽元素
查看代码
<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. 自定义触发元素
默认情况下,整个元素都是可以响应拖拽事件的,我们也支持通过特定元素来进行触发拖拽,比如我们只允许通过点击🐇
触发拖拽
查看代码
<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
用来限制元素的移动
查看代码
<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
来禁用拖拽
查看代码
<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不动才会响应拖拽事件
查看代码
<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. 列表排序
查看代码
<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. 水平列表排序
查看代码
<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. 自定义预览
默认情况下,我们会针对原元素进行一次复制,然后将其作为预览元素使用。用户也可以传入对应的渲染函数或者插槽来自定义预览;
查看代码
<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. 自定义占位
查看代码
<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. 不同列表直接的数据拖拽
查看代码
<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
直接的数据交互
查看代码
<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. 不同列表直接的数据拖拽进入判断
在某些情况下,我们可能需要对进入的数据进行判断,看是否符合要求。 可以传入一个函数来进行处理。 比如下面的例子中,我们只允许町、時の流れ、人在两者之间进行数据传递
查看代码
<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