liujiaxin 2 tygodni temu
rodzic
commit
f02b3569dc

+ 6 - 1
api/live.js

@@ -22,7 +22,7 @@ const api = {
 	liveGoodsDetail: (productId) => `/app/live/liveGoods/liveGoodsDetail/${productId}`, // 商品详情
 	liveOrderUser: (liveId) => `/app/live/liveOrder/liveOrderUser/${liveId}`, // 正在购买
 	showGoods: (liveId) => `/app/live/liveGoods/showGoods/${liveId}`, // 弹出商品卡片
-
+	currentActivities: (liveId) => `/app/live/currentActivities?liveId=${liveId}`, // 弹出商品卡片
 }
 // 点赞
 export function liveDataLike(liveId, data = {}) {
@@ -119,4 +119,9 @@ export function getLiveViewData(liveId, data = {}) {
 }
 
 
+// 红包 卡片 抽奖
+export function currentActivities(liveId, data = {}) {
+	return request(api.currentActivities(liveId), data, 'GET', 'application/json;charset=UTF-8',false)
+}
+
 

+ 1 - 1
manifest.json

@@ -115,7 +115,7 @@
     "quickapp" : {},
     /* 小程序特有相关 */
     "mp-weixin" : {
-        "appid" : "wxd70f99287830cb51",
+        "appid" : "wx73f85f8d62769119",
         "libVersion" : "3.4.2",
         "setting" : {
             "urlCheck" : true,

+ 350 - 253
pages/home/living.vue

@@ -46,7 +46,7 @@
 							</view>
 						</view>
 						<!-- 红包内容 -->
-						<view class="hongbao-box" v-if="liveItem.redInfo?.redStatus==1">
+						<view class="hongbao-box" v-if="liveItem.redInfo?.redStatus==1&&isShowRed">
 							<view class="u-flex-y-center">
 								<view class="tip">领红包</view>
 								<view class="item">
@@ -66,15 +66,27 @@
 						<view class="videolist" style="margin: auto 0">
 							<view class="video" style="height:100vh">
 								<!-- 视频组件 -->
-								<video v-if="currentSwiperIndex === index && liveItem.livingUrl"
+								<!-- 	<live-player v-if="currentSwiperIndex === index && liveItem.livingUrl" mode="live"
+									orientation="vertical" :id="'myVideo_' + liveItem.liveId" :src="liveItem.livingUrl"
+									 :autoplay="currentSwiperIndex === index"
+									bindstatechange="onStatechange"></live-player> -->
+
+
+								<!-- <video v-if="currentSwiperIndex === index && liveItem.livingUrl"
 									:id="'myVideo_' + liveItem.liveId"
 									:class="liveItem.showType == 1 ? 'video_row' : 'videotop'" :src="liveItem.livingUrl"
-									:autoplay="currentSwiperIndex === index" :controls='false' object-fit='contain'
-									:custom-cache="false" :enable-progress-gesture="false"
-									vslide-gesture-in-fullscreen='true' :show-center-play-btn="false"
-									:http-cache="false" @error="videoError">
-								</video>
-
+									:controls='false' object-fit='contain' :custom-cache="false"
+									:enable-progress-gesture="false" vslide-gesture-in-fullscreen='true'
+									:show-center-play-btn="false" :http-cache="false" @error="videoError">
+								</video> -->
+								<live-player v-if="currentSwiperIndex === index && liveItem.livingUrl"
+									:id="'myLivePlayer_' + liveItem.liveId"
+									:class="liveItem.showType == 1 ? 'video_row' : 'videotop'" :src="liveItem.livingUrl"
+									mode="live" orientation="vertical" :autoplay="currentSwiperIndex === index"
+									:controls="false" object-fit="contain" :enable-progress-gesture="false"
+									:show-center-play-btn="false" @error="livePlayerError"
+									@statechange="onLivePlayerStateChange"
+									@fullscreenchange="onLiveFullScreenChange"></live-player>
 								<video v-if="currentSwiperIndex === index && liveItem.videoUrl"
 									:class="liveItem.showType == 1 ? 'video_row' : 'videotop'" :src="liveItem.videoUrl"
 									:autoplay="currentSwiperIndex === index" :controls='false' object-fit='contain'
@@ -140,43 +152,14 @@
 									<view class="send" @click="sendMsg(liveItem)">发送</view>
 								</view>
 								<view class="justify-between mr15 align-center">
-									<!-- <view class="icon ml20" @click="onLike(liveItem)">
-										<image src="/static/images/live/like.png" class="w56 h56"></image>
-										<view>{{liveItem.liveViewData?.like||0}}</view>
-									</view>
-									<view class="icon ml20">
-										<button open-type="share" class="button">
-											<image src="/static/images/live/share.png" class="w56 h56"></image>
-										</button>
-										<view>分享</view>
-									</view> -->
 									<view class="icon-bg ml20" @click="openCart(liveItem)">
 										<image src="/static/images/shopping.png" class="w48 h48"></image>
 									</view>
 								</view>
 							</view>
 						</view>
-
-						<!-- 弹出商品 -->
-						<!--  v-if="liveItem.goodsCard.isShow" -->
-						<!-- <view class="goods">
-							<view class="top">
-								<view class="left">
-									<image class="w30 h30 mr8" src="/static/images/signal.png"></image>讲解中
-								</view>
-								<image class="w30 h30" src="/static/images/close.png"
-									@click="liveItem.goodsCard.isShow=false"></image>
-							</view>
-							<image class="photo" :src="liveItem.goodsCard.imgUrl"></image>
-							<view class="item">
-								<view class="price"><text class="red">¥{{liveItem.goodsCard.price}} </text><text
-										class="del">¥{{liveItem.goodsCard.otPrice}}</text></view>
-								<view class="title oneline-hide">{{liveItem.goodsCard.productName}}</view>
-								<view class="button">立即抢购</view>
-							</view>
-						</view> -->
 					</view>
-					<u-popup :show="liveItem.goodsCard.isShow" @close="!liveItem.goodsCard.isShow" round='20rpx'
+					<u-popup :show="liveItem.goodsCard?.isShow" @close="!liveItem.goodsCard?.isShow" round='20rpx'
 						mode="center" bgColor='#ffffff'>
 						<view class="goods">
 							<view class="top">
@@ -186,19 +169,20 @@
 								<image class="w30 h30" src="/static/images/close.png"
 									@click="liveItem.goodsCard.isShow=false"></image>
 							</view>
-							<image class="photo" :src="liveItem.goodsCard.imgUrl"></image>
+							<image class="photo" :src="liveItem.goodsCard?.imgUrl"></image>
 							<view class="item">
-								<view class="price"><text class="red">¥{{liveItem.goodsCard.price}} </text><text
-										class="del">¥{{liveItem.goodsCard.otPrice}}</text></view>
-								<view class="title oneline-hide">{{liveItem.goodsCard.productName}}</view>
+								<view class="price"><text class="red">¥{{liveItem.goodsCard?.price}} </text><text
+										class="del">¥{{liveItem.goodsCard?.otPrice}}</text></view>
+								<view class="title oneline-hide">{{liveItem.goodsCard?.productName}}</view>
 								<view class="button"
-									@click="goShop(liveItem.goodsCard.productId,liveItem.goodsCard.goodsId)">立即抢购</view>
+									@click="goShop(liveItem.goodsCard?.productId,liveItem.goodsCard?.goodsId)">立即抢购
+								</view>
 							</view>
 						</view>
 					</u-popup>
-					
+
 					<!-- 抽奖 -->
-				<!-- 	<u-popup :show="true" @close="" round='40rpx' bgColor='#fff'>
+					<!-- 	<u-popup :show="true" @close="" round='40rpx' bgColor='#fff'>
 						<view class=" p20 bgf" style="border-radius: 40rpx;height: fit-content;">
 							<view class="u-flex-row-reverse u-flex">
 								<u-icon name="close" size="18" @click=""></u-icon>
@@ -330,7 +314,7 @@
 		collectStore, // 店铺收藏/取消收藏
 		collectGoods, // 商品收藏/取消收藏
 		store, // 查询店铺
-		showGoods, //弹出商品卡片
+		// showGoods, //弹出商品卡片
 		// follow, // 关注/取消关注
 		watchUserList, //获取直播间用户(展示在线用户)
 		liveMsg, //获取最近聊天记录
@@ -339,7 +323,8 @@
 		// liveGoodsDetail, //商品详情,
 		liveOrderUser, //正在购买
 		getLiveInfo, //获取直播间信息接口
-		getLiveViewData //直播间点赞、关注、在线人数数据
+		getLiveViewData, //直播间点赞、关注、在线人数数据
+		currentActivities //红包 卡片 抽奖
 	} from '@/api/live'
 	import {
 		liveOrderList, // 订单列表
@@ -371,6 +356,17 @@
 	export default {
 		data() {
 			return {
+				livePlayerInstances: {}, // 存储 live-player 组件实例(key: liveId, value: 实例)
+				livePlayerStates: {}, // 存储每个直播间的直播状态(key: liveId, value: 状态码)
+				liveRetryCounts: {}, // 直播重连次数(key: liveId, value: 次数)
+				maxLiveRetry: 3, // 最大重连次数
+
+
+
+
+				timer: null,
+
+				isShowRed: false,
 				// 新增:滑动节流相关变量
 				lastSwiperChangeTime: 0, // 上次切换时间戳
 				swiperChangeThrottle: 300, // 切换节流阈值(毫秒)
@@ -491,6 +487,7 @@
 			} else {
 				// console.log('当前平台无需显示分享菜单');
 			}
+			this.getCurrentActivities(options.liveId);
 		},
 		onReady() {},
 		onShareAppMessage() {
@@ -520,38 +517,45 @@
 		onHide() {
 			const currentLive = this.list[this.currentSwiperIndex];
 			if (currentLive) {
-				this.pauseVideo(currentLive);
-				this.clearTimeTimer(currentLive); // 隐藏时清除当前直播间定时器
-			} // 隐藏时关闭所有连接
+				// 暂停当前直播/回放
+				if (currentLive.livingUrl) {
+					this.pauseLive(currentLive);
+				} else if (currentLive.videoUrl) {
+					this.pauseVideo(currentLive);
+				}
+				this.clearTimeTimer(currentLive);
+			}
 			this.closeAllWebSockets();
 		},
+
 		onUnload() {
-			// 清除所有直播间的时间定时器
+			// 1. 销毁所有 live-player 实例
+			Object.values(this.livePlayerInstances).forEach(player => {
+				try {
+					player.stop(); // 停止直播
+				} catch (e) {
+					console.error('销毁 live-player 失败', e);
+				}
+			});
+			this.livePlayerInstances = {};
+			this.livePlayerStates = {};
+			this.liveRetryCounts = {};
+
+			// 2. 保留原有清理逻辑(WebSocket、定时器、HLS等)
 			this.list.forEach(liveItem => {
 				this.clearTimeTimer(liveItem);
+				if (liveItem.videoUrl) {
+					const videoContext = uni.createVideoContext(`myVideo_${liveItem.liveId}`, this);
+					videoContext?.pause();
+				}
 			});
-			// 关闭所有WebSocket连接
 			this.closeAllWebSockets();
-			this.socketInstances = {}; // 强制清空实例对象
-			// 移除所有全局事件监听
 			this.removeAllEventListeners();
-
-			// 清理定时器
 			this.clearAllTimers();
-
-			// 销毁HLS播放器(如果使用了hls.js)
 			if (this.hlsPlayer) {
 				this.hlsPlayer.destroy();
 				this.hlsPlayer = null;
 			}
-
-			this.list.forEach(liveItem => {
-				const videoId = `myVideo_${liveItem.liveId}`;
-				const videoContext = uni.createVideoContext(videoId, this);
-				if (videoContext) {
-					videoContext.pause(); // 仅暂停视频即可
-				}
-			});
 		},
 		watch: {
 			// 监听orderUser.count的变化
@@ -565,7 +569,173 @@
 				immediate: true
 			}
 		},
-		methods: { // 计算当前时间与 liveItem.startTime 的差值,并更新 totalTime
+		methods: {
+			// 1. 直播播放器状态变化回调(核心)
+			onLivePlayerStateChange(e) {
+				const liveId = this.getLiveIdFromPlayerId(e.currentTarget.id); // 从ID中解析liveId
+				const state = e.detail.code; // 状态码:0-初始,1-连接中,2-播放中,3-暂停,4-播放结束,5-错误,6-缓冲中
+				this.livePlayerStates[liveId] = state; // 记录当前状态
+
+				const liveItem = this.list.find(item => item.liveId === liveId);
+				if (!liveItem) return;
+
+				// 根据状态码处理逻辑
+				switch (state) {
+					case 1: // 连接中
+						console.log(`直播间${liveId}:正在连接直播流`);
+						break;
+					case 2: // 播放中
+						console.log(`直播间${liveId}:直播已开始`);
+						this.liveRetryCounts[liveId] = 0; // 播放成功,重置重连次数
+						break;
+					case 5: // 错误状态(需重连)
+						console.error(`直播间${liveId}:直播播放错误,状态码=${state}`);
+						this.handleLivePlayerError(liveItem); // 触发错误处理(重连)
+						break;
+					case 6: // 缓冲中
+						uni.showToast({
+							title: '直播缓冲中...',
+							icon: 'none',
+							duration: 1000
+						});
+						break;
+					default:
+						break;
+				}
+			},
+
+			// 2. 直播错误回调(补充 statechange 未覆盖的错误场景)
+			livePlayerError(e) {
+				const liveId = this.getLiveIdFromPlayerId(e.currentTarget.id);
+				const liveItem = this.list.find(item => item.liveId === liveId);
+				if (liveItem) {
+					this.handleLivePlayerError(liveItem);
+				}
+			},
+
+			// 3. 直播错误统一处理(重连逻辑)
+			handleLivePlayerError(liveItem) {
+				const liveId = liveItem.liveId;
+				// 初始化重连次数
+				if (this.liveRetryCounts[liveId] === undefined) {
+					this.liveRetryCounts[liveId] = 0;
+				}
+				// 超过最大重连次数,停止重试
+				if (this.liveRetryCounts[liveId] >= this.maxLiveRetry) {
+					uni.showToast({
+						title: `直播连接失败,请检查网络(已重试${this.maxLiveRetry}次)`,
+						icon: 'none',
+						duration: 3000
+					});
+					return;
+				}
+				// 重连计数+1,延迟重试(指数退避:1s→2s→4s)
+				this.liveRetryCounts[liveId]++;
+				const delay = 1000 * Math.pow(2, this.liveRetryCounts[liveId] - 1);
+				setTimeout(() => {
+					console.log(`直播间${liveId}:第${this.liveRetryCounts[liveId]}次重连`);
+					this.playLive(liveItem); // 触发重连播放
+				}, delay);
+			},
+
+			// 4. 播放直播(获取 live-player 实例并控制播放)
+			playLive(liveItem) {
+				if (!liveItem || !liveItem.livingUrl) return;
+				const playerId = `myLivePlayer_${liveItem.liveId}`;
+
+				// 获取 live-player 实例(兼容小程序异步获取)
+				const getPlayerInstance = () => {
+					return new Promise((resolve) => {
+						const player = uni.createLivePlayerContext(playerId, this);
+						if (player) {
+							this.livePlayerInstances[liveItem.liveId] = player; // 缓存实例
+							resolve(player);
+						} else {
+							// 实例获取失败,100ms后重试(最多3次)
+							let retry = 0;
+							const retryGet = setInterval(() => {
+								const retryPlayer = uni.createLivePlayerContext(playerId, this);
+								if (retryPlayer || retry >= 3) {
+									clearInterval(retryGet);
+									resolve(retryPlayer);
+								}
+								retry++;
+							}, 100);
+						}
+					});
+				};
+
+				// 控制播放
+				getPlayerInstance().then(player => {
+					if (player) {
+						player.play({
+							success: () => {
+								console.log(`直播间${liveItem.liveId}:直播播放指令已发送`);
+							},
+							fail: (err) => {
+								console.error(`直播间${liveItem.liveId}:播放指令失败`, err);
+							}
+						});
+					}
+				});
+			},
+
+			// 5. 暂停直播(切换swiper时调用)
+			pauseLive(liveItem) {
+				if (!liveItem) return;
+				const player = this.livePlayerInstances[liveItem.liveId];
+				if (player) {
+					player.pause({
+						success: () => {
+							console.log(`直播间${liveItem.liveId}:直播已暂停`);
+						},
+						fail: (err) => {
+							console.error(`直播间${liveItem.liveId}:暂停失败`, err);
+						}
+					});
+				}
+			},
+
+			// 6. 从 playerId 中解析 liveId(辅助方法)
+			getLiveIdFromPlayerId(playerId) {
+				// playerId 格式:myLivePlayer_123456 → 提取 123456
+				return playerId.replace('myLivePlayer_', '');
+			},
+
+			// 7. 全屏变化处理(可选,补全体验)
+			onLiveFullScreenChange(e) {
+				const isFullScreen = e.detail.fullScreen;
+				const liveId = this.getLiveIdFromPlayerId(e.currentTarget.id);
+				console.log(`直播间${liveId}:${isFullScreen ? '进入' : '退出'}全屏`);
+				// 可根据全屏状态调整页面样式(如隐藏底部输入框)
+				this.isLiveFullScreen = isFullScreen;
+			},
+
+			//  红包 卡片 抽奖
+			getCurrentActivities(liveItem) {
+				if (!liveItem.liveId) return;
+				// const res = currentActivities(liveId);
+				currentActivities(liveItem.liveId).then(res => {
+						if (res.code == 200) {
+							console.log("红包 卡片 抽奖res>>>", res)
+							// 商品卡片
+							this.$set(liveItem, 'goodsCard', res.goods);
+							// 红包
+							this.$set(liveItem, 'redInfo', res.red[0]);
+							this.isShowRed = true
+							// 抽奖
+							this.$set(liveItem, 'lottery', res.lottery);
+						} else {
+							uni.showToast({
+								title: res.msg,
+								icon: 'none'
+							});
+						}
+					},
+					rej => {}
+				);
+			},
+			// 计算当前时间与 liveItem.startTime 的差值,并更新 totalTime
 			calculateTimeDiff(liveItem) {
 				// 1. 校验 startTime 格式(防止无效时间)
 				if (!liveItem.startTime) {
@@ -714,8 +884,9 @@
 					// 然后并行加载其他数据
 					await Promise.allSettled([
 						this.getLiveMsg(currentLive),
+						this.getCurrentActivities(currentLive),
 						this.getliveViewData(currentLive),
-						this.getShowGoods(currentLive),
+						// this.getShowGoods(currentLive),
 						this.getliving(currentLive)
 					]);
 
@@ -787,48 +958,47 @@
 				const oldIndex = this.currentSwiperIndex;
 
 				// 相同索引或节流期间,不处理
-				if (newIndex === oldIndex) return;
-				if (now - this.lastSwiperChangeTime < this.swiperChangeThrottle) {
-					console.log('Swiper切换过快,已节流');
+				if (newIndex === oldIndex || now - this.lastSwiperChangeTime < this.swiperChangeThrottle) {
 					return;
 				}
-
-				// 更新最后切换时间
 				this.lastSwiperChangeTime = now;
 
-				// 1. 立即关闭旧直播间的WebSocket(同步操作
+				// 1. 暂停旧直播间(直播用 pauseLive,回放用 pauseVideo
 				const oldLive = this.list[oldIndex];
-				this.closeShop()
 				if (oldLive) {
-					this.pauseVideo(oldLive);
-					this.clearTimeTimer(oldLive); // 清除旧直播间的时间定时器
-					console.log("oldLive", oldLive)
-					// 强制关闭并删除实例,确保连接被释放
+					if (oldLive.livingUrl) {
+						this.pauseLive(oldLive); // 暂停旧直播
+						// 清理旧直播状态
+						delete this.livePlayerInstances[oldLive.liveId];
+						delete this.livePlayerStates[oldLive.liveId];
+						delete this.liveRetryCounts[oldLive.liveId];
+					} else if (oldLive.videoUrl) {
+						this.pauseVideo(oldLive); // 暂停旧回放
+					}
+					this.clearTimeTimer(oldLive); // 清除旧直播间定时器
+					// 关闭旧直播间WebSocket(原有逻辑保留)
 					if (this.socketInstances[oldLive.liveId]) {
-						try {
-							this.socketInstances[oldLive.liveId].instance.close({
-								code: 1000,
-								reason: '切换直播间'
-							});
-							console.log(`关闭旧直播间 ${oldLive.liveId} WebSocket`);
-						} catch (e) {
-							console.error('关闭旧连接失败:', e);
-						}
-						delete this.socketInstances[oldLive.liveId]; // 彻底删除实例
+						this.closeWebSocket(oldLive.liveId);
 					}
 				}
 
-				// 2. 处理新直播间(后续逻辑不变)
+				// 2. 处理新直播间
 				this.currentSwiperIndex = newIndex;
 				const newLive = this.list[newIndex];
 				if (!newLive) return;
 
 				this.liveId = newLive.liveId;
 				this.resetUserListParams();
-				await this.initCurrentLiveData(); // 内部会启动新直播间的定时器
+				await this.initCurrentLiveData(); // 初始化当前直播间数据
 				this.getliveUser(false);
+
+				// 3. 播放新直播间(直播用 playLive,回放用 playVideo)
 				setTimeout(() => {
-					this.playVideo(newLive);
+					if (newLive.livingUrl) {
+						this.playLive(newLive); // 播放新直播
+					} else if (newLive.videoUrl) {
+						this.playVideo(newLive); // 播放新回放
+					}
 				}, 100);
 			},
 			// 获取直播间列表
@@ -922,8 +1092,9 @@
 						await Promise.all([
 							this.getliving(liveItem),
 							this.getLiveMsg(liveItem),
+							this.getCurrentActivities(liveItem),
 							this.getliveViewData(liveItem),
-							this.getShowGoods(liveItem)
+							// this.getShowGoods(liveItem)
 						]);
 						liveItem.loaded = true;
 					} catch (error) {
@@ -1051,18 +1222,18 @@
 			onRed(item) {
 				// console.log("点了this.liveId", item)
 				if (!item.liveId) return;
-				// console.log("点了this.liveId", this.liveId)
 				if (!item.redInfo?.redId) return;
-				// console.log("点了this.redInfo?.redId", this.redInfo.redId)
-				// console.log("点了this.userinfo.userId", this.userinfo.userId)
+
 				let data = {
 					liveId: item.liveId,
 					userId: this.userinfo.userId,
 					redId: item.redInfo.redId,
 				}
-				console.log("点了this.liveId", data)
 				liveRed(data).then(res => {
+						this.isShowRed = false
 						if (res.code == 200) {
+							console.log(this.isShowRed)
+							console.log("点了this.liveId", liveItem.redInfo)
 							uni.showToast({
 								title: res.msg,
 								icon: 'none'
@@ -1338,56 +1509,7 @@
 					console.error('点赞失败:', error);
 				}
 			},
-			// 点赞
-			// async onLike(liveItem) {
-			// 	// 基础校验
-			// 	if (!liveItem || !liveItem.liveId) return;
-
-			// 	// 确保liveViewData存在(初始化默认值)
-			// 	if (!liveItem.liveViewData) {
-			// 		await this.getliveViewData(liveItem);
-			// 		if (!liveItem.liveViewData) {
-			// 			uni.showToast({
-			// 				title: "数据加载中,请稍后再试",
-			// 				icon: 'none'
-			// 			});
-			// 			return;
-			// 		}
-			// 		// 初始化点赞数(防止undefined)
-			// 		liveItem.liveViewData.like = liveItem.liveViewData.like || 0;
-			// 	}
-
-			// 	// 限制最多10次点赞
-			// 	const maxLikeCount = 10;
-			// 	if (liveItem.liveViewData.like >= maxLikeCount) {
-			// 		uni.showToast({
-			// 			title: "最多可点赞10次哦~",
-			// 			icon: 'none'
-			// 		});
-			// 		return;
-			// 	}
 
-			// 	try {
-			// 		const res = await liveDataLike(liveItem.liveId);
-			// 		if (res.code === 200) {
-			// 			// 无论后端是否返回res.like,前端按规则累加(最多到10次)
-			// 			const newLikeCount = Math.min(liveItem.liveViewData.like + 1, maxLikeCount);
-			// 			liveItem.liveViewData.like = newLikeCount;
-
-			// 		} else {
-			// 			uni.showToast({
-			// 				title: res.msg || "点赞失败",
-			// 				icon: 'none'
-			// 			});
-			// 		}
-			// 	} catch (error) {
-			// 		console.error('点赞失败:', error);
-			// 		uni.showToast({
-			// 			title: "网络异常,点赞失败",
-			// 			icon: 'none'
-			// 		});
-			// 	}
-			// },
 			//直播间点赞、关注、在线人数数据
 			async getliveViewData(liveItem) {
 				if (!liveItem || !liveItem.liveId) return;
@@ -1407,17 +1529,17 @@
 				}
 			},
 			//弹出商品卡片
-			async getShowGoods(liveItem) {
-				if (!liveItem || !liveItem.liveId) return;
-				try {
-					const res = await showGoods(liveItem.liveId);
-					if (res.code == 200) {
-						this.$set(liveItem, 'goodsCard', res);
-					}
-				} catch (error) {
-					console.error("暂无商品卡片", error);
-				}
-			},
+			// async getShowGoods(liveItem) {
+			// 	if (!liveItem || !liveItem.liveId) return;
+			// 	try {
+			// 		const res = await showGoods(liveItem.liveId);
+			// 		if (res.code == 200) {
+			// 			this.$set(liveItem, 'goodsCard', res);
+			// 		}
+			// 	} catch (error) {
+			// 		console.error("暂无商品卡片", error);
+			// 	}
+			// },
 			// 去购买,跳商品详情
 			goShop(productId, goodsId) {
 				const currentLive = this.list[this.currentSwiperIndex];
@@ -1743,91 +1865,57 @@
 			// 处理Socket消息
 			handleSocketMessage(message, liveItem) {
 				let data = JSON.parse(message.data)
-				const socketMessage = data.data
-				// console.log("处理",data)
+				const socketMessage = data.data // 服务端返回的消息体
 				if (data.code == 200) {
 					const messageData = {
-						...socketMessage, // 原数据(可能包含部分字段)
-						cmd: socketMessage.cmd || '' // 强制添加cmd,确保存在(默认空字符串)
+						...socketMessage,
+						cmd: socketMessage.cmd || '', // 确保cmd字段存在
+						ts: Date.now() // 新增时间戳,用于排序(可选)
 					};
-					// console.log("messageData是",messageData)
+
+					// 【关键修改2】处理服务端返回的sendMsg消息,加入本地列表
 					if (socketMessage.cmd == 'sendMsg') {
-						// this.$set(liveItem, 'talklist', [...liveItem.talklist, messageData]);
+						// 1. 将消息追加到当前直播间的talklist中(响应式更新)
+						this.$set(liveItem, 'talklist', [...(liveItem.talklist || []), messageData]);
+						// 2. 延迟下一帧滚动到最新消息(确保DOM已更新)
 						this.$nextTick(() => {
-							this.$set(liveItem, 'scrollIntoView', `list_${liveItem.talklist.length-1}`);
+							const lastIndex = liveItem.talklist.length - 1;
+							this.$set(liveItem, 'scrollIntoView', `list_${lastIndex}`);
 						});
-					} else if (socketMessage.cmd == 'red') {
-						// 领红包
+					}
+					// 以下是原有其他cmd的处理逻辑,保持不变
+					else if (socketMessage.cmd == 'red') {
 						this.$set(liveItem, 'redInfo', JSON.parse(socketMessage.data));
-						console.log("红包是liveItem.redInfo", liveItem)
-						// liveItem.redInfo = JSON.parse(message.data.data);
-
-					} else if (socketMessage.cmd == 'sendRedPacketQuestion') {
-						const list = JSON.parse(message.data.data);
-						// 处理红包答题逻辑
+						this.isShowRed = true
+						this.timer = setInterval(() => {
+							this.isShowRed = true
+						}, liveItem.redInfo.duration * 1000)
 					} else if (socketMessage.cmd == 'entry' || socketMessage.cmd == 'out') {
-
 						this.$set(liveItem, 'talklist', [...liveItem.talklist, messageData]);
-						// 先重置为 false(避免残留状态)
 						this.$set(liveItem, 'showWelcomeMessage', false);
-						// 立即设为 true(触发 DOM 更新)
 						this.$set(liveItem, 'showWelcomeMessage', true);
-						this.getliveUser(false); // 调用获取在线用户接口
-						// console.log("开启欢迎消息:", liveItem.showWelcomeMessage);
-						if (liveItem.welcomeTimer) {
-							clearTimeout(liveItem.welcomeTimer);
-						}
-						// 存储定时器到 liveItem 中(避免引用丢失)
+						this.getliveUser(false);
+						if (liveItem.welcomeTimer) clearTimeout(liveItem.welcomeTimer);
 						liveItem.welcomeTimer = setTimeout(() => {
 							this.$set(liveItem, 'showWelcomeMessage', false);
-							// console.log("关闭欢迎消息:", liveItem.showWelcomeMessage);
-						}, 1000); // 1秒后隐藏
+						}, 1000);
 					} else if (socketMessage.cmd == 'Integral') {
 						let integral = {
 							msg: socketMessage.msg,
 							status: true
 						}
-						// 观看奖励事件 弹出框2s 显示msg
 						this.$set(liveItem, 'integral', integral);
-						// uni.showToast({
-						// 	title: socketMessage.msg,
-						// 	icon: 'none',
-						// 	duration: 2000
-						// });
-
 					} else if (socketMessage.cmd == 'blockUser') {
-						//拉黑事件 主动删除用户token,强制跳转到登录页面
 						uni.removeStorage({
-							key: 'AppToken', // 存储 Token 时的 key
+							key: 'AppToken',
 							success: () => {
 								uni.reLaunch({
-									url: '/pages/auth/login' // 登录页路径,需根据项目实际路径调整
+									url: '/pages/auth/login'
 								});
-								console.log('Token 删除成功');
-							},
-							fail: (err) => {
-								console.error('Token 删除失败:', err);
 							}
 						});
 					} else if (socketMessage.cmd == 'goods') {
 						this.$set(liveItem, 'goodsCard', JSON.parse(socketMessage.data));
-					} else if (socketMessage.cmd == 'out') {
-						// console.log("进入:", liveItem);
-						console.log("有人退出了:");
-
-					} else if (socketMessage.cmd == 'live_start') {
-						// 直播开始
-					} else if (socketMessage.cmd == 'live_end') {
-						// 直播结束
-					} else if (socketMessage.data.cmd == 'deleteId') {
-						uni.$emit('deleteId');
-					} else if (socketMessage.data.cmd == 'sendRedPacketQuestion') {
-						// const list = JSON.parse(socketMessage)
-						// liveItem.redanswerAll = [...liveItem.redanswerAll, ...list]
-						// if (liveItem.redanswerAll[1].randomAmount !== null) {
-						// 	liveItem.redanswertips = JSON.parse(liveItem.redanswerAll[0]
-						// 		.randomAmount)
-						// }
 					}
 				} else {
 					uni.showToast({
@@ -1835,7 +1923,6 @@
 						icon: 'none'
 					});
 				}
-
 			},
 			scheduleReconnect(liveId) {
 				if (this.isManualClose || this.reconnectCount >= this.maxReconnectAttempts) return;
@@ -1871,22 +1958,12 @@
 				const liveId = liveItem.liveId;
 				const socketItem = this.socketInstances[liveId];
 
-				// 乐观更新:立即把消息加入到本地聊天列表并清空输入(更好的体验)
-				const localMsg = {
-					cmd: 'sendMsg',
-					msg: text,
-					nickName: this.userinfo.nickName,
-					avatar: this.userinfo.avatar,
-					userId: this.userinfo.userId,
-					local: true, // 标记为本地消息(可用于样式或后续差异处理)
-					ts: Date.now()
-				};
-				this.$set(liveItem, 'talklist', [...(liveItem.talklist || []), localMsg]);
-				this.$set(liveItem, 'value', ''); // 立即清空输入
+				// 【关键修改1】删除“发送时直接加入本地列表”的代码,仅清空输入框
+				this.$set(liveItem, 'value', ''); // 立即清空输入框,提升体验
 
+				// 检查socket连接状态
 				if (!socketItem || !socketItem.instance || !socketItem.isOpen) {
-					// 如果 socket 未连接,尝试建立连接(但不要阻塞用户)
-					this.initSocket(liveItem);
+					this.initSocket(liveItem); // 尝试重连
 					uni.showToast({
 						title: "连接未建立,正在重连",
 						icon: 'none'
@@ -1894,21 +1971,23 @@
 					return;
 				}
 
+				// 构造发送给服务端的消息数据
 				const data = {
 					liveId,
 					userId: this.userinfo.userId,
 					userType: 0,
-					cmd: "sendMsg",
+					cmd: "sendMsg", // 指令标识,服务端会根据此返回sendMsg消息
 					msg: text,
 					nickName: this.userinfo.nickName,
 					avatar: this.userinfo.avatar
 				};
 
+				// 发送socket消息
 				try {
 					socketItem.instance.send({
 						data: JSON.stringify(data),
 						success: () => {
-							// 比如找到最后一条 local 消息并标记为 sent
+							// 发送成功无需额外操作,等待服务端返回消息后再更新列表
 						},
 						fail: (err) => {
 							console.error("发送失败:", err);
@@ -1916,13 +1995,17 @@
 								title: "发送失败,请重试",
 								icon: 'none'
 							});
-							// 可选择回滚本地显示(此处简单提示即可)
+							// 发送失败时可恢复输入框内容(可选,提升体验)
+							this.$set(liveItem, 'value', text);
 						}
 					});
 				} catch (err) {
 					console.error('socket send 异常', err);
+					// 异常时恢复输入框内容(可选)
+					this.$set(liveItem, 'value', text);
 				}
 			}
+
 		}
 	};
 </script>
@@ -2129,6 +2212,8 @@
 			z-index: 5;
 			border-radius: 16rpx;
 			background-color: rgba(77, 77, 77, 0.5);
+			width: 90rpx;
+			height: 100rpx;
 
 			.tip {
 				position: absolute;
@@ -2139,7 +2224,6 @@
 				background-color: rgba(15, 15, 15, 0.8);
 				border-radius: 16rpx;
 				text-align: center;
-				padding: 4rpx 0;
 			}
 
 			.item {
@@ -2147,7 +2231,7 @@
 
 				image {
 					width: 80rpx;
-					margin: 6rpx 0;
+					height: 100%;
 				}
 			}
 		}
@@ -2255,6 +2339,35 @@
 			width: 100%;
 			background-color: rgba(57, 57, 57, 0.4);
 
+			.videolist {
+				position: relative;
+
+				.video {
+					height: 100vh;
+					width: 100%;
+					background-color: rgba(57, 57, 57, 0.4);
+
+					.time {
+						color: #ffffff;
+						font-size: 20rpx;
+						margin-left: 10rpx;
+					}
+
+					// 横竖屏样式(直播和回放共用)
+					.videotop {
+						width: 100%;
+						height: 100%;
+					}
+
+					.video_row {
+						width: 100%;
+						max-height: 500rpx;
+						overflow: hidden;
+						margin-top: 360rpx;
+					}
+				}
+			}
+
 			.time {
 				color: #ffffff;
 				font-size: 20rpx;
@@ -2408,24 +2521,6 @@
 
 	}
 
-
-
-
-	.icon {
-		font-weight: 500;
-		font-size: 24rpx;
-		color: #FFFFFF;
-		text-align: center;
-		margin-bottom: -10rpx;
-
-		.button {
-			background-color: transparent;
-			margin: 0;
-			line-height: 1;
-			padding: 0;
-		}
-	}
-
 	.icon-bg {
 		background-color: rgba(57, 57, 57, 0.8);
 		border-radius: 50%;
@@ -2518,7 +2613,8 @@
 			background: #fff;
 		}
 	}
-// 抽奖
+
+	// 抽奖
 	.answerpop {
 		background: linear-gradient(to right, #fff7f8, #fee1e2);
 		margin-top: 30rpx;
@@ -2548,6 +2644,7 @@
 			}
 		}
 	}
+
 	.shoppop {
 		padding: 22rpx 16rpx;
 

+ 8 - 23
pages/list/index.vue

@@ -4,30 +4,23 @@
 			:down="downOption" :up="upOption">
 			<view class="list">
 				<view class="list-item" @click="goLive(item)" v-for="(item,index) in list" :key="index">
-					<image v-if="!item._isInView && item.liveImgUrl" :src="item.liveImgUrl"></image>
+					<image v-if="item.liveImgUrl" :src="item.liveImgUrl"></image>
 					<!-- 直播流 -->
-					<video v-if="item._isInView && item.liveType == 1 && item.flvHlsUrl" :id="'myVideo_' + item.liveId"
-						:src="item.flvHlsUrl" :autoplay="false" :controls="false" object-fit="contain"
+					<video v-if=" item.liveType == 1 && item.flvHlsUrl" :id="'myVideo_' + item.liveId"
+						:src="item.flvHlsUrl" :autoplay="false" muted  :controls="false" object-fit="contain"
 						:custom-cache="false" :enable-progress-gesture="false" :show-center-play-btn="false"
 						:http-cache="false" :muted="true" @error="onVideoError(item, $event)"></video>
 
 					<!-- 录播视频 -->
-					<video v-if="item._isInView && item.liveType == 2 && item.videoUrl " :id="'myVideo_' + item.liveId"
-						:src="item.videoUrl" :autoplay="false" :controls="false" object-fit="contain"
+					<video v-if=" item.liveType == 2 && item.videoUrl " :id="'myVideo_' + item.liveId"
+						:src="item.videoUrl" :autoplay="false" muted  :controls="false" object-fit="contain"
 						:custom-cache="false" :enable-progress-gesture="false" :show-center-play-btn="false"
 						:http-cache="false" :loop="true" :muted="true" @error="onVideoError(item, $event)"
 						@loadedmetadata="onVideoLoaded(item)"></video>
 
 					<view class="info">
-						<!-- <text v-if="item.liveType == 1" class="live-badge">直播</text>
-						<text v-else class="record-badge">录播</text> -->
 						<text>{{item.liveName}}</text>
 					</view>
-
-					<!-- 添加错误提示 -->
-					<!-- <view v-if="item._error" class="error-tip">
-						视频加载失败
-					</view> -->
 				</view>
 			</view>
 		</mescroll-body>
@@ -117,11 +110,6 @@
 				}).observe(`#${videoId}`, (res) => {
 					const isInView = res.intersectionRatio > 0; // 是否进入视口(交叉比例>0)
 
-					// 1. 动态更新 _isInView 状态(确保响应式)
-					if (isInView !== item._isInView) {
-						this.$set(item, '_isInView', isInView);
-					}
-
 					// 2. 联动播放/暂停:进入视口播放,离开暂停
 					if (isInView) {
 						this.playVideo(item); // 进入视口:播放视频
@@ -136,8 +124,8 @@
 
 			// 播放视频
 			playVideo(item) {
-				// 仅当:进入视口(_isInView=true)、未在播放(_isPlaying=false)、无错误(_error=false)时播放
-				if (!item._isInView || item._isPlaying || item._error) return;
+				
+				if ( item._isPlaying || item._error) return;
 
 				const videoId = `myVideo_${item.liveId}`;
 				const isLive = item.liveType == 1;
@@ -212,7 +200,6 @@
 					this.$set(item, '_hlsInstance', null);
 				}
 				//#endif
-				// 暂停后标记 _isPlaying=false(_isInView 已由视口监听控制)
 				this.$set(item, '_isPlaying', false);
 			},
 
@@ -244,8 +231,7 @@
 			cleanupAllVideos() {
 				this.list.forEach(item => {
 					this.pauseVideo(item);
-					// 重置视口状态:未进入视口
-					this.$set(item, '_isInView', false);
+					
 					// 销毁观察器
 					if (item._observer) {
 						item._observer.disconnect();
@@ -288,7 +274,6 @@
 								...item,
 								_error: false,
 								_isPlaying: false,
-								_isInView: false // 关键:初始化为未进入视口
 							};
 						});
 

Plik diff jest za duży
+ 0 - 0
unpackage/dist/dev/mp-weixin/api/live.js


Plik diff jest za duży
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/home/living.js


Plik diff jest za duży
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/home/living.wxml


Plik diff jest za duży
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/home/living.wxss


Plik diff jest za duży
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/list/index.js


+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/list/index.wxml

@@ -1 +1 @@
-<view class="content data-v-7bd28468"><mescroll-body wx:if="{{f}}" class="r data-v-7bd28468" u-s="{{['d']}}" u-r="mescrollRef" bindinit="{{c}}" binddown="{{d}}" bindup="{{e}}" u-i="7bd28468-0" bind:__l="__l" u-p="{{f}}"><view class="list data-v-7bd28468"><view wx:for="{{a}}" wx:for-item="item" wx:key="n" class="list-item data-v-7bd28468" bindtap="{{item.m}}"><image wx:if="{{item.a}}" class="data-v-7bd28468" src="{{item.b}}"></image><video wx:if="{{item.c}}" class="data-v-7bd28468" id="{{item.d}}" src="{{item.e}}" autoplay="{{false}}" controls="{{false}}" object-fit="contain" custom-cache="{{false}}" enable-progress-gesture="{{false}}" show-center-play-btn="{{false}}" http-cache="{{false}}" muted="{{true}}" binderror="{{item.f}}"></video><video wx:if="{{item.g}}" class="data-v-7bd28468" id="{{item.h}}" src="{{item.i}}" autoplay="{{false}}" controls="{{false}}" object-fit="contain" custom-cache="{{false}}" enable-progress-gesture="{{false}}" show-center-play-btn="{{false}}" http-cache="{{false}}" loop="{{true}}" muted="{{true}}" binderror="{{item.j}}" bindloadedmetadata="{{item.k}}"></video><view class="info data-v-7bd28468"><text class="data-v-7bd28468">{{item.l}}</text></view></view></view></mescroll-body></view>
+<view class="content data-v-7bd28468"><mescroll-body wx:if="{{f}}" class="r data-v-7bd28468" u-s="{{['d']}}" u-r="mescrollRef" bindinit="{{c}}" binddown="{{d}}" bindup="{{e}}" u-i="7bd28468-0" bind:__l="__l" u-p="{{f}}"><view class="list data-v-7bd28468"><view wx:for="{{a}}" wx:for-item="item" wx:key="n" class="list-item data-v-7bd28468" bindtap="{{item.m}}"><image wx:if="{{item.a}}" class="data-v-7bd28468" src="{{item.b}}"></image><video wx:if="{{item.c}}" class="data-v-7bd28468" id="{{item.d}}" src="{{item.e}}" autoplay="{{false}}" muted controls="{{false}}" object-fit="contain" custom-cache="{{false}}" enable-progress-gesture="{{false}}" show-center-play-btn="{{false}}" http-cache="{{false}}" muted="{{true}}" binderror="{{item.f}}"></video><video wx:if="{{item.g}}" class="data-v-7bd28468" id="{{item.h}}" src="{{item.i}}" autoplay="{{false}}" muted controls="{{false}}" object-fit="contain" custom-cache="{{false}}" enable-progress-gesture="{{false}}" show-center-play-btn="{{false}}" http-cache="{{false}}" loop="{{true}}" muted="{{true}}" binderror="{{item.j}}" bindloadedmetadata="{{item.k}}"></video><view class="info data-v-7bd28468"><text class="data-v-7bd28468">{{item.l}}</text></view></view></view></mescroll-body></view>

+ 1 - 1
unpackage/dist/dev/mp-weixin/project.config.json

@@ -13,7 +13,7 @@
   },
   "compileType": "miniprogram",
   "libVersion": "3.4.2",
-  "appid": "wxd70f99287830cb51",
+  "appid": "wx73f85f8d62769119",
   "projectname": "签约",
   "condition": {
     "search": {

+ 1 - 1
unpackage/dist/dev/mp-weixin/project.private.config.json

@@ -2,7 +2,7 @@
   "projectname": "签约",
   "setting": {
     "compileHotReLoad": true,
-    "urlCheck": true
+    "urlCheck": false
   },
   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
   "condition": {

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików