<template>
|
<view :class="deviceList.length == 0?'page_bg':'page_bg1'">
|
<uv-navbar title="我的设备" :placeholder="true" leftIcon="" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar>
|
|
<view class="hi" v-if="deviceList.length == 0">
|
HI,你来了~
|
</view>
|
<view class="add_device" v-if="deviceList.length == 0">
|
<navigator url="/pages/addDevice/addDevice" hover-class="none">
|
<image class="ico_add" src="/static/img/btn_1.png" mode="widthFix"></image>
|
<view class="name">
|
添加设备
|
</view>
|
</navigator>
|
</view>
|
|
<view class="device_list" v-if="deviceList.length != 0">
|
<view class="device_item" v-for="(item,index) in deviceList" :key="index">
|
<view class="item">
|
<image class="ph" src="/static/img/ph_1.png" mode="aspectFill"></image>
|
<view class="info">
|
<view class="name">
|
{{item.name}}
|
</view>
|
<view class="version">
|
v {{item.versions}}
|
</view>
|
<view class="power">
|
<view v-if="devKwh && dId == item.id">
|
电量{{devKwh * 10}}%
|
</view>
|
</view>
|
|
<view class="state" v-if="dId != item.id">
|
未连接
|
</view>
|
|
<view class="state green" v-if="dId == item.id">
|
已连接
|
</view>
|
<view class="btn" v-if="dId != item.id">
|
<uv-button type="primary" shape="circle" text="连接" size="small"
|
@click="createBLEConnection(item,index)"></uv-button>
|
<view class="btn_none"></view>
|
<uv-button type="primary" shape="circle" text="删除" size="small"
|
@click="clickEquipmentDel(item.id, index)"></uv-button>
|
</view>
|
<view class="btn" v-if="dId == item.id">
|
<uv-button type="primary" shape="circle" text="关闭" size="small"
|
@click="clickCloseBLEConnection(index)"></uv-button>
|
</view>
|
|
<!-- <view class="btn">
|
<uv-button type="primary" shape="circle" text="设备升级" size="small"></uv-button>
|
</view> -->
|
</view>
|
</view>
|
<view class="item_btn" v-if="dId == item.id">
|
<!-- <uv-button type="primary" shape="circle" text="已存序列" size="small" :plain="true"
|
@click="commandLIST"></uv-button>
|
<uv-button type="primary" shape="circle" :text="screenOn ? '关闭屏幕' : '打开屏幕'" size="small" :plain="true"
|
@click="toggleScreen()"></uv-button> -->
|
<uv-button type="primary" shape="circle" text="调节亮度" size="small" :plain="true"
|
@click="this.$refs.popupBRIG.open();"></uv-button>
|
<uv-button type="primary" shape="circle" text="发送LED" size="small" :plain="true"
|
@click="getTempleteList"></uv-button>
|
</view>
|
</view>
|
<!-- <view class="device_item">
|
<view class="item">
|
<image class="ph" src="/static/img/ph_1.png" mode="aspectFill"></image>
|
<view class="info">
|
<view class="name">
|
设备名称占位文字
|
</view>
|
<view class="version">
|
v2.21
|
</view>
|
<view class="power">
|
|
</view>
|
<view class="state">
|
未连接
|
</view>
|
<view class="btn">
|
<uv-button type="primary" shape="circle" text="设备升级" size="small"></uv-button>
|
</view>
|
</view>
|
</view>
|
</view> -->
|
|
<navigator class="add_device_small" url="/pages/addDevice/addDevice" hover-class="none"
|
:render-link="false">
|
<image class="ico_add" src="/static/img/btn_1.png" mode="widthFix"></image>
|
<view class="name">
|
添加设备
|
</view>
|
</navigator>
|
|
</view>
|
|
|
<!-- 调节亮度 -->
|
<uv-popup ref="popupBRIG" mode="center" round="30rpx">
|
<view class="popup_pwd">
|
<view class="title">调节亮度</view>
|
<view class="cont">
|
<uv-slider v-model="brightness" :min="0" :max="9" :step="1" block-size="24"
|
block-color="#2979ff"></uv-slider>
|
</view>
|
<view class="btns_two">
|
<view class="btn2">
|
<uv-button type="primary" plain @click="this.$refs.popupBRIG.close();">关闭</uv-button>
|
</view>
|
<view class="btn2">
|
<uv-button type="primary" @click="commandBRIG()">确定</uv-button>
|
</view>
|
</view>
|
</view>
|
</uv-popup>
|
|
<!-- 模版列表 -->
|
<uv-popup ref="popupTemplete" mode="center" round="30rpx">
|
<view class="popup_pwd">
|
<view class="title">发送LED</view>
|
<view class="cont cont_led">
|
|
<view class="led_item" v-for="(item,index) in templeteData" :key="index">
|
<view class="led_item_pic">
|
<uv-image :src="baseURL+item.pic" width="160rpx" height="120rpx" :radius="5"></uv-image>
|
</view>
|
<view class="led_item_text">
|
<view class="name">
|
{{item.name}}
|
</view>
|
<view class="text">
|
{{item.screen}}
|
</view>
|
</view>
|
<uv-button type="primary" shape="circle" size="small" @click="commandSendTemplate(item)">播放</uv-button>
|
</view>
|
|
</view>
|
<view class="btns">
|
<uv-button type="primary" plain @click="this.$refs.popupTemplete.close();">关闭</uv-button>
|
</view>
|
</view>
|
</uv-popup>
|
|
</view>
|
</template>
|
|
<script>
|
import {
|
equipmentList,
|
equipmentDel,
|
templeteList
|
} from '@/api/api.js'
|
export default {
|
data() {
|
return {
|
searchIng: true,
|
userId: '',
|
deviceList: [],
|
|
bluetoothList: [], //搜索到的蓝牙设备列表
|
deviceId: '', //设备id
|
serviceId: '', //服务uuid
|
characteristicId1: '', //特征uuid
|
characteristicId2: '',
|
characteristicId3: '',
|
showPwdPopup: false,
|
password: '', //设置的密码
|
checId: '', //智能壳的ID
|
batchNumber: '', //生产顺序号
|
fileNames: [], //文件名称列表
|
brightness: 3, //亮度
|
nameIndex: 1,
|
name: '',
|
|
ledName: '', // 名称
|
screen: '',
|
ledTotal: 3, // 总帧数
|
ledCurrent: 0, // 当前帧数
|
ledData: [],
|
ledSpeed: '', //速度
|
isLink: false,
|
listIndex: 0,
|
templeteData: [],
|
baseURL: '',
|
dId: '' ,//已连接的设备id,
|
devKwh: '',
|
platform: '',
|
screenOn: true,
|
waitDallThenSend: false // 标记:收到DALL清除所有信息指令回应后,再开始发送INST帧
|
}
|
},
|
onLoad() {
|
//uni.setStorageSync('dId', '');
|
this.userId = uni.getStorageSync('userId');
|
this.baseURL = uni.$uv.http.config.baseURL
|
const systemInfo = uni.getSystemInfoSync();
|
if (systemInfo.platform === 'ios') {
|
// iOS 设备
|
this.platform = 'ios'
|
} else if (systemInfo.platform === 'android') {
|
// Android 设备
|
this.platform = 'android'
|
}
|
else{
|
this.platform = ''
|
}
|
},
|
onShow() {
|
this.getEquipmentList()
|
this.openBluetooth()
|
|
const that = this
|
this.userId = uni.getStorageSync('userId');
|
|
this.dId = uni.getStorageSync('dId');
|
if(this.dId){
|
this.deviceId = uni.getStorageSync('deviceId');
|
this.serviceId = uni.getStorageSync('serviceId');
|
this.characteristicId1 = uni.getStorageSync('characteristicId1');
|
this.characteristicId2 = uni.getStorageSync('characteristicId2');
|
this.characteristicId3 = uni.getStorageSync('characteristicId3');
|
this.checId = uni.getStorageSync('checId');
|
this.password = uni.getStorageSync('password');
|
this.startNotify()
|
}
|
|
console.log('dId---',this.dId)
|
|
//this.getTempleteList()
|
},
|
onHide() {
|
console.log('onHide-----')
|
uni.$off('BLECharacteristic')
|
|
uni.setStorageSync('dId', this.dId);
|
uni.setStorageSync('deviceId', this.deviceId);
|
uni.setStorageSync('serviceId', this.serviceId);
|
uni.setStorageSync('characteristicId1', this.characteristicId1);
|
uni.setStorageSync('characteristicId2', this.characteristicId2);
|
uni.setStorageSync('characteristicId3', this.characteristicId3);
|
uni.setStorageSync('checId', this.checId);
|
uni.setStorageSync('password', this.password);
|
|
//this.clickCloseBLEConnection()
|
|
//this.closeBluetooth()
|
//this.closeBLEConnection()
|
},
|
onUnload() {
|
console.log('onUnload-----')
|
uni.$off('BLECharacteristic')
|
|
//this.closeBluetooth()
|
//this.closeBLEConnection()
|
},
|
methods: {
|
// 获取模版列表
|
getTempleteList() {
|
uni.showLoading({
|
title: '加载中...',
|
mask: true
|
});
|
templeteList({
|
userId: this.userId,
|
pageNum: 1,
|
pageSize: 99
|
}).then(res => {
|
console.log('模版列表-------', res.rows)
|
uni.hideLoading();
|
this.templeteData = res.rows
|
if (this.templeteData.length != 0) {
|
this.$refs.popupTemplete.open();
|
} else {
|
uni.showToast({
|
title: '暂无数据,请先添加模版!',
|
icon: 'none',
|
mask: true
|
});
|
}
|
|
})
|
},
|
// 删除设备
|
clickEquipmentDel(id, index) {
|
uni.showModal({
|
title: '提示',
|
content: '删除后无法恢复,确定删除吗?',
|
success: (resa) => {
|
if (resa.confirm) {
|
console.log('用户点击确定');
|
equipmentDel(id).then(res=>{
|
console.log(res)
|
uni.showToast({
|
title: res.msg,
|
duration: 2000,
|
icon: 'none'
|
});
|
if(res.code == 200){
|
//this.getTempleteList()
|
this.deviceList.splice(index, 1)
|
}
|
})
|
} else if (resa.cancel) {
|
console.log('用户点击取消');
|
}
|
}
|
});
|
},
|
// 设备列表
|
getEquipmentList() {
|
this.deviceList = []
|
equipmentList({
|
userId: this.userId,
|
code: this.platform,
|
pageNum: 1,
|
pageSize: 99
|
}).then(res => {
|
console.log('设备列表------', res.rows)
|
this.deviceList = res.rows
|
})
|
},
|
// 初始化蓝牙
|
openBluetooth() {
|
uni.openBluetoothAdapter({
|
success: (res) => {
|
console.log('蓝牙初始化成功');
|
this.onBLEStateChange()
|
},
|
fail: (err) => {
|
console.log('蓝牙初始化失败', err);
|
}
|
});
|
},
|
// 关闭蓝牙
|
closeBluetooth() {
|
uni.closeBluetoothAdapter({
|
success: (res) => {
|
console.log(res)
|
}
|
})
|
},
|
// 断开与低功耗蓝牙设备的连接
|
closeBLEConnection() {
|
const that = this
|
uni.closeBLEConnection({
|
deviceId: this.deviceId,
|
success(res) {
|
console.log('断开与低功耗蓝牙设备的连接', res)
|
that.dId = ''
|
uni.setStorageSync('dId', '');
|
}
|
})
|
},
|
// 关闭连接
|
clickCloseBLEConnection() {
|
const that = this
|
console.log('that.deviceId-----', that.deviceId)
|
uni.closeBLEConnection({
|
deviceId: that.deviceId,
|
success(res) {
|
console.log('关闭连接', res)
|
// 关闭连接清空已连接的设备id
|
that.dId = ''
|
uni.setStorageSync('dId', '');
|
//that.isLink = false
|
//that.deviceList[that.listIndex].isLink = false
|
}
|
})
|
},
|
// 发送LED
|
commandSendTemplate(item) {
|
console.log('--------------', item);
|
|
uni.showLoading({
|
title: '发送中0%',
|
mask: true
|
});
|
|
const name = 'A' + this.nameIndex++;
|
this.ledName = name;
|
this.screen = item.screen;
|
this.ledTotal = item.fpsTotal;
|
this.ledSpeed = item.speed
|
this.ledCurrent = 0;
|
|
try {
|
this.ledData = JSON.parse(item.fpsData);
|
console.log('this.ledData------',this.ledData)
|
} catch (e) {
|
uni.hideLoading();
|
console.error('fpsData 解析失败:', e);
|
uni.showToast({
|
title: 'fpsData 解析失败',
|
duration: 2000
|
});
|
return;
|
}
|
|
if (this.ledData.length > 0) {
|
// 向智能壳发送INST帧前,先发送一次清除所有信息指令(DALL),待智能壳回应后再开始发送
|
this.waitDallThenSend = true;
|
this.commandDALL();
|
} else {
|
uni.hideLoading();
|
uni.showToast({
|
title: '无可发送的帧数据',
|
duration: 2000
|
});
|
console.warn('无可发送的帧数据');
|
}
|
},
|
// 连接
|
createBLEConnection(item, index) {
|
const that = this
|
if (this.dId) {
|
uni.showToast({
|
title: '请先关闭已连接的设备',
|
icon: 'none',
|
mask: true
|
});
|
return
|
}
|
this.deviceId = item.deviceId
|
this.serviceId = item.serviceId
|
this.characteristicId1 = item.characteristicId1
|
this.characteristicId2 = item.characteristicId2
|
this.characteristicId3 = item.characteristicId3
|
this.checId = item.checId
|
this.password = item.password
|
|
|
uni.showLoading({
|
title: '连接中...',
|
mask: true
|
});
|
uni.createBLEConnection({
|
deviceId: this.deviceId,
|
success: (res) => {
|
console.log('连接低功耗蓝牙设备', res);
|
this.startNotify()
|
// setTimeout(() => {
|
// that.setBaud()
|
// that.setBLEMTU()
|
// }, 500);
|
this.dId = item.id
|
|
setTimeout(() => {
|
this.commandPOWR()
|
}, 1500);
|
},
|
fail: (err) => {
|
//console.log('连接失败', err);
|
this.closeBLEConnection()
|
uni.hideLoading();
|
uni.showToast({
|
title: '连接失败',
|
icon: 'none',
|
mask: true
|
});
|
}
|
});
|
},
|
// 设置波特率
|
setBaud() {
|
var that = this
|
var hexStr = "06000900009600000801009F"
|
uni.writeBLECharacteristicValue({
|
deviceId: this.deviceId,
|
serviceId: this.serviceId,
|
characteristicId: this.characteristicId3,
|
value: this.hexStringToArrayBuffer(hexStr),
|
writeType: 'write',
|
success: (res) => {
|
console.log('设置波特率成功----', res);
|
//that.commandBIND()
|
uni.hideLoading();
|
},
|
fail: (err) => {
|
console.log('设置波特率失败----', err);
|
uni.hideLoading();
|
uni.showToast({
|
title: '设置波特率失败',
|
icon: 'none',
|
mask: true
|
});
|
}
|
});
|
},
|
// 设置蓝牙最大传输单元
|
setBLEMTU() {
|
var that = this
|
uni.setBLEMTU({
|
deviceId: this.deviceId,
|
mtu: 80, // 最大值一般 512,蓝牙 4.2 通常支持 247
|
success: (res) => {
|
console.log('设置MTU成功:', res);
|
uni.hideLoading();
|
},
|
fail: (err) => {
|
console.error('设置MTU失败:', err);
|
uni.hideLoading();
|
uni.showToast({
|
title: '设置MTU失败',
|
icon: 'none',
|
mask: true
|
});
|
}
|
});
|
},
|
commandCHEC() {
|
//555555555555555555557e41434845430095977e
|
// 构建指令数据
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x41; // 包类型
|
const command = [0x43, 0x48, 0x45, 0x43]; // "CHEC"的ASCII码
|
const reserved = 0x00; // 保留字节
|
|
// 组合数据
|
const data = [
|
...startBytes,
|
frameHeader,
|
packageType,
|
...command,
|
reserved
|
];
|
|
// 计算CRC校验码(从包头段开始计算)
|
const crc = this.calcCRC16(data.slice(11));
|
|
// 添加CRC和帧尾
|
data.push((crc >> 8) & 0xFF); // CRC高8位
|
data.push(crc & 0xFF); // CRC低8位
|
data.push(frameHeader); // 帧尾
|
console.log('CHEC data----AAAA--------', this.hexArrayToHexString(data))
|
|
const buffer = new Uint8Array(data).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(buffer)
|
}, 500);
|
},
|
// 加密绑定号 发送绑定号
|
commandBIND() {
|
// uni.showToast({
|
// title: '发送绑定号',
|
// icon: 'none',
|
// mask: true
|
// });
|
//console.log('checId---', this.checId)
|
// 59 5C 37 39 71 4D 56 65 5A 37 12 18 07 06 2A 18 36 1E 1E 06
|
// 7e 41 42 49 4e 44 6d 71 41 45 95 4e 61 78 9e 39 49 12 15 13 71 42 94 50 50 17 7c c9 7e
|
//帧头+0x41+ BIND +加密绑定号+CRC校验码+帧尾
|
// 构建指令数据
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x41; // 包类型
|
const command = [0x42, 0x49, 0x4E, 0x44]; // "BIND"的ASCII码
|
const reserved = this.replaceScreenSizeInID(this.checId, this.password); // 密码
|
//console.log('reserved----111', reserved)
|
const reservedId = this.encryptSmartShellID(reserved)
|
//console.log('reservedid--222', reservedId)
|
// 组合数据
|
const data = [
|
...startBytes,
|
frameHeader,
|
packageType,
|
...command,
|
...this.hexStringToArray(reservedId)
|
];
|
|
// 计算CRC校验码(从包头段开始计算)
|
const crc = this.calcCRC16(data.slice(11));
|
|
// 添加CRC和帧尾
|
data.push((crc >> 8) & 0xFF); // CRC高8位
|
data.push(crc & 0xFF); // CRC低8位
|
data.push(frameHeader); // 帧尾
|
//console.log('BIND data----BBBB--------', this.hexArrayToHexString(data))
|
|
const buffer = new Uint8Array(data).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(buffer)
|
}, 500);
|
},
|
|
//向低功耗蓝牙设备特征值中写入二进制数据
|
//keepLoading: 发送INST帧(含DALL前置指令、结束帧)过程中保持进度loading,发送成功后不关闭
|
writeBLECharacteristicValue(buffer, keepLoading = false) {
|
const that = this
|
uni.writeBLECharacteristicValue({
|
deviceId: this.deviceId,
|
serviceId: this.serviceId,
|
characteristicId: this.characteristicId2,
|
value: buffer,
|
writeType: 'write',
|
success: (res) => {
|
//console.log('写入指令发送成功----', res);
|
if (!keepLoading) {
|
uni.hideLoading();
|
}
|
},
|
fail: (err) => {
|
//console.log('写入指令发送失败----', err);
|
uni.hideLoading();
|
uni.showToast({
|
title: '写入指令发送失败',
|
icon: 'none',
|
mask: true
|
});
|
}
|
});
|
},
|
// 调节亮度
|
commandBRIG() {
|
console.log('brightness----', this.brightness)
|
//1.手机发指令:启动字节+帧头+0x41+ BRIG +亮度+CRC校验码+帧尾。
|
//2.智能壳回指令:启动字节+帧头+0x41+ BRIG +返回标志+CRC校验码+帧尾。
|
// 构建指令数据
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x41; // 包类型
|
const command = [0x42, 0x52, 0x49, 0x47]; // "BRIG"的ASCII码
|
|
//亮度:单字节,0,1,2,3。。。ASIC码
|
const brightness = this.stringToASCIIByteArray(this.brightness.toString())
|
//const brightness = 0x32
|
|
// 组合数据
|
const data = [
|
...startBytes,
|
frameHeader,
|
packageType,
|
...command,
|
brightness
|
];
|
// 计算CRC校验码(从包头段开始计算)
|
const crc = this.calcCRC16(data.slice(11));
|
// 添加CRC和帧尾
|
data.push((crc >> 8) & 0xFF); // CRC高8位
|
data.push(crc & 0xFF); // CRC低8位
|
data.push(frameHeader); // 帧尾
|
console.log('BRIG data----', data)
|
const buffer = new Uint8Array(data).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(buffer)
|
}, 500);
|
|
},
|
// 切换屏幕状态
|
toggleScreen() {
|
if (this.screenOn) {
|
this.commandBLAC()
|
} else {
|
this.commandOPAC()
|
}
|
},
|
// 关闭屏幕
|
commandBLAC() {
|
console.log('关闭屏幕BLAC----')
|
const startBytes = new Array(10).fill(0x55);
|
const frameHeader = 0x7E;
|
const packageType = 0x41;
|
const command = [0x42, 0x4C, 0x41, 0x43];
|
|
const data = [
|
...startBytes,
|
frameHeader,
|
packageType,
|
...command
|
];
|
|
const crc = this.calcCRC16(data.slice(11));
|
data.push((crc >> 8) & 0xFF);
|
data.push(crc & 0xFF);
|
data.push(frameHeader);
|
console.log('BLAC data----', this.hexArrayToHexString(data))
|
|
const buffer = new Uint8Array(data).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(buffer)
|
}, 500);
|
},
|
// 打开屏幕
|
commandOPAC() {
|
console.log('打开屏幕OPAC----')
|
const startBytes = new Array(10).fill(0x55);
|
const frameHeader = 0x7E;
|
const packageType = 0x41;
|
const command = [0x4F, 0x50, 0x41, 0x43];
|
|
const data = [
|
...startBytes,
|
frameHeader,
|
packageType,
|
...command
|
];
|
|
const crc = this.calcCRC16(data.slice(11));
|
data.push((crc >> 8) & 0xFF);
|
data.push(crc & 0xFF);
|
data.push(frameHeader);
|
console.log('OPAC data----', this.hexArrayToHexString(data))
|
|
const buffer = new Uint8Array(data).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(buffer)
|
}, 500);
|
},
|
// 清除所有信息指令(DALL)
|
commandDALL() {
|
// 1.手机发指令:启动字节+帧头+包头段(0x43+简易绑定号+屏大小)+DALL+CRC校验码+帧尾。
|
// 2.智能壳回指令:启动字节+帧头+包头段+DALL+返回标志+加密地址+CRC校验码+帧尾。
|
// 构建指令数据
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x43; // 包类型
|
// 使用新的简易绑定号生成函数
|
const simpleBindId = this.generateSimpleBindId();
|
|
// 屏大小 (2 bytes)
|
const screenSize = [0x0C, 0x18];
|
const packageHeader = [packageType, ...simpleBindId, ...screenSize];
|
const command = [0x44, 0x41, 0x4C, 0x4C]; // "DALL"的ASCII码
|
|
// 组合数据
|
const data = [
|
...startBytes,
|
frameHeader,
|
...packageHeader,
|
...command
|
];
|
// 计算CRC校验码(从包头段开始计算)
|
const crc = this.calcCRC16(data.slice(11));
|
// 添加CRC和帧尾
|
data.push((crc >> 8) & 0xFF); // CRC高8位
|
data.push(crc & 0xFF); // CRC低8位
|
data.push(frameHeader); // 帧尾
|
console.log('DALL data----', this.hexArrayToHexString(data))
|
|
const buffer = new Uint8Array(data).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(buffer, true)
|
}, 50);
|
},
|
//添加信息指令(INST)
|
commandINST(hexString) {
|
// 1.手机发指令:启动+帧头+包头段(0x43+简易绑定号+屏大小)+INST+信息属性+文件名+总帧数+当前帧*+块描述+块数据+CRC校验码+帧尾
|
//注意:当前帧如果是0xff,则表示是结束帧,后面四个字节,第一个字节(表示速度),第二个字节(播放次数,0表示一直播放),后面二个字节保留;其他数字表示表示当前是第几帧。
|
//2.智能壳指令:启动+帧头+包头段+INST+返回标志+加密地址+CRC校验码+帧尾
|
// 构建指令数据
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x43; // 包类型
|
// 使用新的简易绑定号生成函数
|
const simpleBindId = this.generateSimpleBindId();
|
|
// 屏大小 (2 bytes): 从ID中获取
|
//const screenSize = this.hexStringToArray(this.checId.slice(20, 24));
|
//console.log('屏大小ID----',this.checId)
|
//console.log('屏大小----',screenSize,this.checId.slice(20, 24))
|
const screenSize = [0x0C,0x18]
|
const packageHeader = [packageType, ...simpleBindId, ...screenSize];
|
const command = [0x49, 0x4E, 0x53, 0x54]; // "INST"的ASCII码
|
const infoAttribute = 0x02; // 信息属性: 0=一般信息
|
|
const fileName = this.stringToASCIIByteArray(this.ledName) //文件名称
|
//console.log('fileName----', this.ledName)
|
// 总帧数
|
const totalFrame = this.hexStringToArray(this.ledTotal.toString(16).padStart(2, '0')
|
.toUpperCase()) //[0x01]; //this.hexStringToArray('01')
|
//console.log('totalFrame----', totalFrame)
|
// 当前帧
|
const currentFrame = this.hexStringToArray(this.ledCurrent.toString(16).padStart(2, '0').toUpperCase()) //0x00
|
|
// 块描述
|
const tblockDesc = 0x00
|
|
// 块数据
|
const blockDescription = this.hexStringToArray(hexString)
|
|
// 组合数据
|
const data = [
|
...startBytes,
|
frameHeader,
|
...packageHeader,
|
...command,
|
infoAttribute,
|
...fileName,
|
...totalFrame,
|
...currentFrame,
|
tblockDesc,
|
...blockDescription
|
];
|
// 计算CRC校验码(从包头段开始计算)
|
const crc = this.calcCRC16(data.slice(11));
|
// 添加CRC和帧尾
|
data.push((crc >> 8) & 0xFF); // CRC高8位
|
data.push(crc & 0xFF); // CRC低8位
|
data.push(frameHeader); // 帧尾
|
//console.log(this.ledCurrent, '----INST data----', this.hexArrayToHexString(data))
|
//const buffer = new Uint8Array(data).buffer;
|
console.log('第几帧-------',this.ledCurrent)
|
// 根据总帧数和已发送数量更新发送进度(当前帧发送前,已发送ledCurrent帧),范围10%~99%
|
const sendPercent = Math.max(10, Math.min(99, Math.round(this.ledCurrent / this.ledTotal * 100)));
|
uni.showLoading({
|
title: '发送中' + sendPercent + '%',
|
mask: true
|
});
|
setTimeout(() => {
|
//this.writeBLECharacteristicValue(buffer)
|
this.sendBLEDataInChunks(
|
this.hexArrayToHexString(data)
|
)
|
}, 50);
|
},
|
// 发送结束针
|
commandEnd() {
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x43; // 包类型
|
// 使用新的简易绑定号生成函数
|
const simpleBindId = this.generateSimpleBindId();
|
|
// 屏大小 (2 bytes): 从ID中获取
|
const screenSize = this.hexStringToArray(this.checId.slice(20, 24));
|
const packageHeader = [packageType, ...simpleBindId, ...screenSize];
|
const command = [0x49, 0x4E, 0x53, 0x54]; // "INST"的ASCII码
|
const infoAttribute = 0x02; // 信息属性: 0=一般信息
|
|
const fileName = this.stringToASCIIByteArray(this.ledName) //文件名称
|
//console.log('fileName----', this.ledName)
|
// 总帧数
|
const totalFrame = this.hexStringToArray(this.ledTotal.toString(16).padStart(2, '0').toUpperCase()) //[0x01]; //this.hexStringToArray('01')
|
//console.log('totalFrame----', totalFrame)
|
|
//结束幕:
|
const ffdata = this.hexStringToArray('ff')
|
const speedData = this.hexStringToArray(this.ledSpeed.toString(16).padStart(2, '0').toUpperCase())
|
|
console.log('speedData------',speedData)
|
const ffend = this.hexStringToArray('000000')
|
|
const endData = [
|
...startBytes,
|
frameHeader,
|
...packageHeader,
|
...command,
|
infoAttribute,
|
...fileName,
|
...totalFrame,
|
...ffdata,
|
...speedData,
|
...ffend
|
]
|
// 计算CRC校验码(从包头段开始计算)
|
const endcrc = this.calcCRC16(endData.slice(11));
|
|
// 添加CRC和帧尾
|
endData.push((endcrc >> 8) & 0xFF); // CRC高8位
|
endData.push(endcrc & 0xFF); // CRC低8位
|
endData.push(frameHeader); // 帧尾
|
//console.log('INST endData----', this.hexArrayToHexString(endData))
|
|
const endbuffer = new Uint8Array(endData).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(endbuffer, true)
|
}, 50);
|
},
|
async sendBLEDataInChunks(hexStr) {
|
const maxBytes = 76
|
const interval = 20
|
const chunks = this.splitHexData(hexStr, maxBytes)
|
|
//console.log(`📦 总共需要发送 ${chunks.length} 包,每包 ${maxBytes} 字节,间隔 ${interval}ms`)
|
|
for (let i = 0; i < chunks.length; i++) {
|
const buffer = this.hexToBuffer(chunks[i])
|
try {
|
await new Promise((resolve, reject) => {
|
uni.writeBLECharacteristicValue({
|
deviceId: this.deviceId,
|
serviceId: this.serviceId,
|
characteristicId: this.characteristicId2,
|
value: buffer,
|
success: (res) => {
|
//console.log(`✅ 第 ${i + 1} 包发送成功`, res)
|
resolve()
|
},
|
fail: (err) => {
|
//console.error(`❌ 第 ${i + 1} 包发送失败`, err)
|
uni.hideLoading();
|
reject(err)
|
}
|
})
|
})
|
} catch (err) {
|
console.error(`🚫 发送中断,第 ${i + 1} 包发送失败`)
|
uni.hideLoading();
|
break
|
}
|
// 延迟下一包发送
|
await new Promise(r => setTimeout(r, interval))
|
}
|
//console.log("🎉 数据发送完成")
|
},
|
// 读取电量
|
commandPOWR() {
|
console.log('读取电量POWR----', )
|
// 构建指令数据
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x41; // 包类型
|
const command = [0x50, 0x4F, 0x57, 0x52]; // "POWR"的ASCII码
|
|
// 组合数据
|
const data = [
|
...startBytes,
|
frameHeader,
|
packageType,
|
...command
|
];
|
|
// 计算CRC校验码(从包头段开始计算)
|
const crc = this.calcCRC16(data.slice(11));
|
|
// 添加CRC和帧尾
|
data.push((crc >> 8) & 0xFF); // CRC高8位
|
data.push(crc & 0xFF); // CRC低8位
|
data.push(frameHeader); // 帧尾
|
//console.log('POWR----', data)
|
|
const buffer = new Uint8Array(data).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(buffer)
|
}, 500);
|
},
|
// 监听蓝牙连接状态
|
onBLEStateChange(){
|
const that = this
|
uni.onBLEConnectionStateChange(res => {
|
//console.log('监听蓝牙连接状态------------',res)
|
if (!res.connected) {
|
// uni.showToast({
|
// title: '蓝牙设备已断开',
|
// icon: 'none',
|
// mask: true
|
// });
|
that.dId = ''
|
uni.setStorageSync('dId', '');
|
//console.log('蓝牙设备已断开------------',res)
|
//that.isLink = false
|
// for(let i = 0;i<that.deviceList.length;i++){
|
// if(that.deviceList[i].isLink){
|
// that.deviceList[i].isLink = false
|
// }
|
// }
|
}
|
})
|
},
|
// 开始监听
|
startNotify() {
|
const that = this
|
uni.notifyBLECharacteristicValueChange({
|
deviceId: that.deviceId,
|
serviceId: that.serviceId,
|
characteristicId: that.characteristicId1,
|
state: true,
|
success: () => {
|
console.log('开始监听------');
|
setTimeout(() => {
|
that.setBaud()
|
if (uni.getSystemInfoSync().platform === 'android') {
|
that.setBLEMTU();
|
}
|
else{
|
uni.hideLoading();
|
}
|
|
}, 500);
|
|
uni.$off('BLECharacteristic');
|
uni.$on('BLECharacteristic', function(res) {
|
|
//console.log('BLECharacteristic---------------index', res)
|
const resData = that.ab2hex(res.value);
|
const result = that.removeStartBytes(resData)
|
const resName = that.getCommandType(result);
|
//console.log("回调返回数据-------", resData);
|
//console.log('result-----------', result)
|
//console.log('resName----------', resName)
|
//uni.hideLoading();
|
if (resName == 'CHEC') {
|
|
} else if (resName == 'BIND') {
|
//that.isLink = true;
|
//that.deviceList[that.listIndex].isLink = true;
|
//that.commandPOWR()
|
} else if (resName == 'LIST') {
|
that.parseFileList(result)
|
} else if (resName == 'BRIG') {
|
|
} else if (resName == 'BLAC') {
|
console.log('BLAC----', result)
|
if (that.screenOn) {
|
that.screenOn = false;
|
uni.showToast({
|
title: '屏幕已关闭',
|
icon: 'none',
|
duration: 1500
|
});
|
} else {
|
that.screenOn = true;
|
uni.showToast({
|
title: '屏幕已打开',
|
icon: 'none',
|
duration: 1500
|
});
|
}
|
} else if (resName == 'PLAY') {
|
|
} else if (resName == 'INST') {
|
// 添加动画帧
|
that.ledCurrent++
|
if (that.ledCurrent < that.ledTotal) {
|
// uni.showLoading({
|
// title: '发送中',// + that.ledCurrent,
|
// mask: true
|
// });
|
that.commandINST(that.ledData[that.ledCurrent]);
|
} else if (that.ledCurrent == that.ledTotal) {
|
// 所有数据帧已发送完成,发送结束帧,进度显示100%
|
uni.showLoading({
|
title: '发送中100%',
|
mask: true
|
});
|
that.commandEnd()
|
} else {
|
// 结束帧应答(ledCurrent == ledTotal + 1),全部发送完成,隐藏loading
|
uni.hideLoading();
|
}
|
|
} else if (resName == 'DALL') {
|
// 清除所有信息指令回应:返回标志0x30工作无异常,0x31工作有异常
|
if (that.waitDallThenSend) {
|
that.waitDallThenSend = false;
|
// 收到DALL回应后,开始发送第一帧INST
|
that.commandINST(that.ledData[that.ledCurrent]);
|
}
|
} else if (resName == 'POWR') {
|
// 查询电量
|
let capacityHex = result.slice(-8, -6); // "0a"
|
let capacity = parseInt(capacityHex, 16); // 转十进制
|
that.devKwh = capacity
|
} else {
|
//that.isLink == false
|
uni.showToast({
|
title: '返回数据错误',
|
duration: 2000,
|
icon: 'none'
|
});
|
// 关闭蓝牙设备
|
that.closeBLEConnection()
|
}
|
})
|
},
|
fail: (err) => {
|
//console.log('监听失败----', err);
|
uni.hideLoading();
|
// uni.showToast({
|
// title: '监听失败',
|
// icon: 'none',
|
// mask: true
|
// });
|
that.dId = ''
|
uni.setStorageSync('dId', '');
|
}
|
});
|
},
|
//查询智能壳已存信息序列(LIST)
|
commandLIST() {
|
//1.手机发指令:启动字节+帧头+包头段(0x43+简易绑定号+屏大小)+LIST+信息属性+CRC校验码+帧尾。
|
//2.智能壳指令:启动字节+帧头+包头段+LIST+返回标志+加密地址+文件数+[信息名1+信息名2+...+信息名n]+CRC校验码+帧尾。
|
// 构建指令数据
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x43; // 包类型
|
// 使用新的简易绑定号生成函数
|
const simpleBindId = this.generateSimpleBindId();
|
|
// 屏大小 (2 bytes): 从ID中获取
|
const screenSize = this.hexStringToArray(this.checId.slice(20, 24));
|
const packageHeader = [packageType, ...simpleBindId, ...screenSize];
|
const command = [0x4C, 0x49, 0x53, 0x54]; // "LIST"的ASCII码
|
const infoAttribute = 0x00; // 信息属性: 0=一般信息
|
// 组合数据
|
const data = [
|
...startBytes,
|
frameHeader,
|
...packageHeader,
|
...command,
|
infoAttribute
|
];
|
|
// 计算CRC校验码(从包头段开始计算)
|
const crc = this.calcCRC16(data.slice(11));
|
|
// 添加CRC和帧尾
|
data.push((crc >> 8) & 0xFF); // CRC高8位
|
data.push(crc & 0xFF); // CRC低8位
|
data.push(frameHeader); // 帧尾
|
console.log('LIST data----', this.hexArrayToHexString(data))
|
|
const buffer = new Uint8Array(data).buffer;
|
setTimeout(() => {
|
this.writeBLECharacteristicValue(buffer)
|
}, 1000);
|
},
|
// 获取文件名称文件数量
|
parseFileList(hexString) {
|
// 文件数占1字节(2个十六进制字符
|
const fileCountHex = hexString.slice(32, 34);
|
const fileCount = parseInt(fileCountHex, 16);
|
console.log('文件数:', fileCount);
|
|
// 获取文件名称列表
|
const fileNames = [];
|
let nameStartIndex = 34; // 文件数后开始
|
|
for (let i = 0; i < fileCount; i++) {
|
// 每个信息名占2个字节(4个十六进制字符)
|
const nameHex = hexString.slice(nameStartIndex, nameStartIndex + 4);
|
const nameBytes = nameHex.match(/.{2}/g);
|
if (nameBytes && nameBytes.length === 2) {
|
// 将两个字节转换为ASCII字符
|
const char1 = String.fromCharCode(parseInt(nameBytes[0], 16));
|
const char2 = String.fromCharCode(parseInt(nameBytes[1], 16));
|
const fileName = char1 + char2;
|
fileNames.push({
|
fileName: fileName,
|
nameHex: nameHex
|
});
|
}
|
|
nameStartIndex += 4; // 移动到下一个文件名
|
}
|
console.log('文件名称列表:', fileNames);
|
},
|
// 生成简易绑定号
|
generateSimpleBindId() {
|
if (!this.checId || !this.password) {
|
//console.error("生成简易绑定号失败:checId或password不可用");
|
uni.hideLoading();
|
uni.showToast({
|
title: '请先连接设备',
|
icon: 'none',
|
mask: true
|
});
|
return;
|
}
|
// 从checId中提取生产顺序号(最后4字节,即8个字符)
|
const productionNumber = this.checId.slice(-8); // 生产顺序号
|
// 生产顺序号+密码
|
const simpleBindData = productionNumber + this.password;
|
// 用加密数字加密
|
const encryptedSimpleBind = this.encryptSmartShellID(simpleBindData);
|
// 取前6字节作为简易绑定号
|
const simpleBindId = this.hexStringToArray(encryptedSimpleBind.slice(0, 12));
|
//console.log('简易绑定号生成----', simpleBindId);
|
return simpleBindId;
|
},
|
// 判断并去除10个0x55启动字节(20个hex字符)
|
removeStartBytes(hexStr) {
|
// 10个0x55 = 20个hex字符
|
const startBytes = '55'.repeat(10); // '55555555555555555555'
|
if (hexStr.startsWith(startBytes)) {
|
return hexStr.slice(20); // 去掉前20个字符
|
}
|
return hexStr;
|
},
|
// 返回指令类型 判断
|
getCommandType(hexString) {
|
// 判断是否有 返回标志 单字节。0(0x30),工作无异常;1(0x31)工作有异常
|
// 判断是否包含返回标志 30 或 31
|
// 查找30或31的位置
|
let returnFlag = '';
|
let cmd = '';
|
let packageType = hexString.slice(2, 4);
|
|
if (packageType == '41') {
|
returnFlag = hexString.slice(12, 14);
|
const before8Chars = hexString.slice(4, 12);
|
cmd = before8Chars.match(/.{2}/g)
|
.map(h => String.fromCharCode(parseInt(h, 16)))
|
.join('');
|
return cmd;
|
} else if (packageType == '43') {
|
returnFlag = hexString.slice(28, 30);
|
const before8Chars = hexString.slice(20, 28);
|
cmd = before8Chars.match(/.{2}/g)
|
.map(h => String.fromCharCode(parseInt(h, 16)))
|
.join('');
|
return cmd;
|
}
|
//console.log('returnFlag------', returnFlag)
|
},
|
// 将十六进制字符串转为 ArrayBuffer
|
hexToBuffer(hexStr) {
|
const typedArray = new Uint8Array(hexStr.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16)))
|
return typedArray.buffer
|
},
|
// 将大十六进制数据切分为每段 maxBytes 大小(单位:字节)
|
splitHexData(hexStr, maxBytes = 100) {
|
const result = []
|
for (let i = 0; i < hexStr.length; i += maxBytes * 2) {
|
result.push(hexStr.slice(i, i + maxBytes * 2))
|
}
|
return result
|
},
|
// 指令ASCII码转换
|
stringToASCIIByteArray(str) {
|
return str.split('').map(char => char.charCodeAt(0));
|
},
|
//16进制数组 转 16进制字符串
|
hexArrayToHexString(hexArray) {
|
return hexArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
},
|
// 16进制字符串转16进制数组
|
hexStringToArray(hexStr) {
|
if (!hexStr) return [];
|
return hexStr.match(/.{2}/g).map(byte => parseInt(byte, 16));
|
},
|
// 16进制数据转二进数据
|
hexStringToArrayBuffer(hexStr) {
|
const bytes = new Uint8Array(hexStr.match(/.{2}/g).map(byte => parseInt(byte, 16)));
|
return bytes.buffer;
|
},
|
// 二进数据转16进制数据
|
ab2hex(buffer) {
|
const hexArr = Array.prototype.map.call(
|
new Uint8Array(buffer),
|
function(bit) {
|
return ('00' + bit.toString(16)).slice(-2)
|
}
|
)
|
return hexArr.join('')
|
},
|
calcCRC16(data) {
|
// 创建查表
|
const table = this.buildCRC16Table();
|
let crc = 0;
|
// 计算CRC
|
for (let i = 0; i < data.length; i++) {
|
crc = ((crc << 8) & 0xFF00) ^ table[((crc >> 8) ^ data[i]) & 0xFF];
|
}
|
return crc & 0xFFFF;
|
},
|
buildCRC16Table() {
|
const table = new Array(256);
|
const poly = 0x1021; // CRC-CCITT多项式
|
for (let i = 0; i < 256; i++) {
|
let nData = i << 8;
|
let nAccum = 0;
|
for (let j = 0; j < 8; j++) {
|
if ((nData ^ nAccum) & 0x8000) {
|
nAccum = (nAccum << 1) ^ poly;
|
} else {
|
nAccum <<= 1;
|
}
|
nData <<= 1;
|
}
|
table[i] = nAccum & 0xFFFF;
|
}
|
return table;
|
},
|
// 密码置换掉ID号中"屏的大小",加密下发
|
replaceScreenSizeInID(originalHexString, password4Digit) {
|
// 密码4个数字,每二个合起来一个字节,如3472变成 0x34 0x72
|
const high = password4Digit.slice(0, 2); // "34"
|
const low = password4Digit.slice(2, 4); // "72"
|
|
// 替换屏幕大小字段(第 10、11 个字节,即第20-23个字符)
|
const before = originalHexString.slice(0, 20);
|
const after = originalHexString.slice(24);
|
|
return before + high + low + after;
|
},
|
// 解密函数
|
decryptSmartShellID(encryptedHex) {
|
const key = '2021101236011119680215001413714294505017';
|
const decrypted = [];
|
for (let i = 0; i < 20; i++) {
|
const byteHex = encryptedHex.substr(i * 2, 2);
|
const encryptedByte = parseInt(byteHex, 16);
|
const keyHex = key.substr(i * 2, 2)
|
const keyByte = parseInt(keyHex, 16);
|
const decryptedByte = (encryptedByte - keyByte);
|
decrypted.push(decryptedByte.toString(16).padStart(2, '0').toUpperCase());
|
}
|
return decrypted.join('');
|
},
|
// 加密函数
|
encryptSmartShellID(encryptedHex) {
|
const key = '2021101236011119680215001413714294505017';
|
const decrypted = [];
|
for (let i = 0; i < 20; i++) {
|
const byteHex = encryptedHex.substr(i * 2, 2);
|
const encryptedByte = parseInt(byteHex, 16);
|
const keyHex = key.substr(i * 2, 2)
|
const keyByte = parseInt(keyHex, 16);
|
const decryptedByte = (encryptedByte + keyByte);
|
decrypted.push(decryptedByte.toString(16).padStart(2, '0').toUpperCase());
|
}
|
return decrypted.join('');
|
},
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.btns_two {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 0 40rpx;
|
margin-top: 50rpx;
|
|
.btn2 {
|
width: 47%;
|
}
|
}
|
|
.cont_led {
|
max-height: 700rpx;
|
overflow-y: auto;
|
}
|
|
.popup_pwd {
|
width: 680rpx;
|
box-sizing: border-box;
|
padding: 0 40rpx 50rpx;
|
|
.led_item {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 15rpx 0;
|
.led_item_text{
|
width: 100%;
|
padding:0 20rpx;
|
}
|
.name {
|
font-size: 30rpx;
|
color: #333;
|
line-height: 50rpx;
|
}
|
.text{
|
font-size: 28rpx;
|
color: #666;
|
line-height: 50rpx;
|
}
|
|
.btn {
|
flex-shrink: 0;
|
width: 120rpx;
|
}
|
}
|
|
.title {
|
text-align: center;
|
font-weight: 500;
|
font-size: 32rpx;
|
color: rgba(0, 0, 0, 0.9);
|
line-height: 42rpx;
|
padding: 50rpx 0 40rpx;
|
}
|
|
.btns {
|
margin-top: 40rpx;
|
}
|
}
|
|
.device_list {
|
padding: 0 30rpx;
|
|
.device_item {
|
background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%);
|
box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4);
|
border-radius: 20rpx;
|
border: 2rpx solid #FFFFFF;
|
padding: 22rpx;
|
margin-top: 24rpx;
|
position: relative;
|
|
.item_btn {
|
display: flex;
|
align-items: center;
|
justify-content: flex-end;
|
margin-top: 20rpx;
|
border-top: 1rpx dashed #ddd;
|
padding-top: 22rpx;
|
gap: 20rpx;
|
}
|
|
.item {
|
display: flex;
|
align-items: stretch;
|
justify-content: space-between;
|
position: relative;
|
}
|
|
.ph {
|
display: block;
|
flex-shrink: 0;
|
width: 230rpx;
|
height: 170rpx;
|
border-radius: 20rpx;
|
margin-right: 24rpx;
|
}
|
|
.info {
|
width: 100%;
|
display: flex;
|
flex-direction: column;
|
justify-content: space-between;
|
position: relative;
|
|
.name {
|
font-weight: 500;
|
font-size: 27rpx;
|
color: #000000;
|
line-height: 50rpx;
|
box-sizing: border-box;
|
padding-right: 80rpx;
|
}
|
|
.version {
|
font-size: 26rpx;
|
color: rgba(0, 0, 0, 0.7);
|
line-height: 40rpx;
|
}
|
|
.power {
|
font-size: 26rpx;
|
color: rgba(0, 0, 0, 0.7);
|
line-height: 40rpx;
|
height: 40rpx;
|
}
|
|
.state {
|
position: absolute;
|
top: 10rpx;
|
right: 0;
|
font-size: 22rpx;
|
color: rgba(0, 0, 0, 0.7);
|
line-height: 38rpx;
|
|
&.green {
|
color: #2BA471;
|
}
|
}
|
|
.btn {
|
position: absolute;
|
right: 0;
|
bottom: 0;
|
display: flex;
|
align-items: center;
|
justify-content: end;
|
|
.btn_none {
|
flex-shrink: 0;
|
width: 20rpx;
|
}
|
}
|
}
|
}
|
}
|
|
.add_device_small {
|
margin: 30rpx auto 0;
|
background: rgba(255, 255, 255, 0.95) url('/static/img/bg_1.png') no-repeat center bottom;
|
background-size: 100% auto;
|
box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.2), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4);
|
border-radius: 23rpx 23rpx 23rpx 23rpx;
|
border: 2rpx solid #FFFFFF;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 20rpx 30rpx 10rpx;
|
|
.ico_add {
|
width: 100rpx;
|
}
|
|
.name {
|
font-weight: 400;
|
font-size: 31rpx;
|
color: rgba(0, 0, 0, 0.7);
|
line-height: 40rpx;
|
margin-bottom: 10rpx;
|
text-align: center;
|
}
|
}
|
|
.add_device {
|
margin: 60rpx auto 0;
|
width: 688rpx;
|
height: 319rpx;
|
background: rgba(255, 255, 255, 0.95) url('/static/img/bg_1.png') no-repeat center bottom;
|
background-size: 100% auto;
|
border-radius: 23rpx 23rpx 23rpx 23rpx;
|
border: 2rpx solid #FFFFFF;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
flex-direction: column;
|
|
.ico_add {
|
width: 220rpx;
|
}
|
|
.name {
|
font-weight: 400;
|
font-size: 31rpx;
|
color: rgba(0, 0, 0, 0.7);
|
line-height: 40rpx;
|
margin-bottom: 10rpx;
|
text-align: center;
|
}
|
}
|
|
.hi {
|
text-align: center;
|
color: #fff;
|
font-weight: 500;
|
font-size: 42rpx;
|
line-height: 42rpx;
|
padding: 40rpx 0 0;
|
}
|
|
.page_bg {
|
width: 100%;
|
min-height: calc(100vh - var(--window-bottom));
|
background: linear-gradient(3deg, rgba(114, 229, 255, 0) 14%, rgba(114, 229, 255, 0.2) 33%, rgba(114, 229, 255, 0.4) 48%, #72E5FF 62%, #3B74EE 100%);
|
}
|
|
.page_bg1 {
|
width: 100%;
|
min-height: calc(100vh - var(--window-bottom));
|
background: url('/static/img/bg_2.jpg') no-repeat center top;
|
background-size: 100% auto;
|
}
|
</style>
|