Uniapp 实现左滑显示操作按钮的列表(适配多端 + 实战案例)

在 Uniapp 开发中,左滑显示编辑 / 删除按钮是移动端列表的常见交互需求,本文将基于实战场景,讲解如何实现「左滑显示操作按钮、默认隐藏」的列表效果,同时解决平板端适配、滑动误触、数据加载等核心问题。

注意:页面中有些简写样式是我自己封装的:比如h-146实际上就是.h-146{height:146rpx};有哪不明白的可以留下评论~

一、需求分析

本次实现的核心需求:

  1. 列表项默认只显示内容区域,编辑 / 删除按钮隐藏;
  2. 左滑列表项时显示操作按钮,右滑 / 点击其他项自动收起;
  3. 适配手机、iPad Pro 等多端设备,避免大屏布局错乱;
  4. 支持分页加载数据,操作按钮点击后有交互反馈。

二、核心实现思路

  1. 布局层 :使用scroll-viewscroll-x实现横向滑动,操作按钮区固定宽度,默认在内容区右侧隐藏;
  2. 交互层 :监听touchstart/touchend事件,通过计算滑动距离判断滑动方向,控制scroll-left值显示 / 隐藏按钮;
  3. 适配层:通过媒体查询和宽度限制,保证平板端布局和手机端一致;
  4. 数据层:实现分页加载,操作按钮点击后处理数据并更新视图。

三、完整代码实现

1. 模板结构(template)

html 复制代码
<template>
	<view>
		<view class="overflow-hidden">
			<block v-for="(item,index) in list" :key="index">
				<scroll-view 
					class="scroll-view_H m-top-20" 
					scroll-x="true" 
					:scroll-left="item.scrollLeft"  
					@touchstart="touchStart($event,index)" 
					@touchend="touchEnd($event,index)" 
					scroll-with-animation="true"
					:show-scrollbar="false"
				>
					<view class="scroll-view-item_H">
						<view class="dis-flex ali-center ">
							<!-- 内容展示区 -->
							<view class="content h-146 dis-flex ali-center jus-spa back-white border-radius-12 "  @tap="item.show_btn?showInfo(index):''">
								<view class="w-620 dis-flex ali-center">
									<image class="w-65 h-65 dis-block m-left-20" src="/static/grzx.png" mode=""></image>
									<view class="m-left-20">
										<view class="f-30 w-450 shenglue-11">
											{{item.address}}
										</view>
										<view class="f-26 col-gray m-top-10 w-450">
											{{item.name}} {{item.phone}}
										</view>
									</view>
								</view>
								<view class="w-90">
									<view class="w-90 h-90 dis-flex ali-center jus-center">
										<image class="w-25 h-24 dis-block" src="/static/images/my/bj.png" mode=""></image>
									</view>
								</view>
							</view>
							
							<!-- 操作按钮区:左滑显示 -->
							<view class="w-160 m-left-20 dis-flex ali-center">
								<view @touchstart="editItem(index)" class="w-80 h-80 back-white border-radius-50 dis-flex ali-center jus-center">
									<image class="w-40 h-40 dis-block" src="/static/common/e_icon.svg" mode=""></image>
								</view>
								<view @touchstart="deleteItem(index)" class="w-80 h-80 back-white border-radius-50 dis-flex ali-center jus-center">
									<image class="w-40 h-40 dis-block" src="/static/common/d_icon.svg" mode=""></image>
								</view>
							</view>
						</view>
					</view>
				</scroll-view>
			</block>
		</view>
		<view class="h-30"></view>
	</view>
</template>
  1. 逻辑处理(script)
