Эх сурвалжийг харах

修复视频播放商品挂件无法左右滑动问题

蒋开心 2 долоо хоног өмнө
parent
commit
addbf14b6f

+ 88 - 33
components/feed-reel/vertical-reel.vue

@@ -21,6 +21,7 @@
 			:acceleration="true"
 			:circular="swiperCircular"
 			:current="activeIndex"
+			:disable-touch="goodsTouchLock"
 			:style="rootStyle"
 			@change="onSlideChange"
 		>
@@ -184,28 +185,23 @@
 								</view>
 							</view>
 						</view>
-						<!-- 挂商品;仅当前页渲染,避免 iOS 邻页占位撑乱布局 -->
+						<!-- 挂商品:自定义横滑(iOS 竖滑 swiper 内 scroll-view 常被抢走手势) -->
 						<view
 							v-if="isActiveSlide(index) && goodsListOf(clip).length > 0"
 							class="reel-goods"
 							@click.stop
+							@touchstart="onGoodsPanStart"
+							@touchmove="onGoodsPanMove"
+							@touchend="onGoodsPanEnd"
+							@touchcancel="onGoodsPanEnd"
 						>
-							<scroll-view
-								class="reel-goods-scroll"
-								scroll-x="true"
-								:enable-flex="true"
-								:show-scrollbar="false"
-								:style="{ width: frameW + 'px' }"
-								@touchmove.stop
-							>
-								<view class="reel-goods-row">
+							<view class="reel-goods-viewport" :style="{ width: frameW + 'px' }">
+								<view class="reel-goods-track" :style="goodsTrackStyle(clip)">
 									<view
 										v-for="(goods, gIndex) in goodsListOf(clip)"
 										:key="goods.id || gIndex"
 										class="reel-goods-item"
-										:style="{ width: Math.round(frameW * 0.8) + 'px' }"
-										@touchstart="onGoodsTouchStart"
-										@touchmove="onGoodsTouchMove"
+										:style="{ width: goodsCardWidth + 'px' }"
 										@tap="onGoodsItemTap(goods)"
 									>
 										<image
@@ -223,8 +219,9 @@
 											</view>
 										</view>
 									</view>
+									<view class="reel-goods-tail" />
 								</view>
-							</scroll-view>
+							</view>
 						</view>
 					</view>
 				</view>
