開發小程序中,遇到的wepy的幾點坑,記錄一下;前端
更詳細的項目總結記錄請見個人我的博客:https://fanghongliang.github.io/git
1.定時器:github
在頁面中有須要用到倒計時或者其餘定時器任務時,新建的定時器在卸載頁面時必定要清除掉,有時候頁面可能不止一個定時器需求,在卸載頁面(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,阻止事件點擊向上傳播,由於每一層都添加有點擊事件,互不干擾、app
解決: 添加函數 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) },
至此,圖片上傳解決