update:优化ota状态共享
This commit is contained in:
@@ -158,6 +158,7 @@ const handleUpdateClick = () => {
|
||||
<view class="progress-track">
|
||||
<view class="progress-fill" :style="{ width: `${progressValue}%` }"></view>
|
||||
</view>
|
||||
<text class="progress-warning">请勿离开当前页面!</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
connectDeviceWifiAPI,
|
||||
getHardwareBoxVersionAPI,
|
||||
} from "@/apis";
|
||||
import { useOtaUpdate } from "./composables/useOtaUpdate";
|
||||
import { useOtaUpdate } from "@/composables/useOtaUpdate";
|
||||
|
||||
const STATES = {
|
||||
SCANNING: "SCANNING",
|
||||
|
||||
+90
-138
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import {computed, onMounted, onUnmounted, ref, watch} from "vue";
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
import {onShareAppMessage, onShareTimeline, onShow} from "@dcloudio/uni-app";
|
||||
import Container from "@/components/Container.vue";
|
||||
import AppFooter from "@/components/AppFooter.vue";
|
||||
@@ -16,10 +16,10 @@ import {
|
||||
getHomeData,
|
||||
getMyDevicesAPI,
|
||||
getScoreRankList,
|
||||
sendHardwareBoxUpdateAPI,
|
||||
silentLoginAPI,
|
||||
} from "@/apis";
|
||||
import {topThreeColors} from "@/constants";
|
||||
import {useOtaUpdate} from "@/composables/useOtaUpdate";
|
||||
|
||||
import useStore from "@/store";
|
||||
import {storeToRefs} from "pinia";
|
||||
@@ -70,9 +70,6 @@ const deviceCardAsset = computed(() => deviceCardAssets[deviceCardState.value]);
|
||||
// OTA 相关
|
||||
const otaVisible = ref(false);
|
||||
const wifiRequiredVisible = ref(false);
|
||||
const otaState = ref("new_version");
|
||||
const otaProgress = ref(0);
|
||||
const otaPhase = ref("started");
|
||||
const otaInfo = ref({
|
||||
versionNumber: "",
|
||||
versionInfo: "",
|
||||
@@ -83,26 +80,16 @@ const otaInfo = ref({
|
||||
const isStartingOta = ref(false);
|
||||
let isCheckingOta = false;
|
||||
let otaCheckQueuedForOnline = false;
|
||||
let otaTimeoutTimer = null;
|
||||
let otaUpdateRunId = 0;
|
||||
const OTA_UPDATE_TIMEOUT = 10 * 60 * 1000;
|
||||
const OTA_PROGRESS_EVENT = "/addons/shoot/otaProgress";
|
||||
const OTA_RESULT_EVENT = "/addons/shoot/otaResult";
|
||||
|
||||
// 清理首页 OTA 更新超时计时器。
|
||||
const clearOtaUpdateTimers = () => {
|
||||
clearTimeout(otaTimeoutTimer);
|
||||
otaTimeoutTimer = null;
|
||||
};
|
||||
|
||||
// 使当前 OTA 运行失效,确保旧请求返回后不会覆盖新状态。
|
||||
const invalidateOtaUpdateRun = () => {
|
||||
otaUpdateRunId += 1;
|
||||
clearOtaUpdateTimers();
|
||||
};
|
||||
|
||||
const isOtaUpdateRunActive = (runId) =>
|
||||
runId === otaUpdateRunId && otaState.value === "update_progress";
|
||||
const {
|
||||
updating: otaUpdating,
|
||||
progress: otaProgress,
|
||||
phase: otaPhase,
|
||||
resultVisible: otaResultVisible,
|
||||
resultStatus: otaResultStatus,
|
||||
startUpdate: startOtaUpdate,
|
||||
closeResult: closeOtaResult,
|
||||
} = useOtaUpdate();
|
||||
|
||||
// 获取并保存后端返回的 OTA 版本信息,供弹窗展示和更新接口使用。
|
||||
const applyOtaVersionInfo = (versionInfo) => {
|
||||
@@ -118,11 +105,21 @@ const applyOtaVersionInfo = (versionInfo) => {
|
||||
|
||||
// 检查当前设备盒子是否存在可升级版本。
|
||||
const checkOtaUpdate = async () => {
|
||||
if (isCheckingOta || otaVisible.value) return;
|
||||
if (
|
||||
isCheckingOta ||
|
||||
otaVisible.value ||
|
||||
otaUpdating.value ||
|
||||
otaResultVisible.value
|
||||
) return;
|
||||
isCheckingOta = true;
|
||||
|
||||
try {
|
||||
if (online.value !== true || otaVisible.value) return;
|
||||
if (
|
||||
online.value !== true ||
|
||||
otaVisible.value ||
|
||||
otaUpdating.value ||
|
||||
otaResultVisible.value
|
||||
) return;
|
||||
|
||||
let versionInfo;
|
||||
try {
|
||||
@@ -130,20 +127,24 @@ const checkOtaUpdate = async () => {
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
if (otaVisible.value) return;
|
||||
if (otaVisible.value || otaUpdating.value || otaResultVisible.value) return;
|
||||
applyOtaVersionInfo(versionInfo);
|
||||
if (!otaInfo.value.needUpdate) return;
|
||||
|
||||
const dismissedAt = uni.getStorageSync("ota_dismissed_at");
|
||||
const now = Date.now();
|
||||
if (!otaInfo.value.forceUpdate && dismissedAt && now - dismissedAt < 24 * 60 * 60 * 1000) return;
|
||||
otaState.value = "new_version";
|
||||
otaVisible.value = true;
|
||||
} finally {
|
||||
isCheckingOta = false;
|
||||
const shouldRecheckForOnline = otaCheckQueuedForOnline;
|
||||
otaCheckQueuedForOnline = false;
|
||||
if (shouldRecheckForOnline && !otaVisible.value) {
|
||||
if (
|
||||
shouldRecheckForOnline &&
|
||||
!otaVisible.value &&
|
||||
!otaUpdating.value &&
|
||||
!otaResultVisible.value
|
||||
) {
|
||||
void checkOtaUpdate();
|
||||
}
|
||||
}
|
||||
@@ -151,7 +152,13 @@ const checkOtaUpdate = async () => {
|
||||
|
||||
// 设备由 WS 通知上线时补查 OTA;已显示任何 OTA 弹窗时保留当前流程和结果。
|
||||
watch(online, (nextOnline, previousOnline) => {
|
||||
if (previousOnline !== false || nextOnline !== true || otaVisible.value) return;
|
||||
if (
|
||||
previousOnline !== false ||
|
||||
nextOnline !== true ||
|
||||
otaVisible.value ||
|
||||
otaUpdating.value ||
|
||||
otaResultVisible.value
|
||||
) return;
|
||||
if (isCheckingOta) {
|
||||
otaCheckQueuedForOnline = true;
|
||||
return;
|
||||
@@ -159,6 +166,13 @@ watch(online, (nextOnline, previousOnline) => {
|
||||
void checkOtaUpdate();
|
||||
});
|
||||
|
||||
// 任一页面发起或恢复 OTA 后,关闭首页的版本发现弹窗,避免两层弹窗重叠。
|
||||
watch([otaUpdating, otaResultVisible], ([updating, resultVisible]) => {
|
||||
if (!updating && !resultVisible) return;
|
||||
otaVisible.value = false;
|
||||
isStartingOta.value = false;
|
||||
});
|
||||
|
||||
// 拼接 OTA WiFi 页参数,让未连 WiFi 的设备继续使用同一份版本信息。
|
||||
const getOtaWifiUrl = () => {
|
||||
const { versionNumber, resourceUrl } = otaInfo.value;
|
||||
@@ -177,89 +191,17 @@ const handleOtaDismiss = () => {
|
||||
otaVisible.value = false;
|
||||
};
|
||||
|
||||
// 将首页 OTA 直连更新流程标记为失败。
|
||||
const failHomeOtaUpdate = (runId) => {
|
||||
if (!isOtaUpdateRunActive(runId)) return;
|
||||
invalidateOtaUpdateRun();
|
||||
isStartingOta.value = false;
|
||||
otaState.value = "update_failure";
|
||||
otaVisible.value = true;
|
||||
};
|
||||
|
||||
// 将首页 OTA 直连更新流程标记为成功。
|
||||
const completeHomeOtaUpdate = (runId) => {
|
||||
if (!isOtaUpdateRunActive(runId)) return;
|
||||
invalidateOtaUpdateRun();
|
||||
isStartingOta.value = false;
|
||||
otaInfo.value = {...otaInfo.value, needUpdate: false};
|
||||
otaProgress.value = 100;
|
||||
otaPhase.value = "installing";
|
||||
otaState.value = "update_success";
|
||||
otaVisible.value = true;
|
||||
};
|
||||
|
||||
// 首页只处理当前设备、当前目标版本的 OTA WebSocket 消息。
|
||||
const handleHomeOtaSocketMessage = (message) => {
|
||||
const runId = otaUpdateRunId;
|
||||
if (!isOtaUpdateRunActive(runId) || Number(message?.code ?? 0) !== 0) return;
|
||||
if (![OTA_PROGRESS_EVENT, OTA_RESULT_EVENT].includes(message?.event)) return;
|
||||
|
||||
const messageDeviceId = String(message.data?.deviceId || "");
|
||||
const currentDeviceId = String(device.value?.deviceId || "");
|
||||
if (messageDeviceId && currentDeviceId && messageDeviceId !== currentDeviceId) return;
|
||||
|
||||
const messageVersion = String(message.data?.versionNumber || "");
|
||||
const currentVersion = String(otaInfo.value.versionNumber || "");
|
||||
if (messageVersion && currentVersion && messageVersion !== currentVersion) return;
|
||||
|
||||
if (message.event === OTA_PROGRESS_EVENT) {
|
||||
const nextProgress = Math.min(
|
||||
100,
|
||||
Math.max(0, Number(message.data?.progress) || 0)
|
||||
);
|
||||
otaProgress.value = Math.max(otaProgress.value, nextProgress);
|
||||
if (["started", "downloading", "installing"].includes(message.data?.phase)) {
|
||||
otaPhase.value = message.data.phase;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.data?.status === "success") {
|
||||
completeHomeOtaUpdate(runId);
|
||||
} else if (message.data?.status === "failed") {
|
||||
failHomeOtaUpdate(runId);
|
||||
}
|
||||
};
|
||||
|
||||
// 设备盒子已连 WiFi 时,从首页直接传空 WiFi 信息发起 OTA 更新。
|
||||
// 设备盒子已连 WiFi 时,通过共享 OTA 状态发起更新。
|
||||
const startHomeOtaUpdate = async () => {
|
||||
invalidateOtaUpdateRun();
|
||||
const runId = otaUpdateRunId;
|
||||
otaState.value = "update_progress";
|
||||
otaVisible.value = true;
|
||||
otaProgress.value = 0;
|
||||
otaPhase.value = "started";
|
||||
otaTimeoutTimer = setTimeout(() => {
|
||||
if (!isOtaUpdateRunActive(runId)) return;
|
||||
failHomeOtaUpdate(runId);
|
||||
}, OTA_UPDATE_TIMEOUT);
|
||||
|
||||
try {
|
||||
const updateResult = await sendHardwareBoxUpdateAPI({
|
||||
versionNumber: otaInfo.value.versionNumber,
|
||||
wifiSsid: "",
|
||||
wifiPassword: "",
|
||||
resourceUrl: otaInfo.value.resourceUrl,
|
||||
});
|
||||
if (!isOtaUpdateRunActive(runId)) return;
|
||||
if (!updateResult?.taskId) {
|
||||
failHomeOtaUpdate(runId);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
if (!isOtaUpdateRunActive(runId)) return;
|
||||
failHomeOtaUpdate(runId);
|
||||
}
|
||||
otaVisible.value = false;
|
||||
await startOtaUpdate({
|
||||
versionNumber: otaInfo.value.versionNumber,
|
||||
resourceUrl: otaInfo.value.resourceUrl,
|
||||
onSuccess: () => {
|
||||
otaInfo.value = {...otaInfo.value, needUpdate: false};
|
||||
},
|
||||
});
|
||||
isStartingOta.value = false;
|
||||
};
|
||||
|
||||
// 点击立即更新时先判断设备是否在线并已通过 WiFi 联网,未联网时先展示连接引导。
|
||||
@@ -305,15 +247,25 @@ const goWifiForOtaUpdate = () => {
|
||||
uni.navigateTo({ url: getOtaWifiUrl() });
|
||||
};
|
||||
|
||||
// 处理 OTA 更新成功后的完成按钮,关闭结果弹窗。
|
||||
// 处理 OTA 更新成功后的完成按钮,关闭共享结果弹窗。
|
||||
const handleOtaDone = () => {
|
||||
otaVisible.value = false;
|
||||
closeOtaResult();
|
||||
};
|
||||
|
||||
// 处理 OTA 更新失败后的重试按钮,重新走立即更新判断流程。
|
||||
const handleOtaRetry = () => {
|
||||
invalidateOtaUpdateRun();
|
||||
handleOtaUpdate();
|
||||
// 处理共享 OTA 更新失败后的重试,先刷新版本参数再重新判断网络状态。
|
||||
const handleOtaRetry = async () => {
|
||||
closeOtaResult();
|
||||
try {
|
||||
const versionInfo = await getHardwareBoxVersionAPI();
|
||||
applyOtaVersionInfo(versionInfo);
|
||||
if (!otaInfo.value.needUpdate) return;
|
||||
await handleOtaUpdate();
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: "获取更新版本失败,请重试",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 提取积分榜接口返回的榜单数组,兼容数组和对象两种返回格式。
|
||||
@@ -383,18 +335,11 @@ const syncHomeDevice = async () => {
|
||||
|
||||
};
|
||||
|
||||
onShow(async (options) => {
|
||||
onShow(async () => {
|
||||
const env = uni.getAccountInfoSync().miniProgram.envVersion;
|
||||
const token = uni.getStorageSync(`${env}_token`);
|
||||
|
||||
// 检查是否从 OTA 更新页面返回
|
||||
if (options && options.updateResult) {
|
||||
otaState.value = options.updateResult;
|
||||
if (options.updateResult === "update_success") {
|
||||
otaInfo.value = {...otaInfo.value, needUpdate: false};
|
||||
}
|
||||
otaVisible.value = true;
|
||||
} else if (token || user.value.id) {
|
||||
if (token || user.value.id) {
|
||||
await checkOtaUpdate();
|
||||
}
|
||||
|
||||
@@ -455,17 +400,11 @@ onShow(async (options) => {
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
uni.$on("socket-inbox", handleHomeOtaSocketMessage);
|
||||
const config = await getAppConfig();
|
||||
updateConfig(config);
|
||||
console.log("全局配置:", config);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off("socket-inbox", handleHomeOtaSocketMessage);
|
||||
invalidateOtaUpdateRun();
|
||||
});
|
||||
|
||||
onShareAppMessage(() => {
|
||||
return {
|
||||
title: "智能真弓:实时捕捉+毫秒级同步,弓箭选手全球竞技!", // 分享卡片的标题
|
||||
@@ -486,21 +425,34 @@ onShareTimeline(() => {
|
||||
|
||||
<template>
|
||||
<Container :isHome="true" :showBackToGame="true">
|
||||
<!-- OTA 升级弹窗:使用 visible 控制显隐,description 为副标题,changelog 为详细说明 -->
|
||||
<!-- 首页版本发现弹窗仅负责触发更新,执行状态由三个页面共享。 -->
|
||||
<OtaModal
|
||||
:visible="otaVisible"
|
||||
:state="otaState"
|
||||
:visible="otaVisible && !otaUpdating && !otaResultVisible"
|
||||
state="new_version"
|
||||
:version="otaInfo.versionNumber"
|
||||
:progress="otaProgress"
|
||||
:phase="otaPhase"
|
||||
:description="''"
|
||||
:changelog="otaInfo.versionInfo"
|
||||
:forceUpdate="otaInfo.forceUpdate"
|
||||
@update="handleOtaUpdate"
|
||||
@skip="handleOtaDismiss"
|
||||
@close="handleOtaDismiss"
|
||||
/>
|
||||
<OtaModal
|
||||
:visible="otaUpdating"
|
||||
state="update_progress"
|
||||
:progress="otaProgress"
|
||||
:phase="otaPhase"
|
||||
/>
|
||||
<OtaModal
|
||||
:visible="otaResultVisible && otaResultStatus === 'success'"
|
||||
state="update_success"
|
||||
@done="handleOtaDone"
|
||||
/>
|
||||
<OtaModal
|
||||
:visible="otaResultVisible && otaResultStatus === 'failed'"
|
||||
state="update_failure"
|
||||
@retry="handleOtaRetry"
|
||||
@close="closeOtaResult"
|
||||
/>
|
||||
<ModalDialog
|
||||
:show="wifiRequiredVisible"
|
||||
|
||||
Reference in New Issue
Block a user