Sprinkler-app/pages/index/index.vue
2026-01-17 17:37:00 +08:00

3370 lines
99 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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;">
<image src="https://api.ccttiot.com/smartmeter/img/static/uhRjXxrVaNwOCld2fYUl" mode=""></image>
</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">正在加载中...</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>暂无设备,请先绑定设备</text>
<view class="bang" @click="btnaddsb">
绑定
</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;" @click="btnlj" v-if="vardataflag == 1">
<image style="width: 158rpx;height: 40rpx;vertical-align: sub;" src="https://api.ccttiot.com/smartmeter/img/static/uIkBDnclZIqgUbWom2Aw" mode=""></image>
</view>
<view class="lj" v-if="vardataflag == 2">
连接中
</view>
<view class="lj" style="color: #15C55D;border: 1px solid #15C55D;" v-if="vardataflag == 3">
已连接
</view>
<!-- <view class="" style="font-weight: 400;font-size: 24rpx;color: #ccc;margin-left: 10rpx;" v-if="vardataflag == 2">
{{vatatxt}}
</view> -->
</view>
<view class="selectshezhi" style="display: flex;">
<view class="gateway-item" @click="goToGatewayList">
<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 class="gateway-title">网关</view> <image src="https://api.ccttiot.com/smartmeter/img/static/uKpBiM6T9kj4BddzJtDc" style="width: 12rpx;height: 26rpx;margin-left: 10rpx;" mode=""></image>
</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">型号:{{user.model == undefined ? '--' : user.model}}</view>
<view class="">MAC{{user.mac == undefined ? '--' : user.mac}}</view>
</view>
</view>
<view class="" style="font-weight: 400;font-size: 26rpx;margin-left: 20rpx;display: flex;align-items: center;">
<image style="width: 44rpx;height: 44rpx;" src="https://api.ccttiot.com/smartmeter/img/static/u4yJdBWPMHqgqYjZkQKM" mode=""></image>
<view class="">
<view class="" style="font-size: 28rpx;color: #999999;">
剩余电量
</view>
<view class="" style="font-size: 24rpx;color: #48893B;">
{{sydl}}%
</view>
</view>
</view>
</view>
<view class="deviceweek" v-if="pre == 'WATER'" @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">
每{{ver_data.p_set1[4]}}天
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
每天
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #48893B;">
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">
每{{ver_data.p_set1[4]}}天
</view>
<view class="interval" v-else>
每天
</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">
每{{ver_data.p_set2[4]}}天
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
每天
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #48893B;">
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">
每{{ver_data.p_set2[4]}}天
</view>
<view class="interval" v-else>
每天
</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">
每{{ver_data.p_set3[4]}}天
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
每天
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #48893B;">
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">
每{{ver_data.p_set3[4]}}天
</view>
<view class="interval" v-else>
每天
</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">
每{{ver_data.p_set4[4]}}天
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
每天
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #48893B;">
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">
每{{ver_data.p_set4[4]}}天
</view>
<view class="interval" v-else>
每天
</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">
每{{ver_data.p_set5[4]}}天
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
每天
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #48893B;">
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">
每{{ver_data.p_set5[4]}}天
</view>
<view class="interval" v-else>
每天
</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">
每{{ver_data.p_set6[4]}}天
</view>
<view style="color: rgba(0,0,0,.3);" class="interval" v-else>
每天
</view>
</view>
<view class="" v-else>
<view class="kg" style="color: #48893B;">
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">
每{{ver_data.p_set6[4]}}天
</view>
<view class="interval" v-else>
每天
</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>
启动时间
</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>
{{(zaixianobj.h.value === undefined ? '-' : (String(zaixianobj.h.value).length < 2 ? '0' + zaixianobj.h.value : zaixianobj.h.value) + ':') +
(zaixianobj.m.value === undefined ? '-' : (String(zaixianobj.m.value).length < 2 ? '0' + zaixianobj.m.value : zaixianobj.m.value))}}
</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>
工作时长
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-if="csbobj.second">
{{csbobj.second == undefined ? '--' : csbobj.second + '秒'}}
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-else>
{{zaixianobj.t.value == undefined ? '--' : zaixianobj.t.value + '秒'}}
</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>
间隔天数
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-if="csbobj.day">
{{csbobj.day == undefined ? '--' : csbobj.day + '天'}}
</view>
<view class="" style="margin-top:10rpx;font-size: 36rpx;font-weight: 600;" v-else>
{{zaixianobj.d.value == undefined ? '--' : zaixianobj.d.value + '天'}}
</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">
当前时间 <text>同步</text>
</view>
<view class="">
点击可同步当前时间
</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="pre == 'WATER'">
<image src="https://api.ccttiot.com/smartmeter/img/static/u7NwkNOoQYYsvHVMkDlu" mode=""></image>
<view class="yushui">
雨水感应
<u-switch v-if="yschecked" v-model="one" @change="btnyushui" inactive-color="#eee"
active-color="#eee" size="40"></u-switch>
<u-switch v-else v-model="ones" @change="btnyushuis" active-color="#7FAD76"
inactive-color="#7FAD76" size="40"></u-switch>
</view>
</view>
<view class="switch_he" v-if="pre == 'WATER'">
<image src="https://api.ccttiot.com/smartmeter/img/static/u7kd92ocUgDN052nhp4R" mode=""></image>
<view class="yushui">
儿童锁 <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="pre == 'WATER'">6组</text>
</view>
<view class="dstime">
定时管理 <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>{{ sdminutes ? sdminutes + ':' : '' }}{{ sdseconds ? sdseconds : '' }}
</view>
<view class="dstime">
手动浇水 <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="pre == 'WATER'" @click="btnjs">
<view class="wateringlogtop">
<image src="https://api.ccttiot.com/smartmeter/img/static/uGh3pRM7mMRSHdmvtzv3" mode=""></image>
浇水日志
</view>
<view class="wateringlogbd">
<view class="lt" >
<view class="">上次浇水时长:{{jsobj.sprayingTime == undefined ? '--' : jsobj.sprayingTime + 's'}}</view>
<view class="">上次浇水:{{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="pre == 'WATER'" 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;">
保存定时设置
</view>
</view>
<!-- 切换设备 -->
<view class="tabsb" v-if="xuanzeflag">
<view class="selectbox">
<view class="selectname" @click="btnksxz">
切换设备 <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>
<view class="rt">
<image :src="item.modelPicture" mode="aspectFit"></image>
</view>
</view>
</view>
<view class="qdan" @click="btnaddsb">
添加新设备
</view>
</view>
<view class="mask" v-if="xuanzeflag" @click="xuanzeflag = !xuanzeflag"></view>
<!-- 手动浇水 -->
<view class="manualjs" v-if="sdjsflag">
<view class="toptit">
选择浇水时长
</view>
<view class="container" @click="btnshowjs">
<view class="">{{minute}}分</view>
<text>:</text>
<view class="">{{second}}秒</view>
</view>
<view class="anniu">
<view class="qx" @click="btnqx">
取消
</view>
<view class="qd" @click="btnqd">
确定
</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>
<!-- 自定义错误弹窗 -->
<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">处理建议:</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 || '取消'}}
</view>
<view class="error-btn error-btn-confirm" @click="handleErrorConfirm">
{{errorModalData.confirmText || '确定'}}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
var xBlufi = require("@/components/blufi/xBlufi.js")
export default {
data() {
return {
timeflag: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: '',
datalist: '',
devicelist:[],
shebid:'',
user:{},
lastChar:'',
timer:'',
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:'--',
zaixianobj:{},
modelId:'',
jsobj:{},
isLoading: false ,// 设备请求加载状态
vatatxt:'',
sn:'',
onlineTime:'',
onlineStatus:''
}
},
// 分享到好友(会话)
onShareAppMessage: function() {
return {
title: '绿小能',
path: '/pages/index/index'
}
},
// 分享到朋友圈
onShareTimeline: function() {
return {
title: '绿小能',
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: '提示',
content: '绑定成功',
showCancel: false,
confirmText: '知道了'
})
}else if(res.code == 401){
this.jmlogin()
} else{
uni.showModal({
title: '提示',
content: res.msg,
showCancel: false,
confirmText: '知道了'
})
}
})
}
xBlufi.initXBlufi(1)
},
onShow() {
// xBlufi.notifyStartDiscoverBle({
// 'isStart': true
// })
// xBlufi.listenDeviceMsgEvent(true, this.funListenDeviceMsgEvent)
// 请求个人信息
this.getinfo()
this.getshuju() // 页面加载时启动定时器
},
mounted() {
},
onHide() {
// 页面隐藏时清除定时器
this.clearTimer()
this.clearDisconnectTimer()
this.clearDataTimeoutTimer()
this.clearConnectionTimeout()
},
onUnload() {
// 页面卸载时清除定时器
this.clearTimer()
this.clearDisconnectTimer()
this.clearDataTimeoutTimer()
this.clearConnectionTimeout()
},
methods: {
// 跳转到网关列表
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.vardataflag != 3){
this.ver_data = this.buildVerDataFromBackend(this.zaixianobj)
}
if(this.pre === 'pre' && this.vardataflag != 3){
this.ver_data = this.buildVerDataFromBackend(this.zaixianobj)
console.log(this.ver_data);
}
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) {
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
}
this.devicetime = res.data.iotData.time.value
// 取值
this.onlineTime = res.data.onlineTime
this.onlineStatus = res.data.onlineStatus
}
})
},
// 点击保存设置
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: '保存成功',
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: '保存成功',
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){
uni.reLaunch({
url:'/pages/login/login'
})
}
})
}
}
})
},
// 获取用户信息
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.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
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: '设备已连接',
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 = '设备查找中...';
const matchedDevice = that.devicesarr.find(device => {
if (device.name) {
return device.name.slice(-12) === that.mac.slice(-12);
}
});
if (matchedDevice) {
// 找到设备的处理逻辑...
that.vatatxt = '查找成功,配对中...';
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: '设备配对超时',
userAction: '可能原因:\n1. 设备未开机\n2. 设备距离过远\n3. 设备已连接其他设备\n\n处理建议\n1. 确保设备已开机\n2. 靠近设备1米内\n3. 重启设备后重试',
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: '设备连接已断开',
userAction: '可能原因:\n1. 设备距离过远\n2. 设备断电\n3. 连接不稳定\n\n处理建议\n1. 靠近设备重试\n2. 检查设备是否开机\n3. 重新连接',
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: '连接成功',
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: '蓝牙服务发现失败',
userAction: '可能原因:\n1. 设备不支持该服务\n2. 设备故障\n3. 连接不稳定\n\n处理建议\n1. 重启设备后重试\n2. 靠近设备重试\n3. 如问题持续,请联系客服',
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 (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: '固件升级成功',
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: '固件升级失败',
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.getchuli()
}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: '蓝牙搜索启动失败',
userAction: '可能原因:\n1. 蓝牙权限未授权\n2. 蓝牙服务异常\n3. 系统限制\n\n处理建议\n1. 检查蓝牙权限设置\n2. 重启小程序后重试\n3. 重启手机蓝牙后重试',
canAutoFix: false
})
}
return
}
break
}
},
// 雨水感应开启
btnyushui() {
this.handleUserAction()
this.one = false
this.ones = true
if(this.vardataflag != 3){
uni.showLoading({
title: '开启中...'
})
this.$u.post(`/app/device/yudi/${this.shebid}`).then(res => {
if (res.code == 200) {
uni.hideLoading()
this.yschecked = false
uni.showToast({
title: '开启成功',
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=yudi`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: '开启中...'
})
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.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: '同步成功',
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: '设置中...'
// })
// 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: '设置中...'
// })
// 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.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: '设置成功',
icon: 'success',
duration:2000
})
}else{
this.devicetime = '--'
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
// 雨水感应关闭
btnyushuis() {
this.handleUserAction()
this.one = false
this.ones = true
if(this.vardataflag != 3){
uni.showLoading({
title: '关闭中...'
})
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: '关闭成功',
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.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: '开启中...'
})
// 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: '开启成功',
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.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: '关闭中...'
})
// 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: '关闭成功',
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.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()
}
})
},
// 点击选择浇水时间
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){
uni.navigateTo({
url: '/page_user/dingshi?pre=' + this.pre + '&shebid=' + this.shebid
})
}else{
uni.navigateTo({
url: '/page_user/dingshi?list=' + JSON.stringify(this.ver_data) + '&pre=' + this.pre
})
}
},
// 点击取消手动浇水
btnqx() {
this.handleUserAction()
this.sdjsflag = false
this.sdminutes = ''
this.sdseconds = ''
this.kgflag = true
this.jsked = true
this.jskeds = 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.minute == '--' || this.second == '--') {
uni.showToast({
title: '请选择浇水时长',
icon: 'none',
duration: 3000
})
} else if(this.vardataflag == 3){
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=time`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: '开启中...'
})
xBlufi.notifySendCustomData({
customData: '11' + this.format(res.data, {time: this.jstime})
})
this.getjl()
uni.hideLoading()
this.startTimer(this.jstime)
this.sdjsflag = false
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}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: '开启成功',
icon: 'success',
duration:2000
})
this.getjl()
uni.hideLoading()
this.startTimer(this.jstime)
this.sdjsflag = false
}else{
this.kgflag = true
this.jsked = false
this.jskeds = false
uni.hideLoading()
this.sdjsflag = false
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
// 手动浇水定时器
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)
// this.btnkq()
setTimeout(()=>{
this.sdminutes = ''
this.sdseconds = ''
this.kgflag = true
this.jsked = true
this.jskeds = false
},1000)
}
}, 1000)
},
// 关闭手动浇水
btnkq() {
this.handleUserAction()
console.log('关闭')
if(this.vardataflag != 3){
uni.showLoading({
title: '关闭中...'
})
// let data = {
// deviceId:this.shebid,
// instructionKey:'close',
// }
this.$u.post(`/app/device/close/${this.shebid}`).then(res => {
if (res.code == 200) {
uni.showToast({
title: '关闭成功',
icon: 'success',
duration:2000
})
clearInterval(this.timer)
uni.hideLoading()
this.sdminutes = ''
this.sdseconds = ''
this.kgflag = true
this.jsked = true
this.jskeds = false
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}else{
this.$u.get(`/app/getModelCommand/${this.modelId}?instructionKey=close`).then(res =>{
if(res.code == 200){
uni.showLoading({
title: '关闭中...'
})
xBlufi.notifySendCustomData({
customData: "11" + res.data
})
clearInterval(this.timer)
this.sdminutes = ''
this.sdseconds = ''
this.kgflag = true
this.jsked = true
this.jskeds = false
uni.hideLoading()
}else{
uni.hideLoading()
uni.showToast({
title: res.msg,
icon: 'none',
duration:2000
})
}
})
}
},
// 开启手动浇水
btngb() {
this.handleUserAction()
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){
uni.showToast({
title: res.msg,
icon: 'success',
duration: 2000
})
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
})
}
})
},
// 获取设备信息
getshebxq(){
this.$u.get(`/app/device/getDetail?deviceId=${this.shebid}`).then(res => {
if(res.code == 200){
this.pre = res.data.pre
this.mac = res.data.mac
this.modelId = res.data.modelId
this.imgpic = res.data.modelPicture
this.user = res.data
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
this.isLoading = false
console.log("当前页面路径:", currentPage.route);
console.log("当前页面参数:", currentPage.options);
wx.reLaunch({
url: `/${currentPage.route}`
})
}else{
this.isLoading = false
}
})
},
// 点击切换设备
btnksxz() {
if (this.xuanzeflag == true) {
this.xuanzeflag = false
} else {
this.xuanzeflag = true
}
},
//跳转到浇水日志页
btnjs() {
uni.showToast({
title: '此功能暂未开通',
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("定时器已清除");
}
},
// 处理抽水泵接收数据
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
},
}
}
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
}
})
},
// 处理从设备接收数据
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 = '';
}
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
console.log(this.showobj.showArray,'this.showobj.showArraythis.showobj.showArraythis.showobj.showArray')
// 设置开关状态
if(this.showobj.showArray && this.showobj.showArray.length >= 2){
this.yschecked = this.showobj.showArray[1] == 0 ? true : false
this.etchecked = this.showobj.showArray[0] == 0 ? true : false
}
this.dianya = this.showobj.showArray[this.showobj.showArray.length - 1]
console.log('this.yscheckedthis.yschecked',this.yschecked,this.etchecked)
this.getdianliang()
// 计算浇水时间
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 `${minutes}分${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.ver_data[key][3] === 1) {
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 || (currentDay % 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 ? `${nextTimeDiff.hours}时${nextTimeDiff.minutes}分` : '无距离下次浇水时间'
this.xctimesc = nextWaterDuration || '未知'
this.sctimejs = prevTime ? prevTime.toTimeString().slice(0, 5) : '无上次浇水时间'
this.xctimesj = nextTime ? nextTime.toTimeString().slice(0, 5) : '无下次浇水时间'
console.log('解析后的数据:', {
ver_data: this.ver_data,
showobj: this.showobj,
nextTime: this.xctimesj,
nextTimeDiff: this.xctime,
prevTime: this.sctimejs
});
if(this.ver == 'ver290' || this.ver == 'ver280'){
this.xinp = this.showobj.showArray[this.showobj.showArray.length - 2]
this.dianya = this.showobj.showArray[this.showobj.showArray.length - 1]
}else{
this.xinp = this.showobj.showArray[this.showobj.showArray.length - 1]
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
}
// 在 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 yiStr = String(this.showobj.showArray[6])
const erStr = String(this.showobj.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 = 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
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.clearDataTimeoutTimer()
uni.showToast({
title: '蓝牙已断开连接',
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;
},
// 判断是否显示图标
shouldShowIcon(pSetIndex) {
const pSet = this.ver_data[`p_set${pSetIndex}`];
// 如果定时未开启,不显示图标
if (!pSet || pSet[3] !== 1) 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 && checkSet[3] === 1) {
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 结构
buildVerDataFromBackend(params){
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)
// 按天间隔(若后台没有提供分组的 d1..d6则默认 1 天)
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
},
// 检查并修复蓝牙状态
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 = '蓝牙初始化失败'
let canAutoFix = false
let userAction = ''
if (errMsg.includes('auth deny') || errMsg.includes('authorize')) {
errorType = 'PERMISSION_DENIED'
errorMessage = '蓝牙权限未授权'
userAction = '请在微信设置中开启蓝牙权限:设置 > 隐私 > 蓝牙'
} else if (errMsg.includes('not available') || errMsg.includes('unavailable')) {
errorType = 'BLUETOOTH_UNAVAILABLE'
errorMessage = '蓝牙不可用'
userAction = '请检查手机蓝牙是否已打开:设置 > 蓝牙'
} else if (errMsg.includes('open fail')) {
errorType = 'OPEN_FAILED'
errorMessage = '手机蓝牙打开失败'
userAction = '请检查手机蓝牙功能是否打开'
} else if (errMsg.includes('system permission denied')) {
errorType = 'SYSTEM_PERMISSION_DENIED'
errorMessage = '系统蓝牙权限被拒绝'
userAction = '请在系统设置中允许微信使用蓝牙:设置 > 应用 > 微信 > 权限'
} else if (errMsg.includes('bluetooth service unavailable')) {
errorType = 'SERVICE_UNAVAILABLE'
errorMessage = '蓝牙服务不可用'
userAction = '请重启小程序后重试'
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: '蓝牙不可用',
userAction: '请检查手机蓝牙是否已打开:设置 > 蓝牙',
canAutoFix: true
})
return // 找到错误,停止检查
}
// 3. 蓝牙可用,检查是否找到设备
const deviceCount = this.devicesarr ? this.devicesarr.length : 0
if (deviceCount === 0) {
// 找到第二个错误:未搜索到任何设备
this.handleConnectionError({
type: 'NO_DEVICES_FOUND',
message: '未搜索到任何蓝牙设备',
userAction: '请检查:\n1. 设备是否已开机\n2. 设备是否在附近建议距离1米内\n3. 手机蓝牙是否已打开\n\n处理建议\n1. 确保设备已开机\n2. 靠近设备重试\n3. 重启设备后重试',
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: `搜索到${deviceCount}个设备,但未找到目标设备`,
userAction: '可能原因:\n1. 设备未开机\n2. 设备距离过远\n3. 设备已连接其他设备\n\n处理建议\n1. 检查设备是否开机\n2. 靠近设备重试\n3. 确认设备MAC地址是否正确',
canAutoFix: false
})
return // 找到错误,停止检查
}
// 如果所有检查都通过但没有连接成功,可能是其他原因
// 这种情况不应该发生,但为了安全起见还是处理一下
if (!this.errorModalShown) {
this.handleConnectionError({
type: 'UNKNOWN_ERROR',
message: '连接失败,原因未知',
userAction: '请重试连接,如问题持续,请联系客服',
canAutoFix: false
})
}
},
fail: (err) => {
// 获取状态失败,尝试重新初始化
this.fixBluetoothAdapter().then(() => {
// 修复成功,提示用户重试
if (!this.errorModalShown) {
this.handleConnectionError({
type: 'ADAPTER_ERROR',
message: '蓝牙适配器异常,已自动修复',
userAction: '请点击"连接设备"按钮重试',
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 || '连接失败'
const userAction = errorInfo.userAction || '请重试'
const canAutoFix = errorInfo.canAutoFix || false
const autoFixed = errorInfo.autoFixed || false
// 清除连接状态
this.vardataflag = 1
this.clearConnectionTimeout()
xBlufi.notifyStartDiscoverBle({
'isStart': false
})
// 如果已自动修复,显示成功提示
if (autoFixed) {
this.showCustomErrorModal({
title: '已自动修复',
message: message,
userAction: userAction,
showCancel: false,
confirmText: '知道了',
onConfirm: () => {
// 重置错误状态,允许下次检查
this.errorModalShown = false
this.currentErrorType = null
// 可以自动重试
if (canAutoFix) {
setTimeout(() => {
this.btnlj()
}, 1000)
}
}
})
return
}
// 根据错误类型决定是否可以自动修复
if (canAutoFix && type === 'BLUETOOTH_UNAVAILABLE') {
this.showCustomErrorModal({
title: '连接失败',
message: message,
userAction: userAction,
showCancel: true,
cancelText: '取消',
confirmText: '自动修复',
onConfirm: () => {
// 重置错误状态
this.errorModalShown = false
this.currentErrorType = null
// 尝试自动修复
this.fixBluetoothAdapter().then(() => {
uni.showToast({
title: '修复成功,请重试',
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: '连接失败',
message: message,
userAction: userAction,
showCancel: true,
cancelText: '取消',
confirmText: '重试',
onConfirm: () => {
// 重置错误状态,允许下次检查
this.errorModalShown = false
this.currentErrorType = null
// 用户点击重试
setTimeout(() => {
this.btnlj()
}, 500)
}
})
}
},
// 显示自定义错误弹窗
showCustomErrorModal(data) {
this.errorModalData = {
title: data.title || '提示',
message: data.message || '',
userAction: data.userAction || '',
showCancel: data.showCancel !== undefined ? data.showCancel : true,
cancelText: data.cancelText || '取消',
confirmText: data.confirmText || '确定',
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
},
// 处理连接超时
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: `连接超时(已等待${Math.floor(elapsedTime / 1000)}秒)`,
userAction: '可能原因:\n1. 设备距离过远\n2. 设备未开机\n3. 蓝牙信号干扰\n\n处理建议\n1. 靠近设备1米内\n2. 检查设备是否开机\n3. 重启设备后重试',
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 #48893B;
}
@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: #48893B;
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: #48893B;
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: 400rpx;
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: 34rpx;
.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;
}
}
}
}
.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: red;
margin-left: 10rpx;
font-weight: 400;
font-size: 28rpx;
border: 1px solid red;
padding: 4rpx;
box-sizing: border-box;
border-radius: 10rpx;
}
text {
display: inline-block;
width: 14rpx;
height: 14rpx;
background: #15C55D;
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: center;
// 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: 32rpx;
color: #333333;
font-weight: 500;
margin-bottom: 2rpx;
}
.gateway-desc {
font-size: 24rpx;
color: #15C55D;
}
}
}
}
/* 加载遮罩层样式 */
.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>