基于 uni-app + <movable-view>拖拽实现的标签排序-适用于微信小程序、H5等多端

在实际业务中,我们经常遇到「标签排序」或「菜单调整」的场景。微信小程序原生的 movable-view 为我们提供了一个简单、高效的拖拽能力,结合 Vue3 + uni-app 的组合,我们可以实现一个体验良好的标签管理界面

核心组件<movable-view><movable-area>

1.每项数据结构加入 y 坐标

javascript 复制代码
const itemHeight = uni.upx2px(110); // 每项高度,决定拖动步长

function initDragg(list) {
  return list.map((item, index) => ({
    ...item,
    y: index * itemHeight, // 初始位置
  }));
}

2. 拖动状态管理变量

javascript 复制代码
const canDrag = ref(false);          // 当前是否允许拖动(长按才激活)
const draggingIndex = ref(-1);       // 当前拖动的 item 的 index

3.用户手指按下某项

javascript 复制代码
<image
  @touchstart="canDrag = true"
  :src="getSpecImgUrl('sort_set')"
/>

4.进入拖动状态

javascript 复制代码
function onTouchStart(index) {
  if (!canDrag.value) return;
  draggingIndex.value = index;
}

5.拖动过程中(自动触发)

javascript 复制代码
function onChange(e, index) {
  const dy = e.detail.y;
  const targetIndex = Math.round(dy / itemHeight);
  
  if (targetIndex !== index && targetIndex >= 0 && targetIndex < groupList.value.length) {
    // 拖动项移到新位置
    const moved = groupList.value.splice(index, 1)[0];
    groupList.value.splice(targetIndex, 0, moved);
    draggingIndex.value = targetIndex;
  } else {
    // 仅移动视觉,不换位置
    groupList.value[index].y = dy;
  }
}

6.拖动结束

javascript 复制代码
function onTouchEnd() {
  if (!canDrag.value) return;
  draggingIndex.value = -1;

  // 所有项重置为标准位置
  groupList.value.forEach((item, i) => {
    item.y = i * itemHeight;
  });

  // 同步顺序到后端
  sortLabelList(groupList.value);
  canDrag.value = false;
}

完整代码测试代码

javascript 复制代码
<template>
  <scroll-view scroll-y style="height: 100vh">
    <movable-area style="height: 1000rpx; width: 100vw; position: relative">
      <block v-for="(item, index) in list" :key="item.id">
        <movable-view
          direction="vertical"
          damping="50"
          inertia
          :y="item.y"
          :style="getStyle(index)"
          @touchstart="onTouchStart(index)"
          @touchend="onTouchEnd"
          @change="onChange($event, index)"
        >
          <view class="item">
            {{ item.name }}
          </view>
        </movable-view>
      </block>
    </movable-area>
  </scroll-view>
</template>

<script setup>
  import { ref, reactive, onMounted } from 'vue';
  import { debounce } from '@/utils/util';

  const itemHeight = 100; // 每项高度,单位 rpx(需配合实际样式调整)

  const list = reactive([
    { id: 1, name: '任务一', y: 0 },
    { id: 2, name: '任务二', y: 100 },
    { id: 3, name: '任务三', y: 200 },
    { id: 4, name: '任务四', y: 300 },
  ]);

  let draggingIndex = ref(-1);

  function onTouchStart(index) {
    draggingIndex.value = index;
  }

  function onTouchEnd(e, index) {
    console.log(444);
    draggingIndex.value = -1;
    // 重置 Y 防止漂移
    list.forEach((item, i) => {
      item.y = i * itemHeight;
    });
  }

  // 拖拽过程中计算是否需要交换
  function onChange(e, index) {
    // const bounce = debounce(foo,500);
    // bounce()
    const dy = e.detail.y;
    const targetIndex = Math.round(dy / itemHeight);
    console.log(2222,e,index,targetIndex);

    if (
      targetIndex !== index &&
      targetIndex >= 0 &&
      targetIndex < list.length
    ) {
      console.log(3333);
      // 交换数据
      const moved = list.splice(index, 1)[0];
      list.splice(targetIndex, 0, moved);

      // 重新设置 y 值
      // list.forEach((item, i) => {
      //   item.y = i * itemHeight;
      // });

      draggingIndex.value = targetIndex;
    } else {
      list[index].y = dy;
    }
  }

  function getStyle(index) {
    return `position: absolute; left: 0; width: 100%; height: ${itemHeight}rpx; z-index: ${
      draggingIndex.value === index ? 10 : 1
    };`;
  }
</script>

<style scoped>
  .item {
    background-color: #f1f1f1;
    margin: 10rpx;
    border-radius: 10rpx;
    text-align: center;
    line-height: 100rpx;
    box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
  }
</style>
相关推荐
小小王app小程序开发26 分钟前
招工招聘小程序运营风险清单:资质备案 + 信息审核 + 服务履约避坑实操
小程序
2501_915921432 小时前
混合开发应用安全方案,在多技术栈融合下构建可持续、可回滚的保护体系
android·安全·ios·小程序·uni-app·iphone·webview
q_19132846952 小时前
基于SpringBoot2+Vue2+uniapp的考研社区论坛网站及小程序
java·vue.js·spring boot·后端·小程序·uni-app·毕业设计
pearbing2 小时前
多平台发力:小程序搜索优化的实用策略指南
小程序·小程序搜索排名·小程序优化·小程序搜索排名优化·小程序搜索优化·小程序seo
2501_915106323 小时前
Charles抓包怎么用 Charles抓包工具详细教程、网络调试方法、HTTPS配置与手机抓包实战
网络·ios·智能手机·小程序·https·uni-app·webview
apollo_qwe3 小时前
基于 uView 的 u-picker 自定义时分秒选择器实现(支持反显)
uni-app
00后程序员张3 小时前
Fastlane 结合 开心上架,构建跨平台可发布的 iOS 自动化流水线实践
android·运维·ios·小程序·uni-app·自动化·iphone
游戏开发爱好者84 小时前
iOS 性能测试的工程化方法,构建从底层诊断到真机监控的多工具测试体系
android·ios·小程序·https·uni-app·iphone·webview
计算机毕设指导64 小时前
基于Springboot+微信小程序流浪动物救助管理系统【源码文末联系】
java·spring boot·后端·spring·微信小程序·tomcat·maven
2501_916008894 小时前
iOS App 混淆的真实世界指南,从构建到成品 IPA 的安全链路重塑
android·安全·ios·小程序·uni-app·cocoa·iphone