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