yitisheng-mini-app/components/m-drag/m-drag.vue

269 lines
6.5 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ul class="m-drag" :style="{ height: (itemHeight * state.newList.length) + 'px' }">
<li v-for="(item, index) in state.newList" :key="index" class="m-drag-item"
:class="{ active: state.currentIndex === index }" :style="{
top: state.itemYList[index].top + 'px'
}">
<slot :item="item" />
<!-- css实现拖拽图标 -->
<div class="icon" @touchstart.stop="touchStart($event, index)" @touchmove.stop="touchMove" @touchend.stop="touchEnd">
<image src="/static/icons/move.png" class="icon50 margin-left-30" />
<!-- <i class="lines" /> -->
</div>
<div class="icon" >
<image src="/static/icons/delete.png" class="icon50 margin-left-30" />
</div>
</li>
</ul>
</template>
<script setup>
import { reactive, watch } from 'vue'
const emits = defineEmits(['change'])
const props = defineProps({
// 每一项item高度必须
itemHeight: {
type: Number,
required: true
},
// 数据列表,必须
list: {
type: Array,
required: true
},
// 是否只读
readonly: {
type: Boolean,
default: false
}
})
const state = reactive({
// 数据
newList: [],
// 初始数据
initialList: [],
// 记录所有item的初始坐标
initialItemYList: [],
// 坐标数据
itemYList: [],
// 记录当前手指的垂直方向的坐标
touchY: 0,
// 记录当前操作的item数据
currentItemY: {},
// 当前操作的item的下标
currentIndex: -1
})
watch(
() => props.list,
(val) => {
if (!val?.length) return
// 获取数据列表
state.newList = [...val]
// 记录初始数据
state.initialList = [...val]
// 获取所有item的初始坐标
state.initialItemYList = getItemsY()
// 初始化坐标
state.itemYList = getItemsY()
},
{
immediate: true
}
)
/** @初始化各个item的坐标 **/
function getItemsY() {
return props.list.map((item, i) => {
return {
top: (i * props.itemHeight)
}
})
}
let isDragging = false
function touchStart(event, index) {
if (props.readonly) return
// ⚠️ 标记为正在拖拽
state.isDragging = true
h5BodyScroll(false)
const [{ pageY }] = event.touches
state.currentIndex = index
state.touchY = pageY
state.currentItemY = state.itemYList[index]
}
function touchMove(event) {
if (props.readonly || !state.isDragging) return
const [{ pageY }] = event.touches
const current = state.itemYList[state.currentIndex]
const prep = state.itemYList[state.currentIndex - 1]
const next = state.itemYList[state.currentIndex + 1]
// 计算移动距离
const diff = Math.abs(pageY - state.touchY)
// 只有移动超过一定距离才认为是拖拽(避免误触)
if (diff > 5) { // 5px阈值
state.itemYList[state.currentIndex] = {
top: current.top + (pageY - state.touchY)
}
state.touchY = pageY
// 交换位置逻辑
if (next && current.top > next.top - props.itemHeight / 2) {
changePosition(state.currentIndex + 1)
} else if (prep && current.top < prep.top + props.itemHeight / 2) {
changePosition(state.currentIndex - 1)
}
}
}
/** @手指松开 */
function touchEnd() {
if (props.readonly) return
// 触发change
change()
// 归位
state.itemYList[state.currentIndex] = state.initialItemYList[state.currentIndex]
state.initialList = [...state.newList]
state.currentIndex = -1
state.isDragging = false // ← 重置拖拽状态
// 恢复滚动
h5BodyScroll(true)
}
/** @交换位置 **/
// index 需要与第几个下标交换位置
function changePosition(index) {
// 记录当前拖拽的item数据
const tempItem = state.newList[state.currentIndex]
// 设置原来位置的item
state.newList[state.currentIndex] = state.newList[index]
// 将临时存放的数据设置好
state.newList[index] = tempItem
// 调整位置item
state.itemYList[index] = state.itemYList[state.currentIndex]
state.itemYList[state.currentIndex] = state.currentItemY
// 改变当前操作的的下标
state.currentIndex = index
// 记录新位置的数据
state.currentItemY = state.initialItemYList[state.currentIndex]
}
/** @回传数据 **/
function change() {
// 如果顺序未发生改变则不触发change事件
if (JSON.stringify(state.newList) == JSON.stringify(state.initialList)) return
// 传给父组件新数据
emits('change', state.newList, state.newList[state.currentIndex])
}
// h5 ios回弹
function h5BodyScroll(flag) {
// #ifdef H5
document.body.style.overflow = flag ? 'initial' : 'hidden'
document.body.style.webkitOverflowScrolling = flag ? 'touch' : 'auto'
// #endif
}
</script>
<style scoped lang="scss">
.m-drag {
position: relative;
}
.m-drag-item {
position: absolute;
left: 0;
right: 0;
transition: all ease 0.25s;
display: flex;
align-items: center;
// 防止长按选中文字或图片
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
.content-wrapper {
flex: 1;
}
.icon {
position: relative;
top: -40rpx;
// padding: 30rpx;
.lines {
background: #e0e0e0;
width: 20px;
height: 2px;
border-radius: 100rpx;
margin-left: auto;
position: relative;
display: block;
transition: all ease 0.25s;
top: -40rpx;
&::before,
&::after {
position: absolute;
width: inherit;
height: inherit;
border-radius: inherit;
background: #e0e0e0;
transition: inherit;
content: '';
display: block;
}
&::before {
top: -14rpx;
}
&::after {
bottom: -14rpx;
}
}
}
&.active {
box-shadow: 0 0 14rpx rgba(0, 0, 0, 0.08);
transition: initial;
z-index: 1;
.icon .lines {
background: #2e97f9;
&::before,
&::after {
background: #2e97f9;
}
}
}
}
</style>