最近在做一个uni-app的项目,其中有列表页中弹出查询弹窗的功能。列表中有下拉刷新和上拉加载的功能。

遇到的问题有:

1。在弹窗中上下滑动,底层页面也随着滑动

2。在弹窗中下拉时,也会触发下拉刷新功能,上划时,也可能会触发上拉加载功能;

3。在弹窗中的树形列表框加了滚动后,会报错:Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted.

4。在禁了底层滑动后,在弹窗中有一个弹出的树形列表框,在列表框里的空白区域,上下滑动,底层页面还是会随着滑动。

5。scroll-view横向滚动

解决方法一:

1。在弹窗中上下滑动,底层页面也随着滑动

需要在弹窗的最外层和显示区域的最外层加上@touchmove.prevent或@touchmove.prevent="moveHandle",moveHandle方法中只有一个return false;

<template>
    <view v-if="isShowConfirm" class="my-confirm-notice" @touchmove.prevent="moveHandle">
        <view class="confirm-content-wrap1" @touchmove.prevent="moveHandle">
            <!-- 标题 -->
            <view class="unipop__ui_tit">
            	<view v-if="titleText" class="unipop__ui_tit_text">{{titleText}}</view>
            	<image src="../../static/images/workbeach/icon_close.png" class="close_image" @click="close()"></image>
            </view>
            <!-- 内容 -->
			<view class="content_margin">
				<view class="flex_row_center label_text">实体分类:
					<input type="text" disabled="true" class="inputStyle" ref='groupInput'
					 @click="popOrganization" placeholder="请选择实体分类" :value='groupName'>
				</view>
				<view v-show="isShow" class="top_level">
					<view class="flex_row_center label_text">
						<view style="width: 140rpx;"></view>
						<scroll-view ref="scrollId" class="scroll_view_style" :scroll-y="true" :scroll-x="true" @touchmove.stop>
							<ly-tree ref="tree" :treeData="treeData" v-if="ready"
								nodeKey="orgCode" :showRadio="true" :checkOnClickNode="true" :expandOnClickNode="false"
								:defaultExpandAll="true" @check='handleCheck' class="ly_tree_style"
								></ly-tree>
						</scroll-view>
					</view>
				</view>
			</view>
            <view v-if="btns" class="unipop__ui_btns">
                <text v-for="(item,index) in btns" :key="index" class="btn" :style="item.style" @tap="btnTaped(item)">{{item.text}}</text>
            </view>
        </view>
    </view>
</template>

2. 在弹窗中下拉时,也会触发下拉刷新功能,这个使用的是动态设置下拉刷新的功能解决的,在app上亲测可用,在H5上会报错。

isPullDown(isPull) {
                                //获取当前 Webview 窗口对象
				const pages = getCurrentPages();
				const page = pages[pages.length - 1];  
				const currentWebview = page.$getAppWebview();
                                //根据状态值来切换禁用/开启下拉刷新
				currentWebview.setStyle({  
				  pullToRefresh: {  
				    support: isPull,  
				    // style: plus.os.name === 'Android' ? 'circle' : 'default',
					style:'circle'  
				  }  
				}); 
			},

在弹窗显示的show方法中设置

this.isPullDown(false);

在弹窗关闭方法中设置

this.isPullDown(true);

即可在弹窗显示时,禁用下层的下拉刷新功能,弹窗关闭时,开启下拉刷新;

3。弹窗中的树形列表框,数据太长,加了scroll-view,滚动显示;但在滚动时,总是报“Ignored attempt to cancel a touchmove event with cancelable=false, for example because scrolling is in progress and cannot be interrupted.”

解决方法:

在scroll-view中加上@touchmove.stop

<scroll-view ref="scrollId" class="scroll_view_style" :scroll-y="true" :scroll-x="true" @touchmove.stop>
	<ly-tree ref="tree" :treeData="treeData" v-if="ready"
		nodeKey="orgCode" :showRadio="true" :checkOnClickNode="true" :expandOnClickNode="false"
		:defaultExpandAll="true" @check='handleCheck' class="ly_tree_style"
	></ly-tree>
</scroll-view>

4。加了第1个问题的解决措施后,在树形列表框中的空白区域上下滑动,下层页面也会随着滚动。

在scroll-view中加入@touchmove.prevent会解决下层页面随之滚动的问题,但第3个问题中的@touchmove.stop就会失效。

另一种解决方法:

上述4个问题中第1和第4个问题的另一种解决方法:

