Просмотр исходного кода

Merge branch 'master' of http://1.14.104.71:10880/liujiaxin/baiyuchengpin

李妹妹 1 неделя назад
Родитель
Сommit
bf1384cfb2
1 измененных файлов с 275 добавлено и 44 удалено
  1. 275 44
      pages_course/living.vue

+ 275 - 44
pages_course/living.vue

@@ -685,6 +685,24 @@
 					</view>
 				</view>
 			</view>
+
+			<!-- 完课积分弹窗 -->
+			<u-popup :show="showCompletionPoints" round="20rpx" mode="center" bgColor="#ffffff" zIndex="10076">
+				<view class="completion-points-popup">
+					<view class="popup-icon">🎉</view>
+					<view class="popup-title">恭喜完成观看任务!</view>
+					<view class="popup-content">
+						<view class="content-item">观看时长达标</view>
+						<view class="content-item highlight">奖励积分:{{ completionPointsData?.pointsAwarded || 0 }} 分</view>
+						<view class="content-item">连续完课:第 {{ completionPointsData?.continuousDays || 1 }} 天</view>
+					</view>
+					<view class="popup-buttons">
+						<view class="popup-button secondary" @click="closeCompletionPoints">稍后再说</view>
+						<view class="popup-button primary" @click="receiveCompletionPoints">立即领取</view>
+					</view>
+				</view>
+			</u-popup>
+
 			<view v-if="liveItem.status == 1" class="ash_bg"></view>
 		</view>
 	</view>
@@ -785,6 +803,8 @@
 				totalTraffic: 0, // 总流量(字节)
 				bitrate: 800, // 录播默认码率 0.16Mbps
 				bitrateLive: 1600, // 直播默认码率 0.16Mbps
+				videoLoaded: false, // 视频是否加载成功
+				trafficStartTime: 0, // 流量计算开始时间
 
 				//定时器
 				trafficTimer: null,
@@ -815,6 +835,10 @@
 
 				stayTime: 0,
 				startTime: 0,
+				watchStartTime: 0, // 观看开始时间(毫秒)
+				totalWatchTime: 0, // 总观看时长(秒)
+				showCompletionPoints: false, // 是否显示完课积分弹窗
+				completionPointsData: null, // 完课积分数据
 
 				scrollTop: 0,
 				currentScrollTop: 0, // 当前实际滚动位置
@@ -1174,6 +1198,9 @@
 				this.startTime = 0;
 				this.totalTraffic = 0;
 			}