@@ -545,6 +542,10 @@ export default {
 			/** 强制重建 swiper / 当前 video,规避 iOS 首条黑屏 */
 			swiperEpoch: 0,
 			mediaEpoch: 0,
+			/** 触摸商品条时锁定竖滑,避免 iOS 抢走横向滑动 */
+			goodsTouchLock: false,
+			/** 商品条横向偏移(自定义横滑) */
+			goodsOffsetX: 0,
 			// ─── 评论弹层 ───
 			commentVisible: false,
 			commentLoading: false,
@@ -723,6 +724,9 @@ export default {
 			} catch (_) {
 				return false
 			}
+		},
+		goodsCardWidth() {
+			return Math.round((Number(this.frameW) || 0) * 0.8)
 		}
 	},
 	watch: {
@@ -1042,25 +1046,71 @@ export default {
 			const path = goods && (goods.image || goods.image_url || goods.cover)
 			return path ? buildOssUrl(path, 120, 120) : ''
 		},
-		/** 商品卡:区分横滑与点击,避免 @tap.stop 抢走 scroll-view 滑动 */
-		onGoodsTouchStart(e) {
+		goodsTrackWidth(clip) {
+			const n = this.goodsListOf(clip).length
+			if (!n) return 0
+			const gap = uni.upx2px(14)
+			const tail = uni.upx2px(24)
+			return n * (this.goodsCardWidth + gap) + tail
+		},
+		goodsMinOffset(clip) {
+			return Math.min(0, (Number(this.frameW) || 0) - this.goodsTrackWidth(clip))
+		},
+		goodsTrackStyle(clip) {
+			const x = this.goodsOffsetX
+			return {
+				width: this.goodsTrackWidth(clip) + 'px',
+				transform: `translateX(${x}px)`,
+				webkitTransform: `translateX(${x}px)`
+			}
+		},
+		onGoodsPanStart(e) {
 			const t = e && e.touches && e.touches[0]
-			this._goodsTouchX = t ? t.pageX : 0
-			this._goodsTouchY = t ? t.pageY : 0
-			this._goodsTouchMoved = false
+			this._gStartX = t ? t.pageX : 0
+			this._gStartY = t ? t.pageY : 0
+			this._gBase = this.goodsOffsetX
+			this._gAxis = ''
+			this._gMoved = false
 		},
-		onGoodsTouchMove(e) {
+		onGoodsPanMove(e) {
 			const t = e && e.touches && e.touches[0]
 			if (!t) return
-			if (
-				Math.abs(t.pageX - this._goodsTouchX) > 8 ||
-				Math.abs(t.pageY - this._goodsTouchY) > 8
-			) {
-				this._goodsTouchMoved = true
+			const dx = t.pageX - this._gStartX
+			const dy = t.pageY - this._gStartY
+			if (!this._gAxis) {
+				if (Math.abs(dx) < 8 && Math.abs(dy) < 8) return
+				this._gAxis = Math.abs(dx) >= Math.abs(dy) ? 'x' : 'y'
+			}
+			if (this._gAxis !== 'x') {
+				this.goodsTouchLock = false
+				return
 			}
+			this.goodsTouchLock = true
+			this._gMoved = true
+			if (e && e.cancelable && typeof e.preventDefault === 'function') {
+				e.preventDefault()
+			}
+			const clip = this.clips[this.slideIndex(this.activeIndex)]
+			const min = this.goodsMinOffset(clip)
+			let next = this._gBase + dx
+			if (next > 0) next *= 0.35
+			else if (next < min) next = min + (next - min) * 0.35
+			this.goodsOffsetX = next
+		},
+		onGoodsPanEnd() {
+			const clip = this.clips[this.slideIndex(this.activeIndex)]
+			const min = this.goodsMinOffset(clip)
+			if (this.goodsOffsetX > 0) this.goodsOffsetX = 0
+			else if (this.goodsOffsetX < min) this.goodsOffsetX = min
+			this.goodsTouchLock = false
+			// 延后清除,避免 touchend 后立刻触发 tap
+			setTimeout(() => {
+				this._gMoved = false
+				this._gAxis = ''
+			}, 50)
 		},
 		onGoodsItemTap(goods) {
-			if (this._goodsTouchMoved) return
+			if (this._gMoved) return
 			this.onGoodsTap(goods)
 		},
 		onGoodsTap(goods) {
@@ -1822,6 +1872,8 @@ export default {
 		onSlideChange(e) {
 			const next = this.slideIndex(e && e.detail && e.detail.current)
 			if (next === this.slideIndex(this.activeIndex)) return
+			this.goodsTouchLock = false
+			this.goodsOffsetX = 0
 			this.circularUnlocked = true
 			this.haltAll()
 			if (this.seriesSheetVisible) this.closeSeriesSheet()
@@ -2417,23 +2469,21 @@ export default {
 	z-index: 9;
 	margin-top: 16rpx;
 }
-.reel-goods-scroll {
+.reel-goods-viewport {
 	height: 174rpx;
-	/* #ifndef APP-PLUS */
-	white-space: nowrap;
-	/* #endif */
+	overflow: hidden;
 }
-.reel-goods-row {
+.reel-goods-track {
 	display: flex;
 	flex-direction: row;
-	align-items: stretch;
+	align-items: center;
 	height: 174rpx;
-	padding-right: 24rpx;
 }
 .reel-goods-item {
 	display: flex;
 	flex-direction: row;
 	align-items: center;
+	flex-shrink: 0;
 	background-color: rgba(255, 255, 255, 0.92);
 	border-radius: 18rpx;
 	margin-left: 14rpx;
@@ -2441,6 +2491,11 @@ export default {
 	height: 154rpx;
 	box-sizing: border-box;
 }
+.reel-goods-tail {
+	width: 24rpx;
+	height: 154rpx;
+	flex-shrink: 0;
+}
 .reel-goods-img {
 	width: 126rpx;
 	height: 126rpx;