通过设置下层页面的css样式,设置一个标志位isShowDialog,初始值为false,当isShowDialog=true时,禁止页面滚动,当isShowDialog=false时,取消禁止。需要在弹窗显示时,将此标志位设为true,弹窗关闭时设为false,在弹窗中使用$emit把值传递给底层页面,底层页面使用$on接收。若弹窗页面中没有滚动scroll-view,直接用第一种方法就可以。

<template>
	<view :class="isShowDialog?'no-scroll-bg':''">
        ……
    </view>
</template>

data() {
    return {
	isShowDialog:false
    }
},

onLoad() {
	uni.$on('noScrollP',(options) => {
		this.isShowDialog = false;
	})
},

<style>
        .no-scroll-bg {
		overflow: hidden;
		position: fixed;
		height: 100%;
		width: 100%;
	}
</style>

 在弹窗页面关闭方法中加入:

uni.$emit('noScrollP',this.isShowConfirm);

 使用这种方法就可以把上面方法中的@touchmove.prevent去掉。但有点小问题(当弹窗弹出时,不管底层的列表页显示到哪个位置,都会滚动到顶部)

详细代码如下:

<template>
	<view :class="isShowDialog?'no-scroll-bg':''">
		<uni-nav-bar left-icon="back" title="人员管理" backgroundColor="#074498" color="white" fixed="true"
			statusBar='true' @clickLeft="goBack" @clickRight="searchPerson">
			<view slot="right" class="flex_vcenter">
				<image src="../../static/images/workbeach/icon_search.png" style="width: 25px; height: 25px;"></image>
			</view>
		</uni-nav-bar>
		<uni-list :border="false">
			<uni-list-item v-for="(item,index) in personList" :key="index" :border="false" direction="column" class="item" clickable @click="gotoDetail(item)">
				<template v-slot:header>
					<view class="common_color title_size">{{item.nickName}}</view>
					<view class="status_style" :class="item.status=='0'?'status_color_use':'status_color_forbidden'">{{item.status=='0'?'正常':'停用'}}</view>
				</template>
				<template v-slot:body>
					<view class="divider"></view>
					<view class="flex_vcenter">
						<view class="item_content">
							<text class="item_text">员工编码:{{item.userCode}}</text>
							<text class="item_text">所属部门:{{item.deptName}}</text>
							<text class="item_text">职位:{{item.postNames}}</text>
							<text class="item_text">入职时间:{{item.employmentDate}}</text>
						</view>
						<image src="../../static/images/workbeach/arrow_right.png" class="arrow_right"></image>
					</view>
				</template>
			</uni-list-item>
		</uni-list>
		<uni-load-more :status="more" :contentText="contentText" @clickLoadMore="more=='error'?getPersonelList():''"></uni-load-more>
		<PopDialog ref='personDialog'></PopDialog>
	</view>
</template>

