zhuli 1 неделя назад
Родитель
Сommit
128b39d513
2 измененных файлов с 1296 добавлено и 67 удалено
  1. 658 34
      HBuilderProjects/shareCheckInUndefined.html
  2. 638 33
      HBuilderProjects/shareUndefined.html

+ 658 - 34
HBuilderProjects/shareCheckInUndefined.html

@@ -185,6 +185,62 @@
 			pointer-events: none;
 		}
 
+		.fab-dock__slot {
+			width: 100%;
+			max-width: 320px;
+			height: 48px;
+			margin: 0 auto;
+			pointer-events: auto;
+		}
+
+		#openApp img {
+			display: block;
+			width: 100%;
+			height: 48px;
+			object-fit: contain;
+			pointer-events: none;
+		}
+
+		#launch-btn {
+			display: none;
+			width: 100%;
+			height: 48px;
+			min-height: 48px;
+			border-radius: 24px;
+			overflow: hidden;
+			opacity: 1;
+		}
+
+		body.is-wechat.wx-jssdk-ready #launch-btn {
+			display: block;
+		}
+
+		body.is-wechat.wx-jssdk-ready #openApp {
+			display: none !important;
+		}
+
+		#openAppToast {
+			display: none;
+			position: fixed;
+			left: 16px;
+			right: 16px;
+			bottom: calc(72px + var(--safe-bottom));
+			z-index: 10001;
+			padding: 10px 14px;
+			font-size: 13px;
+			line-height: 1.45;
+			color: #fff;
+			text-align: center;
+			background: rgba(0, 0, 0, 0.78);
+			border-radius: 8px;
+			pointer-events: none;
+			word-break: break-all;
+		}
+
+		#openApp {
+			touch-action: manipulation;
+		}
+
 		.fab-wrap .fab {
 			pointer-events: auto;
 		}
@@ -236,13 +292,51 @@
 		<p id="recEmpty" style="padding:12px;color:#999;font-size:14px;display:none;">暂无推荐</p>
 	</div>
 
+	<div id="openAppToast" role="status" aria-live="polite"></div>
 	<div class="fab-wrap">
-		<button type="button" class="fab" id="openApp">
-			<img src="images/uBtn.png" alt="APP内打开" decoding="async">
-		</button>
+		<div class="fab-dock__slot">
+			<!-- 非微信 / 微信 JSSDK 未就绪:scheme 唤起 -->
+			<button type="button" class="fab" id="openApp">
+				<img src="images/uBtn.png" alt="APP内打开" decoding="async">
+			</button>
+			<!-- 微信内 wx.config 成功后:仅此按钮可唤起 App(须用户直接点击) -->
+			<wx-open-launch-app id="launch-btn" appid="wxf5f1efe3a9f5012e" extinfo="">
+				<script type="text/wxtag-template">
+					<style>
+						.wx-open-app-btn {
+							display: block;
+							width: 100%;
+							height: 48px;
+							margin: 0;
+							padding: 0;
+							border: none;
+							border-radius: 24px;
+							background: #F47D1F;
+							box-shadow: 0 4px 16px rgba(245, 130, 32, 0.45);
+							cursor: pointer;
+							overflow: hidden;
+							-webkit-tap-highlight-color: transparent;
+						}
+						.wx-open-app-btn img {
+							display: block;
+							width: 100%;
+							height: 48px;
+							object-fit: contain;
+							opacity: 1;
+							pointer-events: none;
+							-webkit-user-drag: none;
+						}
+					</style>
+					<button class="wx-open-app-btn" aria-label="APP内打开">
+						<img src="https://test.ailien.shop/h5/HBuilderProjects/images/uBtn.png" alt="APP内打开" width="320" height="48" />
+					</button>
+				</script>
+			</wx-open-launch-app>
+		</div>
 		<div class="home-indicator" aria-hidden="true"></div>
 	</div>
 
