|
|
@@ -676,6 +676,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>
|
|
|
@@ -802,6 +820,10 @@
|
|
|
|
|
|
stayTime: 0,
|
|
|
startTime: 0,
|
|
|
+ watchStartTime: 0, // 观看开始时间(毫秒)
|
|
|
+ totalWatchTime: 0, // 总观看时长(秒)
|
|
|
+ showCompletionPoints: false, // 是否显示完课积分弹窗
|
|
|
+ completionPointsData: null, // 完课积分数据
|
|
|
|
|
|
scrollTop: 0,
|
|
|
currentScrollTop: 0, // 当前实际滚动位置
|
|
|
@@ -2756,14 +2778,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({
|
|
|
@@ -3334,6 +3360,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) {
|
|
|
@@ -4056,10 +4131,10 @@
|
|
|
},
|
|
|
startHeartBeat() {
|
|
|
this.stopHeartBeat(); // 先停止现有心跳,防止重复
|
|
|
- // 使用自适应间隔循环发送心跳
|
|
|
+ // 使用自适应间隔循环发送心跳(改为30秒)
|
|
|
this.heartBeatTimer = setInterval(() => {
|
|
|
this.sendHeartBeat();
|
|
|
- }, this.adaptiveHeartBeatInterval);
|
|
|
+ }, 30000); // 30秒间隔
|
|
|
},
|
|
|
initSocket() {
|
|
|
// 检查是否正在连接中
|
|
|
@@ -4168,6 +4243,9 @@
|
|
|
this.isConnecting = false;
|
|
|
this.isSocketOpen = true;
|
|
|
|
|
|
+ // 记录观看开始时间
|
|
|
+ this.watchStartTime = Date.now();
|
|
|
+
|
|
|
// 计算连接延迟(性能监控)
|
|
|
if (this.connectionStartTime > 0) {
|
|
|
this.connectionLatency = Date.now() - this.connectionStartTime;
|
|
|
@@ -4566,6 +4644,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({
|
|
|
@@ -6640,6 +6728,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>
|