From c7e4e5e26b09a9c3fe7f4bf9da898a7340a71b38 Mon Sep 17 00:00:00 2001 From: YeMingfei666 <1619116647@qq.com> Date: Mon, 19 May 2025 09:07:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9websocket=E4=B8=BA=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=8F=AA=E6=9C=89=E4=B8=80=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/carts-websocket - 副本.js | 364 ++++ common/js/carts-websocket.js | 41 +- pages/order/confirm-order.vue | 256 ++- pages/order/index.vue | 8 + pages/product/index - 副本.vue | 2632 +++++++++++++++++++++++++++ pages/product/index.vue | 339 ++-- stores/carts-websocket.js | 368 ++++ stores/carts.js | 41 +- 8 files changed, 3780 insertions(+), 269 deletions(-) create mode 100644 common/js/carts-websocket - 副本.js create mode 100644 pages/product/index - 副本.vue create mode 100644 stores/carts-websocket.js diff --git a/common/js/carts-websocket - 副本.js b/common/js/carts-websocket - 副本.js new file mode 100644 index 0000000..12558a4 --- /dev/null +++ b/common/js/carts-websocket - 副本.js @@ -0,0 +1,364 @@ +import { + ref, + onMounted, + onBeforeUnmount +} from 'vue'; + +const useWebSocket = (options = {}) => { + const { + heartbeatInterval = 10000, //心跳是10秒一次 + reconnectInterval = 3000, //重新连接间隔时间的一个参数 + maxReconnectAttempts = 3, //最大重连接次数 + initialReconnectInterval = 3000, // 初始重连间隔 + initMessage, + initMessageRetryCount = 3, // 新增:初始化消息发送重试次数 + initMessageRetryInterval = 2000, // 新增:初始化消息重试间隔 + maxReconnectDuration = Infinity, + onMessage + } = options; + const autoReconnect = ref(true); //是否自动重新连接 + const socketTask = ref(null); + const isConnected = ref(false); //表示是否已连接上。 + const heartbeatTimer = ref(null); //心跳定时器 + const reconnectTimer = ref(null); //重连定时器 + const reconnectAttempts = ref(0); //重连的尝试次数 + const isNetworkConnected = ref(true); //监听当前网络连接状态 + const isManuallyClosed = ref(false); //是否是被手动关闭的 + const receivedMessages = ref(); //储从 WebSocket 服务器接收到的消息 + const initMessageSendAttempts = ref(0); //初始化连接多少次 + const reconnectStartTime = ref(0); //新增:记录重连开始时间 + const isPongReceived = ref(false) + const allowReconnect = ref(true); // 新增:控制是否允许重连 + // 关闭现有连接并清理资源 + const closeExistingConnection = () => { + if (socketTask.value) { + // 关闭 WebSocket 连接 + socketTask.value.close({ + success: () => { + console.log('WebSocket 连接已关闭'); + }, + fail: (err) => { + console.error('关闭 WebSocket 连接失败:', err); + } + }); + + // 清除心跳定时器 + clearInterval(heartbeatTimer.value); + heartbeatTimer.value = null; + + // 清除重连定时器 + clearTimeout(reconnectTimer.value); + reconnectTimer.value = null; + + // 标记连接已断开 + isConnected.value = false; + } + }; + const websocketsendMessage = (data) => { + uni.$u.debounce(sendMessage(data), 500) + } + // 连接 WebSocket + const connect = (connectMsg) => { + if (!isNetworkConnected.value) { + uni.showToast({ + title: '网络未连接...', + icon: 'none' + }) + setTimeout(() => { + uni.pro.switchTab('index/index') + }, 1000) + console.log('网络未连接,暂不尝试连接 WebSocket'); + return; + } + + // 关闭现有连接并清理资源 + try{ + closeExistingConnection(); + }catch(err){ + + } + socketTask.value = uni.connectSocket({ + url: uni.conf.baseUrlwws, + success: (res) => { + isConnected.value = true; + // 监听初始化成功在开启心跳 + startHeartbeat(); + }, + fail: () => { + console.error('WebSocket 连接失败,尝试重连'); + if (autoReconnect.value && allowReconnect.value) { + handleReconnect(); + } + } + }); + + if (socketTask.value) { + socketTask.value.onOpen(() => { + // 初始化 初始购物车 + sendMessage(connectMsg ? connectMsg : initMessage) + }); + socketTask.value.onMessage((res) => { + receivedMessages.value = JSON.parse(res.data) + websocketsendMessage({ + type: 'receipt', + msg_id: receivedMessages.value.msg_id + }) + if (onMessage) { + onMessage(receivedMessages.value) + } + // receivedMessages.value.push(list); + if (receivedMessages.value == 'ok' || receivedMessages.value.operate_type == 'init') { + console.log('初始化正常,心跳响应正常'); + // 清除重连定时器 + clearTimeout(reconnectTimer.value); + allowReconnect.value = false + reconnectTimer.value = null; + } + + }); + + socketTask.value.onClose((res) => { + console.log(res, 'WebSocket 连接已关闭,尝试重连'); + isConnected.value = false; + clearInterval(heartbeatTimer.value); // 停止心跳定时器 + clearTimeout(reconnectTimer.value); // 清除重连定时器 + if (res.code == '1006' && !allowReconnect.value) { + uni.showToast({ + title: '网络异常,请重新扫码', + icon: 'none' + }); + autoReconnect.value = false; + setTimeout(() => { + uni.pro.switchTab('index/index'); + }, 1000) + return false; + } + if (autoReconnect.value && !isManuallyClosed.value) { + handleReconnect(); + } + }); + + socketTask.value.onError((err) => { + console.error('WebSocket 连接发生错误:', err); + isConnected.value = false; + clearInterval(heartbeatTimer.value); + if (autoReconnect.value && !isManuallyClosed.value) { + handleReconnect(); + } + }); + } else { + console.error('socketTask 未正确初始化'); + } + }; + + // 启动心跳机制 + const startHeartbeat = () => { + if (!isNetworkConnected.value) { + console.log('网络未连接,暂停心跳'); + uni.showToast({ + title: '网络未连接...', + icon: 'none' + }); + setTimeout(() => { + uni.pro.switchTab('index/index'); + }, 1000); + return; + } + heartbeatTimer.value = setInterval(() => { + if (isConnected.value) { + console.log('发送心跳消息'); + isPongReceived.value = false; // 每次发送心跳消息前重置标记 + socketTask.value.send({ + data: JSON.stringify({ + type: 'ping_interval', + set: 'shopping' + }), + success: () => { + console.log('心跳消息发送成功'); + const pongTimer = setTimeout(() => { + if (!isPongReceived.value) { + console.error('心跳超时,未收到响应,尝试重连'); + clearInterval(heartbeatTimer.value); + if (autoReconnect.value && reconnectAttempts.value < + maxReconnectAttempts && allowReconnect.value) { + handleReconnect(); + } else { + console.error('重连次数达到上限,停止重连和心跳'); + clearInterval(heartbeatTimer.value); + autoReconnect.value = false; + uni.pro.switchTab('index/index'); + } + } + }, heartbeatInterval * 1.2); + + const handlePong = (res) => { + try { + let data = JSON.parse(res.data); + if (data.operate_type == "init" || (data.msg === 'ok' && + data.msg_id == + 'ping_interval')) { + isPongReceived.value = true; + console.log('收到心跳响应,清除超时定时器'); + clearTimeout(pongTimer); + } + } catch (error) { + console.error('解析心跳响应数据时出错:', error); + } + }; + socketTask.value.onMessage(handlePong); + }, + fail: () => { + console.error('心跳消息发送失败,尝试重连'); + clearInterval(heartbeatTimer.value); + if (autoReconnect.value && reconnectAttempts.value < + maxReconnectAttempts && allowReconnect.value) { + handleReconnect(); + } else { + console.error('重连次数达到上限,停止重连和心跳'); + clearInterval(heartbeatTimer.value); + autoReconnect.value = false; + uni.pro.switchTab('index/index'); + } + } + }); + } + }, heartbeatInterval); + }; + + + // 手动关闭连接 + const closeSocket = () => { + isManuallyClosed.value = true; + closeExistingConnection(); + }; + + // 发送消息 + const sendMessage = async (data) => { + if (isConnected.value) { + await socketTask.value.send({ + data: JSON.stringify(data), + success: () => { + // console.log('消息发送成功'); + }, + fail: () => { + // console.error('消息发送失败'); + } + }); + } else { + console.error('WebSocket 未连接,无法发送消息'); + } + }; + + // 处理重连逻辑 + const handleReconnect = () => { + if (!isNetworkConnected.value) { + console.log('网络未连接,暂停重连'); + return; + } + if (isManuallyClosed.value) { + console.log('手动关闭连接,不进行重连'); + return; + } + + if (!allowReconnect.value) { + console.log('重连功能已关闭,不进行重连'); + return; + } + + if (reconnectAttempts.value < maxReconnectAttempts) { + reconnectAttempts.value++; + const reconnectInterval = initialReconnectInterval * Math.pow(2, reconnectAttempts.value - 1); + const randomizedInterval = reconnectInterval + Math.floor(Math.random() * 1000); + uni.showLoading({ + title: `正在努力连接..`, + mask: true + }) + console.log(`尝试第 ${reconnectAttempts.value} 次重连,重连间隔: ${randomizedInterval}ms...`); + + reconnectTimer.value = setTimeout(() => { + connect(); + }, randomizedInterval); + } else { + console.error('重连次数达到上限,停止重连'); + uni.showToast({ + title: '重连次数达到上限,停止重连', + icon: 'none' + }); + clearInterval(heartbeatTimer.value); + autoReconnect.value = false; + setTimeout(() => { + uni.hideLoading(); + uni.pro.switchTab('index/index'); + }, 1000) + } + }; + + + // / 网络状态监听 + const initNetworkListener = () => { + uni.getSystemInfo({ + success: (res) => { + if (res.platform !== 'devtools') { + uni.onNetworkStatusChange((statusRes) => { + isNetworkConnected.value = statusRes.isConnected; + if (statusRes.isConnected && !isManuallyClosed.value) { + console.log('网络已连接,尝试重新连接 WebSocket'); + if (!isConnected.value && autoReconnect.value) { + connect(); + } + } else if (!statusRes.isConnected) { + console.log('网络已断开,暂停 WebSocket 操作'); + clearInterval(heartbeatTimer.value); + clearTimeout(reconnectTimer.value); + if (socketTask.value) { + socketTask.value.close(); + isConnected.value = false; + } + } + }); + } + }, + fail: (err) => { + console.error('获取系统信息失败:', err); + } + }); + + uni.getNetworkType({ + success: (res) => { + isNetworkConnected.value = res.networkType !== 'none'; + if (!isNetworkConnected.value) { + console.log('初始网络未连接,暂不尝试连接 WebSocket'); + } + } + }); + }; + + // 页面显示,尝试连接 WebSocket + const onShowconnect = () => { + if (autoReconnect.value) { + uni.showLoading({ + title: `尝试再次连接`, + mask: true + }) + connect(); + } + } + + onBeforeUnmount(() => { + closeSocket(); + }); + + return { + isConnected, + sendMessage, + closeSocket, + receivedMessages, + closeExistingConnection, + connect, + onShowconnect, + initNetworkListener, + connect,allowReconnect + }; +}; + +export default useWebSocket; \ No newline at end of file diff --git a/common/js/carts-websocket.js b/common/js/carts-websocket.js index 12558a4..e90cb3b 100644 --- a/common/js/carts-websocket.js +++ b/common/js/carts-websocket.js @@ -31,8 +31,7 @@ const useWebSocket = (options = {}) => { const allowReconnect = ref(true); // 新增:控制是否允许重连 // 关闭现有连接并清理资源 const closeExistingConnection = () => { - if (socketTask.value) { - // 关闭 WebSocket 连接 + try { socketTask.value.close({ success: () => { console.log('WebSocket 连接已关闭'); @@ -52,13 +51,32 @@ const useWebSocket = (options = {}) => { // 标记连接已断开 isConnected.value = false; + } catch (error) { + console.error(error) + //TODO handle the exception } + + }; const websocketsendMessage = (data) => { uni.$u.debounce(sendMessage(data), 500) } + + + const safeConnect=(connectMsg)=>{ + return new Promise((resolve,reject)=>{ + try { + sendMessage(connectMsg ? connectMsg : initMessage) + resolve(true) + } catch (error) { + reject(false) + //TODO handle the exception + } + }) + } + // 连接 WebSocket - const connect = (connectMsg) => { + const connect =async (connectMsg) => { if (!isNetworkConnected.value) { uni.showToast({ title: '网络未连接...', @@ -72,13 +90,14 @@ const useWebSocket = (options = {}) => { } // 关闭现有连接并清理资源 - try{ - closeExistingConnection(); - }catch(err){ - + const sendSuccess= await safeConnect(connectMsg) + if(sendSuccess){ + return + }else{ + closeExistingConnection() } socketTask.value = uni.connectSocket({ - url: uni.conf.baseUrlwws, + url: uni.conf.baseUrlwws + '?' + Date.now(), success: (res) => { isConnected.value = true; // 监听初始化成功在开启心跳 @@ -228,6 +247,7 @@ const useWebSocket = (options = {}) => { // 手动关闭连接 const closeSocket = () => { + console.log('手动关闭连接'); isManuallyClosed.value = true; closeExistingConnection(); }; @@ -345,7 +365,7 @@ const useWebSocket = (options = {}) => { } onBeforeUnmount(() => { - closeSocket(); + // closeSocket(); }); return { @@ -357,7 +377,8 @@ const useWebSocket = (options = {}) => { connect, onShowconnect, initNetworkListener, - connect,allowReconnect + connect, + allowReconnect }; }; diff --git a/pages/order/confirm-order.vue b/pages/order/confirm-order.vue index bbd84b6..02960fb 100644 --- a/pages/order/confirm-order.vue +++ b/pages/order/confirm-order.vue @@ -63,7 +63,8 @@ @@ -472,7 +472,9 @@ // websocket // import useWebSocket from '@/common/js/websocket.js'; - import useWebSocket from '@/common/js/carts-websocket.js'; + import { + useWebSocket + } from '@/stores/carts-websocket.js'; // pinia管理 import { @@ -1135,16 +1137,8 @@ shop_id: uni.cache.get('shopId') } } - const { - isConnected, - sendMessage, - closeSocket: manualClose, - receivedMessages, - closeExistingConnection, - onShowconnect, - initNetworkListener - } = useWebSocket(options); - + const useSocket = useWebSocket(); + useSocket.connect(options.initMessage, onMessage) //购物车显示 const showCart = ref(false) @@ -1154,6 +1148,7 @@ // 更新商品数量的方法 const updateProductQuantities = () => { + console.log('updateProductQuantities'); // 先将所有商品的 cartNumber 初始化为 0 shopProductList.hots.forEach((i) => { i.cartNumber = 0 @@ -1171,7 +1166,7 @@ group.productList.forEach((product) => { if (product.id == cartItem.product_id && product.skuId == cartItem .sku_id) { - product.cartNumber = cartItem.number + product.cartNumber = cartItem.number || 0 product.cartListId = cartItem.id } }); @@ -1185,7 +1180,7 @@ if (group.id == cartItem.product_id) { // 更新商品的数量 group.cartListId = cartItem.id - group.cartNumber = cartItem.number + group.cartNumber = cartItem.number || 0 } }); }); @@ -1193,115 +1188,141 @@ //websocket产值 const websocketsendMessage = (data) => { - uni.$u.debounce(sendMessage(data), 500) + console.log(data); + uni.$u.debounce(useSocket.sendMessage(data), 500) } // 用于记录已经处理过的消息的 msg_id const processedMessageIds = new Set(); - // 监听接收到的消息变化 - watchEffect(async () => { - if (isDataLoaded.value && receivedMessages.value) { - const Message = receivedMessages.value - if (Message) { - console.log(Message.data); - // 心跳返回 过滤 - if (Message.type == "ping_interval" || Message.msg_id == "ping_interval") { - isLoading.value = false; - return false - } - // 检查消息是否已经处理过 - if (processedMessageIds.has(Message.msg_id)) { - return; - } - processedMessageIds.add(Message.msg_id); - // 初始化 - if (Message.operate_type == "init") { - cartStore.carts = Message.data - uni.hideLoading(); - isLoading.value = false; - } - - // 清空购物车 - if (Message.operate_type == 'cleanup') { - cartStore.carts = [] - setTimeout(() => { - Historicalorders() - }, 400) - showCart.value = false - } - - // 删除除购物车 - if (Message.operate_type == 'del' && Message.status == 1) { - // 优化:使用可选链操作符避免报错 - cartStore.carts = cartStore.carts.filter(item => item.id !== Message.data?.id); - // cartStore.carts = cartStore.carts.filter(item => item.id != Message.data.id); - } - - // 添加或者减少购物后返回 - if (Message.operate_type == 'add' || Message.operate_type == 'edit') { - [Message.data].forEach((objA) => { - const index = cartStore.carts.findIndex((objB) => objB.id == objA.id); - if (index !== -1) { - cartStore.carts[index] = objA; - } else { - cartStore.carts.push(objA); - } - }); - } - - // 历史订单 - if (Message.operate_type == 'clearOrder') { - Historicalorders() - } - - // 购物车数据更新从新请求 - if (Message.type == 'product' && Message.data_type == 'product_update' && Message - .operate_type == 'product_update') { - isDataLoaded.value = false; - productqueryProduct() - } - - // 提示 - if (Message.status == 0 && Message.type != 'no_suit_num') { - uni.showToast({ - title: Message.msg, - icon: "none" - }) - } - - if (Message.type == 'no_suit_num') { - // console.log(specifications) - uni.showModal({ - title: '提示', - showCancel: false, - content: '此商品库存不足起售数量!', - success: async (data) => { - await websocketsendMessage({ - id: Message.id, - type: 'shopping', - table_code: uni.cache.get('tableCode'), - shop_id: uni.cache.get('shopId'), - operate_type: 'del', - is_print: 1, - }) - } - }); - } - - //除去p 每次返回都回执消息 - await websocketsendMessage({ - type: 'receipt', - msg_id: Message.msg_id - }) - - // 初始化商品数量 - await updateProductQuantities() - - } + //设置商品初始选中 + function setGoodsInitSel(cartsArr) { + console.log('setGoodsInitSel'); + const arr = (cartsArr && cartsArr.length) ? cartsArr : cartStore.carts + if (arr.length <= 0) { + return } - }) + shopProductList.hots.map(v => { + const item = arr.find(cart => cart.product_id == v.id) + if (item) { + v.cartNumber = `${item.number}`; + } + }) + shopProductList.productInfo.map(info => { + info.productList.map(v => { + const item = arr.find(cart => cart.product_id == v.id) + if (item) { + v.cartNumber = `${item.number}`; + console.log(v.cartNumber); + } + }) + }) + } + + // 收到的消息变化 + async function onMessage(Message) { + if (Message) { + console.log('product index 收到消息',Message); + // 心跳返回 过滤 + if (Message.type == "ping_interval" || Message.msg_id == "ping_interval") { + isLoading.value = false; + return false + } + // 检查消息是否已经处理过 + if (processedMessageIds.has(Message.msg_id)) { + return; + } + processedMessageIds.add(Message.msg_id); + + // 初始化 + if (Message.operate_type == "init") { + cartStore.carts = Message.data + uni.hideLoading(); + isLoading.value = false; + // 初始化商品数量 + // setGoodsInitSel(Message.data) + } + + // 清空购物车 + if (Message.operate_type == 'cleanup') { + cartStore.carts = [] + setTimeout(() => { + Historicalorders() + }, 400) + showCart.value = false + } + + // 删除除购物车 + if (Message.operate_type == 'del' && Message.status == 1) { + // 优化:使用可选链操作符避免报错 + cartStore.carts = cartStore.carts.filter(item => item.id !== Message.data?.id); + // cartStore.carts = cartStore.carts.filter(item => item.id != Message.data.id); + } + + // 添加或者减少购物后返回 + if (Message.operate_type == 'add' || Message.operate_type == 'edit') { + [Message.data].forEach((objA) => { + const index = cartStore.carts.findIndex((objB) => objB.id == objA.id); + if (index !== -1) { + cartStore.carts[index] = objA; + } else { + cartStore.carts.push(objA); + } + }); + } + + // 历史订单 + if (Message.operate_type == 'clearOrder') { + Historicalorders() + } + + // 购物车数据更新从新请求 + if (Message.type == 'product' && Message.data_type == 'product_update' && Message + .operate_type == 'product_update') { + isDataLoaded.value = false; + productqueryProduct() + } + + // 提示 + if (Message.status == 0 && Message.type != 'no_suit_num') { + uni.showToast({ + title: Message.msg, + icon: "none" + }) + } + + if (Message.type == 'no_suit_num') { + // console.log(specifications) + uni.showModal({ + title: '提示', + showCancel: false, + content: '此商品库存不足起售数量!', + success: async (data) => { + await websocketsendMessage({ + id: Message.id, + type: 'shopping', + table_code: uni.cache.get('tableCode'), + shop_id: uni.cache.get('shopId'), + operate_type: 'del', + is_print: 1, + }) + } + }); + } + + //除去p 每次返回都回执消息 + await websocketsendMessage({ + type: 'receipt', + msg_id: Message.msg_id + }) + + // 初始化商品数量 + await updateProductQuantities() + + } + } + // 更新购物车数据shopProductList.hots const matchedProducts = computed(() => { @@ -1322,6 +1343,7 @@ for (const group of Specialstop) { for (const product of group.productList) { if (product.id == cartItem.product_id) { + console.log(cartItem); return { ...product, cartListinfo: cartItem, @@ -1490,29 +1512,50 @@ }); // 定义 ifcartNumber 计算属性方法 展示数量 - const ifcartNumber = computed(() => { - return (item) => { - // 如果 item 为空或者 cartNumber 不是字符串类型,返回 0 - if (!item || typeof item.cartNumber !== 'string') { - return 0; - } - let numValue = parseFloat(item.cartNumber); - if (isNaN(numValue)) { - // 如果转换结果是 NaN,说明 cartNumber 不是有效的数字字符串,返回 0 - return 0; - } - // type string 商品类型 single-单规格商品 sku-多规格商品 package-套餐商品 weight-称重商品 coupon-团购券 - if (item.type === 'weight') { - // 如果类型是称重重量,将值保留两位小数 - return parseFloat(numValue.toFixed(2)); - } else { - // 如果类型是整数,将值转换为整数 - return Math.round(numValue); - } - // 如果类型不匹配,返回原始值 - return item.cartNumber; - }; - }) + function ifcartNumber(item) { + // 如果 item 为空或者 cartNumber 不是字符串类型,返回 0 + if (!item || item.cartNumber * 1 == 0) { + return ''; + } + let numValue = parseFloat(item.cartNumber); + if (isNaN(numValue)) { + // 如果转换结果是 NaN,说明 cartNumber 不是有效的数字字符串,返回 0 + return ''; + } + // type string 商品类型 single-单规格商品 sku-多规格商品 package-套餐商品 weight-称重商品 coupon-团购券 + if (item.type === 'weight') { + // 如果类型是称重重量,将值保留两位小数 + return parseFloat(numValue.toFixed(2)); + } else { + // 如果类型是整数,将值转换为整数 + return Math.round(numValue); + } + // 如果类型不匹配,返回原始值 + return item.cartNumber; + } + // const ifcartNumber = computed(() => { + // return (item) => { + // // 如果 item 为空或者 cartNumber 不是字符串类型,返回 0 + // if (!item || typeof item.cartNumber !== 'string') { + // return 0; + // } + // let numValue = parseFloat(item.cartNumber); + // if (isNaN(numValue)) { + // // 如果转换结果是 NaN,说明 cartNumber 不是有效的数字字符串,返回 0 + // return 0; + // } + // // type string 商品类型 single-单规格商品 sku-多规格商品 package-套餐商品 weight-称重商品 coupon-团购券 + // if (item.type === 'weight') { + // // 如果类型是称重重量,将值保留两位小数 + // return parseFloat(numValue.toFixed(2)); + // } else { + // // 如果类型是整数,将值转换为整数 + // return Math.round(numValue); + // } + // // 如果类型不匹配,返回原始值 + // return item.cartNumber; + // }; + // }) // 计算处理后的购物车列表 // 用于筛选后的购物车数组 const cartListFilter = computed(() => { @@ -1609,6 +1652,7 @@ isDataLoaded.value = true; // 历史订单 Historicalorders() + updateProductQuantities() } else { uni.showToast({ title: '暂无列表数据,请重新扫码', @@ -1628,27 +1672,28 @@ await proxy.$onLaunched; }) - onShow(async() => { + onShow(async () => { // 监听页面显示和隐藏 - onShowconnect() + useSocket.setOnMessage(onMessage) + useSocket.onShowconnect() let res = await APIhistoryOrder({ tableCode: uni.cache.get('tableCode'), }) - if(res){ + if (res) { orderinfo.value = { id: res.id, detailMap: res.detailMap, placeNum: res.placeNum } - }else{ + } else { orderinfo.value = { - id:'' + id: '' } } }) onHide(() => { - closeExistingConnection() + }) onMounted(async () => { @@ -1670,7 +1715,7 @@ } setTimeout(() => { // 启动网络监听 - initNetworkListener() + useSocket.initNetworkListener() getElementTop() }, 500) }) diff --git a/stores/carts-websocket.js b/stores/carts-websocket.js new file mode 100644 index 0000000..0155f59 --- /dev/null +++ b/stores/carts-websocket.js @@ -0,0 +1,368 @@ +import { + defineStore +} from 'pinia'; +import { + ref, + computed, + reactive, + onMounted, + onBeforeUnmount, + watchEffect +} from 'vue'; + + + +export const useWebSocket = defineStore('socketTask', () => { + const options = {} + let { + heartbeatInterval = 10000, //心跳是10秒一次 + reconnectInterval = 3000, //重新连接间隔时间的一个参数 + maxReconnectAttempts = 3, //最大重连接次数 + initialReconnectInterval = 3000, // 初始重连间隔 + initMessage, + initMessageRetryCount = 3, // 新增:初始化消息发送重试次数 + initMessageRetryInterval = 2000, // 新增:初始化消息重试间隔 + maxReconnectDuration = Infinity, + } = options; + const autoReconnect = ref(true); //是否自动重新连接 + const socketTask = ref(null); + const isConnected = ref(false); //表示是否已连接上。 + const heartbeatTimer = ref(null); //心跳定时器 + const reconnectTimer = ref(null); //重连定时器 + const reconnectAttempts = ref(0); //重连的尝试次数 + const isNetworkConnected = ref(true); //监听当前网络连接状态 + const isManuallyClosed = ref(false); //是否是被手动关闭的 + const receivedMessages = ref(); //储从 WebSocket 服务器接收到的消息 + const initMessageSendAttempts = ref(0); //初始化连接多少次 + const reconnectStartTime = ref(0); //新增:记录重连开始时间 + const isPongReceived = ref(false) + const allowReconnect = ref(true); // 新增:控制是否允许重连 + // 关闭现有连接并清理资源 + const closeExistingConnection = () => { + try { + socketTask.value.close({ + success: () => { + console.log('WebSocket 连接已关闭'); + }, + fail: (err) => { + console.error('关闭 WebSocket 连接失败:', err); + } + }); + + // 清除心跳定时器 + clearInterval(heartbeatTimer.value); + heartbeatTimer.value = null; + + // 清除重连定时器 + clearTimeout(reconnectTimer.value); + reconnectTimer.value = null; + + // 标记连接已断开 + isConnected.value = false; + } catch (error) { + console.error(error) + //TODO handle the exception + } + + + }; + const websocketsendMessage = (data) => { + uni.$u.debounce(sendMessage(data), 500) + } + + + const handlePong = (res) => { + try { + let data = JSON.parse(res.data); + if (data.operate_type == "init" || (data + .msg === 'ok' && + data.msg_id == + 'ping_interval')) { + isPongReceived.value = true; + console.log('收到心跳响应,清除超时定时器'); + clearTimeout(pongTimer); + } + } catch (error) { + console.error('解析心跳响应数据时出错:', error); + } + }; + + let onMessage = () => { + + } + function setOnMessage(onMessageBallBack){ + onMessage=onMessageBallBack + } + // 连接 WebSocket + const connect = async (connectMsg, onMessageBallBack) => { + if (!isNetworkConnected.value) { + uni.showToast({ + title: '网络未连接...', + icon: 'none' + }) + setTimeout(() => { + uni.pro.switchTab('index/index') + }, 1000) + console.log('网络未连接,暂不尝试连接 WebSocket'); + return; + } + if (connectMsg) { + initMessage = connectMsg + } + if (onMessageBallBack) { + onMessage = onMessageBallBack + } + if (socketTask.value && isConnected.value) { + try { + sendMessage(connectMsg ? connectMsg : initMessage) + } catch (error) { + //TODO handle the exception + } + return + } + socketTask.value = uni.connectSocket({ + url: uni.conf.baseUrlwws + '?' + Date.now(), + success: (res) => { + console.log('连接成功'); + isConnected.value = true; + // 监听初始化成功在开启心跳 + startHeartbeat(); + }, + fail: () => { + console.error('WebSocket 连接失败,尝试重连'); + if (autoReconnect.value && allowReconnect.value) { + handleReconnect(); + } + } + }); + + if (socketTask.value) { + socketTask.value.onOpen(() => { + // 初始化 初始购物车 + sendMessage(connectMsg ? connectMsg : initMessage) + isConnected.value = true + }); + socketTask.value.onMessage((res) => { + receivedMessages.value = JSON.parse(res.data) + console.log('收到消息',receivedMessages.value) + sendMessage({ + type: 'receipt', + msg_id: receivedMessages.value.msg_id + }) + if (onMessage) { + onMessage(receivedMessages.value) + } + // receivedMessages.value.push(list); + if (receivedMessages.value == 'ok' || receivedMessages.value + .operate_type == 'init') { + console.log('初始化正常,心跳响应正常'); + // 清除重连定时器 + clearTimeout(reconnectTimer.value); + allowReconnect.value = false + reconnectTimer.value = null; + } + + }); + + socketTask.value.onClose((res) => { + console.log(res, 'WebSocket 连接已关闭,尝试重连'); + isConnected.value = false; + clearInterval(heartbeatTimer.value); // 停止心跳定时器 + clearTimeout(reconnectTimer.value); // 清除重连定时器 + if (res.code == '1006' && !allowReconnect.value) { + uni.showToast({ + title: '网络异常,请重新扫码', + icon: 'none' + }); + autoReconnect.value = false; + setTimeout(() => { + uni.pro.switchTab('index/index'); + }, 1000) + return false; + } + if (autoReconnect.value && !isManuallyClosed.value) { + handleReconnect(); + } + }); + + socketTask.value.onError((err) => { + console.error('WebSocket 连接发生错误:', err); + isConnected.value = false; + clearInterval(heartbeatTimer.value); + if (autoReconnect.value && !isManuallyClosed.value) { + handleReconnect(); + } + }); + } else { + console.error('socketTask 未正确初始化'); + } + }; + + // 启动心跳机制 + const startHeartbeat = () => { + if (!isNetworkConnected.value) { + console.log('网络未连接,暂停心跳'); + uni.showToast({ + title: '网络未连接...', + icon: 'none' + }); + setTimeout(() => { + uni.pro.switchTab('index/index'); + }, 1000); + return; + } + clearInterval(heartbeatTimer.value) + heartbeatTimer.value = setInterval(() => { + sendMessage({ + type: 'ping_interval', + set: 'shopping' + }) + }, heartbeatInterval); + }; + + + // 手动关闭连接 + const closeSocket = () => { + console.log('手动关闭连接'); + isManuallyClosed.value = true; + closeExistingConnection(); + }; + + // 发送消息 + const sendMessage = (data) => { + if (isConnected.value && data) { + // console.log('发送消息', data); + socketTask.value.send({ + data: JSON.stringify(data), + success: () => { + // console.log('消息发送成功'); + }, + fail: () => { + // console.error('消息发送失败'); + } + }); + } else { + console.error('WebSocket 未连接,无法发送消息'); + } + + }; + + // 处理重连逻辑 + const handleReconnect = () => { + if (!isNetworkConnected.value) { + console.log('网络未连接,暂停重连'); + return; + } + if (isManuallyClosed.value) { + console.log('手动关闭连接,不进行重连'); + return; + } + + if (!allowReconnect.value) { + console.log('重连功能已关闭,不进行重连'); + return; + } + + if (reconnectAttempts.value < maxReconnectAttempts) { + reconnectAttempts.value++; + const reconnectInterval = initialReconnectInterval * Math.pow(2, reconnectAttempts + .value - 1); + const randomizedInterval = reconnectInterval + Math.floor(Math.random() * 1000); + uni.showLoading({ + title: `正在努力连接..`, + mask: true + }) + console.log(`尝试第 ${reconnectAttempts.value} 次重连,重连间隔: ${randomizedInterval}ms...`); + + reconnectTimer.value = setTimeout(() => { + connect(); + }, randomizedInterval); + } else { + console.error('重连次数达到上限,停止重连'); + uni.showToast({ + title: '重连次数达到上限,停止重连', + icon: 'none' + }); + clearInterval(heartbeatTimer.value); + autoReconnect.value = false; + setTimeout(() => { + uni.hideLoading(); + uni.pro.switchTab('index/index'); + }, 1000) + } + }; + + + // / 网络状态监听 + const initNetworkListener = () => { + uni.getSystemInfo({ + success: (res) => { + if (res.platform !== 'devtools') { + uni.onNetworkStatusChange((statusRes) => { + isNetworkConnected.value = statusRes.isConnected; + if (statusRes.isConnected && !isManuallyClosed.value) { + console.log('网络已连接,尝试重新连接 WebSocket'); + if (!isConnected.value && autoReconnect.value) { + connect(); + } + } else if (!statusRes.isConnected) { + console.log('网络已断开,暂停 WebSocket 操作'); + clearInterval(heartbeatTimer.value); + clearTimeout(reconnectTimer.value); + if (socketTask.value) { + socketTask.value.close(); + isConnected.value = false; + } + } + }); + } + }, + fail: (err) => { + console.error('获取系统信息失败:', err); + } + }); + + uni.getNetworkType({ + success: (res) => { + isNetworkConnected.value = res.networkType !== 'none'; + if (!isNetworkConnected.value) { + console.log('初始网络未连接,暂不尝试连接 WebSocket'); + } + } + }); + }; + + // 页面显示,尝试连接 WebSocket + const onShowconnect = () => { + if (autoReconnect.value) { + uni.showLoading({ + title: `尝试再次连接`, + mask: true + }) + connect(); + } + } + + + + onBeforeUnmount(() => { + // closeSocket(); + }); + + + + + return { + isConnected, + sendMessage, + closeSocket, + receivedMessages, + closeExistingConnection, + connect, + onShowconnect, + initNetworkListener, + connect, + allowReconnect, + socketTask,setOnMessage + }; +}) \ No newline at end of file diff --git a/stores/carts.js b/stores/carts.js index 5191204..9965c60 100644 --- a/stores/carts.js +++ b/stores/carts.js @@ -27,7 +27,7 @@ export const useCartsStore = defineStore('cart', const goodsMap = reactive({}) //获取商品数据 async function goodsInit() { - goodsIsloading.value=true; + goodsIsloading.value = true; //获取招牌菜商品 const hotres = await productminiApphotsquery(); for (let product of hotres) { @@ -102,12 +102,10 @@ export const useCartsStore = defineStore('cart', return getProductDetails(v) }).filter(v => v) } - + //收到socket消息时对购物车进行处理 async function onMessage(Message) { - console.log('Message'); - console.log(Message); if (Message) { // 心跳返回 过滤 if (Message.type == "ping_interval" || Message.msg_id == "ping_interval") { @@ -184,8 +182,8 @@ export const useCartsStore = defineStore('cart', } } - - + + //购物车数据 const carts = ref([]) @@ -219,13 +217,13 @@ export const useCartsStore = defineStore('cart', //是否使用会员价 const useVipPrice = computed(() => { - const isUse=(orderVIP.value.isVip && shopInfo.value.isMemberPrice)?true:false - console.log('useVipPrice',isUse); + const isUse = (orderVIP.value.isVip && shopInfo.value.isMemberPrice) ? true : false return isUse }) - function currentCalcMpneyNumber(item){ - const n=item.number-(item.returnNum||0) - return n<=0?0:n + + function currentCalcMpneyNumber(item) { + const n = item.number - (item.returnNum || 0) + return n <= 0 ? 0 : n } //历史订单商品价格总和 const oldOrderMoney = computed(() => { @@ -239,7 +237,7 @@ export const useCartsStore = defineStore('cart', const memberPrice = cur.skuData ? (cur.skuData.memberPrice || cur.skuData .salePrice) : 0 const price = (discount_sale_amount || cur.salePrice || 0) - const number =currentCalcMpneyNumber(cur) + const number = currentCalcMpneyNumber(cur) return prve + (number <= 0 ? 0 : number) * (discount_sale_amount || (useVipPrice .value ? memberPrice : price)) }, 0) @@ -252,7 +250,7 @@ export const useCartsStore = defineStore('cart', const money = carts.value.reduce((prve, cur) => { const memberPrice = cur.memberPrice || cur.salePrice const price = useVipPrice.value ? memberPrice : cur.salePrice; - const curMoney = price * currentCalcMpneyNumber(cur) + const curMoney = price * currentCalcMpneyNumber(cur) return prve + curMoney }, 0) return money @@ -260,7 +258,7 @@ export const useCartsStore = defineStore('cart', // 霸王餐购物车原价,不享受任何优惠 const totalOriginPrice = computed(() => { const money = carts.value.reduce((prve, cur) => { - const curMoney = cur.salePrice * currentCalcMpneyNumber(cur) + const curMoney = cur.salePrice * currentCalcMpneyNumber(cur) return prve + curMoney }, 0) return money @@ -274,7 +272,7 @@ export const useCartsStore = defineStore('cart', // return pre + returnProDiscount(cur, index) * 1; // }, 0); // }); - + //返回打包数量(称重商品打包数量最大为1) function returnCartPackNumber(cur) { @@ -285,17 +283,17 @@ export const useCartsStore = defineStore('cart', pack_number = pack_number <= 0 ? 0 : pack_number return pack_number * 1 } - - + + //当前购物车打包费 const totalPackFee = computed(() => { const money = carts.value.reduce((prve, cur) => { - const curMoney = (cur.packFee || 0) * currentCalcMpneyNumber(cur) + const curMoney = (cur.packFee || 0) * currentCalcMpneyNumber(cur) return prve + curMoney }, 0) return money }) - + //打包费 const packFee = computed(() => { const nowPackFee = carts.value.reduce((acc, cur) => { @@ -351,7 +349,7 @@ export const useCartsStore = defineStore('cart', console.log('isBwc'); console.log(isBwc); let cart = matchedProducts.reduce((total, item) => { - if(isBwc===true){ + if (isBwc === true) { return total + (parseFloat(item.price) * parseFloat(item.num - item.returnNum)); } // 是否启用会员价 0否1是 @@ -447,7 +445,8 @@ export const useCartsStore = defineStore('cart', totalPrice, totalPackFee, updateData, - useVipPrice,totalOriginPrice + useVipPrice, + totalOriginPrice }; } ); \ No newline at end of file