7 Commits
4 changed files with 109 additions and 23 deletions
+25
View File
@@ -25,6 +25,7 @@ try {
const ADDONS_BASE_URL = BASE_URL.replace(/\/api\/shoot$/, "/api/shoot"); const ADDONS_BASE_URL = BASE_URL.replace(/\/api\/shoot$/, "/api/shoot");
// 统一处理业务接口请求,包含登录态、业务错误和 WiFi 连接空响应兼容。
function request(method, url, data = {}, baseUrl = BASE_URL) { function request(method, url, data = {}, baseUrl = BASE_URL) {
const token = uni.getStorageSync( const token = uni.getStorageSync(
`${uni.getAccountInfoSync().miniProgram.envVersion}_token` `${uni.getAccountInfoSync().miniProgram.envVersion}_token`
@@ -39,6 +40,10 @@ function request(method, url, data = {}, baseUrl = BASE_URL) {
data, data,
timeout: 10000, timeout: 10000,
success: (res) => { success: (res) => {
if (url === "/user/hardwareBox/connectWifi" && res.statusCode === 200 && res.data && Object.keys(res.data).length === 0) {
resolve({});
return;
}
if (res.data) { if (res.data) {
const {code, data, message} = res.data; const {code, data, message} = res.data;
if (code === 0) resolve(data); if (code === 0) resolve(data);
@@ -473,6 +478,26 @@ export const getDeviceBatteryAPI = async () => {
return request("GET", "/user/device/battery"); return request("GET", "/user/device/battery");
}; };
// 设备连接指定 WiFi,只下发 WiFi 凭证,不触发 OTA 升级。
export const connectDeviceWifiAPI = async (ssid, password) => {
return request("POST", "/user/hardwareBox/connectWifi", {ssid, password});
};
// 获取硬件盒子版本信息,用于判断当前设备是否需要 OTA 升级。
export const getHardwareBoxVersionAPI = async () => {
return request("GET", "/user/hardwareBox/version");
};
// 发送硬件盒子 OTA 更新指令,服务端会返回后续轮询使用的任务 ID。
export const sendHardwareBoxUpdateAPI = async (data) => {
return request("POST", "/user/hardwareBox/sendUpdate", data);
};
// 根据任务 ID 获取硬件盒子 OTA 更新状态。
export const getHardwareBoxTaskStatusAPI = async (taskId) => {
return request("GET", `/user/hardwareBox/taskStatus?taskId=${taskId}`);
};
export const addNoteAPI = async (id, remark) => { export const addNoteAPI = async (id, remark) => {
return request("POST", "/user/score/sheet/remark", {id, remark}); return request("POST", "/user/score/sheet/remark", {id, remark});
}; };
+36 -3
View File
@@ -1,5 +1,10 @@
<script setup> <script setup>
import { computed } from "vue"; import { computed } from "vue";
import { getDeviceBatteryAPI } from "@/apis";
const OTA_MIN_BATTERY = 50;
const OTA_LOW_BATTERY_TEXT = "电量不足 50%,暂不支持 OTA 升级";
const OTA_OFFLINE_TEXT = "请先开启智能弓";
const props = defineProps({ const props = defineProps({
visible: { visible: {
@@ -42,6 +47,32 @@ const isSuccess = computed(() => props.state === "update_success");
const isFailure = computed(() => props.state === "update_failure"); const isFailure = computed(() => props.state === "update_failure");
// Clamp progress to keep the progress bar width within its container. // Clamp progress to keep the progress bar width within its container.
const progressValue = computed(() => Math.min(100, Math.max(0, Number(props.progress) || 0))); const progressValue = computed(() => Math.min(100, Math.max(0, Number(props.progress) || 0)));
// 点击立即更新前先校验设备在线状态,再校验设备电量。
const handleUpdateClick = async () => {
try {
const deviceStatus = await getDeviceBatteryAPI();
if (deviceStatus?.online !== true) {
uni.showToast({
title: OTA_OFFLINE_TEXT,
icon: "none",
});
return;
}
if (Number(deviceStatus?.battery) <= OTA_MIN_BATTERY) {
uni.showToast({
title: OTA_LOW_BATTERY_TEXT,
icon: "none",
});
return;
}
} catch (err) {
emit("update");
return;
}
emit("update");
};
</script> </script>
<template> <template>
@@ -91,7 +122,7 @@ const progressValue = computed(() => Math.min(100, Math.max(0, Number(props.prog
<block v-if="isNewVersion"> <block v-if="isNewVersion">
<image src="../static/ota/new-ver.png" mode="aspectFit" class="new-ver-img" /> <image src="../static/ota/new-ver.png" mode="aspectFit" class="new-ver-img" />
<view v-if="version" class="version-tag-wrap"> <view v-if="version" class="version-tag-wrap">
<image src="../static/ota/ota-ver.png" mode="aspectFill" class="version-tag-bg-img" /> <image src="../static/ota/ota-ver.png" mode="aspectFit" class="version-tag-bg-img" />
<text class="version-tag">{{ version }}</text> <text class="version-tag">{{ version }}</text>
</view> </view>
<!-- 副标题新版本将优化智能弓体验离下方详情 12rpx --> <!-- 副标题新版本将优化智能弓体验离下方详情 12rpx -->
@@ -99,7 +130,7 @@ const progressValue = computed(() => Math.min(100, Math.max(0, Number(props.prog
<!-- 详细说明升级前请确保... --> <!-- 详细说明升级前请确保... -->
<text v-if="changelog" class="changelog-text">{{ changelog }}</text> <text v-if="changelog" class="changelog-text">{{ changelog }}</text>
<view class="btn-group"> <view class="btn-group">
<view class="primary-btn" @click="emit('update')"> <view class="primary-btn" @click="handleUpdateClick">
<text class="primary-btn-text">立即更新</text> <text class="primary-btn-text">立即更新</text>
</view> </view>
<text v-if="!forceUpdate" class="skip-text" @click="emit('skip')">暂不更新</text> <text v-if="!forceUpdate" class="skip-text" @click="emit('skip')">暂不更新</text>
@@ -146,7 +177,7 @@ const progressValue = computed(() => Math.min(100, Math.max(0, Number(props.prog
<!-- 关闭按钮仅新版本状态非强制更新时位于弹窗下方 --> <!-- 关闭按钮仅新版本状态非强制更新时位于弹窗下方 -->
<view <view
v-if="isNewVersion && !forceUpdate" v-if="(isNewVersion || isFailure) && !forceUpdate"
class="ota-close-below" class="ota-close-below"
@click="emit('close')" @click="emit('close')"
> >
@@ -270,6 +301,8 @@ const progressValue = computed(() => Math.min(100, Math.max(0, Number(props.prog
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 116rpx;
height: 44rpx;
/* 离标题图 -10rpx,左边距 50rpx,离下方副标题 22rpx */ /* 离标题图 -10rpx,左边距 50rpx,离下方副标题 22rpx */
margin-top: -10rpx; margin-top: -10rpx;
margin-left: 50rpx; margin-left: 50rpx;
+10 -1
View File
@@ -198,7 +198,7 @@ const startHomeOtaUpdate = async () => {
} }
}; };
// 点击立即更新时先判断设备是否已通过 WiFi 联网,已联网则首页直接更新,否则跳转 WiFi 页面。 // 点击立即更新时先判断设备是否在线并已通过 WiFi 联网,已联网则首页直接更新,否则跳转 WiFi 页面。
const handleOtaUpdate = async () => { const handleOtaUpdate = async () => {
if (isStartingOta.value) return; if (isStartingOta.value) return;
isStartingOta.value = true; isStartingOta.value = true;
@@ -214,6 +214,15 @@ const handleOtaUpdate = async () => {
return; return;
} }
if (deviceStatus?.online !== true) {
isStartingOta.value = false;
uni.showToast({
title: "请先开启智能弓",
icon: "none",
});
return;
}
if (String(deviceStatus?.netType || "").toLowerCase() === "wifi") { if (String(deviceStatus?.netType || "").toLowerCase() === "wifi") {
startHomeOtaUpdate(); startHomeOtaUpdate();
return; return;
+38 -19
View File
@@ -1,4 +1,4 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from "vue"; import { ref, computed, onMounted, onUnmounted } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app"; import { onLoad, onShow } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue"; import Container from "@/components/Container.vue";
@@ -52,8 +52,10 @@ let wifiConnectTimer = null;
let wifiConnectRequestId = 0; let wifiConnectRequestId = 0;
let wifiConnectPollCount = 0; let wifiConnectPollCount = 0;
const WIFI_CONNECT_POLL_INTERVAL = 2000; const WIFI_CONNECT_POLL_INTERVAL = 2000;
const WIFI_CONNECT_MAX_POLL_COUNT = 15; const WIFI_CONNECT_MAX_POLL_COUNT = 30;
const WIFI_CONNECT_FAILED_TEXT = "连接失败,请检查WiFi密码或WiFi状态"; const WIFI_CONNECT_FAILED_TEXT = "连接失败,请检查WiFi密码或WiFi状态";
const OTA_MIN_BATTERY = 50;
const OTA_LOW_BATTERY_TEXT = "电量不足 50%,暂不支持 OTA 升级";
// 控制授权拒绝弹窗显示/隐藏 // 控制授权拒绝弹窗显示/隐藏
const wifiAuthDeniedVisible = ref(false); const wifiAuthDeniedVisible = ref(false);
@@ -271,12 +273,20 @@ const cancelWifiConnectPolling = () => {
uni.hideLoading(); uni.hideLoading();
}; };
// 判断设备是否已经在线且连接到 WiFi。 // 判断设备电量接口返回的 online/netType 字段,确定设备是否已通过 WiFi 在线
// 返回值含义:true → WiFi 在线成功;"net_fail" → 设备走 4g 失败;false → 未就绪,需继续轮询。
const isDeviceConnectedByWifi = (deviceStatus) => { const isDeviceConnectedByWifi = (deviceStatus) => {
return deviceStatus?.online === true && String(deviceStatus?.netType || "").toLowerCase() === "wifi"; // online 不为 true → 设备不在线,需继续轮询
if (deviceStatus?.online !== true) return false;
const netType = String(deviceStatus?.netType || "").toLowerCase();
// online:true + netType:4g → 设备已切 4gWiFi 连接失败
if (netType === "4g") return "net_fail";
// online:true + netType:wifi → WiFi 连接成功
// online:true + netType:"" → 设备在线但 netType 暂未上报,继续轮询等待
return netType === "wifi";
}; };
// 轮询设备电量接口,确认设备已经切换到 WiFi 在线状态 // 轮询设备电量接口,等待设备切到 WiFi 在线netType:4g 快速失败,超时 30 次后放弃
const waitForDeviceWifiConnected = (requestId) => { const waitForDeviceWifiConnected = (requestId) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const poll = async () => { const poll = async () => {
@@ -292,10 +302,19 @@ const waitForDeviceWifiConnected = (requestId) => {
resolve(false); resolve(false);
return; return;
} }
if (isDeviceConnectedByWifi(deviceStatus)) { const connResult = isDeviceConnectedByWifi(deviceStatus);
// online:true + netType:wifi → 成功
if (connResult === true) {
resolve(true); resolve(true);
return; return;
} }
// online:true + netType:4g → 立即失败(设备已切 4g,WiFi 连不上)
if (connResult === "net_fail") {
resolve(false);
return;
}
// online:false + netType:"" → 继续轮询
// online:true + netType:"" → 忽略,继续轮询(netType 暂未上报)
} catch (err) { } catch (err) {
if (requestId !== wifiConnectRequestId) { if (requestId !== wifiConnectRequestId) {
resolve(false); resolve(false);
@@ -436,8 +455,8 @@ const pollUpdateTaskStatus = (taskId) => {
// 判断设备是否满足 OTA 更新条件,不满足时返回精确提示文案。 // 判断设备是否满足 OTA 更新条件,不满足时返回精确提示文案。
const getUpdateDisabledReason = (deviceStatus) => { const getUpdateDisabledReason = (deviceStatus) => {
if (deviceStatus?.online !== true) return "设备已离线,请先开启设备并保持在线"; if (deviceStatus?.online !== true) return "请先开启智能弓";
if (Number(deviceStatus?.battery) <= 20) return "设备电量不足,请充电至 20% 以上后再更新"; if (Number(deviceStatus?.battery) <= OTA_MIN_BATTERY) return OTA_LOW_BATTERY_TEXT;
if (String(deviceStatus?.netType || "").toLowerCase() !== "wifi") return "设备当前未连接 WiFi,请先连接 WiFi 后再更新"; if (String(deviceStatus?.netType || "").toLowerCase() !== "wifi") return "设备当前未连接 WiFi,请先连接 WiFi 后再更新";
return ""; return "";
}; };
@@ -529,18 +548,18 @@ const handleWsFail = () => {
failUpdate(); failUpdate();
}; };
// 处理更新完成返回,兼容首页 OTA 弹窗入口和设备页普通入口。
const handleDone = () => { const handleDone = () => {
uni.navigateBack({ const pages = getCurrentPages();
delta: 1, const prevPage = pages[pages.length - 2];
success() { const prevVm = prevPage?.$vm;
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2]; if (prevVm && "otaState" in prevVm && "otaVisible" in prevVm) {
if (prevPage) { prevVm.otaState = "update_success";
prevPage.$vm.otaState = "update_success"; prevVm.otaVisible = true;
prevPage.$vm.otaVisible = true; }
}
}, uni.navigateBack({ delta: 1 });
});
}; };
const handleRetry = () => { const handleRetry = () => {