最近在做一个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>