+			// 重置视频加载状态和流量计算开始时间
+			this.videoLoaded = false;
+			this.trafficStartTime = 0;
 			this.startTimer();
 
 			this.$nextTick(() => {
@@ -1333,8 +1360,14 @@
 			//  清除所有定时器(使用增强清理)
 			// this.clearAllTimersEnhanced();
 			// this.stopHeartBeat();
+			// 页面隐藏时,提交当前流量数据
+			if (this.videoLoaded && this.trafficStartTime && this.trafficTimer) {
+				const watchDuration = Math.floor((Date.now() - this.trafficStartTime) / 1000);
+				this.submitTraffic(watchDuration);
+			}
 			if (this.trafficTimer) {
 				clearInterval(this.trafficTimer);
+				this.trafficTimer = null;
 			}
 
 			// 页面隐藏时清理部分数据,减少内存占用
@@ -1351,6 +1384,14 @@
 			// 保存视频进度
 			this.saveVideoProgress();
 
+			// 用户退出时,再次请求一次10s流量接口
+			if (this.videoLoaded && this.trafficStartTime) {
+				const watchDuration = Math.floor((Date.now() - this.trafficStartTime) / 1000);
+				// 提交最后一次流量数据(至少10秒)
+				const finalDuration = Math.max(watchDuration, 10);
+				this.submitTraffic(finalDuration);
+			}
+
 			// 清理直播相关定时器
 			if (this.liveItem) {
 				this.pauseVideo();
@@ -2585,62 +2626,110 @@
 			// 		that.calculateTraffic(bitrate);
 			// 	}, 10000); // 每10秒计算一次
 			// },
-			//直播计算流量
+			//直播计算流量 - 统一流量计算方法
 			startTrafficCalculation() {
+				// 检查必要数据
+				if (!this.liveItem || !this.liveItem.videoFileSize) {
+					console.warn('视频文件大小数据不完整,无法启动流量计算');
+					return;
+				}
+				
+				// 检查视频总时长
+				const totalDuration = this.liveItem.videoDuration || this.liveItem.duration;
+				if (!totalDuration) {
+					console.warn('视频总时长数据不完整,无法启动流量计算');
+					return;
+				}
+
+				// 清除已有定时器
 				if (this.trafficTimer) {
 					clearInterval(this.trafficTimer);
 					this.trafficTimer = null;
 				}
 
-				this.startTime = Date.now();
+				// 记录流量计算开始时间
+				this.trafficStartTime = Date.now();
 				var that = this;
 
-				// 计算码率
-				let bitrate = this.calculateBitrate();
+				// 立即提交一次10s流量(模拟10秒观看)
+				setTimeout(() => {
+					that.submitTraffic(10);
+				}, 100); // 延迟100ms确保数据已准备好
 
+				// 启动定时器,每10秒计算并提交一次流量
 				this.trafficTimer = setInterval(() => {
-					that.calculateTraffic(bitrate);
+					that.calculateAndSubmitTraffic();
 				}, 10000); // 每10秒计算一次
 			},
-			// 计算流量
-			// calculateTraffic(bitrate) {
-			// 	const currentTime = Date.now();
-			// 	const duration = (currentTime - this.startTime) / 1000; // 持续时间(秒)
-			// 	// 流量 = 码率 × 时间
-			// 	// 码率单位: bps, 时间单位: 秒, 流量单位: 比特
-			// 	const trafficBits = bitrate * duration;
-			// 	// 转换为字节
-			// 	this.totalTraffic = trafficBits / 8;
-			// 	this.getLiveInternetTraffic();
-			// },
-			calculateBitrate() {
-				// 如果接口返回了视频文件大小和时长,使用这些数据计算码率
-				if (this.liveItem.videoFileSize && this.liveItem.videoDuration) {
-					// 码率 = 文件大小(字节) / 时长(秒) × 8 (转换为bps) × 5
-					const calculatedBitrate = (this.liveItem.videoFileSize / this.liveItem.videoDuration) * 8 * 5;
-					console.log(
-						`使用接口数据计算码率: ${calculatedBitrate} bps (文件大小: ${this.liveItem.videoFileSize} 字节, 时长: ${this.liveItem.videoDuration} 秒)`
-					);
-					return calculatedBitrate;
-				} else {
-					// 如果任一字段为空,使用默认码率 1500 bps
-					console.log('接口数据不完整,使用默认码率: 1500 bps');
-					return 1500;
+			// 计算并提交流量
+			calculateAndSubmitTraffic() {
+				if (!this.liveItem || !this.liveItem.videoFileSize) {
+					const totalDuration = this.liveItem.videoDuration || this.liveItem.duration;
+					if (!totalDuration) {
+						return;
+					}
 				}
+
+				// 计算用户观看视频时长(秒)
+				const watchDuration = Math.floor((Date.now() - this.trafficStartTime) / 1000);
+				
+				// 获取视频总时长(秒)
+				const totalDuration = this.liveItem.videoDuration || this.liveItem.duration || 0;
+				
+				// 获取视频文件大小(字节)
+				const videoFileSize = this.liveItem.videoFileSize || 0;
+
+				if (totalDuration <= 0 || videoFileSize <= 0) {
+					console.warn('视频总时长或文件大小无效,无法计算流量');
+					return;
+				}
+
+				// 流量计算方法:用户观看视频时长/视频总时长*视频文件大小
+				const calculatedTraffic = (watchDuration / totalDuration) * videoFileSize;
+				
+				// 更新总流量
+				this.totalTraffic = calculatedTraffic;
+
+				// 提交流量数据
+				this.submitTraffic(watchDuration);
 			},
-			calculateTraffic(bitrate) {
-				const currentTime = Date.now();
-				const duration = (currentTime - this.startTime) / 1000; // 持续时间(秒)
+			// 提交流量数据到后端
+			submitTraffic(watchDuration) {
+				if (!this.liveId || !this.userInfo || !this.userInfo.userId) {
+					return;
+				}
 
-				// 流量 = 码率 × 时间
-				// 码率单位: bps, 时间单位: 秒, 流量单位: 比特
-				const trafficBits = bitrate * duration;
+				// 计算当前观看时长对应的流量
+				const totalDuration = this.liveItem.videoDuration || this.liveItem.duration || 0;
+				const videoFileSize = this.liveItem.videoFileSize || 0;
+				
+				if (totalDuration <= 0 || videoFileSize <= 0) {
+					console.warn('视频总时长或文件大小无效,无法提交流量', {
+						totalDuration,
+						videoFileSize,
+						videoDuration: this.liveItem.videoDuration,
+						duration: this.liveItem.duration
+					});
+					return;
+				}
 
-				// 转换为字节
-				this.totalTraffic = trafficBits / 8;
+				// 流量 = 用户观看视频时长/视频总时长*视频文件大小
+				const traffic = (watchDuration / totalDuration) * videoFileSize;
+
+				const param = {
+					userId: this.userInfo.userId || '',
+					liveId: this.liveId || '',
+					uuId: dayjs().format('YYYYMMDD') + this.uuId,
+					internetTraffic: Math.round(traffic), // 四舍五入到整数
+					watchDuration: watchDuration, // 观看时长(秒)
+					totalDuration: totalDuration, // 视频总时长(秒)
+					videoFileSize: videoFileSize // 视频文件大小(字节)
+				};
 
-				// 调用流量上报接口
-				this.getLiveInternetTraffic();
+				console.log('提交流量数据:', param);
+				liveInternetTraffic(param).catch(err => {
+					console.error('流量数据提交失败:', err);
+				});
 			},
 			startTimer() {
 				this.startTime = Date.now();
@@ -2917,14 +3006,18 @@
 				}
 				this.lastHeartBeatTime = Date.now();
 
+				// 计算观看时长(秒)
+				const watchDuration = this.watchStartTime > 0 ? Math.floor((this.lastHeartBeatTime - this.watchStartTime) / 1000) : 0;
+
 				try {
 					const heartBeatMsg = JSON.stringify({
 						cmd: 'heartbeat',
 						msg: 'ping',
-						userId: this.userInfo.userId || '',
-						liveId: this.liveId,
 						timestamp: this.lastHeartBeatTime,
-						networkType: this.networkType
+						networkType: this.networkType,
+						liveId: this.liveId,
+						userId: this.userInfo.userId || '',
+						data: String(watchDuration) // 观看时长(秒),字符串格式
 					});
 
 					this.socket.send({
@@ -3246,6 +3339,15 @@
 				// }
 				this.videoProgressKey = `videoProgress_${this.liveId}`;
 				this.setVideoProgress();
+				
+				// 视频加载成功后,启动流量计算
+				if (!this.videoLoaded) {
+					this.videoLoaded = true;
+					// 延迟一下确保数据已加载
+					setTimeout(() => {
+						this.startTrafficCalculation();
+					}, 500);
+				}
 			},
 			setVideoProgress() {
 				// 只有录播和回放需要设置进度
@@ -3500,6 +3602,55 @@
 				this.havePrize = false;
 				uni.setStorageSync('havePrize', this.havePrize);
 			},
+			// 领取完课积分
+			async receiveCompletionPoints() {
+				if (!this.completionPointsData || !this.completionPointsData.id) {
+					uni.showToast({
+						title: '数据异常,请重试',
+						icon: 'none'
+					});
+					return;
+				}
+
+				try {
+					uni.showLoading({
+						title: '领取中...'
+					});
+
+					const res = await this.$u.post('/api/live/completion/receive', {
+						recordId: this.completionPointsData.id,
+						userId: this.userInfo.userId
+					});
+
+					uni.hideLoading();
+
+					if (res.code === 0 || res.code === 200) {
+						uni.showToast({
+							title: '积分领取成功!',
+							icon: 'success'
+						});
+						// 关闭弹窗
+						this.showCompletionPoints = false;
+						this.completionPointsData = null;
+					} else {
+						uni.showToast({
+							title: res.msg || '领取失败',
+							icon: 'none'
+						});
+					}
+				} catch (error) {
+					uni.hideLoading();
+					console.error('领取积分失败:', error);
+					uni.showToast({
+						title: '网络异常,请重试',
+						icon: 'none'
+					});
+				}
+			},
+			// 稍后领取
+			closeCompletionPoints() {
+				this.showCompletionPoints = false;
+			},
 			// 商品收藏
 			onGoodsCollect(item) {
 				if (!item || item.length === 0 || !item.goodsId) {
@@ -4237,10 +4388,10 @@
 			},
 			startHeartBeat() {
 				this.stopHeartBeat(); // 先停止现有心跳,防止重复
-				// 使用自适应间隔循环发送心跳
+				// 使用自适应间隔循环发送心跳(改为30秒)
 				this.heartBeatTimer = setInterval(() => {
 					this.sendHeartBeat();
-				}, this.adaptiveHeartBeatInterval);
+				}, 30000); // 30秒间隔
 			},
 			initSocket() {
 				// 检查是否正在连接中
@@ -4349,6 +4500,9 @@
 						this.isConnecting = false;
 						this.isSocketOpen = true;
 
+						// 记录观看开始时间
+						this.watchStartTime = Date.now();
+
 						// 计算连接延迟(性能监控)
 						if (this.connectionStartTime > 0) {
 							this.connectionLatency = Date.now() - this.connectionStartTime;
@@ -4747,6 +4901,16 @@
 									});
 								}
 							});
+						} else if (socketMessage.cmd == 'completionPoints') {
+							// 完课积分弹窗
+							try {
+								const pointsData = socketMessage.data ? JSON.parse(socketMessage.data) : {};
+								this.completionPointsData = pointsData;
+								this.showCompletionPoints = true;
+								console.log('收到完课积分弹窗:', pointsData);
+							} catch (err) {
+								console.error('解析完课积分数据失败:', err);
+							}
 						}
 					} else {
 						uni.showToast({
@@ -6967,6 +7131,73 @@
 					}
 				}
 			}
+
+			// 完课积分弹窗
+			.completion-points-popup {
+				width: 600rpx;
+				padding: 60rpx 40rpx 40rpx;
+				text-align: center;
+				background: #ffffff;
+				border-radius: 20rpx;
+
+				.popup-icon {
+					font-size: 80rpx;
+					margin-bottom: 20rpx;
+				}
+
+				.popup-title {
+					font-size: 36rpx;
+					font-weight: 600;
+					color: #333333;
+					margin-bottom: 30rpx;
+				}
+
+				.popup-content {
+					padding: 30rpx 20rpx;
+					background: #f8f9fa;
+					border-radius: 12rpx;
+					margin-bottom: 30rpx;
+
+					.content-item {
+						font-size: 28rpx;
+						color: #666666;
+						margin: 8rpx 0;
+
+						&.highlight {
+							font-size: 32rpx;
+							font-weight: 600;
+							color: #ff5c03;
+							margin: 16rpx 0;
+						}
+					}
+				}
+
+				.popup-buttons {
+					display: flex;
+					gap: 20rpx;
+					justify-content: space-between;
+
+					.popup-button {
+						flex: 1;
+						height: 80rpx;
+						line-height: 80rpx;
+						border-radius: 40rpx;
+						font-size: 30rpx;
+						font-weight: 500;
+						text-align: center;
+
+						&.secondary {
+							background: #f5f7fa;
+							color: #666666;
+						}
+
+						&.primary {
+							background: linear-gradient(270deg, #ff5c03 0%, #ffac64 100%);
+							color: #ffffff;
+						}
+					}
+				}
+			}
 		}
 	}
 </style>