From 44dd9c4eb8a2489c63a5eb53fd4254bbb3510e95 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Thu, 18 Jun 2026 09:14:57 +0800
Subject: [PATCH] Merge branch 'feature/v9.0/9.0.4'
---
src/pages.json | 6 ++-
src/manifest.json | 16 ++++++-
src/hooks/useGlobalWS.js | 65 ++++++++++++++++++++++++++------
3 files changed, 69 insertions(+), 18 deletions(-)
diff --git a/src/hooks/useGlobalWS.js b/src/hooks/useGlobalWS.js
index edb84c8..eed40a2 100644
--- a/src/hooks/useGlobalWS.js
+++ b/src/hooks/useGlobalWS.js
@@ -4,6 +4,9 @@
let socketTask = null
let heartbeatTimer = null
+// SocketTask 没有稳定的 readyState,这里用事件维护连接状态。
+let isSocketOpen = false
+const HEARTBEAT_INTERVAL = 30000
export function useGlobalWS() {
const userStore = useUserStore();
@@ -13,8 +16,24 @@
const access_token = computed(() => userStore?.userInfo?.access_token)
const {VITE_APP_WS_API_URL} = getEnvObj()
+ // 后端正常业务消息是 JSON;心跳或异常数据不应该中断消息监听。
+ function parseSocketMessage(data) {
+ try {
+ return JSON.parse(data)
+ } catch (err) {
+ console.warn('ws消息解析失败:', data)
+ return null
+ }
+ }
+
+ // 心跳响应只用于保活,不进入业务刷新/退出登录逻辑。
+ function isHeartbeatMessage(payload) {
+ return payload?.type === 'pong' || payload?.type === 'ping'
+ }
+
// 消息处理
function messageHandler(payload) {
+ if (!payload || isHeartbeatMessage(payload)) return
switch (payload.biz_code) {
case 'JOB_ISREFRESH':
appStore.setJobUpdateKeyAdd()
@@ -38,7 +57,10 @@
// 关闭ws
function closeWS() {
stopHeartbeat()
- socketTask?.close({
+ isSocketOpen = false
+ const currentSocketTask = socketTask
+ socketTask = null
+ currentSocketTask?.close({
success: () => {
console.log('ws关闭连接');
},
@@ -54,7 +76,7 @@
+ `?x-auth-token=${encodeURI(access_token?.value)}`
+ `&model_type=3&workspace-id=${userId.value}`
// 创建连接
- socketTask = uni.connectSocket({
+ const currentSocketTask = uni.connectSocket({
url: url,
success: () => {
console.log('ws连接成功');
@@ -63,22 +85,31 @@
console.error('ws连接失败:', err);
}
});
+ socketTask = currentSocketTask
// 消息监听
- socketTask.onMessage((result) => {
- messageHandler(JSON.parse(result.data))
+ currentSocketTask.onMessage((result) => {
+ // 旧连接的异步回调可能晚于新连接触发,忽略旧实例事件。
+ if (socketTask !== currentSocketTask) return
+ messageHandler(parseSocketMessage(result.data))
})
//==================================
// 监听连接打开
- socketTask.onOpen((res) => {
+ currentSocketTask.onOpen(() => {
+ // 连接成功后才启动心跳,避免连接中/已关闭时发送失败。
+ if (socketTask !== currentSocketTask) return
console.log('✅ WebSocket连接已建立')
+ isSocketOpen = true
// reconnectAttempts = 0 // 连接成功后重置重连次数
// 可以在这里发送心跳或订阅消息
- // startHeartbeat()
+ startHeartbeat()
})
// 监听连接关闭
- socketTask.onClose((res) => {
+ currentSocketTask.onClose((res) => {
+ // 关闭后必须停掉心跳,否则定时器会持续发送到失效连接。
+ if (socketTask !== currentSocketTask) return
console.log(`WebSocket连接关闭,代码: ${res.code}, 原因: ${res.reason}`)
- startHeartbeat()
+ isSocketOpen = false
+ stopHeartbeat()
// 根据不同的关闭代码处理
if (res.code === 1000) { // 正常关闭
console.log('连接正常关闭')
@@ -92,21 +123,29 @@
})
// 监听错误
- socketTask.onError((err) => {
+ currentSocketTask.onError((err) => {
+ if (socketTask !== currentSocketTask) return
console.error('WebSocket发生错误:', err)
+ isSocketOpen = false
+ stopHeartbeat()
})
}
function startHeartbeat() {
stopHeartbeat()
heartbeatTimer = setInterval(() => {
- if (socketTask && socketTask.readyState === 1) {
- // 尝试以 JSON 格式发送,并降低频率(30秒一次)以减少服务器负担
+ if (socketTask && isSocketOpen) {
+ // 保持和 PC 端一致,按 JSON ping 包保活。
socketTask.send({
- data: JSON.stringify({ type: 'ping', timestamp: Date.now() })
+ data: JSON.stringify({ type: 'ping', timestamp: Date.now() }),
+ fail: (err) => {
+ console.error('ws心跳发送失败:', err)
+ isSocketOpen = false
+ stopHeartbeat()
+ }
});
}
- }, 30000)
+ }, HEARTBEAT_INTERVAL)
}
function stopHeartbeat() {
diff --git a/src/manifest.json b/src/manifest.json
index 53d6dee..4dedbab 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -56,12 +56,22 @@
"<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />",
"<uses-permission android:name=\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\" />",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />",
- "<uses-permission android:name=\"android.permission.USE_FULL_SCREEN_INTENT\" />"
- ]
+ "<uses-permission android:name=\"android.permission.USE_FULL_SCREEN_INTENT\" />",
+ "<uses-permission android:name=\",android.permission.WRITE_EXTERNAL_STORAGE\" />",
+ "<uses-permission android:name=\",android.permission.READ_EXTERNAL_STORAGE\" />",
+ "<uses-permission android:name=\",android.permission.MANAGE_EXTERNAL_STORAGE\" />"
+ ],
+ "permissionExternalStorage" : {
+ "request" : "always",
+ "prompt" : "应用保存运行状态等信息,需要获取读写手机存储(系统提示为访问设备上的照片、媒体内容和文件)权限,请允许。"
+ }
},
/* ios打包配置 */
"ios" : {
- "dSYMs" : false
+ "dSYMs" : false,
+ "privacyDescription" : {
+ "NSLocationAlwaysUsageDescription" : ""
+ }
},
/* SDK配置 */
"sdkConfigs" : {},
diff --git a/src/pages.json b/src/pages.json
index f007342..2f2ceb2 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -59,7 +59,8 @@
"path": "droneConsole/index",
"style": {
"navigationBarTitleText": "直播监控",
- "navigationStyle": "custom"
+ "navigationStyle": "custom",
+ "pageOrientation": "landscape"
}
},
{
@@ -186,6 +187,7 @@
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
- "backgroundColor": "#F8F8F8"
+ "backgroundColor": "#F8F8F8",
+ "pageOrientation": "portrait" //横屏配置,全局屏幕旋转设置(仅 APP/微信/QQ小程序),支持 auto / portrait / landscape
}
}
--
Gitblit v1.9.3