开发小程序中,遇到的wepy的几点坑,记录一下;
更详细的项目总结记录请见我的个人博客:https://fanghongliang.github.io/
1.定时器:
在页面中有需要用到倒计时或者其他定时器任务时,新建的定时器在卸载页面时一定要清除掉,有时候页面可能不止一个定时器需求,在卸载页面(onUnload钩子函数)的时候一定要清除掉当前不用的定时器
定时器用来做倒计时效果也不错,初始时间后台获取,前端处理,后台直接在数据库查询拿到的标准时间(数据库原始时间,T分割),前端需要正则处理一下这个时间:
let overTimeStr = data.over_time.split('T') let time1 = overTimeStr[0].replace(/-/g,",") let time2 = overTimeStr[1].replace(/:/g,',') let overTime = time1+ ',' + time2 let overTimeArr = overTime.split(',') that.countDownCtrl( overTimeArr, 0 );
最终把时间分割为[年,月, 日, 时, 分, 秒]的数组,(如果后端已经把时间处理过了那就更好了),然后把该数组传递给倒计时函数:
countDownCtrl( time, group ) { let deadline = new Date()//免费截止时间,月的下从0开始 deadline.setFullYear(time[0], time[1]-1, time[2]) deadline.setHours(time[3], time[4], time[5]) let curTimeJudge = new Date().getTime() let timeJudge = deadline.getTime()-curTimeJudge let remainTimeJudge = parseInt(timeJudge/1000) if( remainTimeJudge < 0) { log('倒计时已经过期') return; } this.interva1 = setInterval(() => { let curTime = new Date().getTime() let time = deadline.getTime()-curTime //剩余毫秒数 let remainTime = parseInt(time/1000) //总的剩余时间,以秒计 let day = parseInt( remainTime/(24*3600) )//剩余天 let hour = parseInt( (remainTime-day*24*3600)/3600 )//剩余小时 let minute = parseInt((remainTime-day*24*3600-hour*3600)/60)//剩余分钟 let sec = parseInt(remainTime%60)//剩余秒 hour = hour < 10 ? '0' + hour : hour; minute = minute < 10 ? '0' + minute : minute sec = sec < 10 ? '0' + sec : sec let countDownText = hour+ ":" +minute+ ":" +sec if( group === 0) { //个人业务逻辑,因为一个页面有两个倒计时需求,代码复用区分 this.countDown = countDownText; } else if( group === 1 ) { this.countDownGroup = countDownText } this.$apply() }, 1000 ); }
至此,倒计时效果处理完毕,PS:终止时间一定要大于currentDate,否则显示会出现异常(包括但不限于倒计时闪烁、乱码等)
最后,退出该页面去其他页面时,一定要在页码卸载钩子中清除倒计时!!!
onUnload() { clearInterval(this.interva1); }
2.三层组件,阻止点击事件传播:
三层组件嵌套,第三层的点击事件不能传到第一层去,适用于遮罩层+picker,阻止事件点击向上传播,因为每一层都添加有点击事件,互不干扰、
解决: 添加函数 catchtap="funcName" 即可,funcName可为空函数,也可以直接不写
3.组件传值:
组件传值和Vue有点细微区别,Vue强调父组件的数组和对象不要直接传到子组件使用,应为子组件可能会修改这个data,如图:
但是,wepy中,有时候确实需要把一个对象传递到子组件使用,单个传递对象属性过于繁琐,而且!!!如果单个传递对象的属性到子组件,如果该属性是一个数组,则子组件永远会接收到 undefined 。此时最好用整个对象传值替代单个对象属性逐个传值的方法,
且一定要在传值时加入 .sync 修饰符,双向传值绑定。确保从接口拿到的数据也能传递到子组件,而非 undefined
:circleMembersList.sync="circleMembersList"
4.token判断
在与后台交互的时候,token必不可少。尤其是在小程序分享出去的链接,由其他用户点开分享链接进入小程序内部,此时更是要判断token,token的判断一般选在 onShow()钩子执行而不在 onLoad()钩子内执行。若不存在token,则应该执行登录去拿取token
5.formID
微信提供了服务通知,即在你支付、快递等行为时,微信会直接给你发一个服务通知来提醒,每次提醒都会消耗该用户存储的formID,formID为消耗品,用一个少一个,只有通过用户的表单提交行为才可以拿到formID,
<form @submit="submitForm" report-submit="true"> <button form-type="submit" class="editCard" @tap = "goModifiPage('editFormTab')">修改</button> </form>
submitForm(e) { this.postFormId( e.detail.formId ) }
6.微信支付
准备: crypto-js.js md5.js
微信支付流程为: 前端点击支付按钮 ==》 准备加密数据 ==》 调用后端接口,传入需要的加密数据 ==》 后端验证加密数据,再返回加密数据 ==》 前端拿到后端加密数据(时间戳、内容、签名),对时间戳和内容进行本地签名,再判断本地签名和后端签名是否
一致,若不一致,直接返回,退出支付,支付失败!若一致,对刚刚后台返回的content(内容)进行解析,拿到所需订单数据,前端调起微信支付借口,参数传入刚刚解析数据 ===》 得到支付结果 success or fail !结束
Sign函数:
/** * 签名 */ function sign(timestamp, content) { var raw = timestamp + salt + content var hash = CryptoJS.SHA256(raw).toString() return CryptoJS.MD5(hash).toString() }
前端点击支付按钮:
// 单独支付接口 alonePay(arg) { const that = this; if( that.buttonClicked === false ) return; that.buttonClicked = false; let mode = 1; let appId = this.$parent.globalData.appId; let content; let sign; const timeStamp = new Date().Format("yyyy-MM-dd hh:mm:ss").toString(); let code = wepy.getStorageSync('code'); wepy.login().then((res) => { if(res.code) { let code = res.code; log('code', code) wepy.setStorage({ key: "code", data: code }) } }).then( res => { content = `mode=${mode}&app_id=${appId}` sign = Sign.sign(timeStamp,content); }).then(res => { that.goCirclePay( that.circle_id, timeStamp, sign, content, mode ) }) },
支付函数:
// 支付 goCirclePay( circle_id, timestamp, sign, content, mode) { const that = this; circleApi.goCirclePay({ data: { circle_id, timestamp, sign, content }, getToken: true }).then( res => { log('支付res:', res) let data = res.data const SignServer = data.sign const timeStampServer = data.timestamp let contentServer = data.content const SignLocal = Sign.sign(timeStampServer,contentServer); if( mode === 0 && data.status === "success") { that.nav('/pages/circleDetail?circle_id=' + that.circle_id) return; } if( SignLocal !== SignServer ) { log('签名不一致!') wx.showToast({ title: "您已经支付过了", duration: 1500, image: "../images/common/icon_wxchat.png", }) return } let contentArr = contentServer.split('&') const timeStamp = contentArr[0].split('=')[1]; const nonceStr = contentArr[1].split('=')[1]; let index = contentArr[2].indexOf("="); const package1 = contentArr[2].slice(index+1) const signType = contentArr[3].split('=')[1]; const paySign = contentArr[4].split('=')[1]; wepy.requestPayment({ timeStamp, nonceStr, package: package1, signType, paySign }).then(res => { return new Promise(resolve => { setTimeout(() => { resolve() }, 1000) }) }).then(res => { that.buttonClicked = true; let groupFormIdGet; circleApi.getGroupFormId({ ////获取getGroupFormId data: { circle_id: that.circle_id }, getToken: true }).then( res => { let data = res.data that.group_form_id = data.group_form_id groupFormIdGet = data.group_form_id if( mode === 1) { that.nav(`/pages/paySuccess?circle_id=${that.circle_id}&shareLink=${that.shareLink}`) } else if( mode === 2) { that.nav(`/pages/paySuccess?circle_id=${that.circle_id}&group_form_id=${groupFormIdGet}`) } that.$apply() }) }).catch(res => { log('支付失败', res) that.buttonClicked = true; }) }) }
至此,支付就已经完成!
7.图片上传(采用七牛云)
图片上传服务器采用七牛云服务,在APP内小程序触发的时候,请求七牛云拿到token存为全局变量。
图片需要上传的地方,直接放代码:
页面结构:
<!-- 上传生活照 --> <view class="baseInfoTip" style="border: 0">上传生活照 <view class="imgUploadText">(最多9张)</view> <view class="leftOriginLine"></view> </view> <view class="uploadImgBox"> <repeat for="{{images}}" index="index" item="item" key="index"> <view class="itemBox"> <image class="imgItem" src="{{item}}" mode="aspectFill"></image> <image class="imgItemCancel" id="{{index}}" src="../images/common/icon_cardImg_cancel.png" @tap.stop="cancelUploadImg"></image> </view> </repeat> <view class="itemBox" @tap="addImg" wx:if="{{!addImgCtrl}}"> <image class="imgItem" src="../images/common/icon_addImg.png"></image> </view> </view>
base.oploadImg()函数:
// 上传图片 const uploadImg = (imageURL, uptokenURL) => { return new Promise((resolve, reject) => { qiniuyun.upload(imageURL, (res) => { resolve(res); }, (error) => { reject(error); }, { region: 'ECN', domain: '填入域名', uptoken: uptokenURL }); }); }
上传图片函数:
// 从相册选择照片上传 addImg(){ const that = this; if( that.buttonClicked === false ) return; that.buttonClicked = false; wepy.chooseImage({ count:9 - that.images.length, sizeType: 'compressed', }).then(async(res1) => { that.buttonClicked = true; that.toast('上传图片中...','loading'); let filePath = res1.tempFilePaths; for(let i = 0;i < filePath.length;i++){ let imgSrc= res1.tempFilePaths[i]; let imgType = imgSrc.substring(imgSrc.length-3); let imgSize = res1.tempFiles[i].size; if(imgSize > 2000000 || imgType === 'gif'){ that.toast('该图片格式错误!请重新选择一张', 'none', 3000); continue } let res = await base.uploadImg(filePath[i], that.$parent.globalData.qiniuToken); that.images.push(res.imageURL); log('image长度:', that.images.length) log('image:', that.images) if( that.images.length >= 9) { that.addImgCtrl = true } if(that.images.length > 9){ that.images = that.images.slice(0,9) } if(that.images.length >0 && that.config.fImages){ that.config.progress = that.config.progress + parseFloat(that.config.getConfigs.lifepicweight*100); that.config.fImages = false } that.$apply(); // 上传用户头像列表 that.userInfo.photos = that.images if(i === filePath.length -1){ wepy.hideToast(); } } }).catch((res) => { if(res.errMsg === "chooseImage:fail:system permission denied"){ that.toast('请打开微信调用摄像头的权限', 'none', 3500) } }) }, // 取消图片上传 cancelUploadImg(e) { if( this.images.length < 10 ) { this.addImgCtrl = false } let index = e.target.id this.images.splice(index, 1) },
至此,图片上传解决