430 lines
11 KiB
Vue
430 lines
11 KiB
Vue
<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×109px(375px基准)× 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>
|