2025-08-27 11:06:49 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="container">
|
|
|
|
|
|
<!-- 蓝牙设备列表页 -->
|
|
|
|
|
|
<view v-if="currentPage === 'deviceList'">
|
|
|
|
|
|
<view class="title">蓝牙设备列表</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="status-bar">
|
|
|
|
|
|
<text>蓝牙状态: {{ bluetoothStatus }}</text>
|
|
|
|
|
|
<button class="btn" :disabled="isLoading" @click="toggleBluetooth">
|
|
|
|
|
|
{{ isBluetoothOpen ? '关闭蓝牙' : '开启蓝牙' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="search-area">
|
|
|
|
|
|
<button class="btn search-btn" :disabled="!isBluetoothOpen || isSearching" @click="startSearch">
|
|
|
|
|
|
{{ isSearching ? '正在搜索...' : '搜索设备' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<text class="tips">{{ searchTips }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="devices-list" v-if="deviceList.length > 0">
|
|
|
|
|
|
<view class="device-item" v-for="(device, index) in deviceList" :key="index"
|
|
|
|
|
|
:class="{'active': device.name === targetDeviceName}"
|
|
|
|
|
|
@click="connectDevice(device.deviceId, device.name)">
|
|
|
|
|
|
<text class="device-name">{{ device.name || '未知设备' }}</text>
|
|
|
|
|
|
<text class="device-rssi">{{ device.RSSI }}dBm</text>
|
|
|
|
|
|
<text class="device-status" v-if="device.name === targetDeviceName">已连接</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 主控制页面 -->
|
|
|
|
|
|
<view v-if="currentPage === 'controlPanel'">
|
|
|
|
|
|
<view class="title">6155控制器</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="status-bar">
|
|
|
|
|
|
<text>已连接: {{ targetDeviceName }}</text>
|
|
|
|
|
|
<button class="btn" @click="disconnectDevice(null)">断开连接</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<text>发送间隔:</text>
|
|
|
|
|
|
<input type="number" v-model="inteval" class="txt" />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<view class="function-tabs">
|
|
|
|
|
|
<button class="tab-btn" :class="{'active': currentTab === 'device'}" @click="getDevice()">设备信息</button>
|
|
|
|
|
|
<button class="tab-btn" :class="{'active': currentTab === 'mode'}"
|
|
|
|
|
|
@click="currentTab = 'mode'">主灯模式</button>
|
|
|
|
|
|
<button class="tab-btn" :class="{'active': currentTab === 'fumode'}"
|
|
|
|
|
|
@click="currentTab = 'fumode'">副灯模式</button>
|
|
|
|
|
|
<button class="tab-btn" :class="{'active': currentTab === 'brightness'}"
|
|
|
|
|
|
@click="currentTab = 'brightness'">亮度调节</button>
|
|
|
|
|
|
<button class="tab-btn" :class="{'active': currentTab === 'splash'}"
|
|
|
|
|
|
@click="currentTab = 'splash'">开机画面</button>
|
|
|
|
|
|
<button class="tab-btn" :class="{'active': currentTab === 'text'}"
|
|
|
|
|
|
@click="currentTab = 'text'">文字设置</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 设备信息选项卡 -->
|
|
|
|
|
|
<view v-if="currentTab === 'device'">
|
|
|
|
|
|
<view class="modes-area">
|
|
|
|
|
|
<view class="mode-title">固件版本:{{gujianVer}}</view>
|
|
|
|
|
|
<view class="mode-title">Mac地址:{{MacAdd}}</view>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<view class="modes-grid">
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': currentMode === 'close'}" @click="ReStart()">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">设备重启</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': currentMode === 'close'}" @click="Restore()">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">恢复出厂设置</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模式设置选项卡 -->
|
|
|
|
|
|
<view v-if="currentTab === 'mode'">
|
|
|
|
|
|
<view class="modes-area">
|
|
|
|
|
|
<view class="mode-title">选择灯光模式</view>
|
|
|
|
|
|
<view class="modes-grid">
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': currentMode === 'close'}"
|
|
|
|
|
|
@click="setMode('close','main')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">关闭</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': currentMode === 'low'}"
|
|
|
|
|
|
@click="setMode('low','main')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">弱光</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': currentMode === 'work'}"
|
|
|
|
|
|
@click="setMode('work','main')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">工作光</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': currentMode === 'high'}"
|
|
|
|
|
|
@click="setMode('high','main')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">强光</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': currentMode === 'flash'}"
|
|
|
|
|
|
@click="setMode('flash','main')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">爆闪</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view v-if="currentTab === 'fumode'">
|
|
|
|
|
|
<view class="modes-area">
|
|
|
|
|
|
<view class="mode-title">选择灯光模式</view>
|
|
|
|
|
|
<view class="modes-grid">
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': fucurrentMode === 'close'}"
|
|
|
|
|
|
@click="setMode('close','fu')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">关闭</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': fucurrentMode === 'low'}"
|
|
|
|
|
|
@click="setMode('low','fu')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">弱光</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': fucurrentMode === 'work'}"
|
|
|
|
|
|
@click="setMode('work','fu')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">工作光</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': fucurrentMode === 'high'}"
|
|
|
|
|
|
@click="setMode('high','fu')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">强光</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="mode-item" :class="{'selected': fucurrentMode === 'flash'}"
|
|
|
|
|
|
@click="setMode('flash','fu')">
|
|
|
|
|
|
|
|
|
|
|
|
<text class="mode-name">爆闪</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 亮度调节选项卡 -->
|
|
|
|
|
|
<view v-if="currentTab === 'brightness'">
|
|
|
|
|
|
<view class="brightness-area">
|
|
|
|
|
|
<view class="brightness-title">亮度调节</view>
|
|
|
|
|
|
<view class="brightness-value">{{ brightness }}%</view>
|
|
|
|
|
|
<slider class="brightness-slider" :value="brightness" min="0" max="100"
|
|
|
|
|
|
@changing="onBrightnessChanging" @change="onBrightnessChanged" />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 开机画面选项卡 -->
|
|
|
|
|
|
<view v-if="currentTab === 'splash'">
|
|
|
|
|
|
<view class="splash-controls">
|
|
|
|
|
|
<button class="btn" @click="importImage">导入图片</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button class="btn" :disabled="!hasImage || isSending" @click="sendSplashImage">确认发送</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="splash-preview">
|
|
|
|
|
|
<view class="splash-frame">
|
|
|
|
|
|
<image v-if="tempImagePath" :src="tempImagePath" mode="aspectFill" :style="imageStyle"
|
|
|
|
|
|
@touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"></image>
|
|
|
|
|
|
<view v-else class="empty-frame">点击"导入图片"选择开机画面</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="frame-info">
|
|
|
|
|
|
<text>框选区域: 160×80像素</text>
|
|
|
|
|
|
<text v-if="hasImage">缩放: {{ scale.toFixed(2) }}x</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="sending-progress" v-if="isSending">
|
|
|
|
|
|
<view class="progress-bar">
|
|
|
|
|
|
<view class="progress-fill" :style="{ width: progress + '%' }"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text>正在发送: {{ progress }}% ({{ currentPacket }}/{{ totalPackets }})</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 文字设置选项卡 -->
|
|
|
|
|
|
<view v-if="currentTab === 'text'">
|
|
|
|
|
|
<TextToHex class="TextToHex" ref="textToHex" :txts="textLines" :bgColor="'#000000'" :color="'#FFFFFF'"
|
|
|
|
|
|
:fontSize="16" />
|
|
|
|
|
|
<view class="text-inputs">
|
|
|
|
|
|
<view class="text-line" v-for="(line, index) in textLines" :key="index">
|
|
|
|
|
|
<text>第{{ index + 1 }}行:</text>
|
|
|
|
|
|
<input v-model="textLines[index]" placeholder="请输入文字" maxlength="30"
|
|
|
|
|
|
@confirm="onTextChanged(index)" />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<button class="btn send-text-btn" :disabled="isSendingText" @click="sendText">发送文字</button>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="sending-progress" v-if="isSendingText">
|
|
|
|
|
|
<view class="progress-bar">
|
|
|
|
|
|
<view class="progress-fill" :style="{ width: textProgress + '%' }"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text>正在发送: {{ textProgress }}% ({{ currentTextPacket }}/{{ totalTextPackets }})</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 隐藏的canvas用于图片处理 -->
|
|
|
|
|
|
<canvas canvas-id="splashCanvas" style="width: 160px; height: 80px; z-index: 9999;position: fixed; "
|
|
|
|
|
|
:style="{top:canvasTop,left:canvasLeft}"></canvas>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import gbk from '@/utils/gbk.js';
|
2025-09-15 11:55:44 +08:00
|
|
|
|
import TextToHexVue from '@/components/TextToHex/TextToHexV1.vue';
|
2025-08-27 11:06:49 +08:00
|
|
|
|
export default {
|
|
|
|
|
|
comments: {
|
|
|
|
|
|
TextToHexVue
|
|
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
gujianVer: '',
|
|
|
|
|
|
MacAdd: '',
|
|
|
|
|
|
|
|
|
|
|
|
canvasTop: '-1000px',
|
|
|
|
|
|
canvasLeft: '-1000px',
|
|
|
|
|
|
inteval: 100,
|
|
|
|
|
|
isLoading: false,
|
|
|
|
|
|
isBluetoothOpen: false,
|
|
|
|
|
|
isSearching: false,
|
|
|
|
|
|
isConnected: false,
|
|
|
|
|
|
isSending: false,
|
|
|
|
|
|
isSendingText: false,
|
|
|
|
|
|
bluetoothStatus: '未初始化',
|
|
|
|
|
|
searchTips: '',
|
|
|
|
|
|
deviceList: [],
|
|
|
|
|
|
connectedDeviceId: '',
|
|
|
|
|
|
serviceId: '0xFFE1',
|
|
|
|
|
|
writeCharacteristicId: '0xFFE1',
|
|
|
|
|
|
notifyCharacteristicId: '0xFFE2',
|
|
|
|
|
|
currentMode: '',
|
|
|
|
|
|
fucurrentMode: '',
|
|
|
|
|
|
logList: [],
|
|
|
|
|
|
packetCount: 1,
|
|
|
|
|
|
currentPage: 'deviceList',
|
|
|
|
|
|
targetDeviceName: 'JQZM-EF4651',
|
|
|
|
|
|
currentTab: 'mode',
|
|
|
|
|
|
brightness: 50,
|
|
|
|
|
|
brightnessTimer: null,
|
|
|
|
|
|
tempImagePath: '',
|
|
|
|
|
|
hasImage: false,
|
|
|
|
|
|
scale: 1,
|
|
|
|
|
|
translateX: 0,
|
|
|
|
|
|
translateY: 0,
|
|
|
|
|
|
lastX: 0,
|
|
|
|
|
|
lastY: 0,
|
|
|
|
|
|
isDragging: false,
|
|
|
|
|
|
progress: 0,
|
|
|
|
|
|
currentPacket: 0,
|
|
|
|
|
|
totalPackets: 100,
|
|
|
|
|
|
imageWidth: 0,
|
|
|
|
|
|
imageHeight: 0,
|
|
|
|
|
|
textLines: ['你好', '你是谁', '你在哪'],
|
|
|
|
|
|
textProgress: 0,
|
|
|
|
|
|
currentTextPacket: 0,
|
|
|
|
|
|
totalTextPackets: 4
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
imageStyle() {
|
|
|
|
|
|
return `transform: scale(${this.scale}) translate(${this.translateX}px, ${this.translateY}px); transform-origin: 0 0;`;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onLoad() {
|
|
|
|
|
|
this.initBluetoothAdapter();
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
//获取设备信息
|
|
|
|
|
|
getDevice() {
|
|
|
|
|
|
this.currentTab = 'device';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 创建 ArrayBuffer 和 DataView
|
|
|
|
|
|
var buffer = new ArrayBuffer(4);
|
|
|
|
|
|
var dataView = new DataView(buffer);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dataView.setUint8(0, 0x70);
|
|
|
|
|
|
dataView.setUint8(1, 0x65);
|
|
|
|
|
|
dataView.setUint8(2, 0xA1);
|
|
|
|
|
|
dataView.setUint8(3, 0x00);
|
|
|
|
|
|
|
|
|
|
|
|
this.sendData(buffer).then(() => {
|
|
|
|
|
|
console.log("发送命令成功,等待设备响应");
|
|
|
|
|
|
}).catch(err => {
|
|
|
|
|
|
console.log("发送命令失败", err);
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
// 蓝牙初始化
|
|
|
|
|
|
initBluetoothAdapter() {
|
|
|
|
|
|
console.log('开始初始化蓝牙适配器');
|
|
|
|
|
|
uni.openBluetoothAdapter({
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
this.isBluetoothOpen = true;
|
|
|
|
|
|
this.bluetoothStatus = '已开启';
|
|
|
|
|
|
console.log('蓝牙适配器初始化成功');
|
|
|
|
|
|
this.getBluetoothAdapterState();
|
|
|
|
|
|
|
|
|
|
|
|
// 自动开始搜索设备
|
|
|
|
|
|
this.startSearch();
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
this.bluetoothStatus = '初始化失败';
|
|
|
|
|
|
console.log(`蓝牙适配器初始化失败: ${err.errMsg}`);
|
|
|
|
|
|
if (err.errCode === 10001) {
|
|
|
|
|
|
uni.onBluetoothAdapterStateChange((res) => {
|
|
|
|
|
|
if (res.available) {
|
|
|
|
|
|
this.isBluetoothOpen = true;
|
|
|
|
|
|
this.bluetoothStatus = '已开启';
|
|
|
|
|
|
console.log('蓝牙适配器已开启');
|
|
|
|
|
|
this.startSearch();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取蓝牙适配器状态
|
|
|
|
|
|
getBluetoothAdapterState() {
|
|
|
|
|
|
uni.getBluetoothAdapterState({
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
this.isBluetoothOpen = res.available;
|
|
|
|
|
|
this.bluetoothStatus = res.available ? '已开启' : '已关闭';
|
|
|
|
|
|
if (!res.available) {
|
|
|
|
|
|
console.log('蓝牙适配器未开启');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 开关蓝牙
|
|
|
|
|
|
toggleBluetooth() {
|
|
|
|
|
|
if (this.isBluetoothOpen) {
|
|
|
|
|
|
this.closeBluetoothAdapter();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.initBluetoothAdapter();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭蓝牙适配器
|
|
|
|
|
|
closeBluetoothAdapter() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.stopSearch();
|
|
|
|
|
|
this.disconnectDevice(() => {
|
|
|
|
|
|
uni.closeBluetoothAdapter({
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
console.log("蓝牙已关闭");
|
|
|
|
|
|
this.isBluetoothOpen = false;
|
|
|
|
|
|
this.isSearching = false;
|
|
|
|
|
|
this.isConnected = false;
|
|
|
|
|
|
this.bluetoothStatus = '已关闭';
|
|
|
|
|
|
this.deviceList = [];
|
|
|
|
|
|
this.connectedDeviceId = '';
|
|
|
|
|
|
this.currentMode = '';
|
|
|
|
|
|
this.currentPage = 'deviceList';
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 开始搜索蓝牙设备
|
|
|
|
|
|
startSearch() {
|
|
|
|
|
|
if (this.isSearching) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.isSearching = true;
|
|
|
|
|
|
this.deviceList = [];
|
|
|
|
|
|
this.searchTips = '搜索中...';
|
|
|
|
|
|
console.log('开始搜索蓝牙设备');
|
|
|
|
|
|
|
|
|
|
|
|
uni.startBluetoothDevicesDiscovery({
|
|
|
|
|
|
services: ["0xFFE0"],
|
|
|
|
|
|
allowDuplicatesKey: false,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
console.log('开始搜索蓝牙设备成功');
|
|
|
|
|
|
this.onDeviceFound();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
this.isSearching = false;
|
|
|
|
|
|
this.searchTips = '搜索失败';
|
|
|
|
|
|
console.log(`搜索蓝牙设备失败: ${err.errMsg}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 停止搜索蓝牙设备
|
|
|
|
|
|
stopSearch() {
|
|
|
|
|
|
this.isSearching = false;
|
|
|
|
|
|
this.searchTips = this.deviceList.length > 0 ? '搜索完成' : '未发现蓝牙设备';
|
|
|
|
|
|
|
|
|
|
|
|
uni.stopBluetoothDevicesDiscovery({
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
console.log('停止搜索蓝牙设备');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 监听发现新设备
|
|
|
|
|
|
onDeviceFound() {
|
|
|
|
|
|
uni.onBluetoothDeviceFound((res) => {
|
|
|
|
|
|
var device = res.devices[0];
|
|
|
|
|
|
if (!device.name && !device.localName) return;
|
|
|
|
|
|
// if ((device.name || device.localName) === this.targetDeviceName) {
|
|
|
|
|
|
// 检查是否已存在该设备
|
|
|
|
|
|
var index = this.deviceList.findIndex(item => item.deviceId === device.deviceId);
|
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
|
this.deviceList.push({
|
|
|
|
|
|
deviceId: device.deviceId,
|
|
|
|
|
|
name: device.name || device.localName,
|
|
|
|
|
|
RSSI: device.RSSI
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 更新信号强度
|
|
|
|
|
|
this.deviceList[index].RSSI = device.RSSI;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// 如果找到目标设备,自动连接
|
|
|
|
|
|
if ((device.name || device.localName) === this.targetDeviceName) {
|
|
|
|
|
|
console.log(`发现目标设备: ${JSON.stringify(device)}`);
|
|
|
|
|
|
this.connectDevice(device.deviceId, this.targetDeviceName);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 连接蓝牙设备
|
|
|
|
|
|
connectDevice(deviceId, deviceName) {
|
|
|
|
|
|
if (this.isConnected && this.connectedDeviceId === deviceId) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.isLoading = true;
|
|
|
|
|
|
console.log(`开始连接设备: ${deviceName}`);
|
|
|
|
|
|
var these = this;
|
|
|
|
|
|
uni.createBLEConnection({
|
|
|
|
|
|
deviceId: deviceId,
|
|
|
|
|
|
timeout: 10000,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
this.isConnected = true;
|
|
|
|
|
|
this.connectedDeviceId = deviceId;
|
|
|
|
|
|
this.targetDeviceName = deviceName;
|
|
|
|
|
|
console.log(`连接设备 ${deviceName} 成功`);
|
|
|
|
|
|
this.enableNotification(deviceId);
|
|
|
|
|
|
// 连接成功后切换到控制面板
|
|
|
|
|
|
this.currentPage = 'controlPanel';
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
|
uni.setBLEMTU({
|
|
|
|
|
|
deviceId: deviceId,
|
|
|
|
|
|
mtu: 512,
|
|
|
|
|
|
success: (info) => {
|
|
|
|
|
|
console.log("设置mtu成功")
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (ex) => {
|
|
|
|
|
|
console.log("设置mtu失败" + JSON.stringify(ex));
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
these.getDeviceServices(deviceId);
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
// 获取服务和特征值
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 启用通知
|
|
|
|
|
|
|
|
|
|
|
|
this.stopSearch();
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.log(`连接设备失败: ${JSON.stringify(err)}`);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '连接失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
complete: () => {
|
|
|
|
|
|
this.isLoading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 断开蓝牙连接
|
|
|
|
|
|
disconnectDevice(callback) {
|
|
|
|
|
|
var these = this;
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.connectedDeviceId) {
|
|
|
|
|
|
this.isConnected = false;
|
|
|
|
|
|
this.connectedDeviceId = '';
|
|
|
|
|
|
this.currentMode = '';
|
|
|
|
|
|
this.currentPage = 'deviceList';
|
|
|
|
|
|
if (callback) {
|
|
|
|
|
|
callback();
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
uni.closeBLEConnection({
|
|
|
|
|
|
deviceId: this.connectedDeviceId,
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
|
|
|
|
|
|
console.log('蓝牙连接已断开');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (ex) => {
|
|
|
|
|
|
let arr = [{
|
|
|
|
|
|
code: 0,
|
|
|
|
|
|
remark: '正常'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10000,
|
|
|
|
|
|
remark: '未初始化蓝牙适配器'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10001,
|
|
|
|
|
|
remark: '当前蓝牙适配器不可用'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10002,
|
|
|
|
|
|
remark: '没有找到指定设备'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10003,
|
|
|
|
|
|
remark: '连接失败'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10004,
|
|
|
|
|
|
remark: '没有找到指定服务'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10005,
|
|
|
|
|
|
remark: '没有找到指定特征值'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10006,
|
|
|
|
|
|
remark: '当前连接已断开'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10007,
|
|
|
|
|
|
remark: '当前特征值不支持此操作'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10008,
|
|
|
|
|
|
remark: '其余所有系统上报的异常'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10009,
|
|
|
|
|
|
remark: 'Android 系统特有,系统版本低于 4.3 不支持 BLE'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10010,
|
|
|
|
|
|
remark: '已连接'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10011,
|
|
|
|
|
|
remark: '配对设备需要配对码'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10012,
|
|
|
|
|
|
remark: '连接超时'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 10013,
|
|
|
|
|
|
remark: '连接 deviceId 为空或者是格式不正确'
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
let f = arr.find(function(v) {
|
|
|
|
|
|
return v.code == ex.code;
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log("无法断开蓝牙连接:" + f.remark);
|
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "无法断开蓝牙连接:" + f.remark,
|
|
|
|
|
|
icon: 'fail'
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
complete: () => {
|
|
|
|
|
|
this.isConnected = false;
|
|
|
|
|
|
this.connectedDeviceId = '';
|
|
|
|
|
|
this.currentMode = '';
|
|
|
|
|
|
this.currentPage = 'deviceList';
|
|
|
|
|
|
if (callback) {
|
|
|
|
|
|
callback();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取设备服务
|
|
|
|
|
|
getDeviceServices(deviceId) {
|
|
|
|
|
|
console.log("deviceId=" + deviceId);
|
|
|
|
|
|
var these = this;
|
|
|
|
|
|
uni.getBLEDeviceServices({
|
|
|
|
|
|
deviceId: deviceId,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
|
|
|
|
|
|
console.log("发现服务" + JSON.stringify(res.services))
|
|
|
|
|
|
if (res.services.length == 0) {
|
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
|
these.getDeviceServices(deviceId);
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 查找目标服务
|
|
|
|
|
|
var targetService = res.services.find(service => service.uuid.indexOf('FFE0') > -1);
|
|
|
|
|
|
|
|
|
|
|
|
if (targetService) {
|
|
|
|
|
|
console.log(`找到目标服务: ${targetService.uuid}`);
|
|
|
|
|
|
this.serviceId = targetService.uuid;
|
|
|
|
|
|
this.getDeviceCharacteristics(deviceId, targetService.uuid);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.log(`获取设备服务失败: ${err.errMsg}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取设备特征值
|
|
|
|
|
|
getDeviceCharacteristics(deviceId, serviceId) {
|
|
|
|
|
|
var these = this;
|
|
|
|
|
|
uni.getBLEDeviceCharacteristics({
|
|
|
|
|
|
deviceId: deviceId,
|
|
|
|
|
|
serviceId: serviceId,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
console.log(`获取设备特征值成功,共发现 ${res.characteristics.length} 个特征值`);
|
|
|
|
|
|
console.log(`获取设备特征值成功${JSON.stringify(res.characteristics)}`);
|
|
|
|
|
|
// 查找可写特征值
|
|
|
|
|
|
var writeChar = res.characteristics.find(char =>
|
|
|
|
|
|
char.uuid.indexOf("FFE1") >= 1
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 查找通知特征值
|
|
|
|
|
|
var notifyChar = res.characteristics.find(char => {
|
|
|
|
|
|
return char.properties.notify;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (writeChar) {
|
|
|
|
|
|
console.log(`找到可写特征值: ${writeChar.uuid}`);
|
|
|
|
|
|
this.writeCharacteristicId = writeChar.uuid;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log(`未找到可写特征值: ${this.writeCharacteristicId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (notifyChar) {
|
|
|
|
|
|
console.log(`找到通知特征值: ${notifyChar.uuid}`);
|
|
|
|
|
|
this.notifyCharacteristicId = notifyChar.uuid;
|
|
|
|
|
|
console.log("serviceId=" + serviceId);
|
|
|
|
|
|
console.log("characteristicId=" + notifyChar.uuid);
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.notifyBLECharacteristicValueChange({
|
|
|
|
|
|
deviceId: deviceId,
|
|
|
|
|
|
serviceId: serviceId,
|
|
|
|
|
|
characteristicId: notifyChar.uuid,
|
|
|
|
|
|
state: true,
|
|
|
|
|
|
success: (info) => {
|
|
|
|
|
|
console.log('启用通知成功' + JSON.stringify(info));
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "订阅消息成功",
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
});
|
|
|
|
|
|
// 监听特征值变化
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.onBLECharacteristicValueChange((
|
|
|
|
|
|
receive) => {
|
|
|
|
|
|
console.log("receive=",
|
|
|
|
|
|
receive);
|
|
|
|
|
|
var hexs = [];
|
|
|
|
|
|
var bytesToHex =
|
|
|
|
|
|
function(bytes) {
|
|
|
|
|
|
return bytes.map(
|
|
|
|
|
|
byte =>
|
|
|
|
|
|
byte
|
|
|
|
|
|
.toString(
|
|
|
|
|
|
16)
|
|
|
|
|
|
.padStart(
|
|
|
|
|
|
2, '0')
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var parseData = (
|
|
|
|
|
|
bytes) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 跳过帧头(第一二个字节),从第三个字节开始解析
|
|
|
|
|
|
let type =
|
|
|
|
|
|
bytes[2];
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case 0xA1: //获取设备信息
|
|
|
|
|
|
let v1 =
|
|
|
|
|
|
parseInt(
|
|
|
|
|
|
bytes[
|
|
|
|
|
|
3
|
|
|
|
|
|
],
|
|
|
|
|
|
16
|
|
|
|
|
|
);
|
|
|
|
|
|
let v2 =
|
|
|
|
|
|
parseInt(
|
|
|
|
|
|
bytes[
|
|
|
|
|
|
4
|
|
|
|
|
|
],
|
|
|
|
|
|
16
|
|
|
|
|
|
);
|
|
|
|
|
|
let vers =
|
|
|
|
|
|
`${v1}.${v2}`;
|
|
|
|
|
|
console
|
|
|
|
|
|
.log(
|
|
|
|
|
|
hexs[
|
|
|
|
|
|
5
|
|
|
|
|
|
]
|
|
|
|
|
|
.toString()
|
|
|
|
|
|
);
|
|
|
|
|
|
let mac =
|
|
|
|
|
|
`${hexs[5]}:${hexs[6]}:${hexs[7]}:${hexs[8]}:${hexs[9]}:${hexs[10]}`;
|
|
|
|
|
|
console
|
|
|
|
|
|
.log(
|
|
|
|
|
|
"解析成功,固件版本:" +
|
|
|
|
|
|
vers +
|
|
|
|
|
|
',MAC地址:' +
|
|
|
|
|
|
mac
|
|
|
|
|
|
);
|
|
|
|
|
|
these
|
|
|
|
|
|
.MacAdd =
|
|
|
|
|
|
mac;
|
|
|
|
|
|
these
|
|
|
|
|
|
.gujianVer =
|
|
|
|
|
|
vers;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 0xB2: //设备主动上报数据
|
|
|
|
|
|
let main =
|
|
|
|
|
|
bytes[
|
|
|
|
|
|
3
|
|
|
|
|
|
]; //主灯
|
|
|
|
|
|
if (main ==
|
|
|
|
|
|
0x00
|
|
|
|
|
|
) {
|
|
|
|
|
|
main =
|
|
|
|
|
|
'关闭';
|
|
|
|
|
|
these
|
|
|
|
|
|
.currentMode =
|
|
|
|
|
|
'close';
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
main ==
|
|
|
|
|
|
0x01
|
|
|
|
|
|
) {
|
|
|
|
|
|
main =
|
|
|
|
|
|
'弱光模式';
|
|
|
|
|
|
these
|
|
|
|
|
|
.currentMode =
|
|
|
|
|
|
'low';
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
main ==
|
|
|
|
|
|
0x02
|
|
|
|
|
|
) {
|
|
|
|
|
|
main =
|
|
|
|
|
|
'工作光模式';
|
|
|
|
|
|
these
|
|
|
|
|
|
.currentMode =
|
|
|
|
|
|
'work';
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
main ==
|
|
|
|
|
|
0x03
|
|
|
|
|
|
) {
|
|
|
|
|
|
main =
|
|
|
|
|
|
'强光模式';
|
|
|
|
|
|
these
|
|
|
|
|
|
.currentMode =
|
|
|
|
|
|
'high';
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
main ==
|
|
|
|
|
|
0x04
|
|
|
|
|
|
) {
|
|
|
|
|
|
main =
|
|
|
|
|
|
'爆闪模式';
|
|
|
|
|
|
these
|
|
|
|
|
|
.currentMode =
|
|
|
|
|
|
'flash';
|
|
|
|
|
|
}
|
|
|
|
|
|
let fu =
|
|
|
|
|
|
bytes[
|
|
|
|
|
|
4
|
|
|
|
|
|
]; //副灯
|
|
|
|
|
|
if (fu ==
|
|
|
|
|
|
0x00
|
|
|
|
|
|
) {
|
|
|
|
|
|
fu = '关闭'
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
fu ==
|
|
|
|
|
|
0x01
|
|
|
|
|
|
) {
|
|
|
|
|
|
fu = '弱光模式'
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
fu ==
|
|
|
|
|
|
0x02
|
|
|
|
|
|
) {
|
|
|
|
|
|
fu = '工作光模式'
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
fu ==
|
|
|
|
|
|
0x03
|
|
|
|
|
|
) {
|
|
|
|
|
|
fu = '强光模式'
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
fu ==
|
|
|
|
|
|
0x04
|
|
|
|
|
|
) {
|
|
|
|
|
|
fu = '爆闪模式'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let battery =
|
|
|
|
|
|
bytes[
|
|
|
|
|
|
5
|
|
|
|
|
|
]; //电量
|
|
|
|
|
|
let batteryLevel =
|
|
|
|
|
|
Math
|
|
|
|
|
|
.max(
|
|
|
|
|
|
0,
|
|
|
|
|
|
Math
|
|
|
|
|
|
.min(
|
|
|
|
|
|
100,
|
|
|
|
|
|
battery
|
|
|
|
|
|
)
|
|
|
|
|
|
); //电量百分比
|
|
|
|
|
|
|
|
|
|
|
|
let addBatt =
|
|
|
|
|
|
bytes[
|
|
|
|
|
|
6
|
|
|
|
|
|
]; //充电状态
|
|
|
|
|
|
if (addBatt ==
|
|
|
|
|
|
0x00
|
|
|
|
|
|
) {
|
|
|
|
|
|
addBatt
|
|
|
|
|
|
=
|
|
|
|
|
|
"未充电"
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
addBatt ==
|
|
|
|
|
|
0x01
|
|
|
|
|
|
) {
|
|
|
|
|
|
addBatt
|
|
|
|
|
|
=
|
|
|
|
|
|
"充电中"
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
addBatt ==
|
|
|
|
|
|
0x02
|
|
|
|
|
|
) {
|
|
|
|
|
|
addBatt
|
|
|
|
|
|
=
|
|
|
|
|
|
"已充满"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let lightingTime =
|
|
|
|
|
|
(bytes[
|
|
|
|
|
|
7
|
|
|
|
|
|
] <<
|
|
|
|
|
|
8
|
|
|
|
|
|
) |
|
|
|
|
|
|
bytes[
|
|
|
|
|
|
8
|
|
|
|
|
|
]; //续航时间
|
|
|
|
|
|
|
|
|
|
|
|
console
|
|
|
|
|
|
.log(
|
|
|
|
|
|
"解析成功,主灯模式:" +
|
|
|
|
|
|
main +
|
|
|
|
|
|
',副灯模式:' +
|
|
|
|
|
|
fu +
|
|
|
|
|
|
",电量:" +
|
|
|
|
|
|
batteryLevel +
|
|
|
|
|
|
"%,充电状态:" +
|
|
|
|
|
|
addBatt +
|
|
|
|
|
|
",续航时间:" +
|
|
|
|
|
|
lightingTime +
|
|
|
|
|
|
"分钟"
|
|
|
|
|
|
);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
console
|
|
|
|
|
|
.log(
|
|
|
|
|
|
"收到了未知的类型数据,无法解析"
|
|
|
|
|
|
);
|
|
|
|
|
|
try {
|
|
|
|
|
|
var uint8Array =
|
|
|
|
|
|
new Uint8Array(
|
|
|
|
|
|
receive
|
|
|
|
|
|
.value
|
|
|
|
|
|
);
|
|
|
|
|
|
let str =
|
|
|
|
|
|
'';
|
|
|
|
|
|
for (
|
|
|
|
|
|
let i =
|
|
|
|
|
|
0; i <
|
|
|
|
|
|
uint8Array
|
|
|
|
|
|
.length; i++
|
|
|
|
|
|
) {
|
|
|
|
|
|
// 将每个字节转换为对应的字符
|
|
|
|
|
|
str +=
|
|
|
|
|
|
String
|
|
|
|
|
|
.fromCharCode(
|
|
|
|
|
|
uint8Array[
|
|
|
|
|
|
i
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
console
|
|
|
|
|
|
.log(
|
|
|
|
|
|
"将数据转文本",
|
|
|
|
|
|
str
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (
|
|
|
|
|
|
ex
|
|
|
|
|
|
) {
|
|
|
|
|
|
console
|
|
|
|
|
|
.log(
|
|
|
|
|
|
"将数据转文本失败",
|
|
|
|
|
|
ex
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
'数据解析错误:',
|
|
|
|
|
|
error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var dataView =
|
|
|
|
|
|
new DataView(receive
|
|
|
|
|
|
.value);
|
|
|
|
|
|
console.log("dataView=",
|
|
|
|
|
|
dataView);
|
|
|
|
|
|
// 转换为字节数组
|
|
|
|
|
|
var bytes = [];
|
|
|
|
|
|
for (let i = 0; i <
|
|
|
|
|
|
dataView
|
|
|
|
|
|
.byteLength; i++) {
|
|
|
|
|
|
bytes.push(dataView
|
|
|
|
|
|
.getUint8(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存原始数据用于调试
|
|
|
|
|
|
this.receivedData = bytes;
|
|
|
|
|
|
hexs = bytesToHex(bytes);
|
|
|
|
|
|
console.log('接收到原始数据:',
|
|
|
|
|
|
hexs.join(' '));
|
|
|
|
|
|
|
|
|
|
|
|
parseData(bytes);
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.error('启用通知失败', err);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log(`未找到通知特征值: ${this.notifyCharacteristicId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.log(`获取设备特征值失败: ${err.errMsg}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 启用通知
|
|
|
|
|
|
enableNotification(deviceId) {
|
|
|
|
|
|
uni.notifyBLECharacteristicValueChange({
|
|
|
|
|
|
deviceId: deviceId,
|
|
|
|
|
|
serviceId: this.serviceId,
|
|
|
|
|
|
characteristicId: this.notifyCharacteristicId,
|
|
|
|
|
|
state: true,
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
console.log('启用通知成功');
|
|
|
|
|
|
|
|
|
|
|
|
// 监听通知
|
|
|
|
|
|
uni.onBLECharacteristicValueChange((res) => {
|
|
|
|
|
|
this.handleNotification(res.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.log(`启用通知失败: ${err.errMsg}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 处理通知数据
|
|
|
|
|
|
handleNotification(buffer) {
|
|
|
|
|
|
console.log("收到设备通知数据")
|
|
|
|
|
|
// 解析从设备接收的数据
|
|
|
|
|
|
var dataView = new DataView(buffer);
|
|
|
|
|
|
let result = '';
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < dataView.byteLength; i++) {
|
|
|
|
|
|
result += String.fromCharCode(dataView.getUint8(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`收到设备数据: ${result}`);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 设置灯光模式
|
|
|
|
|
|
setMode(mode, type) {
|
|
|
|
|
|
if (!this.isConnected) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '未连接设备',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (type == 'main') {
|
|
|
|
|
|
if (this.currentMode === mode) return;
|
|
|
|
|
|
type = 0x04;
|
|
|
|
|
|
} else if (type == 'fu') {
|
|
|
|
|
|
if (this.fucurrentMode === mode) return;
|
|
|
|
|
|
type = 0x05;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.currentMode = mode;
|
|
|
|
|
|
let dataValue = 0;
|
|
|
|
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
|
|
case 'close':
|
|
|
|
|
|
dataValue = 0x00;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'low':
|
|
|
|
|
|
dataValue = 0x01;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'work':
|
|
|
|
|
|
dataValue = 0x02;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'high':
|
|
|
|
|
|
dataValue = 0x03;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'flash':
|
|
|
|
|
|
dataValue = 0x04;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建数据包
|
|
|
|
|
|
var buffer = new ArrayBuffer(4);
|
|
|
|
|
|
var dataView = new DataView(buffer);
|
|
|
|
|
|
|
|
|
|
|
|
dataView.setUint8(0, 0x70); // 帧头
|
|
|
|
|
|
dataView.setUint8(1, 0x65); // 帧头
|
|
|
|
|
|
dataView.setUint8(2, type); // 帧类型
|
|
|
|
|
|
|
|
|
|
|
|
dataView.setUint8(3, dataValue); // 数据
|
|
|
|
|
|
|
|
|
|
|
|
// 发送数据
|
|
|
|
|
|
this.sendData(buffer);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 亮度调节
|
|
|
|
|
|
onBrightnessChanging(e) {
|
|
|
|
|
|
this.brightness = e.detail.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 清除之前的定时器
|
|
|
|
|
|
if (this.brightnessTimer) {
|
|
|
|
|
|
clearTimeout(this.brightnessTimer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置新的定时器,控制发送频率
|
|
|
|
|
|
this.brightnessTimer = setTimeout(() => {
|
|
|
|
|
|
this.sendBrightness();
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onBrightnessChanged(e) {
|
|
|
|
|
|
this.brightness = e.detail.value;
|
|
|
|
|
|
//this.sendBrightness();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
sendBrightness() {
|
|
|
|
|
|
if (!this.isConnected) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 构建数据包
|
|
|
|
|
|
var buffer = new ArrayBuffer(4);
|
|
|
|
|
|
var dataView = new DataView(buffer);
|
|
|
|
|
|
let data = '0x' + parseInt(this.brightness).toString(16);
|
|
|
|
|
|
console.log("亮度:" + this.brightness + ',16进制:' + data);
|
|
|
|
|
|
dataView.setUint8(0, 0x70); // 帧头
|
|
|
|
|
|
dataView.setUint8(1, 0x65); // 帧类型:亮度调节
|
|
|
|
|
|
dataView.setUint8(2, 0x09); // 包序号
|
|
|
|
|
|
dataView.setUint8(3, data); // 数据
|
|
|
|
|
|
|
|
|
|
|
|
// 发送数据
|
|
|
|
|
|
this.sendData(buffer);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 导入图片
|
|
|
|
|
|
importImage() {
|
|
|
|
|
|
uni.chooseImage({
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
sourceType: ['album'],
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
this.tempImagePath = res.tempFilePaths[0];
|
|
|
|
|
|
this.hasImage = true;
|
|
|
|
|
|
this.scale = 1;
|
|
|
|
|
|
this.translateX = 0;
|
|
|
|
|
|
this.translateY = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取图片尺寸
|
|
|
|
|
|
uni.getImageInfo({
|
|
|
|
|
|
src: this.tempImagePath,
|
|
|
|
|
|
success: (info) => {
|
|
|
|
|
|
this.imageWidth = info.width;
|
|
|
|
|
|
this.imageHeight = info.height;
|
|
|
|
|
|
this.autoFitImage();
|
|
|
|
|
|
console.log('图片导入成功,请调整图片位置和大小');
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.log(`获取图片信息失败: ${err.errMsg}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.log(`选择图片失败: ${err.errMsg}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 自动适配图片到容器
|
|
|
|
|
|
autoFitImage() {
|
|
|
|
|
|
var frameWidth = 320; // 预览框宽度,根据实际UI调整
|
|
|
|
|
|
var frameHeight = 640; // 预览框高度,根据实际UI调整
|
|
|
|
|
|
|
|
|
|
|
|
var widthRatio = frameWidth / this.imageWidth;
|
|
|
|
|
|
var heightRatio = frameHeight / this.imageHeight;
|
|
|
|
|
|
|
|
|
|
|
|
// 取较小的缩放比例,确保图片能完全显示在容器内
|
|
|
|
|
|
this.scale = Math.min(widthRatio, heightRatio);
|
|
|
|
|
|
|
|
|
|
|
|
// 居中显示
|
|
|
|
|
|
this.translateX = (frameWidth - this.imageWidth * this.scale) / 2;
|
|
|
|
|
|
this.translateY = (frameHeight - this.imageHeight * this.scale) / 2;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 触摸事件处理
|
|
|
|
|
|
onTouchStart(e) {
|
|
|
|
|
|
if (e.touches.length === 1) {
|
|
|
|
|
|
this.isDragging = true;
|
|
|
|
|
|
this.lastX = e.touches[0].clientX;
|
|
|
|
|
|
this.lastY = e.touches[0].clientY;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onTouchMove(e) {
|
|
|
|
|
|
if (this.isDragging && e.touches.length === 1) {
|
|
|
|
|
|
var currentX = e.touches[0].clientX;
|
|
|
|
|
|
var currentY = e.touches[0].clientY;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算位移
|
|
|
|
|
|
var deltaX = currentX - this.lastX;
|
|
|
|
|
|
var deltaY = currentY - this.lastY;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新位置
|
|
|
|
|
|
this.translateX += deltaX;
|
|
|
|
|
|
this.translateY += deltaY;
|
|
|
|
|
|
|
|
|
|
|
|
// 限制移动范围
|
|
|
|
|
|
this.limitImagePosition();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新最后位置
|
|
|
|
|
|
this.lastX = currentX;
|
|
|
|
|
|
this.lastY = currentY;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onTouchEnd() {
|
|
|
|
|
|
this.isDragging = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 限制图片移动范围
|
|
|
|
|
|
limitImagePosition() {
|
|
|
|
|
|
var frameWidth = 320; // 预览框宽度,根据实际UI调整
|
|
|
|
|
|
var frameHeight = 640; // 预览框高度,根据实际UI调整
|
|
|
|
|
|
|
|
|
|
|
|
var maxX = 0;
|
|
|
|
|
|
var minX = frameWidth - this.imageWidth * this.scale;
|
|
|
|
|
|
var maxY = 0;
|
|
|
|
|
|
var minY = frameHeight - this.imageHeight * this.scale;
|
|
|
|
|
|
|
|
|
|
|
|
this.translateX = Math.max(minX, Math.min(maxX, this.translateX));
|
|
|
|
|
|
this.translateY = Math.max(minY, Math.min(maxY, this.translateY));
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 发送开机画面
|
|
|
|
|
|
sendSplashImage() {
|
|
|
|
|
|
if (!this.hasImage) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '请先导入图片',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: '处理图片中...',
|
|
|
|
|
|
mask: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 将图片转换为RGB565格式并发送
|
|
|
|
|
|
this.convertAndSendImage().then(() => {
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
console.log('开机画面发送完成');
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '开机画面发送完成',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
});
|
|
|
|
|
|
}).catch((error) => {
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
console.log(`图片发送失败: ${error}`);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '图片发送失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 转换图片并发送
|
|
|
|
|
|
convertAndSendImage() {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
console.log("111111");
|
|
|
|
|
|
// 创建canvas用于处理图片
|
|
|
|
|
|
var ctx = uni.createCanvasContext('splashCanvas', this);
|
|
|
|
|
|
console.log("2222222");
|
|
|
|
|
|
// 绘制图片到canvas
|
|
|
|
|
|
ctx.drawImage(this.tempImagePath, 0, 0, 160, 80);
|
|
|
|
|
|
console.log("33333333");
|
|
|
|
|
|
try {
|
|
|
|
|
|
ctx.draw(false, () => {
|
|
|
|
|
|
console.log("4444444");
|
|
|
|
|
|
// 获取canvas像素数据
|
|
|
|
|
|
this.canvasTop = '0px';
|
|
|
|
|
|
this.canvasLeft = '0px';
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.canvasGetImageData({
|
|
|
|
|
|
canvasId: 'splashCanvas',
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
width: 160,
|
|
|
|
|
|
height: 80,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
// 处理像素数据并发送
|
|
|
|
|
|
console.log("res.data.length=" + res.data
|
|
|
|
|
|
.length);
|
|
|
|
|
|
this.processAndSendImageData(res.data).then(
|
|
|
|
|
|
resolve).catch(reject);
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
reject(`获取canvas数据失败: ${err.errMsg}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 300); // 等待canvas绘制完成
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
|
console.log("出现异常" + JSON.stringify(ex))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 处理像素数据并发送
|
|
|
|
|
|
processAndSendImageData(pixels) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
// 创建RGB565格式的像素数据
|
|
|
|
|
|
var rgb565Data = this.convertToRGB565(pixels, 160, 80);
|
|
|
|
|
|
// console.log("rgb565Data=,",rgb565Data);
|
|
|
|
|
|
// 分包发送
|
|
|
|
|
|
this.sendImagePackets(rgb565Data).then(resolve).catch(reject);
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 将RGBA转换为RGB565
|
|
|
|
|
|
convertToRGB565(pixels, width, height) {
|
|
|
|
|
|
|
|
|
|
|
|
if (!width) {
|
|
|
|
|
|
width = 160;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!height) {
|
|
|
|
|
|
height = 80;
|
|
|
|
|
|
}
|
|
|
|
|
|
var result = new Uint16Array(width * height);
|
|
|
|
|
|
let index = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < pixels.length; i += 4) {
|
|
|
|
|
|
var r = pixels[i];
|
|
|
|
|
|
var g = pixels[i + 1];
|
|
|
|
|
|
var b = pixels[i + 2];
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为RGB565 (R:5bit, G:6bit, B:5bit)
|
|
|
|
|
|
var rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
|
|
|
|
|
//let rgb565=rgbaToRgb565(r,g,b);
|
|
|
|
|
|
result[index++] = rgb565;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 分包发送图片数据
|
|
|
|
|
|
sendImagePackets(imageData) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
this.isSending = true;
|
|
|
|
|
|
this.progress = 0;
|
|
|
|
|
|
this.currentPacket = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 总数据包数
|
|
|
|
|
|
|
|
|
|
|
|
let currentPacket = 1;
|
|
|
|
|
|
let packetSize = 120;
|
|
|
|
|
|
let mode = imageData.length % packetSize;
|
|
|
|
|
|
let totalPackets = parseInt(imageData.length / packetSize) + (mode > 0 ? 1 : 0)
|
|
|
|
|
|
let inteval = parseInt(this.inteval ? this.inteval : 100);
|
|
|
|
|
|
this.totalPackets = totalPackets;
|
|
|
|
|
|
// 发送单个数据包
|
|
|
|
|
|
var sendNextPacket = () => {
|
|
|
|
|
|
if (currentPacket > totalPackets) {
|
|
|
|
|
|
this.isSending = false;
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var start = 0;
|
|
|
|
|
|
var dataLength = 0;
|
|
|
|
|
|
var bufferSize = 0;
|
|
|
|
|
|
if (currentPacket == 1) {
|
|
|
|
|
|
bufferSize = packetSize * 2 + 5;
|
|
|
|
|
|
dataLength = packetSize * 2;
|
|
|
|
|
|
start = 5;
|
|
|
|
|
|
} else if (currentPacket == totalPackets) {
|
|
|
|
|
|
bufferSize = mode > 0 ? (mode * 2) : (packetSize * 2);
|
|
|
|
|
|
dataLength = bufferSize
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bufferSize = packetSize * 2;
|
|
|
|
|
|
dataLength = bufferSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 创建数据包
|
|
|
|
|
|
var startIndex = (currentPacket - 1) * packetSize;
|
|
|
|
|
|
var endIndex = Math.min(startIndex + packetSize, imageData.length);
|
|
|
|
|
|
if (startIndex > endIndex) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var packetData = imageData.slice(startIndex,
|
|
|
|
|
|
endIndex); // imageData.subarray(startIndex, endIndex);
|
|
|
|
|
|
|
|
|
|
|
|
// 构建数据包
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var buffer = new ArrayBuffer(bufferSize);
|
|
|
|
|
|
var dataView = new DataView(buffer);
|
|
|
|
|
|
|
|
|
|
|
|
if (currentPacket == 1) { //第一包要填充头部
|
|
|
|
|
|
// 填充头部
|
|
|
|
|
|
dataView.setUint8(0, 0x70); // 帧头
|
|
|
|
|
|
dataView.setUint8(1, 0x65); // 帧类型:开机画面
|
|
|
|
|
|
dataView.setUint8(2, 0x0A); // 包序号
|
|
|
|
|
|
dataView.setUint16(3, dataLength, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
//console.log("packetSize="+packetSize+",startIndex="+startIndex+",endIndex="+endIndex+",imageData.length="+imageData.length+",start="+start+",packetData.length="+packetData.length);
|
|
|
|
|
|
for (let i = 0; i < packetData.length; i++) {
|
|
|
|
|
|
dataView.setUint16(start + i * 2, packetData[i], false); //大端字节序
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//发送数据包
|
|
|
|
|
|
this.sendData(buffer).then(() => {
|
|
|
|
|
|
// 更新进度
|
|
|
|
|
|
this.currentPacket = currentPacket;
|
|
|
|
|
|
this.progress = Math.round((currentPacket / totalPackets) * totalPackets);
|
|
|
|
|
|
|
|
|
|
|
|
currentPacket++;
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(sendNextPacket, inteval);
|
|
|
|
|
|
}).catch(err => {
|
|
|
|
|
|
|
|
|
|
|
|
if (err.code == 10007) {
|
|
|
|
|
|
setTimeout(sendNextPacket, inteval);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.isSending = false;
|
|
|
|
|
|
reject(err);
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//牵着你的手
|
|
|
|
|
|
// var HoldYouHand = () => {
|
|
|
|
|
|
// var str = "transmit start"; //握手的协议字符串
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // 1. 创建 ArrayBuffer 和 DataView
|
|
|
|
|
|
// var buffer = new ArrayBuffer(str.length);
|
|
|
|
|
|
// var dataView = new DataView(buffer);
|
|
|
|
|
|
|
|
|
|
|
|
// // 2. 将字符串转换为 ASCII 码并写入 DataView
|
|
|
|
|
|
// for (let i = 0; i < str.length; i++) {
|
|
|
|
|
|
// dataView.setUint8(i, str.charCodeAt(i));
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// this.sendData(buffer).then(() => {
|
|
|
|
|
|
// // 开始发送第一个包
|
|
|
|
|
|
// console.log("握手成功了,等待500ms");
|
|
|
|
|
|
// setTimeout(sendNextPacket, 500);
|
|
|
|
|
|
|
|
|
|
|
|
// }).catch(err => {
|
|
|
|
|
|
// console.log("握手没有成功");
|
|
|
|
|
|
// reject(err);
|
|
|
|
|
|
// });
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
sendNextPacket();
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 文字输入变化
|
|
|
|
|
|
onTextChanged(index) {
|
|
|
|
|
|
console.log(`第${index + 1}行文字已更新`);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 发送文字
|
|
|
|
|
|
async sendText() {
|
|
|
|
|
|
|
|
|
|
|
|
var sendTxtPackge = (rgbdata, type, str) => {
|
|
|
|
|
|
|
|
|
|
|
|
var promise = new Promise((resolve, reject) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
|
|
let packetSize = 120; //每包均分的数量
|
|
|
|
|
|
let mode = rgbdata.length % packetSize; //最后一包的数量
|
|
|
|
|
|
let cnt = parseInt(rgbdata.length / packetSize) + (mode > 0 ? 1 : 0); //总包数量
|
|
|
|
|
|
let curr = 1; //当前包序号
|
|
|
|
|
|
|
|
|
|
|
|
let sendNext = () => {
|
|
|
|
|
|
|
|
|
|
|
|
if (curr > cnt) {
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log("正在发送" + curr + "/" + cnt)
|
|
|
|
|
|
let bufferSize = 0;
|
|
|
|
|
|
if (curr == 1) {
|
|
|
|
|
|
bufferSize = packetSize * 2 + 5;
|
|
|
|
|
|
} else if (curr == cnt) {
|
|
|
|
|
|
bufferSize = mode > 0 ? (mode * 2) : (packetSize * 2);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
bufferSize = packetSize * 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let buffer = new ArrayBuffer(bufferSize);
|
|
|
|
|
|
let dataView = new DataView(buffer);
|
|
|
|
|
|
let startIndex = (curr - 1) * packetSize;
|
|
|
|
|
|
|
|
|
|
|
|
let endIndex = Math.min(startIndex + packetSize, rgbdata.length);
|
|
|
|
|
|
if (startIndex > endIndex) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let packetData = rgbdata.slice(startIndex, endIndex); //取一片数据发送
|
|
|
|
|
|
|
|
|
|
|
|
let start = 0;
|
|
|
|
|
|
if (curr == 1) {
|
|
|
|
|
|
dataView.setUint8(0, 0x70);
|
|
|
|
|
|
dataView.setUint8(1, 0x65);
|
|
|
|
|
|
dataView.setUint8(2, type);
|
|
|
|
|
|
dataView.setUint16(3, str.length, false);
|
|
|
|
|
|
start = 5;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (let i = 0; i < packetData.length; i++) {
|
|
|
|
|
|
dataView.setUint16(start + i * 2, packetData[i], false); // 大端字节序
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let inteval = parseInt(this.inteval ? this.inteval : 0);
|
|
|
|
|
|
|
|
|
|
|
|
//发送数据包
|
|
|
|
|
|
this.sendData(buffer).then(() => {
|
|
|
|
|
|
console.log("发送成功,准备发送下一包" + curr)
|
|
|
|
|
|
curr++;
|
|
|
|
|
|
setTimeout(sendNext, inteval);
|
|
|
|
|
|
}).catch(err => {
|
|
|
|
|
|
console.log("出现错误", err)
|
|
|
|
|
|
if (err.code == '10007') {
|
|
|
|
|
|
setTimeout(sendNext, inteval);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sendNext();
|
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
|
console.log("出现异常", ex)
|
|
|
|
|
|
reject(ex);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return promise;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
var result = await this.$refs.textToHex.drawAndGetPixels();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let h3dic = [0x06, 0x07, 0x08];
|
|
|
|
|
|
let pros = [];
|
|
|
|
|
|
let flag = true;
|
|
|
|
|
|
for (var i = 0; i < result.length; i++) {
|
|
|
|
|
|
|
|
|
|
|
|
let str = this.textLines[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (str.length > 0) {
|
|
|
|
|
|
|
|
|
|
|
|
let width = str.length * 16;
|
|
|
|
|
|
var rgb = this.convertToRGB565(result[i].pixelData, width, 16);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await sendTxtPackge(rgb, h3dic[i], str);
|
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
|
flag = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
if (flag) {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: "提示",
|
|
|
|
|
|
content: "发送成功"
|
|
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: "错误",
|
|
|
|
|
|
content: "出现了异常发送失败"
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 发送数据通用方法
|
|
|
|
|
|
sendData(buffer) {
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
uni.writeBLECharacteristicValue({
|
|
|
|
|
|
deviceId: this.connectedDeviceId,
|
|
|
|
|
|
serviceId: this.serviceId,
|
|
|
|
|
|
characteristicId: this.writeCharacteristicId,
|
|
|
|
|
|
value: buffer,
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
console.log("发送数据成功");
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
console.log("发送数据失败");
|
|
|
|
|
|
reject(`发送数据失败: ${err.errMsg}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 添加日志
|
|
|
|
|
|
addLog(message) {
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
ReStart() { //重启
|
|
|
|
|
|
if (!this.isConnected) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 构建数据包
|
|
|
|
|
|
var buffer = new ArrayBuffer(4);
|
|
|
|
|
|
var dataView = new DataView(buffer);
|
|
|
|
|
|
let data = '0x' + parseInt(this.brightness).toString(16);
|
|
|
|
|
|
console.log("亮度:" + this.brightness + ',16进制:' + data);
|
|
|
|
|
|
dataView.setUint8(0, 0x70); // 帧头
|
|
|
|
|
|
dataView.setUint8(1, 0x65); // 帧类型:亮度调节
|
|
|
|
|
|
dataView.setUint8(2, 0x0C); // 包序号
|
|
|
|
|
|
dataView.setUint8(3, 0x01); // 数据
|
|
|
|
|
|
|
|
|
|
|
|
// 发送数据
|
|
|
|
|
|
this.sendData(buffer);
|
|
|
|
|
|
},
|
|
|
|
|
|
Restore() { //恢复出厂设置
|
|
|
|
|
|
if (!this.isConnected) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 构建数据包
|
|
|
|
|
|
var buffer = new ArrayBuffer(4);
|
|
|
|
|
|
var dataView = new DataView(buffer);
|
|
|
|
|
|
let data = '0x' + parseInt(this.brightness).toString(16);
|
|
|
|
|
|
console.log("亮度:" + this.brightness + ',16进制:' + data);
|
|
|
|
|
|
dataView.setUint8(0, 0x70); // 帧头
|
|
|
|
|
|
dataView.setUint8(1, 0x65); // 帧类型:亮度调节
|
|
|
|
|
|
dataView.setUint8(2, 0x0D); // 包序号
|
|
|
|
|
|
dataView.setUint8(3, 0x01); // 数据
|
|
|
|
|
|
|
|
|
|
|
|
// 发送数据
|
|
|
|
|
|
this.sendData(buffer);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
onUnload() {
|
|
|
|
|
|
|
|
|
|
|
|
this.closeBluetoothAdapter();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
.container {
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
background-color: #f8f8f8;
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-size: 36rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 20rpx 0;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status-bar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 15rpx 0;
|
|
|
|
|
|
border-bottom: 1rpx solid #eee;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.function-tabs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
margin: 20rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tab-btn {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 15rpx 0;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
border-bottom: 2rpx solid #eee;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tab-btn.active {
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
border-bottom: 2rpx solid #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 设备列表样式 */
|
|
|
|
|
|
.search-area {
|
|
|
|
|
|
padding: 20rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-btn {
|
|
|
|
|
|
background-color: #409eff;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tips {
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
margin-top: 10rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.devices-list {
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
margin-bottom: 15rpx;
|
|
|
|
|
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-item.active {
|
|
|
|
|
|
background-color: #e8f3ff;
|
|
|
|
|
|
border-left: 6rpx solid #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-name {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
max-width: 500rpx;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-rssi {
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-status {
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 模式设置样式 */
|
|
|
|
|
|
.modes-area {
|
|
|
|
|
|
margin-top: 30rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-title {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modes-grid {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-item {
|
|
|
|
|
|
width: 30%;
|
|
|
|
|
|
padding: 20rpx 0;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-item.selected {
|
|
|
|
|
|
background-color: #e8f3ff;
|
|
|
|
|
|
border: 2rpx solid #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-icon {
|
|
|
|
|
|
font-size: 40rpx;
|
|
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-name {
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 亮度调节样式 */
|
|
|
|
|
|
.brightness-area {
|
|
|
|
|
|
margin-top: 30rpx;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.brightness-title {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.brightness-value {
|
|
|
|
|
|
font-size: 36rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: 30rpx;
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.brightness-slider {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 开机画面样式 */
|
|
|
|
|
|
.splash-controls {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
|
margin: 20rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.splash-preview {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.splash-frame {
|
|
|
|
|
|
height: 320rpx;
|
|
|
|
|
|
width: 640rpx;
|
|
|
|
|
|
border: 2rpx solid #409eff;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-frame {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.frame-info {
|
|
|
|
|
|
margin-top: 15rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sending-progress {
|
|
|
|
|
|
margin-top: 30rpx;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-bar {
|
|
|
|
|
|
height: 12rpx;
|
|
|
|
|
|
background-color: #e0e0e0;
|
|
|
|
|
|
border-radius: 6rpx;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-fill {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background-color: #409eff;
|
|
|
|
|
|
transition: width 0.3s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 文字设置样式 */
|
|
|
|
|
|
.text-inputs {
|
|
|
|
|
|
margin-top: 30rpx;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.text-line {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.text-line text {
|
|
|
|
|
|
width: 80rpx;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.text-line input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 60rpx;
|
|
|
|
|
|
padding: 0 15rpx;
|
|
|
|
|
|
border: 1rpx solid #eee;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.send-text-btn {
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
background-color: #409eff;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 日志区域样式 */
|
|
|
|
|
|
.log-area {
|
|
|
|
|
|
margin-top: 30rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.log-title {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 15rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.log-content {
|
|
|
|
|
|
height: 200rpx;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
padding: 15rpx;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.log-item {
|
|
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
|
|
padding: 10rpx 20rpx;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn[disabled] {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.txt {
|
|
|
|
|
|
border: 1px solid #000000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.TextToHex {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: -99999rpx;
|
|
|
|
|
|
left: -99999rpx;
|
|
|
|
|
|
visibility: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|