update
This commit is contained in:
parent
10cd938a18
commit
87508cd1f6
|
|
@ -0,0 +1,269 @@
|
||||||
|
<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>
|
||||||
Loading…
Reference in New Issue