This commit is contained in:
zhouwentao 2025-11-09 09:25:32 +08:00
parent 10cd938a18
commit 87508cd1f6
1 changed files with 269 additions and 0 deletions

View File

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