+	<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
 	<script>
 	(function () {
 		'use strict';
@@ -253,6 +347,7 @@
 		 *
 		 * businessStatus=99(关店,与 shareIndex.html 一致):点「APP内打开」深链为 shopro://pages/index/login?…,不再进打卡页。
 		 */
+		var API_BASE = 'https://test.ailien.shop/alienStore';
 		var API_LIFE_AI_BASE = 'http://183.252.196.135:9100';
 		var STORE_GLOBAL_RECOMMEND_PATH =
 			'/ai/multimodal-services/api/v1/search/global/store-recommend';
@@ -261,15 +356,27 @@
 		var DEFAULT_USER_CITY = '大连市';
 		var DEFAULT_STORE_ID = '378';
 
+		/**
+		 * 微信 JSSDK — 与 shareIndex.html 一致
+		 * POST {API_BASE}/wx/getWxConfig,body 传 url(当前页完整地址,不含 #)
+		 */
+		var WECHAT_MP_APP_ID = 'wx412792c77f47babd';
+		var WECHAT_OPEN_APP_ID = 'wxf5f1efe3a9f5012e';
+		var WECHAT_GET_WX_CONFIG_PATH = '/wx/getWxConfig';
+		var H5_PAGE_BASE_FALLBACK = 'https://test.ailien.shop/h5/HBuilderProjects/';
+		var WX_GET_CONFIG_SIGN_URL = H5_PAGE_BASE_FALLBACK + 'shareCheckInUndefined.html';
+		var WECHAT_JS_SAFE_HOSTS = ['uat.ailien.shop', 'test.ailien.shop'];
+		var weChatJssdkConfigured = false;
+		var wxConfigSignRetriedBaseUrl = false;
+		var wxInitLastError = '';
+		var wxJssdkInitPromise = null;
+
 		var APP_ANDROID_PACKAGE = 'com.alien.Udianzaizhe';
 		var APP_IOS_URL_SCHEME = 'shopro://';
 		var APP_UNI_STORE_PATH = 'pages/checkIn/index';
 
-		function qs() {
-			return new URLSearchParams(location.search || '');
-		}
 		function q(name) {
-			var v = qs().get(name);
+			var v = mergeSearchAndHashParams().get(name);
 			return v == null ? '' : String(v);
 		}
 
@@ -284,24 +391,46 @@
 
 		function mergeSearchAndHashParams() {
 			var params = new URLSearchParams();
-			function ingest(querySlice) {
+			function ingestAppend(querySlice) {
 				if (!querySlice) return;
 				var p = new URLSearchParams(querySlice);
 				p.forEach(function (val, key) {
 					params.append(key, val);
 				});
 			}
+			function ingestSet(querySlice) {
+				if (!querySlice) return;
+				var p = new URLSearchParams(querySlice);
+				p.forEach(function (val, key) {
+					params.set(key, val);
+				});
+			}
 			if (location.search && location.search.length > 1) {
-				ingest(location.search.slice(1));
+				ingestAppend(location.search.slice(1));
 			}
 			var hash = location.hash || '';
 			var qi = hash.indexOf('?');
 			if (qi >= 0) {
-				ingest(hash.slice(qi + 1));
+				ingestSet(hash.slice(qi + 1));
 			}
 			return params;
 		}
 
+		function getRawMergedQueryForAppOpen() {
+			var rawSearch =
+				location.search && location.search.length > 1 ? location.search.slice(1) : '';
+			var hash0 = location.hash || '';
+			var hqi0 = hash0.indexOf('?');
+			var rawHashQ = hqi0 >= 0 ? hash0.slice(hqi0 + 1) : '';
+
+			if (!rawSearch && !rawHashQ) return '';
+
+			if (rawHashQ && rawSearch) {
+				return rawHashQ;
+			}
+			return rawHashQ || rawSearch;
+		}
+
 		/**
 		 * 关店 businessStatus=99:URL(含 hash)带 businessStatus=99 时,唤起 App 进 pages/index/login(与 shareIndex.html isClosedMerchantForAppOpen)。
 		 */
@@ -310,22 +439,28 @@
 				var bs = String(mergeSearchAndHashParams().get('businessStatus') || '').trim();
 				if (bs === '99' || Number(bs) === 99) return true;
 			} catch (e0) {}
-			var bs2 = String(q('businessStatus') || '').trim();
-			return bs2 === '99' || Number(bs2) === 99;
+			return false;
 		}
 
 		function getAppUniPathForCheckInShare() {
 			if (isClosedMerchantForAppOpen()) {
 				return 'pages/index/login';
 			}
-			return String(APP_UNI_STORE_PATH || 'pages/checkIn/index').replace(/^\//, '');
+			var p = mergeSearchAndHashParams();
+			if (!p.has('isShareCheckInSquare')) {
+				return String(APP_UNI_STORE_PATH || 'pages/checkIn/index').replace(/^\//, '');
+			}
+			var v = String(p.get('isShareCheckInSquare') || '').trim().toLowerCase();
+			if (v === '' || v === '0' || v === 'false' || v === 'no') {
+				return String(APP_UNI_STORE_PATH || 'pages/checkIn/index').replace(/^\//, '');
+			}
+			return 'pages/checkIn/details';
 		}
 
 		/** businessStatus=99(商户关闭):插图与文案;否则为「内容已删除」 */
 		function applyEmptyHeroState() {
 			var merged = mergeSearchAndHashParams();
 			var bsRaw = merged.get('businessStatus');
-			if (bsRaw == null || bsRaw === '') bsRaw = q('businessStatus');
 			var bs = bsRaw == null ? '' : String(bsRaw).trim();
 			var isClosed = Number(bs) === 99 || bs === '99';
 			var img = document.getElementById('heroEmptyIllustration');
@@ -344,15 +479,61 @@
 				this.onerror = null;
 				this.src = 'images/empty.png';
 			};
+			if (weChatJssdkConfigured) {
+				refreshWxLaunchTagAttrs();
+			}
 		}
 
 		function buildAppOpenQueryStringMerged() {
+			var rawQs = getRawMergedQueryForAppOpen();
+			if (rawQs) {
+				return '?' + rawQs;
+			}
 			var params = mergeSearchAndHashParams();
 			var sid = params.get('storeId') || params.get('id') || '';
 			if (sid && !params.has('storeId')) {
 				params.set('storeId', sid);
 			}
-			return params.toString() ? ('?' + params.toString()) : '';
+			var qsOut = params.toString();
+			return qsOut ? ('?' + qsOut) : '';
+		}
+
+		function buildAppUniPageLaunchUrl() {
+			var path = getAppUniPathForCheckInShare().replace(/^\//, '');
+			var qs = buildAppOpenQueryStringMerged().replace(/^\?/, '');
+			return qs ? path + '?' + qs : path;
+		}
+
+		/**
+		 * wx-open-launch-app extinfo:pages/checkIn/index、pages/checkIn/details 或关店 pages/index/login
+		 */
+		function buildWeChatLaunchExtinfo() {
+			var uniPage = buildAppUniPageLaunchUrl();
+			if (uniPage.length <= 1024) return uniPage;
+
+			var path = getAppUniPathForCheckInShare().replace(/^\//, '');
+			var rawQs = getRawMergedQueryForAppOpen();
+			if (rawQs) {
+				var packedRaw = path + '?' + rawQs;
+				if (packedRaw.length <= 1024) return packedRaw;
+			}
+
+			var params = mergeSearchAndHashParams();
+			var mini = new URLSearchParams();
+			var storeId = params.get('storeId') || params.get('id') || '';
+			if (storeId) mini.set('storeId', storeId);
+			var square = params.get('isShareCheckInSquare');
+			if (square != null && String(square).trim() !== '') {
+				mini.set('isShareCheckInSquare', String(square).trim());
+			}
+			var bs = params.get('businessStatus');
+			if (bs != null && String(bs).trim() !== '') {
+				mini.set('businessStatus', String(bs).trim());
+			}
+			var shortPage = path + '?' + mini.toString();
+			if (shortPage.length <= 1024) return shortPage;
+			var deep = buildAppDeepLink().replace(/^shopro:\/\//i, '');
+			return deep.length <= 1024 ? deep : shortPage.slice(0, 1024);
 		}
 
 		function buildAppDeepLink() {
@@ -365,6 +546,414 @@
 			return root + '/' + path + s;
 		}
 
+		function isWeChatInAppBrowser() {
+			return /MicroMessenger/i.test(navigator.userAgent || '');
+		}
+
+		function readQueryParam(name) {
+			return q(name);
+		}
+
+		function isWxDebugOn() {
+			return readQueryParam('wxDebug') === '1';
+		}
+
+		function isWxForceDebug() {
+			return readQueryParam('wxForce') === '1';
+		}
+
+		function isWxConfigOnClickDebug() {
+			return readQueryParam('wxConfigOnClick') === '1';
+		}
+
+		function isWxPcAutoDebugHost() {
+			var h = (location.hostname || '').toLowerCase();
+			if (h === 'localhost' || h === '127.0.0.1') return true;
+			for (var i = 0; i < WECHAT_JS_SAFE_HOSTS.length; i++) {
+				if (WECHAT_JS_SAFE_HOSTS[i] === h) return true;
+			}
+			return false;
+		}
+
+		function isWxPcBrowser() {
+			return !isWeChatInAppBrowser();
+		}
+
+		function shouldInitWeChatJssdkOnLoad() {
+			if (isWeChatInAppBrowser()) return true;
+			return isWxForceDebug() || isWxPcAutoDebugHost();
+		}
+
+		function shouldFetchWxConfig(fromUserClick) {
+			if (isWeChatInAppBrowser()) return true;
+			if (fromUserClick) return true;
+			return isWxForceDebug() || isWxPcAutoDebugHost();
+		}
+
+		function getWxHtmlUrl() {
+			var forced = String(q('wxSignUrl') || '').trim();
+			if (forced) return forced.split('#')[0];
+			return String(location.href || '').split('#')[0];
+		}
+
+		function getWxSignPageUrlForApi() {
+			var htmlUrl = getWxHtmlUrl();
+			if (htmlUrl && /^https?:\/\//i.test(htmlUrl)) return htmlUrl;
+			return WX_GET_CONFIG_SIGN_URL;
+		}
+
+		function getWxHtmlUrlBase() {
+			var htmlUrl = getWxSignPageUrlForApi();
+			try {
+				var u = new URL(htmlUrl);
+				return u.origin + u.pathname;
+			} catch (eU) {
+				return WX_GET_CONFIG_SIGN_URL;
+			}
+		}
+
+		function getWxConfigSignUrl() {
+			if (String(q('wxSignBaseOnly') || '') === '1') return getWxHtmlUrlBase();
+			return getWxSignPageUrlForApi();
+		}
+
+		function getWxGetConfigApiUrl() {
+			return API_BASE.replace(/\/$/, '') + WECHAT_GET_WX_CONFIG_PATH;
+		}
+
+		function buildWxGetConfigRequestBody(htmlUrl) {
+			return {
+				url: String(htmlUrl || '').split('#')[0].trim()
+			};
+		}
+
+		function resolveWxConfigAppIdFromSignData(d) {
+			if (!d || typeof d !== 'object') return '';
+			var mp =
+				d.mpAppId ||
+				d.mpAppid ||
+				d.officialAppId ||
+				d.gzhAppId ||
+				d.serviceAppId;
+			if (mp != null && String(mp).trim() !== '') return String(mp).trim();
+			var fromQuery = String(q('wxMpAppId') || WECHAT_MP_APP_ID || '').trim();
+			if (fromQuery) return fromQuery;
+			var raw = d.appId || d.appid || d.wxAppId;
+			return raw != null && String(raw).trim() !== '' ? String(raw).trim() : '';
+		}
+
+		function normalizeWxJssdkSignPayload(res, signUrlUsed) {
+			if (!res || typeof res !== 'object') return null;
+			var d = res.data != null && typeof res.data === 'object' ? res.data : res;
+			if (!d || typeof d !== 'object') return null;
+			var appId = resolveWxConfigAppIdFromSignData(d);
+			var timestamp = d.timestamp != null ? d.timestamp : d.timeStamp;
+			var nonceStr =
+				d.nonceStr != null && String(d.nonceStr) !== ''
+					? d.nonceStr
+					: d.noncestr != null && String(d.noncestr) !== ''
+						? d.noncestr
+						: d.nonce;
+			var signature = d.signature || d.sign;
+			if (!appId || timestamp == null || nonceStr == null || nonceStr === '' || !signature) {
+				return null;
+			}
+			return {
+				appId: String(appId),
+				timestamp: Number(timestamp),
+				nonceStr: String(nonceStr),
+				signature: String(signature),
+				signUrl: String(signUrlUsed || '')
+					.split('#')[0]
+					.trim()
+			};
+		}
+
+		function getWxErrMsg(err) {
+			if (!err) return '';
+			if (err.errMsg) return String(err.errMsg);
+			if (typeof err === 'string') return err;
+			try {
+				return JSON.stringify(err);
+			} catch (e) {
+				return String(err);
+			}
+		}
+
+		function formatWxConfigErrorTip(err, signPageUrl) {
+			var errMsg = getWxErrMsg(err);
+			var tip = '微信 JSSDK 配置失败';
+			if (/invalid signature/i.test(errMsg)) {
+				return (
+					tip +
+					':签名无效。请核对:①后端用与前端相同的 url 签名;②url=' +
+					(signPageUrl || getWxHtmlUrl()) +
+					';③nonceStr/timestamp 与接口返回一致;④appId=' +
+					WECHAT_MP_APP_ID
+				);
+			}
+			if (/require\s*subscribe/i.test(errMsg)) {
+				return tip + ':' + errMsg + '(服务号需用户已关注)';
+			}
+			return errMsg ? tip + ':' + errMsg : tip;
+		}
+
+		function setWxInitError(msg) {
+			wxInitLastError = String(msg || '').trim();
+			if (wxInitLastError) console.warn('[wx]', wxInitLastError);
+		}
+
+		function requestWeChatJssdkSignAndConfig(htmlUrlOptional) {
+			var htmlUrl = String(
+				htmlUrlOptional != null ? htmlUrlOptional : getWxConfigSignUrl()
+			)
+				.split('#')[0]
+				.trim();
+			if (!htmlUrl || !/^https?:\/\//i.test(htmlUrl)) {
+				htmlUrl = WX_GET_CONFIG_SIGN_URL;
+			}
+			if (isWxDebugOn()) {
+				try {
+					window.alert('htmlUrl(签名用):\n' + htmlUrl);
+				} catch (eDbg) {}
+			}
+			var requestUrl = getWxGetConfigApiUrl();
+			var requestBody = buildWxGetConfigRequestBody(htmlUrl);
+			console.log('[wx] htmlUrl=', htmlUrl);
+			console.log('[wx] POST getWxConfig →', requestUrl, requestBody);
+			return fetch(requestUrl, {
+				method: 'POST',
+				mode: 'cors',
+				credentials: 'omit',
+				headers: {
+					Accept: 'application/json',
+					'Content-Type': 'application/json;charset=UTF-8'
+				},
+				body: JSON.stringify(requestBody)
+			})
+				.then(function (r) {
+					if (r.ok) return r.json();
+					return r
+						.text()
+						.catch(function () {
+							return '';
+						})
+						.then(function (text) {
+							var hint = '';
+							try {
+								var j = JSON.parse(text);
+								hint = j.msg || j.message || '';
+							} catch (eP) {
+								if (text) hint = text.slice(0, 120);
+							}
+							throw new Error('getWxConfig HTTP ' + r.status + (hint ? ':' + hint : ''));
+						});
+				})
+				.then(function (res) {
+					if (res && res.code != null) {
+						var c = Number(res.code);
+						if (c !== 200 && c !== 0 && res.success !== true) {
+							throw new Error(res.msg || res.message || 'getWxConfig code ' + c);
+						}
+					}
+					var sign = normalizeWxJssdkSignPayload(res, htmlUrl);
+					if (!sign) {
+						console.warn('[wx] getWxConfig 响应字段不全', res, 'htmlUrl=', htmlUrl);
+						throw new Error(
+							'getWxConfig 缺少 appId/timestamp/nonceStr/signature(见控制台)'
+						);
+					}
+					if (sign.appId !== WECHAT_MP_APP_ID) {
+						console.warn(
+							'[wx] 后端 appId=' + sign.appId + ',期望服务号 ' + WECHAT_MP_APP_ID
+						);
+					}
+					if (typeof wx === 'undefined') {
+						if (isWxPcBrowser()) {
+							console.log('[wx] PC getWxConfig 成功(无 jweixin)', sign);
+							return true;
+						}
+						setWxInitError('jweixin.js 未加载');
+						return false;
+					}
+					return applyWxConfigFromSign(sign, htmlUrl);
+				});
+		}
+
+		function applyWxConfigFromSign(sign, htmlUrl) {
+			var wxConfigParams = {
+				debug: isWxDebugOn(),
+				appId: String(sign.appId),
+				timestamp: sign.timestamp,
+				nonceStr: String(sign.nonceStr),
+				signature: String(sign.signature),
+				jsApiList: [],
+				openTagList: ['wx-open-launch-app']
+			};
+			return new Promise(function (resolve) {
+				wx.config(wxConfigParams);
+				wx.ready(function () {
+					weChatJssdkConfigured = true;
+					document.body.classList.add('wx-jssdk-ready');
+					refreshWxLaunchTagAttrs();
+					console.log('[wx.config] ready, htmlUrl=', htmlUrl);
+					resolve(true);
+				});
+				wx.error(function (err) {
+					weChatJssdkConfigured = false;
+					document.body.classList.remove('wx-jssdk-ready');
+					wxJssdkInitPromise = null;
+					var errMsg = getWxErrMsg(err);
+					setWxInitError(formatWxConfigErrorTip(err, htmlUrl));
+					if (isWxDebugOn()) window.alert(wxInitLastError);
+					console.warn('[wx.config]', errMsg, 'htmlUrl=', htmlUrl);
+					var baseUrl = getWxHtmlUrlBase();
+					var fullUrl = getWxSignPageUrlForApi();
+					if (
+						!/invalid signature/i.test(errMsg) ||
+						wxConfigSignRetriedBaseUrl ||
+						baseUrl === fullUrl ||
+						htmlUrl === baseUrl
+					) {
+						resolve(false);
+						return;
+					}
+					wxConfigSignRetriedBaseUrl = true;
+					requestWeChatJssdkSignAndConfig(baseUrl).then(resolve);
+				});
+			});
+		}
+
+		function refreshWxLaunchTagAttrs() {
+			var tag = document.getElementById('launch-btn');
+			if (!tag) return;
+			try {
+				tag.setAttribute('appid', WECHAT_OPEN_APP_ID);
+				tag.setAttribute('extinfo', buildWeChatLaunchExtinfo());
+			} catch (eA) {}
+		}
+
+		function bindWeChatLaunchTagEvents() {
+			var tag = document.getElementById('launch-btn');
+			if (!tag || tag._wxLaunchBound) return;
+			tag._wxLaunchBound = true;
+			refreshWxLaunchTagAttrs();
+			tag.addEventListener('launch', function () {
+				console.log('[wx-open-launch-app] launch ok');
+				showFabToast('正在打开 U店在哪…');
+			});
+			tag.addEventListener('error', function (e) {
+				var detail = e && e.detail;
+				var errMsg =
+					detail && detail.errMsg
+						? String(detail.errMsg)
+						: detail && detail.errmsg
+							? String(detail.errmsg)
+							: '';
+				console.warn('[wx-open-launch-app]', detail);
+				showAppOpenFailTip(
+					errMsg
+						? '未能唤起 App:' + errMsg
+						: '未能唤起 App,请确认已安装 U店在哪,且开放平台已绑定服务号与移动应用。'
+				);
+			});
+		}
+
+		function initWeChatOpenLaunchApp(fromUserClick) {
+			if (!shouldFetchWxConfig(!!fromUserClick)) {
+				console.log('[wx] 未调用 getWxConfig');
+				return Promise.resolve(false);
+			}
+			if (isWxPcBrowser()) {
+				console.warn(
+					'[wx] PC:请求 getWxConfig' + (fromUserClick ? '(按钮)' : '(进页)')
+				);
+			}
+			if (wxJssdkInitPromise && !fromUserClick) return wxJssdkInitPromise;
+			wxConfigSignRetriedBaseUrl = false;
+			wxInitLastError = '';
+			var htmlUrl = getWxConfigSignUrl();
+			bindWeChatLaunchTagEvents();
+			wxJssdkInitPromise = requestWeChatJssdkSignAndConfig(htmlUrl)
+				.then(function (ok) {
+					if (ok === true) return true;
+					if (!wxInitLastError) {
+						setWxInitError('wx.config 失败,可加 ?wxDebug=1 查看 htmlUrl');
+					}
+					return false;
+				})
+				.catch(function (e) {
+					var msg = e && e.message ? e.message : 'getWxConfig 请求失败';
+					setWxInitError(msg);
+					console.warn('[wx] requestWeChatJssdkSignAndConfig failed', msg, 'htmlUrl=', htmlUrl);
+					if (isWeChatInAppBrowser() && isWxDebugOn()) window.alert(msg);
+					return false;
+				})
+				.finally(function () {
+					if (!weChatJssdkConfigured) wxJssdkInitPromise = null;
+				});
+			return wxJssdkInitPromise;
+		}
+
+		/** 进页即尝试拉签名;jweixin 晚到时自动重试 */
+		function scheduleWeChatJssdkBootstrap() {
+			if (!shouldInitWeChatJssdkOnLoad()) return;
+			var attempts = 0;
+			function tick() {
+				attempts += 1;
+				if (weChatJssdkConfigured) return;
+				initWeChatOpenLaunchApp();
+				if (!weChatJssdkConfigured && attempts < 8 && typeof wx === 'undefined') {
+					setTimeout(tick, 400);
+				}
+			}
+			tick();
+		}
+
+		function showFabToast(msg, ms) {
+			var tip = String(msg || '').trim();
+			if (!tip) return;
+			var el = document.getElementById('openAppToast');
+			if (el) {
+				el.textContent = tip;
+				el.style.display = 'block';
+				if (showFabToast._t) clearTimeout(showFabToast._t);
+				showFabToast._t = setTimeout(function () {
+					el.style.display = 'none';
+				}, ms || 2800);
+			}
+			console.log('[openApp]', tip);
+		}
+
+		function showAppOpenFailTip(msg) {
+			var tip = msg || '未能打开 App,请确认已安装最新版「U店在哪」。';
+			if (typeof uni !== 'undefined' && typeof uni.showToast === 'function') {
+				uni.showToast({ title: tip, icon: 'none', duration: 2800 });
+			} else if (isWeChatInAppBrowser()) {
+				window.alert(tip);
+			} else {
+				console.warn('[openApp]', tip);
+			}
+		}
+
+		function tryFetchWxConfigOnPcClick() {
+			if (!isWxPcBrowser()) return Promise.resolve(false);
+			showFabToast('正在请求 getWxConfig…');
+			wxJssdkInitPromise = null;
+			return initWeChatOpenLaunchApp(true).then(function (ok) {
+				if (ok) {
+					showFabToast(
+						weChatJssdkConfigured
+							? 'getWxConfig 成功,wx.config 已就绪'
+							: 'getWxConfig 成功'
+					);
+				} else {
+					showFabToast(wxInitLastError || 'getWxConfig 失败');
+				}
+				return ok;
+			});
+		}
+
 		function launchAppDeepLink(deepLink) {
 			try {
 				var a = document.createElement('a');
@@ -380,6 +969,19 @@
 		}
 
 		function tryOpenHBuilderApp() {
+			/* 微信内须直接点击 wx-open-launch-app,scheme 会被拦截 */
+			if (isWeChatInAppBrowser()) return;
+
+			tryFetchWxConfigOnPcClick().then(function () {
+				if (isWxConfigOnClickDebug() && !isWxForceDebug() && !isWxPcAutoDebugHost()) {
+					return;
+				}
+				tryOpenHBuilderAppViaScheme();
+			});
+		}
+
+		function tryOpenHBuilderAppViaScheme() {
+			showFabToast('正在打开 U店在哪…');
 			var deepLink = buildAppDeepLink();
 
 			if (typeof plus !== 'undefined' && plus.runtime) {
@@ -398,18 +1000,15 @@
 					showDownloadTip();
 					return;
 				}
-				if (installed === true) {
-					try {
-						plus.runtime.openURL(deepLink);
-					} catch (e2) {
-						console.warn(e2);
-						showDownloadTip();
-					}
-					return;
+				try {
+					plus.runtime.openURL(deepLink);
+				} catch (e2) {
+					console.warn(e2);
+					showDownloadTip();
 				}
+				return;
 			}
 
-			var t0 = Date.now();
 			var done = false;
 			function finish() {
 				if (done) return;
@@ -426,13 +1025,15 @@
 			document.addEventListener('visibilitychange', onVis);
 			window.addEventListener('pagehide', onHide);
 
-			launchAppDeepLink(deepLink);
+			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);
 		}
@@ -672,13 +1273,36 @@
 		}
 
 		function boot() {
+			var launchTag = document.getElementById('launch-btn');
+			if (launchTag) launchTag.setAttribute('appid', WECHAT_OPEN_APP_ID);
+			bindWeChatLaunchTagEvents();
+			if (shouldInitWeChatJssdkOnLoad()) {
+				if (isWeChatInAppBrowser()) document.body.classList.add('is-wechat');
+				if (isWxPcBrowser() && (isWxForceDebug() || isWxPcAutoDebugHost())) {
+					document.body.classList.add('wx-pc-debug');
+				}
+				scheduleWeChatJssdkBootstrap();
+			}
+
 			applyEmptyHeroState();
 			run();
-			var openBtn = document.getElementById('openApp');
-			if (openBtn) {
-				openBtn.addEventListener('click', function () {
-					tryOpenHBuilderApp();
-				});
+
+			if (isWeChatInAppBrowser()) {
+				var openBtnWx = document.getElementById('openApp');
+				if (openBtnWx) {
+					openBtnWx.addEventListener('click', function () {
+						if (!weChatJssdkConfigured) {
+							showFabToast(
+								wxInitLastError || '微信 SDK 初始化中,请稍候再点底部按钮'
+							);
+						}
+					});
+				}
+			} else {
+				var openBtn = document.getElementById('openApp');
+				if (openBtn) {
+					openBtn.addEventListener('click', tryOpenHBuilderApp);
+				}
 			}
 		}
 

+ 638 - 33
HBuilderProjects/shareUndefined.html

@@ -431,6 +431,62 @@
 			pointer-events: none;
 		}
 
+		.fab-dock__slot {
+			width: 100%;
+			max-width: 320px;
+			height: 48px;
+			margin: 0 auto;
+			pointer-events: auto;
+		}
+
+		#openApp img {
+			display: block;
+			width: 100%;
+			height: 48px;
+			object-fit: contain;
+			pointer-events: none;
+		}
+
+		#launch-btn {
+			display: none;
+			width: 100%;
+			height: 48px;
+			min-height: 48px;
+			border-radius: 24px;
+			overflow: hidden;
+			opacity: 1;
+		}
+
+		body.is-wechat.wx-jssdk-ready #launch-btn {
+			display: block;
+		}
+
+		body.is-wechat.wx-jssdk-ready #openApp {
+			display: none !important;
+		}
+
+		#openAppToast {
+			display: none;
+			position: fixed;
+			left: 16px;
+			right: 16px;
+			bottom: calc(72px + var(--safe-bottom));
+			z-index: 10001;
+			padding: 10px 14px;
+			font-size: 13px;
+			line-height: 1.45;
+			color: #fff;
+			text-align: center;
+			background: rgba(0, 0, 0, 0.78);
+			border-radius: 8px;
+			pointer-events: none;
+			word-break: break-all;
+		}
+
+		#openApp {
+			touch-action: manipulation;
+		}
+
 		.fab-wrap .fab {
 			pointer-events: auto;
 		}
@@ -486,13 +542,51 @@
 		<p id="recEmpty" style="padding:12px;color:#999;font-size:14px;display:none;">暂无推荐</p>
 	</div>
 
+	<div id="openAppToast" role="status" aria-live="polite"></div>
 	<div class="fab-wrap">
-		<button type="button" class="fab" id="openApp">
-			<img src="images/ubtn.png" alt="APP内打开" decoding="async">
-		</button>
+		<div class="fab-dock__slot">
+			<!-- 非微信 / 微信 JSSDK 未就绪:scheme 唤起 -->
+			<button type="button" class="fab" id="openApp">
+				<img src="images/ubtn.png" alt="APP内打开" decoding="async">
+			</button>
+			<!-- 微信内 wx.config 成功后:仅此按钮可唤起 App(须用户直接点击) -->
+			<wx-open-launch-app id="launch-btn" appid="wxf5f1efe3a9f5012e" extinfo="">
+				<script type="text/wxtag-template">
+					<style>
+						.wx-open-app-btn {
+							display: block;
+							width: 100%;
+							height: 48px;
+							margin: 0;
+							padding: 0;
+							border: none;
+							border-radius: 24px;
+							background: #F47D1F;
+							box-shadow: 0 4px 16px rgba(245, 130, 32, 0.45);
+							cursor: pointer;
+							overflow: hidden;
+							-webkit-tap-highlight-color: transparent;
+						}
+						.wx-open-app-btn img {
+							display: block;
+							width: 100%;
+							height: 48px;
+							object-fit: contain;
+							opacity: 1;
+							pointer-events: none;
+							-webkit-user-drag: none;
+						}
+					</style>
+					<button class="wx-open-app-btn" aria-label="APP内打开">
+						<img src="https://test.ailien.shop/h5/HBuilderProjects/images/uBtn.png" alt="APP内打开" width="320" height="48" />
+					</button>
+				</script>
+			</wx-open-launch-app>
+		</div>
 		<div class="home-indicator" aria-hidden="true"></div>
 	</div>
 
+	<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
 	<script>
 	(function () {
 		'use strict';
@@ -513,6 +607,21 @@
 		var DEFAULT_REC_RADIUS_KM = 195.69;
 
 		/**
+		 * 微信 JSSDK — 与 shareIndex.html 一致
+		 * POST {API_BASE}/wx/getWxConfig,body 传 url(当前页完整地址,不含 #)
+		 */
+		var WECHAT_MP_APP_ID = 'wx412792c77f47babd';
+		var WECHAT_OPEN_APP_ID = 'wxf5f1efe3a9f5012e';
+		var WECHAT_GET_WX_CONFIG_PATH = '/wx/getWxConfig';
+		var H5_PAGE_BASE_FALLBACK = 'https://test.ailien.shop/h5/HBuilderProjects/';
+		var WX_GET_CONFIG_SIGN_URL = H5_PAGE_BASE_FALLBACK + 'shareUndefined.html';
+		var WECHAT_JS_SAFE_HOSTS = ['uat.ailien.shop', 'test.ailien.shop'];
+		var weChatJssdkConfigured = false;
+		var wxConfigSignRetriedBaseUrl = false;
+		var wxInitLastError = '';
+		var wxJssdkInitPromise = null;
+
+		/**
 		 * 与 secondShareGoods.html 一致:唤起 App 打开二手商品详情页(合并 search + hash 内 query)
 		 */
 		var APP_ANDROID_PACKAGE = 'com.alien.Udianzaizhe';
@@ -530,36 +639,77 @@
 
 		function mergeSearchAndHashParams() {
 			var params = new URLSearchParams();
-			function ingest(querySlice) {
+			function ingestAppend(querySlice) {
 				if (!querySlice) return;
 				var p = new URLSearchParams(querySlice);
 				p.forEach(function (val, key) {
 					params.append(key, val);
 				});
 			}
+			function ingestSet(querySlice) {
+				if (!querySlice) return;
+				var p = new URLSearchParams(querySlice);
+				p.forEach(function (val, key) {
+					params.set(key, val);
+				});
+			}
 			if (location.search && location.search.length > 1) {
-				ingest(location.search.slice(1));
+				ingestAppend(location.search.slice(1));
 			}
 			var hash = location.hash || '';
 			var qi = hash.indexOf('?');
 			if (qi >= 0) {
-				ingest(hash.slice(qi + 1));
+				ingestSet(hash.slice(qi + 1));
 			}
 			return params;
 		}
 
+		function q(name) {
+			var v = mergeSearchAndHashParams().get(name);
+			return v == null ? '' : String(v);
+		}
+
+		function rawQueryHasGoodsId(qs) {
+			return /(?:^|&)(goodsId|id)=/i.test('&' + String(qs || '') + '&');
+		}
+
+		function getRawMergedQueryForAppOpen() {
+			var rawSearch =
+				location.search && location.search.length > 1 ? location.search.slice(1) : '';
+			var hash0 = location.hash || '';
+			var hqi0 = hash0.indexOf('?');
+			var rawHashQ = hqi0 >= 0 ? hash0.slice(hqi0 + 1) : '';
+
+			if (!rawSearch && !rawHashQ) return '';
+
+			if (rawHashQ && rawSearch) {
+				if (rawQueryHasGoodsId(rawHashQ)) return rawHashQ;
+				if (rawQueryHasGoodsId(rawSearch)) return rawSearch;
+				return rawHashQ;
+			}
+			return rawHashQ || rawSearch;
+		}
+
 		/** 本 HTML 仅为不可用商品落地页,唤起路径固定为登录(不依赖 URL 是否带 goodsUnavailable,避免旧链/丢参) */
 		function getAppUniPathForShareUndefined() {
 			return 'pages/index/login';
 		}
 
 		function buildAppOpenQueryStringMerged() {
+			var rawQs = getRawMergedQueryForAppOpen();
+			if (rawQs) {
+				return '?' + rawQs;
+			}
+
 			var params = mergeSearchAndHashParams();
 			var gid = params.get('goodsId') || params.get('id') || '';
 			if (gid && !params.has('goodsId')) {
 				params.set('goodsId', gid);
 			}
-			var sid = params.get('storeId') || params.get('id') || '';
+			if (gid && !params.has('id')) {
+				params.set('id', gid);
+			}
+			var sid = params.get('storeId') || '';
 			if (sid && !params.has('storeId')) {
 				params.set('storeId', sid);
 			}
@@ -567,6 +717,42 @@
 			return qsOut ? ('?' + qsOut) : '';
 		}
 
+		/** Uni 路由串(无 shopro://),供 wx-open-launch-app extinfo */
+		function buildAppUniPageLaunchUrl() {
+			var path = getAppUniPathForShareUndefined().replace(/^\//, '');
+			var qs = buildAppOpenQueryStringMerged().replace(/^\?/, '');
+			return qs ? path + '?' + qs : path;
+		}
+
+		/**
+		 * wx-open-launch-app extinfo:pages/index/login?…(与 shareIndex 关店唤起一致)
+		 */
+		function buildWeChatLaunchExtinfo() {
+			var uniPage = buildAppUniPageLaunchUrl();
+			if (uniPage.length <= 1024) return uniPage;
+
+			var path = getAppUniPathForShareUndefined().replace(/^\//, '');
+			var rawQs = getRawMergedQueryForAppOpen();
+			if (rawQs) {
+				var packedRaw = path + '?' + rawQs;
+				if (packedRaw.length <= 1024) return packedRaw;
+			}
+
+			var params = mergeSearchAndHashParams();
+			var mini = new URLSearchParams();
+			var goodsId = params.get('goodsId') || params.get('id') || '';
+			if (goodsId) {
+				mini.set('goodsId', goodsId);
+				mini.set('id', goodsId);
+			}
+			var sid = params.get('storeId') || '';
+			if (sid) mini.set('storeId', sid);
+			var shortPage = path + '?' + mini.toString();
+			if (shortPage.length <= 1024) return shortPage;
+			var deep = buildAppDeepLink().replace(/^shopro:\/\//i, '');
+			return deep.length <= 1024 ? deep : shortPage.slice(0, 1024);
+		}
+
 		/** 供 App 内嵌 WebView:uni 路由到登录页,query 与深链一致 */
 		function buildUniLoginPageUrl() {
 			var tail = buildAppOpenQueryStringMerged();
@@ -588,6 +774,410 @@
 			return /MicroMessenger/i.test(navigator.userAgent || '');
 		}
 
+		function readQueryParam(name) {
+			return q(name);
+		}
+
+		function isWxDebugOn() {
+			return readQueryParam('wxDebug') === '1';
+		}
+
+		function isWxForceDebug() {
+			return readQueryParam('wxForce') === '1';
+		}
+
+		function isWxConfigOnClickDebug() {
+			return readQueryParam('wxConfigOnClick') === '1';
+		}
+
+		function isWxPcAutoDebugHost() {
+			var h = (location.hostname || '').toLowerCase();
+			if (h === 'localhost' || h === '127.0.0.1') return true;
+			for (var i = 0; i < WECHAT_JS_SAFE_HOSTS.length; i++) {
+				if (WECHAT_JS_SAFE_HOSTS[i] === h) return true;
+			}
+			return false;
+		}
+
+		function isWxPcBrowser() {
+			return !isWeChatInAppBrowser();
+		}
+
+		function shouldInitWeChatJssdkOnLoad() {
+			if (isWeChatInAppBrowser()) return true;
+			return isWxForceDebug() || isWxPcAutoDebugHost();
+		}
+
+		function shouldFetchWxConfig(fromUserClick) {
+			if (isWeChatInAppBrowser()) return true;
+			if (fromUserClick) return true;
+			return isWxForceDebug() || isWxPcAutoDebugHost();
+		}
+
+		function getWxHtmlUrl() {
+			var forced = String(q('wxSignUrl') || '').trim();
+			if (forced) return forced.split('#')[0];
+			return String(location.href || '').split('#')[0];
+		}
+
+		function getWxSignPageUrlForApi() {
+			var htmlUrl = getWxHtmlUrl();
+			if (htmlUrl && /^https?:\/\//i.test(htmlUrl)) return htmlUrl;
+			return WX_GET_CONFIG_SIGN_URL;
+		}
+
+		function getWxHtmlUrlBase() {
+			var htmlUrl = getWxSignPageUrlForApi();
+			try {
+				var u = new URL(htmlUrl);
+				return u.origin + u.pathname;
+			} catch (eU) {
+				return WX_GET_CONFIG_SIGN_URL;
+			}
+		}
+
+		function getWxConfigSignUrl() {
+			if (String(q('wxSignBaseOnly') || '') === '1') return getWxHtmlUrlBase();
+			return getWxSignPageUrlForApi();
+		}
+
+		function getWxGetConfigApiUrl() {
+			return API_BASE.replace(/\/$/, '') + WECHAT_GET_WX_CONFIG_PATH;
+		}
+
+		function buildWxGetConfigRequestBody(htmlUrl) {
+			return {
+				url: String(htmlUrl || '').split('#')[0].trim()
+			};
+		}
+
+		function resolveWxConfigAppIdFromSignData(d) {
+			if (!d || typeof d !== 'object') return '';
+			var mp =
+				d.mpAppId ||
+				d.mpAppid ||
+				d.officialAppId ||
+				d.gzhAppId ||
+				d.serviceAppId;
+			if (mp != null && String(mp).trim() !== '') return String(mp).trim();
+			var fromQuery = String(q('wxMpAppId') || WECHAT_MP_APP_ID || '').trim();
+			if (fromQuery) return fromQuery;
+			var raw = d.appId || d.appid || d.wxAppId;
+			return raw != null && String(raw).trim() !== '' ? String(raw).trim() : '';
+		}
+
+		function normalizeWxJssdkSignPayload(res, signUrlUsed) {
+			if (!res || typeof res !== 'object') return null;
+			var d = res.data != null && typeof res.data === 'object' ? res.data : res;
+			if (!d || typeof d !== 'object') return null;
+			var appId = resolveWxConfigAppIdFromSignData(d);
+			var timestamp = d.timestamp != null ? d.timestamp : d.timeStamp;
+			var nonceStr =
+				d.nonceStr != null && String(d.nonceStr) !== ''
+					? d.nonceStr
+					: d.noncestr != null && String(d.noncestr) !== ''
+						? d.noncestr
+						: d.nonce;
+			var signature = d.signature || d.sign;
+			if (!appId || timestamp == null || nonceStr == null || nonceStr === '' || !signature) {
+				return null;
+			}
+			return {
+				appId: String(appId),
+				timestamp: Number(timestamp),
+				nonceStr: String(nonceStr),
+				signature: String(signature),
+				signUrl: String(signUrlUsed || '')
+					.split('#')[0]
+					.trim()
+			};
+		}
+
+		function getWxErrMsg(err) {
+			if (!err) return '';
+			if (err.errMsg) return String(err.errMsg);
+			if (typeof err === 'string') return err;
+			try {
+				return JSON.stringify(err);
+			} catch (e) {
+				return String(err);
+			}
+		}
+
+		function formatWxConfigErrorTip(err, signPageUrl) {
+			var errMsg = getWxErrMsg(err);
+			var tip = '微信 JSSDK 配置失败';
+			if (/invalid signature/i.test(errMsg)) {
+				return (
+					tip +
+					':签名无效。请核对:①后端用与前端相同的 url 签名;②url=' +
+					(signPageUrl || getWxHtmlUrl()) +
+					';③nonceStr/timestamp 与接口返回一致;④appId=' +
+					WECHAT_MP_APP_ID
+				);
+			}
+			if (/require\s*subscribe/i.test(errMsg)) {
+				return tip + ':' + errMsg + '(服务号需用户已关注)';
+			}
+			return errMsg ? tip + ':' + errMsg : tip;
+		}
+
+		function setWxInitError(msg) {
+			wxInitLastError = String(msg || '').trim();
+			if (wxInitLastError) console.warn('[wx]', wxInitLastError);
+		}
+
+		function requestWeChatJssdkSignAndConfig(htmlUrlOptional) {
+			var htmlUrl = String(
+				htmlUrlOptional != null ? htmlUrlOptional : getWxConfigSignUrl()
+			)
+				.split('#')[0]
+				.trim();
+			if (!htmlUrl || !/^https?:\/\//i.test(htmlUrl)) {
+				htmlUrl = WX_GET_CONFIG_SIGN_URL;
+			}
+			if (isWxDebugOn()) {
+				try {
+					window.alert('htmlUrl(签名用):\n' + htmlUrl);
+				} catch (eDbg) {}
+			}
+			var requestUrl = getWxGetConfigApiUrl();
+			var requestBody = buildWxGetConfigRequestBody(htmlUrl);
+			console.log('[wx] htmlUrl=', htmlUrl);
+			console.log('[wx] POST getWxConfig →', requestUrl, requestBody);
+			return fetch(requestUrl, {
+				method: 'POST',
+				mode: 'cors',
+				credentials: 'omit',
+				headers: {
+					Accept: 'application/json',
+					'Content-Type': 'application/json;charset=UTF-8'
+				},
+				body: JSON.stringify(requestBody)
+			})
+				.then(function (r) {
+					if (r.ok) return r.json();
+					return r
+						.text()
+						.catch(function () {
+							return '';
+						})
+						.then(function (text) {
+							var hint = '';
+							try {
+								var j = JSON.parse(text);
+								hint = j.msg || j.message || '';
+							} catch (eP) {
+								if (text) hint = text.slice(0, 120);
+							}
+							throw new Error('getWxConfig HTTP ' + r.status + (hint ? ':' + hint : ''));
+						});
+				})
+				.then(function (res) {
+					if (res && res.code != null) {
+						var c = Number(res.code);
+						if (c !== 200 && c !== 0 && res.success !== true) {
+							throw new Error(res.msg || res.message || 'getWxConfig code ' + c);
+						}
+					}
+					var sign = normalizeWxJssdkSignPayload(res, htmlUrl);
+					if (!sign) {
+						console.warn('[wx] getWxConfig 响应字段不全', res, 'htmlUrl=', htmlUrl);
+						throw new Error(
+							'getWxConfig 缺少 appId/timestamp/nonceStr/signature(见控制台)'
+						);
+					}
+					if (sign.appId !== WECHAT_MP_APP_ID) {
+						console.warn(
+							'[wx] 后端 appId=' + sign.appId + ',期望服务号 ' + WECHAT_MP_APP_ID
+						);
+					}
+					if (typeof wx === 'undefined') {
+						if (isWxPcBrowser()) {
+							console.log('[wx] PC getWxConfig 成功(无 jweixin)', sign);
+							return true;
+						}
+						setWxInitError('jweixin.js 未加载');
+						return false;
+					}
+					return applyWxConfigFromSign(sign, htmlUrl);
+				});
+		}
+
+		function applyWxConfigFromSign(sign, htmlUrl) {
+			var wxConfigParams = {
+				debug: isWxDebugOn(),
+				appId: String(sign.appId),
+				timestamp: sign.timestamp,
+				nonceStr: String(sign.nonceStr),
+				signature: String(sign.signature),
+				jsApiList: [],
+				openTagList: ['wx-open-launch-app']
+			};
+			return new Promise(function (resolve) {
+				wx.config(wxConfigParams);
+				wx.ready(function () {
+					weChatJssdkConfigured = true;
+					document.body.classList.add('wx-jssdk-ready');
+					refreshWxLaunchTagAttrs();
+					console.log('[wx.config] ready, htmlUrl=', htmlUrl);
+					resolve(true);
+				});
+				wx.error(function (err) {
+					weChatJssdkConfigured = false;
+					document.body.classList.remove('wx-jssdk-ready');
+					wxJssdkInitPromise = null;
+					var errMsg = getWxErrMsg(err);
+					setWxInitError(formatWxConfigErrorTip(err, htmlUrl));
+					if (isWxDebugOn()) window.alert(wxInitLastError);
+					console.warn('[wx.config]', errMsg, 'htmlUrl=', htmlUrl);
+					var baseUrl = getWxHtmlUrlBase();
+					var fullUrl = getWxSignPageUrlForApi();
+					if (
+						!/invalid signature/i.test(errMsg) ||
+						wxConfigSignRetriedBaseUrl ||
+						baseUrl === fullUrl ||
+						htmlUrl === baseUrl
+					) {
+						resolve(false);
+						return;
+					}
+					wxConfigSignRetriedBaseUrl = true;
+					requestWeChatJssdkSignAndConfig(baseUrl).then(resolve);
+				});
+			});
+		}
+
+		function refreshWxLaunchTagAttrs() {
+			var tag = document.getElementById('launch-btn');
+			if (!tag) return;
+			try {
+				tag.setAttribute('appid', WECHAT_OPEN_APP_ID);
+				tag.setAttribute('extinfo', buildWeChatLaunchExtinfo());
+			} catch (eA) {}
+		}
+
+		function bindWeChatLaunchTagEvents() {
+			var tag = document.getElementById('launch-btn');
+			if (!tag || tag._wxLaunchBound) return;
+			tag._wxLaunchBound = true;
+			refreshWxLaunchTagAttrs();
+			tag.addEventListener('launch', function () {
+				console.log('[wx-open-launch-app] launch ok');
+				showFabToast('正在打开 U店在哪…');
+			});
+			tag.addEventListener('error', function (e) {
+				var detail = e && e.detail;
+				var errMsg =
+					detail && detail.errMsg
+						? String(detail.errMsg)
+						: detail && detail.errmsg
+							? String(detail.errmsg)
+							: '';
+				console.warn('[wx-open-launch-app]', detail);
+				showAppOpenFailTip(
+					errMsg
+						? '未能唤起 App:' + errMsg
+						: '未能唤起 App,请确认已安装 U店在哪,且开放平台已绑定服务号与移动应用。'
+				);
+			});
+		}
+
+		function initWeChatOpenLaunchApp(fromUserClick) {
+			if (!shouldFetchWxConfig(!!fromUserClick)) {
+				console.log('[wx] 未调用 getWxConfig');
+				return Promise.resolve(false);
+			}
+			if (isWxPcBrowser()) {
+				console.warn(
+					'[wx] PC:请求 getWxConfig' + (fromUserClick ? '(按钮)' : '(进页)')
+				);
+			}
+			if (wxJssdkInitPromise && !fromUserClick) return wxJssdkInitPromise;
+			wxConfigSignRetriedBaseUrl = false;
+			wxInitLastError = '';
+			var htmlUrl = getWxConfigSignUrl();
+			bindWeChatLaunchTagEvents();
+			wxJssdkInitPromise = requestWeChatJssdkSignAndConfig(htmlUrl)
+				.then(function (ok) {
+					if (ok === true) return true;
+					if (!wxInitLastError) {
+						setWxInitError('wx.config 失败,可加 ?wxDebug=1 查看 htmlUrl');
+					}
+					return false;
+				})
+				.catch(function (e) {
+					var msg = e && e.message ? e.message : 'getWxConfig 请求失败';
+					setWxInitError(msg);
+					console.warn('[wx] requestWeChatJssdkSignAndConfig failed', msg, 'htmlUrl=', htmlUrl);
+					if (isWeChatInAppBrowser() && isWxDebugOn()) window.alert(msg);
+					return false;
+				})
+				.finally(function () {
+					if (!weChatJssdkConfigured) wxJssdkInitPromise = null;
+				});
+			return wxJssdkInitPromise;
+		}
+
+		/** 进页即尝试拉签名;jweixin 晚到时自动重试 */
+		function scheduleWeChatJssdkBootstrap() {
+			if (!shouldInitWeChatJssdkOnLoad()) return;
+			var attempts = 0;
+			function tick() {
+				attempts += 1;
+				if (weChatJssdkConfigured) return;
+				initWeChatOpenLaunchApp();
+				if (!weChatJssdkConfigured && attempts < 8 && typeof wx === 'undefined') {
+					setTimeout(tick, 400);
+				}
+			}
+			tick();
+		}
+
+		function showFabToast(msg, ms) {
+			var tip = String(msg || '').trim();
+			if (!tip) return;
+			var el = document.getElementById('openAppToast');
+			if (el) {
+				el.textContent = tip;
+				el.style.display = 'block';
+				if (showFabToast._t) clearTimeout(showFabToast._t);
+				showFabToast._t = setTimeout(function () {
+					el.style.display = 'none';
+				}, ms || 2800);
+			}
+			console.log('[openApp]', tip);
+		}
+
+		function showAppOpenFailTip(msg) {
+			var tip = msg || '未能打开 App,请确认已安装最新版「U店在哪」。';
+			if (typeof uni !== 'undefined' && typeof uni.showToast === 'function') {
+				uni.showToast({ title: tip, icon: 'none', duration: 2800 });
+			} else if (isWeChatInAppBrowser()) {
+				window.alert(tip);
+			} else {
+				console.warn('[openApp]', tip);
+			}
+		}
+
+		function tryFetchWxConfigOnPcClick() {
+			if (!isWxPcBrowser()) return Promise.resolve(false);
+			showFabToast('正在请求 getWxConfig…');
+			wxJssdkInitPromise = null;
+			return initWeChatOpenLaunchApp(true).then(function (ok) {
+				if (ok) {
+					showFabToast(
+						weChatJssdkConfigured
+							? 'getWxConfig 成功,wx.config 已就绪'
+							: 'getWxConfig 成功'
+					);
+				} else {
+					showFabToast(wxInitLastError || 'getWxConfig 失败');
+				}
+				return ok;
+			});
+		}
+
 		function launchAppDeepLink(deepLink) {
 			try {
 				var a = document.createElement('a');
@@ -602,12 +1192,21 @@
 			} catch (e2) {}
 		}
 
-		/**
-		 * App-Plus:检测是否已安装;H5:scheme 唤起 + 超时提示(与 secondShareGoods 一致)。
-		 * App 内嵌 H5 若注入 uni.navigateTo,优先走页面路由进登录(scheme 在 WebView 内常无效)。
-		 */
 		function tryOpenUShopApp() {
+			/* 微信内须直接点击 wx-open-launch-app,scheme 会被拦截 */
+			if (isWeChatInAppBrowser()) return;
+
+			tryFetchWxConfigOnPcClick().then(function () {
+				if (isWxConfigOnClickDebug() && !isWxForceDebug() && !isWxPcAutoDebugHost()) {
+					return;
+				}
+				tryOpenUShopAppViaScheme();
+			});
+		}
+
+		function tryOpenUShopAppViaScheme() {
 			function openBySchemeOrPlus() {
+				showFabToast('正在打开 U店在哪…');
 				var deepLink = buildAppDeepLink();
 
 				if (typeof plus !== 'undefined' && plus.runtime) {
@@ -635,7 +1234,6 @@
 					return;
 				}
 
-				var t0 = Date.now();
 				var done = false;
 				function finish() {
 					if (done) return;
@@ -652,10 +1250,6 @@
 				document.addEventListener('visibilitychange', onVis);
 				window.addEventListener('pagehide', onHide);
 
-				if (isWeChatInAppBrowser()) {
-					window.alert('若点击后无法打开 App:请先点右上角「···」,选择「在浏览器中打开」,再点「APP内打开」。');
-				}
-
 				try {
 					launchAppDeepLink(deepLink);
 				} catch (e3) {
@@ -665,10 +1259,6 @@
 				}
 
 				window.setTimeout(function () {
-					if (done) return;
-					if (document.visibilityState === 'visible' && Date.now() - t0 < 3500) {
-						showDownloadTip();
-					}
 					finish();
 				}, 2600);
 			}
@@ -702,14 +1292,6 @@
 			openBySchemeOrPlus();
 		}
 
-		function qs() {
-			return new URLSearchParams(location.search || '');
-		}
-		function q(name) {
-			var v = qs().get(name);
-			return v == null ? '' : String(v);
-		}
-
 		function showErr(msg) {
 			var el = document.getElementById('pageError');
 			el.textContent = msg;
@@ -1179,12 +1761,35 @@
 		}
 
 		function boot() {
+			var launchTag = document.getElementById('launch-btn');
+			if (launchTag) launchTag.setAttribute('appid', WECHAT_OPEN_APP_ID);
+			bindWeChatLaunchTagEvents();
+			if (shouldInitWeChatJssdkOnLoad()) {
+				if (isWeChatInAppBrowser()) document.body.classList.add('is-wechat');
+				if (isWxPcBrowser() && (isWxForceDebug() || isWxPcAutoDebugHost())) {
+					document.body.classList.add('wx-pc-debug');
+				}
+				scheduleWeChatJssdkBootstrap();
+			}
+
 			run();
-			var openBtn = document.getElementById('openApp');
-			if (openBtn) {
-				openBtn.addEventListener('click', function () {
-					tryOpenUShopApp();
-				});
+
+			if (isWeChatInAppBrowser()) {
+				var openBtnWx = document.getElementById('openApp');
+				if (openBtnWx) {
+					openBtnWx.addEventListener('click', function () {
+						if (!weChatJssdkConfigured) {
+							showFabToast(
+								wxInitLastError || '微信 SDK 初始化中,请稍候再点底部按钮'
+							);
+						}
+					});
+				}
+			} else {
+				var openBtn = document.getElementById('openApp');
+				if (openBtn) {
+					openBtn.addEventListener('click', tryOpenUShopApp);
+				}
 			}
 		}