Преглед на файлове

1.修复当用户进入直播间,封面未加载成功没有显示直播封面锁 2. 修复点击去支付有时弹出两个余额检查弹框

蒋开心 преди 2 седмици
родител
ревизия
46c935cbea
променени са 4 файла, в които са добавени 139 реда и са изтрити 30 реда
  1. 2 2
      common/config/runtime-manifest.js
  2. 2 2
      manifest.json
  3. 135 26
      pages/live/watch.nvue
  4. BIN
      static/imgs/live-lock-bg.jpg

+ 2 - 2
common/config/runtime-manifest.js

@@ -12,8 +12,8 @@ const ENDPOINTS = Object.freeze(
 )
 
 const IDENTITY = Object.freeze({
-	releaseLabel: '1.1.37',
-	numericBuild: '1137',
+	releaseLabel: '1.1.38',
+	numericBuild: '1138',
 	// 排查"看不到直播"期间临时开启,正式发布请改回 false
 	traceVerbose: false,
 	geoServiceKey: '',

+ 2 - 2
manifest.json

@@ -2,8 +2,8 @@
     "name" : "88live",
     "appid" : "__UNI__3F63D9A",
     "description" : "",
-    "versionName" : "1.1.37",
-    "versionCode" : 1037,
+    "versionName" : "1.1.38",
+    "versionCode" : 1038,
     "transformPx" : false,
     /* 5+App特有相关 */
     "app-plus" : {

+ 135 - 26
pages/live/watch.nvue

@@ -298,33 +298,31 @@
 
 		<!-- 付费遮罩必须在最上层(iOS nvue 后绘制的兄弟节点才能点到) -->
 		<view v-if="paywallOn" class="stage-paywall" :style="paywallLayerStyle">
-			<image class="stage-paywall-bg" :src="posterSrc" :style="fullFrameStyle" mode="aspectFill" />
-			<view class="stage-paywall-mask" :style="fullFrameStyle" />
+			<!-- 封面未就绪时的底色占位,避免纯黑屏 -->
+			<view class="stage-paywall-placeholder" :style="fullFrameStyle" />
+			<image
+				v-if="posterSrc"
+				class="stage-paywall-bg"
+				:src="posterSrc"
+				:style="fullFrameStyle"
+				mode="aspectFill"
+				:lazy-load="false"
+				@load="onPaywallCoverLoad"
+				@error="onPaywallCoverError"
+			/>
+			<view class="stage-paywall-mask" :style="paywallMaskStyle" />
 			<view class="stage-paywall-body" :style="fullFrameStyle">
 				<image class="stage-paywall-lock-img" src="/static/imgs/icon/ic_lock.png" mode="aspectFit" />
 				<text class="stage-paywall-title">{{ copy.trialEnd }}</text>
-				<view
-					class="stage-paywall-btn"
-					@click="openLivePayPopup"
-					@tap="openLivePayPopup"
-				>
+				<!-- 仅父级 @tap,避免 iOS 上 click+tap / 子节点冒泡连弹两次 -->
+				<view class="stage-paywall-btn" @tap="openLivePayPopup">
 					<image
 						class="stage-paywall-btn-icon"
 						src="/static/imgs/icon/ic_d.png"
 						mode="aspectFit"
-						@click.stop="openLivePayPopup"
-						@tap.stop="openLivePayPopup"
 					/>
-					<text
-						class="stage-paywall-btn-amount"
-						@click.stop="openLivePayPopup"
-						@tap.stop="openLivePayPopup"
-					>{{ session.paid_money || 0 }}</text>
-					<text
-						class="stage-paywall-btn-txt"
-						@click.stop="openLivePayPopup"
-						@tap.stop="openLivePayPopup"
-					>{{ copy.goToPay }}</text>
+					<text class="stage-paywall-btn-amount">{{ session.paid_money || 0 }}</text>
+					<text class="stage-paywall-btn-txt">{{ copy.goToPay }}</text>
 				</view>
 			</view>
 		</view>
@@ -430,9 +428,13 @@ export default {
 			sessionPhase: 'loading',
 			paywallOn: false,
 			unlocked: false,
+			/** 付费锁屏封面是否已加载成功 */
+			paywallCoverReady: false,
 			showLivePayPopup: false,
 			showInsufficientBalanceModal: false,
 			unlockLiveBusy: false,
+			_payPopupBusy: false,
+			_payPopupAt: 0,
 			draftMsg: '',
 			_followBusy: false,
 			playerRenderToken: 0,
@@ -482,7 +484,27 @@ export default {
 			return { width: `${this.viewport.w}px`, height: `${this.viewport.h}px` }
 		},
 		posterSrc() {
-			return this.toCdn(this.session.image)
+			const fromSession = this.toCdn(this.session && this.session.image)
+			if (fromSession) return fromSession
+			const fromFeed = this.toCdn(
+				(this.feedCovers && this.feedCovers[this.feedCursor]) || ''
+			)
+			if (fromFeed) return fromFeed
+			const fromAvatar = this.toCdn(
+				(this.session && this.session.shop && this.session.shop.avatar) || ''
+			)
+			if (fromAvatar) return fromAvatar
+			return ''
+		},
+		paywallMaskStyle() {
+			const base = this.fullFrameStyle || {}
+			return {
+				...base,
+				// 封面未出时用深色蓝底,避免纯黑;就绪后再加深遮罩
+				backgroundColor: this.paywallCoverReady
+					? 'rgba(0, 0, 0, 0.55)'
+					: 'rgba(28, 30, 61, 0.55)'
+			}
 		},
 		anchorTitle() {
 			return (this.session.shop && this.session.shop.shopname) || this.copy.liveRoom || ''
@@ -982,6 +1004,7 @@ export default {
 						this._entryMeta.isFollow = normalizeIsFollow(this._entryMeta.isFollow)
 					}
 				}
+				this.seedPosterFromFeed()
 				return
 			}
 			try {
@@ -997,8 +1020,18 @@ export default {
 				this.feedCovers = pairs.map((p) => p.cover)
 				const hit = this.feedIds.indexOf(String(this.roomKey))
 				this.feedCursor = hit >= 0 ? hit : 0
+				this.seedPosterFromFeed()
 			} catch (_) {}
 		},
+		/** 列表进房时先灌封面,详情返回前锁屏/底图不至于空黑 */
+		seedPosterFromFeed() {
+			const cover = (this.feedCovers && this.feedCovers[this.feedCursor]) || ''
+			if (!cover) return
+			if (!this.session) this.session = { shop: {}, state: 0, paid_money: 0, isFollow: false }
+			if (!this.session.image) {
+				this.$set(this.session, 'image', cover)
+			}
+		},
 		async enterSession() {
 			if (!this.roomKey) {
 				uni.showToast({ title: this.copy.roomParamMissing, icon: 'none' })
@@ -1006,6 +1039,8 @@ export default {
 			}
 			this.sessionPhase = 'loading'
 			this.paywallOn = false
+			this.paywallCoverReady = false
+			this.seedPosterFromFeed()
 			let detail = null
 			try {
 				detail = await pullBroadcastDetail(this.roomKey)
@@ -1036,6 +1071,11 @@ export default {
 			if (!Array.isArray(this.session.goods)) {
 				this.$set(this.session, 'goods', [])
 			}
+			// 详情若无封面,保留列表带入的 thumb
+			if (!this.session.image) {
+				this.seedPosterFromFeed()
+			}
+			this.paywallCoverReady = false
 			this.goodsSheetVisible = false
 			// 大厅带入的 user_no 写入 session,供关注接口使用(play 详情常缺此字段)
 			const entryNo = String((this._entryMeta && this._entryMeta.userNo) || '').trim()
@@ -1054,7 +1094,16 @@ export default {
 			console.log('[LIVE-DIAG] platform =', this.platformLabel)
 
 			this.streamArchive = url
-			this.streamSrc = url || ''
+			const paidAmount = Number(detail && detail.paid_money) || 0
+			// 付费未解锁:进房立刻锁屏,避免先出画面再等 getLastTime 才遮罩
+			if (paidAmount > 0 && !this.unlocked) {
+				this.paywallCoverReady = false
+				this.paywallOn = true
+				this.streamSrc = ''
+			} else {
+				this.paywallOn = false
+				this.streamSrc = url || ''
+			}
 			// 强制让 <stream-player> 重新挂载,等价源代码 onShow 里 `playComponentKey = Date.now()`,
 			// 解决 nvue + weex video 在 src 由空变非空时偶发不自动 reload 的问题。
 			this.playerRenderToken += 1
@@ -1068,8 +1117,7 @@ export default {
 			console.log('=== [LIVE-DIAG] banner snap ===', this.lastDetailSnap)
 			console.log('=== [LIVE-DIAG] shouldRenderPlayer =', this.shouldRenderPlayer, ' paywallOn =', this.paywallOn)
 
-			// 试看时长检测必须脱离主流程:源码里 checkFreeTime 也是不 await 的,
-			// 失败不能让 sessionPhase 倒退回 offline,否则正在直播的房间会被误判为已结束。
+			// 试看检测不阻塞进房;付费房已先锁屏,有剩余试看时再放开
 			this.setupTrial().catch((err) => {
 				console.warn('[LIVE-DIAG] setupTrial failed', err)
 			})
@@ -1121,28 +1169,73 @@ export default {
 				sec = await pullTrialSeconds(this.roomKey)
 			} catch (err) {
 				if (appSettings.debug) console.warn('[live] pullTrialSeconds failed', err)
+				// 接口失败:付费房保持/打开锁屏,避免裸播
+				if ((Number(this.session.paid_money) || 0) > 0 && !this.unlocked) {
+					this.triggerPaywall()
+				}
+				return
+			}
+			// -1:免费全场
+			if (sec === -1) {
+				this.releasePaywallForTrial()
+				return
+			}
+			if (sec == null) {
+				if ((Number(this.session.paid_money) || 0) > 0 && !this.unlocked) {
+					this.triggerPaywall()
+				}
 				return
 			}
-			if (sec === -1 || sec == null) return
 			this.trialClock?.stop()
 			this.trialClock = new TrialMeter(() => this.triggerPaywall())
 			if (sec <= 0) {
 				this.triggerPaywall()
 				return
 			}
+			// 仍有试看:关锁屏、开播并倒计时
+			this.releasePaywallForTrial()
 			this.trialClock.start(sec)
 		},
+		/** 有试看时长时放开锁屏并恢复拉流 */
+		releasePaywallForTrial() {
+			if (this.unlocked) return
+			this.paywallOn = false
+			const url = this.streamArchive || resolvePlaybackUrl(this.session)
+			if (!url) return
+			if (this.streamSrc !== url) {
+				this.streamSrc = url
+				this.playerRenderToken += 1
+			}
+		},
 		triggerPaywall() {
+			this.paywallCoverReady = false
 			this.paywallOn = true
 			this.streamSrc = ''
 			this.trialClock?.stop()
 		},
+		onPaywallCoverLoad() {
+			this.paywallCoverReady = true
+		},
+		onPaywallCoverError() {
+			this.paywallCoverReady = false
+		},
 		openLivePayPopup() {
+			const now = Date.now()
+			// iOS nvue 偶发 tap 连发;短时防抖避免连弹两个系统确认框
+			if (this._payPopupBusy || (now - (this._payPopupAt || 0) < 900)) {
+				return
+			}
+			this._payPopupBusy = true
+			this._payPopupAt = now
 			console.log('[live] openLivePayPopup tap')
 			if (this.$appKit && typeof this.$appKit.requireLogin === 'function') {
-				if (!this.$appKit.requireLogin()) return
+				if (!this.$appKit.requireLogin()) {
+					this._payPopupBusy = false
+					return
+				}
 			} else if (!(this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.isLogin)) {
 				uni.showToast({ title: this.copy.needLogin, icon: 'none' })
+				this._payPopupBusy = false
 				return
 			}
 			// #ifdef APP-PLUS
@@ -1153,12 +1246,23 @@ export default {
 				confirmText: this.copy.confirm || '确认',
 				cancelText: this.copy.cancel || '关闭',
 				success: (res) => {
+					this._payPopupBusy = false
+					this._payPopupAt = 0
 					if (res.confirm) this.checkLiveBalanceAndPay()
+				},
+				fail: () => {
+					this._payPopupBusy = false
+					this._payPopupAt = 0
 				}
 			})
+			// 兜底:部分机型 success 偶发不回调时释放锁
+			setTimeout(() => {
+				this._payPopupBusy = false
+			}, 1200)
 			return
 			// #endif
 			this.showLivePayPopup = true
+			this._payPopupBusy = false
 		},
 		noop() {},
 		closeLivePayPopup() {
@@ -2199,6 +2303,12 @@ export default {
 	bottom: 0;
 	z-index: 80;
 }
+.stage-paywall-placeholder {
+	position: absolute;
+	top: 0;
+	left: 0;
+	background-color: #1c1e3d;
+}
 .stage-paywall-bg {
 	position: absolute;
 	top: 0;
@@ -2210,7 +2320,6 @@ export default {
 	left: 0;
 	right: 0;
 	bottom: 0;
-	background-color: rgba(0, 0, 0, 0.55);
 }
 .stage-paywall-body {
 	position: absolute;

BIN
static/imgs/live-lock-bg.jpg