fix:OTA弹窗+wifi页面结构样式修改

This commit is contained in:
2026-06-01 16:44:08 +08:00
parent 8ef64f8f42
commit 7a00c1bb1f
25 changed files with 1139 additions and 1 deletions

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
.history
.github
.claude
openspec
CLAUDE.md
docs

350
src/components/OtaModal.vue Normal file
View File

@@ -0,0 +1,350 @@
<script setup>
import { computed } from "vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
state: {
type: String,
default: "new_version", // new_version | update_success | update_failure
},
version: {
type: String,
default: "",
},
// 副标题:如“新版本将优化智能弓体验”
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 isSuccess = computed(() => props.state === "update_success");
const isFailure = computed(() => props.state === "update_failure");
</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"
/>
<!-- 弹窗卡片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': 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="aspectFill" 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="emit('update')">
<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>
<!-- 更新失败图片左边距 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 && !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;
}
/* 设计图:吉祥物向上突出弹窗顶部 40px375px基准× 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;
}
/* 弹窗卡片overflow:visible 允许按钮溢出底部,背景通过 ota-bg-clip 独立裁剪 */
.ota-dialog {
position: relative;
width: 482rpx;
border-radius: 24rpx;
border: 2px 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;
/* 离标题图 -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;
}
/* 关闭按钮(位于弹窗下方) */
.ota-close-below {
margin-top: 40rpx;
display: flex;
justify-content: center;
}
</style>

View File

@@ -119,6 +119,12 @@
},
{
"path": "pages/mine-bow-data"
},
{
"path": "pages/ota-wifi",
"style": {
"navigationStyle": "custom"
}
}
],
"globalStyle": {

View File

@@ -6,6 +6,7 @@ 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,
@@ -36,6 +37,44 @@ const showModal = ref(false);
const showGuide = ref(false);
const scoreRankList = ref([]);
// OTA 相关
const otaVisible = ref(false);
const otaState = ref("new_version");
const OTA_MOCK = {
hasUpdate: true,
version: "V8.7.0",
description: "新版本将优化智能弓体验",
details: "升级前请确保:\n1、智能弓已开启且电量充足\n2、所处稳定的 Wi-Fi 环境中。",
forceUpdate: false,
};
const checkOtaUpdate = () => {
if (!OTA_MOCK.hasUpdate) return;
const dismissedAt = uni.getStorageSync("ota_dismissed_at");
const now = Date.now();
if (dismissedAt && now - dismissedAt < 24 * 60 * 60 * 1000) return;
otaState.value = "new_version";
otaVisible.value = true;
};
const handleOtaDismiss = () => {
uni.setStorageSync("ota_dismissed_at", Date.now());
otaVisible.value = false;
};
const handleOtaUpdate = () => {
otaVisible.value = false;
uni.navigateTo({ url: "/pages/ota-wifi" });
};
const handleOtaDone = () => {
otaVisible.value = false;
};
const handleOtaRetry = () => {
otaVisible.value = false;
};
// 提取积分榜接口返回的榜单数组,兼容数组和对象两种返回格式。
const getScoreRankData = (result) => {
if (Array.isArray(result)) return result;
@@ -63,7 +102,15 @@ const toRankListPage = () => {
});
};
onShow(async () => {
onShow(async (options) => {
// 检查是否从 OTA 更新页面返回
if (options && options.updateResult) {
otaState.value = options.updateResult;
otaVisible.value = true;
} else {
checkOtaUpdate();
}
const env = uni.getAccountInfoSync().miniProgram.envVersion;
const token = uni.getStorageSync(`${env}_token`);
@@ -158,6 +205,20 @@ onShareTimeline(() => {
<template>
<Container :isHome="true" :showBackToGame="true">
<!-- OTA 升级弹窗使用 visible 控制显隐description 为副标题changelog 为详细说明 -->
<OtaModal
:visible="otaVisible"
:state="otaState"
:version="OTA_MOCK.version"
:description="OTA_MOCK.description"
:changelog="OTA_MOCK.details"
:forceUpdate="OTA_MOCK.forceUpdate"
@update="handleOtaUpdate"
@skip="handleOtaDismiss"
@close="handleOtaDismiss"
@done="handleOtaDone"
@retry="handleOtaRetry"
/>
<view class="container">
<view class="top-theme">
<!-- <image

720
src/pages/ota-wifi.vue Normal file
View File

@@ -0,0 +1,720 @@
<script setup>
import { ref, computed, onMounted, onUnmounted } from "vue";
import Container from "@/components/Container.vue";
const STATES = {
SCANNING: "SCANNING",
LIST: "LIST",
CONNECTING: "CONNECTING",
CONNECTED: "CONNECTED",
UPDATING: "UPDATING",
DONE: "DONE",
FAILED: "FAILED",
};
const isIOS = uni.getDeviceInfo().osName === "ios";
const currentState = ref(STATES.SCANNING);
const connectedWifi = ref(null);
const wifiList = ref([]);
const showWifiBanner = ref(false);
const connectingWifi = ref(null);
const connectInput = ref({ ssid: "", password: "" });
const connectMode = ref("secure"); // secure | open | manual
const connectError = ref("");
const progress = ref(0);
let progressTimer = null;
let timeoutTimer = null;
const MOCK_WIFI_LIST = [
{ SSID: "shelingxingqiu", secure: true, signalStrength: -50 },
{ SSID: "tplink3435_02", secure: true, signalStrength: -65 },
{ SSID: "tplink2025_007", secure: true, signalStrength: -70 },
];
const startScanning = () => {
currentState.value = STATES.SCANNING;
wifiList.value = [];
showWifiBanner.value = false;
connectError.value = "";
wx.startWifi({
success() {
wx.getWifiList({
fail(err) {
if (err.errCode === 12005) showWifiBanner.value = true;
},
});
},
fail(err) {
if (err.errCode === 12005) showWifiBanner.value = true;
},
});
wx.onGetWifiList((res) => {
wifiList.value = res.wifiList || [];
currentState.value = STATES.LIST;
});
setTimeout(() => {
if (currentState.value === STATES.SCANNING) {
wifiList.value = MOCK_WIFI_LIST;
currentState.value = STATES.LIST;
}
}, 2000);
};
const selectWifi = (wifi) => {
connectingWifi.value = wifi;
connectInput.value = { ssid: wifi.SSID, password: "" };
connectMode.value = wifi.secure ? "secure" : "open";
connectError.value = "";
currentState.value = STATES.CONNECTING;
};
const selectOther = () => {
connectingWifi.value = null;
connectInput.value = { ssid: "", password: "" };
connectMode.value = "manual";
connectError.value = "";
currentState.value = STATES.CONNECTING;
};
const closeConnectSheet = () => {
connectError.value = "";
currentState.value = connectedWifi.value ? STATES.CONNECTED : STATES.LIST;
};
const hasChinese = (str) => /[\u4e00-\u9fa5]/.test(str);
const ssidWarning = computed(() => {
if (connectMode.value === "manual" && hasChinese(connectInput.value.ssid)) {
return "网络名称仅支持英文字符及数字,请连接英文名网络或把网络改为英文";
}
return "";
});
const joinDisabled = computed(() => {
if (connectMode.value === "secure") return !connectInput.value.password;
if (connectMode.value === "manual") return !connectInput.value.ssid;
return false;
});
const joinNetwork = () => {
if (joinDisabled.value) return;
connectError.value = "";
const ssid = connectInput.value.ssid;
const password = connectInput.value.password;
wx.connectWifi({
SSID: ssid,
password,
success() {
connectedWifi.value = { SSID: ssid, secure: !!password };
currentState.value = STATES.CONNECTED;
},
fail() {
connectError.value = `无法查找到网络"${ssid}",请检查网络名称与设置!`;
},
});
setTimeout(() => {
if (currentState.value === STATES.CONNECTING && !connectError.value) {
connectedWifi.value = { SSID: ssid, secure: !!password };
currentState.value = STATES.CONNECTED;
}
}, 1000);
};
const startUpdate = () => {
currentState.value = STATES.UPDATING;
progress.value = 0;
clearInterval(progressTimer);
progressTimer = setInterval(() => {
if (progress.value >= 90) {
clearInterval(progressTimer);
return;
}
const increment = Math.max(0.5, 2 - progress.value / 60);
progress.value = Math.min(90, progress.value + increment);
}, 500);
clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(() => {
if (currentState.value === STATES.UPDATING) {
clearInterval(progressTimer);
currentState.value = STATES.FAILED;
}
}, 5 * 60 * 1000);
};
const handleWsDone = () => {
clearInterval(progressTimer);
clearTimeout(timeoutTimer);
progress.value = 100;
setTimeout(() => {
currentState.value = STATES.DONE;
}, 300);
};
const handleWsFail = () => {
clearInterval(progressTimer);
clearTimeout(timeoutTimer);
currentState.value = STATES.FAILED;
};
const handleDone = () => {
uni.navigateBack({
delta: 1,
success() {
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
if (prevPage) {
prevPage.$vm.otaState = "update_success";
prevPage.$vm.otaVisible = true;
}
},
});
};
const handleRetry = () => {
if (connectedWifi.value) {
currentState.value = STATES.CONNECTED;
} else {
startScanning();
}
};
onMounted(() => {
startScanning();
});
onUnmounted(() => {
clearInterval(progressTimer);
clearTimeout(timeoutTimer);
wx.offGetWifiList && wx.offGetWifiList();
});
</script>
<template>
<Container title="连接无线网络">
<!-- WiFi 不可用 Banner -->
<view v-if="showWifiBanner" class="wifi-banner">
<text class="wifi-banner-text">请先开启 WiFi 后刷新重试</text>
</view>
<!-- SCANNING / LIST / CONNECTED -->
<view
v-if="currentState === 'SCANNING' || currentState === 'LIST' || currentState === 'CONNECTED'"
class="wifi-page"
>
<!-- Hero: wifi1.png 单图已内含吉祥物 -->
<view class="hero-area">
<image src="../static/ota/wifi1.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
</view>
<text class="page-title">{{ currentState === 'CONNECTED' ? '连接成功' : '连接无线网络' }}</text>
<text v-if="currentState !== 'CONNECTED'" class="page-subtitle">网络名称仅支持英文字符及数字</text>
<view v-if="isIOS && currentState === 'SCANNING'" class="ios-guide">
<text class="ios-guide-text">请在设备 WiFi 设置中完成扫描后返回</text>
</view>
<!-- 已连接网络卡片 -->
<view v-if="currentState === 'CONNECTED'" class="section-label-row">
<text class="section-label">连接网络</text>
</view>
<view v-if="currentState === 'CONNECTED'" class="wifi-list-card connected-wifi-card">
<view class="wifi-item connected-wifi-item">
<image class="check-icon" src="../static/sicon/check.png" mode="aspectFit" />
<text class="wifi-ssid">{{ connectedWifi?.SSID }}</text>
<view class="wifi-icons">
<image
v-if="connectedWifi?.secure"
class="security-icon"
src="../static/sicon/pwd.png"
mode="aspectFit"
/>
<image class="signal-icon" src="../static/sicon/wifi.png" mode="aspectFit" />
</view>
</view>
</view>
<!-- 网络列表标题 -->
<view class="section-label-row">
<text class="section-label">网络</text>
<image
v-if="currentState === 'SCANNING'"
src="../static/sicon/refresh.png"
mode="aspectFit"
style="width: 34rpx; height: 34rpx; margin-left: 8rpx; opacity: 0.7;"
/>
</view>
<!-- 网络列表卡片 -->
<view class="wifi-list-card">
<block v-if="currentState === 'SCANNING'">
<view class="wifi-item" @click="selectOther">
<text class="wifi-ssid other-text">其他...</text>
</view>
</block>
<block v-else>
<view
v-for="(wifi, idx) in wifiList"
:key="wifi.SSID"
class="wifi-item"
@click="selectWifi(wifi)"
>
<text class="wifi-ssid">{{ wifi.SSID }}</text>
<view class="wifi-icons">
<image
v-if="wifi.secure"
class="security-icon"
src="../static/sicon/pwd.png"
mode="aspectFit"
/>
<image class="signal-icon" src="../static/sicon/wifi.png" mode="aspectFit" />
</view>
</view>
<view class="wifi-item" @click="selectOther">
<text class="wifi-ssid other-text">其他...</text>
</view>
</block>
</view>
<!-- CONNECTED开始更新按钮 -->
<view v-if="currentState === 'CONNECTED'" class="bottom-btn-area">
<view class="primary-btn" @click="startUpdate">
<text class="primary-btn-text">开始更新</text>
</view>
</view>
</view>
<!-- UPDATING -->
<view v-else-if="currentState === 'UPDATING'" class="center-page">
<image src="../static/ota/target-char.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
<text class="page-title" style="margin-top: 24rpx;">更新中,请稍等片刻...</text>
<view class="progress-wrap">
<view class="progress-track">
<view class="progress-fill" :style="{ width: progress + '%' }"></view>
</view>
<text class="progress-pct">{{ Math.floor(progress) }}%</text>
</view>
</view>
<!-- DONE -->
<view v-else-if="currentState === 'DONE'" class="center-page">
<image src="../static/ota/check-char.png" mode="aspectFit" style="width: 194rpx; height: 166rpx;" />
<text class="page-title" style="margin-top: 24rpx;">更新完成</text>
<text class="page-desc-white">请关机并重启智能弓</text>
<view class="done-info-box"></view>
<view class="primary-btn done-btn" @click="handleDone">
<text class="primary-btn-text">完成</text>
</view>
</view>
<!-- FAILED -->
<view v-else-if="currentState === 'FAILED'" class="center-page">
<image src="../static/ota/close-char.png" mode="aspectFit" style="width: 194rpx; height: 164rpx;" />
<text class="page-title fail-title" style="margin-top: 24rpx;">更新失败</text>
<text class="page-desc-white">请确保</text>
<text class="page-desc-white">1智能弓已开启</text>
<text class="page-desc-white">2网路连接稳定</text>
<view class="primary-btn done-btn" style="margin-top: 40rpx;" @click="handleRetry">
<text class="primary-btn-text">重试</text>
</view>
</view>
<!-- CONNECTING 底部弹窗 -->
<view v-if="currentState === 'CONNECTING'" class="sheet-mask" @click="closeConnectSheet">
<view class="sheet" @click.stop="">
<image src="../static/ota/ota-bg.png" mode="aspectFill" class="sheet-bg" />
<view class="sheet-inner">
<!-- 有密码网络 -->
<block v-if="connectMode === 'secure'">
<view class="sheet-header">
<view class="sheet-nav-btn" @click="closeConnectSheet">
<image src="../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
</view>
<text class="sheet-title">加入"{{ connectInput.ssid }}"</text>
<view
class="sheet-nav-btn"
:class="{ 'nav-disabled': joinDisabled }"
@click="joinNetwork"
>
<image src="../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
</view>
</view>
<view class="input-row-card">
<text class="input-label">密码</text>
<input
class="input-field"
password
v-model="connectInput.password"
placeholder="输入网络密码"
placeholder-class="input-placeholder"
confirm-type="done"
@confirm="joinNetwork"
/>
</view>
<text v-if="connectError" class="connect-error">{{ connectError }}</text>
</block>
<!-- 无密码网络 -->
<block v-else-if="connectMode === 'open'">
<view class="sheet-header">
<view class="sheet-nav-btn" @click="closeConnectSheet">
<image src="../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
</view>
<text class="sheet-title">加入"{{ connectInput.ssid }}"</text>
<view class="sheet-nav-btn" @click="joinNetwork">
<image src="../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
</view>
</view>
<text class="sheet-hint">该网络为开放网络点击 加入</text>
<text v-if="connectError" class="connect-error">{{ connectError }}</text>
</block>
<!-- 手动输入 -->
<block v-else-if="connectMode === 'manual'">
<view class="sheet-header">
<view class="sheet-nav-btn" @click="closeConnectSheet">
<image src="../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
</view>
<text class="sheet-title">加入无线网络</text>
<view
class="sheet-nav-btn"
:class="{ 'nav-disabled': joinDisabled }"
@click="joinNetwork"
>
<image src="../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
</view>
</view>
<view class="input-row-card">
<text class="input-label">名称</text>
<input
class="input-field"
v-model="connectInput.ssid"
placeholder="输入网络名称"
placeholder-class="input-placeholder"
/>
</view>
<view class="input-row-card">
<text class="input-label">密码</text>
<input
class="input-field"
password
v-model="connectInput.password"
placeholder="输入网络密码"
placeholder-class="input-placeholder"
confirm-type="done"
@confirm="joinNetwork"
/>
</view>
<text v-if="ssidWarning" class="connect-error">{{ ssidWarning }}</text>
<text v-else-if="connectError" class="connect-error">{{ connectError }}</text>
</block>
</view>
</view>
</view>
</Container>
</template>
<style scoped>
.wifi-banner {
background-color: rgba(255, 200, 0, 0.12);
border: 1rpx solid rgba(254, 216, 71, 0.5);
border-radius: 8rpx;
padding: 16rpx 24rpx;
margin: 16rpx 24rpx 0;
}
.wifi-banner-text {
color: rgba(254, 216, 71, 1);
font-size: 26rpx;
}
.wifi-page {
padding: 0 67rpx 40rpx;
display: flex;
flex-direction: column;
}
.hero-area {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0 24rpx;
}
.page-title {
color: rgba(91, 196, 255, 1);
font-size: 36rpx;
font-weight: 600;
text-align: center;
margin-bottom: 8rpx;
}
.page-subtitle {
color: rgba(255, 255, 255, 0.5);
font-size: 24rpx;
text-align: center;
margin-bottom: 32rpx;
}
.fail-title {
color: rgba(255, 100, 100, 1);
}
.ios-guide {
background-color: rgba(95, 173, 255, 0.12);
border-radius: 8rpx;
padding: 16rpx 24rpx;
margin-bottom: 20rpx;
}
.ios-guide-text {
color: rgba(95, 173, 255, 1);
font-size: 26rpx;
}
.section-label-row {
display: flex;
align-items: center;
margin-bottom: 24rpx;
padding: 0 22rpx;
}
.section-label {
color: rgba(255, 255, 255, 0.56);
font-size: 28rpx;
}
.wifi-list-card {
background-color: rgba(30, 35, 50, 0.96);
border-radius: 24rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.connected-wifi-card {
margin-bottom: 44rpx;
}
.wifi-item {
position: relative;
display: flex;
align-items: center;
min-height: 92rpx;
padding: 0 26rpx 0 66rpx;
}
.wifi-item:not(:last-child)::after {
content: "";
position: absolute;
left: 66rpx;
right: 26rpx;
bottom: 0;
height: 1rpx;
background-color: rgba(255, 255, 255, 0.08);
}
.connected-wifi-item {
padding-left: 24rpx;
}
.wifi-ssid {
color: rgba(255, 255, 255, 1);
font-size: 28rpx;
line-height: 36rpx;
flex: 1;
}
.other-text {
color: rgba(255, 255, 255, 0.86);
}
.wifi-icons {
display: flex;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
}
.check-icon {
width: 28rpx;
height: 24rpx;
margin-right: 16rpx;
flex-shrink: 0;
}
.security-icon {
width: 24rpx;
height: 30rpx;
flex-shrink: 0;
}
.signal-icon {
width: 36rpx;
height: 32rpx;
flex-shrink: 0;
}
.bottom-btn-area {
margin-top: 24rpx;
}
.primary-btn {
background-color: rgba(254, 216, 71, 1);
border-radius: 50rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
}
.primary-btn-text {
color: rgba(0, 0, 0, 1);
font-size: 30rpx;
font-weight: 500;
}
.center-page {
display: flex;
flex-direction: column;
align-items: center;
padding: 80rpx 32rpx 40rpx;
}
.page-desc-white {
color: rgba(255, 255, 255, 1);
font-size: 28rpx;
line-height: 52rpx;
text-align: center;
}
.done-info-box {
width: 100%;
height: 240rpx;
background-color: rgba(255, 255, 255, 1);
border-radius: 24rpx;
margin: 24rpx 0;
}
.done-btn {
width: 80%;
}
.progress-wrap {
width: 80%;
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
margin-top: 40rpx;
}
.progress-track {
width: 100%;
height: 10rpx;
background-color: rgba(255, 255, 255, 0.15);
border-radius: 5rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: rgba(91, 196, 255, 1);
border-radius: 5rpx;
transition: width 0.4s ease;
}
.progress-pct {
color: rgba(91, 196, 255, 1);
font-size: 28rpx;
font-weight: 600;
}
.sheet-mask {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.sheet {
position: relative;
background-color: rgba(57, 47, 29, 1);
border-top-left-radius: 28rpx;
border-top-right-radius: 28rpx;
overflow: hidden;
min-height: 320rpx;
border: 1rpx solid rgba(249, 213, 161, 0.4);
border-bottom: none;
}
.sheet-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.sheet-inner {
position: relative;
z-index: 1;
padding: 32rpx 32rpx 112rpx;
}
.sheet-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32rpx;
}
.sheet-nav-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-disabled {
opacity: 0.3;
}
.sheet-title {
color: rgba(255, 255, 255, 1);
font-size: 30rpx;
font-weight: 600;
flex: 1;
text-align: center;
}
.sheet-hint {
color: rgba(255, 255, 255, 0.7);
font-size: 26rpx;
text-align: center;
margin-bottom: 24rpx;
}
.connect-error {
color: rgba(254, 216, 71, 1);
font-size: 26rpx;
line-height: 40rpx;
margin-top: 16rpx;
display: block;
}
.input-row-card {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
display: flex;
align-items: center;
padding: 24rpx 24rpx;
margin-left: 48rpx;
margin-right: 48rpx;
}
.input-row-card + .input-row-card {
margin-top: 16rpx;
}
.input-label {
color: rgba(255, 255, 255, 0.8);
font-size: 28rpx;
width: 80rpx;
flex-shrink: 0;
}
.input-field {
flex: 1;
color: rgba(255, 255, 255, 1);
font-size: 28rpx;
background: transparent;
border: none;
padding: 0 8rpx;
}
.input-placeholder {
color: rgba(255, 255, 255, 0.3);
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
src/static/ota/new-ver.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src/static/ota/ota-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
src/static/ota/ota-ver.png Normal file

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

BIN
src/static/ota/wifi1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
src/static/ota/wifi2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

BIN
src/static/sicon/cancel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/static/sicon/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

BIN
src/static/sicon/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

BIN
src/static/sicon/pwd.png Normal file

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

BIN
src/static/sicon/tick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
src/static/sicon/wifi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B