html 复制代码
<script>
	export default {
		data() {
			return {
				touchStartX: 0,  // 触屏起始X坐标
				touchStartY: 0,  // 触屏起始Y坐标
				info: null,      // 接口返回信息
				list: [],        // 列表数据
				page: 1,         // 分页页码
				threshold: 50    // 滑动阈值(避免误触)
			}
		},
		onLoad() {
			this.page = 1;
			this.initData(); // 初始化加载数据
		},
		onReachBottom() {
			console.log('===触底加载下一页===');
			this.page += 1;
			this.initData();
		},
		methods: {
			// 分页加载数据
			initData(){
				const that = this;
				const url = 'employee/getList';
				const data = { page: that.page };
				// 异步请求(替换为自己的请求工具)
				that.util.asynRequest(url, data, 'post',
				function(res) {
					if(that.util.isNonEmptyPlainObject(res.data) && Array.isArray(res.data.data) && res.data.data.length > 0){
						that.info = res.data;
						// 拼接分页数据,初始化scrollLeft(隐藏按钮)
						const newData = res.data.data.map(item => ({
							...item,
							scrollLeft: 0,
							show_btn: true
						}));
						that.list = that.list.concat(newData);
					}else{
						if(that.page > 1) that.page--;
						that.util.showToast('暂无更多数据!');
					}
				},
				function(err) {
					console.error('请求失败:', err);
					that.util.showToast(err.info);
					if(that.page > 1) that.page--;
				})
			},
			
			// 编辑操作
			editItem(index){
				const curItem = this.list[index];
				uni.showToast({ title: `编辑${curItem.address}`, icon: 'none' });
				// 编辑后收起按钮
				curItem.scrollLeft = 0;
				curItem.show_btn = true;
				this.$forceUpdate();
			},
			
			// 删除操作
			deleteItem(index){
				const curItem = this.list[index];
				uni.showModal({
					title: '提示',
					content: `确定要删除${curItem.address}吗?`,
					success: (res) => {
						if (res.confirm) {
							this.list.splice(index, 1);
							uni.showToast({ title: '删除成功', icon: 'success' });
						}
					}
				});
			},
			
			// 触摸开始:记录起始坐标
			touchStart(e,index) {  
				this.touchStartX = e.touches[0].clientX;  
				this.touchStartY = e.touches[0].clientY;  
			},  
 
			// 触摸结束:判断滑动方向,控制按钮显示/隐藏
			touchEnd(e,index) {  
				const deltaX = e.changedTouches[0].clientX - this.touchStartX;  
				const deltaY = e.changedTouches[0].clientY - this.touchStartY;  
				
				// 收起其他所有项的按钮(保证同时只展开一个)
				this.list.forEach((item, i) => {
					if(i !== index){
						item.scrollLeft = 0;
						item.show_btn = true;
					}
				});
				
				// 只处理水平滑动(过滤上下滑动)
				if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > this.threshold) {  
					if (deltaX > 0) {  
						// 右滑:隐藏按钮
						this.list[index].scrollLeft = 0;
						this.list[index].show_btn = true;
					} else {  
						// 左滑:显示按钮(按钮总宽度360rpx)
						this.list[index].scrollLeft = 360;
						this.list[index].show_btn = false;
					}  
				}
				this.$forceUpdate();
			},     
		}
	}
</script>
  1. 样式适配(style)
html 复制代码
<style>
	page{
		background: #F6F6F6;
		padding: 0rpx 5rpx 20rpx 20rpx;
		box-sizing: border-box;
	}
	.scroll-view_H{
		white-space: nowrap; 
	}
	/* 平板端适配:限制内容区宽度 */
	@media screen and (min-width:900rpx) {
		.content {
			width: calc(750rpx - 35rpx);
		}
	}
	.scroll-view-item_H {
		display: inline-block;
	}
	/* 隐藏滚动条(多端兼容) */
	/* #ifdef MP-WEIXIN || APP-PLUS */
	::-webkit-scrollbar {
		display: none;
		width: 0 !important;
		height: 0 !important;
		-webkit-appearance: none;
		background: transparent;
	}
	/* #endif */
	/* #ifdef H5 */
	uni-scroll-view .uni-scroll-view::-webkit-scrollbar {
		display: none;
		width: 0 !important;
		height: 0 !important;
	}
	scroll-view ::-webkit-scrollbar {
		width: 0;
		height: 0;
		background-color: transparent;
	}
	/* #endif */
	/* 按钮样式 */
	.w-80 {
		width: 80rpx !important;
	}
