yuhongqi пре 3 недеља
родитељ
комит
60b76c5e03

+ 1 - 1
pages_company/order/productDetails.vue

@@ -159,7 +159,7 @@
 		</popupBottom>
 
 		<view class="loadding" v-if="loadding==true">
-			<image src="../..https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/share.png"></image>
+			<!-- <image src="../..https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/share.png"></image> -->
 			<text class="text">加载中...</text>
 		</view>
 

+ 1 - 1
pages_company/order/productShowDetails.vue

@@ -83,7 +83,7 @@
 		</view>
 		
 		<view class="loadding" v-if="loadding==true">
-			<image src="../..https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/share.png"></image>
+			<!-- <image src="../..https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/app/vip/share.png"></image> -->
 			<text class="text">加载中...</text>
 		</view>
 		

+ 12 - 1
pages_course/living/components/LiveChatPanel.vue

@@ -290,6 +290,16 @@ export default {
 		},
 
 		appendMessage(message) {
+			let msgId = message.msgId
+			if (!msgId && message.data) {
+				try {
+					const msgdata = typeof message.data === 'string' ? JSON.parse(message.data) : message.data
+					msgId = msgdata?.msgId
+				} catch (e) {}
+			}
+			if (msgId && this.talklist.some(item => item.msgId === msgId)) {
+				return
+			}
 			const last = this.talklist[this.talklist.length - 1]
 			if (last && last.msg === message.msg && String(last.userId) === String(message.userId)) {
 				const age = Date.now() - (last.ts || 0)
@@ -508,7 +518,8 @@ export default {
 			if (!socketReady) {
 				if (retries > 0) {
 					uni.showToast({ title: `连接不稳定,正在重试(${retries}次)...`, icon: 'none' })
-					setTimeout(() => this.sendMsg(retries - 1), 800)
+					await new Promise(r => setTimeout(r, 800))
+					return this.sendMsg(retries - 1)
 				} else {
 					uni.showToast({ title: '连接已断开,发送失败', icon: 'none' })
 					this.value = text

+ 185 - 99
pages_course/living/services/liveSocket.js

@@ -15,6 +15,7 @@ export class LiveSocketManager {
 		this.reconnectCount = 0
 		this.maxReconnectAttempts = 5
 		this.reconnectTimer = null
+		this.connectTimeoutTimer = null
 		this.networkRetryTimer = null
 		this.heartBeatTimer = null
 		this.pingTimeoutTimer = null
@@ -29,6 +30,7 @@ export class LiveSocketManager {
 		this.errorCount = 0
 		this.lastHeartBeatTime = 0
 		this.resolvedWsBase = hooks.wsBaseUrl || ''
+		this._suppressReconnect = false
 		// fs-live-ws:7115=Tomcat HTTP,7116=Netty WebSocket
 		this.fallbackWsHost = hooks.fallbackWsHost || 'ws://127.0.0.1:7116'
 	}
@@ -61,10 +63,8 @@ export class LiveSocketManager {
 	}
 
 	resetState() {
-		this.isSocketOpen = false
-		this.isConnecting = false
+		this._forceCloseAll(true)
 		this.isManualClose = true
-		this.pendingSocketTask = null
 		this.reconnectCount = 0
 		this.heartBeatRetryCount = 0
 		this.lastHeartBeatTime = 0
@@ -87,7 +87,9 @@ export class LiveSocketManager {
 	}
 
 	connect() {
-		if (this.isConnecting || this.pendingSocketTask) return
+		if (this.isConnecting || this.pendingSocketTask) {
+			return
+		}
 		if (!this._networkAvailable()) {
 			if (!this.networkRetryTimer) {
 				this.networkRetryTimer = setTimeout(() => {
@@ -122,20 +124,175 @@ export class LiveSocketManager {
 	close(isManual = true) {
 		if (!this.socket && !this.pendingSocketTask && !this.isSocketOpen && !this.isConnecting) return
 		this.isManualClose = isManual
+		this._forceCloseAll(false)
+	}
+
+	/** 关闭本地 SocketTask,并尝试关闭微信全局 WS(避免 CONNECTING 残留触发 clickCheckTask) */
+	_forceCloseAll(useUniCloseSocket = true) {
 		this.cleanupTimers()
-		try {
-			const socketToClose = this.socket || this.pendingSocketTask
-			this.socket = null
+		this._clearConnectTimeout()
+		const tasks = []
+		if (this.socket) tasks.push(this.socket)
+		if (this.pendingSocketTask && this.pendingSocketTask !== this.socket) {
+			tasks.push(this.pendingSocketTask)
+		}
+		this.socket = null
+		this.pendingSocketTask = null
+		this.isSocketOpen = false
+		this.isConnecting = false
+		tasks.forEach((task) => {
+			try {
+				if (task && typeof task.close === 'function') {
+					task.close({ code: 1000, reason: 'close' })
+				}
+			} catch (err) {
+				console.warn('[LiveSocket] close task failed', err)
+			}
+		})
+		if (useUniCloseSocket) {
+			try {
+				uni.closeSocket({})
+			} catch (err) {
+				// 无活跃连接时会失败,可忽略
+			}
+		}
+	}
+
+	/** uni.connectSocket 必须带 success/fail/complete 之一,否则可能返回 Promise 而非 SocketTask */
+	_createSocketTask(url) {
+		return new Promise((resolve, reject) => {
+			let settled = false
+			const finish = (task, err) => {
+				if (settled) return
+				settled = true
+				if (err) reject(err)
+				else resolve(task)
+			}
+			const options = {
+				url,
+				success: () => {},
+				fail: (err) => finish(null, err || new Error('connectSocket fail')),
+				complete: () => {}
+			}
+			let result
+			try {
+				result = uni.connectSocket(options)
+			} catch (err) {
+				finish(null, err)
+				return
+			}
+			if (result && typeof result.then === 'function') {
+				result.then((task) => {
+					if (task && typeof task.onOpen === 'function') {
+						finish(task)
+					} else {
+						finish(null, new Error('connectSocket Promise 未解析为 SocketTask'))
+					}
+				}).catch((err) => finish(null, err))
+				return
+			}
+			if (result && typeof result.onOpen === 'function') {
+				finish(result)
+				return
+			}
+			finish(null, new Error('connectSocket 未返回 SocketTask'))
+		})
+	}
+
+	_bindSocketTask(socketTask) {
+		this.pendingSocketTask = socketTask
+		this._startConnectTimeout(socketTask)
+
+		socketTask.onOpen(() => {
+			if (this.pendingSocketTask !== socketTask && this.socket !== socketTask) return
+			this._clearConnectTimeout()
 			this.pendingSocketTask = null
+			this.socket = socketTask
+			this.isConnecting = false
+			this.isSocketOpen = true
+			if (this.connectionStartTime > 0) {
+				this.connectionLatency = Date.now() - this.connectionStartTime
+			}
+			this.reconnectCount = 0
+			this._resetReconnectState()
+			this.heartBeatRetryCount = 0
+			console.log('[LiveSocket] onOpen', { latency: this.connectionLatency })
+			this.hooks.onOpen?.()
+			this._startHeartBeat()
+		})
+
+		socketTask.onMessage((res) => {
+			this.messageCount++
+			try {
+				const parsed = parseWsEnvelope(res.data)
+				if (parsed?.socketMessage?.cmd === 'heartbeat') {
+					if (this.pingTimeoutTimer) {
+						clearTimeout(this.pingTimeoutTimer)
+						this.pingTimeoutTimer = null
+					}
+					this.heartBeatRetryCount = 0
+					this._adjustHeartBeatInterval(true)
+					return
+				}
+				this.hooks.onMessage?.(res)
+			} catch (err) {
+				console.error('消息解析异常:', err)
+				this.errorCount++
+			}
+		})
+
+		socketTask.onError((err) => {
+			console.error('[LiveSocket] onError', err)
+			this.errorCount++
 			this.isSocketOpen = false
 			this.isConnecting = false
-			socketToClose?.close({ code: 1000, reason: isManual ? '主动关闭' : '异常关闭' })
-		} catch (err) {
-			console.error('关闭WebSocket失败:', err)
-			this.socket = null
-			this.pendingSocketTask = null
+			this._clearConnectTimeout()
+			if (this.pendingSocketTask === socketTask) {
+				this.pendingSocketTask = null
+			}
+			if (this.socket === socketTask) {
+				this.socket = null
+			}
+			this._stopHeartBeat()
+			this._handleConnectionError('连接错误', err)
+		})
+
+		socketTask.onClose((res) => {
+			console.log('[LiveSocket] onClose', res?.code, res?.reason)
 			this.isSocketOpen = false
 			this.isConnecting = false
+			this._clearConnectTimeout()
+			if (this.pendingSocketTask === socketTask) {
+				this.pendingSocketTask = null
+			}
+			if (this.socket === socketTask) {
+				this.socket = null
+			}
+			this._stopHeartBeat()
+			this.hooks.onClose?.(res)
+			if (!this.isManualClose && !this._suppressReconnect && res.code !== 1000) {
+				this._handleReconnect()
+			}
+		})
+	}
+
+	_startConnectTimeout(socketTask) {
+		this._clearConnectTimeout()
+		this.connectTimeoutTimer = setTimeout(() => {
+			this.connectTimeoutTimer = null
+			if (!this.isSocketOpen && (this.pendingSocketTask === socketTask || this.socket === socketTask)) {
+				console.warn('[LiveSocket] connect timeout, force close')
+				this.close(false)
+				this.isManualClose = false
+				this._handleReconnect()
+			}
+		}, 15000)
+	}
+
+	_clearConnectTimeout() {
+		if (this.connectTimeoutTimer) {
+			clearTimeout(this.connectTimeoutTimer)
+			this.connectTimeoutTimer = null
 		}
 	}
 
@@ -226,16 +383,22 @@ export class LiveSocketManager {
 			return
 		}
 
-		this.isConnecting = true
-		this.isSocketOpen = false
 		this.connectionStartTime = Date.now()
 		this._resetReconnectState()
+		this._suppressReconnect = true
+		this._forceCloseAll(true)
+		this._suppressReconnect = false
+		this.isConnecting = true
+		this.isSocketOpen = false
 
 		let wsUrl
 		try {
 			const baseUrl = await this._resolveWsBaseUrl(liveId)
 			wsUrl = this._buildConnectUrl(baseUrl, config)
 			console.log('[LiveSocket] connect', wsUrl.replace(/APPToken=[^&]+/, 'APPToken=***'))
+			if (wsUrl.startsWith('ws://')) {
+				console.warn('[LiveSocket] 使用 ws:// 地址,微信开发者工具需勾选「不校验合法域名」')
+			}
 		} catch (err) {
 			console.error('构建 WebSocket 地址失败', err)
 			this.isConnecting = false
@@ -244,91 +407,13 @@ export class LiveSocketManager {
 		}
 
 		try {
-			const socketTask = uni.connectSocket({
-				url: wsUrl,
-				fail: (err) => {
-					this.isConnecting = false
-					this.pendingSocketTask = null
-					this._handleConnectionError('连接请求失败', err)
-				}
-			})
-			if (!socketTask || typeof socketTask.onOpen !== 'function') {
-				this.isConnecting = false
-				this._handleConnectionError('连接请求失败', new Error('connectSocket 未返回 SocketTask'))
-				return
-			}
-			// 与 living.vue 一致:仅在 onOpen 后持有 socket,避免连接中 clickCheckTask 访问空队列
-			this.pendingSocketTask = socketTask
-
-			socketTask.onOpen(() => {
-				if (this.pendingSocketTask !== socketTask && this.socket !== socketTask) return
-				this.pendingSocketTask = null
-				this.socket = socketTask
-				this.isConnecting = false
-				this.isSocketOpen = true
-				if (this.connectionStartTime > 0) {
-					this.connectionLatency = Date.now() - this.connectionStartTime
-				}
-				this.reconnectCount = 0
-				this._resetReconnectState()
-				this.heartBeatRetryCount = 0
-				this.hooks.onOpen?.()
-				this._startHeartBeat()
-			})
-
-			socketTask.onMessage((res) => {
-				this.messageCount++
-				try {
-					const parsed = parseWsEnvelope(res.data)
-					if (parsed?.socketMessage?.cmd === 'heartbeat') {
-						if (this.pingTimeoutTimer) {
-							clearTimeout(this.pingTimeoutTimer)
-							this.pingTimeoutTimer = null
-						}
-						this.heartBeatRetryCount = 0
-						this._adjustHeartBeatInterval(true)
-						return
-					}
-					this.hooks.onMessage?.(res)
-				} catch (err) {
-					console.error('消息解析异常:', err)
-					this.errorCount++
-				}
-			})
-
-			socketTask.onError((err) => {
-				console.error('WebSocket连接错误:', err)
-				this.errorCount++
-				this.isSocketOpen = false
-				this.isConnecting = false
-				if (this.pendingSocketTask === socketTask) {
-					this.pendingSocketTask = null
-				}
-				if (this.socket === socketTask) {
-					this.socket = null
-				}
-				this._stopHeartBeat()
-				this._handleConnectionError('连接错误', err)
-			})
-
-			socketTask.onClose((res) => {
-				this.isSocketOpen = false
-				this.isConnecting = false
-				if (this.pendingSocketTask === socketTask) {
-					this.pendingSocketTask = null
-				}
-				if (this.socket === socketTask) {
-					this.socket = null
-				}
-				this._stopHeartBeat()
-				this.hooks.onClose?.(res)
-				if (!this.isManualClose && res.code !== 1000) {
-					this._handleReconnect()
-				}
-			})
+			const socketTask = await this._createSocketTask(wsUrl)
+			this._bindSocketTask(socketTask)
 		} catch (e) {
-			console.error('创建WebSocket异常:', e)
-			this._handleReconnect()
+			console.error('[LiveSocket] 创建连接失败:', e)
+			this.isConnecting = false
+			this.pendingSocketTask = null
+			this._handleConnectionError('连接请求失败', e)
 		}
 	}
 
@@ -437,7 +522,8 @@ export class LiveSocketManager {
 		}
 		if (this.reconnectCount < this.maxReconnectAttempts) {
 			this.reconnectCount++
-			const totalDelay = Math.min(1000 * Math.pow(2, this.reconnectCount - 1) + 10000 + Math.random() * 1000, 30000)
+			const baseWait = this.reconnectCount <= 1 ? 500 : 10000
+			const totalDelay = Math.min(1000 * Math.pow(2, this.reconnectCount - 1) + baseWait + Math.random() * 1000, 30000)
 			this.reconnectTimer = setTimeout(() => {
 				this.reconnectTimer = null
 				if (this._networkAvailable() && !this.isManualClose && !this.isAvailable()) {

+ 3 - 3
pages_course/livingList.vue

@@ -92,7 +92,7 @@
 				});
 			},
 			goLive(item) {
-				if(item.liveId == 2018){
+				if(item.liveId == 2016){
 					uni.navigateTo({
 						//url: `./living?liveId=1195`
 						url: `./livingnew?liveId=${item.liveId}`
@@ -103,7 +103,7 @@
 						url: `./living?liveId=${item.liveId}`
 					});
 				}
-				
+
 			}
 		}
 	}
@@ -158,4 +158,4 @@
 			}
 		}
 	}
-</style>
+</style>

+ 33 - 6
pages_course/livingnew.vue

@@ -1873,6 +1873,9 @@
 			},
 			initSocket() {
 				this.ensureLiveSocket();
+				if (!this.isSocketAvailable()) {
+					this.liveSocket.close(true);
+				}
 				this.liveSocket.isManualClose = false;
 				this.liveSocket.connect();
 			},
@@ -1889,12 +1892,36 @@
 			},
 			handleChatSend(payload) {
 				const { data, resolve, reject } = payload || {};
-				if (!this.isSocketAvailable()) {
-					this.handleReconnect();
-					reject(new Error('socket not ready'));
-					return;
-				}
-				this.sendSocketMessage(data).then(resolve).catch(reject);
+				this._sendChatMessage(data).then(resolve).catch(reject);
+			},
+			async _sendChatMessage(data) {
+				const ready = await this._waitSocketReady(6000);
+				if (!ready) {
+					uni.showToast({ title: '连接未就绪,请稍后重试', icon: 'none' });
+					throw new Error('socket not ready');
+				}
+				return this.sendSocketMessage(data);
+			},
+			_waitSocketReady(timeoutMs = 6000) {
+				if (this.isSocketAvailable()) {
+					return Promise.resolve(true);
+				}
+				this.initSocket();
+				const start = Date.now();
+				return new Promise((resolve) => {
+					const tick = () => {
+						if (this.isSocketAvailable()) {
+							resolve(true);
+							return;
+						}
+						if (Date.now() - start >= timeoutMs) {
+							resolve(false);
+							return;
+						}
+						setTimeout(tick, 200);
+					};
+					tick();
+				});
 			},
 			sendSocketMessage(data) {
 				if (!this.liveSocket) {

+ 1 - 1
pages_index/video.vue

@@ -124,7 +124,7 @@
 			<template v-else-if="recentList.length > 0">
 				<view v-for="(group, gIdx) in recentList" :key="gIdx" class="recent-group">
 					<view class="group-date">
-						<image class="date-icon" src="@/static/images/new/time.png"></image>
+						<!-- <image class="date-icon" src="@/static/images/new/time.png"></image> -->
 						<text class="date-text">{{ group.date }}</text>
 					</view>
 					<view