<script>
	import PopDialog from 'pages/personnel/PopDialog'
	import ApiService from './service.js'
	import Tips from '../../utils/tips.js'
	export default {
		components:{
			PopDialog
		},
		data() {
			return {
				personList:[],
				searchCondition:{},
				more:'more',
				pageNum:1,
				pageSize:10,
				contentText: {
					contentdown: '上拉加载更多',
					contentrefresh: '正在加载中',
					contentnomore: '没有更多数据了',
					contenterror: '数据异常,请点击此处重新加载'
				},
				reLoad:false,
				isShowDialog:false
			}
		},
		created() {
			this.getPersonelList();
			// this.getServerData()
			console.log('created');
		},
		mounted() {
			console.log('mounted');
		},
		onShow() {
			console.log('onShow');
		},
		onLoad() {
			uni.$on('noScrollP',(options) => {
				console.log('onLoad=================',options);
				this.isShowDialog = false;
			})
		},
		onReachBottom() {
			console.log('onReachBottom');
			if(this.more != 'noMore') {
				this.more = 'more';
				this.getPersonelList();
			}
		},
		onPullDownRefresh() {
			console.log('onPullDownRefresh');
			this.reLoad = true;
			this.pageNum = 1;
			this.getPersonelList();
		},
		methods:{
			goBack() {
				uni.navigateBack();//跳转到上一个页面
			},
			searchPerson() {
				let _self = this;
				this.isShowDialog = true;
				let uniPop = this.$refs.personDialog
				uniPop.show('查询条件',{
				    btns: [
				        {
				            text: '重置',
				            style: 'color: #ffffff',
				            onTap() {
				                uniPop.close();
				            }
				        },
				        {
				            text: '确定',
				            style: 'color: #ffffff',
				            onTap(data) {
				                console.log('您点击了前往设置!'+JSON.stringify(data));
								console.log('您点击了前往设置!',_self.isShowDialog);
								_self.searchCondition = data;
								_self.pageNum = 1;
								_self.reLoad = true;
								_self.getPersonelList();
								// _self.getServerData();
				            }
				        }
				    ]
				});
			},
			async getPersonelList() {
				this.searchCondition['pageSize'] = this.pageSize;
				this.searchCondition['pageNum'] = this.pageNum;
				this.more = 'loading';
				let res = await ApiService.getPersonnelList(this.searchCondition);
				if(res.success) {
					if(res.data.code == 200) {
						this.personList = this.reLoad?res.data.data.rows:this.personList.concat(res.data.data.rows);
						
						if(this.personList.length != res.data.data.total) {
							this.pageNum += 1;
							this.more = 'more';
						} else {
							this.more = 'noMore';
						}
					} else {
						if(this.pageNum == 1) {
							this.personList = [];
						}
						Tips.toast(res.data.msg);
						this.more = 'error';
					}
					
				} else {
					 Tips.toast(res.message);
					 this.more = 'error';
				}
				if(this.reLoad) {
					uni.stopPullDownRefresh();
					this.reLoad = false;
				}
			},
			gotoDetail(item) {
				console.log(item)
				uni.navigateTo({
					url: 'personnelDetail?item='+JSON.stringify(item)
				})
			}
		}
	}
</script>

<style>
	page {
		background: #f9f9f9;
		overflow-x: hidden;
	}
	.no-scroll-bg {
		overflow: hidden;
		position: fixed;
		height: 100%;
		width: 100%;
	}
	.nav-style {
		font-family: 'PingFangSC-Semibold', 'PingFang SC Semibold', 'PingFang SC';
		height: 45px;
	}
	.status_style {
		float: right;position: absolute;right: 35px;
		font-size: 16px;
		font-family: 'PingFangSC-Regular', 'PingFang SC';
	}
	.common_color {
		color:#074498
	}
	.title_size {
		font-size: 16px;
	}
	.status_color_use {
		color: #73B703;
	}
	.status_color_forbidden {
		color: #D9001B;
	}
	.item_text {
		/* padding-top: 8px; */
		font-family: 'PingFangSC-Regular', 'PingFang SC';
		color: #333333;
		font-size: 14px;
		line-height: 25px;
	}
	.arrow_right {
		width: 20px;
		height: 25px;
		float: right;
		right: 20px;
		position: absolute;
	}
	.item_content {
		padding: 10px 0px 0px 0px; display: flex;flex-direction: column;
	}
	.divider {
		height: 0.5px;width: 100%; background-color: #dbdbdb;margin-top: 10px;
	}
	.flex_vcenter {
		display: flex; align-items: center;
	}
	.item {
		border-bottom: #f9f9f9 8px solid;padding: 0px 15px 0px 15px;
	}
</style>

弹窗代码:

