Merge branch 'test' into feat-prac
This commit is contained in:
+241
-13
@@ -1,19 +1,23 @@
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {onMounted, onUnmounted, ref} from "vue";
|
||||
import {onShareAppMessage, onShareTimeline, onShow} from "@dcloudio/uni-app";
|
||||
import Container from "@/components/Container.vue";
|
||||
import AppFooter from "@/components/AppFooter.vue";
|
||||
import UserHeader from "@/components/UserHeader.vue";
|
||||
import Signin from "@/components/Signin.vue";
|
||||
import BubbleTip from "@/components/BubbleTip.vue";
|
||||
import OtaModal from "@/components/OtaModal.vue";
|
||||
|
||||
import {
|
||||
checkUserBindAPI,
|
||||
getAppConfig,
|
||||
getDeviceBatteryAPI,
|
||||
getHardwareBoxTaskStatusAPI,
|
||||
getHardwareBoxVersionAPI,
|
||||
getHomeData,
|
||||
getMyDevicesAPI,
|
||||
getScoreRankList,
|
||||
sendHardwareBoxUpdateAPI,
|
||||
silentLoginAPI,
|
||||
} from "@/apis";
|
||||
import {topThreeColors} from "@/constants";
|
||||
@@ -37,6 +41,208 @@ const showModal = ref(false);
|
||||
const showGuide = ref(false);
|
||||
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) => {
|
||||
if (Array.isArray(result)) return result;
|
||||
@@ -50,11 +256,6 @@ const toPage = async (path) => {
|
||||
showModal.value = true;
|
||||
return;
|
||||
}
|
||||
// if (path === "/pages/first-try") {
|
||||
// if (canEenter(user.value, device.value, online.value, path)) {
|
||||
// await uni.$checkAudio();
|
||||
// }
|
||||
// }
|
||||
uni.navigateTo({url: path});
|
||||
};
|
||||
|
||||
@@ -64,10 +265,18 @@ const toRankListPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
onShow(async () => {
|
||||
onShow(async (options) => {
|
||||
const env = uni.getAccountInfoSync().miniProgram.envVersion;
|
||||
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) {
|
||||
// showModal.value = true;
|
||||
// try {
|
||||
@@ -141,6 +350,10 @@ onMounted(async () => {
|
||||
console.log("全局配置:", config);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearOtaUpdateTimers();
|
||||
});
|
||||
|
||||
onShareAppMessage(() => {
|
||||
return {
|
||||
title: "智能真弓:实时捕捉+毫秒级同步,弓箭选手全球竞技!", // 分享卡片的标题
|
||||
@@ -161,6 +374,21 @@ onShareTimeline(() => {
|
||||
|
||||
<template>
|
||||
<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="top-theme">
|
||||
<!-- <image
|
||||
@@ -190,7 +418,7 @@ onShareTimeline(() => {
|
||||
<text v-else-if="online">设备在线</text>
|
||||
</block>
|
||||
<image
|
||||
src="../static/first-try.png"
|
||||
src="https://static.shelingxingqiu.com/shootmini/static/first-try.png"
|
||||
mode="widthFix"
|
||||
@click="() => toPage('/pages/first-try')"
|
||||
/>
|
||||
@@ -201,11 +429,11 @@ onShareTimeline(() => {
|
||||
</view>
|
||||
<view class="play-card">
|
||||
<!-- toPage('/pages/practise') -->
|
||||
<view @click="() => toPage('/pages/training/index')">
|
||||
<image src="../static/my-practise.png" mode="widthFix"/>
|
||||
<view @click="$clickSound(() => toPage('/pages/training/index'))">
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/my-practise.png" mode="widthFix"/>
|
||||
</view>
|
||||
<view @click="$clickSound(() => toPage('/pages/friend-battle'))">
|
||||
<image src="../static/friend-battle.png" mode="widthFix"/>
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/friend-battle.png" mode="widthFix"/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -220,7 +448,7 @@ onShareTimeline(() => {
|
||||
hover-class="none"
|
||||
></button>
|
||||
<view class="ranking-players" @click="toRankListPage">
|
||||
<img src="../static/juezhanbang.png" mode="widthFix"/>
|
||||
<img src="https://static.shelingxingqiu.com/shootmini/static/juezhanbang.png" mode="widthFix"/>
|
||||
<view class="divide-line"></view>
|
||||
<view class="player-avatars">
|
||||
<view
|
||||
@@ -254,7 +482,7 @@ onShareTimeline(() => {
|
||||
</view>
|
||||
<view class="my-data">
|
||||
<view @click="() => toPage('/pages/my-growth')">
|
||||
<image src="../static/my-growth.png" mode="widthFix"/>
|
||||
<image src="https://static.shelingxingqiu.com/shootmini/static/my-growth.png" mode="widthFix"/>
|
||||
</view>
|
||||
<view @click="() => toPage('/pages/ranking')">
|
||||
<view>
|
||||
|
||||
Reference in New Issue
Block a user