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: "正在安装固件", }; // 设备详情中的 latestVersion 是升级目标,version 是当前固件版本。 export const getOtaInfoFromDetail = (detail) => ({ versionNumber: String(detail?.latestVersion ?? "").trim(), currentVersion: String(detail?.version ?? "").trim(), versionInfo: String(detail?.versionInfo ?? "").trim(), needUpdate: detail?.needUpdate === true || Number(detail?.needUpdate) === 1, forceUpdate: detail?.forceUpdate === true || Number(detail?.forceUpdate) === 1, }); // 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; } if (!targetVersion && messageVersion) targetVersion = messageVersion; 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 (!targetVersion && messageVersion) targetVersion = messageVersion; 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, wifiSsid = "", wifiPassword = "", onSuccess, }) => { if (updating.value) return false; const nextVersion = String(versionNumber || "").trim(); if (!nextVersion) { uni.showToast({ title: "缺少固件目标版本", icon: "none" }); return false; } updateRunId += 1; const runId = updateRunId; targetVersion = nextVersion; 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, }); 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, }; };