可拖拽单元格组件
uni-drag-cell 是一个基于 uni-app x + uvue + UTS 实现的可拖拽排序组件。
它适合这些场景:
组件通过 v-model 绑定了一个数组,通过插槽渲染绑定数组的内容,通过拖拽调整数组项的顺序。
组件还提供了show-close属性,显示底部删除条,拖到删除条即可删除。
组件还提供了show-append属性,显示尾部的添加插槽。该插槽不参与拖拽排序,始终在最后一个。(添加数组项的逻辑需自行实现)
通过这些能力,本组件具备对数组数据排序、添加、删除的能力。
但本组件不适合超长虚拟列表。不具备拖动到顶部或底部后自动滚动列表的功能。
这个组件的设计不是“拖一下就立刻改数组”,而是分成两个阶段:
拖拽预览阶段
transform 动态调整视觉顺序v-model 还不会被正式改写拖拽提交阶段
update:modelValue 和 change 把最终结果抛给外部这样做的好处是:
组件目前支持两种进入拖拽态的方式:长按拖拽和手柄拖拽。
默认模式。
350ms补充:
适合:
当 handleMode 为 true,且同时提供 handle 插槽时,会启用手柄模式。
适合:
| 插槽名 | 插槽参数 | 说明 |
|---|---|---|
default | { item, index } | 默认内容插槽,用于渲染每一个可排序项。组件不限制 item 的视觉形式,可以自由渲染标签、卡片、宫格、图片等内容。 |
append | - | 列表末尾的固定入口。不参与拖拽排序,不会写入 v-model,不会被删除,适合放“新增按钮”“上传入口”“更多入口”等内容。 |
handle | { item, index } | 拖拽手柄插槽,仅在 handleMode 开启时生效。手柄由组件放在每个 item 的右上角,建议给内容区预留右侧空间,避免手柄遮挡正文。 |
| 事件名 | 回调参数 | 触发时机 | 说明 |
|---|---|---|---|
change | 最新数组 | 排序完成后、删除完成后 | 最终结果事件。拖动中的视觉预览不会频繁触发 change。 |
<uni-drag-cell v-model="tagList">
<template #default="{ item, index }">
<view class="tag-item" @click="onClickTag(item, index)">
<text>{{ item }}</text>
</view>
</template>
</uni-drag-cell>
适合:
说明:
350ms<uni-drag-cell v-model="gridList" :column="3">
<template #default="{ item }">
<view class="grid-item">
<text>{{ item.name }}</text>
</view>
</template>
</uni-drag-cell>
适合:
<uni-drag-cell v-model="list" :show-close="true">
<template #default="{ item }">
<view class="cell-item">
<text>{{ item }}</text>
</view>
</template>
</uni-drag-cell>
交互:
<uni-drag-cell v-model="tagList" :show-append="true">
<template #default="{ item }">
<view class="tag-item">
<text>{{ item }}</text>
</view>
</template>
<template #append>
<view class="tag-add" @click="addTag">
<text>+</text>
</view>
</template>
</uni-drag-cell>
说明:
append 是通用设计,不局限于图片场景append 不参与位置计算<uni-drag-cell
v-model="imageList"
:column="3"
:show-close="true"
:show-append="imageList.length < 9">
<template #default="{ item, index }">
<view class="image-item" @click="previewImage(index)">
<image :src="item" mode="aspectFill"></image>
</view>
</template>
<template #append>
<view class="image-add" @click="chooseImages">
<text>+</text>
</view>
</template>
</uni-drag-cell>
设计说明:
v-modelappend 用来放“添加图片”入口showAppend 设为 false<uni-drag-cell v-model="list" :column="1" :handle-mode="true">
<template #default="{ item }">
<view class="cell-item">
<text>{{ item }}</text>
</view>
</template>
<template #handle>
<view class="cell-handle">
<text>≡</text>
</view>
</template>
</uni-drag-cell>
说明:
拖动过程中,组件内部维护的是“预览顺序”。
实现方式:
transform 让其他项移动到目标位置这样可以避免:
删除命中不是简单按手指坐标判断,而是优先按“拖动内容的真实边界”和“删除条边界”是否接触来判断。
这样做是为了避免:
append 项的设计目标是“固定入口”,因此:
也就是说:
这样可以减少两套逻辑分叉导致的维护成本。
拖拽时耗费性能的点主要有两个:
每次 move 都会更新:
这是拖拽组件的必要开销,当前实现已经把重测布局的频率控制在较低水平,只在必要时重新测量。
当启用 showClose 时,拖动中会优先读取当前拖动内容的实时矩形,以保证删除命中准确。
这部分是“精度优先”的设计,开销可接受,但不建议把它用于超大数量、超复杂 item 的高频拖拽场景。
尤其是宫格模式下,建议每个 item 的宽高尽量稳定。
否则可能带来:
因为手柄是覆盖在 item 右上角的。
建议:
append 只是入口,不是数据项这点非常重要。
不要指望:
append 参与排序append 进入 v-modelappend 被删除如果你需要“真正的数据项”,请直接写进 modelValue。
建议:
不建议:
这个组件当前更适合:
如果是超长列表、虚拟滚动列表,应该使用专门为大数据量设计的方案。
| Web | 微信小程序 | Android | iOS | HarmonyOS | HarmonyOS(Vapor) |
|---|---|---|---|---|---|
| 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 5.08 |
| 名称 | 类型 | 默认值 | 兼容性 | 描述 |
|---|---|---|---|---|
| modelValue | array | - | - | 可拖拽排序的数据源,支持 v-model 双向绑定,内部会在拖拽结束后更新顺序 |
| column | number | 0 | - | 网格列数。大于 0 时按网格排布,等于 0 时按一行流式排布 |
| showClose | boolean | false | - | 是否显示拖拽到底部删除栏,开启后可将 item 拖入底部删除 |
| showAppend | boolean | true | - | 是否显示末尾的 append 插槽(非拖拽状态下) |
| handleMode | boolean | false | - | 是否启用拖拽手柄模式,仅点击 handle 插槽才能触发拖拽 |
| @update:modelValue | Event | - | - | - |
| @change | Event | - | - | - |