CSDN-uniapp-h5-微信JSSDK动态注入与封装实战
UniApp H5:动态注入微信 JSSDK + utils/weixin.js 二次封装(扫码/选图上传/分享)实战
场景:UniApp 项目同时跑 H5 / 小程序 / App。其中 H5 需要在 微信内置浏览器 或 小程序 WebView 中使用微信能力(扫码、选图上传、分享等)。
本文基于我当前项目的真实代码:App.vue的 动态注入 JSSDK +utils/weixin.js的 JS-SDK 工具类封装。
0. 先把两个问题分清楚(本文解决的重点)
很多同学把“微信 H5 能力”混在一起讲,其实在 UniApp H5 里常见的是两个不同问题:
-
问题 A:小程序
web-view里如何跳回小程序页面?
这类问题只需要wx.miniProgram.navigateBack/navigateTo/...,不等同于完整的公众号 H5 JS-SDK 权限体系。官方文档明确:web-view网页里可以使用 JSSDK 1.3.2 提供的接口返回小程序页面(含wx.miniProgram.navigateBack等),参考:微信开放文档 - web-view。 -
问题 B:为什么
wx.config总报权限/接口不可用?
在小程序web-view场景下,网页内 仅支持有限的 JS-SDK 接口(比如scanQRCode / chooseImage / uploadImage / getNetworkType等)。如果你在jsApiList里配置了不支持的能力(例如部分分享相关接口),就会出现“接口不可用/权限失败/调用无响应”等问题。支持列表同样以官方文档为准:微信开放文档 - web-view。
下面正文会按这两个问题分别讲清楚:先解决 web-view 回小程序,再解决 web-view 权限接口清单与 wx.config 配置。
1. 为什么要“动态注入”微信 JSSDK
在 UniApp 的 H5 里,微信能力来自微信 JS-SDK(wx / jWeixin)。但它有几个典型问题:
- 不是所有环境都有
wx:普通浏览器打开页面没有微信 JS-SDK - 加载时机不确定:脚本没加载完就调用会报错
- 必须先
wx.config:否则很多 API 不能用 - 签名 URL 特别容易踩坑:必须去掉
#(hash)部分,但保留 query - 多次并发初始化:多个页面同时需要能力,容易重复加载/重复配置
所以最稳妥的策略是:
- 进入 H5 时 注入 JSSDK 脚本
- 使用一个统一工具类:负责 加载脚本、判断环境、获取签名、config/ready/error、功能封装 + 降级
2. App.vue:H5 环境启动时动态注入微信 JSSDK
项目里在 App.vue 的 onLaunch 中注入脚本(只在 H5 编译条件下执行):
onLaunch: function(options) {
this.initApp()
// #ifdef H5
// 注入微信 JSSDK(提供 wx / wx.miniProgram 能力)
this.injectWxJssdk()
// 获取URL参数并存储到全局
this.getUrlParams(options)
// #endif
},
注入函数本体如下(核心:避免重复注入 + 监听 onload/onerror):
// 动态注入微信 JSSDK(H5 环境)
injectWxJssdk() {
// #ifdef H5
try {
if (typeof document === 'undefined') return
// 避免重复注入
if (document.getElementById('wx-jssdk')) return
const script = document.createElement('script')
script.id = 'wx-jssdk'
script.src = 'https://res.wx.qq.com/open/js/jweixin-1.3.2.js'
script.async = true
script.onload = () => {
// eslint-disable-next-line no-console
console.log('wx jssdk loaded')
}
script.onerror = (e) => {
// eslint-disable-next-line no-console
console.error('wx jssdk load failed:', e)
}
;
(document.head || document.body || document.documentElement).appendChild(script)
} catch (e) {
// eslint-disable-next-line no-console
console.error('injectWxJssdk failed:', e)
}
// #endif
},
这段代码解决了什么?
- 不在 H5 不执行:
// #ifdef H5 - 不会重复注入:
document.getElementById('wx-jssdk') - 能看到加载成功/失败日志:方便排查 CDN、网络、CSP 等问题
- (关键)小程序
web-view回小程序:引入jweixin-1.3.2.js后,网页内才可以稳定使用wx.miniProgram.navigateBack/navigateTo/...。对应官方说明见:微信开放文档 - web-view。
注意:你的项目里
utils/weixin.js也带“动态加载脚本”能力(默认 1.6.0)。如果你主要目标是 小程序 web-view 回跳,务必以官方建议的 1.3.2 为准;否则请统一版本并避免重复加载(文末给建议)。
3. utils/weixin.js:为什么要再封装一层
仅仅把脚本插进来还不够,原因是:
wx可能还没 ready- 你必须先向后端拿
signature再wx.config - 不同环境要做降级(非微信环境用
uni.*)
所以项目里做了一个 WeixinSDK 工具类,并导出单例:
// 创建单例实例
const weixinSDK = new WeixinSDK()
export default weixinSDK
4. 环境识别:微信 H5 / 小程序 WebView
4.1 获取 SDK 对象(兼容 jWeixin / wx)
getWxSdk() {
// #ifdef H5
if (typeof jWeixin !== 'undefined') {
return jWeixin
}
if (typeof wx !== 'undefined' && typeof wx.config === 'function') {
return wx
}
// #endif
return null
}
4.2 判断是否微信环境 / 是否小程序 WebView
checkIsWeixin() {
// #ifdef H5
const ua = navigator.userAgent.toLowerCase()
// 检测微信环境(包括微信H5和小程序webview)
// 小程序webview的UserAgent也包含 'micromessenger'
return ua.indexOf('micromessenger') !== -1
// #endif
// #ifndef H5
return false
// #endif
}
checkIsMiniProgramWebview() {
// #ifdef H5
if (!this.checkIsWeixin()) {
return false
}
// 小程序webview的UserAgent通常包含 'miniprogram'
const ua = navigator.userAgent.toLowerCase()
return ua.indexOf('miniprogram') !== -1
// #endif
// #ifndef H5
return false
// #endif
}
5. 动态加载 JSSDK(更通用的 loader)
utils/weixin.js 里实现了一个“只加载一次”的脚本加载器:已加载直接返回,正在加载复用同一个 Promise。
loadScript(url = null) {
// #ifdef H5
// 如果已经加载,直接返回
if (this.scriptLoaded) {
return Promise.resolve(true)
}
// 如果正在加载中,返回加载Promise
if (this.scriptLoadingPromise) {
return this.scriptLoadingPromise
}
// 如果SDK已经存在,标记为已加载
if (this.getWxSdk()) {
this.scriptLoaded = true
return Promise.resolve(true)
}
// 创建加载Promise
this.scriptLoadingPromise = new Promise((resolve) => {
const scriptUrl = url || this.scriptUrl
// 检查是否已经存在该script标签
const existingScript = document.querySelector(`script[src="${scriptUrl}"]`)
if (existingScript) {
// 如果script标签已存在,等待加载完成
const checkLoaded = () => {
if (this.getWxSdk()) {
this.scriptLoaded = true
this.scriptLoadingPromise = null
resolve(true)
} else {
setTimeout(checkLoaded, 50)
}
}
checkLoaded()
return
}
// 创建script标签
const script = document.createElement('script')
script.type = 'text/javascript'
script.async = true
script.src = scriptUrl
// 加载成功
script.onload = () => {
// 等待一小段时间确保SDK对象可用
setTimeout(() => {
if (this.getWxSdk()) {
this.scriptLoaded = true
this.scriptLoadingPromise = null
resolve(true)
} else {
console.error('微信JS-SDK脚本加载完成但SDK对象不可用')
this.scriptLoadingPromise = null
resolve(false)
}
}, 100)
}
// 加载失败
script.onerror = () => {
console.error('微信JS-SDK脚本加载失败')
this.scriptLoadingPromise = null
resolve(false)
}
// 添加到页面
document.head.appendChild(script)
})
return this.scriptLoadingPromise
// #endif
// #ifndef H5
return Promise.resolve(false)
// #endif
}
6. 初始化 wx.config:签名 URL 处理 + 并发去重
核心逻辑在 initConfig():
- 非微信环境直接返回 false
- 先确保脚本加载成功
- URL 必须 去掉 hash:
https://xxx.com/#/a?b=1签名用的是https://xxx.com/(保留 query,去掉#之后的所有内容) - 调后端接口拿
appId/timestamp/nonceStr/signature wx.config的jsApiList只配置当前场景支持的接口(尤其是小程序web-view)wx.config后等待wx.ready/wx.error
代码如下:
async initConfig(jsApiList = null, debug = false, url = null) {
// 默认权限:扫码、选择图片、上传图片(标准微信JS-SDK流程)
const defaultApiList = ['scanQRCode', 'chooseImage', 'uploadImage']
const finalApiList = jsApiList || defaultApiList
// #ifdef H5
// 如果正在配置中,返回配置Promise
if (this.configPromise) {
return this.configPromise
}
// 检查是否在微信环境(包括微信H5和小程序webview)
if (!this.checkIsWeixin()) {
console.warn('当前不在微信浏览器中')
return Promise.resolve(false)
}
// 先加载微信JS-SDK脚本
const scriptLoaded = await this.loadScript()
if (!scriptLoaded) {
console.error('微信JS-SDK脚本加载失败')
return Promise.resolve(false)
}
// 获取SDK对象
this.wxSdk = this.getWxSdk()
if (!this.wxSdk) {
console.error('微信JS-SDK未加载')
return Promise.resolve(false)
}
// 如果已经配置成功,直接返回
if (this.configReady) {
return Promise.resolve(true)
}
// 创建配置Promise
this.configPromise = new Promise(async (resolve) => {
try {
let currentUrl = url || window.location.href
// 微信JS-SDK签名需要去掉hash部分,但保留查询参数
if (currentUrl.indexOf('#') !== -1) {
currentUrl = currentUrl.split('#')[0]
}
// 调用后端接口获取微信配置
const res = await getWeixinConfig(currentUrl)
if (res.data) {
const config = res.data
this.wxSdk.config({
debug: debug,
appId: config.appId,
timestamp: config.timestamp,
nonceStr: config.nonceStr,
signature: config.signature,
jsApiList: finalApiList
})
this.wxSdk.ready(() => {
console.log('微信JS-SDK配置成功')
this.configReady = true
this.configPromise = null
resolve(true)
})
this.wxSdk.error((err) => {
console.error('微信JS-SDK配置失败:', err)
this.configReady = false
this.configPromise = null
resolve(false)
})
} else {
console.warn('获取微信配置失败:', res.msg || '未知错误')
this.configReady = false
this.configPromise = null
resolve(false)
}
} catch (error) {
console.error('初始化微信配置失败:', error)
this.configReady = false
this.configPromise = null
resolve(false)
}
})
return this.configPromise
// #endif
// #ifndef H5
return Promise.resolve(false)
// #endif
}
这里的
getWeixinConfig(currentUrl)就是后端签名接口:把当前 URL 发给后端,后端用公众号jsapi_ticket计算签名并返回。
小程序web-view场景下,“网页内可用的 JS-SDK 接口”是受限的,哪些接口能配进jsApiList以官方文档为准:微信开放文档 - web-view。
7. “统一入口”封装:能用微信就用微信,用不了就降级
这也是本文最值得复用的点:一套 API 兼容多端。
7.1 统一扫码:微信用 wx.scanQRCode,其他环境用 uni.scanCode
async scanCode(options = {}) {
// #ifdef H5
if (this.checkIsWeixin()) {
const ok = await this.initConfig()
if (!ok) return Promise.reject(new Error('微信配置未完成,请稍后再试'))
return this.scanQRCode({
needResult: options.needResult !== undefined ? options.needResult : 1,
scanType: options.scanType || ['qrCode', 'barCode']
})
}
// #endif
// 非微信H5 / 小程序 / App:走 uni.scanCode
return new Promise((resolve, reject) => {
// eslint-disable-next-line no-undef
uni.scanCode({
onlyFromCamera: options.onlyFromCamera !== undefined ? options.onlyFromCamera : true,
success: (res) => resolve(res.result),
fail: (err) => reject(new Error(err?.errMsg || '扫码失败'))
})
})
}
7.2 统一选图:微信用 wx.chooseImage 返回 localIds,其他环境用 uni.chooseImage 返回 tempFilePaths
async chooseImageUnified(options = {}) {
// #ifdef H5
// 微信H5环境:使用微信SDK标准流程
if (this.checkIsWeixin()) {
try {
// 确保配置完成(默认配置已包含 chooseImage 和 uploadImage)
await this.initConfig()
return this.ready((wxSdk) => {
return new Promise((resolve, reject) => {
wxSdk.chooseImage({
count: options.count || 1,
sizeType: options.sizeType || ['compressed'],
sourceType: options.sourceType || ['album', 'camera'],
success: (res) => {
const localIds = res.localIds || []
if (localIds.length === 0) {
reject(new Error('未选择图片'))
return
}
console.log('微信选择图片成功,localIds:', localIds)
// 返回 localIds,用于后续调用 wx.uploadImage
resolve({ localIds })
},
fail: (err) => {
console.error('选择图片失败:', err)
reject(new Error(err.errMsg || '选择图片失败'))
}
})
})
})
} catch (error) {
// 如果微信SDK失败,降级使用 uni.chooseImage
console.warn('微信SDK选择图片失败,降级使用 uni.chooseImage:', error)
return this.chooseImageUnifiedFallback(options)
}
}
// #endif
// 非微信H5环境:使用 uni.chooseImage
return this.chooseImageUnifiedFallback(options)
}
7.3 分享:朋友圈 / 好友
重要说明:小程序
web-view内网页可用的 JS-SDK 接口是受限的,分享相关接口在web-view场景可能不可用。
如果你的 H5 是放在小程序web-view里并且需要“分享”,通常应该走 小程序层的分享能力(如页面onShareAppMessage获取webViewUrl等),而不是强依赖网页里的分享 JS-SDK。相关限制与能力边界请以官方文档为准:微信开放文档 - web-view。
async updateTimelineShareData(shareData) {
// #ifdef H5
if (!this.checkIsWeixin()) {
return Promise.reject(new Error('请在微信中打开此页面'))
}
return this.ready((wxSdk) => {
return new Promise((resolve, reject) => {
wxSdk.updateTimelineShareData({
title: shareData.title,
link: shareData.link,
imgUrl: shareData.imgUrl,
success: () => resolve(true),
fail: (err) => reject(new Error(err.errMsg || '分享失败'))
})
})
})
// #endif
// #ifndef H5
return Promise.reject(new Error('分享功能需要在微信浏览器中使用'))
// #endif
}
async updateAppMessageShareData(shareData) {
// #ifdef H5
if (!this.checkIsWeixin()) {
return Promise.reject(new Error('请在微信中打开此页面'))
}
return this.ready((wxSdk) => {
return new Promise((resolve, reject) => {
wxSdk.updateAppMessageShareData({
title: shareData.title,
desc: shareData.desc,
link: shareData.link,
imgUrl: shareData.imgUrl,
success: () => resolve(true),
fail: (err) => reject(new Error(err.errMsg || '分享失败'))
})
})
})
// #endif
// #ifndef H5
return Promise.reject(new Error('分享功能需要在微信浏览器中使用'))
// #endif
}
8. 页面里怎么用(示例)
在任意页面:
import weixinSDK from '@/utils/weixin'
export default {
methods: {
async onScan() {
try {
const result = await weixinSDK.scanCode()
console.log('扫码结果:', result)
} catch (e) {
this.$modal?.msgError?.(e.message || String(e))
}
},
async onChooseImage() {
const res = await weixinSDK.chooseImageUnified({ count: 1 })
// 微信H5:res.localIds;其他环境:res.tempFilePaths
console.log(res)
}
}
}
9. 我踩过的坑 & 建议
9.1 wx.config 的 URL 一定要去掉 hash
项目里已经做了:
window.location.href→split('#')[0]
这点极其关键:签名用的 URL 与后端计算必须完全一致。
9.2 动态注入脚本建议“只保留一个入口”
你项目当前同时存在:
App.vue注入jweixin-1.3.2.jsutils/weixin.jsloader 默认jweixin-1.6.0.js
建议二选一(并且先明确你在解决“问题 A”还是“问题 B”):
- 方案 A:只保留
utils/weixin.js的loadScript(),把App.vue的injectWxJssdk()当作可选项移除/注释掉 - 方案 B:保留
App.vue注入,但把utils/weixin.js的scriptUrl改成同版本,并确保loadScript()不会重复加载
如果你主要是在解决 小程序 web-view 回跳(问题 A),请优先遵循官方说明使用 jweixin-1.3.2.js 以获得 wx.miniProgram.* 能力,参考:微信开放文档 - web-view。
9.3 并发初始化要“去重”
utils/weixin.js 用 configPromise 做了并发复用,避免多个页面同时 initConfig() 造成重复 wx.config。
9.4 非微信环境要优雅降级
像 scanCode() 这种“统一入口”非常实用:微信用 wx.scanQRCode,否则用 uni.scanCode。
10. 自测清单
- 微信内置浏览器打开 H5
- 控制台能看到
wx jssdk loaded initConfig()成功(无config:invalid signature)- 扫码/选图/分享可用
- 控制台能看到
- 普通浏览器打开 H5
- 不报错(工具类能识别非微信环境并提示/降级)
- 小程序 WebView 打开 H5
- UA 识别:
micromessenger+miniprogram - 验证
wx.miniProgram.navigateBack/navigateTo可用(解决“web-view 回小程序”) - 验证
wx.config的jsApiList不要配置超出web-view支持范围的接口(解决“权限接口不可用”) - 能力边界以官方文档为准:微信开放文档 - web-view
- UA 识别:
结语
这套思路的核心就是一句话:“把不稳定(脚本加载、config、环境差异)全部收口到一个工具类里”,页面层只管调用统一 API。
后续如果要扩展(如定位、录音、支付、卡券),继续往 utils/weixin.js 里加封装即可。
更多推荐
所有评论(0)