</style>

四、核心知识点解析

1. 滑动交互核心

  • 滑动方向判断 :通过touchstarttouchend的坐标差deltaX判断左右滑,deltaY过滤上下滑动误触;
  • 滑动阈值 :设置threshold: 50,只有滑动距离超过 50px 才触发操作,避免手指轻微滑动导致的误操作;
  • scroll-left 控制 :默认scrollLeft: 0(隐藏按钮),左滑后设为按钮总宽度(显示按钮),结合scroll-with-animation实现平滑滑动。

2. 多端适配技巧

  • 平板端宽度限制 :通过媒体查询@media screen and (min-width:900rpx)固定内容区宽度,避免 iPad 等大屏设备布局拉伸;
  • 滚动条隐藏:区分小程序 / APP/H5 不同环境,通过 CSS 隐藏横向滚动条,保证视觉统一;
  • rpx 单位:全程使用 rpx 作为尺寸单位,自动适配不同屏幕分辨率。

3. 数据处理要点

  • 分页加载onReachBottom监听触底事件,拼接新数据时初始化scrollLeft,避免新数据默认显示按钮;
  • 操作后视图更新 :编辑 / 删除后通过$forceUpdate()强制更新视图,保证按钮状态同步;
  • 删除二次确认 :使用uni.showModal做删除确认,提升用户体验。

五、常见问题及解决方案

问题 1:左滑不显示按钮 / 按钮直接展示

  • 原因:scrollLeft值设置错误,或按钮区宽度与scrollLeft不匹配;
  • 解决:确保scrollLeft值等于操作按钮区总宽度,且内容区宽度覆盖按钮区(默认隐藏)。

问题 2:平板端布局错乱

  • 原因:大屏设备下内容区无限拉伸,按钮位置偏移;
  • 解决:通过媒体查询固定内容区最大宽度,或给scroll-view设置max-width: 750rpx; margin: 0 auto;

问题 3:滑动时多个列表项同时展开

  • 原因:未处理其他项的scrollLeft状态;
  • 解决:在touchEnd中遍历列表,将非当前项的scrollLeft重置为 0。

六、总结

本文实现的 Uniapp 左滑列表,核心是通过scroll-view横向滑动 + 触摸事件监听,结合多端适配技巧,实现了「左滑显示操作按钮、默认隐藏」的交互效果,同时支持分页加载、操作反馈等实战需求。

关键要点:

  1. 滑动阈值过滤误触,保证交互稳定性;
  2. 统一控制scrollLeft状态,避免多列展开;
  3. 媒体查询 + rpx 单位实现多端适配;
  4. 分页数据加载时初始化按钮状态。

该方案可直接应用于地址管理、员工管理、订单列表等场景,稍作修改即可适配不同 UI 设计需求。

相关推荐
蜡台2 小时前
Uniapp 实现 二手车价格评估 功能
前端·javascript·uni-app·估值·汽车抵押·二手车评估
Muchen灬3 小时前
【uniapp】(4) tabbar配置
uni-app
万物得其道者成6 小时前
UniApp 与 H5 双向通信完整教程
uni-app
2501_9160074720 小时前
HTTPS 抓包的流程,代理抓包、设备数据线直连抓包、TCP 数据分析
网络协议·tcp/ip·ios·小程序·https·uni-app·iphone
游戏开发爱好者821 小时前
React Native iOS 代码如何加密,JS 打包 和 IPA 混淆
android·javascript·react native·ios·小程序·uni-app·iphone
2501_915918411 天前
iOS mobileprovision 描述文件管理,新建、下载和内容查看
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张1 天前
iOS 应用程序使用历史记录和耗能记录怎么查?
android·ios·小程序·https·uni-app·iphone·webview
学亮编程手记1 天前
Mars-Admin 基于Spring Boot 3 + Vue 3 + UniApp的企业级管理系统
vue.js·spring boot·uni-app
万物得其道者成1 天前
uni-app CLI:APP 多环境打包(测试/正式)最简配置 + `import.meta.env` 为 `undefined` 的解决
uni-app