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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 微信公众号 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
}