项目需求:需要根据用户喜好手动排序(这里只需要上下排序)
排序前(左图) => 排序时(右图)
拖动演示
思路:
1.创建一个拖动的元素,当拖动元素与其他元素触碰时更换位置重排列表
2.长按元素记录起点位置与下标,并将目标元素赋值与拖动元素
3.移动时处理触碰逻辑
4.松手时清空拖动元素
5.全部代码如下
1.getPos方法获取每个元素的位置top/left,便于交互后重新排列的元素定位
2.getIntersectRow方法根据拖动元素的top及bottom判断与那个元素相交,可能不止一个,取相交部分最多一个
<template>
<view class="realTime">
<uni-nav-bar dark left-icon="left" left-text="" @clickLeft="prePage" :fixed="true" shadow
background-color="#3B45FF" status-bar title="实时参数" />
<view class="wrap boxSize" @touchmove="moveHandle" @touchend="endHandle">
<view class="paramsIt boxSize" @longpress="e => startHandle(e, index, item)" :class="{'sortIt': isSort}"
:style="{'top':item.top + 'px', 'left': item.left + 'px'}" v-for="(item,index) in paramsList"
:key="item.key">
<view class="left">
<image v-if="isSort" src="@/static/svg/sort.svg" mode=""></image>
<view class="name">{{item.key}}</view>
</view>
<view class="val" v-if="!isSort">{{`${item.val}${item.unit}`}}</view>
</view>
<view class="paramsIt boxSize sortIt active" v-if="activeItem"
:style="{'top':activeItem.top + 'px', 'left': activeItem.left + 'px'}">
<view class="left">
<image src="@/static/svg/sort.svg" mode=""></image>
<view class="name">{{activeItem.key}}</view>
</view>
</view>
</view>
<view class="btnBox boxSize">
<button type="default" style="color:#ffffff;backgroundColor:#3B45FF;borderColor:#1AAD19" class="btn"
@tap="sortHandle">{{isSort ? '确认' : '参数排序'}}</button>
</view>
</view>
</template>
<script setup>
import {
getCurrentInstance,
nextTick,
reactive,
toRefs
} from 'vue'
import {
prePage,
goPage
} from '@/utils/util'
import {
request
} from '@/utils/api'
import {
onLoad
} from "@dcloudio/uni-app"
onLoad(config => {
openFunction(config)
})
let state = reactive({
routeParams: {},
isSort: false,
activeIdx: null,
activeItem: null,
copyItem: null,
itemHeight: 65,
positionList: [],
startY: '',
paramsList: []
})
let {
isSort,
activeIdx,
positionList,
paramsList,
activeItem
} = toRefs(state)
let pageInstance = getCurrentInstance()
let openFunction = (config) => {
state.routeParams = JSON.parse(JSON.stringify(config))
getParams()
}
//获取所有参数
let getParams = async () => {
let res = await request('device/FieldOrder/current_data/', 'GET', {
pond_id: state.routeParams.pond_id,
user_id: JSON.parse(uni.getStorageSync('userInfo')).id
})
if (res) {
state.paramsList = JSON.parse(JSON.stringify(res.data))
}
}
//获取所有元素的定位信息
let getPos = () => {
let query = uni.createSelectorQuery(pageInstance.proxy)
query.selectAll('.paramsIt').boundingClientRect().exec(res => {
let arr = []
res[0].forEach((it, idx) => {
let obj = {}
obj.top = (it.top - 88) + 12 * idx
obj.left = it.left
arr.push(obj)
})
state.positionList = JSON.parse(JSON.stringify(arr))
state.paramsList.forEach((it, idx) => {
it.top = state.positionList[idx].top
it.left = state.positionList[idx].left
})
})
}
//开始排序
let sortHandle = async () => {
if (!state.isSort) {
getPos()
state.isSort = true
return
}
let paramsList = state.paramsList.map(it => ({
key: it.key,
val: it.val,
unit: it.unit,
status: it.status
}))
let res = await request('device/FieldOrder/', 'POST', {
pond_id: state.routeParams.pond_id,
user_id: JSON.parse(uni.getStorageSync('userInfo')).id,
result: paramsList
})
if (res) {
uni.showToast({
icon: 'none',
title: '操作成功!'
})
state.isSort = false
getParams()
}
}
//按下
let startHandle = (e, index, item) => {
if (!state.isSort) return
state.activeIdx = index
state.activeItem = JSON.parse(JSON.stringify(item))
state.startY = e.touches[0].clientY
}
//移动
let moveHandle = (e, index) => {
if (!state.activeItem) return
//元素跟随鼠标移动
let diffY = e.touches[0].clientY - state.startY
state.activeItem.top = state.activeItem.top + diffY
state.startY = e.touches[0].clientY
//判断交叉元素
let dragTop = state.activeItem.top
let dragBtm = dragTop + state.itemHeight
let intersectRow = getIntersectRow(dragTop, dragBtm)
if (!intersectRow) return
let intersectIdx = state.positionList.findIndex(i => i.top == intersectRow.top)
//其他元素变换位置
let copyParams = [...state.paramsList]
if (intersectIdx !== state.activeIdx) {
copyParams.splice(state.activeIdx, 1)
copyParams.splice(intersectIdx, 0, state.paramsList[state.activeIdx])
copyParams.forEach((it, idx) => {
it.top = state.positionList[idx].top
it.left = state.positionList[idx].left
})
state.activeIdx = intersectIdx
nextTick(() => {
state.paramsList = [...copyParams]
})
}
}
//抬起
let endHandle = (e, index) => {
state.activeIdx = null
state.activeItem = null
}
//交叉元素
let getIntersectRow = (dragTop, dragBtm) => {
let filter = state.positionList.filter(it => {
let acmeFlag = it.top < dragTop && (it.top + state.itemHeight) > dragTop
let lowFlag = it.top < dragBtm && (it.top + state.itemHeight) > dragBtm
return acmeFlag || lowFlag
})
//取最近一个
let midY = dragTop + state.itemHeight / 2
filter.forEach(i => i.diffY = Math.abs((i.top + state.itemHeight / 2) - midY))
filter.sort((a, b) => a.diffY - b.diffY)
return filter[0] || null
}
</script>
<style lang="scss" scoped>
.realTime {
width: 100%;
height: 100%;
.wrap {
width: 100%;
height: calc(100% - 160px);
padding: 12px 18px;
overflow-y: auto;
border-radius: 8px;
position: relative;
.paramsIt {
position: relative;
width: 100%;
height: 65px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
transition: all 0.3s ease; // 添加过渡效果
.left {
display: flex;
align-items: center;
image {
width: 24px;
height: 24px;
margin-right: 10px;
}
.name {
font-family: PingFang SC;
font-size: 18px;
font-weight: 500;
line-height: normal;
letter-spacing: 0px;
font-variation-settings: "opsz" auto;
/* 正文色/正文色 */
color: #1A1A1A;
}
}
.val {
font-family: PingFang SC;
font-size: 18px;
font-weight: normal;
line-height: normal;
text-align: right;
letter-spacing: 0px;
font-variation-settings: "opsz" auto;
/* 字体/次要文字 */
color: #909399;
}
}
.sortIt {
position: absolute;
width: calc(100% - 36px);
border-radius: 8px;
margin-bottom: 12px;
}
.active {
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
/* 添加阴影 */
opacity: 0.8;
/* 降低透明度 */
// 确保拖拽元素在最上层
z-index: 2;
// 拖拽元素禁用过渡
transition: none !important;
}
}
.btnBox {
width: 100%;
padding: 0 18px;
display: flex;
align-items: center;
justify-content: space-between;
.btn {
width: 100%;
}
}
}
</style>
注意: 拖动后再给原数组赋值时需加上nextTick函数,如需多行多列拖动只需再次基础上加部分逻辑即可