uniapp 拖拽排序,最原生的写法,也是兼容性最好的,适合各种平台

2,178 阅读4分钟

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>