|
|
/*
|
|
|
* 微信公众号 H5 - 微信 JS-SDK
|
|
|
* 签名接口 /wechat-share 需要 Bearer token,须在登录成功后调用
|
|
|
*/
|
|
|
import { ROOTPATH, WECHAT_APPID } from '@/common/config.js'
|
|
|
|
|
|
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) => {
|
|
|
if (typeof window === 'undefined') {
|
|
|
reject(new Error('非浏览器环境'))
|
|
|
return
|
|
|
}
|
|
|
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)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function requestWxSignature(apiPath) {
|
|
|
const token = getStoredToken()
|
|
|
if (!token) {
|
|
|
return Promise.reject(new Error('未登录,无法获取微信签名'))
|
|
|
}
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const pageUrl = getSignPageUrl()
|
|
|
uni.request({
|
|
|
url: `${ROOTPATH}${apiPath}`,
|
|
|
method: 'GET',
|
|
|
header: getAuthHeader(),
|
|
|
data: {
|
|
|
url: pageUrl,
|
|
|
activity_tag: 'walksz',
|
|
|
activity_list_id: 13
|
|
|
},
|
|
|
success: (res) => {
|
|
|
const parsed = parseWxSignatureResponse(res)
|
|
|
if (parsed) {
|
|
|
resolve(parsed)
|
|
|
} else {
|
|
|
reject(new Error(`签名数据无效: ${apiPath}`))
|
|
|
}
|
|
|
},
|
|
|
fail: (err) => reject(err)
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
appId: parsed.appId || WECHAT_APPID,
|
|
|
timestamp: parsed.timestamp,
|
|
|
nonceStr: parsed.nonceStr,
|
|
|
signature: parsed.signature,
|
|
|
jsApiList: options.jsApiList || parsed.jsApiList || DEFAULT_JS_API
|
|
|
})
|
|
|
wxInstance.ready(() => {
|
|
|
if (options.setupShare !== false) {
|
|
|
setupShare(wxInstance, options.shareConfig)
|
|
|
}
|
|
|
if (typeof options.onReady === 'function') {
|
|
|
options.onReady(wxInstance)
|
|
|
}
|
|
|
resolve(wxInstance)
|
|
|
})
|
|
|
wxInstance.error((res) => {
|
|
|
wxInitPromise = null
|
|
|
console.error('wx.config 失败:', res)
|
|
|
reject(res)
|
|
|
})
|
|
|
})
|
|
|
})
|
|
|
.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
|
|
|
})
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
const initWxSDK = (shareConfig = {}) => {
|
|
|
return ensureWechatJssdk({ shareConfig, setupShare: true })
|
|
|
}
|
|
|
|
|
|
export {
|
|
|
loadWxSDK,
|
|
|
getWxSignature,
|
|
|
initWxSDK
|
|
|
}
|