Sprinkler-app/pages/index/index.vue
2026-03-26 17:48:21 +08:00

4278 lines
132 KiB
Vue
Raw Permalink 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.

<template>
<view class="page">
<u-navbar :is-back="false" :border-bottom="false" :background="bgc" title-color='#2E4975' title-size='36'
height='8'></u-navbar>
<view class="title" style="display: flex;padding: 0;align-items: center;font-size: 48rpx;font-weight: 600;color: #48893B;">
<image style="width: 60rpx;margin-right: 10rpx;height: 50rpx;" src="https://api.ccttiot.com/smartmeter/img/static/u6wBJM2Dah2jLVGeSrKL" mode="aspectFit"></image> {{ $t('app.name') }}
</view>
<!-- 加载状态遮罩层 -->
<view class="loading-mask" v-if="isLoading">
<view class="loading-content">
<u-loading mode="circle" size="60" color="#7FAD76"></u-loading>
<text class="loading-text">{{ $t('index.loading') }}</text>
</view>
</view>
<!-- 背景图 -->
<!-- 没设备时展示 -->
<image class="bjimg" v-if="bjflag" src="https://api.ccttiot.com/smartmeter/img/static/uz8MR6BuZW2qRANnYgLu"
mode=""></image>
<!-- 有设备时展示 -->
<image class="bjimg" v-else src="https://api.ccttiot.com/smartmeter/img/static/uXBpJuaUs0XUNb8HTahh" mode="">
</image>
<view class="wusb" v-if="bjflag">
<text>{{ $t('index.noDevice') }}</text>
<view class="bang" @click="btnaddsb">
{{ $t('index.bind') }}
</view>
</view>
<view class="" v-else>
<view class="selectbox">
<view class="shebeiname" >
<view class="" @click="btnksxz">
{{user.deviceName == undefined ? '--' : user.deviceName}}
<image style="width: 32rpx;height: 26rpx;margin-left: 14rpx;" v-if="!xuanzeflag" src="https://api.ccttiot.com/smartmeter/img/static/uwHOBxvbJjkhx1uDiQHI" mode=""></image>
<image style="width: 32rpx;height: 26rpx;margin-left: 14rpx;" v-else class="rotated-image" src="https://api.ccttiot.com/smartmeter/img/static/uwHOBxvbJjkhx1uDiQHI" mode=""></image>
</view>
<view style="margin-left: 10rpx;" class="lj" @click="btnlj" v-if="vardataflag == 1">
{{ $t('index.bleDirect') }}
</view>
<view class="lj" v-if="vardataflag == 2">
{{ $t('index.connecting') }}
</view>
<view class="lj" style="color: #7FAD76;border: 1px solid #7FAD76;" v-if="vardataflag == 3" @click="btnduankai">
{{ $t('index.connected') }}
</view>
</view>
<view class="selectshezhi" style="display: flex;">
<view class="gateway-item" @click="goToGatewayList" style="position: relative;padding: 0 20rpx;">
<image class="gateway-icon-circle" src="https://api.ccttiot.com/ad93702b02429b8adf394569460b8b2a-1758263546164.png" mode="aspectFill"></image>
<view class="gateway-content" style="display: flex;align-items: center;">
</view>
</view>
<image style="margin-left: 10rpx;" @click="btnsz" src="https://api.ccttiot.com/smartmeter/img/static/uvCJ8ro0MpfGZd53vypJ" mode=""></image>
</view>
</view>
<view v-if=" ver != '' && ver.slice(3) != version" @click="btnsz" class="" style="width: 100%;height: 76rpx;margin-top: 20rpx;">
<image style="width: 100%;height: 76rpx;" src="https://api.ccttiot.com/smartmeter/img/static/uFqC94yHg1YXw1GoqTsL" mode=""></image>
</view>
<!-- 设备信息 -->
<view class="devicebox">
<view class="devicetop">
<view class="devicelt" @click="btnsz">
<image :src="imgpic" mode="aspectFit"></image>
<view class="xinghao">
<view class="one" style="display: flex;align-items: center;">
{{ $t('common.model') }}{{user.model == undefined ? '--' : user.model}}
<view class="" style="display: flex;align-items: center;margin-left: 20rpx;" v-if="vardataflag != 3">
<view class="signal-bars" style="display: flex;align-items: flex-end;">
<view class="signal-bar" :class="{'active': signalStrength >= 1}" style="width: 6rpx;height: 10rpx;background-color: #E0E0E0;margin-right: 3rpx;border-radius: 1rpx;"></view>
<view class="signal-bar" :class="{'active': signalStrength >= 2}" style="width: 6rpx;height: 14rpx;background-color: #E0E0E0;margin-right: 3rpx;border-radius: 1rpx;"></view>
<view class="signal-bar" :class="{'active': signalStrength >= 3}" style="width: 6rpx;height: 18rpx;background-color: #E0E0E0;margin-right: 3rpx;border-radius: 1rpx;"></view>
<view class="signal-bar" :class="{'active': signalStrength >= 4}" style="width: 6rpx;height: 22rpx;background-color: #E0E0E0;border-radius: 1rpx;"></view>
</view>
</view>
<view class="battery-wrap" style="font-weight: 400;font-size: 26rpx;margin-left: 20rpx;display: flex;align-items: center;">
<view class="battery-box">
<view class="battery-fill" :style="{ width: (batteryPercent != null ? (Number(batteryPercent) < 100 ? Number(batteryPercent) + 10 : 100) : 0) + '%', backgroundColor: batteryColor }"></view>
<!-- <view class="battery-text">{{ batteryPercent != null ? batteryPercent + '%' : '--' }}</view> -->
</view>
<view class="battery-nub"></view>
</view>
</view>
<view class="">{{ $t('common.mac') }}{{user.mac == undefined ? '--' : user.mac}}</view>
</view>
</view>
<view v-if="vardataflag != 3" class="refresh-btn" :class="{ 'is-refreshing': isRefreshing }" @click="onRefreshClick">
<text class="refresh-icon">{{ isRefreshing ? '⋯' : '↻' }}</text>
<text class="refresh-text">{{ isRefreshing ? $t('index.refreshing') : $t('index.refresh') }}</text>
</view>
</view>
<!-- SATER双通道定时A/B斜跨样式梯形tab -->
<view class="channel-switch" v-if="pre === 'SATER'">
<view class="channel-bg channel-bg-a" :class="{ active: stareChannel === 'A' }"></view>
<view class="channel-bg channel-bg-b" :class="{ active: stareChannel === 'B' }"></view>
<view class="channel-text channel-text-a" :class="{ active: stareChannel === 'A' }" @click.stop="setStareChannel('A')">
<text>A</text>
<view class="channel-indicator" v-if="stareChannel === 'A'"></view>
</view>
<view class="channel-text channel-text-b" :class="{ active: stareChannel === 'B' }" @click.stop="setStareChannel('B')">
<text>B</text>
<view class="channel-indicator" v-if="stareChannel === 'B'"></view>
</view>
</view>
<view class="deviceweek" v-if="['WATER', 'DATER', 'TATER', 'SATER'].includes(pre)" @click="btntime">
<view class="weeklist">
<view class="" v-if="!ver_data.p_set1 || ver_data.p_set1[3] == 0">
<view class="kg" style="color: rgba(0, 0, 0, .3);" v-if="!ver_data.p_set1 || ver_data.p_set1[3] == 0">
OFF
</view>
<view class="weekday" style="color: rgba(0,0,0,.3);">
P1
</view>
<view class="time" style="color: rgba(0,0,0,.3);">
{{ver_data.p_set1 && ver_data.p_set1[0] !== undefined ? ver_data.p_set1[0].toString().padStart(2, '0') + ':' + ver_data.p_set1[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" style="color: rgba(0,0,0,.3);" v-if="ver_data.p_set1 && ver_data.p_set1[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set1[4] }) }}
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #7FAD76;">
ON
</view>
<view class="weekday">
P1
</view>
<view class="time">
{{ver_data.p_set1 && ver_data.p_set1[0] !== undefined ? ver_data.p_set1[0].toString().padStart(2, '0') + ':' + ver_data.p_set1[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" v-if="ver_data.p_set1 && ver_data.p_set1[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set1[4] }) }}
</view>
<view class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="img">
<image v-if="shouldShowIcon(1)"
src="https://api.ccttiot.com/smartmeter/img/static/u9iZpd6bW6bUsUay4uvH" mode="">
</image>
</view>
</view>
<view class="weeklist">
<view class="" v-if="!ver_data.p_set2 || ver_data.p_set2[3] == 0">
<view class="kg" style="color: rgba(0, 0, 0, .3);" v-if="!ver_data.p_set2 || ver_data.p_set2[3] == 0">
OFF
</view>
<view class="weekday" style="color: rgba(0,0,0,.3);">
P2
</view>
<view class="time" style="color: rgba(0,0,0,.3);">
{{ver_data.p_set2 && ver_data.p_set2[0] !== undefined ? ver_data.p_set2[0].toString().padStart(2, '0') + ':' + ver_data.p_set2[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" style="color: rgba(0,0,0,.3);" v-if="ver_data.p_set2 && ver_data.p_set2[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set2[4] }) }}
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #7FAD76;">
ON
</view>
<view class="weekday">
P2
</view>
<view class="time">
{{ver_data.p_set2 && ver_data.p_set2[0] !== undefined ? ver_data.p_set2[0].toString().padStart(2, '0') + ':' + ver_data.p_set2[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" v-if="ver_data.p_set2 && ver_data.p_set2[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set2[4] }) }}
</view>
<view class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="img">
<image v-if="shouldShowIcon(2)"
src="https://api.ccttiot.com/smartmeter/img/static/u9iZpd6bW6bUsUay4uvH" mode="">
</image>
</view>
</view>
<view class="weeklist">
<view class="" v-if="!ver_data.p_set3 || ver_data.p_set3[3] == 0">
<view class="kg" style="color: rgba(0, 0, 0, .3);" v-if="!ver_data.p_set3 || ver_data.p_set3[3] == 0">
OFF
</view>
<view class="weekday" style="color: rgba(0,0,0,.3);">
P3
</view>
<view class="time" style="color: rgba(0,0,0,.3);">
{{ver_data.p_set3 && ver_data.p_set3[0] !== undefined ? ver_data.p_set3[0].toString().padStart(2, '0') + ':' + ver_data.p_set3[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" style="color: rgba(0,0,0,.3);" v-if="ver_data.p_set3 && ver_data.p_set3[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set3[4] }) }}
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #7FAD76;">
ON
</view>
<view class="weekday">
P3
</view>
<view class="time">
{{ver_data.p_set3 && ver_data.p_set3[0] !== undefined ? ver_data.p_set3[0].toString().padStart(2, '0') + ':' + ver_data.p_set3[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" v-if="ver_data.p_set3 && ver_data.p_set3[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set3[4] }) }}
</view>
<view class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="img">
<image v-if="shouldShowIcon(3)"
src="https://api.ccttiot.com/smartmeter/img/static/u9iZpd6bW6bUsUay4uvH" mode="">
</image>
</view>
</view>
<view class="weeklist">
<view class="" v-if="!ver_data.p_set4 || ver_data.p_set4[3] == 0">
<view class="kg" style="color: rgba(0, 0, 0, .3);" v-if="!ver_data.p_set4 || ver_data.p_set4[3] == 0">
OFF
</view>
<view class="weekday" style="color: rgba(0,0,0,.3);">
P4
</view>
<view class="time" style="color: rgba(0,0,0,.3);">
{{ver_data.p_set4 && ver_data.p_set4[0] !== undefined ? ver_data.p_set4[0].toString().padStart(2, '0') + ':' + ver_data.p_set4[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" style="color: rgba(0,0,0,.3);" v-if="ver_data.p_set4 && ver_data.p_set4[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set4[4] }) }}
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #7FAD76;">
ON
</view>
<view class="weekday">
P4
</view>
<view class="time">
{{ver_data.p_set4 && ver_data.p_set4[0] !== undefined ? ver_data.p_set4[0].toString().padStart(2, '0') + ':' + ver_data.p_set4[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" v-if="ver_data.p_set4 && ver_data.p_set4[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set4[4] }) }}
</view>
<view class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="img">
<image v-if="shouldShowIcon(4)"
src="https://api.ccttiot.com/smartmeter/img/static/u9iZpd6bW6bUsUay4uvH" mode="">
</image>
</view>
</view>
<view class="weeklist">
<view class="" v-if="!ver_data.p_set5 || ver_data.p_set5[3] == 0">
<view class="kg" style="color: rgba(0, 0, 0, .3);" v-if="!ver_data.p_set5 || ver_data.p_set5[3] == 0">
OFF
</view>
<view class="weekday" style="color: rgba(0,0,0,.3);">
P5
</view>
<view class="time" style="color: rgba(0,0,0,.3);">
{{ver_data.p_set5 && ver_data.p_set5[0] !== undefined ? ver_data.p_set5[0].toString().padStart(2, '0') + ':' + ver_data.p_set5[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" style="color: rgba(0,0,0,.3);" v-if="ver_data.p_set5 && ver_data.p_set5[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set5[4] }) }}
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #7FAD76;">
ON
</view>
<view class="weekday">
P5
</view>
<view class="time">
{{ver_data.p_set5 && ver_data.p_set5[0] !== undefined ? ver_data.p_set5[0].toString().padStart(2, '0') + ':' + ver_data.p_set5[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" v-if="ver_data.p_set5 && ver_data.p_set5[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set5[4] }) }}
</view>
<view class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="img">
<image v-if="shouldShowIcon(5)"
src="https://api.ccttiot.com/smartmeter/img/static/u9iZpd6bW6bUsUay4uvH" mode="">
</image>
</view>
</view>
<view class="weeklist">
<view class="" v-if="!ver_data.p_set6 || ver_data.p_set6[3] == 0">
<view class="kg" style="color: rgba(0, 0, 0, .3);" v-if="!ver_data.p_set6 || ver_data.p_set6[3] == 0">
OFF
</view>
<view class="weekday" style="color: rgba(0,0,0,.3);">
P6
</view>
<view class="time" style="color: rgba(0,0,0,.3);">
{{ver_data.p_set6 && ver_data.p_set6[0] !== undefined ? ver_data.p_set6[0].toString().padStart(2, '0') + ':' + ver_data.p_set6[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" style="color: rgba(0,0,0,.3);" v-if="ver_data.p_set6 && ver_data.p_set6[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set6[4] }) }}
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #7FAD76;">
ON
</view>
<view class="weekday">
P6
</view>
<view class="time">
{{ver_data.p_set6 && ver_data.p_set6[0] !== undefined ? ver_data.p_set6[0].toString().padStart(2, '0') + ':' + ver_data.p_set6[1].toString().padStart(2, '0') : '--:--'}}
</view>
<view class="interval" v-if="ver_data.p_set6 && ver_data.p_set6[4] > 1">
{{ $t('index.everyNdays', { n: ver_data.p_set6[4] }) }}
</view>
<view class="interval" v-else>
{{ $t('index.daily') }}
</view>
</view>
<view class="img">
<image v-if="shouldShowIcon(6)"
src="https://api.ccttiot.com/smartmeter/img/static/u9iZpd6bW6bUsUay4uvH" mode="">
</image>
</view>
</view>
</view>
<view class="" style="margin-top: 18rpx;display: flex;justify-content: space-between;" v-else>
<view class="" style="font-size: 24rpx;color: #50565A;text-align: center;margin-top: 10rpx;">
<view class="" style="display: flex;align-items: center;">
<image style="width:32rpx;height:32rpx;margin-right: 8rpx;" src="https://api.ccttiot.com/smartmeter/img/static/uhyYG0TLfWi9XUhTQz7D" mode=""></image>
{{ $t('index.startTime') }}
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-if="csbobj.hour">
{{csbobj.hour == undefined ? '--' : csbobj.hour + ':'}}{{csbobj.minute == undefined ? '' : csbobj.minute}}
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-else>
{{formatOnlineStartTime(zaixianobj)}}
</view>
</view>
<view class="" style="font-size: 24rpx;color: #50565A;margin-top: 10rpx;text-align: center;">
<view class="" style="display: flex;align-items: center;">
<image style="width:32rpx;height:32rpx;margin-right: 8rpx;font-size: 36rpx;font-weight: 600;" src="https://api.ccttiot.com/smartmeter/img/static/uuQaK98cmdQQnPWus27Z" mode=""></image>
{{ $t('index.workDuration') }}
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-if="csbobj.second">
{{csbobj.second == undefined ? '--' : csbobj.second + $t('common.sec')}}
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-else>
{{zaixianobj.t.value == undefined ? '--' : zaixianobj.t.value + $t('common.sec')}}
</view>
</view>
<view class="" style="font-size: 24rpx;color: #50565A;margin-top: 10rpx;text-align: center;">
<view class="" style="display: flex;align-items: center;">
<image style="width:32rpx;height:32rpx;margin-right: 8rpx;font-size: 36rpx;font-weight: 600;" src="https://api.ccttiot.com/smartmeter/img/static/uM6Yz4xVqFwzW6xlTRph" mode=""></image>
{{ $t('index.intervalDays') }}
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-if="csbobj.day">
{{csbobj.day == undefined ? '--' : csbobj.day + $t('common.day')}}
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-else>
{{zaixianobj.d.value == undefined ? '--' : zaixianobj.d.value + $t('common.day')}}
</view>
</view>
</view>
</view>
<!-- 开关选择 -->
<view class="dangqian">
<view class="" style="display: flex;">
<image src="https://lxnapi.ccttiot.com/smartmeter/img/static/ufHmxq7KI9PJhv8ey4ds" mode=""></image>
<view class="cen" @click="btntb">
<view class="shen">
{{ $t('index.currentTime') }} <text>{{ $t('index.sync') }}</text>
</view>
<view class="">
{{ $t('index.tapSyncHint') }}
</view>
</view>
</view>
<view class="rt" style="display: flex;align-items: center;" @click="btnsztime">
{{devicetime == '' ? '--' : devicetime.slice(0,5)}} <u-icon name="arrow-right" color="#7FAD76" size="32"></u-icon>
</view>
</view>
<view class="switchbox">
<view class="switch_he" v-if="['WATER', 'DATER', 'TATER', 'SATER'].includes(pre)">
<image src="https://api.ccttiot.com/smartmeter/img/static/u7NwkNOoQYYsvHVMkDlu" mode=""></image>
<view class="yushui">
{{ $t('index.rainSensor') }}
<u-switch v-if="yschecked" v-model="one" @change="btnyushui" :disabled="isRainSensorLocked" inactive-color="#eee"
active-color="#eee" size="40"></u-switch>
<u-switch v-else v-model="ones" @change="btnyushuis" :disabled="isRainSensorLocked" active-color="#7FAD76"
inactive-color="#7FAD76" size="40"></u-switch>
</view>
</view>
<view class="switch_he" v-if="['WATER', 'DATER', 'TATER', 'SATER'].includes(pre)">
<image src="https://api.ccttiot.com/smartmeter/img/static/u7kd92ocUgDN052nhp4R" mode=""></image>
<view class="yushui">
{{ $t('index.childLock') }} <u-switch v-if="etchecked" v-model="two" @change="btnertong" inactive-color="#eee"
active-color="#eee" size="40"></u-switch>
<u-switch v-else v-model="twos" @change="btnertongs" active-color="#7FAD76"
inactive-color="#7FAD76" size="40"></u-switch>
</view>
</view>
</view>
<view class="" style="display: flex;justify-content: space-between;">
<!-- 定时 -->
<view class="dingshi_he" @click="btntime">
<view class="naoz">
<image src="https://api.ccttiot.com/smartmeter/img/static/uJPgzMejk9gaogWnCO9M" mode=""></image> <text v-if="['WATER', 'DATER', 'TATER', 'SATER'].includes(pre)">{{ $t('index.groups6') }}</text>
</view>
<view class="dstime">
{{ $t('index.timerMgmt') }} <image src="https://api.ccttiot.com/smartmeter/img/static/uagx3wGa7RYvqKqoSymy" mode="">
</image>
</view>
</view>
<!-- 手动浇水 -->
<view class="dingshi_he">
<view class="naoz" style="font-weight: 400;font-size: 26rpx;align-items: center;">
<image style="width: 170rpx;" src="https://api.ccttiot.com/smartmeter/img/static/uVYXvo4F6U6fJ5fP27BT" mode="aspectFill"></image>
<text v-if="(sdminutes || sdseconds) && pre === 'SATER'">{{stareChannel}}</text>
{{ sdminutes ? sdminutes + ':' : '' }}{{ sdseconds ? sdseconds : '' }}
<text v-if="showWateringCountdown && wateringCountdownSeconds" style="margin-left: 10rpx;color: #7FAD76;font-size: 24rpx;">
{{ wateringCountdownMinutes ? wateringCountdownMinutes + ':' : '' }}{{ wateringCountdownSeconds }}
</text>
</view>
<view class="dstime">
{{ $t('index.manualWater') }} <u-switch v-if="kgflag" v-model="jskeds" @change="btngb" inactive-color="#eee" active-color="#eee" size="40"></u-switch>
<u-switch v-else v-model="jsked" @change="btnkq" active-color="#7FAD76" inactive-color="#7FAD76" size="40"></u-switch>
</view>
</view>
</view>
<!-- 浇水日志 -->
<view class="wateringlogbox" v-if="['WATER', 'DATER', 'TATER', 'SATER'].includes(pre)" @click="btnjs">
<view class="wateringlogtop">
<image src="https://api.ccttiot.com/smartmeter/img/static/uGh3pRM7mMRSHdmvtzv3" mode=""></image>
{{ $t('index.wateringLog') }}
</view>
<view class="wateringlogbd">
<view class="lt" >
<view class="">{{ $t('index.lastSprayDur') }}{{jsobj.sprayingTime == undefined ? '--' : jsobj.sprayingTime + 's'}}</view>
<view class="">{{ $t('index.lastWater') }}{{jsobj.lastWaterTime == undefined ? '--' : jsobj.lastWaterTime + 's'}}</view>
</view>
<view class="rt">
<image src="https://api.ccttiot.com/smartmeter/img/static/uagx3wGa7RYvqKqoSymy" mode=""></image>
</view>
</view>
</view>
<view class="" @click="btnbc" v-if="['WATER', 'DATER', 'TATER', 'SATER'].includes(pre)" style="width: 100%;height: 80rpx;background-color: #7FAD76;color: #fff;text-align: center;font-size: 32rpx;line-height: 80rpx;margin-top: 30rpx;border-radius: 20rpx;">
{{ $t('index.saveTimer') }}
</view>
</view>
<!-- 切换设备 -->
<view class="tabsb" v-if="xuanzeflag">
<view class="selectbox">
<view class="selectname" @click="btnksxz">
{{ $t('index.switchDevice') }} <image v-if="!xuanzeflag" src="https://api.ccttiot.com/smartmeter/img/static/uwHOBxvbJjkhx1uDiQHI" mode=""></image>
<image v-else class="rotated-image" src="https://api.ccttiot.com/smartmeter/img/static/uwHOBxvbJjkhx1uDiQHI" mode=""></image>
</view>
</view>
<view class="sblist">
<view class="sbist_val" v-for="(item,index) in devicelist" :key="index" @click="btnactive(item.deviceId,index)">
<view class="lt">
<view class="">{{item.deviceName}}</view>
<view class="" style="font-weight: 400;font-size: 26rpx;color: #989090;margin-top: 10rpx;">
{{item.mac}}
</view>
</view>
<view class="rt">
<image :src="item.modelPicture" mode="aspectFit"></image>
</view>
</view>
</view>
<view class="qdan" @click="btnaddsb">
{{ $t('index.addDevice') }}
</view>
</view>
<view class="mask" v-if="xuanzeflag" @click="btntc"></view>
<!-- 手动浇水 -->
<view class="manualjs" v-if="sdjsflag">
<view class="toptit">
{{ $t('index.pickDuration') }}
</view>
<view class="container" @click="btnshowjs">
<view class="">{{minute}}{{ $t('common.min') }}</view>
<text>:</text>
<view class="">{{second}}{{ $t('common.sec') }}</view>
</view>
<view class="anniu">
<view class="qx" @click="btnqx">
{{ $t('common.cancel') }}
</view>
<view class="qd" @click="btnqd">
{{ $t('common.confirm') }}
</view>
</view>
</view>
<view class="manualmask" v-if="sdjsflag"></view>
<!-- 选择浇水时间-->
<u-picker v-model="showjs" mode="time" :default-time="0" :params="params" @confirm="confirm"></u-picker>
<!-- 设置当前时间 -->
<u-picker v-model="timeflag" mode="time" :params="paramss" @confirm="confirmtime"></u-picker>
<tab-bar :indexs='0'></tab-bar>
<language-float />
<!-- 自定义错误弹窗 -->
<view class="error-modal-mask" v-if="showErrorModal" @click="closeErrorModal">
<view class="error-modal-container" @click.stop>
<view class="error-modal-header">
<view class="error-icon">⚠️</view>
<text class="error-modal-title">{{errorModalData.title}}</text>
</view>
<view class="error-modal-content">
<view class="error-message">{{errorModalData.message}}</view>
<view class="error-action" v-if="errorModalData.userAction">
<text class="action-label">{{ $t('index.suggestion') }}</text>
<text class="action-text">{{errorModalData.userAction}}</text>
</view>
</view>
<view class="error-modal-footer">
<view class="error-btn error-btn-cancel" v-if="errorModalData.showCancel" @click="closeErrorModal">
{{errorModalData.cancelText || $t('common.cancel')}}
</view>
<view class="error-btn error-btn-confirm" @click="handleErrorConfirm">
{{errorModalData.confirmText || $t('common.confirm')}}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import LanguageFloat from '@/components/language-float/language-float.vue'
var xBlufi = require("@/components/blufi/xBlufi.js")
export default {
components: {
LanguageFloat
},
data() {
return {
timeflag:false,
// 手动浇水“确定”按钮请求锁:请求未返回前禁止重复提交
isManualWaterRequesting: false,
// 点击确定后立即锁死雨水感应(不等请求返回),请求失败或浇水结束才解锁
rainSensorLockedByWatering: false,
// 手动浇水开关(开启/关闭防频繁点击接口模式等请求结束蓝牙模式至少冷却3秒
isManualWaterSwitchRequesting: false,
manualWaterBtCooldownUntil: 0,
// 浇水倒计时到0后的延迟清理定时器避免和新倒计时串台
wateringCountdownResetTimer: null,
// 刷新按钮“刷新中”状态,约 1.5 秒
isRefreshing: false,
// 最近一次成功上传的时间戳(毫秒)
lastUploadTs: 0,
// 最近一次获取电量的时间戳(毫秒)
lastPowerTs: 0,
one: false,
ones: true,
two: false,
twos: true,
jsked:true,
jskeds:false,
yschecked: false,
etchecked: false,
kgflag: true,
activeshu: 0,
xuanzeflag: false,
selectedMinute: '1',
selectedSecond: '1',
sdjsflag: false,
showjs: false,
params: {
year: false,
month: false,
day: false,
hour: false,
minute: true,
second: true
},
paramss: {
year: false,
month: false,
day: false,
hour: true,
minute: true,
second: false
},
minute: '00',
second: '10',
hasShownVersionAlert: false,
deviceId: '',
name: '',
mac: '',
jstime: '10',
ver_data: {},
vardataflag:1,
showobj: {},
jstimeobj:{},
xctime: '--',
sctimejs: '--',
xctimesc: '--',
xctimesj: '',
sdminutes: '',
sdseconds: '',
wateringCountdownMinutes: '', // 浇水倒计时分钟从showArray[4]获取)
wateringCountdownSeconds: '', // 浇水倒计时秒数从showArray[4]获取)
showWateringCountdown: false, // 是否显示浇水倒计时第三位和第四位都为1时才显示
datalist: '',
devicelist:[],
shebid:'',
user:{},
lastChar:'',
timer:'',
wateringCountdownTimer: null, // 浇水倒计时定时器
bjflag: '',
userobj:{},
devicesarr:[],
intervalId: null,
imgflag:true,
ver:'',
pre:'',
csbobj:{},
disconnectTimer: null,
devicetime:'',
imgpic:'',
xinp:'',
version:'',
jiance:false,
shibainum:0,
searchStartTime: 0, // 记录开始搜索的时间戳
searchTimeout: 10000, // 10秒超时单位毫秒
searchTimer: null, // 搜索定时器
isSearching: false, // 是否正在搜索中
dataTimeoutTimer: null ,// 5秒数据看门狗定时器
connectionStartTime: 0, // 记录连接开始时间
connectionTimeout: null, // 连接超时定时器
connectTimeout: null, // 配对超时定时器
errorModalShown: false, // 错误弹窗是否已显示(防止重复弹出)
currentErrorType: null, // 当前检测到的错误类型
showErrorModal: false, // 是否显示自定义错误弹窗
errorModalData: {}, // 错误弹窗数据
dianya:0,
sydl:'--',
signalStrength: 0, // 信号强度格数1-4
zaixianobj:{},
modelId:'',
jsobj:{},
isLoading: false ,// 设备请求加载状态
vatatxt:'',
sn:'',
onlineTime:'',
onlineStatus:'',
qiehuan:'',
// SATER 双通道A/B
stareChannel: 'A',
// SATER 按类型缓存pA/pB/ver 三组数据不定序到达,存各自最新值
saterDataByType: { pA: '', pB: '', ver: '' },
// SATER 最近一次完整原始数据(包含 A/B用于通道切换后重算
saterRawPayload: '',
// SATER 从 getDetail 接口解析的 A/B 组别(非蓝牙模式展示用)
saterListA: {},
saterListB: {}
}
},
computed: {
// 是否正在浇水(手动定时器或设备倒计时在跑),用于:浇水开关不可关闭、雨水感应不可点击
isWateringInProgress() {
const manualRunning = !!(this.sdminutes || this.sdseconds)
const deviceCountdownRunning = !!(this.showWateringCountdown && (this.wateringCountdownMinutes || this.wateringCountdownSeconds))
return manualRunning || deviceCountdownRunning
},
// 雨水感应是否锁死(点击确定那一刻即锁,请求失败或浇水结束才解锁)
isRainSensorLocked() {
return this.isWateringInProgress || this.rainSensorLockedByWatering
},
// 电量百分比 0-100无效时为 null显示 --
batteryPercent() {
if (this.sydl == null || this.sydl === '' || isNaN(Number(this.sydl))) return null
return Math.min(100, Math.max(0, Math.floor(Number(this.sydl))))
},
// 电量条背景色:低红、中黄、高绿
batteryColor() {
if (this.batteryPercent == null) return '#E0E0E0'
if (this.batteryPercent <= 20) return '#F44336'
if (this.batteryPercent <= 49) return '#FFC107'
return '#7FAD76'
}
},
// 分享到好友(会话)
onShareAppMessage: function() {
return {
title: this.$t('app.name'),
path: '/pages/index/index'
}
},
// 分享到朋友圈
onShareTimeline: function() {
return {
title: this.$t('app.name'),
query: '',
path: '/pages/index/index'
}
},
onLoad(e) {
console.log(e,'000000000000000000000000000000000000');
if(e.q){
function getQueryParam(url, paramName) {
let regex = new RegExp(`[?&]${paramName}=([^&]*)`)
let results = regex.exec(url)
return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null
}
let sceneValue = e.q
let decodedValue = decodeURIComponent(sceneValue)
this.sn = getQueryParam(decodedValue, 's')
let data = {
sn:this.sn
}
this.$u.post(`/app/device/bindDevice`,data).then((res) => {
if (res.code == 200) {
uni.showModal({
title: this.$t('common.tip'),
content: this.$t('common.bindSuccess'),
showCancel: false,
confirmText: this.$t('common.gotIt')
})
}else if(res.code == 401){
this.jmlogin()
} else{
uni.showModal({
title: this.$t('common.tip'),
content: res.msg,
showCancel: false,
confirmText: this.$t('common.gotIt')
})
}
})
}
xBlufi.initXBlufi(1)
},
onShow() {
// 从定时页返回时:若当前是蓝牙已连接状态,先同步定时页可能写入的 BLE 数据并在首页解析
if (this.$store && this.$store.state.bleWaterData && this.$store.state.bleWaterData.raw && this.vardataflag === 3 && (this.pre === 'WATER' || this.pre === 'SATER')) {
const raw = this.$store.state.bleWaterData.raw
this.datalist = (raw.slice(-1) === ';' ? raw : raw.slice(0, -1) + ';')
if (this.pre === 'SATER') {
this.saterRawPayload = this.datalist
this.grisfjx()
} else {
this.getchuli()
}
this.$store.commit('clearBleWaterData')
}
// 蓝牙已连接时确保注册监听,以便返回首页后能继续收到设备推送
if (this.vardataflag === 3) {
xBlufi.listenDeviceMsgEvent(true, this.funListenDeviceMsgEvent)
}
// 请求个人信息
this.getinfo()
this.getshuju() // 页面加载时启动定时器
},
mounted() {
},
onHide() {
// 页面隐藏时清除定时器
this.clearTimer()
this.clearDisconnectTimer()
this.clearDataTimeoutTimer()
this.clearConnectionTimeout()
this.clearWateringCountdownTimer()
},
onUnload() {
// 页面卸载时清除定时器
this.clearTimer()
this.clearDisconnectTimer()
this.clearDataTimeoutTimer()
this.clearConnectionTimeout()
this.clearWateringCountdownTimer()
},
methods: {
pad2(v){
if (v == null || v === '' || v === undefined) return '-'
const s = String(v)
return s.length < 2 ? ('0' + s) : s
},
formatOnlineStartTime(obj){
const h = obj && obj.h ? obj.h.value : undefined
const m = obj && obj.m ? obj.m.value : undefined
if (h === undefined || m === undefined) return '--:--'
return `${this.pad2(h)}:${this.pad2(m)}`
},
// 点击弹窗进行隐藏
btntc(){
this.xuanzeflag = !this.xuanzeflag
},
// 将时间格式HH:mm转换为总秒数
timeToSeconds(timeStr) {
if (!timeStr || timeStr === '--' || !timeStr.includes(':')) {
return 0;
}
const [hours, minutes] = timeStr.split(':').map(Number);
return (hours || 0) * 3600 + (minutes || 0) * 60;
},
// 将总秒数转换为时间格式HH:mm
secondsToTime(seconds) {
if (!seconds && seconds !== 0) {
return '--';
}
const totalSeconds = parseInt(seconds) || 0;
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
},
// 点击断开连接
btnduankai(){
wx.closeBLEConnection({
deviceId: this.deviceId,
})
this.fullPageRefresh()
},
// 跳转到网关列表
goToGatewayList() {
uni.navigateTo({
url: '/pages/gateway/list'
})
},
// 请求浇花器数据
getsj(){
this.$u.get(`/app/device/getDetail?deviceId=${this.shebid}`).then(res => {
if (res.code == 200) {
this.zaixianobj = res.data.iotData
this.modelId = res.data.modelId
console.log(this.vardataflag,this.zaixianobj);
// 非蓝牙模式时,用后台数据组装渲染结构
if((this.pre === 'WATER' || this.pre === 'SATER') && this.vardataflag != 3 || this.pre === 'DATER' && this.vardataflag != 3 || this.pre === 'TATER' && this.vardataflag != 3){
this.ver_data = this.buildVerDataFromBackend(this.zaixianobj)
this.calculateNextWateringTime()
}
if(this.pre === 'SMSJ:' && this.vardataflag != 3){
this.ver_data = this.buildVerDataFromBackend(this.zaixianobj)
// this.ver_data = this.zaixianobj
console.log(this.ver_data,'55555555555prprprprprprprpr');
}
if (res.data && res.data.iotData && res.data.iotData.xinp !== undefined) {
this.xinp = res.data.iotData.xinp.value == undefined ? '--' : res.data.iotData.xinp.value
}
if (res.data && res.data.iotData && res.data.iotData.yudi !== undefined) {
if (!this.isRainSensorLocked) {
this.yschecked = res.data.iotData.yudi.value == 0 ? true : false
}
}
if (res.data && res.data.iotData && res.data.iotData.lock !== undefined) {
this.etchecked = res.data.iotData.lock.value == 0 ? true : false
}
// 优先从 nt.value 获取总秒数,如果没有则从 time.value 获取
let ntValue = res?.data?.iotData?.nt?.value;
if (ntValue !== undefined && ntValue !== null && ntValue !== '') {
// 从总秒数转换为时间格式
this.devicetime = this.secondsToTime(ntValue);
} else {
// 兼容旧数据,从 time.value 获取
let originalValue = (res?.data?.iotData?.time?.value ?? "").toString().trim();
// 去掉首尾可能多出来的英文双引号 "
originalValue = originalValue.replace(/^"|"$/g, '');
this.devicetime = originalValue || "--";
}
console.log('devicetime:', this.devicetime);
// 获取信号强度
if (res.data && res.data.iotData && res.data.iotData.RSSI !== undefined && res.data.iotData.RSSI.value !== undefined) {
this.signalStrength = this.calculateSignalStrength(res.data.iotData.RSSI.value)
} else {
this.signalStrength = 0
}
// 取值
this.onlineTime = res.data.onlineTime
this.onlineStatus = res.data.onlineStatus
if(res.data.iotData && res.data.iotData.bat !== undefined && res.data.iotData.bat.value !== undefined){
this.dianya = res.data.iotData.bat.value
this.getdianliang()
}
}
})
},
// 点击保存设置
btnbc(){
this.handleUserAction()
if(this.vardataflag != 3){
let parameters = {
time:this.xinp
}
let data = {
deviceId:this.shebid,
instructionKey:'sleep',
parameters:parameters
}
this.$u.post(`/app/sendCommandByGateway`,data).then(res => {
if (res.code == 200) {
uni.showToast({
title: this.$t('common.saveOk'),
icon: 'success',
duration:2000
})
}else{
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}else{
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=sleep`).then(res =>{
if(res.code == 200){
uni.showToast({
title: this.$t('common.saveOk'),
icon: 'success',
duration:2000
})
xBlufi.notifySendCustomData({
customData: '11' + this.format(res.data, {time: this.xinp })
})
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
getbanbens(){
this.$u.get(`/app/model/${this.user.modelId}`).then(res => {
if (res.code == 200) {
this.version = res.data.version.slice(1)
console.log(this.version,'this.versionthis.versionthis.version');
}
})
},
// 点击进行设备录入
btnluru(){
this.datalist = ''
this.mac = ''
this.ver_data = {}
uni.reLaunch({
url:'/pages/myorder/index'
})
},
// 点击选择时间
btnshowjs() {
this.showjs = true
},
// 补零函数
formattedTime(minutes, seconds) {
// 将数字转换为字符串并补零
const formattedMinutes = String(minutes).padStart(2, '0')
const formattedSeconds = String(seconds).padStart(2, '0')
// 返回格式化后的时间字符串
console.log(`${formattedMinutes}:${formattedSeconds}`,'111111111')
return `${formattedMinutes}:${formattedSeconds}`
},
// 静默登录
jmlogin() {
let taht = this
wx.login({
success(res) {
if (res.code) {
let data = {
loginCode: res.code,
}
taht.$u.post(`/wxLogin`, data).then(res => {
if (res.code == 200) {
taht.getinfo()
uni.setStorageSync('token', res.token)
}else if(res.code == 10001){
that.bjflag = true
uni.showModal({
title: taht.$t('common.tip'),
content: taht.$t('common.promptLogin'),
success: function (res) {
if (res.confirm) {
uni.reLaunch({
url:'/pages/login/login'
})
} else if (res.cancel) {
}
}
})
}
})
}
}
})
},
// 获取用户信息
getinfo() {
this.isLoading = true // 开始加载
this.$u.get(`/system/user/profile`).then((res) => {
if (res.code == 200) {
this.userobj = res.data
uni.setStorageSync('user',res.data)
uni.setStorageSync('userId',res.data.userId)
// 根据用户id获取当前用户
this.getlist()
}else if(res.code == 401){
this.isLoading = false // 请求失败时隐藏加载状态
this.bjflag = true
this.jmlogin()
}
}).catch(() => {
this.isLoading = false // 请求异常时隐藏加载状态
})
},
// 获取设备列表
getlist(){
this.$u.get(`/app/device/list?userId=${this.userobj.userId}&classifyCodeList=1`).then((res) => {
if (res.code == 200) {
if(res.data.length > 0 || res.data != ''){
this.bjflag = false
this.devicelist = res.data
this.imgpic = res.data[0].modelPicture
this.mac = res.data[0].mac
this.pre = res.data[0].pre
console.log(this.pre,'454545454545454');
this.user = res.data[0]
this.shebid = res.data[0].deviceId
this.getbanbens()
this.getscjsjl()
setTimeout(()=>{
this.getsj()
},1000)
}else{
this.bjflag = true
}
this.isLoading = false // 请求完成后隐藏加载状态
}else{
this.isLoading = false
}
}).catch(() => {
this.isLoading = false // 请求异常时隐藏加载状态
})
},
// 点击连接蓝牙函数
btnlj() {
// 如果正在连接中,不重复连接
if (this.vardataflag === 2) {
return
}
// 如果已连接,提示用户
if (this.vardataflag === 3) {
uni.showToast({
title: this.$t('index.deviceConnected'),
icon: 'none',
duration: 2000
})
return
}
// 重置错误状态(允许重新检查错误)
this.errorModalShown = false
this.currentErrorType = null
this.shibainum = 0
this.connectionStartTime = Date.now() // 记录连接开始时间
this.connectionTimeout = null // 连接超时定时器
// 先检查蓝牙状态并尝试自动修复
this.checkAndFixBluetooth().then(() => {
// 蓝牙正常,开始连接流程
xBlufi.initXBlufi(1)
xBlufi.listenDeviceMsgEvent(true, this.funListenDeviceMsgEvent)
xBlufi.notifyStartDiscoverBle({
'isStart': true
})
this.vardataflag = 2
// 设置连接超时30秒
this.connectionTimeout = setTimeout(() => {
if (this.vardataflag === 2) {
this.handleConnectionTimeout()
}
}, 30000)
this.findDevice()
}).catch((error) => {
// 蓝牙检查失败,显示错误提示(只显示一次)
if (!this.errorModalShown) {
this.handleConnectionError(error)
}
})
},
// ios递归函数匹配
findDevice() {
let that = this;
const findDevices = () => {
console.log(that.shibainum,'失败次数');
// 如果已经尝试了10次及以上直接返回不执行
if (that.shibainum > 10) {
that.vardataflag = 1
that.clearConnectionTimeout()
xBlufi.notifyStartDiscoverBle({
'isStart': false
});
// 诊断连接失败原因
that.diagnoseConnectionFailure();
return;
}
that.vatatxt = that.$t('index.searching');
const matchedDevice = that.devicesarr.find(device => {
if (device.name) {
return device.name.slice(-12) === that.mac.slice(-12);
}
});
if (matchedDevice) {
// 找到设备的处理逻辑...
that.vatatxt = that.$t('index.pairing');
that.clearConnectionTimeout() // 清除超时定时器
xBlufi.notifyStartDiscoverBle({
'isStart': false
});
console.log(matchedDevice, '找到匹配设备');
xBlufi.notifyConnectBle({
isStart: true,
deviceId: matchedDevice.deviceId,
name: matchedDevice.name
});
that.deviceId = matchedDevice.deviceId;
// 设置连接超时8秒
that.connectTimeout = setTimeout(() => {
if (that.vardataflag !== 3 && !that.errorModalShown) {
that.vardataflag = 1
that.shibainum = 0;
that.handleConnectionError({
type: 'CONNECT_TIMEOUT',
message: that.$t('index.ble.pairTimeout'),
userAction: that.$t('index.ble.pairTimeoutHint'),
canAutoFix: false
})
}
}, 8000);
} else {
// 未找到设备,递增计数器并继续尝试
that.shibainum++;
// 只有在还没到10次时才设置定时器继续查找
if (that.shibainum < 10) {
that.findDeviceTimer = setTimeout(findDevices, 1000);
} else {
that.vardataflag = 1
that.clearConnectionTimeout()
that.shibainum = 0;
// 诊断连接失败原因
that.diagnoseConnectionFailure();
}
}
};
// 重置计数器后开始查找
findDevices();
},
// 获取附近蓝牙设备列表
funListenDeviceMsgEvent: function(options) {
switch (options.type) {
case xBlufi.XBLUFI_TYPE.TYPE_STATUS_CONNECTED:
if (!options.result) {
this.vardataflag = 1
this.clearDisconnectTimer()
this.clearConnectionTimeout()
console.log('设备连接断开', options);
// 如果是在连接过程中断开,且还没显示过错误,才显示提示
if (this.connectionStartTime && (Date.now() - this.connectionStartTime) < 5000 && !this.errorModalShown) {
this.handleConnectionError({
type: 'CONNECTION_DISCONNECTED',
message: this.$t('index.ble.disconnected'),
userAction: this.$t('index.ble.disconnectedHint'),
canAutoFix: false
})
}
}
break;
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS:
if (options.result) {
this.devicesarr = options.data
// console.log('搜索的附近设备列表',this.devicesarr);
}
break;
case xBlufi.XBLUFI_TYPE.TYPE_CONNECTED:
console.log("连接回调:" + JSON.stringify(options))
this.clearConnectionTimeout() // 清除连接超时定时器
xBlufi.notifyInitBleEsp32({
deviceId: this.deviceId
})
if (options.result == true){
console.log('服务发现成功:', options);
// 一连上立刻请求数据推送
this.vardataflag = 3
this.initDisconnectTimer()
this.datalist = ''
// this.startDataTimeoutTimer()
xBlufi.notifySendCustomData({
customData: "11get"
})
// 连接成功提示
uni.showToast({
title: this.$t('index.connectOk'),
icon: 'success',
duration: 2000
})
}else{
// 服务发现失败
this.vardataflag = 1
this.clearDisconnectTimer()
this.clearConnectionTimeout()
this.jiance = true
// 如果还没显示过错误,才显示
if (!this.errorModalShown) {
this.handleConnectionError({
type: 'SERVICE_DISCOVERY_FAILED',
message: this.$t('index.ble.serviceFail'),
userAction: this.$t('index.ble.serviceFailHint'),
canAutoFix: false
})
}
}
break
case xBlufi.XBLUFI_TYPE.TYPE_INIT_ESP32_RESULT:
break
case xBlufi.XBLUFI_TYPE.TYPE_RECIEVE_CUSTON_DATA:
console.log("收到设备数据:", options.data)
if(this.pre == 'SATER'){
// SATER 设备分 3 种类型不定序推送pA(pA1:...)、pB(pB1:...)、ver(ver...@show:...)
// 按类型存最新值,集齐后解析并展示
let chunk = String(options.data || '')
if (chunk) {
chunk = chunk.slice(0, -1)
}
if (!chunk) return
if (!chunk.endsWith(';')) {
chunk += ';'
}
// 识别类型并更新该类型的最新数据
if (chunk.includes('pA1:')) {
this.saterDataByType.pA = chunk
} else if (chunk.includes('pB1:')) {
this.saterDataByType.pB = chunk
} else if (chunk.startsWith('ver')) {
this.saterDataByType.ver = chunk
} else {
return
}
const { pA, pB, ver } = this.saterDataByType
if (!pA || !pB || !ver) {
return
}
this.saterRawPayload = [ver, pA, pB].join('')
this.datalist = this.saterRawPayload
this.clearDataTimeoutTimer()
this.vardataflag = 3
this.grisfjx()
return
}
this.saterDataByType = { pA: '', pB: '', ver: '' }
this.saterRawPayload = ''
if (options.data.indexOf("prom:") !== -1) {
// console.log('固件升级中')
var indexOld = options.data.substring(options.data.indexOf('prom:'))
// console.log("indexOld", indexOld);
var load_num = indexOld.substring(
indexOld.indexOf("prom:") + 5,
indexOld.indexOf("@")
)
this.progress = Number(load_num);
// console.log("load_num", load_num);
console.log("升级进度:", this.progress)
if (this.progress === 6000) {
// console.log('固件成功')
this.progress = 100
uni.showToast({
title: this.$t('index.fwOk'),
icon: 'success',
duration: 2000
})
setTimeout(()=>{
this.shengjiflag = false
uni.reLaunch({
url:'/pages/index/index'
})
},2000)
}
if (this.progress === 9000) {
// console.log('固件升级失败')
this.progress = 99
uni.showToast({
title: this.$t('index.fwFail'),
icon: 'none',
duration: 2000
})
setTimeout(()=>{
this.shengjiflag = false
uni.reLaunch({
url:'/pages/index/index'
})
},1000)
}
} else {
this.datalist = options.data.slice(0, -1) + ";"
this.clearDataTimeoutTimer()
this.vardataflag = 3
if(this.pre == 'WATER' || this.pre == 'DATER' || this.pre == 'TATER'){
this.getchuli()
}else if(this.pre == 'SATER'){
this.grisfjx()
}else{
this.getcsbshuju()
}
}
break
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_START:
if (!options.result) {
this.vardataflag = 1
this.jiance = true
this.clearConnectionTimeout()
console.log('蓝牙搜索启动失败', options)
// 如果还没显示过错误,才显示
if (!this.errorModalShown) {
this.handleConnectionError({
type: 'SEARCH_START_FAILED',
message: this.$t('index.ble.searchStartFail'),
userAction: this.$t('index.ble.searchStartFailHint'),
canAutoFix: false
})
}
return
}
break
}
},
// 雨水感应开启
btnyushui() {
this.handleUserAction()
if (this.isRainSensorLocked) {
this.one = true
this.ones = false
uni.showToast({
title: this.$t('index.rainWait'),
icon: 'none',
duration: 2000
})
return
}
this.one = false
this.ones = true
if(this.vardataflag != 3){
uni.showLoading({
title: this.$t('index.opening')
})
this.$u.post(`/app/device/yudi/${this.shebid}`).then(res => {
if (res.code == 200) {
uni.hideLoading()
this.yschecked = false
uni.showToast({
title: this.$t('index.openOk'),
icon: 'success',
duration:2000
})
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
}).catch(error => {
uni.hideLoading() // 务必隐藏加载提示,避免一直显示
const errorMsg = error.message || '';
const isNetworkError = errorMsg.includes('Network Error') ||
errorMsg.includes('网络') ||
errorMsg.includes('超时') ||
errorMsg.includes('fail') || // 小程序请求失败的核心关键词
errorMsg.includes('request:fail');
uni.showToast({
title: isNetworkError ? this.$t('common.networkErrorAlt') : this.$t('common.requestFail'),
icon: 'none',
duration: 5000
})
// 可选:打印错误日志,方便调试
console.error('请求异常:', error)
})
}else{
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=yudi`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: this.$t('index.opening')
})
this.yschecked = false
xBlufi.notifySendCustomData({
customData: '11' + res.data
})
uni.hideLoading()
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
// 点击同步时间
btntb(){
this.handleUserAction()
if(this.vardataflag != 3){
uni.showLoading({
title: this.$t('index.setting')
})
this.devicetime = this.getCurrentTime();
console.log(this.devicetime);
let data = {
deviceId:this.shebid,
time:this.devicetime,
}
this.$u.post(`/app/device/syncTime`,data).then(res => {
if (res.code == 200) {
uni.hideLoading()
uni.showToast({
title: this.$t('index.syncOk'),
icon: 'success',
duration:2000
})
}else{
uni.hideLoading()
this.devicetime = '--'
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}else{
this.devicetime = this.getCurrentTime();
console.log(this.devicetime);
xBlufi.notifySendCustomData({
customData: '11date' + this.devicetime + ':' + '00' + ';'
})
// if(this.pre == 'WATER'){ //单阀
// this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=syncTime`).then(res =>{
// if(res.code == 200){
// uni.showLoading({
// title: this.$t('index.setting')
// })
// xBlufi.notifySendCustomData({
// customData: '11' + this.format(res.data, {date: this.devicetime + ':' + '00' + ';'})
// })
// uni.hideLoading()
// }else{
// uni.hideLoading()
// uni.showToast({
// title: res.msg,
// icon: 'none',
// duration:2000
// })
// }
// })
}
},
// 点击显示设置时间
btnsztime(){
this.handleUserAction()
this.timeflag = true
},
getCurrentTime() {
const now = new Date();
const padZero = num => num.toString().padStart(2, '0');
const hours = padZero(now.getHours()); // 时 (00-23)
const minutes = padZero(now.getMinutes()); // 分 (00-59)
const seconds = padZero(now.getSeconds()); // 秒 (00-59)
return `${hours}:${minutes}`;
},
// 点击设置当前时间
confirmtime(e){
this.devicetime = e.hour + ':' + e.minute
console.log(this.devicetime);
if(this.vardataflag == 3){
xBlufi.notifySendCustomData({
customData: '11date' + this.devicetime + ':' + '00' + ';'
})
// this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=syncTime`).then(res =>{
// if(res.code == 200){
// uni.showLoading({
// title: this.$t('index.setting')
// })
// xBlufi.notifySendCustomData({
// customData: '11' + this.format(res.data, {date: this.devicetime + ':' + '00' + ';'})
// })
// uni.hideLoading()
// }else{
// uni.hideLoading()
// uni.showToast({
// title: res.msg,
// icon: 'none',
// duration:2000
// })
// }
// })
}else{
uni.showLoading({
title: this.$t('index.setting')
})
this.devicetime = e.hour + ':' + e.minute
let data = {
deviceId:this.shebid,
time:this.devicetime,
}
this.$u.post(`/app/device/syncTime`,data).then(res => {
if (res.code == 200) {
uni.hideLoading()
uni.showToast({
title: this.$t('index.setOk'),
icon: 'success',
duration:2000
})
}else{
this.devicetime = '--'
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
// 雨水感应关闭
btnyushuis() {
this.handleUserAction()
if (this.isRainSensorLocked) {
this.one = false
this.ones = false
uni.showToast({
title: this.$t('index.rainWait'),
icon: 'none',
duration: 2000
})
return
}
this.one = false
this.ones = true
if(this.vardataflag != 3){
uni.showLoading({
title: this.$t('index.closing')
})
let data = {
deviceId:this.shebid,
instructionKey:'unyudi',
}
this.$u.post(`/app/device/unyudi/${this.shebid}`).then(res => {
if (res.code == 200) {
uni.hideLoading()
this.yschecked = true
uni.showToast({
title: this.$t('index.closeOk'),
icon: 'success',
duration:2000
})
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}else{
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=unyudi`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: this.$t('index.closing')
})
this.yschecked = true
xBlufi.notifySendCustomData({
customData: '11' + res.data
})
uni.hideLoading()
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
// 点击儿童锁开启
btnertong() {
this.handleUserAction()
this.two = false
this.twos = true
if(this.vardataflag != 3){
uni.showLoading({
title: this.$t('index.opening')
})
// let data = {
// deviceId:this.shebid,
// instructionKey:'lock',
// }
this.$u.post(`/app/device/lock/${this.shebid}`).then(res => {
if (res.code == 200) {
uni.hideLoading()
this.etchecked = false
uni.showToast({
title: this.$t('index.openOk'),
icon: 'success',
duration:2000
})
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}else{
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=lock`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: this.$t('index.opening')
})
this.etchecked = false
xBlufi.notifySendCustomData({
customData: '11' + res.data
})
uni.hideLoading()
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
// 点击儿童锁关闭
btnertongs() {
this.handleUserAction()
this.two = false
this.twos = true
if(this.vardataflag != 3){
uni.showLoading({
title: this.$t('index.closing')
})
// let data = {
// deviceId:this.shebid,
// instructionKey:'unlock',
// }
this.$u.post(`/app/device/unlock/${this.shebid}`).then(res => {
if (res.code == 200) {
uni.hideLoading()
this.etchecked = true
uni.showToast({
title: this.$t('index.closeOk'),
icon: 'success',
duration:2000
})
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}else{
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=unlock`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: this.$t('index.closing')
})
this.etchecked = true
xBlufi.notifySendCustomData({
customData: '11' + res.data
})
uni.hideLoading()
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
// 点击添加设备
btnaddsb() {
this.$u.get(`/system/user/profile`).then((res) => {
if (res.code == 200) {
uni.setStorageSync('user',res.data)
uni.setStorageSync('userId',res.data.userId)
this.vardataflag = 1
this.ver_data = {}
this.datalist = ''
uni.navigateTo({
url: '/page_user/lanya'
})
}else if(res.code == 401){
// this.jmlogin()
uni.showModal({
title: this.$t('common.tip'),
content: this.$t('common.promptLogin'),
success: function (res) {
if (res.confirm) {
uni.navigateTo({
url:'/pages/login/login'
})
} else if (res.cancel) {
}
}
})
}
})
},
// 点击选择浇水时间
confirm(e) {
this.handleUserAction()
this.minute = e.minute
this.second = e.second
this.jstime = Number(e.second) + Number(e.minute * 60)
},
// 点击跳转到定时页面
btntime() {
this.handleUserAction()
if(this.vardataflag != 3){
let url = '/page_user/dingshi?pre=' + this.pre + '&shebid=' + this.shebid
if (this.pre === 'SATER') {
url += '&channel=' + this.stareChannel
}
uni.navigateTo({ url })
}else{
if (this.pre === 'SATER') {
const saterRaw = this.saterRawPayload || this.datalist || ''
const list = encodeURIComponent(JSON.stringify(this.ver_data || {}))
const raw = encodeURIComponent(saterRaw)
uni.navigateTo({
url: '/page_user/dingshi?list=' + list + '&raw=' + raw + '&pre=' + this.pre + '&channel=' + this.stareChannel
})
return
}
uni.navigateTo({
url: '/page_user/dingshi?list=' + JSON.stringify(this.ver_data) + '&pre=' + this.pre
})
}
},
setStareChannel(ch) {
this.stareChannel = ch === 'B' ? 'B' : 'A'
if (this.pre === 'SATER') {
if (this.saterRawPayload) {
this.grisfjx()
} else if (this.vardataflag != 3 && (Object.keys(this.saterListA || {}).length || Object.keys(this.saterListB || {}).length)) {
const target = this.stareChannel === 'B' ? this.saterListB : this.saterListA
this.ver_data = this.normalizeVerDataSwitch(target) || {}
this.calculateNextWateringTime()
}
}
},
// 点击取消手动浇水
btnqx() {
this.handleUserAction()
this.sdjsflag = false
this.sdminutes = ''
this.sdseconds = ''
this.kgflag = true
this.jsked = true
this.jskeds = false
this.rainSensorLockedByWatering = false
},
// 替换
format(template, params) {
return template.replace(/\{(\w+)\}/g, (_, k) => params[k] ?? '');
},
// 手动浇水记录
getjl(){
let that = this
let data = {
deviceId:that.shebid,
sprayingTime:that.jstime
}
that.$u.post(`/app/wateringRecord`,data).then(res =>{})
},
// 请求上次浇水记录
getscjsjl(){
this.$u.get(`/app/wateringRecord/last/${this.shebid}`).then(res =>{
if(res.code == 200){
if(res.data){
this.jsobj = res.data
}else{
this.jsobj = {}
}
}
})
},
// 确定选择时间
btnqd() {
this.handleUserAction()
if (this.isManualWaterRequesting) {
uni.showToast({
title: this.$t('index.waitReq'),
icon: 'none',
duration: 2000
})
return
}
if (this.minute == '--' || this.second == '--') {
uni.showToast({
title: this.$t('index.pickWaterDur'),
icon: 'none',
duration: 3000
})
return
}
// 点击确定那一刻立即锁死雨水感应,不等请求返回
this.rainSensorLockedByWatering = true
this.isManualWaterRequesting = true
if(this.vardataflag == 3){
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=time`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: this.$t('index.opening')
})
let cmd = this.format(res.data, {time: this.jstime})
// SATER命令后追加 A/B例如 timeA10
if (this.pre === 'SATER') {
cmd = String(cmd).replace(/^time/, 'time' + this.stareChannel)
}
xBlufi.notifySendCustomData({
customData: '11' + cmd + '@'
})
this.getjl()
uni.hideLoading()
this.startTimer(this.jstime)
this.sdjsflag = false
}else{
this.rainSensorLockedByWatering = false
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
}).catch(() => {
this.rainSensorLockedByWatering = false
uni.hideLoading()
uni.showToast({
title: this.$t('common.networkError'),
icon: 'none',
duration: 2000
})
}).finally(() => {
this.isManualWaterRequesting = false
})
}else{
let data = {
deviceId:this.shebid,
second:this.jstime,
}
this.$u.post(`/app/device/time`,data).then(res => {
if (res.code == 200) {
uni.showToast({
title: this.$t('index.openOk'),
icon: 'success',
duration:2000
})
this.getjl()
uni.hideLoading()
this.startTimer(this.jstime)
this.sdjsflag = false
}else{
this.rainSensorLockedByWatering = false
this.kgflag = true
this.jsked = false
this.jskeds = false
uni.hideLoading()
this.sdjsflag = false
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
}).catch(() => {
this.rainSensorLockedByWatering = false
uni.hideLoading()
uni.showToast({
title: this.$t('common.networkError'),
icon: 'none',
duration: 2000
})
}).finally(() => {
this.isManualWaterRequesting = false
})
}
},
// 手动浇水定时器
startTimer(totalSeconds) {
let remainingSeconds = totalSeconds
const sdminutes = ''
const sdseconds = ''
this.timer = setInterval(() => {
// 计算分钟和秒
const minutes = Math.floor(remainingSeconds / 60).toString()
const secs = (remainingSeconds % 60).toString()
// 格式化秒和分钟,确保是两位数
this.sdseconds = secs.padStart(2, '0')
this.sdminutes = (minutes > 0 ? minutes : '').padStart(2, '0') // 如果分钟为0则不显示分钟
// 更新剩余秒数
remainingSeconds--
// 如果剩余秒数为0则停止定时器
if (remainingSeconds <= 0) {
clearInterval(this.timer)
// 定时结束:清空计时、自动关闭浇水开关、解锁雨水感应
setTimeout(()=>{
this.sdminutes = ''
this.sdseconds = ''
this.kgflag = true
this.jsked = true
this.jskeds = false
this.rainSensorLockedByWatering = false
},1000)
}
}, 1000)
},
// 启动浇水倒计时定时器
startWateringCountdownTimer(totalSeconds) {
// 启动新倒计时前,先清理旧的 interval 和延迟清理 timeout避免出现负数/串台
this.clearWateringCountdownTimer()
let remainingSeconds = Math.max(0, Math.floor(Number(totalSeconds) || 0))
if (remainingSeconds <= 0) {
this.wateringCountdownMinutes = ''
this.wateringCountdownSeconds = ''
this.showWateringCountdown = false
return
}
const updateDisplay = (secsLeft) => {
const safe = Math.max(0, Math.floor(Number(secsLeft) || 0))
const minutes = Math.floor(safe / 60).toString()
const secs = (safe % 60).toString()
this.wateringCountdownSeconds = secs.padStart(2, '0')
this.wateringCountdownMinutes = (minutes > 0 ? minutes : '').padStart(2, '0')
}
// 先设置初始值
updateDisplay(remainingSeconds)
this.wateringCountdownTimer = setInterval(() => {
// 先减少再渲染避免“多显示1秒”并确保永不小于0
remainingSeconds = Math.max(0, remainingSeconds - 1)
updateDisplay(remainingSeconds)
// 到0立刻停止且用受控的 timeout 延迟清理展示(避免负数和旧 timeout 影响新倒计时)
if (remainingSeconds === 0) {
clearInterval(this.wateringCountdownTimer)
this.wateringCountdownTimer = null
this.wateringCountdownResetTimer = setTimeout(() => {
this.wateringCountdownMinutes = ''
this.wateringCountdownSeconds = ''
this.showWateringCountdown = false
this.rainSensorLockedByWatering = false
this.wateringCountdownResetTimer = null
}, 1000)
}
}, 1000)
},
// 清除浇水倒计时定时器
clearWateringCountdownTimer() {
if (this.wateringCountdownResetTimer) {
clearTimeout(this.wateringCountdownResetTimer)
this.wateringCountdownResetTimer = null
}
if (this.wateringCountdownTimer) {
clearInterval(this.wateringCountdownTimer)
this.wateringCountdownTimer = null
}
},
// 计算信号强度格数根据RSSI值
calculateSignalStrength(rssi) {
if (rssi === undefined || rssi === null) {
return 0
}
// 转换为数字
const rssiValue = Number(rssi)
// 0 到 -504格最强
if (rssiValue >= -50) {
return 4
}
// -50 到 -703格
if (rssiValue >= -70) {
return 3
}
// -70 到 -902格
if (rssiValue >= -90) {
return 2
}
// -90 以下1格
return 1
},
// 关闭手动浇水
btnkq() {
this.handleUserAction()
// 防频繁点击(包含“确定”请求进行中、开关请求进行中)
// if (this.isManualWaterRequesting || this.isManualWaterSwitchRequesting) {
// // 回滚开关状态(当前是“关闭”动作,阻止时保持开启)
// this.jsked = true
// uni.showToast({
// title: this.$t('index.noTap'),
// icon: 'none',
// duration: 2000
// })
// return
// }
console.log('关闭')
// 蓝牙模式每次点击后至少冷却3秒
if (this.vardataflag == 3) {
const now = Date.now()
if (now < this.manualWaterBtCooldownUntil) {
this.jsked = true
uni.showToast({
title: this.$t('index.noTap'),
icon: 'none',
duration: 2000
})
return
}
this.manualWaterBtCooldownUntil = now + 3000
this.isManualWaterSwitchRequesting = true
const unlockAt = this.manualWaterBtCooldownUntil
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=close`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: this.$t('index.closing')
})
xBlufi.notifySendCustomData({
customData: "11" + res.data
})
clearInterval(this.timer)
this.sdminutes = ''
this.sdseconds = ''
this.kgflag = true
this.jsked = true
this.jskeds = false
this.rainSensorLockedByWatering = false
uni.hideLoading()
}else{
// 回滚开关(关闭失败则保持开启)
this.jsked = true
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
}).catch(() => {
// 回滚开关(关闭失败则保持开启)
this.jsked = true
uni.hideLoading()
uni.showToast({
title: this.$t('common.networkError'),
icon: 'none',
duration: 2000
})
}).finally(() => {
const delay = Math.max(0, unlockAt - Date.now())
setTimeout(() => {
this.isManualWaterSwitchRequesting = false
}, delay)
})
return
}
// 接口模式:等待请求结束后才能再次点击
this.isManualWaterSwitchRequesting = true
if(this.vardataflag != 3){
uni.showLoading({
title: this.$t('index.closing')
})
// let data = {
// deviceId:this.shebid,
// instructionKey:'close',
// }
this.$u.post(`/app/device/close/${this.shebid}`).then(res => {
if (res.code == 200) {
uni.showToast({
title: this.$t('index.closeOk'),
icon: 'success',
duration:2000
})
clearInterval(this.timer)
uni.hideLoading()
this.sdminutes = ''
this.sdseconds = ''
this.kgflag = true
this.jsked = true
this.jskeds = false
this.rainSensorLockedByWatering = false
}else{
// 回滚开关(关闭失败则保持开启)
this.jsked = true
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
}).catch(() => {
// 回滚开关(关闭失败则保持开启)
this.jsked = true
uni.hideLoading()
uni.showToast({
title: this.$t('common.networkError'),
icon: 'none',
duration: 2000
})
}).finally(() => {
this.isManualWaterSwitchRequesting = false
})
}
},
// 开启手动浇水
btngb() {
this.handleUserAction()
// 防频繁点击(包含“确定”请求进行中、开关请求进行中)
if (this.isManualWaterRequesting || this.isManualWaterSwitchRequesting) {
// 回滚开关状态(当前是“开启”动作,阻止时保持关闭)
this.jskeds = false
uni.showToast({
title: this.$t('index.noTap'),
icon: 'none',
duration: 2000
})
return
}
// 蓝牙模式每次点击后至少冷却3秒
if (this.vardataflag == 3) {
const now = Date.now()
if (now < this.manualWaterBtCooldownUntil) {
this.jskeds = false
uni.showToast({
title: this.$t('index.noTap'),
icon: 'none',
duration: 2000
})
return
}
this.manualWaterBtCooldownUntil = now + 3000
this.isManualWaterSwitchRequesting = true
const unlockAt = this.manualWaterBtCooldownUntil
setTimeout(() => {
// 到时间自动解锁
if (Date.now() >= unlockAt) this.isManualWaterSwitchRequesting = false
}, 3000)
}
console.log('开启')
this.sdjsflag = true
this.kgflag = false
},
// 跳转到设置
btnsz() {
xBlufi.listenDeviceMsgEvent(false, this.funListenDeviceMsgEvent)
if(this.csbobj.day){
this.xctime = '111'
}
uni.navigateTo({
url: '/page_user/upload?deviceid=' + this.shebid + '&ver=' + this.ver + '&xctime=' + this.xctime + '&xipin=' + this.xinp + '&img=' + this.imgpic + '&varflag=' + this.vardataflag
})
},
// 选择设备
btnactive(deviceId,index) {
// wx.closeBLEConnection({
// deviceId: this.mac,
// })
// xBlufi.notifyConnectBle({
// isStart: false,
// deviceId: this.deviceId,
// name: this.name
// })
this.isLoading = true
this.shebid = deviceId
this.zaixianobj = {}
this.ver = ''
this.jsobj = {}
this.btnxuanze()
},
// 选择确定设备 shebid
btnxuanze() {
this.$u.put(`/app/toggleDevice?userId=${this.userobj.userId}&deviceId=${this.shebid}`).then(res => {
if(res.code == 200){
this.csbobj = {}
this.datalist = ''
this.mac = ''
this.ver_data = {}
this.name = ''
this.deviceId = ''
this.xuanzeflag = false
this.vardataflag = 1
this.sdseconds = ''
this.sdminutes = ''
this.kgflag = true
this.jsked = true
this.jskeds = false
this.getshebxq()
}else{
this.isLoading = false
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
})
}
})
},
// 刷新按钮点击(避免编译成 e0 等匿名方法导致小程序报错)
onRefreshClick() {
if (this.isRefreshing) return
this.qiehuan = 1
this.getshebxq()
},
// 获取设备信息
getshebxq(){
if (this.isRefreshing) return
this.isRefreshing = true
const done = () => {
setTimeout(() => {
this.isLoading = false
this.isRefreshing = false
if(this.qiehuan == 1){
uni.showToast({
title: this.$t('index.refreshOk'),
icon: 'success',
duration:5000
})
}else{
uni.showToast({
title: this.$t('index.switchOk'),
icon: 'success',
duration: 5000
})
this.fullPageRefresh()
}
this.qiehuan = ''
}, 500)
}
this.$u.get(`/app/device/getDetail?deviceId=${this.shebid}`).then(res => {
if (res.code == 200) {
this.zaixianobj = res.data.iotData
this.modelId = res.data.modelId
console.log(this.vardataflag,this.zaixianobj);
// 非蓝牙模式时,用后台数据组装渲染结构
if((this.pre === 'WATER' || this.pre === 'SATER') && this.vardataflag != 3 || this.pre === 'TATER' && this.vardataflag != 3 || this.pre === 'DATER' && this.vardataflag != 3){
this.ver_data = this.buildVerDataFromBackend(this.zaixianobj)
this.calculateNextWateringTime()
}
if(this.pre === 'SMSJ:' && this.vardataflag != 3){
this.ver_data = this.buildVerDataFromBackend(this.zaixianobj)
// this.ver_data = this.zaixianobj
console.log(this.ver_data,'55555555555prprprprprprprpr');
}
if (res.data && res.data.iotData && res.data.iotData.xinp !== undefined) {
this.xinp = res.data.iotData.xinp.value == undefined ? '--' : res.data.iotData.xinp.value
}
if (res.data && res.data.iotData && res.data.iotData.yudi !== undefined) {
if (!this.isRainSensorLocked) {
this.yschecked = res.data.iotData.yudi.value == 0 ? true : false
}
}
if (res.data && res.data.iotData && res.data.iotData.lock !== undefined) {
this.etchecked = res.data.iotData.lock.value == 0 ? true : false
}
// 优先从 nt.value 获取总秒数,如果没有则从 time.value 获取
let ntValue = res?.data?.iotData?.nt?.value;
if (ntValue !== undefined && ntValue !== null && ntValue !== '') {
// 从总秒数转换为时间格式
this.devicetime = this.secondsToTime(ntValue);
} else {
// 兼容旧数据,从 time.value 获取
let originalValue = (res?.data?.iotData?.time?.value ?? "").toString().trim();
// 去掉首尾可能多出来的英文双引号 "
originalValue = originalValue.replace(/^"|"$/g, '');
this.devicetime = originalValue || "--";
}
console.log('devicetime:', this.devicetime);
// 获取信号强度
if (res.data && res.data.iotData && res.data.iotData.RSSI !== undefined && res.data.iotData.RSSI.value !== undefined) {
this.signalStrength = this.calculateSignalStrength(res.data.iotData.RSSI.value)
} else {
this.signalStrength = 0
}
// 取值
this.onlineTime = res.data.onlineTime
this.onlineStatus = res.data.onlineStatus
if(res.data.iotData && res.data.iotData.bat !== undefined && res.data.iotData.bat.value !== undefined){
this.dianya = res.data.iotData.bat.value
this.getdianliang()
}
}
done()
}).catch(() => {
this.isLoading = false
done()
})
},
// 点击切换设备
btnksxz() {
if (this.xuanzeflag == true) {
this.xuanzeflag = false
} else {
this.xuanzeflag = true
}
},
//跳转到浇水日志页
btnjs() {
uni.showToast({
title: this.$t('index.featureSoon'),
icon: 'none',
duration:2000
})
// uni.navigateTo({
// url: '/page_user/jiaoshui'
// })
},
// 每隔一时间发送一次获取数据
getshuju() {
const that = this
that.intervalId = setInterval(() => {
if (that.vardataflag == 3) {
that.datalist = ''
xBlufi.notifySendCustomData({
customData: "11get"
})
}
}, 5000)
},
// 清除定时器
clearTimer() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null; // 重置定时器 ID
console.log("定时器已清除");
}
},
// 无闪屏整页刷新:重置状态并重新拉数,替代 uni.reLaunch 避免白屏闪一下
fullPageRefresh() {
// 1. 清除所有定时器
this.clearTimer()
this.clearConnectionTimeout()
this.clearDataTimeoutTimer()
if (this.disconnectTimer) {
clearTimeout(this.disconnectTimer)
this.disconnectTimer = null
}
if (this.searchTimer) {
clearTimeout(this.searchTimer)
this.searchTimer = null
}
if (this.wateringCountdownTimer) {
clearInterval(this.wateringCountdownTimer)
this.wateringCountdownTimer = null
}
if (this.wateringCountdownResetTimer) {
clearTimeout(this.wateringCountdownResetTimer)
this.wateringCountdownResetTimer = null
}
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
// 2. 关闭可能打开的弹窗
this.showErrorModal = false
this.xuanzeflag = false
// 3. 重置 data 到初始状态(不销毁页面,避免闪屏)
const initial = this.$options.data ? this.$options.data.call(this) : {}
Object.keys(initial).forEach(key => {
this[key] = initial[key]
})
// 4. 重新初始化并拉取数据(相当于重新进入页面)
xBlufi.initXBlufi(1)
this.getinfo()
this.getshuju()
},
// 处理抽水泵接收数据
getcsbshuju() {
// 1. 获取原始数据
const inputString = this.datalist;
// 2. 先处理分号(替换为逗号)
const normalizedString = inputString.replace(/;/g, ',');
// 3. 分割版本信息和数据部分
const parts = normalizedString.split(':');
console.log('分割结果:', parts); // 应该输出 ["ver3@p_set0", "11,5,603,99,19,9,0,"]
// 4. 处理数据部分(移除末尾可能的多余逗号)
let dataPart = parts[1];
if (dataPart.endsWith(',')) {
dataPart = dataPart.slice(0, -1);
}
// 5. 重新组合为期望的格式
const result = [parts[0], dataPart];
console.log('最终结果:', result); // ["ver3@p_set0", "11,5,603,99,19,9,0"]
const versionNumber = result[0].match(/ver(\d+)/)?.[1];
const ver = versionNumber ? parseInt(versionNumber) : 0;
this.ver = 'ver' + ver
console.log(this.ver,'ververver');
// 6. 继续您原来的处理逻辑
const timeParts = dataPart.split(',');
console.log(timeParts,'timePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimePartstimeParts');
this.dianya = timeParts[timeParts.length - 1]
this.getdianliang()
this.csbobj = {
hour: String(timeParts[0]).padStart(2, '0'),
minute: String(timeParts[1]).padStart(2, '0'),
second: timeParts[2],
day: timeParts[3]
};
console.log(this.csbobj,'4564654654584654564654654654654654621654652165');
const hours = parseInt(timeParts[4]) || 0;
const minutes = parseInt(timeParts[5]) || 0;
this.devicetime = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
this.ver_data = {...this.csbobj};
// 生成当前时间字符串 yyyy-MM-dd HH:mm:ss
const now = new Date();
const yyyy = now.getFullYear();
const MM = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const HH = String(now.getHours()).padStart(2, '0');
const mm = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const at = `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`;
// 仅在已连接vardataflag==3且距离上次上传≥60秒时推送
console.log(this.ver_data);
if(this.vardataflag == 3){
const nowTs = Date.now();
if(!this.lastUploadTs || (nowTs - this.lastUploadTs) >= 2000){
let data = {
mac: this.mac,
params: {
h: {
value: String(this.ver_data.hour),
at
},
m: {
value: String(this.ver_data.minute),
at
},
t: {
value: String(this.ver_data.second),
at
},
d: {
value: String(this.ver_data.day),
at
},
time: {
value: String(this.devicetime),
at
},
nt:{
value: String(this.timeToSeconds(this.devicetime)),
at
}
}
}
data.params.xinp = { value: String(this.xinp), at }
this.$u.put(`/app/updateDeviceParam`, data).then(res => {}) //上传数据
this.lastUploadTs = nowTs;
}
}
console.log('设备时间:', this.devicetime);
},
// 获取电量
getdianliang(){
const nowTs = Date.now();
if(this.lastPowerTs && (nowTs - this.lastPowerTs) < 20000){
return
}
this.$u.get(`/app/device/remainingPower/${this.shebid}?voltage=${this.dianya / 1000}`).then(res => {
if (res.code == 200) {
this.sydl = res.data
this.lastPowerTs = nowTs
}
})
},
// 处理双阀的浇花器设备
grisfjx(){
const raw = String(this.saterRawPayload || this.datalist || '')
if (!raw) return
let source = raw.replace(/@/g, ';')
if (!source.endsWith(';')) source += ';'
const channel = this.stareChannel === 'B' ? 'B' : 'A'
const channelMap = {}
const pRegex = /p([AB])(\d+):([^;]+)/g
let pMatch
while ((pMatch = pRegex.exec(source)) !== null) {
const ch = pMatch[1]
const idx = Number(pMatch[2])
const val = pMatch[3]
if (ch === channel && idx >= 1 && idx <= 6) {
channelMap[idx] = val
}
}
const showMatch = source.match(/show:([^;]+)/)
const showPair = showMatch ? `show:${showMatch[1]}` : ''
const mergedPairs = []
if (showPair) {
mergedPairs.push(showPair)
}
for (let i = 1; i <= 6; i++) {
const pickedValue = channelMap[i] || '0,0,0,0,0'
mergedPairs.push(`p_set${i}:${pickedValue}`)
}
const rebuilt = `${mergedPairs.join(';')};`
this.datalist = rebuilt
// 复用已有解析流程(开关/倒计时/电量/上传等)
this.getchuli()
},
// 处理从设备接收数据
getchuli() {
const inputString = this.datalist;
// console.log('接收到的数据:', inputString);
// 检查是否有 @ 分隔符
const hasAtSymbol = inputString.includes('@')
let processedString
let currentDay = 0
if (hasAtSymbol) {
// 只分割第一个 @,保留后面的所有内容
const firstAtIndex = inputString.indexOf('@')
this.ver = inputString.substring(0, firstAtIndex) || ''
processedString = inputString.substring(firstAtIndex + 1) || ''
// 解析当前天数
const showMatch = processedString.match(/show:([^;]+)/)
if (showMatch) {
const showValues = showMatch[1].split(',');
currentDay = parseInt(showValues[showValues.length - 2]) || 0
}
} else {
processedString = inputString;
this.ver = '';
}
if(this.ver.length > 6){
this.ver = this.ver.slice(0,6)
}
console.log(this.ver,'ververver');
const version = this.ver;
// 使用正则表达式提取数字部分
const versionNumber = version.match(/\d+/); // 匹配连续的数字
// 检查并删除 @low:0 或 low:0
if (processedString.includes('@low:0') || processedString.includes('low:0')) {
// 删除 @low:0可能在开头或中间
processedString = processedString.replace(/@low:0@?/g, '');
// 删除 low:0@(在开头,后面跟着@
processedString = processedString.replace(/^low:0@/g, '');
// 删除 low:0处理各种位置的情况
processedString = processedString.replace(/^low:0;?/g, ''); // 开头
processedString = processedString.replace(/;low:0;?/g, ';'); // 中间
processedString = processedString.replace(/;low:0$/g, ''); // 结尾
processedString = processedString.replace(/^low:0$/g, ''); // 只有 low:0
// 清理可能出现的连续分号或开头结尾的分号
processedString = processedString.replace(/;;+/g, ';').replace(/^;+|;+$/g, '');
}
const pairs = processedString.split(';').filter(Boolean)
console.log('分割后的数据对:', pairs)
const showObject = {}
const pSetObjects = {}
// 初始化所有p_set为默认值
for (let i = 1; i <= 6; i++) {
pSetObjects[`p_set${i}`] = [0, 0, 0, 0, 0]
}
// 解析字符串
pairs.forEach(pair => {
const [key, value] = pair.split(':')
console.log(key,'555555555555555555555555')
if (key === 'show') {
showObject.showArray = value.split(',').map(Number)
} else if (key.startsWith('p_set')) {
const values = value.split(',').map(Number)
// 确保数组长度为5
while (values.length < 5) {
values.push(0)
}
pSetObjects[key] = values
}
})
this.ver_data = pSetObjects
this.jstimeobj = pSetObjects
this.showobj = showObject
const showArray = (this.showobj && Array.isArray(this.showobj.showArray)) ? this.showobj.showArray : []
console.log(showArray,'this.showobj.showArraythis.showobj.showArraythis.showobj.showArray')
// 设置开关状态(浇水进行中或已点确定未结束时,不更新雨水感应状态)
if(showArray.length >= 2){
if (!this.isRainSensorLocked) {
this.yschecked = showArray[1] == 0 ? true : false
}
this.etchecked = showArray[0] == 0 ? true : false
}
// 获取浇水倒计时第五位索引为4前提是第三位索引2和第四位索引3都必须是1
if(showArray.length > 4){
// 检查第三位索引2和第四位索引3是否都为1
const thirdValue = showArray[2]
const fourthValue = showArray[3]
if(thirdValue === 1 && fourthValue === 1){
// 第三位和第四位都是1才取第五位的值
const countdown = showArray[4]
if(countdown && countdown > 0){
// 清除之前的倒计时定时器
this.clearWateringCountdownTimer()
// 仅更新设备倒计时显示,不改变浇水开关状态(仅用户点击才改变)
this.showWateringCountdown = true
// 启动倒计时
this.startWateringCountdownTimer(countdown)
} else {
// 倒计时为0清除定时器与显示不改变浇水开关状态
this.clearWateringCountdownTimer()
this.wateringCountdownMinutes = ''
this.wateringCountdownSeconds = ''
this.showWateringCountdown = false
}
} else {
// 第三位或第四位不是1清除定时器与显示不改变浇水开关状态
this.clearWateringCountdownTimer()
this.wateringCountdownMinutes = ''
this.wateringCountdownSeconds = ''
this.showWateringCountdown = false
}
} else {
// 数组长度不够,清除定时器与显示,不改变浇水开关状态
this.clearWateringCountdownTimer()
this.wateringCountdownMinutes = ''
this.wateringCountdownSeconds = ''
this.showWateringCountdown = false
}
this.dianya = showArray.length ? showArray[showArray.length - 1] : 0
console.log('this.yscheckedthis.yschecked',this.yschecked,this.etchecked)
this.getdianliang()
this.calculateNextWateringTime(currentDay)
console.log('解析后的数据:', {
ver_data: this.ver_data,
showobj: this.showobj,
nextTime: this.xctimesj,
nextTimeDiff: this.xctime,
prevTime: this.sctimejs
});
if(showArray.length > 9){
this.xinp = showArray[showArray.length - 2]
this.dianya = showArray[showArray.length - 1]
}else{
this.xinp = showArray.length ? showArray[showArray.length - 1] : 0
this.dianya = 0
}
console.log('息屏',this.xinp,'电压',this.dianya);
this.getdianliang()
// 非 WATER 情况也按 { value, at } 结构上传(大量字段统一包装)
if(this.vardataflag == 3){
const now = new Date();
const yyyy = now.getFullYear();
const MM = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const HH = String(now.getHours()).padStart(2, '0');
const mm = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const at = `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`;
const nowTs = Date.now();
if(!this.lastUploadTs || (nowTs - this.lastUploadTs) >= 2000){
const wrapParams = (obj) => {
const out = {};
if(!obj || typeof obj !== 'object') return out;
Object.keys(obj).forEach((key) => {
const raw = obj[key];
const value = (raw && typeof raw === 'object' && 'value' in raw) ? raw.value : raw;
out[key] = { value: String(value == null ? '' : value), at };
});
return out;
}
// 当后台参数对象为空时,从 BLE 解析的 ver_data 构造 h1/m1/s1/o1/d1 ...
const buildParamsFromState = () => {
const params = {}
if(this.ver_data && typeof this.ver_data === 'object'){
for(let i = 1; i <= 6; i++){
const arr = this.ver_data[`p_set${i}`]
if(Array.isArray(arr) && arr.length >= 5){
params[`h${i}`] = arr[0]
params[`m${i}`] = arr[1]
params[`s${i}`] = arr[2]
params[`o${i}`] = arr[3]
params[`d${i}`] = arr[4]
}
}
}
return params
}
// SATER从 saterDataByType 构造 pA、pB 上传参数
const buildSaterParamsFromState = () => {
const params = {}
const { pA, pB } = this.saterDataByType || {}
if (pA) params.pA = pA.replace(/;+$/, '')
if (pB) params.pB = pB.replace(/;+$/, '')
return params
}
// 在 BLE 已连接场景,优先使用当前解析的 ver_datap_set1..p_set6
const hasVerData = (() => {
if(!this.ver_data || typeof this.ver_data !== 'object') return false;
for(let i = 1; i <= 6; i++){
const arr = this.ver_data[`p_set${i}`]
if(Array.isArray(arr) && arr.length >= 2 && (arr[0] > 0 || arr[1] > 0 || arr[2] > 0 || arr[3] > 0)){
return true
}
}
return false
})()
const hasSaterData = this.pre === 'SATER' && this.saterDataByType && (this.saterDataByType.pA || this.saterDataByType.pB)
const yiStr = String(showArray[6] == null ? 0 : showArray[6])
const erStr = String(showArray[7] == null ? 0 : showArray[7])
// console.log('22222222222222222222222222222222222222222222222222222',yiStr,erStr);
// 判断字符串长度是否为1而不是<10
let yi = yiStr.length === 1 ? '0' + yiStr : yiStr
let er = erStr.length === 1 ? '0' + erStr : erStr
// console.log('33333333333333333333333333333333333333333333333333',yi,er);
this.devicetime = yi + ':' + er
const hasBackend = this.zaixianobj && Object.keys(this.zaixianobj).length > 0
const chosenParams = hasSaterData ? buildSaterParamsFromState() : (hasVerData ? buildParamsFromState() : (hasBackend ? this.zaixianobj : {}))
// 合并额外状态字段:仅保留 yudi雨水感应、lock儿童锁
// 按你的要求直接使用布尔:开启=false关闭=true
const mergedParams = { ...chosenParams }
mergedParams.yudi = this.yschecked ? 0 : 1
mergedParams.lock = this.etchecked ? 0 : 1
mergedParams.time = this.devicetime
mergedParams.bat = this.dianya
mergedParams.nt = { value: String(this.timeToSeconds(this.devicetime)),at }
let data = {
mac: this.mac,
params: wrapParams(mergedParams)
}
data.params.xinp = { value: String(this.xinp), at }
this.$u.put(`/app/updateDeviceParam`, data).then(res => {})
this.lastUploadTs = nowTs;
}
}
},
// 初始化断开蓝牙定时器
initDisconnectTimer() {
this.clearDisconnectTimer()
this.disconnectTimer = setTimeout(() => {
}, 600000)
},
// 重置断开蓝牙定时器
resetDisconnectTimer() {
this.initDisconnectTimer()
},
// 清除断开蓝牙定时器
clearDisconnectTimer() {
if(this.disconnectTimer) {
clearTimeout(this.disconnectTimer)
this.disconnectTimer = null
}
},
// 断开蓝牙连接
disconnectBluetooth() {
if(this.deviceId) {
wx.closeBLEConnection({
deviceId: this.deviceId,
success: () => {
console.log('蓝牙连接已断开')
this.vardataflag = 1
this.datalist = ''
this.saterDataByType = { pA: '', pB: '', ver: '' }
this.saterRawPayload = ''
this.clearDataTimeoutTimer()
uni.showToast({
title: this.$t('index.bleDisconnected'),
icon: 'none',
duration: 2000
})
}
})
xBlufi.notifyConnectBle({
isStart: false,
deviceId: this.deviceId,
name: this.name
})
}
},
// 清理5秒数据看门狗
clearDataTimeoutTimer() {
if (this.dataTimeoutTimer) {
clearTimeout(this.dataTimeoutTimer)
this.dataTimeoutTimer = null
}
},
// 监听用户操作的方法
handleUserAction() {
if(this.vardataflag == 3) {
this.resetDisconnectTimer()
}
},
// 获取时间显示
getTimeDisplay(setKey) {
const set = this.ver_data[setKey];
if (set && set[0] !== undefined) {
return `${set[0].toString().padStart(2, '0')}:${set[1].toString().padStart(2, '0')}`;
}
return '--:--';
},
// 判断是否是当前时间
isCurrentTime(setKey) {
const set = this.ver_data[setKey];
return set && set[0] == this.xctimesj.slice(0,2) && set[1] == this.xctimesj.slice(-2);
},
// 计算时间差值(分钟)
getTimeDifference(hour, minute) {
if (!this.devicetime) return Infinity;
const [currentHour, currentMinute] = this.devicetime.split(':').map(Number);
const currentTotalMinutes = currentHour * 60 + currentMinute;
const targetTotalMinutes = hour * 60 + minute;
// 计算时间差
let diff = targetTotalMinutes - currentTotalMinutes;
// 如果时间已过加上24小时
if (diff <= 0) {
diff += 1440;
}
return diff;
},
// 判断定时是否开启(兼容 1 和 true
isPsetEnabled(arr) {
return arr && (arr[3] === 1 || arr[3] === true);
},
// 计算下一次浇水时间(用于 xctime、xctimesj、小三角图标等
calculateNextWateringTime(currentDay) {
if (!this.ver_data || typeof this.ver_data !== 'object') return
const day = (currentDay != null && currentDay >= 0) ? currentDay : Math.floor(Date.now() / 86400000)
const calculateTimeDifference = (date1, date2) => {
const diffMs = Math.abs(date1 - date2)
const diffMinutes = Math.floor(diffMs / (1000 * 60))
const diffHours = Math.floor(diffMinutes / 60)
const remainingMinutes = diffMinutes % 60
return { hours: diffHours, minutes: remainingMinutes }
}
const formatTime = (seconds) => {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = seconds % 60
return this.$t('index.durationMinSec', { m: minutes, s: remainingSeconds })
}
const getTimeFromArray = (timeArray) => {
if (!timeArray || timeArray.length < 2) return null
const [hour, minute] = timeArray
const date = new Date()
date.setHours(hour, minute, 0, 0)
return date
}
let nextTime = null
let nextTimeDiff = null
let nextWaterDuration = null
let prevTime = null
for (const key in this.ver_data) {
if (this.ver_data.hasOwnProperty(key) && this.isPsetEnabled(this.ver_data[key])) {
const timeArray = this.ver_data[key]
const timeDate = getTimeFromArray(timeArray)
if (!timeDate) continue
const diff = calculateTimeDifference(timeDate, new Date())
const interval = timeArray[4] || 0
const shouldWaterToday = interval === 0 || (day % Math.max(1, interval) === 0)
if (timeDate > new Date() && shouldWaterToday) {
if (!nextTime || (diff.hours * 60 + diff.minutes < (nextTimeDiff?.hours || 0) * 60 + (nextTimeDiff?.minutes || 0))) {
nextTimeDiff = diff
nextTime = timeDate
nextWaterDuration = formatTime(timeArray[2])
}
} else if (!prevTime || timeDate > prevTime) {
prevTime = timeDate
}
}
}
this.xctime = nextTime ? this.$t('index.timeUntil', { h: nextTimeDiff.hours, m: nextTimeDiff.minutes }) : this.$t('index.noNextGap')
this.xctimesc = nextWaterDuration || this.$t('index.unknownDuration')
this.sctimejs = prevTime ? prevTime.toTimeString().slice(0, 5) : this.$t('index.noLastWater')
this.xctimesj = nextTime ? nextTime.toTimeString().slice(0, 5) : this.$t('index.noNextWater')
},
// 判断是否显示图标(下一次浇水小三角)
shouldShowIcon(pSetIndex) {
const pSet = this.ver_data[`p_set${pSetIndex}`];
if (!pSet || !this.isPsetEnabled(pSet)) return false;
const currentDiff = this.getTimeDifference(pSet[0], pSet[1]);
for (let i = 1; i <= 6; i++) {
const checkSet = this.ver_data[`p_set${i}`];
if (checkSet && this.isPsetEnabled(checkSet)) {
const checkDiff = this.getTimeDifference(checkSet[0], checkSet[1]);
if (checkDiff < currentDiff || (checkDiff === currentDiff && i < pSetIndex)) {
return false;
}
}
}
return true;
},
// 将后台返回的 h1/m1/o1/s1... 整合为 ver_data 结构
// SATER从 pA、pB 解析六组定时;其他型号从 h1/m1/s1/o1/d1 解析
buildVerDataFromBackend(params){
if (this.pre === 'SATER' && (params?.pA || params?.pB)) {
const parsed = this.parseSaterFromApiParams(params)
this.saterListA = parsed.A
this.saterListB = parsed.B
const target = this.stareChannel === 'B' ? parsed.B : parsed.A
return this.normalizeVerDataSwitch(target)
}
const result = {}
for(let i = 1; i <= 6; i++){
const h = Number(params?.[`h${i}`]?.value)
const m = Number(params?.[`m${i}`]?.value)
const s = Number(params?.[`s${i}`]?.value)
const o = Number(params?.[`o${i}`]?.value)
const d = Number(params?.[`d${i}`]?.value)
const hour = isNaN(h) ? 0 : h
const minute = isNaN(m) ? 0 : m
const second = isNaN(s) ? 0 : s
const onoff = isNaN(o) ? 0 : o
const interval = isNaN(d) || d <= 0 ? 1 : d
result[`p_set${i}`] = [hour, minute, second, onoff, interval]
}
return result
},
// SATER从 getDetail 的 pA、pB 解析为六组定时 { A: {p_set1..p_set6}, B: {...} }
parseSaterFromApiParams(params){
const getVal = (obj) => {
if (!obj) return ''
const v = (obj && typeof obj === 'object' && 'value' in obj) ? obj.value : obj
return String(v == null ? '' : v).replace(/^["\s]+|["\s]+$/g, '')
}
const pAStr = getVal(params.pA)
const pBStr = getVal(params.pB)
const raw = (pAStr ? pAStr + ';' : '') + (pBStr || '')
return this.parseSaterRawToAB(raw)
},
parseSaterRawToAB(rawInput){
const raw = String(rawInput || '')
const src = raw.replace(/@/g, ';')
const resultA = {}
const resultB = {}
for (let i = 1; i <= 6; i++) {
resultA[`p_set${i}`] = [0, 0, 0, 0, 0]
resultB[`p_set${i}`] = [0, 0, 0, 0, 0]
}
const reg = /p([AB])(\d+):([^;]+)/g
let m
while ((m = reg.exec(src)) !== null) {
const ch = m[1]
const idx = Number(m[2])
if (!idx || idx < 1 || idx > 6) continue
const nums = m[3].split(',').map(v => {
const n = parseInt(v, 10)
return isNaN(n) ? 0 : n
})
while (nums.length < 5) nums.push(0)
nums[3] = nums[3] === 1
const key = `p_set${idx}`
if (ch === 'A') resultA[key] = nums
if (ch === 'B') resultB[key] = nums
}
return { A: resultA, B: resultB }
},
normalizeVerDataSwitch(obj){
const out = {}
if (!obj || typeof obj !== 'object') return out
Object.keys(obj).forEach((k) => {
const arr = Array.isArray(obj[k]) ? [...obj[k]] : [0, 0, 0, 0, 0]
while (arr.length < 5) arr.push(0)
arr[3] = arr[3] === 1 || arr[3] === true
out[k] = arr
})
return out
},
// 检查并修复蓝牙状态
checkAndFixBluetooth() {
return new Promise((resolve, reject) => {
uni.getBluetoothAdapterState({
success: (res) => {
if (res.available) {
// 蓝牙可用,直接继续
resolve()
} else {
// 蓝牙不可用,尝试重新打开
this.fixBluetoothAdapter().then(resolve).catch(reject)
}
},
fail: (err) => {
// 获取状态失败,尝试重新初始化
this.fixBluetoothAdapter().then(resolve).catch(reject)
}
})
})
},
// 修复蓝牙适配器
fixBluetoothAdapter() {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: () => {
console.log('蓝牙适配器重新初始化成功')
resolve()
},
fail: (err) => {
const errMsg = err.errMsg || ''
let errorType = 'UNKNOWN'
let errorMessage = this.$t('index.ble.initFail')
let canAutoFix = false
let userAction = ''
if (errMsg.includes('auth deny') || errMsg.includes('authorize')) {
errorType = 'PERMISSION_DENIED'
errorMessage = this.$t('index.ble.permDenied')
userAction = this.$t('index.ble.permDeniedHint')
} else if (errMsg.includes('not available') || errMsg.includes('unavailable')) {
errorType = 'BLUETOOTH_UNAVAILABLE'
errorMessage = this.$t('index.ble.btUnavailable')
userAction = this.$t('index.ble.btUnavailableHint')
} else if (errMsg.includes('open fail')) {
errorType = 'OPEN_FAILED'
errorMessage = this.$t('index.ble.openFail')
userAction = this.$t('index.ble.openFailHint')
} else if (errMsg.includes('system permission denied')) {
errorType = 'SYSTEM_PERMISSION_DENIED'
errorMessage = this.$t('index.ble.sysPerm')
userAction = this.$t('index.ble.sysPermHint')
} else if (errMsg.includes('bluetooth service unavailable')) {
errorType = 'SERVICE_UNAVAILABLE'
errorMessage = this.$t('index.ble.svcUnavailable')
userAction = this.$t('index.ble.svcUnavailableHint')
canAutoFix = true
} else if (errMsg.includes('already opened')) {
// 已经打开,可以继续
resolve()
return
}
reject({
type: errorType,
message: errorMessage,
errMsg: errMsg,
canAutoFix: canAutoFix,
userAction: userAction
})
}
})
})
},
// 诊断连接失败原因(按优先级逐个检查,找到第一个错误就停止)
diagnoseConnectionFailure() {
// 如果已经显示过错误弹窗,不再重复诊断
if (this.errorModalShown) {
return
}
// 按优先级检查错误,找到第一个就停止
// 1. 首先检查蓝牙适配器状态
uni.getBluetoothAdapterState({
success: (res) => {
// 2. 检查蓝牙是否可用
if (!res.available) {
// 找到第一个错误:蓝牙不可用
this.handleConnectionError({
type: 'BLUETOOTH_UNAVAILABLE',
message: this.$t('index.ble.btUnavailable'),
userAction: this.$t('index.ble.btUnavailableHint'),
canAutoFix: true
})
return // 找到错误,停止检查
}
// 3. 蓝牙可用,检查是否找到设备
const deviceCount = this.devicesarr ? this.devicesarr.length : 0
if (deviceCount === 0) {
// 找到第二个错误:未搜索到任何设备
this.handleConnectionError({
type: 'NO_DEVICES_FOUND',
message: this.$t('index.ble.noDevices'),
userAction: this.$t('index.ble.noDevicesHint'),
canAutoFix: false
})
return // 找到错误,停止检查
}
// 4. 有设备但找不到目标设备
const matchedDevice = this.devicesarr.find(device => {
if (device.name) {
return device.name.slice(-12) === this.mac.slice(-12)
}
return false
})
if (!matchedDevice) {
// 找到第三个错误:未找到目标设备
this.handleConnectionError({
type: 'TARGET_DEVICE_NOT_FOUND',
message: this.$t('index.ble.targetNotFound', { n: deviceCount }),
userAction: this.$t('index.ble.targetNotFoundHint'),
canAutoFix: false
})
return // 找到错误,停止检查
}
// 如果所有检查都通过但没有连接成功,可能是其他原因
// 这种情况不应该发生,但为了安全起见还是处理一下
if (!this.errorModalShown) {
this.handleConnectionError({
type: 'UNKNOWN_ERROR',
message: this.$t('index.ble.unknown'),
userAction: this.$t('index.ble.unknownHint'),
canAutoFix: false
})
}
},
fail: (err) => {
// 获取状态失败,尝试重新初始化
this.fixBluetoothAdapter().then(() => {
// 修复成功,提示用户重试
if (!this.errorModalShown) {
this.handleConnectionError({
type: 'ADAPTER_ERROR',
message: this.$t('index.ble.adapterFixed'),
userAction: this.$t('index.ble.adapterFixedHint'),
canAutoFix: true,
autoFixed: true
})
}
}).catch((error) => {
// 修复失败,显示错误
if (!this.errorModalShown) {
this.handleConnectionError(error)
}
})
}
})
},
// 处理连接错误(只显示一次)
handleConnectionError(error) {
// 如果已经显示过错误弹窗,不再重复显示
if (this.errorModalShown) {
return
}
const errorInfo = error || {}
const type = errorInfo.type || 'UNKNOWN'
// 如果错误类型相同,不再重复显示
if (this.currentErrorType === type) {
return
}
// 标记错误弹窗已显示
this.errorModalShown = true
this.currentErrorType = type
const message = errorInfo.message || this.$t('index.ble.connectFailDefault')
const userAction = errorInfo.userAction || this.$t('index.ble.retryHint')
const canAutoFix = errorInfo.canAutoFix || false
const autoFixed = errorInfo.autoFixed || false
// 清除连接状态
this.vardataflag = 1
this.clearConnectionTimeout()
xBlufi.notifyStartDiscoverBle({
'isStart': false
})
// 如果已自动修复,显示成功提示
if (autoFixed) {
this.showCustomErrorModal({
title: this.$t('index.fixed'),
message: message,
userAction: userAction,
showCancel: false,
confirmText: this.$t('common.gotIt'),
onConfirm: () => {
// 重置错误状态,允许下次检查
this.errorModalShown = false
this.currentErrorType = null
// 可以自动重试
if (canAutoFix) {
setTimeout(() => {
this.btnlj()
}, 1000)
}
}
})
return
}
// 根据错误类型决定是否可以自动修复
if (canAutoFix && type === 'BLUETOOTH_UNAVAILABLE') {
this.showCustomErrorModal({
title: this.$t('index.connectFail'),
message: message,
userAction: userAction,
showCancel: true,
cancelText: this.$t('common.cancel'),
confirmText: this.$t('common.autoRepair'),
onConfirm: () => {
// 重置错误状态
this.errorModalShown = false
this.currentErrorType = null
// 尝试自动修复
this.fixBluetoothAdapter().then(() => {
uni.showToast({
title: this.$t('index.fixOkRetry'),
icon: 'success',
duration: 2000
})
// 修复成功后,允许重新检查错误
setTimeout(() => {
this.errorModalShown = false
this.currentErrorType = null
}, 2000)
}).catch((err) => {
// 修复失败,重新显示错误(重置状态后)
this.errorModalShown = false
this.currentErrorType = null
this.handleConnectionError(err)
})
}
})
} else {
// 需要用户手动处理
this.showCustomErrorModal({
title: this.$t('index.connectFail'),
message: message,
userAction: userAction,
showCancel: true,
cancelText: this.$t('common.cancel'),
confirmText: this.$t('common.retry'),
onConfirm: () => {
// 重置错误状态,允许下次检查
this.errorModalShown = false
this.currentErrorType = null
// 用户点击重试
setTimeout(() => {
this.btnlj()
}, 500)
}
})
}
},
// 显示自定义错误弹窗
showCustomErrorModal(data) {
this.errorModalData = {
title: data.title || this.$t('common.tip'),
message: data.message || '',
userAction: data.userAction || '',
showCancel: data.showCancel !== undefined ? data.showCancel : true,
cancelText: data.cancelText || this.$t('common.cancel'),
confirmText: data.confirmText || this.$t('common.confirm'),
onConfirm: data.onConfirm || (() => {})
}
this.showErrorModal = true
},
// 关闭错误弹窗
closeErrorModal() {
this.showErrorModal = false
// 重置错误状态
this.errorModalShown = false
this.currentErrorType = null
},
// 处理错误弹窗确认
handleErrorConfirm() {
if (this.errorModalData.onConfirm) {
this.errorModalData.onConfirm()
}
this.showErrorModal = false
// xBlufi.initXBlufi(1)
// xBlufi.listenDeviceMsgEvent(true, this.funListenDeviceMsgEvent)
// xBlufi.notifyStartDiscoverBle({
// 'isStart': true
// })
},
// 处理连接超时
handleConnectionTimeout() {
// 如果已经显示过错误弹窗,不再重复显示
if (this.errorModalShown) {
return
}
this.vardataflag = 1
this.clearConnectionTimeout()
xBlufi.notifyStartDiscoverBle({
'isStart': false
})
const elapsedTime = Date.now() - (this.connectionStartTime || Date.now())
this.handleConnectionError({
type: 'CONNECTION_TIMEOUT',
message: this.$t('index.ble.timeout', { s: Math.floor(elapsedTime / 1000) }),
userAction: this.$t('index.ble.timeoutHint'),
canAutoFix: false
})
},
// 清除连接超时定时器
clearConnectionTimeout() {
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout)
this.connectionTimeout = null
}
if (this.connectTimeout) {
clearTimeout(this.connectTimeout)
this.connectTimeout = null
}
},
}
}
</script>
<style lang="scss">
.dangqian{
width: 100%;
height: 160rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 38rpx 0rpx rgba(128,128,128,0.3);
filter: blur(0px);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 26rpx;
box-sizing: border-box;
margin-top: 24rpx;
border-radius: 20rpx;
image{
width: 78rpx;
height: 78rpx;
}
.cen{
margin-left: 30rpx;
.shen{
font-weight: 600;
font-size: 28rpx;
color: #3D3D3D;
margin-bottom: 8rpx;
display: flex;
text{
width: 80rpx;
height: 40rpx;
background-color: #7FAD76;
color: #fff;
text-align: center;
line-height: 40rpx;
border-radius: 10rpx;
margin-left: 20rpx;
}
}
view{
font-size: 24rpx;
color: #808080;
}
}
.rt{
font-weight: 600;
font-size: 48rpx;
color: #50565A;
}
}
.rotated-image {
transform: rotate(180deg); /* 旋转 180 */
transition: transform 1s ease;
}
.container {
display: flex;
align-items: center;
justify-content: center;
margin-top: 22rpx;
view {
width: 242rpx;
height: 208rpx;
background: #F0F0F0;
border-radius: 24rpx 24rpx 24rpx 24rpx;
text-align: center;
line-height: 208rpx;
font-weight: 600;
font-size: 44rpx;
color: #3D3D3D;
}
text {
font-size: 80rpx;
margin-left: 46rpx;
margin-right: 46rpx;
}
}
.anniu {
display: flex;
justify-content: space-between;
margin-top: 58rpx;
.qx {
width: 278rpx;
height: 80rpx;
border-radius: 10rpx 10rpx 10rpx 10rpx;
border: 2rpx solid #7FAD76;
font-size: 36rpx;
color: #7FAD76;
font-weight: 600;
line-height: 80rpx;
text-align: center;
}
.qd {
width: 278rpx;
height: 80rpx;
background: #7FAD76;
border-radius: 10rpx 10rpx 10rpx 10rpx;
font-size: 36rpx;
color: #fff;
font-weight: 600;
line-height: 80rpx;
text-align: center;
}
}
.selected-time {
margin-top: 20px;
font-size: 20px;
}
#activebor {
border: 1px solid #7FAD76;
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
/* 初始位置稍微在上方 */
}
to {
opacity: 1;
transform: translateY(0);
/* 最终位置 */
}
}
.mask{
width: 100%;
height: 100vh;
background: #3D3D3D;
opacity: 0.2;
position: fixed;
top: 0;
left: 0;
z-index: 1;
}
.manualmask {
width: 100%;
height: 100vh;
background: #3D3D3D;
opacity: 0.2;
position: fixed;
top: 0;
left: 0;
z-index: 99;
}
.manualjs {
width: 678rpx;
max-height: 610rpx;
background: #fff;
border-radius: 24rpx 24rpx 24rpx 24rpx;
position: fixed;
top: 538rpx;
left: 50%;
transform: translateX(-50%);
z-index: 100;
padding: 42rpx 44rpx;
box-sizing: border-box;
.toptit {
font-size: 36rpx;
color: #3D3D3D;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
}
.page {
padding: 0 20rpx;
padding-bottom: 300rpx;
box-sizing: border-box;
.tabsb {
width: 750rpx;
max-height: 95vh;
background: #FFFFFF;
position: fixed;
top: 0;
left: 0;
padding: 0 40rpx;
padding-bottom: 60rpx !important;
box-sizing: border-box;
border-radius: 0rpx 0rpx 50rpx 50rpx;
z-index: 2;
padding-top: 110rpx;
box-sizing: border-box;
animation: fadeInDown .5s ease-out forwards;
.qdan {
width: 100%;
height: 102rpx;
font-size: 48rpx;
color: #FFFFFF;
font-weight: 500;
line-height: 102rpx;
text-align: center;
background-color: #7FAD76;
border-radius: 20rpx;
margin-top: 40rpx;
}
.sblist {
width: 100%;
max-height: 68vh;
overflow: scroll;
margin-top: 50rpx;
.sbist_val {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
margin-bottom: 28rpx;
box-sizing: border-box;
background: #F7F7F7;
border-radius: 20rpx 20rpx 20rpx 20rpx;
border: 1px solid transparent;
.lt {
font-size: 36rpx;
color: #3D3D3D;
font-weight: 600;
}
.rt {
image {
width: 124rpx;
height: 124rpx;
}
}
}
}
}
.sbmask {
width: 100%;
height: 100vh;
background-color: rgb(238, 238, 238);
position: fixed;
top: 400rpx;
left: 0;
opacity: .5;
}
.wusb {
margin-top: 514rpx;
text-align: center;
text {
font-size: 28rpx;
color: #737B80;
}
view {
width: 212rpx;
height: 76rpx;
background: #7FAD76;
border-radius: 38rpx 38rpx 38rpx 38rpx;
border-radius: 50rpx;
font-size: 28rpx;
color: #FFFFFF;
text-align: center;
line-height: 76rpx;
margin: auto;
margin-top: 50rpx;
}
}
.wateringlogbox {
width: 100%;
height: 200rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 38rpx 0rpx rgba(128, 128, 128, 0.3);
filter: blur(0px);
border-radius: 20rpx;
margin-top: 20rpx;
padding: 30rpx;
box-sizing: border-box;
.wateringlogbd {
display: flex;
justify-content: space-between;
.lt {
view {
font-size: 28rpx;
color: #50565A;
margin-top: 10rpx;
}
}
.rt {
image {
width: 114rpx;
height: 114rpx;
}
}
}
.wateringlogtop {
display: flex;
align-items: center;
font-size: 32rpx;
color: #3D3D3D;
font-weight: 600;
image {
width: 40rpx;
height: 40rpx;
margin-right: 14rpx;
}
}
}
.dingshi_he {
margin-top: 24rpx;
width: 350rpx;
height: 194rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 38rpx 0rpx rgba(128, 128, 128, 0.3);
filter: blur(0px);
border-radius: 20rpx;
padding: 26rpx;
box-sizing: border-box;
.naoz {
font-size: 40rpx;
color: #3D3D3D;
font-weight: 600;
display: flex;
justify-content: space-between;
image {
width: 78rpx;
height: 78rpx;
}
}
.dstime {
margin-top: 14rpx;
display: flex;
align-items: center;
width: 100%;
justify-content: space-between;
image {
width: 52rpx;
height: 52rpx;
}
}
}
.switchbox {
margin-top: 24rpx;
display: flex;
justify-content: space-between;
.switch_he {
width: 350rpx;
height: 194rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 38rpx 0rpx rgba(128, 128, 128, 0.3);
filter: blur(0px);
border-radius: 20rpx;
padding: 26rpx;
box-sizing: border-box;
.yushui {
width: 100%;
font-size: 28rpx;
color: #3D3D3D;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
}
image {
width: 78rpx;
height: 78rpx;
}
}
}
.devicebox {
width: 100%;
max-height: 800rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 38rpx 0rpx rgba(128, 128, 128, 0.3);
filter: blur(0px);
margin-top: 22rpx;
border-radius: 20rpx;
padding: 30rpx;
box-sizing: border-box;
.deviceweek {
display: flex;
justify-content: space-between;
margin-top: 20rpx;
.weeklist {
text-align: center;
.kg {
font-size: 24rpx;
color: #999999;
font-weight: 600;
}
.weekday {
font-size: 36rpx;
color: #3D3D3D;
font-weight: 600;
margin-top: 4rpx;
}
.time {
font-size: 28rpx;
color: #3D3D3D;
margin-top: 4rpx;
}
.interval {
font-size: 24rpx;
color: #3D3D3D;
font-weight: 600;
margin-top: 4rpx;
}
.img {
text-align: center;
image {
width: 12rpx;
height: 12rpx;
}
}
}
}
.devicetop {
display: flex;
justify-content: space-between;
align-items: center;
.devicert {
text-align: right;
.shoudong {
display: flex;
image {
width: 64rpx;
height: 32rpx;
margin-right: 8rpx;
}
font-size: 24rpx;
color: #3D3D3D;
}
.shengyu {
font-size: 24rpx;
color: #3D3D3D;
margin-top: 6rpx;
}
}
.devicelt {
display: flex;
.xinghao {
font-size: 24rpx;
color: #3D3D3D;
.one {
font-size: 28rpx !important;
color: #3D3D3D;
font-weight: 600 !important;
margin-top: 6rpx;
}
}
image {
width: 74rpx;
height: 80rpx;
margin-right: 32rpx;
}
}
.signal-bars {
.signal-bar {
transition: background-color 0.3s;
&.active {
background-color: #7FAD76 !important;
}
}
}
}
}
/* 电池电量样式边框包裹背景按百分比填充红黄绿 */
.battery-wrap {
display: flex;
align-items: center;
.battery-box {
position: relative;
width: 46rpx;
height: 24rpx;
line-height: 24rpx;
border: 2rpx solid #b1b1b1;
border-radius: 8rpx;
overflow: hidden;
background: #f5f5f5;
.battery-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
min-width: 0;
border-radius: 8rpx;
transition: width 0.3s ease, background-color 0.3s ease;
/* width: 20rpx ; */
/* height: 24rpx ; */
border: 1px solid #fff;
}
.battery-text {
position: relative;
z-index: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 16rpx;
color: #333;
font-weight: 500;
}
}
.battery-nub {
width: 4rpx;
height: 14rpx;
background: #b1b1b1;
border-radius: 0 4rpx 4rpx 0;
margin-left: 2rpx;
}
}
/* 刷新按钮 */
.refresh-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 12rpx 24rpx;
border: 1rpx solid #7FAD76;
border-radius: 32rpx;
background: rgba(127, 173, 118, 0.08);
font-size: 26rpx;
color: #7FAD76;
&:active {
background: rgba(127, 173, 118, 0.2);
}
&.is-refreshing {
opacity: 0.85;
pointer-events: none;
}
.refresh-icon {
font-size: 28rpx;
line-height: 1;
}
.refresh-text {
font-weight: 500;
}
}
.selectbox {
width: 100%;
margin-top: 22rpx;
display: flex;
justify-content: space-between;
align-items: center;
.shebeiname {
font-size: 36rpx;
color: #333;
font-weight: 600;
display: flex;
align-items: center;
.lj{
color: #e21616;
margin-left: 10rpx;
font-weight: 400;
font-size: 28rpx;
border: 1px solid #e21616;
padding: 4rpx;
box-sizing: border-box;
border-radius: 10rpx;
}
text {
display: inline-block;
width: 14rpx;
height: 14rpx;
background: #7FAD76;
border-radius: 50%;
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(0, 255, 102, 0.9), 0rpx 0rpx 30rpx 0rpx rgba(69, 255, 143, 0.9);
margin-left: 22rpx;
}
}
.shebeiadd {
image {
width: 214rpx;
height: 64rpx;
}
}
.selectname {
font-size: 48rpx;
color: #3D3D3D;
font-weight: 600;
z-index: 2;
image {
width: 32rpx;
height: 26rpx;
margin-left: 14rpx;
}
}
.selectnames {
font-size: 28rpx;
color: #3D3D3D;
border: 1px solid;
padding:0 20rpx;
box-sizing: border-box;
border-radius: 10rpx;
background-color: #7FAD76;
color: #fff;
height: 60rpx;
line-height: 60rpx;
image {
width: 32rpx;
height: 26rpx;
margin-left: 14rpx;
}
}
.selectshezhi {
image {
width: 60rpx;
height: 60rpx;
}
}
}
.bjimg {
width: 100%;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: -1;
}
.title {
box-sizing: border-box;
padding: 0 20rpx;
image {
width: 260rpx;
height: 60rpx;
}
}
.gateway-item {
display: flex;
align-items: end;
// padding: 20rpx;
padding-left: 20rpx;
box-sizing: border-box;
background: #FFFFFF;
.gateway-icon-circle {
width: 60rpx;
height: 60rpx;
background: #E5E5E5;
border-radius: 50%;
// margin-right: 10rpx;
flex-shrink: 0;
}
.gateway-content {
display: flex;
align-items: center;
.gateway-title {
font-size: 20rpx;
color: #333333;
font-weight: 500;
margin-bottom: 2rpx;
}
.gateway-desc {
font-size: 24rpx;
color: #7FAD76;
}
}
}
}
.channel-switch{
position: relative;
width: 100%;
margin-top: 20rpx;
height: 72rpx;
border-radius: 12rpx;
overflow: hidden;
border: 2rpx solid #48893B;
box-sizing: border-box;
}
/* 背景层梯形斜切未选中白色选中绿色 */
.channel-bg{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
}
/* A左梯形右边缘斜上窄下宽 */
.channel-bg.channel-bg-a{
clip-path: polygon(0 0, 48% 0, 52% 100%, 0 100%);
z-index: 1;
}
.channel-bg.channel-bg-a.active{
background: #48893B;
z-index: 2;
}
/* B右梯形左边缘斜 */
.channel-bg.channel-bg-b{
clip-path: polygon(48% 0, 100% 0, 100% 100%, 52% 100%);
z-index: 1;
}
.channel-bg.channel-bg-b.active{
background: #48893B;
z-index: 2;
}
/* 文字层独立定位不被裁剪 */
.channel-text{
position: absolute;
top: 0;
bottom: 0;
width: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 3;
font-size: 28rpx;
font-weight: 600;
color: #48893B;
}
.channel-text.channel-text-a{
left: 0;
}
.channel-text.channel-text-b{
right: 0;
left: auto;
}
.channel-text.active{
color: #fff;
}
/* 激活态底部指示条绿色背景上显白色 */
.channel-indicator{
position: absolute;
bottom: 12rpx;
left: 50%;
transform: translateX(-50%);
width: 36rpx;
height: 6rpx;
border-radius: 3rpx;
background: #fff;
}
/* 加载遮罩层样式 */
.loading-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx;
background-color: #fff;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #666;
text-align: center;
}
/* 自定义错误弹窗样式 */
.error-modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.error-modal-container {
width: 640rpx;
background: #FFFFFF;
border-radius: 24rpx;
overflow: hidden;
animation: slideUp 0.3s ease;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
}
@keyframes slideUp {
from {
transform: translateY(100rpx);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.error-modal-header {
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx 40rpx 30rpx;
border-bottom: 1rpx solid #F0F0F0;
}
.error-icon {
font-size: 48rpx;
margin-right: 16rpx;
}
.error-modal-title {
font-size: 36rpx;
font-weight: 600;
color: #333333;
}
.error-modal-content {
padding: 40rpx;
max-height: 60vh;
overflow-y: auto;
}
.error-message {
font-size: 32rpx;
color: #FF6B6B;
font-weight: 500;
line-height: 1.6;
margin-bottom: 30rpx;
word-break: break-all;
}
.error-action {
background: #F8F9FA;
border-radius: 12rpx;
padding: 24rpx;
border-left: 4rpx solid #7FAD76;
}
.action-label {
font-size: 28rpx;
color: #7FAD76;
font-weight: 600;
display: block;
margin-bottom: 16rpx;
}
.action-text {
font-size: 28rpx;
color: #666666;
line-height: 1.8;
white-space: pre-line;
word-break: break-all;
}
.error-modal-footer {
display: flex;
border-top: 1rpx solid #F0F0F0;
}
.error-btn {
flex: 1;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
}
.error-btn-cancel {
color: #999999;
border-right: 1rpx solid #F0F0F0;
background: #FAFAFA;
}
.error-btn-cancel:active {
background: #F0F0F0;
}
.error-btn-confirm {
color: #FFFFFF;
background: linear-gradient(135deg, #7FAD76 0%, #6A9A65 100%);
font-weight: 600;
}
.error-btn-confirm:active {
background: linear-gradient(135deg, #6A9A65 0%, #5A8A55 100%);
opacity: 0.9;
}
</style>