326 lines
8.8 KiB
Vue
326 lines
8.8 KiB
Vue
<template>
|
||
<view class="p-row-32 white">
|
||
<movable-area :style="{ height: `${areaHeight}px`}">
|
||
<block v-for="(item, index) in list" :key="index">
|
||
<movable-view :key="item.sign" @change="e => change(e, index)" @touchend="touchend"
|
||
@touchstart="touchstart($event);touchStart(index)" class="drag_item"
|
||
:y="item.y" direction="vertical" :disabled='disabled' :style="{zIndex:`${current==index?2:1}`}">
|
||
<u-swipe-action :disabled="isSlide" :show="item.show" :index="index" :key="item.sign" @click="click"
|
||
@open="open" :options="options" :style="{zIndex:list.length-index}">
|
||
<view class="touch_view"
|
||
:style="{minHeight: item.itemHeight?item.itemHeight+'px':itemHeight+'px',borderBottom: (index+1)!=list.length?'1px solid #E8E9EB':'1px solid #fff',paddingTop: ifCustomHeight?'48rpx':'32rpx'}"
|
||
@click="$emit('click',item)">
|
||
<view class="left">
|
||
<text>{{ item[name]}}</text>
|
||
</view>
|
||
<view class="right">
|
||
<u-icon size="40" color="#B7B7BA" name="list"></u-icon>
|
||
</view>
|
||
</view>
|
||
</u-swipe-action>
|
||
</movable-view>
|
||
</block>
|
||
</movable-area>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
props: {
|
||
ifCustomHeight: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
propList: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
isSlide: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
height: {
|
||
type: Number,
|
||
default: 104
|
||
},
|
||
name: {
|
||
type: String,
|
||
default: 'name'
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
longpress: false,
|
||
oldIndex: -1, //记录移动前的位置,为了和移动后进行比较是否发生了位移
|
||
newIndex: -1, //当前移动到的位置
|
||
direction: '',
|
||
current: -1,
|
||
itemHeight: uni.upx2px(this.height),
|
||
difference: 0, //记录移动前的位置
|
||
disabled: true, //是否可以拖动
|
||
list: [], //渲染的list
|
||
options: [{
|
||
text: '删除',
|
||
style: {
|
||
backgroundColor: '#dd524d'
|
||
}
|
||
}],
|
||
timer: null,
|
||
startX: 0, //touchStart开始坐标
|
||
startY: 0,
|
||
};
|
||
},
|
||
created() {
|
||
this.ifCustomHeight && this.getElRect()
|
||
},
|
||
computed: {
|
||
areaHeight() {
|
||
let height = this.list.reduce((to, item) => {
|
||
to += (item.itemHeight || this.itemHeight)
|
||
return to
|
||
}, 0)
|
||
return height
|
||
},
|
||
},
|
||
watch: {
|
||
propList: {
|
||
handler(val) {
|
||
this.list = this.updateList(val);
|
||
this.cloneList = JSON.parse(JSON.stringify(this.list));
|
||
},
|
||
immediate: true,
|
||
deep: true
|
||
}
|
||
},
|
||
methods: {
|
||
// 计算滑动角度
|
||
touchstart(e) {
|
||
this.startX = e.changedTouches[0].clientX,
|
||
this.startY = e.changedTouches[0].clientY
|
||
},
|
||
//计算滑动角度 start 起点坐标 end 终点坐标
|
||
angle(start, end) {
|
||
var _X = end.X - start.X,
|
||
_Y = end.Y - start.Y;
|
||
//返回角度 Math.atan()返回数字的反正切值
|
||
return 360 * Math.atan(_Y / _X) / (2 * Math.PI);
|
||
},
|
||
getDirection(e) {
|
||
let {
|
||
startX,
|
||
startY
|
||
} = this;
|
||
let slidingRange = 45;
|
||
let touchMoveX = e.changedTouches[0].clientX;
|
||
let touchMoveY = e.changedTouches[0].clientY;
|
||
let angle = this.angle({
|
||
X: startX,
|
||
Y: startY
|
||
}, {
|
||
X: touchMoveX,
|
||
Y: touchMoveY
|
||
});
|
||
//为了方便计算取绝对值判断
|
||
if (Math.abs(angle) > slidingRange && touchMoveY < startY) {
|
||
// 向上滑动
|
||
this.direction = 'up'
|
||
};
|
||
if (Math.abs(angle) > slidingRange && touchMoveY > startY) {
|
||
// 向下滑动
|
||
this.direction = 'down'
|
||
}
|
||
},
|
||
// 获取一个目标元素的高度
|
||
getElRect() {
|
||
let query = uni.createSelectorQuery().in(this);
|
||
query.selectAll('.drag_item').boundingClientRect(res => {
|
||
// 如果节点尚未生成,res值为null,循环调用执行
|
||
if (res.length == 0) {
|
||
setTimeout(() => {
|
||
this.getElRect();
|
||
}, 10);
|
||
return;
|
||
}
|
||
for (let i = 0; i < res.length; i++) {
|
||
this.$set(this.list[i], 'itemHeight', res[i].height < this.itemHeight ? this.itemHeight :
|
||
res[i].height)
|
||
this.$set(this.list[i], 'y', this.getTop(this.list, i))
|
||
}
|
||
this.updateList(this.list)
|
||
this.cloneList = JSON.parse(JSON.stringify(this.list));
|
||
}).exec();
|
||
},
|
||
getTop(list = [], end = 0) {
|
||
let itemHeight = 0
|
||
for (let i = 0; i < list.length; i++) {
|
||
if (end == i) {
|
||
break
|
||
}
|
||
itemHeight += list[i]['itemHeight']
|
||
}
|
||
return itemHeight
|
||
},
|
||
// 添加或更新数组y值
|
||
updateList(list = []) {
|
||
return list.map((item, index) => {
|
||
let obj = {
|
||
...item,
|
||
show: false,
|
||
sign: Math.random() + index //防止不更新的情况和标识位
|
||
}
|
||
if (this.ifCustomHeight) {
|
||
return obj;
|
||
} else {
|
||
return {
|
||
...obj,
|
||
y: index * this.itemHeight
|
||
};
|
||
}
|
||
});
|
||
},
|
||
touchStart(index) {
|
||
this.disabled = false
|
||
this.current = index;
|
||
this.oldIndex = index;
|
||
},
|
||
change(e, index) {
|
||
// 不是当前元素位移直接不处理
|
||
if (index != this.current) return;
|
||
this.longpress = true
|
||
if (this.ifCustomHeight) {
|
||
this.difference = e.detail.y;
|
||
} else {
|
||
//位移的距离px
|
||
let difference = e.detail.y - this.current * this.itemHeight;
|
||
//计算位移几个位置
|
||
let tempIndex = difference > 0 ? parseInt((difference + (this.itemHeight / 2)) / this.itemHeight) :
|
||
parseInt((difference - (this.itemHeight / 2)) / this.itemHeight);
|
||
//当前拖到的位置
|
||
this.newIndex = Math.abs(this.current + tempIndex);
|
||
if (this.newIndex > -1 && this.oldIndex > -1 && this.newIndex !== this.oldIndex) {
|
||
this.changeList();
|
||
}
|
||
}
|
||
},
|
||
changeList() {
|
||
// 没想到怎么改变原数组, 直接拷贝一份数组出来,不考虑情况 一把梭 直接遍历拷贝的原数组.
|
||
//直接把改变位置赋值回去不行,不知道为什么,只能改变每一项的y值
|
||
let arr = JSON.parse(JSON.stringify(this.cloneList));
|
||
arr.splice(this.newIndex, 0, ...arr.splice(this.current, 1));
|
||
this.list.forEach((item, index) => {
|
||
if (index !== this.current) {
|
||
item.y = arr.findIndex(citem => citem.sign == item.sign) * this.itemHeight;
|
||
}
|
||
});
|
||
this.oldIndex = this.newIndex;
|
||
},
|
||
touchend(e) {
|
||
if (!this.longpress) {
|
||
return
|
||
}
|
||
if (this.ifCustomHeight) {
|
||
if (!this.list[this.current] || this.current < 0) {
|
||
return
|
||
}
|
||
this.getDirection(e)
|
||
let itemHeight = this.list[this.current]['itemHeight'];
|
||
console.log(this.difference);
|
||
let difference = this.difference;
|
||
let arr = JSON.parse(JSON.stringify(this.cloneList));
|
||
let index = this.current;
|
||
for (var i = 0; i < arr.length; i++) {
|
||
let item = arr[i]['y'] + arr[i]['itemHeight'] / 2
|
||
if (this.direction == 'down' && difference + itemHeight >= item) {
|
||
index = i
|
||
} else if (this.direction == 'up' && item >= difference) {
|
||
index = i
|
||
break
|
||
}
|
||
}
|
||
index >= 0 && arr.splice(index, 0, ...arr.splice(this.current, 1));
|
||
for (var i = 0; i < arr.length; i++) {
|
||
arr[i]['y'] = this.getTop(arr, i)
|
||
}
|
||
this.list = this.updateList(arr);
|
||
} else {
|
||
if (this.newIndex > -1 && this.current > -1 && this.newIndex !== this.current) {
|
||
this.cloneList.splice(this.newIndex, 0, ...this.cloneList.splice(this.current, 1));
|
||
this.list = this.updateList(this.cloneList);
|
||
}
|
||
}
|
||
this.cloneList = JSON.parse(JSON.stringify(this.list));
|
||
this.emitChang(this.list)
|
||
this.current = -1;
|
||
this.oldIndex = -1;
|
||
this.newIndex = -1;
|
||
this.disabled = true
|
||
this.difference=0
|
||
this.longpress = false
|
||
},
|
||
click(index, index1) {
|
||
if (index1 == 0) {
|
||
this.$emit('del', index)
|
||
}
|
||
},
|
||
// 如果打开一个的时候,不需要关闭其他,则无需实现本方法
|
||
open(index) {
|
||
// 先将正在被操作的swipeAction标记为打开状态,否则由于props的特性限制,
|
||
// 原本为'false',再次设置为'false'会无效
|
||
this.list[index].show = true;
|
||
this.list.map((val, idx) => {
|
||
if (index != idx) this.list[idx].show = false;
|
||
});
|
||
},
|
||
emitChang(arr) {
|
||
let tempArr = JSON.parse(JSON.stringify(arr))
|
||
tempArr.forEach(item => {
|
||
delete item.y
|
||
delete item.show
|
||
delete item.sign
|
||
})
|
||
this.$emit('change', tempArr)
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.drag_item {
|
||
width: 686rpx;
|
||
background-color: #fff;
|
||
height: max-content;
|
||
}
|
||
|
||
movable-area {
|
||
width: 686rpx;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.touch_view {
|
||
width: 686rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
|
||
.left {
|
||
display: flex;
|
||
align-items: center;
|
||
padding-bottom: 16rpx;
|
||
padding-right: 16rpx;
|
||
|
||
text {
|
||
font-size: 28rpx;
|
||
color: #31353D;
|
||
}
|
||
}
|
||
|
||
.right {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
background: #FFFFFF;
|
||
box-shadow: 0px 0px 8rpx 0px rgba(0, 0, 0, 0.08);
|
||
border-radius: 4rpx;
|
||
}
|
||
}
|
||
</style>
|