微信jssdk在history模式下的调用问题
- Published on
- 发布于·预估阅读13分钟
- 基础使用
- 踩坑问题
- url上有特殊字符如@符号等
- url内的query参数有json对象,且json对象内有不止一对key
- ios跳转到一个外链之后,通过返回键返回
- 微信jssdk,error与ready方法都执行,并且执行顺序不确定
- 公众号开放平台配置js安全域名
- ios下单页应用url保持不变
- 签名url与当前url不一致,导致config失败,这在调试过程中90%
- 项目内封装
- Authors
- Name
- willson-wang
基础使用
根据微信jssdk文档得到如下信息
1、 同一个url仅需调用一次,但是安卓的单页应用,每次页面切换的时候url都会改变,所以安卓需要每次重新调用
所以这里需要根据平台进行判断,ios下只需要调用一次,成功之后则不需要调用;安卓每次都要调用
2、 config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
wx.ready(function(){
// 常用需要在ready触发之后进行的api
1. updateAppMessageShareData 分享朋友
2. updateTimelineShareData 分享朋友圈
3. onMenuShareTimeline 分享朋友圈 即将废弃
4. onMenuShareAppMessage 分享朋友 即将废弃
5. getLocation
// 常用不需要在ready触发之后进行的api
1. chooseImage
2. previewImage
3. uploadImage
4. downloadImage
5. getLocalImgData
6. openLocation
});
因为config是一步的,所以我们我需要在ready或error内进行相应的操作
3、 config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
wx.error(function(res){
// error与ready执行的顺序不固定
})
所以我们在error被调用之后,需要进行对应的操作,好让我们制定此次config失败
4、 根据是否需要在ready之后触发进行不同处理
wx.ready(function(res){
// 不论config成功还是失败ready方法都会被执行
})
所以这里我们不能使用单个变量判断此次config是成功还是失败
按照职责单一原则,我们定义的调用jssdk的方法如下所示
let wxJsdk = require('./wxapi').default
const act_type = `${window.location.origin}/`
const defaultApiList = ['checkJsApi', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'getLocation', 'openLocation', 'chooseImage', 'uploadImage']
// 用于判断此次config是否成功
const hasConfiged = []
// 调用config,并判断成功还是失败
async function configByPath(path, jsApiList, os) {
// ios下只需要执行一次config即可
const againConfig = hasConfiged.length && hasConfiged.length < 2 && os.ios
if (!path || againConfig) return
hasConfiged.length = 0
let SIGN_PATH = encodeURIComponent(path.substring(1))
const query = qs.parse(window.location.search.substr(1))
const token = brokerToken || query.token
const res = await http.get('/api/xxx/wxapi&act=jsapi', {
params: {
path: SIGN_PATH,
token,
act_type
}
})
const { appId, timestamp, nonceStr, signature, url } = res
wxJsdk.config({
debug: !!query.debug,
appId,
timestamp,
nonceStr,
signature,
jsApiList
})
await new Promise((resolve, reject) => {
wxJsdk.ready(result => {
hasConfiged.push('ready')
resolve(result)
})
wxJsdk.error(error => {
hasConfiged.push('error')
customLog('wx_config_error', {
SIGN_PATH,
err_msg: error.errMsg,
api_url: url,
current_url: window.location.href,
current_search: window.location.search.slice(1)
})
reject(error)
})
})
}
export async function getWxConfig(jsApiList = []) {
const { os = {} } = getDeviceInfo() || {}
const env = await runningEnv()
let newApiList = [...jsApiList]
if (os.ios) {
jsApiList.push('openLocation')
newApiList = Array.from(new Set([...defaultApiList, ...jsApiList]))
}
let wxLocation = window.location
let PATH = wxLocation.pathname + wxLocation.search
if (os.ios || env.isMiniWebView) {
PATH = getEnterLocation()
}
console.warn('jssdk getWxConfig', { PATH, os, wxLocation, newApiList, togglePath })
// 只考虑了history模式,没有考虑hash,模式;安卓下当前页面的url
try {
await configByPath(PATH, newApiList, os)
} catch (error) {
console.warn('getWxConfig error', error)
}
return wxJsdk
}
export default getWxConfig
踩坑问题
url上有特殊字符如@符号等
安卓可以正常调起jssdk,但是ios不行
解决方法:就是判断如果是直接通过包含@符号的链接访问,就需要对最终传到接口的SIGN_PATH内的@符进行两次encode,这样接口解码一次就ok了
if (os.ios && path.indexOf('@') > -1) {
const temp = path.split('@')
SIGN_PATH = `${encodeURIComponent(temp[0].substring(1))}${encodeURIComponent(encodeURIComponent('@'))}${encodeURIComponent(temp[1])}`
} else {
SIGN_PATH = encodeURIComponent(path.substring(1))
}
url内的query参数有json对象,且json对象内有不止一对key
安卓不可以正常调起jssdk,ios可以正常调起
原因:也是编码与解码的问题 解决方案:特殊处理json对象内逗号
ios跳转到一个外链之后,通过返回键返回
安卓下正常,ios下调不起jssdk
原因:ios下通过返回键返回没有重新刷新页面及执行js代码;导致入口url变成了返回到的这个页面,而不是最开始的url 解决:通过pageshow事件,监听页面是否返回,如果是的话,重新更新入口url
function pageShowHandle (e) {
console.log('e', e.persisted, window.performance.navigation.type)
if (e.persisted && (window.performance && window.performance.navigation.type == 2)) {
setEnterLocation()
hasConfiged.length = 0
}
}
function listenPageShow () {
const { os = {} } = getDeviceInfo() || {}
if (!(os.ios && isWechat)) return
// @TODO 解决ios下通过location.href跳转的链接,通过返回键返回不重新加载页面bug
window.removeEventListener('pageshow', pageShowHandle)
window.addEventListener('pageshow', pageShowHandle, false)
}
微信jssdk,error与ready方法都执行,并且执行顺序不确定
原因:wx.config是异步的,在调用config之后ready方法都会执行,而error会在错误的时候被调用,且执行顺序不固定 解决:不能勇敢一个变量来判断是成功还是失败,可以通过一个数据来判断,通过在ready及error方法内都push一次值,然后通过数组长度即可判断此次config是成功还是失败
wxJsdk.config({
debug: !!query.debug,
appId,
timestamp,
nonceStr,
signature,
jsApiList
})
await new Promise((resolve, reject) => {
wxJsdk.ready(result => {
hasConfiged.push('ready')
resolve(result)
})
wxJsdk.error(error => {
hasConfiged.push('error')
reject(error)
})
})
公众号开放平台配置js安全域名
如果有其它的公众号被授权管理在开发平台,需要在开发平台配置js安全域名;另外配置安全域名的时候最好配置根域名,这样所有子域名都可以使用
ios下单页应用url保持不变
原因:ios下单页应用url保持不变,安卓单页应用url切换时变化 解决方法:ios下成功一次之后就可以不在调用,安卓每次重新获取配置参数并调用config
签名url与当前url不一致,导致config失败,这在调试过程中90%
这个需要判断环境,然后在根据签名接口传入的path与接口拿去签名的最终返回的path及入口location、当前location这个四个值去比较判断,只有一致时,config才会成功
项目内封装
最终代码如下所示
let wxJsdk = require('./wxapi').default
const act_type = `${window.location.origin}/`
const defaultApiList = ['checkJsApi', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'getLocation', 'openLocation', 'chooseImage', 'uploadImage']
const hasConfiged = []
function pageShowHandle (e) {
console.log('e', e.persisted, window.performance.navigation.type)
if (e.persisted && (window.performance && window.performance.navigation.type == 2)) {
setEnterLocation()
hasConfiged.length = 0
}
}
function listenPageShow () {
const { os = {} } = getDeviceInfo() || {}
if (!(os.ios && isWechat)) return
// @TODO 解决ios下通过location.href跳转的链接,通过返回键返回不重新加载页面bug
window.removeEventListener('pageshow', pageShowHandle)
window.addEventListener('pageshow', pageShowHandle, false)
}
try {
listenPageShow()
} catch (error) {
console.log('error listenPageShow', error)
}
async function configByPath(path, jsApiList, os) {
// ios下只需要执行一次config即可
const againConfig = hasConfiged.length && hasConfiged.length < 2 && os.ios
if (!path || againConfig) return
hasConfiged.length = 0
let SIGN_PATH = ''
if (os.ios && path.indexOf('@') > -1) {
const temp = path.split('@')
SIGN_PATH = `${encodeURIComponent(temp[0].substring(1))}${encodeURIComponent(encodeURIComponent('@'))}${encodeURIComponent(temp[1])}`
} else {
SIGN_PATH = encodeURIComponent(path.substring(1))
}
const query = qs.parse(window.location.search.substr(1))
const token = brokerToken || query.token
const res = await http.get('/api/xxx/wxapi&act=jsapi', {
params: {
path: SIGN_PATH,
token,
act_type
}
})
const { appId, timestamp, nonceStr, signature, url } = res
wxJsdk.config({
debug: !!query.debug,
appId,
timestamp,
nonceStr,
signature,
jsApiList
})
await new Promise((resolve, reject) => {
wxJsdk.ready(result => {
hasConfiged.push('ready')
resolve(result)
})
wxJsdk.error(error => {
hasConfiged.push('error')
customLog('wx_config_error', {
SIGN_PATH,
err_msg: error.errMsg,
api_url: url,
current_url: window.location.href,
current_search: window.location.search.slice(1)
})
reject(error)
})
})
}
export async function getWxConfig(jsApiList = []) {
const { os = {} } = getDeviceInfo() || {}
const env = await runningEnv()
let newApiList = [...jsApiList]
if (os.ios) {
jsApiList.push('openLocation')
newApiList = Array.from(new Set([...defaultApiList, ...jsApiList]))
}
let wxLocation = window.location
let PATH = wxLocation.pathname + wxLocation.search
if (os.ios || env.isMiniWebView) {
PATH = getEnterLocation()
}
console.warn('jssdk getWxConfig', { PATH, os, wxLocation, newApiList, togglePath })
// 只考虑了history模式,没有考虑hash,模式;安卓下当前页面的url
try {
await configByPath(PATH, newApiList, os)
} catch (error) {
console.warn('getWxConfig error', error)
}
return wxJsdk
}
export default getWxConfig
export async function initShare(shareConfig) {
const apis = ['checkJsApi', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ']
const { os = {} } = getDeviceInfo() || {}
if (os.ios) {
apis.push('openLocation')
}
try {
const wxconf = await getWxConfig(apis)
const config = {
title: '',
desc: '',
link: '',
imgUrl: '',
success() {
console.log('success')
},
fail(e) {
console.log('fail', e)
},
cancel() {
console.log('cancel')
},
...shareConfig
}
wxconf.onMenuShareTimeline(config)
wxconf.onMenuShareAppMessage(config)
wxconf.onMenuShareQQ(config)
} catch (e) {
console.log(e)
}
}
export async function getCurrentLocation() {
return new Promise(async (resolve, reject) => {
try {
const wx = await getWxConfig(['getLocation'])
wx.getLocation({
type: 'wgs84', // wgs84: gps坐标、 gcj02: 火星坐标
success: resolve,
error: reject,
})
} catch (e) {
resolve(defaultCoords)
}
})
}
export async function uploadImage() {
const wx = await getWxConfig(['chooseImage', 'uploadImage'])
const imageArr = []
wx.chooseImage({
count: that.maximum, // 微信允许最大9张
sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: async res => {
if (res) {
const { localIds } = res
const serverIdArr = []
for (let i = 0; i < localIds.length; i += 1) {
// 只能一张传完,才能传下一张,不能使用promise.all
try {
const temp = await that.wxUploadImage(localIds[i])
serverIdArr.push(temp.serverId)
} catch (error) {
console.error('error', error)
}
}
if (!serverIdArr.length) return
showLoading({
title: ' ',
position: 'absolute',
transition: 'none'
})
const uploadUrl = that.isRegistComponent ? '/api/site/upload-wx-file-without-login' : '/api/broker/index/upload-wx-file'
http.get(uploadUrl, {})
}
},
fail: err => {
console.warn(err)
}
})
}
export async function openLocation() {
const wx = await getWxConfig(['openLocation'])
wx.openLocation({
latitude, // 要去的纬度-地址
longitude, // 要去的经度-地址
name: this.houseDetail.lp_name,
address: this.houseDetail.lp_name,
scale: 15
})
}
微信jssdk难点还是在调试上面,常见的问题有签名失败、url不匹配、js安全域名未配置、config配置成功,但是实际分享失败、url上的对象参数等问题,所以碰到问题,找准方向,耐心调试即可;
参考文档
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Official_Accounts/js_sdk_instructions.html https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/how_to_apply.html