uniapp 拖拽排序原生写法,兼容各种平台,简单好用,易维护 新建一个.vue文件,做个封装 这里是vue3的写法,如果有需要,可以自己改成vue2的,如果不会改,随便找个 AI帮你转一下也行 uniapp 的 文件中创建好后放入下面的代码
其中这两个样式的内容你自己自定义改下就行 .touch
.active
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
interface ListItem {
index: number;
y: number;
animation: boolean;
[key: string]: any;
}
const props = defineProps<{
height: number;
list: Array<any>;
sortable: boolean;
}>();
const emit = defineEmits<{
(e: 'change', payload: { data: any; frontData: any | undefined }): void;
}>();
const currentList = ref<ListItem[]>([]);
const active = ref<number>(-1); // 当前激活的item
const itemIndex = ref<number>(0); // 当前激活的item的原index
const topY = ref<number>(0); // 距离顶部的距离
const deviationY = ref<number>(0); // 偏移量
const currentListLength = computed(() => {
return currentList.value.length * props.height;
});
const onUpdateCurrentList = () => {
const arr: ListItem[] = [];
for (const key in props.list) {
arr.push({
...props.list[key],
index: Number(key),
y: Number(key) * props.height,
animation: true
});
}
currentList.value = arr;
};
watch(() => props.list, onUpdateCurrentList);
onUpdateCurrentList();
const touchstart = (e: TouchEvent) => {
const query = uni.createSelectorQuery().in(this);
query.select('#drag').boundingClientRect();
query.exec((res: any) => {
topY.value = res[0].top;
const touchY = e.touches[0].clientY - res[0].top;
deviationY.value = touchY % props.height;
for (const key in currentList.value) {
if (
currentList.value[key].index * props.height < touchY &&
(currentList.value[key].index + 1) * props.height > touchY
) {
active.value = Number(key);
itemIndex.value = currentList.value[key].index;
break;
}
}
});
};
const touchmove = (e: TouchEvent) => {
if (active.value < 0) return;
const touchY = e.touches[0].clientY - topY.value - deviationY.value;
currentList.value[active.value].y = touchY;
for (const key in currentList.value) {
if (currentList.value[key].index !== currentList.value[active.value].index) {
if (currentList.value[key].index > currentList.value[active.value].index) {
if (touchY > currentList.value[key].index * props.height - props.height / 2) {
currentList.value[active.value].index = currentList.value[key].index;
currentList.value[key].index = currentList.value[key].index - 1;
currentList.value[key].y = currentList.value[key].index * props.height;
break;
}
} else {
if (touchY < currentList.value[key].index * props.height + props.height / 2) {
currentList.value[active.value].index = currentList.value[key].index;
currentList.value[key].index = currentList.value[key].index + 1;
currentList.value[key].y = currentList.value[key].index * props.height;
break;
}
}
}
}
};
const touchend = () => {
if (itemIndex.value !== currentList.value[active.value].index && active.value > -1) {
emit('change', {
data: (() => {
const data = { ...currentList.value[active.value] };
delete data.index;
delete data.y;
delete data.animation;
return data;
})(),
frontData: (() => {
for (const iterator of currentList.value) {
if (currentList.value[active.value].index - 1 === iterator.index) {
const data = { ...iterator };
delete data.index;
delete data.y;
delete data.animation;
return data;
}
}
})()
});
}
currentList.value[active.value].animation = true;
currentList.value[active.value].y = currentList.value[active.value].index * props.height;
active.value = -1;
};
</script>
<template>
<movable-area
id="drag"
class="drag-sort"
:style="{height: currentListLength + 'px'}"
>
<movable-view
v-for="(item, index) in currentList"
class="drag-sort-item"
:class="{'active': active === index, 'vh-1px-t': item.index > 0}"
:key="index"
:x="0"
:y="item.y"
:animation="active !== index"
:style="{ height: height + 'px'}"
direction="vertical"
disabled
damping="40"
>
<view :style="{width: '100%', height: height + 'px'}">
<slot name="item" :item="item" :index="index"></slot>
</view>
</movable-view>
<movable-view
v-if="sortable"
class="touch"
:x="2000"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend"
catchtouchstart
catchtouchmove
catchtouchend
></movable-view>
</movable-area>
</template>
<style lang='scss' scoped>
@import './styles/1px.scss';
.drag-sort {
width: 100%;
}
.drag-sort-item {
position: absolute !important;
display: flex;
align-items: center;
width: 100%;
padding: 0;
margin: 0;
background: #fff;
padding: 0 15px;
box-sizing: border-box;
.item {
flex: 1;
}
}
.touch {
height: 100%;
width: 50px;
background: forestgreen;
cursor: pointer;
}
.active {
box-shadow: 0 0 40rpx #dddddd;
z-index: 99;
}
</style>
然后就是样式部分 1px.scss
@mixin setLine($c: #eee) {
content: " ";
position: absolute;
left: 0;
top: 0;
width: 200%;
color: $c;
height: 200%;
transform-origin: left top;
transform: scale(0.5);
}
.vh-1px,
.vh-1px-t,
.vh-1px-b,
.vh-1px-tb,
.vh-1px-l,
.vh-1px-r {
position: relative;
}
.vh-1px {
&:before {
@include setLine();
}
}
@mixin setTopLine($c: #eee) {
content: " ";
position: absolute;
left: 0;
top: 0;
right: 0;
height: 1rpx;
color: $c;
transform-origin: 0 0;
transform: scaleY(0.5);
}
.vh-1px-t {
&:before {
@include setTopLine();
}
}
@mixin setBottomLine($c: #eee) {
content: " ";
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 1rpx;
color: $c;
transform-origin: 0 100%;
transform: scaleY(0.5);
}
.vh-1px-b {
&:after {
@include setBottomLine();
}
}
.vh-1px-tb {
&:before {
@include setTopLine();
}
&:after {
@include setBottomLine();
}
}
@mixin setLeftLine($c: #eee) {
content: " ";
position: absolute;
left: 0;
top: 0;
width: 1rpx;
bottom: 0;
color: $c;
transform-origin: 0 0;
transform: scaleX(0.5);
}
.vh-1px-l {
&:before {
@include setLeftLine();
}
}
@mixin setRightLine($c: #eee) {
content: " ";
position: absolute;
right: 0;
top: 0;
width: 1rpx;
bottom: 0;
color: $c;
transform-origin: 100% 0;
transform: scaleX(0.5);
}
.vh-1px-r {
&:after {
@include setRightLine();
}
}
之后为了不同业务需要,这里封装了一个使用方法,然后也可以参考这个使用方法的封装,去延展更多功能 然后这个也是使用方法, 这里在建一个.vue文件
注意 xxgSortableList> 这个组件就是我上面封装的第一个.vue文件的名字,你也可以自定义,这里写出来是提醒这个组件的来源
之所以用vue2的方式写,是因为懒,最后没有转了,同时也是准备着在弄一个vue2 vue3兼容的 uniapp 的 拖拽组件,就将就看吧,有需要直接转vue3,当然,不转的话 vue3也一样用,兼容的
这里面有个 <template #item="data"> 的插槽,里面是你自定义内容的地方, data就是数据了,里面的内容要使用这个data数据,才能正常的维护好拖拽排序的内容!如果有其他需要,再这里面也可以继续封装,我注释了一条lolt> 这个就是继续对外的插槽,也就是我二次封装的意义,当然,这个和使用无关,你不喜欢可以把 template #item="data"> 里面的代码全删掉写自己的,只需要数据用 data的就好了,你如果测试的话就先直接照抄!!!,更容易出效果
<template>
<xxgSortableList
v-if="list && list.length > 0"
:list="list"
:height="itemHeight"
:sortable="true"
@change="sortChange"
>
<template #item="data">
<uni-swipe-action>
<uni-swipe-action-item
:left-options="rightOptions"
:auto-close="false"
@change="swipeChange($event, data.index)"
@tap="swipeClick($event, data.index)"
>
<view
:style="{height: itemHeight + 'px'}"
@tap="onItemClick(data.item, data.index)"
>
<!-- <slot
name="item"
:item="data.item"
:index="data.index"
></slot> -->
{{ data.item.title }}
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</template>
</xxgSortableList>
<!-- <view>123123</view> -->
</template>
<script>
import xxgSortableList from "./xxgSortableList.vue"
export default {
name: "cs",
components:{
xxgSortableList
},
props: {
//数据
data: {
type: Array,
default () {
return []
}
},
// 作为key的字段
rowKey: {
type: String,
default: 'key'
},
// 条目高度
itemHeight: {
type: Number,
default: 60
},
// 禁用滑动
disabled: {
type: Boolean,
default: false
},
// 右侧滑动选项
rightOptions: {
type: Array,
default () {
return [{
text: '删除',
action: 'delete',
style: {
backgroundColor: '#F56C6C'
}
}]
}
},
// 是否可排序
sortable: {
type: Boolean,
default: true
}
},
data() {
return {
list: undefined,
startX: 0,
startY: 0,
dragX: 0,
dragY: 0,
offsetXMouse: 0,
offsetYMouse: 0,
currentIndex: -1,
targetIndex: -1,
}
},
created() {
this.initData(this.data)
},
methods: {
/**
* @param {Object} data
* 初始数据
*/
initData(data) {
console.log('我运行了???')
this.list = []
for(let i = 0 ; i < 10; i++) {
this.list.push({
key: i.toString(),
title: `title_${i}`
})
}
console.log(this.list)
},
/**
* @param {Object} 排序改变
*/
sortChange(e) {
console.log(e)
},
swipeClick(e, index) {
console.log(e, index);
},
swipeChange(e, index) {
console.log(e, index);
},
onItemClick(item, index) {
console.log(item, index);
},
}
}
</script>
最后呢,在你需要的页面文件中加上,就可以直接看效果了 记得import cs form ‘你自己放的位置’
简单的说,你们上面的代码一复制,然后这里一调用就成了,非常简单,然后自己去根据自己的需求去改吧!!!
<template>
<cs></cs>
</template>