From 25a3dba93188783336edc323de4adf18fd28f742 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Thu, 30 Jul 2026 20:20:24 +0800 Subject: [PATCH] =?UTF-8?q?fix:bug=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 14 +- src/App.vue | 50 ++- src/common/chat-token-manager.js | 106 ++++- src/common/request.js | 19 +- src/common/wssocket.js | 290 ++++++++++---- src/main.ts | 10 +- src/pages/home/city.vue | 201 ++++++---- src/pages/home/detail.vue | 1 + src/pages/home/report.vue | 1 + src/pages/message/chat-box.vue | 1 + src/pages/message/friend.vue | 1 + src/pages/message/user-info.vue | 1 + src/pages/moments/publish.vue | 368 ++++++++++++------ src/pages/profile/about.vue | 1 + src/pages/profile/apply-vip-record.vue | 3 +- src/pages/profile/apply-vip.vue | 97 +++-- src/pages/profile/black-list.vue | 1 + src/pages/profile/face-verify/boy-verify.vue | 146 +++---- .../profile/face-verify/girl-verify-1.vue | 1 + .../profile/face-verify/girl-verify-2.vue | 1 + .../profile/face-verify/girl-verify-3.vue | 1 + .../profile/face-verify/girl-verify-4.vue | 253 +++++++----- src/pages/profile/feedback.vue | 1 + src/pages/profile/gender-choose.vue | 1 + src/pages/profile/like.vue | 1 + src/pages/profile/my-moment.vue | 38 +- src/pages/profile/pay-result.vue | 1 + src/pages/profile/recharge-record.vue | 1 + src/pages/profile/recharge.vue | 137 +++++-- src/pages/profile/set-wechat.vue | 1 + src/pages/profile/setting.vue | 1 + src/pages/profile/upload-album.vue | 1 + src/pages/profile/userinfo-simple.vue | 3 +- src/pages/profile/userinfo.vue | 1 + src/pages/profile/verify-status.vue | 1 + src/pages/profile/view-record.vue | 1 + src/pages/tabbar/home.vue | 192 ++++----- src/pages/tabbar/message.vue | 1 + src/pages/tabbar/moments.vue | 1 + src/pages/tabbar/profile.vue | 1 + src/pages/user/goods_details_store/index.vue | 45 +-- src/pages/wechat-tabbar/index.vue | 1 + src/pages/wechat-tabbar/me.vue | 3 +- src/pages/wechat/pages/gift/gift-record.vue | 3 +- src/pages/wechat/pages/gift/history.vue | 3 +- src/pages/wechat/pages/gift/index.vue | 3 +- src/pages/wechat/pages/inviter/index.vue | 1 + src/pages/wechat/pages/poster.vue | 3 +- src/pages/wechat/pages/withdraw.vue | 3 +- src/pages/wechat/pages/withraw-record.vue | 1 + src/sheep/api/member/auth.js | 33 ++ src/sheep/api/member/recent.ts | 27 ++ .../components/moment-card/moment-card.vue | 322 +++++++++------ src/sheep/components/s-layout/s-layout.vue | 6 +- src/sheep/config/index.js | 9 + src/sheep/platform/index.js | 26 +- src/sheep/platform/pay.js | 151 ++++--- src/sheep/platform/status-bar.js | 104 +++++ src/sheep/request/index.js | 5 +- src/sheep/store/user.js | 5 +- src/sheep/ui/su-video/su-video.vue | 35 +- src/sheep/util/location.ts | 108 +++++ src/sheep/util/wechat-tool.ts | 2 +- src/shime-uni.d.ts | 5 +- 64 files changed, 1980 insertions(+), 875 deletions(-) create mode 100644 src/sheep/platform/status-bar.js create mode 100644 src/sheep/util/location.ts diff --git a/.env b/.env index 1e542811..9a2fa39e 100644 --- a/.env +++ b/.env @@ -1,7 +1,10 @@ -# 打包目标 -# H5 - 全部都是使用H5支付 -# APP - 全部都是使用APP支付 -SHOPRO_BUILD_TARGET=H5 +# 支付目标 +# H5 - 强制使用 H5/WAP 支付渠道 +# APP - 非微信小程序端跳转到微信小程序支付 +# SHOPRO_BUILD_TARGET 为兼容旧配置的默认值,下面两项可分别覆盖支付宝和微信支付 +SHOPRO_BUILD_TARGET=APP +SHOPRO_ALIPAY_BUILD_TARGET=H5 +SHOPRO_WECHAT_BUILD_TARGET=APP # 版本号 SHOPRO_VERSION=v2.4.1 @@ -12,6 +15,7 @@ SHOPRO_API_PATH=/app-api # 后端接口 - 测试环境(通过 process.env.NODE_ENV == development) # SHOPRO_DEV_BASE_URL = https://api.htysapp.com SHOPRO_DEV_BASE_URL=https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc +# SHOPRO_DEV_BASE_URL=http://localhost:48083 # 后端接口 - 正式环境(通过 process.env.NODE_ENV != development) SHOPRO_BASE_URL=https://api.htysapp.com @@ -26,7 +30,7 @@ SHOPRO_WEBSOCKET_PATH=/infra/ws SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn ### SHOPRO_STATIC_URL = https://file.sheepjs.com -# 前端 H5 访问域名 +# 前端 H5 访问域名(App 调起 H5 支付时作为支付完成后的回跳地址) SHOPRO_H5_URL=http://127.0.0.1:3000 # 是否开启直播 1 开启直播 | 0 关闭直播 diff --git a/src/App.vue b/src/App.vue index dc125078..800dadeb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,9 +7,24 @@ import * as enums from './common/enums'; import * as wsApi from './common/wssocket'; import chatTokenManager from './common/chat-token-manager'; import CHAT_ENV from '@/chat-env.js'; +import AuthUtil from '@/sheep/api/member/auth'; import useChatStore from '@/chat-store/chatStore.js'; +const reissueChatToken = async () => { + const response = await AuthUtil.reissueChatToken(); + const chatToken = response?.data?.chatToken; + if (response?.code !== 0 || !chatToken) { + const error = new Error(response?.msg || '重新签发聊天 token 失败'); + error.code = response?.code; + throw error; + } + return chatToken; +}; + +// 页面可能早于异步 onLaunch 完成加载,需在模块初始化时就注册重签能力。 +chatTokenManager.setReissueToken(reissueChatToken); + export default { /** ↓↓↓ 以下为app启动聊天相关模块初始化,WebSocket链接,加载聊天消息,加载离线消息,加载群组消息,初始化聊天相关的全局状态仓库 */ data() { @@ -75,7 +90,11 @@ export default { this.refreshUnreadBadge() } catch (error) { console.log("初始化聊天:", error); - this.exit(); + this.configStore?.setAppInit(false); + uni.showToast({ + title: '聊天服务暂时不可用,请稍后重试', + icon: 'none', + }); } }, initWebSocket() { @@ -344,7 +363,6 @@ export default { handleSystemMessage(msg) { if (msg.type == enums.MESSAGE_TYPE.USER_BANNED) { // 用户被封禁 - wsApi.close(3099); uni.showModal({ content: '您的账号已被管理员封禁,原因:' + msg.content, showCancel: false, @@ -433,7 +451,7 @@ export default { console.log('exit'); this.isExit = true; chatTokenManager.stop(); - wsApi.close(3099); + wsApi.destroy(3099); uni.removeStorageSync('loginInfo'); uni.reLaunch({ url: '/pages/login/login', @@ -447,19 +465,19 @@ export default { // this.audioTip.play(); }, startChatTokenCheck() { - // 轮询只处理聊天 token;仅凭证明确失效时退出,网络错误等待下次检查。 + // IM 双 token 都过期时,使用仍有效的主站登录态重新签发。 chatTokenManager.start({ + reissueToken: reissueChatToken, onRefreshError: (error) => { - const code = error?.code ?? error?.data?.code; - if (code == 400 || code == 401) { - this.exit(); - } + console.warn('聊天 token 自动续期失败,等待下次重试', error); }, }); }, reconnectWs() { - // 已退出则不再重连 - if (this.isExit) { + let loginInfo = uni.getStorageSync('loginInfo'); + // 退出时可能已有异步重连排队,需要同时校验主站和 IM 登录态。 + if (this.isExit || !uni.getStorageSync('token') || !loginInfo?.accessToken) { + this.reconnecting = false; return; } // 记录标志 @@ -468,11 +486,19 @@ export default { this.userStore .loadUser() .then((userInfo) => { + loginInfo = uni.getStorageSync('loginInfo'); + if (this.isExit || !uni.getStorageSync('token') || !loginInfo?.accessToken) { + this.reconnecting = false; + return; + } // 重新连接 - let loginInfo = uni.getStorageSync('loginInfo'); wsApi.reconnect(CHAT_ENV.WS_URL, loginInfo.accessToken); }) .catch(() => { + if (this.isExit || !uni.getStorageSync('token')) { + this.reconnecting = false; + return; + } // 5s后重试 setTimeout(() => { this.reconnectWs(); @@ -528,7 +554,7 @@ export default { // #ifndef MP-WEIXIN this.closeSplashscreen(1000); // 延迟1s,避免用户看到页面跳转 let loginInfo = uni.getStorageSync('loginInfo'); - if (!loginInfo || !loginInfo.refreshToken) { + if (!uni.getStorageSync('token')) { this.closeSplashscreen(0); return; } diff --git a/src/common/chat-token-manager.js b/src/common/chat-token-manager.js index 3e6e4d33..80cc3dbb 100644 --- a/src/common/chat-token-manager.js +++ b/src/common/chat-token-manager.js @@ -10,6 +10,8 @@ class ChatTokenManager { // 所有调用共享同一个刷新 Promise,避免并发刷新后 token 相互覆盖。 this.refreshPromise = null; this.onRefreshError = null; + // IM 的 refreshToken 也失效时,由主站登录态重新签发一套聊天 token。 + this.reissueToken = null; // 用于忽略定时器停止后才返回的旧检查结果。 this.monitorVersion = 0; } @@ -17,9 +19,13 @@ class ChatTokenManager { // 启动时立即检查一次,之后每 30 秒检查一次;重复调用不会创建多个定时器。 start(options = {}) { this.onRefreshError = options.onRefreshError || null; + if (options.reissueToken) { + this.reissueToken = options.reissueToken; + } const loginInfo = uni.getStorageSync('loginInfo'); - if (!loginInfo?.refreshToken) { + const canReissueFromMainLogin = uni.getStorageSync('token') && this.reissueToken; + if (!loginInfo?.refreshToken && !canReissueFromMainLogin) { this.stop(); return; } @@ -44,6 +50,10 @@ class ChatTokenManager { this.onRefreshError = null; } + setReissueToken(reissueToken) { + this.reissueToken = reissueToken; + } + runCheck(monitorVersion = this.monitorVersion) { this.checkAccessToken().catch((error) => { if (monitorVersion !== this.monitorVersion) { @@ -57,6 +67,9 @@ class ChatTokenManager { checkAccessToken() { const loginInfo = uni.getStorageSync('loginInfo'); if (!loginInfo?.refreshToken) { + if (uni.getStorageSync('token') && typeof this.reissueToken === 'function') { + return this.refreshAccessToken(loginInfo); + } this.stop(); return Promise.resolve(null); } @@ -74,6 +87,17 @@ class ChatTokenManager { return false; } + const loginInfo = uni.getStorageSync('loginInfo'); + if ( + loginInfo?.accessToken === accessToken && + Number.isFinite(Number(loginInfo.accessTokenExpiresAt)) + ) { + return ( + Number(loginInfo.accessTokenExpiresAt) - Date.now() <= + TOKEN_REFRESH_THRESHOLD_SECONDS * 1000 + ); + } + try { // JWT 第二段是 payload,H5 和 App 均通过 uni API 完成 Base64 解码。 const payload = accessToken.split('.')[1]; @@ -103,33 +127,50 @@ class ChatTokenManager { } refreshAccessToken(loginInfo = uni.getStorageSync('loginInfo')) { - if (!loginInfo?.refreshToken) { - return Promise.reject(new Error('缺少聊天 refreshToken')); - } - if (this.refreshPromise) { return this.refreshPromise; } - const refreshToken = loginInfo.refreshToken; - this.refreshPromise = this.requestRefreshToken(refreshToken) - .then((res) => { - if (!res || res.data?.code != 200) { - throw res?.data || new Error('刷新聊天 token 失败'); + const refreshVersion = this.monitorVersion; + const accessToken = loginInfo?.accessToken; + const refreshToken = loginInfo?.refreshToken; + const refreshRequest = refreshToken + ? this.requestRefreshToken(refreshToken).then((res) => { + if (!res || res.data?.code != 200 || !res.data?.data) { + throw res?.data || new Error('刷新聊天 token 失败'); + } + return res.data.data; + }) + : Promise.reject(new Error('缺少聊天 refreshToken')); + + this.refreshPromise = refreshRequest + .catch((error) => { + if (!this.canReissueToken(error, refreshToken)) { + throw error; + } + return this.reissueToken(); + }) + .then((newLoginInfo) => { + if (refreshVersion !== this.monitorVersion) { + throw new Error('聊天登录状态已停止,忽略本次 token 刷新结果'); + } + if (!newLoginInfo?.accessToken || !newLoginInfo?.refreshToken) { + throw new Error('重新签发的聊天 token 不完整'); } // 刷新期间用户可能退出或重新登录,旧请求结果不能覆盖新的登录状态。 const currentLoginInfo = uni.getStorageSync('loginInfo'); - if (currentLoginInfo?.refreshToken !== refreshToken) { + const loginChanged = refreshToken + ? currentLoginInfo?.refreshToken !== refreshToken + : accessToken && currentLoginInfo?.accessToken !== accessToken; + if (loginChanged) { if (currentLoginInfo?.accessToken) { return currentLoginInfo; } throw new Error('聊天登录状态已变化,忽略本次 token 刷新结果'); } - const newLoginInfo = res.data.data; - uni.setStorageSync('loginInfo', newLoginInfo); - return newLoginInfo; + return this.saveTokenInfo(newLoginInfo); }) .finally(() => { this.refreshPromise = null; @@ -138,6 +179,43 @@ class ChatTokenManager { return this.refreshPromise; } + canReissueToken(error, refreshToken) { + if (!uni.getStorageSync('token') || typeof this.reissueToken !== 'function') { + return false; + } + if (!refreshToken) { + return true; + } + + const code = error?.code ?? error?.data?.code ?? error?.statusCode; + const message = error?.message ?? error?.data?.message ?? error?.errMsg ?? ''; + return ( + code == 400 || + code == 401 || + message.includes('登录信息已过期') || + message.includes('refreshToken已过期') + ); + } + + saveTokenInfo(tokenInfo) { + if (!tokenInfo) { + return tokenInfo; + } + + const now = Date.now(); + const storedTokenInfo = { ...tokenInfo }; + const accessTokenExpiresIn = Number(tokenInfo.accessTokenExpiresIn); + const refreshTokenExpiresIn = Number(tokenInfo.refreshTokenExpiresIn); + if (Number.isFinite(accessTokenExpiresIn) && accessTokenExpiresIn > 0) { + storedTokenInfo.accessTokenExpiresAt = now + accessTokenExpiresIn * 1000; + } + if (Number.isFinite(refreshTokenExpiresIn) && refreshTokenExpiresIn > 0) { + storedTokenInfo.refreshTokenExpiresAt = now + refreshTokenExpiresIn * 1000; + } + uni.setStorageSync('loginInfo', storedTokenInfo); + return storedTokenInfo; + } + requestRefreshToken(refreshToken) { return new Promise((resolve, reject) => { uni.request({ diff --git a/src/common/request.js b/src/common/request.js index 3a109ff2..fc64ef01 100644 --- a/src/common/request.js +++ b/src/common/request.js @@ -1,7 +1,9 @@ import CHAT_ENV from '@/chat-env.js'; import chatTokenManager from './chat-token-manager'; -const request = (options) => { +const MAX_AUTH_RETRY_COUNT = 2; + +const request = (options, authRetryCount = 0) => { const header = options.header || {}; const loginInfo = uni.getStorageSync('loginInfo'); if (loginInfo) { @@ -23,33 +25,32 @@ const request = (options) => { await chatTokenManager.refreshAccessToken(currentLoginInfo); } catch (error) { console.log('刷新token失败', error); - getApp().$vm.exit(); return reject(error); } } return resolve(res.data.data); - } else if (res.data.code == 400) { - console.log('code 400'); - getApp().$vm.exit(); - } else if (res.data.code == 401) { + } else if (res.data.code == 400 || res.data.code == 401) { console.log('token失效,尝试重新获取'); const currentLoginInfo = uni.getStorageSync('loginInfo'); + if (authRetryCount >= MAX_AUTH_RETRY_COUNT) { + return reject(res.data); + } + // 当前请求使用的是旧 token,直接用已刷新的 token 重试。 if ( currentLoginInfo?.accessToken && currentLoginInfo.accessToken !== header.accessToken ) { - return resolve(request(options)); + return resolve(request(options, authRetryCount + 1)); } try { await chatTokenManager.refreshAccessToken(currentLoginInfo); // 重新发送刚才的请求 - return resolve(request(options)); + return resolve(request(options, authRetryCount + 1)); } catch (error) { console.log('刷新token失败', error); - getApp().$vm.exit(); return reject(error); } } else { diff --git a/src/common/wssocket.js b/src/common/wssocket.js index 44891d4a..fc45eaf0 100644 --- a/src/common/wssocket.js +++ b/src/common/wssocket.js @@ -1,129 +1,266 @@ let accessToken = ''; +let wsUrl = ''; let messageCallBack = null; let closeCallBack = null; let connectCallBack = null; -let isConnect = false; //连接标识 避免重复连接 +let isConnect = false; +let enabled = false; let rec = null; -let lastConnectTime = new Date(); // 最后一次连接时间 +let connectRetryTimer = null; +let lastConnectTime = 0; let socketTask = null; -let connect = (wsurl, token) => { - accessToken = token; - if (isConnect) { +const clearReconnectTimers = () => { + if (rec) { + clearTimeout(rec); + rec = null; + } + if (connectRetryTimer) { + clearTimeout(connectRetryTimer); + connectRetryTimer = null; + } +}; + +const closeSocketTask = (task, code = 1000) => { + if (!task) { return; } - lastConnectTime = new Date(); - socketTask = uni.connectSocket({ - url: wsurl, - success: (res) => {}, - fail: (e) => { - console.log(e); - console.log('WebSocket连接失败,10s后重连'); - setTimeout(() => { - connect(); - }, 10000); - }, - }); + try { + task.close({ + code, + fail: (error) => { + console.log('关闭websocket连接失败', error); + }, + }); + } catch (error) { + console.log('关闭websocket连接失败', error); + } +}; - socketTask.onOpen((res) => { +const finishConnection = (task, event) => { + // 旧连接可能晚于新连接触发事件,不能让它污染当前连接状态。 + if (task !== socketTask) { + return; + } + socketTask = null; + isConnect = false; + heartCheck.stop(); + closeCallBack && closeCallBack(event); +}; + +const scheduleConnectRetry = (url, token) => { + if (!enabled) { + return; + } + if (connectRetryTimer) { + clearTimeout(connectRetryTimer); + } + connectRetryTimer = setTimeout(() => { + connectRetryTimer = null; + if (!enabled || socketTask) { + return; + } + connect(url, token); + }, 10000); +}; + +let connect = (url, token) => { + if (!url || !token) { + console.warn('缺少WebSocket地址或登录凭证,已跳过连接'); + return; + } + + wsUrl = url; + accessToken = token; + enabled = true; + clearReconnectTimers(); + if (socketTask) { + return; + } + + lastConnectTime = Date.now(); + let task = null; + try { + task = uni.connectSocket({ + url, + success: () => {}, + fail: (error) => { + if (!enabled || (task && task !== socketTask)) { + return; + } + console.log(error); + console.log('WebSocket连接失败,10s后重连'); + socketTask = null; + isConnect = false; + scheduleConnectRetry(url, token); + }, + }); + } catch (error) { + console.log('WebSocket连接失败,10s后重连', error); + scheduleConnectRetry(url, token); + return; + } + + if (!task) { + scheduleConnectRetry(url, token); + return; + } + socketTask = task; + + task.onOpen(() => { + if (task !== socketTask || !enabled) { + closeSocketTask(task); + return; + } console.log('WebSocket连接成功'); isConnect = true; - // 发送登录命令 - let loginInfo = { - cmd: 0, - data: { - accessToken: accessToken, - }, - }; - socketTask.send({ - data: JSON.stringify(loginInfo), + if (connectRetryTimer) { + clearTimeout(connectRetryTimer); + connectRetryTimer = null; + } + task.send({ + data: JSON.stringify({ + cmd: 0, + data: { + accessToken: token, + }, + }), }); }); - socketTask.onMessage((res) => { - let sendInfo = JSON.parse(res.data); + task.onMessage((res) => { + if (task !== socketTask || !enabled) { + return; + } + let sendInfo; + try { + sendInfo = JSON.parse(res.data); + } catch (error) { + console.warn('WebSocket消息解析失败', error); + return; + } + if (sendInfo.cmd == 0) { heartCheck.start(); connectCallBack && connectCallBack(); console.log('WebSocket登录成功'); } else if (sendInfo.cmd == 1) { - // 重新开启心跳定时 heartCheck.reset(); } else { - // 其他消息转发出去 console.log('接收到消息', sendInfo); messageCallBack && messageCallBack(sendInfo.cmd, sendInfo.data); } }); - socketTask.onClose((res) => { + task.onClose((res) => { + if (task !== socketTask) { + return; + } console.log('WebSocket连接关闭'); - isConnect = false; - closeCallBack && closeCallBack(res); + finishConnection(task, res); }); - socketTask.onError((e) => { - console.log('ws错误:', e); - close(1006); - isConnect = false; - closeCallBack && closeCallBack(e); + task.onError((error) => { + if (task !== socketTask || !enabled) { + return; + } + console.log('ws错误:', error); + closeSocketTask(task); + finishConnection(task, error); }); }; -//定义重连函数 -let reconnect = (wsurl, accessToken) => { - console.log('尝试重新连接'); - if (isConnect) { +// 网络异常断开后重连;主动销毁后不会再接受旧任务发起的重连。 +let reconnect = (url, token) => { + if (!enabled || socketTask) { return; } - // 延迟10秒重连 避免过多次过频繁请求重连 - let timeDiff = new Date().getTime() - lastConnectTime.getTime(); - let delay = timeDiff < 10000 ? 10000 - timeDiff : 0; - rec && clearTimeout(rec); - rec = setTimeout(function () { - connect(wsurl, accessToken); + const reconnectUrl = url || wsUrl; + const reconnectToken = token || accessToken; + if (!reconnectUrl || !reconnectToken) { + return; + } + + console.log('尝试重新连接'); + const timeDiff = Date.now() - lastConnectTime; + const delay = timeDiff < 10000 ? 10000 - timeDiff : 0; + if (rec) { + clearTimeout(rec); + } + rec = setTimeout(() => { + rec = null; + if (!enabled || socketTask) { + return; + } + connect(reconnectUrl, reconnectToken); }, delay); }; -//设置关闭连接 -let close = (code) => { - if (!isConnect) { +// 临时关闭当前连接,关闭事件仍交给上层决定是否需要重连。 +let close = (code = 1000) => { + clearReconnectTimers(); + heartCheck.stop(); + isConnect = false; + if (!socketTask) { return; } - socketTask.close({ - code: code, - complete: (res) => { - isConnect = false; - console.log('关闭websocket连接'); - }, - fail: (e) => { - console.log('关闭websocket连接失败', e); - }, - }); + closeSocketTask(socketTask, code); +}; + +// 退出登录时永久销毁当前连接,并抑制旧连接的关闭回调和重连任务。 +let destroy = (code = 1000) => { + enabled = false; + clearReconnectTimers(); + heartCheck.stop(); + + const task = socketTask; + socketTask = null; + isConnect = false; + accessToken = ''; + wsUrl = ''; + closeSocketTask(task, code); }; -// 心跳设置 let heartCheck = { - timeout: 20000, //每段时间发送一次心跳包 这里设置为20s - timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) - start: function () { - if (isConnect) { + timeout: 20000, + timeoutObj: null, + start() { + if (enabled && isConnect) { console.log('发送WebSocket心跳'); - let heartBeat = { - cmd: 1, - data: {}, - }; - sendMessage(JSON.stringify(heartBeat)); + sendMessage( + JSON.stringify({ + cmd: 1, + data: {}, + }), + ); } }, - reset: function () { - clearTimeout(this.timeoutObj); + reset() { + this.stop(); + if (!enabled || !isConnect) { + return; + } this.timeoutObj = setTimeout(() => heartCheck.start(), this.timeout); }, + stop() { + if (this.timeoutObj) { + clearTimeout(this.timeoutObj); + this.timeoutObj = null; + } + }, }; let sendMessage = (message) => { - socketTask.send({ data: message }); + if (!enabled || !isConnect || !socketTask) { + return false; + } + try { + socketTask.send({ data: message }); + return true; + } catch (error) { + console.log('发送WebSocket消息失败', error); + return false; + } }; let onConnect = (callback) => { @@ -138,5 +275,4 @@ let onClose = (callback) => { closeCallBack = callback; }; -// 将方法暴露出去 -export { connect, reconnect, close, sendMessage, onConnect, onMessage, onClose }; +export { connect, reconnect, close, destroy, sendMessage, onConnect, onMessage, onClose }; diff --git a/src/main.ts b/src/main.ts index c3eac2b5..3e57c1b8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import App from './App.vue'; import { createSSRApp } from 'vue'; import { setupPinia } from './sheep/store'; +import { initStatusBarHeight, statusBarPageStyle } from '@/sheep/platform/status-bar'; import 'virtual:uno.css'; /** 聊天common模块 */ @@ -18,14 +19,12 @@ import * as recorder from './common/recorder-h5'; import ImageResize from 'quill-image-resize-mp'; import Quill from 'quill'; import { patchH5Navigation } from '@/sheep/router/h5-navigation'; -import { initH5HostShell } from '@/sheep/platform/h5-shell'; // 以下组件用于兼容部分手机聊天边框无法输入的问题 window.Quill = Quill; window.ImageResize = { default: ImageResize }; // 调试器 // import VConsole from 'vconsole'; // new VConsole(); -initH5HostShell(); patchH5Navigation(); // #endif // #ifndef H5 @@ -39,6 +38,9 @@ import useGroupStore from '@/chat-store/groupStore.js'; import useConfigStore from '@/chat-store/configStore.js'; import useUserStore from '@/chat-store/userStore.js'; +// H5 会同步应用宿主高度;App 和小程序会在异步系统信息返回后更新页面根变量。 +void initStatusBarHeight(); + /** 聊天页面的导航组件 */ import barGroup from '@/components/bar/bar-group'; import arrowBar from '@/components/bar/arrow-bar'; @@ -48,6 +50,10 @@ import switchBar from '@/components/bar/switch-bar'; export function createApp() { const app = createSSRApp(App); + Object.defineProperty(app.config.globalProperties, '$statusBarPageStyle', { + get: () => statusBarPageStyle.value, + }); + const pinia = setupPinia(app); // 聊天common模块挂载为全局属性 diff --git a/src/pages/home/city.vue b/src/pages/home/city.vue index f0340dfa..7e04d2c4 100644 --- a/src/pages/home/city.vue +++ b/src/pages/home/city.vue @@ -1,4 +1,5 @@