罗广辉
2026-06-18 44dd9c4eb8a2489c63a5eb53fd4254bbb3510e95
Merge branch 'feature/v9.0/9.0.4'
3 files modified
87 ■■■■ changed files
src/hooks/useGlobalWS.js 65 ●●●● patch | view | raw | blame | history
src/manifest.json 16 ●●●● patch | view | raw | blame | history
src/pages.json 6 ●●●●● patch | view | raw | blame | history
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() {
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" : {},
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
  }
}