<template>
    <view v-if="isShowConfirm" class="my-confirm-notice">
        <view class="confirm-content-wrap1">
            <!-- 标题 -->
            <view class="unipop__ui_tit">
				<view v-if="titleText" class="unipop__ui_tit_text">{{titleText}}</view>
				<image src="../../static/images/workbeach/icon_close.png" class="close_image" @click="close()"></image>
			</view>
            <!-- 内容 -->
            <view class="content_margin">
            	<view class="flex_row_center label_text">组织机构:
            		<input type="text" disabled="true" class="inputStyle" ref='groupInput'
            		 @click="popOrganization" placeholder="请选择组织机构" :value='groupName'>
            	</view>
            	<view v-show="isShow" class="top_level">
            		<view class="flex_row_center label_text">
            			<view style="width: 140rpx;"></view>
            			<scroll-view class="scroll_view_style" :scroll-y="true" :scroll-x="true" @touchmove.stop @scrolltoupper='upScroll'>
            				<ly-tree ref="tree" :treeData="treeData" v-if="ready" :showRadio="true"
            					nodeKey="deptId" :checkOnClickNode="true" 
            					:expandOnClickNode="false" :defaultExpandAll="true"
								@check='handleCheck' :props="props" class="ly_tree_style"
            					></ly-tree>
            			</scroll-view>
            		</view>
            	</view>
            	<view class="flex_row_center label_text label_margin">
					<view style="width: 140rpx;">姓名:</view>
            		<input type="text" class="inputStyle" placeholder="姓名" @input="onKeyInput" :value="userName" >
            	</view>
				<view class="flex_row_center label_text label_margin">
					<view style="width: 140rpx;">状态:</view>
					<wyb-drop-down 
						 ref="dropDown"
						 :options="dropDownOptions"
						 :heightSize="heightSize"
						 @select="onItemSelect"
						 class="dropDownStyle">
					   <!-- 自定义模式开启时,这里可以放内容 -->
					</wyb-drop-down>
				</view>
				<view class="flex_row_center label_text label_margin">
					<view style="width: 140rpx;">入职时间:</view>
					<datetimePicker
						type="date"
						:value="start"
						@change="changeStart"
						class="dataPickerStyle"/>
				</view>
				<view class="flex_row_center label_text label_margin">
					<view class="dateOperator">-</view>
					<datetimePicker
						type="date"
						:value="end"
						@change="changeEnd"
						class="dataPickerStyle"/>
				</view>
            </view>
            <view v-if="btns" class="unipop__ui_btns">
                <text v-for="(item,index) in btns" :key="index" class="btn" :style="item.style" @tap="btnTaped(item)">{{item.text}}</text>
            </view>
        </view>
    </view>
</template>


<script>
	import datetimePicker from '@/components/uni-datetime-picker/uni-datetime-picker.vue'
	import ApiService from './service.js'
	import Tips from '../../utils/tips.js'
	export default {
		components:{datetimePicker},
		data() {
		    return {
		        isShowConfirm: false,
		        titleText: '', // 提示框标题
		        outerData: null, // 用于记录外部传进来的数据,也可以给外部监听userBehavior,事件的函数提供判断到底是哪个事件触发的
		        btns: [],
				treeData:[],
				ready:false,
				isShow:false,
				selectCondition:{},
				userName:'',
				groups:[],
				groupName:'',
				start: '',
				end:'',
				dropDownOptions: [{
					header: '状态',
					// contents在自定义开启时可以不用传
					contents: ['状态', '正常', '停用'], 
					custom: false // 该栏目开启自定义模式
				}],
				heightSize:{
					header:40,content:48
				},
				props: {
					// id:'deptId',
					label: 'deptName' // 指把数据中的‘categoryName’当做label也就是节点名称
				}
		    }
		},
		mounted() {
			console.log('mounted')
			this.getDeptData();
			// this.loadData();
			// this.single = new Date();
		},
		methods: {
			upScroll(params) {
				console.log('upScroll',params);
			},
			show (titleText, config) {
				this.titleText = titleText || '查询条件'
			
				if (Object.prototype.toString.call(config) === '[object Object]') {
					// 确保用户传递的是一个对象
					this.outerData = config || null
					this.btns = this.outerData.btns
					console.log('noticeDialog', this.outerData)
				}
				this.isPullDown(false);
				this.isShowConfirm = true
			},
			close(){
				this.isShowConfirm = false;
				this.isShow = false;
				this.groupName = '';
				this.groups = [];
				this.userName = '';
				this.start = '';
				this.end = '';
				this.dropDownOptions[0].header = '状态';
				this.selectCondition = {};
				this.isPullDown(true);
				uni.$emit('noScrollP',this.isShowConfirm);
			},
			btnTaped(item) {
				console.log(item);
				if(item.text == '重置') {
					this.isShow = false;
					this.groupName = '';
					this.groups = [];
					this.$refs.tree.setCheckedKeys([]);

					this.userName = '';
					this.start = '';
					this.end = '';
					this.dropDownOptions[0].header = '状态';
					this.$refs.dropDown.onContentItemsTap(0);
					this.selectCondition = {};
				} else {
					item.onTap(this.selectCondition);
					this.close();
				}
			},
			async getDeptData() {
				let res = await ApiService.getDeptList();
				console.log('getDeptData',res);
				if(res.success) {
					if(res.data.code == 200) {
						this.treeData = res.data.data;
						// this.findData(this.treeData);
						// console.log('getDeptData',JSON.stringify(this.treeData));
						this.ready = true;
					} else {
						Tips.toast(res.data.msg);
					}
				} else {
					Tips.toast(res.message);
				}
			},
			findData(arr) {
				arr.forEach((item) => {
					item.id = item.deptId;
					if(item.children.length > 0) {
						this.findData(item.children);
					}
				})
			},
			popOrganization() {
				this.isShow = !this.isShow;
			},
			onKeyInput(event) {
				this.userName = event.target.value;
				this.selectCondition['nickName'] = this.userName;
			},
			handleCheck(obj) {
				console.log('handleCheck='+JSON.stringify(obj))
				this.getSelectData(obj.node);
			},
			getSelectData(obj) {
				let groupParam = '';
				let groupNames = '';
				if(obj.checked) {
					groupParam = obj.data.deptId;
					groupNames = obj.data.deptName;
				} else {
					groupParam = '';
					groupNames = '';
				}
				this.groupName = groupNames;
				this.selectCondition = {'deptId':groupParam};
				console.log('selectCondition='+groupParam)
			},
			changeStart(e) {
				this.start = e;
				console.log("-change事件:", e);
				this.selectCondition['paramBeginTime'] = this.start;
			},
			changeEnd(e) {
				this.end = e;
				console.log("-change事件:", e);
				this.selectCondition['paramEndTime'] = this.end;
			},
			onItemSelect(e) {
				console.log("onItemSelect事件:", e);
				this.dropDownOptions[0].header = e.content;
				if(e.contentIndex > 0) {
					this.selectCondition['status'] = (e.contentIndex-1).toString();
				} else {
					this.selectCondition['status'] = '';
				}
				this.$refs.dropDown.close();
			},
			moveHandle() {
				return false;
			},
			isPullDown(isPull) {
				const pages = getCurrentPages();
				const page = pages[pages.length - 1];  
				const currentWebview = page.$getAppWebview();
				currentWebview.setStyle({  
				  pullToRefresh: {  
				    support: isPull,  
				    // style: plus.os.name === 'Android' ? 'circle' : 'default',
					style:'circle'  
				  }  
				}); 
			},
		}
	}
