更新记录

1.0.0(2026-04-30) 下载此版本

  • 支持 uni-drag-cell 组件

平台兼容性

uni-app x(5.0)

Chrome Safari Android iOS 鸿蒙 微信小程序

uni-drag-cell 是一个基于 uni-app x + uvue + UTS 实现的可拖拽排序组件。

它适合这些场景:

  • 标签排序
  • 宫格排序
  • 单列列表排序
  • 带删除能力的拖拽
  • 尾部追加“+”入口
  • 手柄拖拽排序
  • 九宫格图片选择和排序
  • 支付方式排序

组件通过 v-model 绑定了一个数组,通过插槽渲染绑定数组的内容,通过拖拽调整数组项的顺序。

组件还提供了show-close属性,显示底部删除条,拖到删除条即可删除。

组件还提供了show-append属性,显示尾部的添加插槽。该插槽不参与拖拽排序,始终在最后一个。(添加数组项的逻辑需自行实现)

通过这些能力,本组件具备对数组数据排序、添加、删除的能力。

但本组件不适合超长虚拟列表。不具备拖动到顶部或底部后自动滚动列表的功能。

设计目标

这个组件的设计不是“拖一下就立刻改数组”,而是分成两个阶段:

  1. 拖拽预览阶段

    • 拖动过程中,通过 transform 动态调整视觉顺序
    • 其他 item 会根据碰撞结果做位移动画
    • 此时外部 v-model 还不会被正式改写
  2. 拖拽提交阶段

    • 松手后,才正式修改内部数组顺序
    • 然后通过 update:modelValuechange 把最终结果抛给外部

这样做的好处是:

  • 拖动过程更平滑
  • 外部响应式更新次数更少
  • 更容易支持删除条、追加入口、手柄拖拽等扩展能力

交互模式

组件目前支持两种进入拖拽态的方式:长按拖拽和手柄拖拽。

1. 长按拖拽

默认模式。

  • 长按某个 item 后进入拖拽态
  • 当前长按触发时间为 350ms
  • 进入拖拽态后,item 会跟随手指/鼠标移动,并且会scale放大1.12倍。
  • 拖动过程中持续做碰撞检测,并实时预览排序结果

补充:

  • 如果按下后移动距离超过组件内部阈值,会取消本次长按判定,避免页面滚动时误触发拖拽

适合:

  • 标签排序
  • 宫格排序
  • 图片排序

2. 手柄拖拽

handleModetrue,且同时提供 handle 插槽时,会启用手柄模式。

  • 不再需要长按
  • 只有拖动手柄才会开始排序
  • 内容区本身可以继续点击、滚动、预览

适合:

  • 设置项排序
  • 表单项排序
  • 内容区本身还有点击行为的列表

插槽

插槽名 插槽参数 说明
default { item, index } 默认内容插槽,用于渲染每一个可排序项。组件不限制 item 的视觉形式,可以自由渲染标签、卡片、宫格、图片等内容。
append - 列表末尾的固定入口。不参与拖拽排序,不会写入 v-model,不会被删除,适合放“新增按钮”“上传入口”“更多入口”等内容。
handle { item, index } 拖拽手柄插槽,仅在 handleMode 开启时生效。手柄由组件放在每个 item 的右上角,建议给内容区预留右侧空间,避免手柄遮挡正文。

事件

事件名 回调参数 触发时机 说明
change 最新数组 排序完成后、删除完成后 最终结果事件。拖动中的视觉预览不会频繁触发 change

使用方式

1. 基础标签排序

<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

2. 宫格排序

<uni-drag-cell v-model="gridList" :column="3">
  <template #default="{ item }">
    <view class="grid-item">
      <text>{{ item.name }}</text>
    </view>
  </template>
</uni-drag-cell>

适合:

  • 应用图标
  • 功能入口宫格
  • 九宫格类布局

3. 带删除条的拖拽

<uni-drag-cell v-model="list" :show-close="true">
  <template #default="{ item }">
    <view class="cell-item">
      <text>{{ item }}</text>
    </view>
  </template>
</uni-drag-cell>

交互:

  • 长按进入拖拽
  • 拖到屏幕底部删除条
  • 删除条高亮后松手即删除

4. 尾部追加一个固定入口

<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 不参与位置计算

5. 九宫格图片选择

<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-model
  • append 用来放“添加图片”入口
  • 当达到上限时,把 showAppend 设为 false

6. 手柄拖拽排序

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

说明:

  • 手柄模式下,不需要长按
  • 当前实现中,手柄模式不会再把整个 item 放大
  • 这样可以避免单列场景下被父容器裁剪

组件内部设计说明

1. 排序预览不是直接改数组

拖动过程中,组件内部维护的是“预览顺序”。

实现方式:

  • 记录每个 item 的稳定 id
  • 根据碰撞结果生成一份预览顺序
  • 通过 transform 让其他项移动到目标位置
  • 松手后再正式更新数组和 id 顺序

这样可以避免:

  • 拖动中频繁改外部数组
  • 频繁触发父组件重渲染
  • item 身份丢失导致动画错乱

2. 删除判定基于拖动内容区域

删除命中不是简单按手指坐标判断,而是优先按“拖动内容的真实边界”和“删除条边界”是否接触来判断。

这样做是为了避免:

  • item 外层有 padding 时提前触发删除
  • 不同高度 item 在删除区表现不一致
  • web 与 app 坐标系不一致时出现明显偏移

3. 追加项与删除条互不干扰

append 项的设计目标是“固定入口”,因此:

  • 不参与碰撞排序
  • 不参与删除
  • 不写入绑定数组

4. 手柄模式与长按模式共用同一套拖拽链路

也就是说:

  • 排序逻辑相同
  • 删除逻辑相同
  • 只是“进入拖拽态”的入口不同

这样可以减少两套逻辑分叉导致的维护成本。

性能注意

拖拽时耗费性能的点主要有两个:

1. 拖动中的碰撞检测

每次 move 都会更新:

  • 当前拖动偏移
  • 删除条命中状态
  • 目标悬停位置

这是拖拽组件的必要开销,当前实现已经把重测布局的频率控制在较低水平,只在必要时重新测量。

2. 删除条命中时的实时矩形读取

当启用 showClose 时,拖动中会优先读取当前拖动内容的实时矩形,以保证删除命中准确。

这部分是“精度优先”的设计,开销可接受,但不建议把它用于超大数量、超复杂 item 的高频拖拽场景。

使用注意事项

1. 建议 item 尺寸稳定

尤其是宫格模式下,建议每个 item 的宽高尽量稳定。

否则可能带来:

  • 拖动命中感不一致
  • 排序预览观感不稳定

2. 手柄模式下给内容区预留右侧空间

因为手柄是覆盖在 item 右上角的。

建议:

  • 内容区右侧留白
  • 或者把手柄区域设计成视觉上独立的一块

3. append 只是入口,不是数据项

这点非常重要。

不要指望:

  • append 参与排序
  • append 进入 v-model
  • append 被删除

如果你需要“真正的数据项”,请直接写进 modelValue

4. 外部不要在拖拽过程中频繁整体替换列表

建议:

  • 由组件在松手后统一回传结果
  • 父组件收到结果后再更新自己的数据源

不建议:

  • 拖动过程中再由父组件持续重建整个数组

5. 不建议用于超大列表或虚拟列表场景

这个组件当前更适合:

  • 小中型标签列表
  • 宫格
  • 表单排序
  • 九宫格图片选择

如果是超长列表、虚拟滚动列表,应该使用专门为大数据量设计的方案。

平台说明

Web

  • 支持 pointer / touch 交互

Android

  • 支持触摸拖拽

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。