zhuli 1 miesiąc temu
rodzic
commit
241636f09c
1 zmienionych plików z 571 dodań i 2 usunięć
  1. 571 2
      HBuilderProjects/shareDynamic.html

+ 571 - 2
HBuilderProjects/shareDynamic.html

@@ -38,6 +38,153 @@
 			overflow-x: hidden;
 		}
 
+		/* getDeleteFlagById 返回「暂无承载数据」:与 shareIndex 关店态一致,上方提示 + 更多推荐 */
+		#dynPageMain {
+			display: block;
+		}
+
+		.closed-rec-wrap {
+			display: none;
+		}
+
+		.closed-rec-divider {
+			height: 8px;
+			background: #F7F7F7;
+			margin: 0;
+		}
+
+		.closed-rec-title {
+			padding: 8px 15px 12px;
+			font-size: 16px;
+			font-weight: 700;
+		}
+
+		.closed-rec-grid {
+			display: grid;
+			grid-template-columns: repeat(2, 1fr);
+			gap: 10px;
+			padding: 0 15px 20px;
+		}
+
+		#shareClosedRecEmpty {
+			grid-column: 1 / -1;
+		}
+
+		.closed-rec-empty {
+			padding: 12px;
+			color: var(--text-secondary);
+			font-size: 14px;
+		}
+
+		.closed-rec-card {
+			min-width: 0;
+			background: #fff;
+			border-radius: 10px;
+			overflow: hidden;
+			box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+		}
+
+		.closed-rec-card__img {
+			aspect-ratio: 4 / 3;
+			background: #eee;
+		}
+
+		.closed-rec-card__img img {
+			width: 100%;
+			height: 100%;
+			object-fit: cover;
+			display: block;
+			border-radius: 0;
+		}
+
+		.closed-rec-card__body {
+			padding: 10px;
+		}
+
+		.closed-rec-card__top {
+			display: flex;
+			justify-content: space-between;
+			align-items: baseline;
+			gap: 8px;
+			margin-bottom: 6px;
+		}
+
+		.closed-rec-card__name {
+			font-size: 15px;
+			font-weight: 700;
+			flex: 1;
+			min-width: 0;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+		}
+
+		.closed-rec-card__dist {
+			font-size: 12px;
+			color: var(--text-secondary);
+			flex-shrink: 0;
+		}
+
+		.closed-rec-card__rating {
+			display: flex;
+			align-items: center;
+			flex-wrap: wrap;
+			gap: 4px 6px;
+		}
+
+		.closed-rec-card__rating .closed-rec-stars {
+			display: inline-flex;
+			align-items: center;
+			gap: 2px;
+		}
+
+		.closed-rec-card__rating .closed-rec-star {
+			display: block;
+			flex-shrink: 0;
+		}
+
+		.closed-rec-card__rating .closed-rec-rating-num {
+			font-size: 12px;
+			font-weight: 600;
+			color: var(--orange);
+		}
+
+		.closed-rec-meta {
+			font-size: 12px;
+			color: var(--text-secondary);
+		}
+
+		.closed-rec-card__footer {
+			margin-top: 8px;
+			font-size: 12px;
+			color: var(--text-secondary);
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+		}
+
+		.share-none {
+			display: none;
+			text-align: center;
+			padding: 28px 20px 16px;
+			color: var(--text-muted);
+			font-size: 15px;
+			line-height: 1.5;
+		}
+
+		.share-none img {
+			width: 240px;
+			height: 240px;
+			margin-bottom: 12px;
+			display: inline-block;
+		}
+
+		.share-none p {
+			margin: 0;
+			padding: 0 0 60px 0;
+			color: #999;
+		}
+
 		/* 顶部轮播 */
 		.dyn-hero {
 			position: relative;
@@ -380,6 +527,19 @@
 	</style>
 </head>
 <body class="page-dynamic">
+	<div class="share-none" id="shareClosedBanner" aria-hidden="true">
+		<img src="images/storeNone.png" alt="">
+		<p>抱歉,商户已关闭,看看别的吧</p>
+	</div>
+	<div class="closed-rec-wrap" id="shareClosedRecommendWrap" aria-hidden="true">
+		<div class="closed-rec-divider"></div>
+		<h3 class="closed-rec-title">更多推荐</h3>
+		<div class="closed-rec-grid" id="shareClosedRecList">
+			<p id="shareClosedRecEmpty" class="closed-rec-empty" style="display:none;">暂无推荐</p>
+		</div>
+	</div>
+
+	<div id="dynPageMain">
 	<!-- 顶部配图:含 .mp4 时只展示首帧静图(不播放视频)、不轮播;纯图片可多图轮播。 -->
 
 	<div class="dyn-hero" id="dynHero">
@@ -426,6 +586,7 @@
 			<span>暂无评论</span>
 		</div>
 	</div>
+	</div>
 
 	<div class="fab-wrap">
 		<button type="button" class="fab" id="openApp">
@@ -439,6 +600,17 @@
 		'use strict';
 
 		var API_BASE = 'https://test.ailien.shop/alienStore';
+		/**
+		 * 暂无承载数据时更多推荐:POST …/ai/multimodal-services/api/v1/search/global/store-recommend
+		 * 与 shareIndex.html / shareCheckInUndefined.html 一致。
+		 */
+		var API_LIFE_AI_BASE = 'http://124.93.18.180:9100';
+		var STORE_GLOBAL_RECOMMEND_PATH =
+			'/ai/multimodal-services/api/v1/search/global/store-recommend';
+		var DEFAULT_REC_USER_LAT = 38.925747;
+		var DEFAULT_REC_USER_LNG = 121.662531;
+		var DEFAULT_REC_USER_CITY = '大连市';
+
 		var COMMENT_PAGE_NUM = 1;
 		var COMMENT_PAGE_SIZE = 20;
 		var COMMENT_AVATAR_FALLBACK = 'images/demouser.png';
@@ -459,6 +631,8 @@
 		 * 与 shareIndex / group_user manifest 一致
 		 * 动态详情默认落地:pages/newdetails/index(onLoad 使用 item 等,与当前页合并 query 一致)
 		 * 打开其它 App 页:H5 URL 上带 appPath 或 appPage,如 ?appPath=pages%2FcheckIn%2Findex(不要前导 /)
+		 * 关店 businessStatus=99:getOne 置 closedMerchantFlag 或 URL 带 businessStatus=99 时,「APP内打开」深链为
+		 * shopro://pages/index/login?…(与 shareIndex.html、group_user pages/index/login 一致)。
 		 */
 		var APP_ANDROID_PACKAGE = 'com.alien.Udianzaizhe';
 		var APP_IOS_URL_SCHEME = 'shopro://';
@@ -601,6 +775,32 @@
 		/**
 		 * 真机系统浏览器对超长 shopro:// 常截断;去掉超大 item,用 id/type 让 App 内再拉详情。
 		 */
+		/** getOne 返回关店后置位;与 shareIndex.html closedMerchantFlag 一致 */
+		var closedMerchantFlag = false;
+
+		function isGetOneBusinessStatus99(res) {
+			if (!res || typeof res !== 'object') return false;
+			var d = res.data;
+			if (d && typeof d === 'object') {
+				var bs = d.businessStatus;
+				if (bs === 99 || bs === '99' || Number(bs) === 99) return true;
+			}
+			return false;
+		}
+
+		/**
+		 * 关店 businessStatus=99:getOne 已置 closedMerchantFlag,或 URL(含 hash)带 businessStatus=99;
+		 * 唤起 App 时深链进 pages/index/login(与 shareIndex.html getAppUniPathForBusinessSection 一致)。
+		 */
+		function isClosedMerchantForAppOpen() {
+			if (closedMerchantFlag) return true;
+			try {
+				var bs = String(mergeSearchAndHashParams().get('businessStatus') || '').trim();
+				if (bs === '99' || Number(bs) === 99) return true;
+			} catch (e0) {}
+			return false;
+		}
+
 		function compactShoproDeepLinkIfTooLong(fullUrl, maxLen) {
 			maxLen = maxLen || 7200;
 			if (!fullUrl || fullUrl.length <= maxLen) return fullUrl;
@@ -628,6 +828,13 @@
 		}
 
 		function buildAppDeepLink() {
+			if (isClosedMerchantForAppOpen()) {
+				var sClosed = buildAppOpenQueryStringMerged();
+				var rootClosed = APP_IOS_URL_SCHEME.replace(/\/$/, '');
+				var pathClosed = 'pages/index/login'.replace(/^\//, '');
+				var rawClosed = !sClosed ? rootClosed + '/' + pathClosed : rootClosed + '/' + pathClosed + sClosed;
+				return compactShoproDeepLinkIfTooLong(rawClosed, 7200);
+			}
 			var explicit = (getMergedParam('appPath') || getMergedParam('appPage') || '').trim().replace(/^\//, '');
 			var fromHash = extractUniPagePathFromHash();
 			var defaultPath = String(APP_UNI_STORE_PATH || '/pages/newdetails/index').replace(/^\//, '');
@@ -899,6 +1106,355 @@
 			return v == null ? '' : String(v);
 		}
 
+		/**
+		 * getDeleteFlagById:data 为数字 1(旧格式),或 data.businessStatus 为 99 时进「已删除」落地页。
+		 * 与 shareCheckIn.html 一致。
+		 */
+		function shouldRedirectToShareCheckInUndefined(res) {
+			if (!res || typeof res !== 'object') return false;
+			var d = res.data;
+			if (d === 1 || d === '1' || Number(d) === 1) return true;
+			if (d && typeof d === 'object') {
+				var bs = d.businessStatus;
+				if (bs === 99 || bs === '99' || Number(bs) === 99) return true;
+			}
+			return false;
+		}
+
+		/** 当前页合并 query + 接口返回的 storeId、businessStatus(若有)供 shareCheckInUndefined 使用 */
+		function buildShareCheckInUndefinedHref(res) {
+			var params = new URLSearchParams(getMergedQueryString());
+			if (res && res.data && typeof res.data === 'object') {
+				if (res.data.storeId != null) {
+					var sid = String(res.data.storeId).trim();
+					if (sid) params.set('storeId', sid);
+				}
+				if (res.data.businessStatus != null && String(res.data.businessStatus).trim() !== '') {
+					params.set('businessStatus', String(res.data.businessStatus).trim());
+				}
+			}
+			var m = params.toString();
+			return 'shareCheckInUndefined.html' + (m ? ('?' + m) : '');
+		}
+
+		function tryParseJsonObject(raw) {
+			raw = String(raw == null ? '' : raw).trim();
+			if (!raw) return null;
+			try {
+				var o = JSON.parse(raw);
+				return o && typeof o === 'object' && !Array.isArray(o) ? o : null;
+			} catch (e) {
+				return null;
+			}
+		}
+
+		/**
+		 * 与 App buildDynamicShareH5FullUrl 一致:店铺 id 常在整段 item= / dynamicItem= JSON 里,
+		 * 顶层未必带 storeId(sourceId 是动态 id,不能当店铺传给 getDeleteFlagById)。
+		 */
+		function parseShareDynamicItemBlob() {
+			var a = tryParseJsonObject(getMergedParam('item'));
+			if (a) return a;
+			return tryParseJsonObject(getMergedParam('dynamicItem'));
+		}
+
+		/**
+		 * getDeleteFlagById 要求 query 参数名为 id,值为店铺 storeId(与后端约定一致)。
+		 */
+		function resolveStoreIdForDeleteFlagApi() {
+			var sid =
+				getMergedParam('storeId').trim() ||
+				q('storeId').trim();
+			if (!sid) {
+				var optItem = parseOptionsItem();
+				if (optItem && optItem.storeId != null && String(optItem.storeId).trim() !== '') {
+					sid = String(optItem.storeId).trim();
+				}
+			}
+			if (!sid) {
+				var blob = parseShareDynamicItemBlob();
+				if (blob) {
+					if (blob.storeId != null && String(blob.storeId).trim() !== '') {
+						sid = String(blob.storeId).trim();
+					} else if (blob.store_id != null && String(blob.store_id).trim() !== '') {
+						sid = String(blob.store_id).trim();
+					}
+				}
+			}
+			return sid;
+		}
+
+		/** 进入页先拉删除标记;如 getDeleteFlagById?id=<storeId> */
+		function fetchGetDeleteFlagByIdIfId() {
+			var id = resolveStoreIdForDeleteFlagApi();
+			if (!id) return Promise.resolve(null);
+			var qs = new URLSearchParams();
+			qs.set('id', id);
+			var path = '/store/info/getOne?' + qs.toString();
+			return apiFetch(path)
+				.then(function (res) {
+					return res;
+				})
+				.catch(function (e) {
+					console.warn('[getDeleteFlagById]', e);
+					return null;
+				});
+		}
+
+		function isNoCarryingDataDeleteFlagMsg(res) {
+			if (!res || typeof res !== 'object') return false;
+			var tip = res.msg != null ? String(res.msg).trim() : '';
+			return tip === '暂无承载数据';
+		}
+
+		var CLOSED_REC_STAR_PATH =
+			'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z';
+
+		function closedRecEscHtml(s) {
+			return String(s == null ? '' : s)
+				.replace(/&/g, '&amp;')
+				.replace(/</g, '&lt;')
+				.replace(/>/g, '&gt;')
+				.replace(/"/g, '&quot;');
+		}
+
+		function closedRecStarsHtml(score) {
+			var s = Number(score);
+			if (isNaN(s)) s = 0;
+			s = Math.max(0, Math.min(5, s));
+			var rounded = Math.round(s);
+			var parts = [];
+			var i;
+			for (i = 1; i <= 5; i++) {
+				var fill = i <= rounded ? '#F58220' : '#E5E5E5';
+				parts.push(
+					'<svg class="closed-rec-star" width="11" height="11" viewBox="0 0 24 24" aria-hidden="true">' +
+					'<path fill="' + fill + '" d="' + CLOSED_REC_STAR_PATH + '"/></svg>'
+				);
+			}
+			return '<span class="closed-rec-stars">' + parts.join('') + '</span>';
+		}
+
+		function closedRecFormatDistance(item) {
+			if (item.distance != null && item.distance !== '') {
+				var km = Number(item.distance);
+				if (!isNaN(km) && km >= 0) {
+					return Math.round(km * 1000) + '米';
+				}
+				return String(item.distance).trim();
+			}
+			if (item.position != null && String(item.position).trim() !== '') {
+				return String(item.position).trim();
+			}
+			if (item.dist != null && item.dist !== '') {
+				var dn = Number(item.dist);
+				if (!isNaN(dn)) {
+					return dn >= 1 ? dn.toFixed(dn % 1 === 0 ? 0 : 1) + 'km' : Math.round(dn * 1000) + '米';
+				}
+			}
+			return '';
+		}
+
+		function closedRecPickScore(item) {
+			var x =
+				item.scoreAvg != null ? Number(item.scoreAvg)
+					: item.score != null ? Number(item.score)
+						: item.rating != null ? Number(item.rating)
+							: item.starScore != null ? Number(item.starScore)
+								: NaN;
+			return isNaN(x) ? null : x;
+		}
+
+		function closedRecPickReviewCount(item) {
+			var n =
+				item.commitCount != null ? Number(item.commitCount)
+					: item.commentCount != null ? Number(item.commentCount)
+						: item.reviewCount != null ? Number(item.reviewCount)
+							: item.evaluateCount != null ? Number(item.evaluateCount)
+								: NaN;
+			return isNaN(n) ? 0 : Math.max(0, Math.floor(n));
+		}
+
+		function normalizeClosedStoreRecommendList(res) {
+			if (!res || typeof res !== 'object') return [];
+			var raw = res.data != null ? res.data : res.result;
+			if (Array.isArray(raw)) return raw;
+			if (raw && typeof raw === 'object') {
+				if (Array.isArray(raw.list)) return raw.list;
+				if (Array.isArray(raw.records)) return raw.records;
+				if (Array.isArray(raw.rows)) return raw.rows;
+				if (Array.isArray(raw.content)) return raw.content;
+				if (Array.isArray(raw.stores)) return raw.stores;
+				if (Array.isArray(raw.storeList)) return raw.storeList;
+				if (Array.isArray(raw.storeVos)) return raw.storeVos;
+				if (Array.isArray(raw.items)) return raw.items;
+			}
+			if (Array.isArray(res.list)) return res.list;
+			if (Array.isArray(res.records)) return res.records;
+			return [];
+		}
+
+		function fetchShareClosedStoreRecommend(storeIdStr) {
+			var latRaw = (q('userLat') || q('latitude') || q('lat') || q('weidu')).trim();
+			var lngRaw = (q('userLng') || q('longitude') || q('lon') || q('jingdu')).trim();
+			var userLat =
+				latRaw !== '' && !isNaN(Number(latRaw)) ? Number(latRaw) : DEFAULT_REC_USER_LAT;
+			var userLng =
+				lngRaw !== '' && !isNaN(Number(lngRaw)) ? Number(lngRaw) : DEFAULT_REC_USER_LNG;
+
+			var page = parseInt(q('page') || '1', 10);
+			var pageSize = parseInt(q('pageSize') || '10', 10);
+			if (isNaN(page) || page < 1) page = 1;
+			if (isNaN(pageSize) || pageSize < 1) pageSize = 10;
+
+			var userCityRaw = (q('userCity') || q('city') || '').trim();
+			var userCity = userCityRaw !== '' ? userCityRaw : DEFAULT_REC_USER_CITY;
+
+			var body = {
+				page: page,
+				pageSize: pageSize,
+				storeId: String(storeIdStr || ''),
+				userCity: userCity,
+				userLat: userLat,
+				userLng: userLng
+			};
+
+			return fetch(API_LIFE_AI_BASE + STORE_GLOBAL_RECOMMEND_PATH, {
+				method: 'POST',
+				mode: 'cors',
+				credentials: 'omit',
+				headers: {
+					Accept: 'application/json',
+					'Content-Type': 'application/json;charset=UTF-8'
+				},
+				body: JSON.stringify(body)
+			}).then(function (res) {
+				if (!res.ok) throw new Error('HTTP ' + res.status);
+				return res.json();
+			});
+		}
+
+		function renderShareClosedRecommended(list) {
+			var wrap = document.getElementById('shareClosedRecList');
+			var empty = document.getElementById('shareClosedRecEmpty');
+			if (!wrap || !empty) return;
+			wrap.querySelectorAll('.closed-rec-card').forEach(function (n) {
+				n.remove();
+			});
+			if (!list || !list.length) {
+				empty.style.display = 'block';
+				return;
+			}
+			empty.style.display = 'none';
+
+			list.forEach(function (item) {
+				if (!item || typeof item !== 'object') return;
+
+				var imgUrlField = item.imgUrl != null ? String(item.imgUrl).trim() : '';
+				var home = item.homeImage != null ? String(item.homeImage).trim() : '';
+				if (home && /\.mp4(\?|#|$)/i.test(home)) {
+					var vf = item.videoFirstFrame != null ? String(item.videoFirstFrame).trim() : '';
+					if (vf) home = vf;
+				}
+				var imgUrl =
+					imgUrlField ||
+					home ||
+					(item.coverUrl != null ? String(item.coverUrl).trim() : '') ||
+					(item.mainImage != null ? String(item.mainImage).trim() : '') ||
+					(item.goodsImage != null ? String(item.goodsImage).trim() : '') ||
+					(item.entranceImage != null ? String(item.entranceImage).trim() : '') ||
+					(Array.isArray(item.goodsImageList) && item.goodsImageList[0]) ||
+					(Array.isArray(item.imageList) && item.imageList[0]) ||
+					(Array.isArray(item.storeAlbumUrlList) && item.storeAlbumUrlList[0]) ||
+					'';
+
+				var name = item.title != null && String(item.title).trim() !== ''
+					? String(item.title).replace(/\r?\n/g, ' ').replace(/\s+/g, ' ').trim()
+					: (item.storeName || item.goodsName || item.secondGoodsTitle || item.name || '推荐');
+
+				var dist = closedRecFormatDistance(item);
+
+				var scoreVal = closedRecPickScore(item);
+				var displayScore = scoreVal != null ? scoreVal.toFixed(1) : '—';
+				var starScore = scoreVal != null ? scoreVal : 0;
+
+				var rc = closedRecPickReviewCount(item);
+				var reviewLabel = rc > 0 ? rc + '条评价' : '';
+
+				var seller =
+					item.userName != null && String(item.userName).trim() !== ''
+						? String(item.userName).trim()
+						: (item.nickName != null && String(item.nickName).trim() !== ''
+							? String(item.nickName).trim()
+							: '');
+
+				var card = document.createElement('article');
+				card.className = 'closed-rec-card';
+				card.innerHTML =
+					'<div class="closed-rec-card__img"><img class="closed-rec-card__cover" src="" alt="" decoding="async"></div>' +
+					'<div class="closed-rec-card__body">' +
+					'<div class="closed-rec-card__top">' +
+					'<span class="closed-rec-card__name">' + closedRecEscHtml(name) + '</span>' +
+					(dist ? '<span class="closed-rec-card__dist">' + closedRecEscHtml(dist) + '</span>' : '') +
+					'</div>' +
+					'<div class="closed-rec-card__rating">' +
+					closedRecStarsHtml(starScore) +
+					'<span class="closed-rec-rating-num">' + closedRecEscHtml(displayScore) + '</span>' +
+					(reviewLabel ? '<span class="closed-rec-meta">' + closedRecEscHtml(reviewLabel) + '</span>' : '') +
+					'</div>' +
+					(seller ? '<div class="closed-rec-card__footer">' + closedRecEscHtml(seller) + '</div>' : '') +
+					'</div>';
+
+				var coverIm = card.querySelector('img.closed-rec-card__cover');
+				if (coverIm) {
+					coverIm.src = imgUrl;
+					coverIm.onerror = function () {
+						this.onerror = null;
+						this.src = '';
+					};
+				}
+				wrap.appendChild(card);
+			});
+		}
+
+		function loadDynNoCarryRecommendations() {
+			var sid = resolveStoreIdForDeleteFlagApi();
+			if (!sid) {
+				renderShareClosedRecommended([]);
+				return;
+			}
+			fetchShareClosedStoreRecommend(sid)
+				.then(function (res) {
+					var list = normalizeClosedStoreRecommendList(res);
+					if (!list.length && res && res.msg) {
+						console.warn('[store-recommend]', res.msg);
+					}
+					renderShareClosedRecommended(list);
+				})
+				.catch(function (e) {
+					console.error(e);
+					renderShareClosedRecommended([]);
+				});
+		}
+
+		function showDynNoCarryingDataState() {
+			var main = document.getElementById('dynPageMain');
+			if (main) {
+				main.style.display = 'none';
+			}
+			var banner = document.getElementById('shareClosedBanner');
+			var recWrap = document.getElementById('shareClosedRecommendWrap');
+			if (banner) {
+				banner.style.display = 'block';
+				banner.setAttribute('aria-hidden', 'false');
+			}
+			if (recWrap) {
+				recWrap.style.display = 'block';
+				recWrap.setAttribute('aria-hidden', 'false');
+			}
+			loadDynNoCarryRecommendations();
+		}
+
 		function pushUniqueUrl(list, u) {
 			u = String(u == null ? '' : u).trim();
 			if (!u) return;
@@ -1617,8 +2173,21 @@
 		}
 
 		function boot() {
-			applyQueryContent();
-			loadComments();
+			fetchGetDeleteFlagByIdIfId().then(function (res) {
+				if (isGetOneBusinessStatus99(res)) {
+					closedMerchantFlag = true;
+				}
+				// if (shouldRedirectToShareCheckInUndefined(res)) {
+				// 	window.location.replace(buildShareCheckInUndefinedHref(res));
+				// 	return;
+				// }
+				if (isNoCarryingDataDeleteFlagMsg(res)) {
+					showDynNoCarryingDataState();
+					return;
+				}
+				applyQueryContent();
+				loadComments();
+			});
 			var openBtn = document.getElementById('openApp');
 			if (openBtn) {
 				openBtn.addEventListener('click', function () {