</script>

<style lang="scss">
	.my-confirm-notice {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		background-color: rgba(0, 0, 0, 0.5);
		z-index: 998;
		/* 这里防止当用户长按屏幕,出现的黑色背景色块,以及 iPhone 横平时字体的缩放问题 */
		-webkit-text-size-adjust: 100%;
		-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
		display: flex;
		align-items: center;
	}
	.confirm-content-wrap1 {
		position: relative;
		left: 0;
		right: 0;
		width: 90%;
		height: auto;
		margin: 0 auto;
		background-color: white;
		border-radius: 10px;
		z-index: 999;
		user-select: none;
	}
	.unipop__ui_tit {
		background-color: #074498;
		width: 100%;
		border-top-left-radius: 10px;
		border-top-right-radius: 10px;
		height: 44px;
		display: flex;
		align-items: center;
	}
	.unipop__ui_tit_text {
		color: white;
		text-align: center;
		line-height: 44px;
		width: 100%;
		font-size: 16px;
	}
	.close_image {
		z-index: 100;position: absolute;float: right;right: 10px;width:30px;height: 30px;
	}
	.unipop__ui_btns{
		height: 80px;
		justify-content: space-around;
		display: flex;
		padding-left: 20px;
		padding-right: 20px;
		align-items: center;
	}
	.btn {
		width: 80px;
		height: 34px;
		background-color: #074498;
		line-height: 34px;
		text-align: center;
		font-size: 13px;
		border-radius: 6rpx;
	}
	.content_margin {
		padding: 15px 0px 5px 0px;
	}
	.flex_row_center {
		display: flex; flex-direction: row; justify-content: center; align-items: center;
	}
	.label_text {
		font-size: 28rpx;color: #555555;
	}
	.label_margin {
		margin-top: 36rpx;
	}
	.inputStyle {
		border: #bababa 0.5px solid; width: 169px;height: 40px; box-sizing: border-box; 
		font-size: 14px; color: #aaaaaa; padding: 3px 5px;
	}
	.top_level {
		position: fixed;z-index: 100;width: 90%;
	}
	.ly_tree_style {
		display: inline-block;
	}
	.scroll_view_style {
		border: #F5F5F5 1px solid; width: 169px;height: 200px;
	}
	.dataPickerStyle {
		width: 169px;height: 40px;  font-size: 14px; color: #aaaaaa;
	}
	.dateOperator {
		width: 120rpx;text-align: right;padding-right: 10px;
	}
	.dropDownStyle {
		width: 169px; height: 40px; border: #bababa 1px solid;
	}
</style>