You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

294 lines
7.3 KiB

/*
2 weeks ago
* 微信公众号 H5 - 微信 JS-SDK
* 签名接口 /wechat-share 需要 Bearer token须在登录成功后调用
*/
import { ROOTPATH, WECHAT_APPID } from '@/common/config.js'
2 weeks ago
const DEFAULT_JS_API = [
'getLocation',
'openLocation',
'updateAppMessageShareData',
'updateTimelineShareData'
]
const SIGNATURE_APIS = [
'/api/mobile/user/wechat-share',
'/api/mobile/user/wechat-login-url'
]
let wxInstance = null
let wxInitPromise = null
export function isWechatBrowser() {
return typeof navigator !== 'undefined' && /MicroMessenger/i.test(navigator.userAgent)
}
export function getStoredToken() {
const walksz_lifeData = uni.getStorageSync('walksz_lifeData') || {}
return walksz_lifeData.vuex_token || ''
}
export function resetWxJssdk() {
wxInitPromise = null
}
/**
* 参与签名的页面 URL须与后端 wechat-share 入参 url 完全一致
* 后端常按 https://域名/h5walksz/ 带尾斜杠签名
*/
export function getSignPageUrl() {
let url = window.location.href.split('#')[0]
if (url.endsWith('/h5walksz')) {
url += '/'
}
return url
}
function buildJsApiList(serverList) {
const required = [
'getLocation',
'openLocation',
'updateAppMessageShareData',
'updateTimelineShareData',
'onMenuShareTimeline',
'onMenuShareAppMessage'
]
const merged = [...(Array.isArray(serverList) ? serverList : []), ...required]
return Array.from(new Set(merged))
}
/**
* 解析 wechat-share 返回根级含 appId/nonceStr/timestamp/signature/jsApiList
*/
export function normalizeSignaturePayload(body) {
if (!body) return null
let payload = body
if (typeof payload === 'string') {
try {
payload = JSON.parse(payload)
} catch (e) {
return null
}
}
if (payload.data && payload.data.signature) {
payload = payload.data
}
if (!payload.signature) return null
const timestamp = payload.timestamp || payload.timeStamp
const nonceStr = payload.nonceStr || payload.noncestr
if (!timestamp && timestamp !== 0) return null
if (!nonceStr) return null
return {
signature: payload.signature,
timestamp: String(timestamp),
nonceStr,
appId: payload.appId || payload.appid || WECHAT_APPID,
jsApiList: buildJsApiList(payload.jsApiList),
signUrl: payload.url || ''
}
}
function getAuthHeader() {
const token = getStoredToken()
return token ? { Authorization: `Bearer ${token}` } : {}
}
function parseWxSignatureResponse(res) {
if (!res || res.statusCode !== 200) return null
let body = res.data
if (!body) return null
if (body.errcode !== undefined && body.errcode !== 0) {
console.warn('微信签名接口返回错误:', body.errcode, body.errmsg || body.message)
return null
}
return normalizeSignaturePayload(body)
}
const loadWxSDK = () => {
return new Promise((resolve, reject) => {
2 weeks ago
if (typeof window === 'undefined') {
reject(new Error('非浏览器环境'))
return
}
2 weeks ago
if (window.wx) {
wxInstance = window.wx
resolve(wxInstance)
return
}
const script = document.createElement('script')
script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
script.onload = () => {
wxInstance = window.wx
resolve(wxInstance)
}
script.onerror = () => reject(new Error('加载微信 JS-SDK 失败'))
document.head.appendChild(script)
})
}
2 weeks ago
function requestWxSignature(apiPath) {
const token = getStoredToken()
if (!token) {
return Promise.reject(new Error('未登录,无法获取微信签名'))
}
return new Promise((resolve, reject) => {
2 weeks ago
const pageUrl = getSignPageUrl()
uni.request({
2 weeks ago
url: `${ROOTPATH}${apiPath}`,
method: 'GET',
2 weeks ago
header: getAuthHeader(),
data: {
2 weeks ago
url: pageUrl,
activity_tag: 'walksz',
activity_list_id: 13
},
success: (res) => {
2 weeks ago
const parsed = parseWxSignatureResponse(res)
if (parsed) {
resolve(parsed)
} else {
2 weeks ago
reject(new Error(`签名数据无效: ${apiPath}`))
}
},
2 weeks ago
fail: (err) => reject(err)
})
})
}
2 weeks ago
const getWxSignature = async () => {
let lastErr = null
for (let i = 0; i < SIGNATURE_APIS.length; i++) {
try {
return await requestWxSignature(SIGNATURE_APIS[i])
} catch (e) {
lastErr = e
console.warn('尝试签名接口失败:', SIGNATURE_APIS[i], e.message || e)
}
}
throw lastErr || new Error('获取微信签名失败')
}
function setupShare(wx, shareConfig = {}) {
const link = shareConfig.link || window.location.href.split('#')[0]
const imgUrl = shareConfig.imgUrl || `${window.location.origin}/h5walksz/static/share.jpg`
wx.updateAppMessageShareData({
title: shareConfig.title || '打卡苏州市党史教育基地',
desc: shareConfig.desc || '打卡苏州市党史教育基地',
link,
imgUrl
})
wx.updateTimelineShareData({
title: shareConfig.title || '打卡苏州市党史教育基地',
link,
imgUrl
})
}
/**
* 使用已拿到的签名数据配置 JSSDK推荐通过 $u.api.share 获取签名
*/
export function configWechatJssdk(signatureData, options = {}) {
const parsed = normalizeSignaturePayload(signatureData)
if (!parsed) {
return Promise.reject(new Error('微信签名数据格式不正确'))
}
if (wxInitPromise) return wxInitPromise
const pageUrl = getSignPageUrl()
if (parsed.signUrl && parsed.signUrl !== pageUrl) {
console.warn('微信签名 URL 与当前页不一致,可能导致 config 失败', {
signUrl: parsed.signUrl,
pageUrl
})
}
wxInitPromise = loadWxSDK()
.then(() => {
return new Promise((resolve, reject) => {
wxInstance.config({
debug: false,
2 weeks ago
appId: parsed.appId || WECHAT_APPID,
timestamp: parsed.timestamp,
nonceStr: parsed.nonceStr,
signature: parsed.signature,
jsApiList: options.jsApiList || parsed.jsApiList || DEFAULT_JS_API
})
2 weeks ago
wxInstance.ready(() => {
if (options.setupShare !== false) {
setupShare(wxInstance, options.shareConfig)
}
if (typeof options.onReady === 'function') {
options.onReady(wxInstance)
}
resolve(wxInstance)
})
2 weeks ago
wxInstance.error((res) => {
wxInitPromise = null
console.error('wx.config 失败:', res)
reject(res)
})
})
})
2 weeks ago
.catch((err) => {
wxInitPromise = null
throw err
})
return wxInitPromise
}
export function ensureWechatJssdk(options = {}) {
if (!isWechatBrowser()) {
return Promise.reject(new Error('非微信内置浏览器'))
}
if (!getStoredToken()) {
return Promise.reject(new Error('请先登录后再使用微信地图能力'))
}
if (wxInitPromise) return wxInitPromise
return getWxSignature().then((data) => configWechatJssdk(data, options))
}
export function getWechatLocation() {
return ensureWechatJssdk({ setupShare: false }).then((wx) => {
return new Promise((resolve, reject) => {
wx.getLocation({
type: 'gcj02',
success: (res) => {
resolve({
longitude: res.longitude,
latitude: res.latitude
})
},
cancel: () => reject(new Error('用户取消定位')),
fail: (err) => reject(err)
})
})
})
}
export function openWechatLocation(lat, lng, name, address = '') {
return ensureWechatJssdk({ setupShare: false }).then((wx) => {
return new Promise((resolve, reject) => {
wx.openLocation({
latitude: parseFloat(lat),
longitude: parseFloat(lng),
name: name || '目的地',
address: address || '',
scale: 16,
success: resolve,
fail: reject
})
})
})
}
2 weeks ago
const initWxSDK = (shareConfig = {}) => {
return ensureWechatJssdk({ shareConfig, setupShare: true })
}
export {
loadWxSDK,
getWxSignature,
initWxSDK
}