uniapp兼容微信小程序和支付宝小程序遇见的坑
最近公司项目,需要用到微信小程序和支付宝小程序,由于为了提升开发效率,所以理所当然的选择了uni-app,但经过研究之后,发现并不像开发之前单纯的认为可以一套代码完全解决所有的问题,简直嗨皮的有点为时过早了,后边还有很多让人头疼的事情需要自己动手去做......
经过这么一遭,仿佛又回到了当年兼容 IE 浏览器的年代,经过探索、借鉴、归纳总结经验,希望能帮到同样遇到相同困扰的程序猿(媛)同行们
1、获取当前帐号信息
getAccountInfoSync兼容;my.getOpenUserInfo 无效的授权关系
微信小程序:wx.getAccountInfoSync()
支付宝小程序:
<button class="popup-btn" @click="openAuth" type="primary" size="mini">获取</button>
my.getOpenUserInfo({ fail: (res) => {}, success: (res) => { let userInfo = JSON.parse(res.response).response // 以下方的报文格式解析两层 response console.log('userInfo==========',userInfo); my.alert({ content: userInfo }); }, });
弹出报错:{code: "40003", msg: "Insufficient Conditions", subCode: "isv.invalid-auth-relations", subMsg: "无效的授权关系"}
改成如下
<button @click="openAuth" type="primary" size="mini" scope="userInfo" class="popup-btn" open-type="getAuthorize">获取</button>
2、设置小程序头部信息
'setNavigationBarColor' method of platform ;'支付宝小程序' does not support option 'frontColor'
支付宝不支持frontColor,不支持animation
3、样式单位
支付宝小程序rem和微信小程序的rem差别很大
根据设计稿,我将css中的1rem换成37.65rpx,页面正常
- 样式分开微信小程序和支付宝小程序
/* #ifdef MP-WEIXIN */ /* #endif */ /* #ifdef MP-ALIPAY || APP-PLUS */ /* #endif */ /* #ifdef MP-ALIPAY */ /* #endif */ /* #ifdef MP-WEIXIN || MP-QQ */ /* #endif */
- 函数中分开微信小程序和支付宝小程序
//#ifdef MP-ALIPAY //#endif //#ifdef MP-WEIXIN //#endif
- html中分开微信小程序和支付宝小程序
<!-- #ifdef MP-WEIXIN --> <wxapp v-if="showOauth" :oauthPlugin="oauthPlugin" @hasLogin="hasLogin"></wxapp> <!-- #endif -->
<!-- #ifdef H5 --> <web v-if="showOauth" :oauthPlugin="oauthPlugin" @hasLogin="hasLogin"></web> <!-- #endif -->
<!-- #ifdef MP-ALIPAY --> <aliapp v-if="showOauth" :oauthPlugin="oauthPlugin" @hasLogin="hasLogin"></aliapp> <!-- #endif -->
4、获取节点位置
支付宝不兼容uni.createSelectorQuery().in中的in
//#ifdef MP-ALIPAY my.createSelectorQuery().select(`#goods-${item.id}`).boundingClientRect().exec((ret) => { console.log('ret=======', ret); if (ret.length > 0) { item.top = h h += Math.floor(ret[0].height) item.bottom = h } }) //#endif //#ifdef MP-WEIXIN let view = uni.createSelectorQuery().in(that).select(`#goods-${item.id}`); console.log('========calcSize view', view) view.fields({ size: true }, data => { console.log('========calcSize data', data) if (data != null) { item.top = h h += Math.floor(data.height) item.bottom = h } }).exec() //#endif
5、textarea定义了最大长度,不同样式兼容
微信小程序端,定义了textarea maxlength="50"下方还是需要自己写一个0/50
支付宝小程序端,定义了textarea maxlength="50",无需再额外定义
下图为支付宝小程序端:
<textarea class="text-area" maxlength="50" @input="onInput" :value='remark' placeholder-style="color: #C4C4C4;font-size:14px;line-height: 20px;" placeholder="可填写额外备注信息,如:少盐"
/> <!-- #ifdef MP-WEIXIN --> <view class="length">{{remarkLength}}/50</view> <!-- #endif -->
6、uni-easyinput空白背景样式兼容
支付宝小程序端:
微信小程序端:
支付宝中尝试了N种办法,发现样式添加不上去,由于考虑到我们的项目,颜色基本都是固定了的。最后改了组件源码,在源码中给输入框添加了背景颜色
.uni-easyinput__content-input { /* #ifndef APP-NVUE */ width: auto; /* #endif */ position: relative; overflow: hidden; flex: 1; line-height: 1; font-size: 14px; background-color: #F6F6FC; }
然后再需要使用的地方重新引入一下即可。
同时,当输入框引入这个组件,当我们想要调整删除图标不要靠近输入框右侧时,需要调整源码中某一个部分width: 80%;如下
.uni-easyinput__content { flex: 1; /* #ifndef APP-NVUE */ width: 80%; display: flex; box-sizing: border-box; min-height: 36px; /* #endif */ flex-direction: row; align-items: center; }
7、uni-icons支付宝小程序中样式不生效
直接在支付宝的时候,用一个view包裹这个icons
<!-- #ifdef MP-ALIPAY --> <view class="search-icon" > <uni-icons type="search" color="#C4C4C4" size="18"></uni-icons> </view> <!-- #endif -->
<!-- #ifdef MP-WEIXIN --> <uni-icons type="search" color="#C4C4C4" size="18" class="search-icon"></uni-icons> <!-- #endif --> .search-icon { margin-left: 32rpx; color: red; }
8、input、textarea等输入框,在安卓机上,点击键盘后,无法看到光标,输入的内容也无法在输入框显示,关闭键盘后可以正常显示
添加 :enableNative='{{false}}' 即可解决,参考文档:支付宝官方文档-表单组件常见问题(https://opendocs.alipay.com/support/01rb8r)
<textarea :enableNative="'{{false}}'" class="text-area" maxlength="50" @input="onInput" :value='remark' style="background-color: #F6F6FC;" placeholder-style="color: #C4C4C4;font-size:14px;line-height: 20px;" placeholder="可填写额外备注信息,如:少盐" />
还有一个问题解决不了:无法对输入框的父级进行top,否则光标会偏移
9、度量单位(rpx)
不要使用upx、rem等,尽量使用rpx
10、scroll-view组件,flex兼容问题,支付宝设置了flex会无法滚动
<scroll-view :scroll-y="true" class="content" v-if="product != null"> ... </scroll-view> /*神奇的地方来了 微信小程序,样式这样没影响 */ .content { display: flex; flex-direction: column; font-size: $font-size-sm; color: $text-color-assist; max-height: calc(100vh - 776rpx); } /*支付宝小程序必须注释下面两行,不然滚动不了*/ .content { /* display: flex; */ /* flex-direction: column; */ font-size: $font-size-sm; color: $text-color-assist; max-height: calc(100vh - 776rpx); }
使用竖向滚动时,需要指定固定高度,否则滚动不生效。
<scroll-view :scroll-y="true"> <!-- #ifdef MP-WEIXIN --> <view scroll-y="true" class='detail_view'> <!-- #endif --> <!-- #ifdef MP-ALIPAY --> <view scroll-y="true" class='detail_view' :style="{height:'calc(100vh - '+ navbarHeight+'px)'}"> </view> </scroll-view> data() { return { navbarHeight:0 } }, onReady(){ this.navbarHeight = this.StatusBar + this.CustomBar + 48;//这个48是我下面有个地方设置了padding,所以我这里也设置一下 }, .detail_view { height: 100%; background-color: #FFFFFF; border-radius: 8px; overflow-y: scroll;//注意一定要添加overflow-y: scroll;
}
11、滚动动画
在支付宝端如果设置了:scroll-with-animation="true" 那么就需要设置:scroll-animation-duration="250"。如果支付宝小程序不设置的话,那么动画会比较缓慢,和微信小程序滚动动画速度不同步
而微信小程序不需要设置:scroll-animation-duration="250"。
<!-- #ifdef MP-WEIXIN --> <scroll-view class="product-section" scroll-y scroll-with-animation :scroll-top="goodsScrollTop" @scroll="productsScroll"> <!-- #endif --> <!-- #ifdef MP-ALIPAY --> <scroll-view class="product-section" :scroll-y="true" :scroll-with-animation="true" :scroll-animation-duration="250" :scroll-top="goodsScrollTop" @scroll="productsScroll"> <!-- #endif -->
12、按钮边框问题
支付宝小程序按钮会有一个边框
微信小程序按钮不会有边框
直接给按钮添加border:1px solid 颜色和背景颜色一样
注:
- 支付宝小程序按钮button在view会稍微往下偏移,所以line-height不要设置和高度一样
- 支付宝小程序,安卓端键盘会把absolute内容撑上去
不要通过bottom来定位,而是通过margin-top来撑开
<view class="bottom" :style="{marginTop:'calc('+ screenHeight + ' - ' + '32rpx)'}" > //这个32rpx是我想要距离底部的高度 <text>底部</text> </view> data() { return { screenHeight:'0px', } }, onReady() { const e = uni.getSystemInfoSync(); let eHeight = e.screenHeight if(e.windowHeight){ eHeight = e.windowHeight }else if(e.screen&&e.screen.height){ eHeight = e.screen.height } this.screenHeight = eHeight - this.CustomBar + 'px';//此处看情况,我的this.CustomBar是状态栏高度
},
13、支付宝小程序,安卓端设置了背景颜色的内容块在textarea上面时,会变成透明色
1.解决安卓端textarea通透
使用官方的解决办法:cover-view包裹我们想要在textarea等层级内容上面,然后设置z-index, uni-app官网(https://uniapp.dcloud.net.cn/component/cover-view.html#cover-view)
<!-- #ifdef MP-ALIPAY --> <textarea :enableNative="'{{false}}'" class="text-area" maxlength="50" @input="onInput" :value='remark' style="background-color: #F6F6FC;" placeholder-style="color: #C4C4C4;font-size:14px;line-height: 20px;" placeholder="可填写额外备注信息,如:少盐" /> <!-- #endif --> <!-- #ifdef MP-ALIPAY --> <cover-view class="controls-title"> <!-- #endif --> <view class="buttom"> 底部内容 </view> <!-- #ifdef MP-ALIPAY --> </cover-view> <!-- #endif --> .controls-title{ z-index: 999; }
2.ios端textarea输入内容后会被底部fixed内容遮挡
解决办法:设置一个view,因为问题只有在Ios端才会复现,所以设置一个v-if。然后height高度我是设置的大于了我底部那个fixed内容块的高度。这个看个人,其实height只要大于等于底部fixed内容块就行了。
<!-- #ifdef MP-ALIPAY --> <textarea :enableNative="'{{false}}'" class="text-area" maxlength="50" @input="onInput" :value='remark' style="background-color: #F6F6FC;" placeholder-style="color: #C4C4C4;font-size:14px;line-height: 20px;" placeholder="可填写额外备注信息,如:少盐" /> <view v-if="platform === 'ios'" style="height: 150rpx;width: 100%;"></view> <!-- #endif --> data() { return { platform:'android' }; }, onReady() { const e = uni.getSystemInfoSync(); console.log('==============e.platform',e.platform) if (e.platform == 'android') { this.platform = 'android'; } else { this.platform = 'ios' }; }
14、支付宝小程序和微信小程序兼容websocket
// App.vue //启动websocket let that = this; interval = setInterval(function(){ let accessToken = uni.getStorageSync("accessToken"); if(accessToken != null && accessToken != ""){ that.$websocket.dispatch('WEBSOCKET_INIT', accessToken); // console.log("0000011111_"+accessToken); } console.log("00000"); },5000);
websocketStore.js
区别是,支付宝小程序不兼容socketTask,具体可关注此链接--支付宝开放社区(https://forum.alipay.com/mini-app/post/9501017)
支付宝小程序关闭之后,也要在my.onSocketClose内写四个关闭my.offSocketMessage();、my.offSocketError();、my.offSocketOpen();、my.offSocketClose();
my.onSocketClose((res) => { // 支付宝小程序的ws连接问题,关闭连接时需关闭对于接受,防止关闭失败 my.offSocketMessage(); my.offSocketError(); my.offSocketOpen(); my.offSocketClose(); });
不然会导致一直重复叠加之前的,不会像微信小程序覆盖之前的
import Vue from 'vue' import Vuex from 'vuex' import { wssHost } from '../fetch/env.js'; Vue.use(Vuex) export default new Vuex.Store({ state: { socketTask: null, eventlist: [], wssHost, interval: null, intervalAli: null, socketTaskAli: null, }, mutations: { WEBSOCKET_INIT(state, accessToken) { //#ifdef MP-WEIXIN if (state.socketTask != null) { console.log('=======return') return; } //console.log(wssHost+uid); // 创建一个this.socketTask对象【发送、接收、关闭socket都由这个对象操作】 state.socketTask = uni.connectSocket({ url: wssHost + accessToken, // 【非常重要】必须确保你的服务器是成功的,如果是手机测试千万别使用ws://127.0.0.1:9099【特别容易犯的错误】 success(data) { console.log("websocket连接成功"); console.log(data); }, fail(data) { console.log("websocket连接失败"); }, complete(data) { console.log("websocket连接完成"); }, }); // 消息的发送和接收必须在正常连接打开中,才能发送或接收【否则会失败】 state.socketTask.onOpen((res) => { console.log("WebSocket连接正常打开中...!"); state.is_open_socket = true; // 注:只有连接正常打开中 ,才能正常收到消息 state.socketTask.onMessage((res) => { console.log("收到服务器内容:" + res.data); let d = JSON.parse(res.data) if (d.type != -1) { //除了心跳,其他信息都往下发布 uni.$emit("socketMsg", d); //广播出去 } }); //启动心跳 if (state.interval == null) { var msg = { type: '-1', action: 'online', data: '定时心跳' }; state.interval = setInterval(function() { state.socketTask.send({ data: JSON.stringify(msg), async success() { console.log("心跳发送成功:"+JSON.stringify(msg)); }, }) }, 5000); } }); state.socketTask.onClose(function(msg) { if (state.interval != null) { clearInterval(state.interval); state.interval = null; } state.socketTask = null; console.log("链接被关闭"); console.log(msg); }); state.socketTask.onError(function(msg) { console.log("链接错误:"); console.log(msg); }); //#endif //#ifdef MP-ALIPAY if (state.socketTaskAli != null) { console.log('=======return') return; } my.connectSocket({ url: wssHost + accessToken, // 开发者服务器接口地址,必须是 wss 协议,且域名必须是后台配置的合法域名 data: {}, header: { 'content-type': 'application/json' }, success: (res) => { state.socketTaskAli = 'true' console.log("websocket连接成功", JSON.stringify(res)); // 返回 "{}",一个空的字典对象。 }, fail: () => { console.log("websocket连接失败"); }, complete: (res) => { console.log("websocket连接完成"); }, }); my.onSocketOpen(function(res) { console.log("WebSocket连接正常打开中...!"); state.is_open_socket = true; // 注:只有连接正常打开中 ,才能正常收到消息 my.onSocketMessage(function(res) { console.log("收到服务器内容:" + res.data); let d = JSON.parse(res.data) if (d.type != -1) { //除了心跳,其他信息都往下发布 uni.$emit("socketMsg", d); //广播出去 } }); //启动心跳 if (state.intervalAli == null) { var msg = { type: '-1', action: 'online', data: '定时心跳' }; state.intervalAli = setInterval(function() { my.sendSocketMessage({ data: JSON.stringify(msg), async success() { console.log("心跳发送成功:" + JSON.stringify(msg)); }, }) }, 5000); } }); my.onSocketClose((res) => { if (state.intervalAli != null) { clearInterval(state.intervalAli); state.intervalAli = null; } // 支付宝小程序的ws连接问题,关闭连接时需关闭对于接受,防止关闭失败 my.offSocketMessage(); my.offSocketError(); my.offSocketOpen(); my.offSocketClose(); state.socketTaskAli = null; console.log("链接被关闭"); console.log(res); }); my.onSocketError(function(res) { console.log("链接错误:"); console.log(res); }); //#endif }, WEBSOCKET_SEND(state, p) { console.log("ws发送!"); //#ifdef MP-WEIXIN state.socketTask.send({ data: p, async success() { console.log("消息发送成功"); }, }); //#endif //#ifdef MP-ALIPAY my.sendSocketMessage({ data: p, // 需要发送的内容 async success() { console.log("消息发送成功"); }, }); //#endif }, WEBSOCKET_CLOSE(state, accessToken) { console.log("关闭推送"); //#ifdef MP-WEIXIN if (state.socketTask == null) { return; } state.socketTask.close(); //#endif //#ifdef MP-ALIPAY if (state.socketTaskAli == null) { return; } my.onSocketClose((res) => { if (state.intervalAli != null) { clearInterval(state.intervalAli); state.intervalAli = null; } // 支付宝小程序的ws连接问题,关闭连接时需关闭对于接受,防止关闭失败 my.offSocketMessage(); my.offSocketError(); my.offSocketOpen(); my.offSocketClose(); state.socketTaskAli = null; console.log("链接被关闭"); console.log(res); }); // my.onSocketClose(); //#endif } }, actions: { WEBSOCKET_INIT({ commit }, accessToken) { commit('WEBSOCKET_INIT', accessToken) }, WEBSOCKET_SEND({ commit }, p) { commit('WEBSOCKET_SEND', p) }, WEBSOCKET_CLOSE({ commit }, p) { commit('WEBSOCKET_CLOSE', p) } } })
15、支付宝小程序需要authCode
//需要一个按钮 <button @click="openAuth" type="primary" size="mini" class="popup-btn" open-type="getAuthorize">开始点餐</button> //函数事件 async open() { let that = this; uni.login({ provider: 'alipay', success: function (loginRes) { that.$refs['popup'].open(); }, }); }, async openAuth(){ let that = this; uni.showLoading({ title: "登录中" }) my.getAuthCode({ scopes: 'auth_user', fail: (res) => { uni.hideLoading(); }, success: (res) => { console.log('++++++++++++++++++++++++res-------------------', res.authCode) that.authCode = res.authCode; that.checkAuth(); uni.hideLoading(); } }); }, async checkAuth(){ console.log('======checkAuth') let that = this; let params = this.oauthPlugin.pluginsParams; if (params == null && params.length == 0) { uni.showModal({ title: '提示', content: this.oauthPlugin.name + " 配置异常", success: function (res) { that.$emit('initData'); } }); } params.forEach(param => { console.log('param=====', param.ckey); if (param.ckey == "state") { that.getToken(param); return; } }); }
16、自定义导航栏问题
微信小程序
"navigationStyle":"custom"
支付宝小程序(支付宝小程序返回按钮不能自定义)
"mp-alipay": { "transparentTitle": "always", // 头部透明 "titlePenetrate": "YES", // 导航栏点击穿透 "allowsBounceVertical": "No" },
此时,全局设置页面状态栏背景色字体颜色失效,需要每个页面单独设置
uni.setNavigationBarColor({ frontColor: '#ffffff', backgroundColor: '#000000', animation: { duration: 400, timingFunc: 'easeIn' } });
支付宝小程序隐藏标题栏上的返回首页图标和右上角通用菜单中的返回首页功能的 API:
my.hideBackHome();
17、自定义导航栏高度问题
在App.vue里获取当前手机系统的导航栏和状态栏高度
微信小程序
// #ifdef MP-WEIXIN Vue.prototype.StatusBar = e.statusBarHeight; let custom = wx.getMenuButtonBoundingClientRect(); //获取右上角胶囊位置信息 Vue.prototype.CustomBar = (custom.top - e.statusBarHeight)*2 + custom.height; // #endif
支付宝小程序
// #ifdef MP-ALIPAY Vue.prototype.StatusBar = e.statusBarHeight; Vue.prototype.CustomBar = e.titleBarHeight; // #endif
18、设置全局高度问题
微信小程序
/* #ifdef MP-WEIXIN */ page { height: 100%; } /* #endif */
支付宝小程序
/* #ifdef MP-ALIPAY */ page { height: 100vh; width: 100%; position: absolute; top: 0; } /* #endif */
19、设置整体不能上下滚动(是否允许向下拉拽)
"disableScroll":true, //微信小程序 "mp-alipay": { "allowsBounceVertical":"NO" //支付宝小程序 }
20、调起支付
微信小程序
//#ifdef MP-WEIXIN uni.requestPayment({ provider: 'wxpay', timeStamp: ""+data.data.timeStamp, nonceStr: data.data.nonceStr, package: data.data.package, signType: data.data.signType, paySign: data.data.paySign, success: function (res) { }, fail: function (err) { }, });
支付宝小程序(遇到问题:取消支付走了成功的回调方法)
原因:支付宝success 回调函数有resultCode结果码
结果码 | 描述 |
4 | 无权限调用(N22104) |
9000 | 订单处理成功 |
8000 | 正在处理中。支付结果未知(有可能已经支付成功) |
4000 | 订单处理失败 |
6001 | 用户中途取消 |
6002 | 网络连接出错 |
6004 | 处理结果未知(有可能已经成功) |
//#ifdef MP-ALIPAY my.tradePay({ tradeNO:data.data.order_no, success: function (res) { if(res.resultCode == 9000 ){ }else { } }, fail: function (err) { }, }); //#endif
21、扫码功能
微信小程序
在页面onLoad事件中获取参数
if (e.scene) { scene = decodeURIComponent(e.scene) } else if (e.q) { scene = this.getQueryString(decodeURIComponent(e.q), 'scene') }
支付宝小程序
在app.js(App.vue) 中的 onShow 和 onLaunch 方法中获取后存在全局
// App.vue onLaunch(options) { Vue.prototype.query = options.query }, onShow(options) { Vue.prototype.query = options.query }, //index.vue if (this.query && this.query.scene) { scene = this.query.scene } else if (this.query && this.query.qrCode) { scene = this.getQueryString(decodeURIComponent(this.query.qrCode), 'scene') }
遗留问题:支付宝小程序内扫一扫功能,扫描小程序码无法获取参数
- 支付宝小程序ios不能自动弹出键盘
- 支付宝小程序,部分机型不支持position: fixed;