Files
shoot-miniprograms/src/components/OtaModal.vue
T

430 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
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({
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="https://static.shelingxingqiu.com/shootmini/static/ota/ota-mascot.png"
mode="aspectFit"
class="float-icon float-mascot"
/>
<image
v-else-if="isSuccess"
src="https://static.shelingxingqiu.com/shootmini/static/ota/check-char.png"
mode="aspectFit"
class="float-icon float-check"
/>
<image
v-else-if="isFailure"
src="https://static.shelingxingqiu.com/shootmini/static/ota/close-char.png"
mode="aspectFit"
class="float-icon float-close"
/>
<image
v-else-if="isProgress"
src="https://static.shelingxingqiu.com/shootmini/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="https://static.shelingxingqiu.com/shootmini/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="https://static.shelingxingqiu.com/shootmini/static/ota/new-ver.png" mode="aspectFit" class="new-ver-img" />
<view v-if="version" class="version-tag-wrap">
<image src="https://static.shelingxingqiu.com/shootmini/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="https://static.shelingxingqiu.com/shootmini/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="https://static.shelingxingqiu.com/shootmini/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="https://static.shelingxingqiu.com/shootmini/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>