|
|
@@ -469,6 +469,28 @@
|
|
|
pointer-events: auto;
|
|
|
}
|
|
|
|
|
|
+ .fab-wx-launch-host {
|
|
|
+ display: none;
|
|
|
+ position: absolute;
|
|
|
+ left: 24px;
|
|
|
+ right: 24px;
|
|
|
+ bottom: calc(12px + var(--safe-bottom));
|
|
|
+ z-index: 3;
|
|
|
+ max-width: 320px;
|
|
|
+ margin: 0 auto;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fab-wx-launch-host--active {
|
|
|
+ display: block;
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fab-wx-launch-host wx-open-launch-app {
|
|
|
+ display: block;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
.fab {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
@@ -969,6 +991,7 @@
|
|
|
</div>
|
|
|
<div id="shareBelowContentEl">
|
|
|
<div class="fab-wrap">
|
|
|
+ <div class="fab-wx-launch-host" id="openAppWxLaunchHost" aria-hidden="true"></div>
|
|
|
<button type="button" class="fab" id="openApp">
|
|
|
<img src="images/uBtn.png" alt="APP内打开" decoding="async">
|
|
|
</button>
|
|
|
@@ -995,6 +1018,10 @@
|
|
|
* 请用本地 HTTP 打开(如 VS Code Live Server、npx serve)或让后端为 H5 域名配置 Access-Control-Allow-Origin。
|
|
|
*/
|
|
|
var API_BASE = 'https://test.ailien.shop/alienStore';
|
|
|
+ /** 公众号内 JS-SDK 签名:GET …/wx/getWxConfig?url=当前页地址(不含 #) */
|
|
|
+ var WX_CONFIG_API = 'https://test.ailien.shop/wx/getWxConfig';
|
|
|
+ /** 微信开放平台「移动应用」AppID;若 getWxConfig 未返回 launchAppId 等字段则使用此值 */
|
|
|
+ var WX_LAUNCH_APP_ID = '';
|
|
|
|
|
|
/**
|
|
|
* 关店(businessStatus=99)更多推荐:POST …/ai/multimodal-services/api/v1/search/global/store-recommend
|
|
|
@@ -1080,6 +1107,216 @@
|
|
|
return /MicroMessenger/i.test(navigator.userAgent || '');
|
|
|
}
|
|
|
|
|
|
+ var wxJssdkLoadPromise = null;
|
|
|
+ var wxConfigReady = false;
|
|
|
+ var wxLaunchAppIdCache = '';
|
|
|
+ var wxLaunchMounted = false;
|
|
|
+ var wxPreparePromise = null;
|
|
|
+
|
|
|
+ function getWxSignPageUrl() {
|
|
|
+ return String(location.href || '').split('#')[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ function loadWxJssdk() {
|
|
|
+ if (typeof wx !== 'undefined' && typeof wx.config === 'function') {
|
|
|
+ return Promise.resolve();
|
|
|
+ }
|
|
|
+ if (wxJssdkLoadPromise) return wxJssdkLoadPromise;
|
|
|
+ wxJssdkLoadPromise = new Promise(function (resolve, reject) {
|
|
|
+ function attach(src, isFallback) {
|
|
|
+ var s = document.createElement('script');
|
|
|
+ s.src = src;
|
|
|
+ s.async = true;
|
|
|
+ s.onload = function () {
|
|
|
+ if (typeof wx !== 'undefined') resolve();
|
|
|
+ else reject(new Error('jweixin 未加载'));
|
|
|
+ };
|
|
|
+ s.onerror = function () {
|
|
|
+ if (!isFallback) {
|
|
|
+ attach('https://res2.wx.qq.com/open/js/jweixin-1.6.0.js', true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ reject(new Error('jweixin 加载失败'));
|
|
|
+ };
|
|
|
+ document.head.appendChild(s);
|
|
|
+ }
|
|
|
+ attach('https://res.wx.qq.com/open/js/jweixin-1.6.0.js', false);
|
|
|
+ });
|
|
|
+ return wxJssdkLoadPromise;
|
|
|
+ }
|
|
|
+
|
|
|
+ function fetchWxConfig() {
|
|
|
+ var pageUrl = getWxSignPageUrl();
|
|
|
+ var apiUrl = WX_CONFIG_API + '?url=' + encodeURIComponent(pageUrl);
|
|
|
+ return fetch(apiUrl, {
|
|
|
+ method: 'GET',
|
|
|
+ mode: 'cors',
|
|
|
+ credentials: 'omit',
|
|
|
+ headers: { Accept: 'application/json' }
|
|
|
+ }).then(function (res) {
|
|
|
+ if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
|
+ return res.json();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function normalizeWxConfigPayload(res) {
|
|
|
+ var d = res;
|
|
|
+ if (!res || typeof res !== 'object') return null;
|
|
|
+ if (res.data != null && typeof res.data === 'object') d = res.data;
|
|
|
+ else if (res.result != null && typeof res.result === 'object') d = res.result;
|
|
|
+ var appId = d.appId || d.appid || d.publicId || '';
|
|
|
+ var timestamp = d.timestamp != null ? d.timestamp : d.timeStamp;
|
|
|
+ var nonceStr = d.nonceStr || d.noncestr || '';
|
|
|
+ var signature = d.signature || '';
|
|
|
+ var launchAppId =
|
|
|
+ d.launchAppId ||
|
|
|
+ d.mobileAppId ||
|
|
|
+ d.openAppId ||
|
|
|
+ d.jumpAppId ||
|
|
|
+ d.appAppId ||
|
|
|
+ d.wxAppId ||
|
|
|
+ WX_LAUNCH_APP_ID ||
|
|
|
+ '';
|
|
|
+ if (!appId || !signature || timestamp == null || !nonceStr) return null;
|
|
|
+ return {
|
|
|
+ appId: String(appId),
|
|
|
+ timestamp: Number(timestamp) || 0,
|
|
|
+ nonceStr: String(nonceStr),
|
|
|
+ signature: String(signature),
|
|
|
+ launchAppId: String(launchAppId || '')
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyWxConfig(cfg) {
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ if (!cfg || !cfg.appId) {
|
|
|
+ reject(new Error('微信签名参数不完整'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ wx.config({
|
|
|
+ debug: true,
|
|
|
+ appId: cfg.appId,
|
|
|
+ timestamp: cfg.timestamp,
|
|
|
+ nonceStr: cfg.nonceStr,
|
|
|
+ signature: cfg.signature,
|
|
|
+ jsApiList: [],
|
|
|
+ openTagList: ['wx-open-launch-app']
|
|
|
+ });
|
|
|
+ wx.ready(function () {
|
|
|
+ wxConfigReady = true;
|
|
|
+ if (cfg.launchAppId) wxLaunchAppIdCache = cfg.launchAppId;
|
|
|
+ resolve(cfg);
|
|
|
+ });
|
|
|
+ wx.error(function (err) {
|
|
|
+ reject(err || new Error('wx.config 失败'));
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function buildAppExtInfo() {
|
|
|
+ var path = getAppUniPathForBusinessSection().replace(/^\//, '');
|
|
|
+ var qs = buildAppOpenQueryString();
|
|
|
+ return path + (qs || '');
|
|
|
+ }
|
|
|
+
|
|
|
+ function escapeHtmlAttr(s) {
|
|
|
+ return String(s)
|
|
|
+ .replace(/&/g, '&')
|
|
|
+ .replace(/"/g, '"')
|
|
|
+ .replace(/</g, '<');
|
|
|
+ }
|
|
|
+
|
|
|
+ function mountWxOpenLaunchApp(launchAppId) {
|
|
|
+ if (!launchAppId) return false;
|
|
|
+ var host = document.getElementById('openAppWxLaunchHost');
|
|
|
+ var btn = document.getElementById('openApp');
|
|
|
+ if (!host || !btn) return false;
|
|
|
+ if (wxLaunchMounted && host.querySelector('wx-open-launch-app')) return true;
|
|
|
+
|
|
|
+ var extinfo = escapeHtmlAttr(buildAppExtInfo());
|
|
|
+ var appid = escapeHtmlAttr(launchAppId);
|
|
|
+ host.innerHTML =
|
|
|
+ '<wx-open-launch-app id="wxOpenLaunchApp" appid="' +
|
|
|
+ appid +
|
|
|
+ '" extinfo="' +
|
|
|
+ extinfo +
|
|
|
+ '">' +
|
|
|
+ '<script type="text/wxtag-template">' +
|
|
|
+ '<style>.wx-fab-inner{display:flex;align-items:center;justify-content:center;width:100%;height:48px;border:0;padding:0;margin:0;background:transparent;}</style>' +
|
|
|
+ '<button type="button" class="wx-fab-inner" aria-label="APP内打开">' +
|
|
|
+ '<img src="images/uBtn.png" alt="" style="height:48px;width:auto;max-width:100%;display:block;">' +
|
|
|
+ '</button>' +
|
|
|
+ '<\/script></wx-open-launch-app>';
|
|
|
+
|
|
|
+ var tag = host.querySelector('wx-open-launch-app');
|
|
|
+ if (tag) {
|
|
|
+ tag.addEventListener('launch', function () {
|
|
|
+ console.log('[wx-open-launch-app] launch');
|
|
|
+ });
|
|
|
+ tag.addEventListener('error', function (e) {
|
|
|
+ console.warn('[wx-open-launch-app]', e && e.detail);
|
|
|
+ tryLaunchAppBySchemeOnly();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ host.classList.add('fab-wx-launch-host--active');
|
|
|
+ host.setAttribute('aria-hidden', 'false');
|
|
|
+ btn.setAttribute('aria-hidden', 'true');
|
|
|
+ btn.style.visibility = 'hidden';
|
|
|
+ btn.style.pointerEvents = 'none';
|
|
|
+ wxLaunchMounted = true;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ function clickWxOpenLaunchInner() {
|
|
|
+ var host = document.getElementById('openAppWxLaunchHost');
|
|
|
+ if (!host) return false;
|
|
|
+ var inner = host.querySelector('.wx-fab-inner');
|
|
|
+ if (inner && typeof inner.click === 'function') {
|
|
|
+ inner.click();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 公众号内:先 GET getWxConfig,再 wx.config + 开放标签唤起 App */
|
|
|
+ function prepareWeChatAppLaunch(forceRefresh) {
|
|
|
+ if (!forceRefresh && wxPreparePromise) return wxPreparePromise;
|
|
|
+ wxPreparePromise = loadWxJssdk()
|
|
|
+ .then(function () {
|
|
|
+ return fetchWxConfig();
|
|
|
+ })
|
|
|
+ .then(function (res) {
|
|
|
+ var cfg = normalizeWxConfigPayload(res);
|
|
|
+ if (!cfg) throw new Error('getWxConfig 返回无效');
|
|
|
+ if (wxConfigReady && cfg.launchAppId && wxLaunchAppIdCache === cfg.launchAppId) {
|
|
|
+ return cfg;
|
|
|
+ }
|
|
|
+ wxConfigReady = false;
|
|
|
+ return applyWxConfig(cfg);
|
|
|
+ })
|
|
|
+ .then(function (cfg) {
|
|
|
+ var launchId = (cfg && cfg.launchAppId) || wxLaunchAppIdCache;
|
|
|
+ if (!launchId) throw new Error('缺少移动应用 AppId');
|
|
|
+ mountWxOpenLaunchApp(launchId);
|
|
|
+ return cfg;
|
|
|
+ });
|
|
|
+ return wxPreparePromise;
|
|
|
+ }
|
|
|
+
|
|
|
+ function openAppViaWeChat() {
|
|
|
+ prepareWeChatAppLaunch(false)
|
|
|
+ .then(function () {
|
|
|
+ if (!clickWxOpenLaunchInner()) {
|
|
|
+ tryLaunchAppBySchemeOnly();
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(function (e) {
|
|
|
+ console.warn('[微信唤起App]', e);
|
|
|
+ tryLaunchAppBySchemeOnly();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
/** 部分 WebView 对 location.href 拦截,用 a 标签点击略稳 */
|
|
|
function launchAppDeepLink(deepLink) {
|
|
|
|
|
|
@@ -1096,11 +1333,51 @@
|
|
|
} catch (e2) {}
|
|
|
}
|
|
|
|
|
|
+ /** 仅 scheme 唤起(无微信提示),供开放标签失败时回退 */
|
|
|
+ function tryLaunchAppBySchemeOnly() {
|
|
|
+ var deepLink = buildAppDeepLink();
|
|
|
+ var t0 = Date.now();
|
|
|
+ var done = false;
|
|
|
+ function finish() {
|
|
|
+ if (done) return;
|
|
|
+ done = true;
|
|
|
+ document.removeEventListener('visibilitychange', onVis);
|
|
|
+ window.removeEventListener('pagehide', onHide);
|
|
|
+ }
|
|
|
+ function onVis() {
|
|
|
+ if (document.visibilityState === 'hidden') finish();
|
|
|
+ }
|
|
|
+ function onHide() {
|
|
|
+ finish();
|
|
|
+ }
|
|
|
+ document.addEventListener('visibilitychange', onVis);
|
|
|
+ window.addEventListener('pagehide', onHide);
|
|
|
+ try {
|
|
|
+ launchAppDeepLink(deepLink);
|
|
|
+ } catch (e3) {
|
|
|
+ finish();
|
|
|
+ showDownloadTip();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ window.setTimeout(function () {
|
|
|
+ if (done) return;
|
|
|
+ if (document.visibilityState === 'visible' && Date.now() - t0 < 3500) {
|
|
|
+ showDownloadTip();
|
|
|
+ }
|
|
|
+ finish();
|
|
|
+ }, 2600);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 有 plus:检测 App 是否安装后 openURL;深链形如 shopro://{APP_UNI_STORE_PATH}?…
|
|
|
- * 系统浏览器:唤起 scheme;微信内常拦截 scheme,需「在浏览器打开」。
|
|
|
+ * 系统浏览器:唤起 scheme;微信内走 getWxConfig + wx-open-launch-app。
|
|
|
*/
|
|
|
function tryOpenHBuilderApp() {
|
|
|
+ if (isWeChatInAppBrowser()) {
|
|
|
+ openAppViaWeChat();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
var deepLink = buildAppDeepLink();
|
|
|
|
|
|
if (typeof plus !== 'undefined' && plus.runtime) {
|
|
|
@@ -1145,10 +1422,6 @@
|
|
|
document.addEventListener('visibilitychange', onVis);
|
|
|
window.addEventListener('pagehide', onHide);
|
|
|
|
|
|
- if (isWeChatInAppBrowser()) {
|
|
|
- window.alert('若点击后无法打开 App:请先点右上角「···」,选择「在浏览器中打开」,再点「APP内打开」。');
|
|
|
- }
|
|
|
-
|
|
|
try {
|
|
|
launchAppDeepLink(deepLink);
|
|
|
} catch (e3) {
|
|
|
@@ -2312,12 +2585,25 @@
|
|
|
run();
|
|
|
bindMarketingMore();
|
|
|
bindStaffSection();
|
|
|
+ if (isWeChatInAppBrowser()) {
|
|
|
+ prepareWeChatAppLaunch(false).catch(function (e) {
|
|
|
+ console.warn('[微信预加载 getWxConfig]', e);
|
|
|
+ });
|
|
|
+ }
|
|
|
var openBtn = document.getElementById('openApp');
|
|
|
if (openBtn) {
|
|
|
openBtn.addEventListener('click', function () {
|
|
|
tryOpenHBuilderApp();
|
|
|
});
|
|
|
}
|
|
|
+ var wxHost = document.getElementById('openAppWxLaunchHost');
|
|
|
+ if (wxHost) {
|
|
|
+ wxHost.addEventListener('click', function () {
|
|
|
+ if (!wxConfigReady) {
|
|
|
+ openAppViaWeChat();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (document.readyState === 'complete') {
|