前言:
大家好,.今天为大家讲一下微信公众号开发,调用微信的JSSDK的案例.
我们发现了一种非常巧妙且实用的“曲线救国”方案——利用微信公众号 JS-SDK 的扫一扫功能(wx.scanQRCode)在小程序内打开H5。
核心痛点与绕过原理
1. 痛点
- 小程序原生的
wx.scanCode只能获取扫码结果(字符串)。如果扫出的是一个非白名单 URL,小程序无法通过<web-view>直接打开它(会提示“不支持打开非业务域名”)。 - 直接将域名配入小程序业务域名需要上传校验文件,这在合作方不配合的情况下无法实现。
2. 绕过原理
- 小程序提供一个专属页面(如
page/scanning/index),该页面嵌套一个指向自主可控且已配置白名单的 H5 页面(如https://xxx.com)。 - 在该 H5 页面中,引入微信公众号 JS-SDK,并完成权限签名(
wx.config)。 - H5 页面加载时自动或通过用户触发调用JS-SDK 的
wx.scanQRCode接口。 - 关键设置:调用时将参数
needResult设为0(即扫码结果由微信处理)。 - 最终效果:当微信客户端扫描到一个 URL 时,由于
needResult: 0,微信客户端会自动接管并使用微信内置的 H5 浏览器容器(非小程序 web-view 容器)直接打开该 URL。
架构与调用流程
小程序主页/菜单 │ ▼ (跳转/切换 Tab) 小程序扫码页 (page/scanning/index) │ ▼ (加载) <web-view src="https://xxx.com"> │ ▼ (H5 初始化) 引入 JS-SDK 并调用 wx.config 签名 │ ▼ (触发扫码) 调用 wx.scanQRCode({ needResult: 0 }) │ ▼ (扫描 URL) 微信客户端接管流程 ──(在新窗口打开)──> 微信原生内置浏览器 (加载H5)具体实现步骤
第一步:微信绑定域名
调用微信的东西,那必须的看官方文档必然!
微信开放文档
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
按照说明文档的使用方法:
第一步:绑定域名---登录公众号---设置与开发--功能设置--JS接口安全域名
第二步:小程序端配置
在小程序中,创建一个专门用来承载扫码的页面
嵌套<web-view>,加载已配置为小程序业务域名的 H5 链接:
在页面展示时,设置 H5 地址并传入必要参数(如渠道、商户标识等):
<template> <web-view :src="result"></web-view> </template> <script setup> import { ref } from "vue" import { onShow, onHide } from "@dcloudio/uni-app" let showStep = 0 const result = ref("") const payUrl = "https://xxx.xxx/index.html" const switchToHome = () => { uni.switchTab({ url: "/pages/purch-home/index" }) } onShow(() => { if (showStep === 0) { result.value = payUrl } else { switchToHome() } showStep = showStep === 0 ? 1 : 0 }) onHide(() => { result.value = "" }) </script> <style scoped> /* 原 isIpx 适配样式可写在这里 */ </style>第三步:H5 端配置
这是本方案的核心。由于 JS-SDK 依赖公众号(服务号)的安全域名和签名,因此该 H5 页面需要绑定一个已认证的微信公众号。
1. 安装微信 JS-SDK
安装:
npm install weixin-js-sdk使用:
// commonjs var wx = require('weixin-js-sdk'); // es module import wx from 'weixin-js-sdk'https://www.npmjs.com/package/weixin-js-sdk?activeTab=readme
2. 服务端生成签名并初始化wx.config
在 H5 服务端,需要使用公众号的appId和appSecret获取access_token和jsapi_ticket,计算当前 URL 的签名并返回给前端:
import jWeixin from 'weixin-js-sdk' const DEFAULT_TIMEOUT = 10000 function isWechatBrowser() { if (typeof window === 'undefined') return false return /MicroMessenger/i.test(window.navigator.userAgent) } function withTimeout(promise, timeout = DEFAULT_TIMEOUT) { if (!timeout || timeout <= 0) return promise let timer = null return Promise.race([ promise, new Promise((_, reject) => { timer = setTimeout(() => { reject( new Error('微信授权超时,请检查是否在微信内打开或签名配置是否正确') ) }, timeout) }) ]).finally(() => { if (timer) clearTimeout(timer) }) } /** * 步骤一:发起网络请求获取微信 JSSDK 签名参数 */ function fetchSignature(options, currentUrl) { return new Promise((resolve, reject) => { uni.request({ url: options.apiUrl, method: options.method || 'GET', header: options.headers || {}, data: { link: currentUrl, ...options.data }, success: (res) => { try { let authData = null if (typeof options.parseResponse === 'function') { authData = options.parseResponse(res) } else { authData = res.data || {} } if (!authData || !authData.appId || !authData.signature) { reject( new Error( '微信签名参数解析失败,请检查接口返回结构或传入 parseResponse 自定义解析' ) ) } else { resolve(authData) } } catch (err) { reject(err) } }, fail: (err) => { console.error('获取微信签名接口调用失败:', err) reject(err) } }) }) } /** * 步骤二:调用微信 JSSDK Config 配置并监听 ready/error 事件 */ function configureJssdk(authData, options) { return new Promise((resolve, reject) => { jWeixin.config({ debug: !!options.debug, appId: authData.appId, timestamp: authData.timestamp, nonceStr: authData.nonceStr, signature: authData.signature, jsApiList: options.jsApiList || [], openTagList: options.openTagList || [] }) jWeixin.ready(() => { console.log('wx-sdk-ready') resolve() }) jWeixin.error((error) => { console.error('wx-sdk-error:', error) reject(error) }) }) } /** * 步骤三:检测微信客户端是否支持给定的 JS APIs */ function verifyApis(apiList) { return new Promise((resolve, reject) => { if (!apiList || apiList.length === 0) { return resolve() } jWeixin.checkJsApi({ jsApiList: apiList, success: (checkRes) => { console.log('checkJsApi result:', checkRes) const checkResult = checkRes.checkResult || {} const unsupportedApis = apiList.filter((api) => !checkResult[api]) if (unsupportedApis.length > 0) { reject( new Error( `当前客户端环境不支持以下 JS API: ${unsupportedApis.join(', ')}` ) ) } else { resolve() } }, fail: (checkErr) => { reject( new Error( `检测微信 JS API 支持性失败: ${checkErr.errMsg || '未知错误'}` ) ) } }) }) } /** * 初始化微信 JSSDK * @param {Object} options 配置参数 * @param {string} options.apiUrl 获取签名的接口地址 * @param {string} [options.method='GET'] 请求方法 * @param {Object} [options.headers={}] 自定义请求头 * @param {Object} [options.data={}] 其他请求参数 * @param {string} [options.url] 签名用的当前页面 URL,默认自动获取 window.location.href.split('#')[0] * @param {boolean} [options.debug=false] 是否开启 debug 模式 * @param {Array} [options.jsApiList=[]] 需要使用的 JS 接口列表 * @param {Array} [options.openTagList=[]] 需要使用的开放标签列表,例如跳转App * @param {Function} [options.parseResponse] 自定义解析响应数据的函数,需返回 { appId, timestamp, nonceStr, signature } * @param {boolean} [options.checkWechat=true] 是否检查微信浏览器环境 * @param {number} [options.timeout=10000] 初始化超时时间,传 0 可关闭超时 */ export async function initJssdk(options = {}) { if (!options.apiUrl) { throw new Error('请传入获取微信签名的接口地址 apiUrl') } // 1. 检查运行环境 if (typeof window === 'undefined') { throw new Error('非浏览器环境,无法初始化 JSSDK') } if (options.checkWechat !== false && !isWechatBrowser()) { throw new Error('请在微信内打开页面后使用扫一扫') } await withTimeout( (async () => { // 获取当前页面 URL (去掉 # 之后的部分) const currentUrl = options.url || window.location.href.split('#')[0] console.log('JSSDK 授权 URL:', currentUrl) // 2. 获取签名参数 const authData = await fetchSignature(options, currentUrl) // 3. 配置微信 JSSDK 并在 ready 后 resolve await configureJssdk(authData, options) // 4. 动态检测所使用的 API 支持度 await verifyApis(options.jsApiList || []) })(), options.timeout ) } export default { initJssdk }3. 调用扫码 API
当 JS-SDK 验证通过后(在wx.ready回调中),直接调用wx.scanQRCode:
<template> <view class="flex min-h-screen flex-col items-center justify-center bg-white p-[40rpx]"> <!-- 状态指示区 --> <view class="flex flex-col items-center justify-center"> <view class="mb-[40rpx] flex h-[120rpx] w-[120rpx] items-center justify-center rounded-[30rpx] bg-gradient-to-br from-[#1cd66c] to-[#07c160] shadow-lg shadow-[#07c160]/20"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="3" y="3" width="7" height="7" /> <rect x="14" y="3" width="7" height="7" /> <rect x="14" y="14" width="7" height="7" /> <rect x="3" y="14" width="7" height="7" /> </svg> </view> <!-- 提示语 --> <text class="mt-[20rpx] text-center font-semibold text-[#1f2937] text-[32rpx]"> {{ statusMessage }} </text> <!-- 加载动画 --> <view v-if="loading" class="mt-[40rpx] h-[50rpx] w-[50rpx] animate-spin rounded-full border-[4rpx] border-b-transparent border-l-transparent border-r-transparent border-t-[#07c160]"></view> <!-- 手动重新扫码按钮 --> <button v-if="showRetryButton" @click="startScanProcess" class="mt-[60rpx] flex h-[88rpx] items-center justify-center rounded-[44rpx] bg-[#07c160] px-[80rpx] font-semibold text-[28rpx] text-white shadow-md active:opacity-90"> 重新扫码 </button> </view> </view> </template> <script setup> import { ref } from 'vue' import { onLoad } from '@dcloudio/uni-app' import jWeixin from 'weixin-js-sdk' import { initJssdk } from '@cmpay/utils/initJssdk' const loading = ref(true) const statusMessage = ref('正在安全加载中...') const showRetryButton = ref(false) // 获取错误消息,防止展示为 [object Object] const getErrorMessage = (error) => { if (!error) return '未知错误' if (typeof error === 'string') return error if (error.message) return error.message if (error.errMsg) return error.errMsg if (typeof error === 'object') { try { return JSON.stringify(error) } catch (e) { return String(error) } } return String(error) } // 错误弹窗提示 const showErrorModal = (content) => { uni.showModal({ title: '提示', content: content, showCancel: false, confirmText: '确定', success: () => { showRetryButton.value = true } }) } // 核心初始化与调用扫码逻辑 const startScanProcess = async () => { loading.value = true showRetryButton.value = false statusMessage.value = '正在唤起扫一扫...' try { // 1. 初始化 JSSDK (已成功则跳过) statusMessage.value = '正在配置微信授权...' await initJssdk({ apiUrl: `${import.meta.env.VITE_BASE_URL || ''}/xxx/xxx`, method: 'GET', jsApiList: ['scanQRCode'], debug: false }) // 2. 自动拉起扫码 statusMessage.value = '请在扫码界面完成扫码...' jWeixin.scanQRCode({ needResult: 0, scanType: ['qrCode', 'barCode'], success: (res) => { console.log(res) }, fail: (err) => { console.error('微信扫码失败:', err) loading.value = false statusMessage.value = '扫码被取消或失败' showErrorModal(`扫码失败: ${getErrorMessage(err)}`) } }) } catch (error) { console.error('初始化微信 JSSDK 失败:', error) loading.value = false statusMessage.value = '授权失败' showErrorModal(`初始化微信 JSSDK 失败: ${getErrorMessage(error)}`) } } onLoad(() => { startScanProcess() }) </script> <style scoped> @keyframes spin { to { transform: rotate(360deg); } } .animate-spin { animation: spin 1s linear infinite; } </style>总结
本项目采用的小程序 Web-view 嵌套自建 H5 + 公众号 JSSDKscanQRCode(needResult: 0)是一套非常经典且巧妙的架构设计。它将小程序的便捷入口与公众号 H5 容器的相对自由度结合起来,成功解决了无法在合作方网站放置校验文件的棘手问题,是跨生态协作和扫码流转场景下的首选方案。