22 Commits
Author SHA1 Message Date
chenlimao ef13ff6a2b fix: 修复wifi页面连接超时问题 2026-07-01 15:56:21 +08:00
chenlimao 8b7a94b8c4 fix: 修复wifi页面连接超时问题 2026-07-01 15:54:58 +08:00
chenlimao 40b94dbad8 fix: OTAwifi页面跳转逻辑优化 2026-06-30 17:06:52 +08:00
chenlimao 422a93dbe2 fix: 首页ota更新失败弹窗增加关闭按钮 2026-06-29 15:44:52 +08:00
chenlimao 5c74edf001 fix: 修复版本号背景图展示不完全问题 2026-06-29 15:11:06 +08:00
chenlimao 937308e51d feat: OTA部分接口对接后端 2026-06-29 13:41:28 +08:00
chenlimao 165f1a40bd Merge branch 'test' into ota-mode
# Conflicts:
#	src/apis.js
2026-06-26 14:45:51 +08:00
chenlimao 35c49bbb10 feat: OTA部分对接代码提交 2026-06-26 14:43:44 +08:00
chenlimao 54e5427a77 fix:新增更新中图片&设备页面新增跳转wifi页面入口 2026-06-09 09:59:13 +08:00
chenlimao 9b47b313a7 fix:wifi页面底部弹窗错误文案样式修改 2026-06-03 16:07:47 +08:00
chenlimao 350c6373ab fix:修复wifi弹窗不同输入法导致的结构错层 2026-06-03 13:42:48 +08:00
chenlimao 048b0e9fa9 fix:OTA弹窗新增更新中状态弹窗 2026-06-03 10:09:27 +08:00
chenlimao 39d34d5bff fix:wifi页面各种更新状态UI样式优化 2026-06-02 17:55:18 +08:00
chenlimao 37f4f0c019 fix:wifi页面密码查看图标引入 2026-06-02 16:50:10 +08:00
chenlimao e9cd9c8c9f fix:wifi页面新增授权失败的逻辑 2026-06-02 15:52:31 +08:00
chenlimao 73a41ab897 fix:wifi页面wifi列表新增刷新功能 2026-06-02 15:30:31 +08:00
chenlimao 32c37e283f fix:wifi页面弹窗隐藏逻辑修改&密码输入框新增查看密码功能 2026-06-02 15:03:10 +08:00
chenlimao 209103742d fix:wifi页面解决输入框遮住问题 2026-06-02 14:48:24 +08:00
chenlimao 27d11a8129 fix:小程序获取wifi兼容iso设备 2026-06-01 18:10:17 +08:00
chenlimao c9ea368a19 fix:wifi页面列表样式优化 2026-06-01 17:46:18 +08:00
chenlimao b5e7e72104 fix:已连接网络模块结构样式完成 2026-06-01 17:09:14 +08:00
chenlimao 7a00c1bb1f fix:OTA弹窗+wifi页面结构样式修改 2026-06-01 16:44:08 +08:00
31 changed files with 1972 additions and 3 deletions
+1
View File
@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules node_modules
.history .history
.github .github
.claude
openspec openspec
CLAUDE.md CLAUDE.md
docs docs
+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});
}; };
+429
View File
@@ -0,0 +1,429 @@
<script setup>
import { computed } from "vue";
import { getDeviceBatteryAPI } from "@/apis";
const OTA_MIN_BATTERY = 20;
const OTA_LOW_BATTERY_TEXT = "电量不足 50%,暂不支持 OTA 升级";
const OTA_OFFLINE_TEXT = "请先开启智能弓";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
state: {
type: String,
default: "new_version", // new_version | update_progress | update_success | update_failure
},
version: {
type: String,
default: "",
},
progress: {
type: Number,
default: 40,
},
// 副标题:如“新版本将优化智能弓体验”
description: {
type: String,
default: "",
},
// 详细说明:如“升级前请确保:...”
changelog: {
type: String,
default: "",
},
forceUpdate: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update", "skip", "close", "done", "retry"]);
const isNewVersion = computed(() => props.state === "new_version");
const isProgress = computed(() => props.state === "update_progress");
const isSuccess = computed(() => props.state === "update_success");
const isFailure = computed(() => props.state === "update_failure");
// 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 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>
<template>
<view v-if="visible" class="ota-mask">
<!-- 图标 + 弹窗卡片 容器 -->
<view
class="ota-outer"
:class="isNewVersion ? 'outer-new' : 'outer-result'"
>
<!-- 悬浮图标溢出卡片顶部 -->
<image
v-if="isNewVersion"
src="../static/ota/ota-mascot.png"
mode="aspectFit"
class="float-icon float-mascot"
/>
<image
v-else-if="isSuccess"
src="../static/ota/check-char.png"
mode="aspectFit"
class="float-icon float-check"
/>
<image
v-else-if="isFailure"
src="../static/ota/close-char.png"
mode="aspectFit"
class="float-icon float-close"
/>
<image
v-else-if="isProgress"
src="../static/ota/target-char.png"
mode="aspectFit"
class="float-icon float-target"
/>
<!-- 弹窗卡片overflow:visible 允许按钮溢出底部背景图通过 ota-bg-clip 独立裁剪保持圆角 -->
<view class="ota-dialog">
<view class="ota-bg-clip">
<image src="../static/ota/ota-bg.png" mode="aspectFill" class="ota-bg" />
</view>
<view
class="ota-content"
:class="{ 'content-new': isNewVersion, 'content-result': isProgress || isSuccess || isFailure }"
>
<!-- 发现新版本new-ver.png 已包含标题图不再重复文字版本号使用 ota-ver.png 胶囊背景 -->
<block v-if="isNewVersion">
<image src="../static/ota/new-ver.png" mode="aspectFit" class="new-ver-img" />
<view v-if="version" class="version-tag-wrap">
<image src="../static/ota/ota-ver.png" mode="aspectFit" class="version-tag-bg-img" />
<text class="version-tag">{{ version }}</text>
</view>
<!-- 副标题新版本将优化智能弓体验离下方详情 12rpx -->
<text v-if="description" class="desc-text">{{ description }}</text>
<!-- 详细说明升级前请确保... -->
<text v-if="changelog" class="changelog-text">{{ changelog }}</text>
<view class="btn-group">
<view class="primary-btn" @click="handleUpdateClick">
<text class="primary-btn-text">立即更新</text>
</view>
<text v-if="!forceUpdate" class="skip-text" @click="emit('skip')">暂不更新</text>
</view>
</block>
<!-- 更新成功图片左边距 34rpx文案左边距 44rpx按钮浮动底部居中 -->
<block v-else-if="isSuccess">
<image src="../static/ota/update-ok.png" mode="aspectFit" class="result-title-img" style="width: 220rpx; height: 62rpx;" />
<text class="dialog-desc">请关机并重启智能弓</text>
<view class="btn-group-result">
<view class="primary-btn" @click="emit('done')">
<text class="primary-btn-text">完成</text>
</view>
</view>
</block>
<!-- 更新中复用成功标题图正文区域展示进度条无底部按钮 -->
<block v-else-if="isProgress">
<image src="../static/ota/update_progress.png" mode="aspectFit" class="result-title-img" style="width: 220rpx; height: 62rpx;" />
<view class="progress-wrap">
<view class="progress-track">
<view class="progress-fill" :style="{ width: `${progressValue}%` }"></view>
</view>
</view>
</block>
<!-- 更新失败图片左边距 34rpx文案左对齐 44rpx按钮浮动底部居中 -->
<block v-else-if="isFailure">
<image src="../static/ota/update-fail.png" mode="aspectFit" class="result-title-img" style="width: 222rpx; height: 62rpx;" />
<text class="dialog-desc">请确保</text>
<text class="dialog-desc">1智能弓已开启</text>
<text class="dialog-desc">2网路连接稳定</text>
<view class="btn-group-result">
<view class="primary-btn" @click="emit('retry')">
<text class="primary-btn-text">重试</text>
</view>
</view>
</block>
</view>
</view>
</view>
<!-- 关闭按钮仅新版本状态非强制更新时位于弹窗下方 -->
<view
v-if="(isNewVersion || isFailure) && !forceUpdate"
class="ota-close-below"
@click="emit('close')"
>
<image src="../static/sicon/close.png" mode="aspectFit" style="width: 56rpx; height: 56rpx;" />
</view>
</view>
</template>
<style scoped>
.ota-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 外层容器:相对定位,为浮动图标创造溢出空间 */
.ota-outer {
position: relative;
overflow: visible;
}
/* 设计图:吉祥物向上突出弹窗顶部 40px(375px基准)× 2 = 80rpx */
.outer-new {
padding-top: 80rpx;
}
.outer-result {
padding-top: 80rpx;
padding-bottom: 66rpx;
}
/* 浮动图标(绝对定位,位于卡片顶部上方) */
.float-icon {
position: absolute;
z-index: 2;
}
/* 吉祥物尺寸:设计图 149×109px375px基准)× 2 = 298×218rpx */
.float-mascot {
width: 298rpx;
height: 218rpx;
top: -5px;
right: -74rpx;
}
.float-check {
width: 194rpx;
height: 166rpx;
top: 20px;
right: 30rpx;
}
.float-close {
width: 194rpx;
height: 164rpx;
top: 20px;
right: 30rpx;
}
.float-target {
width: 194rpx;
height: 166rpx;
top: 20px;
right: 30rpx;
}
/* 弹窗卡片:overflow:visible 允许按钮溢出底部,背景通过 ota-bg-clip 独立裁剪 */
.ota-dialog {
position: relative;
width: 482rpx;
border-radius: 24rpx;
border: 2rpx solid #F9D5A1;
overflow: visible;
background-color: #392F1D;
}
/* 背景图裁剪层:独立 overflow:hidden + border-radius 保持圆角效果 */
.ota-bg-clip {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 24rpx;
overflow: hidden;
z-index: 0;
}
.ota-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ota-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
/* 按钮以外内容均左对齐 */
align-items: flex-start;
}
.content-new {
padding: 30rpx 0 40rpx 0;
}
.content-result {
padding: 30rpx 0 66rpx 0;
}
/* 发现新版本内容 */
.new-ver-img {
width: 274rpx;
height: 62rpx;
/* 左边距 34rpx,去掉 margin-bottom */
margin-left: 34rpx;
}
/* 版本号胶囊容器:相对定位,使 ota-ver.png 作为背景衬底 */
.version-tag-wrap {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 116rpx;
height: 44rpx;
/* 离标题图 -10rpx,左边距 50rpx,离下方副标题 22rpx */
margin-top: -10rpx;
margin-left: 50rpx;
margin-bottom: 22rpx;
}
/* ota-ver.png 胶囊背景图 */
.version-tag-bg-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* 版本号文字:浮于背景图之上 */
.version-tag {
position: relative;
z-index: 1;
color: rgba(254, 222, 100, 1);
font-size: 24rpx;
padding: 8rpx 22rpx 4rpx 24rpx;
}
/* 副标题(如"新版本将优化智能弓体验"):左边距 44rpx,离下方文案 12rpx */
.desc-text {
font-weight: 500;
font-size: 26rpx;
color: #FFFFFF;
line-height: 36rpx;
text-align: left;
margin-left: 44rpx;
margin-bottom: 12rpx;
}
/* 详细说明文案(如“升级前请确保:...”):左边距 44rpx */
.changelog-text {
font-weight: 400;
font-size: 26rpx;
color: #FFFFFF;
line-height: 40rpx;
text-align: left;
margin-left: 44rpx;
margin-bottom: 0;
}
/* 按钮组(新版本状态):离上方文案 30rpx,内部按钮间距 24rpx */
.btn-group {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 30rpx;
gap: 24rpx;
}
/* 按钮组(结果状态):绝对定位,溢出卡片底边 -35rpx 悬浮在底边中间 */
.btn-group-result {
position: absolute;
bottom: -35rpx;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
}
/* 主按钮:按照设计规范 width: 232rpx, height: 70rpx */
.primary-btn {
width: 232rpx;
height: 70rpx;
background-color: #FED847;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
}
.primary-btn-text {
font-weight: 500;
font-size: 26rpx;
color: #000000;
line-height: 36rpx;
}
/* 暂不更新:设计规范颜色 #5FADFF 蓝色 */
.skip-text {
font-weight: 400;
font-size: 26rpx;
color: #5FADFF;
line-height: 36rpx;
}
/* 更新结果内容:图片左边距 34rpx,下边距 16rpx */
.result-title-img {
margin-left: 34rpx;
margin-bottom: 16rpx;
}
/* 结果页文案:左对齐,左边距 44rpx,与 new_version 保持一致 */
.dialog-desc {
font-weight: 400;
font-size: 26rpx;
color: #FFFFFF;
line-height: 40rpx;
text-align: left;
margin-left: 44rpx;
}
.progress-wrap {
width: 394rpx;
margin-top: 40rpx;
margin-left: 44rpx;
}
.progress-track {
width: 100%;
height: 18rpx;
background-color: rgba(255, 255, 255, 0.28);
border-radius: 999rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: #FED847;
border-radius: 999rpx;
}
/* 关闭按钮(位于弹窗下方) */
.ota-close-below {
margin-top: 40rpx;
display: flex;
justify-content: center;
}
</style>
+5 -1
View File
@@ -64,7 +64,11 @@
"usingComponents" : true, "usingComponents" : true,
"darkmode" : true, "darkmode" : true,
"themeLocation" : "theme.json", "themeLocation" : "theme.json",
"permission" : {}, "permission" : {
"scope.userLocation": {
"desc": "用于扫描附近 WiFi,完成设备 OTA 升级网络连接"
}
},
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ] "requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
} }
} }
+6
View File
@@ -125,6 +125,12 @@
}, },
{ {
"path": "pages/mine-bow-data" "path": "pages/mine-bow-data"
},
{
"path": "pages/ota-wifi",
"style": {
"navigationStyle": "custom"
}
} }
], ],
"globalStyle": { "globalStyle": {
+235 -2
View File
@@ -1,19 +1,23 @@
<script setup> <script setup>
import {onMounted, ref} from "vue"; import {onMounted, onUnmounted, ref} 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";
import UserHeader from "@/components/UserHeader.vue"; import UserHeader from "@/components/UserHeader.vue";
import Signin from "@/components/Signin.vue"; import Signin from "@/components/Signin.vue";
import BubbleTip from "@/components/BubbleTip.vue"; import BubbleTip from "@/components/BubbleTip.vue";
import OtaModal from "@/components/OtaModal.vue";
import { import {
checkUserBindAPI, checkUserBindAPI,
getAppConfig, getAppConfig,
getDeviceBatteryAPI, getDeviceBatteryAPI,
getHardwareBoxTaskStatusAPI,
getHardwareBoxVersionAPI,
getHomeData, getHomeData,
getMyDevicesAPI, getMyDevicesAPI,
getScoreRankList, getScoreRankList,
sendHardwareBoxUpdateAPI,
silentLoginAPI, silentLoginAPI,
} from "@/apis"; } from "@/apis";
import {topThreeColors} from "@/constants"; import {topThreeColors} from "@/constants";
@@ -37,6 +41,208 @@ const showModal = ref(false);
const showGuide = ref(false); const showGuide = ref(false);
const scoreRankList = ref([]); const scoreRankList = ref([]);
// OTA 相关
const otaVisible = ref(false);
const otaState = ref("new_version");
const otaProgress = ref(0);
const otaInfo = ref({
versionNumber: "",
versionInfo: "",
resourceUrl: "",
forceUpdate: false,
});
const isStartingOta = ref(false);
let otaProgressTimer = null;
let otaStatusTimer = null;
let otaTimeoutTimer = null;
// 清理首页 OTA 更新定时器,避免弹窗关闭或页面卸载后继续轮询。
const clearOtaUpdateTimers = () => {
clearInterval(otaProgressTimer);
clearTimeout(otaStatusTimer);
clearTimeout(otaTimeoutTimer);
otaProgressTimer = null;
otaStatusTimer = null;
otaTimeoutTimer = null;
};
// 启动首页 OTA 本地进度动画,最终成功失败以后端任务状态为准。
const startOtaProgressAnimation = () => {
clearInterval(otaProgressTimer);
otaProgressTimer = setInterval(() => {
if (otaProgress.value >= 90) {
clearInterval(otaProgressTimer);
return;
}
const increment = Math.max(0.5, 2 - otaProgress.value / 60);
otaProgress.value = Math.min(90, otaProgress.value + increment);
}, 500);
};
// 获取并保存后端返回的 OTA 版本信息,供弹窗展示和更新接口使用。
const applyOtaVersionInfo = (versionInfo) => {
otaInfo.value = {
versionNumber: versionInfo?.versionNumber || "",
versionInfo: versionInfo?.versionInfo || "",
resourceUrl: versionInfo?.resourceUrl || "",
forceUpdate: Number(versionInfo?.forceUpdate) === 1,
};
};
// 检查当前设备盒子是否存在可升级版本。
const checkOtaUpdate = async () => {
let versionInfo;
try {
versionInfo = await getHardwareBoxVersionAPI();
} catch (err) {
return;
}
if (!versionInfo?.needUpdate) return;
applyOtaVersionInfo(versionInfo);
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;
};
// 拼接 OTA WiFi 页参数,让未连 WiFi 的设备继续使用同一份版本信息。
const getOtaWifiUrl = () => {
const { versionNumber, resourceUrl } = otaInfo.value;
const query = [
`versionNumber=${encodeURIComponent(versionNumber)}`,
`resourceUrl=${encodeURIComponent(resourceUrl)}`,
].join("&");
return `/pages/ota-wifi?${query}`;
};
// 处理 OTA 弹窗暂不更新,强制更新时不允许关闭。
const handleOtaDismiss = () => {
if (otaInfo.value.forceUpdate) return;
uni.setStorageSync("ota_dismissed_at", Date.now());
otaVisible.value = false;
};
// 将首页 OTA 直连更新流程标记为失败。
const failHomeOtaUpdate = () => {
clearOtaUpdateTimers();
isStartingOta.value = false;
otaState.value = "update_failure";
otaVisible.value = true;
};
// 将首页 OTA 直连更新流程标记为成功。
const completeHomeOtaUpdate = () => {
clearOtaUpdateTimers();
isStartingOta.value = false;
otaProgress.value = 100;
setTimeout(() => {
otaState.value = "update_success";
otaVisible.value = true;
}, 300);
};
// 轮询首页直接发起的 OTA 更新任务状态。
const pollHomeOtaTaskStatus = (taskId) => {
clearTimeout(otaStatusTimer);
otaStatusTimer = setTimeout(async () => {
try {
const taskStatus = await getHardwareBoxTaskStatusAPI(taskId);
const status = Number(taskStatus?.status);
if (status === 2) {
completeHomeOtaUpdate();
return;
}
if (status === 3) {
failHomeOtaUpdate();
return;
}
if (status === 0 || status === 1) {
pollHomeOtaTaskStatus(taskId);
return;
}
failHomeOtaUpdate();
} catch (err) {
failHomeOtaUpdate();
}
}, 3000);
};
// 设备盒子已连 WiFi 时,从首页直接传空 WiFi 信息发起 OTA 更新。
const startHomeOtaUpdate = async () => {
otaState.value = "update_progress";
otaVisible.value = true;
otaProgress.value = 0;
startOtaProgressAnimation();
otaTimeoutTimer = setTimeout(() => {
if (otaState.value === "update_progress") {
failHomeOtaUpdate();
}
}, 5 * 60 * 1000);
try {
const updateResult = await sendHardwareBoxUpdateAPI({
versionNumber: otaInfo.value.versionNumber,
wifiSsid: "",
wifiPassword: "",
resourceUrl: otaInfo.value.resourceUrl,
});
if (!updateResult?.taskId) {
failHomeOtaUpdate();
return;
}
pollHomeOtaTaskStatus(updateResult.taskId);
} catch (err) {
failHomeOtaUpdate();
}
};
// 点击立即更新时先判断设备是否在线并已通过 WiFi 联网,已联网则首页直接更新,否则跳转 WiFi 页面。
const handleOtaUpdate = async () => {
if (isStartingOta.value) return;
isStartingOta.value = true;
let deviceStatus;
try {
deviceStatus = await getDeviceBatteryAPI();
} catch (err) {
isStartingOta.value = false;
uni.showToast({
title: "获取设备状态失败,请重试",
icon: "none",
});
return;
}
if (deviceStatus?.online !== true) {
isStartingOta.value = false;
uni.showToast({
title: "请先开启智能弓",
icon: "none",
});
return;
}
if (String(deviceStatus?.netType || "").toLowerCase() === "wifi") {
startHomeOtaUpdate();
return;
}
isStartingOta.value = false;
otaVisible.value = false;
uni.navigateTo({ url: getOtaWifiUrl() });
};
// 处理 OTA 更新成功后的完成按钮,关闭结果弹窗。
const handleOtaDone = () => {
otaVisible.value = false;
};
// 处理 OTA 更新失败后的重试按钮,重新走立即更新判断流程。
const handleOtaRetry = () => {
handleOtaUpdate();
};
// 提取积分榜接口返回的榜单数组,兼容数组和对象两种返回格式。 // 提取积分榜接口返回的榜单数组,兼容数组和对象两种返回格式。
const getScoreRankData = (result) => { const getScoreRankData = (result) => {
if (Array.isArray(result)) return result; if (Array.isArray(result)) return result;
@@ -64,10 +270,18 @@ const toRankListPage = () => {
}); });
}; };
onShow(async () => { onShow(async (options) => {
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 (options && options.updateResult) {
otaState.value = options.updateResult;
otaVisible.value = true;
} else if (token || user.value.id) {
await checkOtaUpdate();
}
if (!user.value.id && !token) { if (!user.value.id && !token) {
// showModal.value = true; // showModal.value = true;
// try { // try {
@@ -141,6 +355,10 @@ onMounted(async () => {
console.log("全局配置:", config); console.log("全局配置:", config);
}); });
onUnmounted(() => {
clearOtaUpdateTimers();
});
onShareAppMessage(() => { onShareAppMessage(() => {
return { return {
title: "智能真弓:实时捕捉+毫秒级同步,弓箭选手全球竞技!", // 分享卡片的标题 title: "智能真弓:实时捕捉+毫秒级同步,弓箭选手全球竞技!", // 分享卡片的标题
@@ -161,6 +379,21 @@ onShareTimeline(() => {
<template> <template>
<Container :isHome="true" :showBackToGame="true"> <Container :isHome="true" :showBackToGame="true">
<!-- OTA 升级弹窗使用 visible 控制显隐description 为副标题changelog 为详细说明 -->
<OtaModal
:visible="otaVisible"
:state="otaState"
:version="otaInfo.versionNumber"
:progress="otaProgress"
:description="''"
:changelog="otaInfo.versionInfo"
:forceUpdate="otaInfo.forceUpdate"
@update="handleOtaUpdate"
@skip="handleOtaDismiss"
@close="handleOtaDismiss"
@done="handleOtaDone"
@retry="handleOtaRetry"
/>
<view class="container"> <view class="container">
<view class="top-theme"> <view class="top-theme">
<!-- <image <!-- <image
+10
View File
@@ -103,6 +103,11 @@ const unbindDevice = async () => {
clearDevice(); clearDevice();
}; };
/** 连接wifi跳转到wifi列表页面 */
const joinWifi = () => {
uni.navigateTo({ url: "/pages/ota-wifi" });
};
const toDeviceIntroPage = () => { const toDeviceIntroPage = () => {
uni.navigateTo({ uni.navigateTo({
url: "/pages/device-intro", url: "/pages/device-intro",
@@ -322,6 +327,11 @@ onShow(async () => {
>解绑</SButton >解绑</SButton
> >
</view> </view>
<view :style="{ marginTop: '20rpx' }">
<SButton :onClick="() => $clickSound(joinWifi)" width="80vw" :rounded="40"
>设备连接WIFI</SButton
>
</view>
</view> </view>
</Container> </Container>
</template> </template>
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B