From 01e8f6c6da65d3dbb6670d99a04bb2e8767d6d37 Mon Sep 17 00:00:00 2001 From: zhangyibo95 <690096405@qq.com> Date: Mon, 21 Sep 2026 16:22:05 +0800 Subject: [PATCH] =?UTF-8?q?update=EF=BC=9A=E4=BC=98=E5=8C=96ota=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=85=B1=E4=BA=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/OtaModal.vue | 9 + src/composables/useOtaUpdate.js | 227 ++++++++++++++++++ src/pages/device/composables/useOtaUpdate.js | 188 --------------- src/pages/device/my-device.vue | 2 +- src/pages/device/ota-wifi.vue | 2 +- src/pages/index.vue | 228 ++++++++----------- 6 files changed, 328 insertions(+), 328 deletions(-) create mode 100644 src/composables/useOtaUpdate.js delete mode 100644 src/pages/device/composables/useOtaUpdate.js diff --git a/src/components/OtaModal.vue b/src/components/OtaModal.vue index c94047c..4a61c0a 100644 --- a/src/components/OtaModal.vue +++ b/src/components/OtaModal.vue @@ -158,6 +158,7 @@ const handleUpdateClick = () => { + 请勿离开当前页面! @@ -439,6 +440,14 @@ const handleUpdateClick = () => { background-color: #FED847; border-radius: 999rpx; } +.progress-warning { + display: block; + margin-top: 16rpx; + color: rgba(255, 255, 255, 0.88); + font-size: 22rpx; + line-height: 32rpx; + text-align: center; +} /* 关闭按钮(位于弹窗下方) */ .ota-close-below { diff --git a/src/composables/useOtaUpdate.js b/src/composables/useOtaUpdate.js new file mode 100644 index 0000000..2fcc788 --- /dev/null +++ b/src/composables/useOtaUpdate.js @@ -0,0 +1,227 @@ +import { computed, ref } from "vue"; +import { sendHardwareBoxUpdateAPI } from "@/apis"; +import useStore from "@/store"; +import { storeToRefs } from "pinia"; + +const OTA_PROGRESS_EVENT = "/addons/shoot/otaProgress"; +const OTA_RESULT_EVENT = "/addons/shoot/otaResult"; +const UPDATE_TIMEOUT = 10 * 60 * 1000; + +const phaseTextMap = { + started: "正在准备固件更新", + downloading: "正在下载固件", + installing: "正在安装固件", +}; + +// OTA 状态由首页、WiFi 设置页和我的设备页共享,页面切换后仍可继续接收进度。 +const updating = ref(false); +const progress = ref(0); +const phase = ref("started"); +const resultVisible = ref(false); +const resultStatus = ref(""); +const resultReason = ref(""); + +let deviceRef = null; +let updateRunId = 0; +let targetVersion = ""; +let lastFinishedVersion = ""; +let timeoutTimer = null; +let successCallback = null; +let socketListening = false; + +const phaseText = computed( + () => phaseTextMap[phase.value] || "正在进行固件更新" +); +const resultTitle = computed(() => + resultStatus.value === "success" ? "固件更新完成" : "固件更新失败" +); +const resultContent = computed(() => { + if (resultStatus.value === "success") return "固件更新已完成"; + return resultReason.value || "更新失败,请检查设备及网络后重试"; +}); + +const clearTimers = () => { + clearTimeout(timeoutTimer); + timeoutTimer = null; +}; + +const resetActiveUpdate = () => { + clearTimers(); + successCallback = null; +}; + +const finishUpdate = (status, reason = "", runId = updateRunId) => { + if (runId !== updateRunId || !updating.value) return; + const onSuccess = successCallback; + resetActiveUpdate(); + updating.value = false; + resultStatus.value = status; + resultReason.value = reason; + lastFinishedVersion = targetVersion; + if (status === "success") { + progress.value = 100; + phase.value = "installing"; + onSuccess?.(); + } + resultVisible.value = true; +}; + +const isCurrentDeviceMessage = (data) => { + const messageDeviceId = String(data?.deviceId || ""); + const currentDeviceId = String(deviceRef?.value?.deviceId || ""); + return !messageDeviceId || !currentDeviceId || messageDeviceId === currentDeviceId; +}; + +const isCurrentVersionMessage = (data) => { + const messageVersion = String(data?.versionNumber || ""); + return !messageVersion || !targetVersion || messageVersion === targetVersion; +}; + +// 页面切换导致弹窗关闭后,使用下一条进度消息恢复当前 OTA 会话。 +const recoverUpdateFromMessage = (data) => { + updateRunId += 1; + const runId = updateRunId; + targetVersion = String(data?.versionNumber || ""); + lastFinishedVersion = ""; + successCallback = null; + resultVisible.value = false; + resultStatus.value = ""; + resultReason.value = ""; + progress.value = 0; + phase.value = "started"; + updating.value = true; + clearTimers(); + timeoutTimer = setTimeout(() => { + finishUpdate("failed", "固件更新超时,请稍后重试", runId); + }, UPDATE_TIMEOUT); +}; + +function handleSocketMessage(message) { + if (Number(message?.code ?? 0) !== 0) return; + if (![OTA_PROGRESS_EVENT, OTA_RESULT_EVENT].includes(message?.event)) return; + if (!isCurrentDeviceMessage(message.data)) return; + + const messageVersion = String(message.data?.versionNumber || ""); + + if (message.event === OTA_PROGRESS_EVENT) { + if (!updating.value) { + if ( + lastFinishedVersion && + (!messageVersion || messageVersion === lastFinishedVersion) + ) { + return; + } + recoverUpdateFromMessage(message.data); + } else if (!isCurrentVersionMessage(message.data)) { + return; + } + + const nextProgress = Math.min( + 100, + Math.max(0, Number(message.data?.progress) || 0) + ); + progress.value = Math.max(progress.value, nextProgress); + if (phaseTextMap[message.data?.phase]) { + phase.value = message.data.phase; + } + return; + } + + if (!updating.value) { + if ( + lastFinishedVersion && + (!messageVersion || messageVersion === lastFinishedVersion) + ) { + return; + } + recoverUpdateFromMessage(message.data); + } else if (!isCurrentVersionMessage(message.data)) { + return; + } + + if (message.data?.status === "success") { + finishUpdate("success"); + } else if (message.data?.status === "failed") { + finishUpdate("failed", message.data?.reason || ""); + } +} + +const startSocketListening = () => { + if (socketListening) return; + uni.$on("socket-inbox", handleSocketMessage); + socketListening = true; +}; + +export const useOtaUpdate = () => { + const store = useStore(); + const { device } = storeToRefs(store); + deviceRef = device; + startSocketListening(); + + const startUpdate = async ({ + versionNumber, + resourceUrl, + wifiSsid = "", + wifiPassword = "", + onSuccess, + }) => { + if (updating.value) return false; + + updateRunId += 1; + const runId = updateRunId; + targetVersion = String(versionNumber || ""); + lastFinishedVersion = ""; + successCallback = onSuccess || null; + resultVisible.value = false; + resultStatus.value = ""; + resultReason.value = ""; + progress.value = 0; + phase.value = "started"; + updating.value = true; + clearTimers(); + timeoutTimer = setTimeout(() => { + finishUpdate("failed", "固件更新超时,请稍后重试", runId); + }, UPDATE_TIMEOUT); + + try { + const updateResult = await sendHardwareBoxUpdateAPI({ + versionNumber: targetVersion, + wifiSsid, + wifiPassword, + resourceUrl, + }); + if (runId !== updateRunId || !updating.value) return false; + if (!updateResult?.taskId) { + finishUpdate("failed", "固件更新任务创建失败,请稍后重试", runId); + return false; + } + return true; + } catch (error) { + if (runId === updateRunId) { + finishUpdate( + "failed", + error?.message || "固件更新请求失败,请稍后重试", + runId + ); + } + return false; + } + }; + + const closeResult = () => { + resultVisible.value = false; + }; + + return { + updating, + progress, + phase, + phaseText, + resultVisible, + resultStatus, + resultTitle, + resultContent, + startUpdate, + closeResult, + }; +}; diff --git a/src/pages/device/composables/useOtaUpdate.js b/src/pages/device/composables/useOtaUpdate.js deleted file mode 100644 index 6c6b39f..0000000 --- a/src/pages/device/composables/useOtaUpdate.js +++ /dev/null @@ -1,188 +0,0 @@ -import { computed, onUnmounted, ref } from "vue"; -import { sendHardwareBoxUpdateAPI } from "@/apis"; -import useStore from "@/store"; -import { storeToRefs } from "pinia"; - -const OTA_PROGRESS_EVENT = "/addons/shoot/otaProgress"; -const OTA_RESULT_EVENT = "/addons/shoot/otaResult"; -const UPDATE_TIMEOUT = 10 * 60 * 1000; - -const phaseTextMap = { - started: "正在准备固件更新", - downloading: "正在下载固件", - installing: "正在安装固件", -}; - -export const useOtaUpdate = () => { - const store = useStore(); - const { device } = storeToRefs(store); - const updating = ref(false); - const progress = ref(0); - const phase = ref("started"); - const resultVisible = ref(false); - const resultStatus = ref(""); - const resultReason = ref(""); - - let updateRunId = 0; - let targetVersion = ""; - let timeoutTimer = null; - let successCallback = null; - let socketListening = false; - - const phaseText = computed( - () => phaseTextMap[phase.value] || "正在进行固件更新" - ); - const resultTitle = computed(() => - resultStatus.value === "success" ? "固件更新完成" : "固件更新失败" - ); - const resultContent = computed(() => { - if (resultStatus.value === "success") return "固件更新已完成"; - return resultReason.value || "更新失败,请检查设备及网络后重试"; - }); - - const clearTimers = () => { - clearTimeout(timeoutTimer); - timeoutTimer = null; - }; - - const stopSocketListening = () => { - if (!socketListening) return; - uni.$off("socket-inbox", handleSocketMessage); - socketListening = false; - }; - - const resetActiveUpdate = () => { - clearTimers(); - stopSocketListening(); - successCallback = null; - }; - - const finishUpdate = (status, reason = "", runId = updateRunId) => { - if (runId !== updateRunId || !updating.value) return; - const onSuccess = successCallback; - resetActiveUpdate(); - updating.value = false; - resultStatus.value = status; - resultReason.value = reason; - if (status === "success") { - progress.value = 100; - phase.value = "installing"; - onSuccess?.(); - } - resultVisible.value = true; - }; - - const isCurrentUpdateMessage = (data) => { - const messageDeviceId = String(data?.deviceId || ""); - const currentDeviceId = String(device.value?.deviceId || ""); - if (messageDeviceId && currentDeviceId && messageDeviceId !== currentDeviceId) { - return false; - } - - const messageVersion = String(data?.versionNumber || ""); - return !messageVersion || !targetVersion || messageVersion === targetVersion; - }; - - function handleSocketMessage(message) { - if (!updating.value || Number(message?.code ?? 0) !== 0) return; - if (![OTA_PROGRESS_EVENT, OTA_RESULT_EVENT].includes(message?.event)) return; - if (!isCurrentUpdateMessage(message.data)) return; - - if (message.event === OTA_PROGRESS_EVENT) { - const nextProgress = Math.min( - 100, - Math.max(0, Number(message.data?.progress) || 0) - ); - progress.value = Math.max(progress.value, nextProgress); - if (phaseTextMap[message.data?.phase]) { - phase.value = message.data.phase; - } - return; - } - - if (message.data?.status === "success") { - finishUpdate("success"); - return; - } - if (message.data?.status === "failed") { - finishUpdate("failed", message.data?.reason || ""); - } - } - - const startSocketListening = () => { - if (socketListening) return; - uni.$on("socket-inbox", handleSocketMessage); - socketListening = true; - }; - - const startUpdate = async ({ - versionNumber, - resourceUrl, - wifiSsid = "", - wifiPassword = "", - onSuccess, - }) => { - if (updating.value) return false; - - updateRunId += 1; - const runId = updateRunId; - targetVersion = String(versionNumber || ""); - successCallback = onSuccess || null; - resultVisible.value = false; - resultStatus.value = ""; - resultReason.value = ""; - progress.value = 0; - phase.value = "started"; - updating.value = true; - startSocketListening(); - timeoutTimer = setTimeout(() => { - finishUpdate("failed", "固件更新超时,请稍后重试", runId); - }, UPDATE_TIMEOUT); - - try { - const updateResult = await sendHardwareBoxUpdateAPI({ - versionNumber: targetVersion, - wifiSsid, - wifiPassword, - resourceUrl, - }); - if (runId !== updateRunId || !updating.value) return false; - if (!updateResult?.taskId) { - finishUpdate("failed", "固件更新任务创建失败,请稍后重试", runId); - return false; - } - return true; - } catch (error) { - if (runId === updateRunId) { - finishUpdate( - "failed", - error?.message || "固件更新请求失败,请稍后重试", - runId - ); - } - return false; - } - }; - - const closeResult = () => { - resultVisible.value = false; - }; - - onUnmounted(() => { - updateRunId += 1; - resetActiveUpdate(); - }); - - return { - updating, - progress, - phase, - phaseText, - resultVisible, - resultStatus, - resultTitle, - resultContent, - startUpdate, - closeResult, - }; -}; diff --git a/src/pages/device/my-device.vue b/src/pages/device/my-device.vue index ff7b693..2a4f61f 100644 --- a/src/pages/device/my-device.vue +++ b/src/pages/device/my-device.vue @@ -18,7 +18,7 @@ import { DEVICE_NAME_STORAGE_KEY, useDeviceStatus, } from "./composables/useDeviceStatus"; -import { useOtaUpdate } from "./composables/useOtaUpdate"; +import { useOtaUpdate } from "@/composables/useOtaUpdate"; const store = useStore(); const { updateDevice, clearDevice } = store; diff --git a/src/pages/device/ota-wifi.vue b/src/pages/device/ota-wifi.vue index 805a8d5..d287558 100644 --- a/src/pages/device/ota-wifi.vue +++ b/src/pages/device/ota-wifi.vue @@ -9,7 +9,7 @@ import { connectDeviceWifiAPI, getHardwareBoxVersionAPI, } from "@/apis"; -import { useOtaUpdate } from "./composables/useOtaUpdate"; +import { useOtaUpdate } from "@/composables/useOtaUpdate"; const STATES = { SCANNING: "SCANNING", diff --git a/src/pages/index.vue b/src/pages/index.vue index 8597b64..9a3a965 100644 --- a/src/pages/index.vue +++ b/src/pages/index.vue @@ -1,5 +1,5 @@