在實際業務中,我們經常遇到「標簽排序」或「菜單調整」的場景。微信小程序原生的 movable-view
為我們提供了一個簡單、高效的拖拽能力,結合 Vue3 + uni-app
的組合,我們可以實現一個體驗良好的標簽管理界面。
核心組件:<movable-view>
、<movable-area>
1.每項數據結構加入 y
坐標
const itemHeight = uni.upx2px(110); // 每項高度,決定拖動步長function initDragg(list) {return list.map((item, index) => ({...item,y: index * itemHeight, // 初始位置}));
}
2. 拖動狀態管理變量
const canDrag = ref(false); // 當前是否允許拖動(長按才激活)
const draggingIndex = ref(-1); // 當前拖動的 item 的 index
3.用戶手指按下某項
<image@touchstart="canDrag = true":src="getSpecImgUrl('sort_set')"
/>
4.進入拖動狀態
function onTouchStart(index) {if (!canDrag.value) return;draggingIndex.value = index;
}
5.拖動過程中(自動觸發)
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.拖動結束
function onTouchEnd() {if (!canDrag.value) return;draggingIndex.value = -1;// 所有項重置為標準位置groupList.value.forEach((item, i) => {item.y = i * itemHeight;});// 同步順序到后端sortLabelList(groupList.value);canDrag.value = false;
}
完整代碼測試代碼
<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-viewdirection="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>