项目需求:需要根据用户喜好手动排序(这里只需要上下排序)
排序前(左图) => 排序时(右图)
拖动演示
思路:
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" shadowbackground-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 * idxobj.left = it.leftarr.push(obj)})state.positionList = JSON.parse(JSON.stringify(arr))state.paramsList.forEach((it, idx) => {it.top = state.positionList[idx].topit.left = state.positionList[idx].left})})}//开始排序let sortHandle = async () => {if (!state.isSort) {getPos()state.isSort = truereturn}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 = falsegetParams()}}//按下let startHandle = (e, index, item) => {if (!state.isSort) returnstate.activeIdx = indexstate.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.startYstate.activeItem.top = state.activeItem.top + diffYstate.startY = e.touches[0].clientY//判断交叉元素let dragTop = state.activeItem.toplet dragBtm = dragTop + state.itemHeightlet intersectRow = getIntersectRow(dragTop, dragBtm)if (!intersectRow) returnlet 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].topit.left = state.positionList[idx].left})state.activeIdx = intersectIdxnextTick(() => {state.paramsList = [...copyParams]})}}//抬起let endHandle = (e, index) => {state.activeIdx = nullstate.activeItem = null}//交叉元素let getIntersectRow = (dragTop, dragBtm) => {let filter = state.positionList.filter(it => {let acmeFlag = it.top < dragTop && (it.top + state.itemHeight) > dragToplet lowFlag = it.top < dragBtm && (it.top + state.itemHeight) > dragBtmreturn acmeFlag || lowFlag})//取最近一个let midY = dragTop + state.itemHeight / 2filter.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函数,如需多行多列拖动只需再次基础上加部分逻辑即可