更新记录
V1.0.0(2026-04-28) 下载此版本
- 新增 uni-app x 专用组件:lf-dnd-x
- 新增 uni-app x 拖拽项组件:lf-dnd-x-item
- 新增 uni-app x 手柄组件:lf-dnd-x-handle
- 新增 uni-app x 示例页面 demo-x.uvue
- 保留传统 uni-app 稳定版组件:lf-dnd、lf-dnd-item、lf-dnd-handle
- 支持基础列表拖拽排序
- 支持多列宫格拖拽排序
- 支持图片拖拽排序
- 支持 disabled 禁用项
- 支持 fixed 固定项
- 支持 longpress 长按拖拽
- 支持 useHandle 手柄拖拽
- 支持拖拽悬浮、占位和让位动画
- 支持 update:list、change、drag-start、drag-move、drop 事件
- 已验证传统 uni-app 下 H5 Chrome、H5 Safari、Android App-vue、微信小程序可正常使用
- 修复 Safari H5 图片长按触发系统菜单导致拖拽中断的问题
- 优化 H5 鼠标拖拽体验
- 优化移动端拖拽性能
平台兼容性
uni-app(5.07)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | × | √ | - | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | × | × | × | × | × | × | × | × | × | × | × |
LfDnd 拖拽排序
LfDnd 是一个基于 uni-app / uni-app x 的拖拽排序组件,支持列表拖拽、宫格拖拽、多列布局、图片排序、禁用项、固定项、长按拖拽、手柄拖拽等场景。
插件同时提供两套组件:
| 项目类型 | 使用组件 |
|---|---|
| 传统 uni-app | lf-dnd / lf-dnd-item / lf-dnd-handle |
| uni-app x | lf-dnd-x / lf-dnd-x-item / lf-dnd-x-handle |
平台兼容
已验证平台
| 平台 | 状态 |
|---|---|
| H5 Chrome | 正常 |
| H5 Safari | 正常 |
| Android App-vue | 正常 |
| 微信小程序 | 正常 |
适配说明
| 平台 | 说明 |
|---|---|
| 传统 uni-app | 已验证 H5 Chrome、H5 Safari、Android App-vue、微信小程序 |
| uni-app x | 已提供专用 uvue 组件,已验证 H5 Chrome、H5 Safari、Android App-vue、微信小程序 |
| app-nvue | 暂不支持 |
| 其他小程序 | 暂未适配,需自行测试 |
安装方式
将插件目录放入项目的 uni_modules 下:
你的项目/
├─ pages/
├─ static/
├─ pages.json
└─ uni_modules/
└─ lf-dnd/
├─ components/
├─ pages/
├─ package.json
├─ readme.md
└─ changelog.md
插件目录必须是:
uni_modules/lf-dnd
不要出现下面这种错误结构:
uni_modules/lf-dnd-plugin/uni_modules/lf-dnd
uni_modules/uni_modules/lf-dnd
组件选择
传统 uni-app
使用:
<lf-dnd>
<lf-dnd-item>
...
</lf-dnd-item>
</lf-dnd>
uni-app x
使用:
<lf-dnd-x>
<lf-dnd-x-item>
...
</lf-dnd-x-item>
</lf-dnd-x>
注意:uni-app x 初版建议使用 :list + @update:list,不要优先依赖 v-model:list。
传统 uni-app 基础用法
<template>
<view class="page">
<lf-dnd
v-model:list="list"
:item-height="88"
:gap="10"
@change="handleChange"
>
<lf-dnd-item
v-for="(item, index) in list"
:key="item.id"
:index="index"
:data="item"
>
<view class="row">
{{ item.text }}
</view>
</lf-dnd-item>
</lf-dnd>
</view>
</template>
<script setup>
import { ref } from 'vue';
const list = ref([
{ id: 1, text: '项目 1' },
{ id: 2, text: '项目 2' },
{ id: 3, text: '项目 3' },
{ id: 4, text: '项目 4' }
]);
const handleChange = e => {
console.log('排序完成:', e);
};
</script>
<style scoped>
.page {
min-height: 100vh;
padding: 24rpx;
background: #f6f7f9;
box-sizing: border-box;
}
.row {
height: 88px;
line-height: 88px;
padding: 0 24rpx;
background: #ffffff;
border-radius: 12rpx;
box-sizing: border-box;
}
</style>
uni-app x 基础用法
<template>
<view class="page">
<lf-dnd-x
:list="list"
:item-height="88"
:gap="10"
@update:list="handleUpdate"
@change="handleChange"
>
<lf-dnd-x-item
v-for="(item, index) in list"
:key="item.id"
:index="index"
:data="item"
>
<view class="row">
{{ item.text }}
</view>
</lf-dnd-x-item>
</lf-dnd-x>
</view>
</template>
<script lang="uts">
type DndItem = {
id: number,
text: string,
disabled: boolean,
fixed: boolean
}
export default {
data() {
return {
list: [
{ id: 1, text: '项目 1', disabled: false, fixed: false },
{ id: 2, text: '项目 2', disabled: false, fixed: false },
{ id: 3, text: '项目 3', disabled: false, fixed: false },
{ id: 4, text: '项目 4', disabled: false, fixed: false }
] as DndItem[]
}
},
methods: {
handleUpdate(list: DndItem[]) {
this.list = list
},
handleChange(e: any) {
console.log('排序完成', e)
}
}
}
</script>
<style>
.page {
min-height: 100%;
padding: 24rpx;
background-color: #f6f7f9;
}
.row {
height: 88px;
line-height: 88px;
padding-left: 24rpx;
padding-right: 24rpx;
background-color: #ffffff;
border-radius: 12rpx;
}
</style>
多列宫格拖拽
传统 uni-app
<lf-dnd
v-model:list="imageList"
:column="3"
:item-height="150"
:gap="16"
longpress
>
<lf-dnd-item
v-for="(item, index) in imageList"
:key="item.id"
:index="index"
:data="item"
v-slot="slotProps"
>
<view
class="image-card"
:class="{ active: slotProps && slotProps.active }"
@contextmenu.prevent
@dragstart.prevent
>
<image
class="image"
:src="item.url"
mode="aspectFill"
draggable="false"
@contextmenu.prevent
@dragstart.prevent
/>
<view class="mask">
{{ item.text }}
</view>
</view>
</lf-dnd-item>
</lf-dnd>
uni-app x
<lf-dnd-x
:list="imageList"
:column="3"
:item-height="150"
:gap="16"
:longpress="true"
@update:list="handleImageUpdate"
>
<lf-dnd-x-item
v-for="(item, index) in imageList"
:key="item.id"
:index="index"
:data="item"
>
<view class="image-card">
<image class="image" :src="item.url" mode="aspectFill" />
<view class="mask">
{{ item.text }}
</view>
</view>
</lf-dnd-x-item>
</lf-dnd-x>
图片样式建议
Safari H5 长按图片可能会触发系统菜单,导致拖拽中断。图片拖拽场景建议加以下样式:
.image-card {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 16rpx;
background: #f2f2f2;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
.image {
width: 100%;
height: 100%;
display: block;
pointer-events: none;
-webkit-touch-callout: none;
-webkit-user-drag: none;
-webkit-user-select: none;
user-select: none;
}
.mask {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 56rpx;
line-height: 56rpx;
text-align: center;
color: #ffffff;
font-size: 24rpx;
background: rgba(0, 0, 0, 0.35);
pointer-events: none;
}
禁用项
disabled 表示当前项不可被拖动。
<lf-dnd-item
v-for="(item, index) in list"
:key="item.id"
:index="index"
:disabled="item.disabled"
>
<view class="row" :class="{ disabled: item.disabled }">
{{ item.text }}
</view>
</lf-dnd-item>
const list = ref([
{ id: 1, text: '禁用项 1', disabled: true },
{ id: 2, text: '可拖拽项 1', disabled: false },
{ id: 3, text: '可拖拽项 2', disabled: false }
]);
固定项
fixed 表示当前项位置固定,不能拖动,也不能被其他项替换位置。
<lf-dnd-item
v-for="(item, index) in list"
:key="item.id"
:index="index"
:fixed="item.fixed"
>
<view class="row" :class="{ fixed: item.fixed }">
{{ item.text }}
</view>
</lf-dnd-item>
const list = ref([
{ id: 1, text: '固定项 1', fixed: true },
{ id: 2, text: '普通项 1', fixed: false },
{ id: 3, text: '普通项 2', fixed: false }
]);
长按拖拽
开启 longpress 后,需要长按一段时间才会触发拖拽。
<lf-dnd
v-model:list="list"
longpress
:longpress-delay="350"
>
...
</lf-dnd>
uni-app x:
<lf-dnd-x
:list="list"
:longpress="true"
:longpress-delay="350"
@update:list="list = $event"
>
...
</lf-dnd-x>
手柄拖拽
开启 use-handle 后,只有拖动手柄区域才会触发排序。
传统 uni-app
<lf-dnd v-model:list="list" use-handle>
<lf-dnd-item
v-for="(item, index) in list"
:key="item.id"
:index="index"
>
<view class="row row-between">
<text>{{ item.text }}</text>
<lf-dnd-handle>
<text class="handle">☰</text>
</lf-dnd-handle>
</view>
</lf-dnd-item>
</lf-dnd>
uni-app x
<lf-dnd-x
:list="list"
:use-handle="true"
@update:list="list = $event"
>
<lf-dnd-x-item
v-for="(item, index) in list"
:key="item.id"
:index="index"
>
<view class="row row-between">
<text>{{ item.text }}</text>
<lf-dnd-x-handle>
<text class="handle">☰</text>
</lf-dnd-x-handle>
</view>
</lf-dnd-x-item>
</lf-dnd-x>
.row-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.handle {
font-size: 36rpx;
color: #999999;
}
Demo 页面
传统 uni-app demo
在 pages.json 顶层 pages 中添加:
{
"path": "uni_modules/lf-dnd/pages/demo/demo",
"style": {
"navigationBarTitleText": "LfDnd 拖拽排序"
}
}
跳转:
uni.navigateTo({
url: '/uni_modules/lf-dnd/pages/demo/demo'
});
uni-app x demo
在 pages.json 顶层 pages 中添加:
{
"path": "uni_modules/lf-dnd/pages/demo-x/demo-x",
"style": {
"navigationBarTitleText": "LfDnd X 拖拽排序"
}
}
跳转:
uni.navigateTo({
url: '/uni_modules/lf-dnd/pages/demo-x/demo-x'
});
注意:
path不要写.vue或.uvue后缀。path不要以/开头。navigateTo的url建议以/开头。- demo 页面配置要放在顶层
pages中,不要放进分包subPackages,否则路径会被拼接成分包路径。
错误示例:
{
"root": "pages/CSS",
"pages": [
{
"path": "uni_modules/lf-dnd/pages/demo-x/demo-x"
}
]
}
这个会被解析成:
pages/CSS/uni_modules/lf-dnd/pages/demo-x/demo-x
导致页面不存在。
API
lf-dnd / lf-dnd-x Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
list |
Array | [] |
拖拽数据列表 |
column |
Number | 1 |
列数,1 为普通列表,大于 1 为宫格 |
itemHeight |
Number | 50 |
每项高度,单位 px |
gap |
Number | 0 |
项目间距 |
disabled |
Boolean | false |
是否禁用整个拖拽容器 |
longpress |
Boolean | false |
是否开启长按拖拽 |
longpressDelay |
Number | 350 |
长按触发时间,单位 ms |
useHandle |
Boolean | false |
是否只允许通过手柄拖拽 |
dragScale |
Number | 1.03 |
拖拽中缩放比例 |
dragOpacity |
Number | 0.95 |
拖拽中透明度 |
animationDuration |
Number | 300 |
让位动画时间 |
lf-dnd-item / lf-dnd-x-item Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
index |
Number | 必填 | 当前项索引 |
disabled |
Boolean | false |
当前项是否不可拖动 |
fixed |
Boolean | false |
当前项是否固定位置 |
data |
Object | {} |
当前项数据 |
lf-dnd-handle / lf-dnd-x-handle Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
disabled |
Boolean | false |
是否禁用当前手柄 |
Events
| 事件 | 说明 | 返回值 |
|---|---|---|
update:list |
返回排序后的新数组 | newList |
change |
排序完成后触发 | { list, oldIndex, newIndex, item } |
drag-start |
开始拖拽 | { item, index } |
drag-move |
拖拽移动中 | { item, oldIndex, newIndex, deltaX, deltaY } |
drop |
释放拖拽 | { item, oldIndex, newIndex, list } |
Slot
lf-dnd-item 默认插槽
传统 uni-app 中,lf-dnd-item 默认插槽会返回拖拽状态:
<lf-dnd-item
v-for="(item, index) in list"
:key="item.id"
:index="index"
v-slot="slotProps"
>
<view :class="{ active: slotProps && slotProps.active }">
{{ item.text }}
</view>
</lf-dnd-item>
建议使用:
v-slot="slotProps"
不要直接写:
v-slot="{ active }"
部分 uni-app 编译环境首次渲染时插槽参数可能为空,直接解构会报错。
常见问题
1. 组件不识别
如果控制台出现:
Failed to resolve component: lf-dnd
Failed to resolve component: lf-dnd-item
Failed to resolve component: lf-dnd-handle
请检查插件目录是否正确:
uni_modules/lf-dnd/components/lf-dnd/lf-dnd.vue
uni_modules/lf-dnd/components/lf-dnd-item/lf-dnd-item.vue
uni_modules/lf-dnd/components/lf-dnd-handle/lf-dnd-handle.vue
如果 easycom 没有自动识别,可以在页面中手动引入:
<script setup>
import LfDnd from '@/uni_modules/lf-dnd/components/lf-dnd/lf-dnd.vue';
import LfDndItem from '@/uni_modules/lf-dnd/components/lf-dnd-item/lf-dnd-item.vue';
import LfDndHandle from '@/uni_modules/lf-dnd/components/lf-dnd-handle/lf-dnd-handle.vue';
</script>
uni-app x:
<script lang="uts">
import LfDndX from '@/uni_modules/lf-dnd/components/lf-dnd-x/lf-dnd-x.uvue'
import LfDndXItem from '@/uni_modules/lf-dnd/components/lf-dnd-x-item/lf-dnd-x-item.uvue'
import LfDndXHandle from '@/uni_modules/lf-dnd/components/lf-dnd-x-handle/lf-dnd-x-handle.uvue'
export default {
components: {
LfDndX,
LfDndXItem,
LfDndXHandle
}
}
</script>
2. demo 页面找不到
如果报错:
page `/pages/tabBar/uni_modules/lf-dnd/pages/demo-x/demo-x` is not found
说明 navigateTo 使用了相对路径。
错误:
uni.navigateTo({
url: 'uni_modules/lf-dnd/pages/demo-x/demo-x'
});
正确:
uni.navigateTo({
url: '/uni_modules/lf-dnd/pages/demo-x/demo-x'
});
3. Safari H5 图片长按弹出菜单
给图片和图片外层加:
-webkit-touch-callout: none;
-webkit-user-drag: none;
-webkit-user-select: none;
user-select: none;
pointer-events: none;
4. H5 可以拖,App 或小程序不能拖
优先检查:
- 是否组件没有识别成功。
itemHeight是否传入了正确高度。- 多列布局中
column / itemHeight / gap是否和样式一致。 - 是否有外层元素拦截了
touchmove。 - 图片元素是否触发了默认拖拽或长按行为。

收藏人数:
下载插件并导入HBuilderX
赞赏(0)
下载 26
赞赏 0
下载 11731557
赞赏 1911
赞赏
京公网安备:11010802035340号