uniapp如何连接电子秤蓝牙,实现监听数据转换并实时返回

796 阅读7分钟

概述:Uniapp实现连接电子秤蓝牙,实现监听数据同步回显,实现数据实时监听,并在弹框中打印日志功能,本文将具体阐述。

image.png

bffb88b1ab060017f0ca9460e53c677.jpg

蓝牙电子秤连接步骤:

初始化蓝牙---搜索蓝牙---连接蓝牙(蓝牙设备值)---获取服务值(蓝牙设备值)---获取特征值(蓝牙设备值+蓝牙服务值)---监听---处理蓝牙ArrayBufer数据---页面回显

疑难杂症场景+解决办法:

1. 连接上了,但是获取特征值,无回调无任何反应?

答:是因为服务值和设备id对应不上,请检查该设备id对应的服务值是否正确

2. 监听成功,但是在页面显示电子秤数据的时候,出现闪屏或数据为空

答:是因为notifyBLECharacteristicValueChange方法,返回的ArrayBufer格式不正确,比如我这边就是13.0 13.0,返回了两次,正确的应该是:13.0,只有一次,此种问题请联系电子秤商家的技术人员,更改蓝牙电子秤的传输数据格式

3. notifyBLECharacteristicValueChange返回的数据格式可以修改吗?比如我截取一个重复的.

答:不能!因为ArrayBuffer二进制数据,我们只能解析和读取,无法对它原数据进行修改,所以如果想通过处理数据方式去重,显然不可以

4. 监听启动成功,但是后续无任何反应和回调?

答:设备服务值和特征值不匹配,因为每个服务值都会有多个对应的特征值,如果是自动连接循环获取,他们并不是一一对应,则会出现此问题,所以请排查特征值和服务值是否一一对应。

5.可以让后端通过chrome://bluetooth-internals/#devices/c8:b2:1e:97:9e:a3 页面,获取服务值和特征值,然后通过接口传给前端,从而实现自动连接蓝牙,同时监听电子秤数据吗?

答:不能!因为虽然电子秤设备id是固定的,但是它的服务值会有多个,每个服务值对应的特征值也不一样,而特征值分为:可读、可写、可监听等几个类型,只有选择对应类型为true的特征值,才可以实现监听功能,通过后端接口拿,如果拿到了不具备监听功能的特征值,或者服务值和特征值不匹配,就会监听失败。

<template>
	<view class="main-container">
		<u-navbar :title="title"></u-navbar>
		<view class="tit-nav mt-50">
			<view class="border-left"></view>
			<view class="user-type">{{ pageType == 'manger' ? '管理员回收' : '用户回收' }}</view>
		</view>
		<view class="common-box operator-info">
			<view class="flex">
				<image class="user-img" mode="aspectFill" src="@/static/images/login.png"></image>
				<view class="user-name ellipsis">{{ userData.name }}</view>   
			</view>
			<image class="check-img" mode="aspectFill" src="@/static/images/check-type.png" @click="getCheckType"></image>
		</view>
		<view class="tit-nav">
			<view class="border-left"></view>
			<view class="user-type">选择回收类别</view>
		</view>
		<view class="common-box large-category">
			<view class="flex">
				<view class="name add-weight-tit">选择废品类型</view>
				<view class="desc-type">选择您要回收的废品类型,再进行称重</view>
			</view>
			<view class="category-box-list">
				<view class="category-list" v-for="(item, index) in largeCategory" :key="index" :class="currentActive == index ? 'active-btn' : ''" @click="getSelectType(item, index)">
					<view class="category-name">{{ item.name }}</view>
				</view>
			</view> 
			<view class="common-box accounting-box">
				<view class="flex" @click="getInput">
					<view class="flex">
						<input class="input-number" :class="weightNumber.length == 6 ? 'customer-input-number' : ''" placeholder="请输入重量" v-model="weightNumber" placeholder-style="color:#7D7D7D; font-size: 22rpx;" maxlength="6" type="text" data-datatag='weightNumber' @blur="blurNumberInput($event)" @focus="onfocus" placeholder-class="weight-placeholder" :disabled="weightInputFlag" @input="updateData" />
						<span class="unit number-unit">{{ weightInputFlag ? copyPopData.unit : selectWasteData.unit }}</span>
					</view>
					<view class="flex money-box">
						<view class="money" :class="weightNumber.length == 6 ? 'customer-input-money' : ''">{{ totalAmount }}</view>
						<span class="unit"></span>
					</view>
				</view>
				<view class="flex-img" v-if="bluetoothLinkStatus">
					<image class="link-image" mode="aspectFill" src="@/static/images/success-link.png"></image>
					<view class="bluetooth-desc">{{ bluetoothCustomerName }}电子秤已连接</view>
				</view>
				<image v-else class="link-image" mode="aspectFill" src="@/static/images/no-link.png"></image>
			</view>
			<view class="flex">
				<view v-if="!bluetoothLinkStatus" class="common-btn link-btn mr-16" @click="getLink">连接蓝牙</view>
				<view class="common-btn link-btn" @click="getLinkStatus">连接状态检测</view>
			</view>
			<view class="accounting-list" :class="accountingList.length > 4 ? 'scroll-box' : ''">
				<view class="accounting-item" v-for="(item, index) in accountingList" :key="index">
					<view class="item-list ellipsis">{{ item.time }}</view>
					<view class="item-list">{{ item.name }}</view>
					<view class="item-list">{{ item.weight }}{{ item.unit }}</view>
					<view class="item-list">{{ item.total }}元</view>
					<image v-if="item.deleteFlag" class="delete-image" mode="aspectFill" src="@/static/images/delete-img.png" @click="getDelete(index)"></image>
					<!-- 占位符 -->
					<view v-else class="delete-image"></view>
				</view>
			</view>
			<view v-if="totalEndAmount > 0 || (bluetoothLinkStatus && totalAmount > 0)" class="bottom-nav">
				<view class="flex">
					<view class="summarize">总结:</view>
					<view class="total-number">{{ totalEndAmount }}</view>
					<view class="summarize mount-unit"></view>
				</view>
				<view class="flex">
					<view v-if="bluetoothLinkStatus" class="common-btn" :class="continueBtnFlag && !finshRecycleFlag ? 'disabled-btn' : ''" @click="getContinue">完成称重</view>
					<view class="common-btn over-btn" @click="getOrderSure" :class="finshRecycleFlag ? 'disabled-btn' : ''">确认订单</view>
				</view>
			</view>
		</view>
		<lf-popup v-model="showPopup" class="customer-popup">
			<view class="tit-nav">
				<view class="border-left"></view>
				<view class="user-type">{{ selectlargeTypeData.name }}</view>
			</view>
			<view class="content-box">
				<view class="list-item" v-for="(item, index) in wasteList" :key="index" :class="selectActive == index ? 'select-btn' : ''" @click="getCurrent(item, index)">
					<view class="">
						<view class="item-name">{{ item.name }}</view>
						<view class="price">¥{{ (item.recyclingPrice)/100 }}/{{ item.unit }}</view>
					</view>
				</view>
				<view class="bottom-nav popup-bottom">
					<view class="flex">
						<view class="common-btn border-btn" @click="getCancel">取消</view>
						<view class="common-btn over-btn" @click="getSure" :class="selectActive == -1 ? 'disabled-btn' : ''">确认</view>
					</view>
				</view>
			</view>
		</lf-popup>
		<lf-popup v-model="showLogPopup" class="customer-popup">
			<view class="titile-box">
				<image class="arr-img" mode="aspectFill" src="@/static/images/arr-down.png" @click="onClose"></image>
				<view class="popup-tit" @click="onClose">蓝牙连接状态</view>
			</view>
			<scroll-view v-if=" logStatusList.length > 0" scroll-y="true" class="scroll-box">
				<view class="" v-for="(item, index) in logStatusList" :key="index">
					<view class="item-status">{{ item.name }}</view>
				</view>
			</scroll-view>
			<view v-esle class="empty-box-center" >
				<u-empty text="暂未连接" mode="favor"></u-empty>
			</view>
		</lf-popup>
		<Bluetooth ref="BluetoothRef" @getInitBluetooth="getInitBluetooth" @getStatusLog="getStatusLog"></Bluetooth>
	</view>
</template>


<script>
	import Bluetooth from '../Bluetooth.vue'
	export default {
		components: { Bluetooth },
		data() {
			return {
				logStatusList: [],
				bluetoothName: '',
				copyPopData: {},
				focusFlag: false,
				title: '开始回收',
				optionsData: {},
				userData: {},
				pageType: '',
				previousSelect: {},
				viewDetail: false,
				totalAmount: 0,
				showPopup: false,
				showLogPopup: false,
				weightType: 0,
				bluetoothCustomerName: '',
				finshRecycleFlag: true,
				continueBtnFlag: false,
				bluetoothLinkStatus: false,
				largeCategory: [],
				weightNumber: '',
				currentActive: -1,
				selectActive: -1,
				selectCurrentData: {},
				weightInputFlag: true,
				selectWasteData: {},
				selectlargeTypeData: {},
				accountingList: [],
				wasteList: [],
				totalEndAmount: 0,
				historySelectIndex: -1
			};
		},
		watch: {
			showPopup(status) {
				// 弹框关闭,如果未选择废品单价。则回显上次选中
				if (!status) {
					if (this.historySelectIndex !== -1) {
						this.currentActive = this.historySelectIndex
					}
				}
			},
		},
		onLoad(options) {
			this.optionsData = options
			this.pageType = options.pageType
			this.getInit()
			this.getUserInfo()
		},
		methods: {
			getInit() {
				this.weightType = 0
				this.viewDetail = false
				this.focusFlag = false
				this.weightInputFlag = true
				this.continueBtnFlag = false
				this.finshRecycleFlag = true
				this.bluetoothLinkStatus = uni.getStorageSync('bluetoothLinkStatus')
				if (this.optionsData.qrCodeUid) {
					this.getUserInfo('qrCode')
				}
				this.$H.get('/app-api/recycling/app/category/list').then(res => {
					if(res.code == 0) {
						this.largeCategory = res.data
					} else {
						uni.showToast({
							title: res.msg,
							icon: "none",
							duration: 2000
						})
					}
				})
			},
			getInitBluetooth(status, value, type) {
				if (JSON.stringify(uni.getStorageSync('bluetoothLanyaData')) !== '{}') {
					this.bluetoothName = uni.getStorageSync('bluetoothLanyaData').name
				}
				// 蓝牙每次断开或第一次重新连接,重置数据
				if (type == 'init') {
					this.getInitData()
					this.bluetoothLinkStatus = true
				}
				this.weightNumber = value
				this.weightType = status ? 1 : 0
				const moneyTotal = (this.selectWasteData.recyclingPrice * this.weightNumber)/100
				if (moneyTotal > 0) {
					const numStr = moneyTotal.toString();
					const decimalIndex = numStr.indexOf('.');
					if (decimalIndex !== -1) {
					  // 截取小数点后一位
					  this.totalAmount = numStr.slice(0, decimalIndex + 3);
						// 如果是整数,直接返回 
					} else {
						this.totalAmount = numStr
					}
				} else {
					this.totalAmount = 0
				}
			},
			getStatusLog(data) {
				data.forEach((item, index) => {
					this.$set(this.logStatusList, index, item)
				})
				this.bluetoothCustomerName = this.bluetoothName
			},
			getLinkStatus() {
				this.showLogPopup = true
			},
			getUserInfo(qrCode) {
				if (qrCode) {
					this.$H.get('/app-api/recycling/app/recycler-manager/getUserInfo', {
						uid: this.optionsData.qrCodeUid
					}).then(res => {
						if(res.code == 0) {
							this.userData = res.data
						} else {
							uni.showToast({
								title: res.msg,
								icon: "none",
								duration: 2000
							})
						}
					})
				} else {
					if (this.pageType == 'userRecyle') {
						this.userData.name = this.optionsData.userName
					} else {
						this.userData = uni.getStorageSync('managerInfo')
					}
				}
			},
			getCheckType() {
				uni.navigateTo({
					url: '/pages/recycleType/recycleType'
				})
			},
			onfocus() {
				this.weightNumber = ''
			},
			getInput() {
				if (!this.bluetoothLinkStatus) {
					this.getValite()
				}
			},
			getLink() {
				this.$refs.BluetoothRef.getInitConcat()
			},
			blurNumberInput(e) {
				if(!e.target.value) {
					this.weightNumber = ''
				} else {
					if (this.totalAmount > 0) {
						this.getAddRecycleList()
					}
				}
			},
			getContinue() {
				this.getValite()
			},
			getValite() {
				if (JSON.stringify(this.selectlargeTypeData) == '{}') {
					uni.showToast({
						title: '请选择废品类别!',
						icon: "none",
						duration: 1500
					})
					return
				} else if (JSON.stringify(this.selectWasteData) == '{}') {
					uni.showToast({
						title: '请选择废品单价!',
						icon: "none",
						duration: 1500
					})
					return
				}
				if (this.totalAmount > 0) {
					this.getAddRecycleList()
				}
			},
			getAddRecycleList() {
				this.finshRecycleFlag = false
				this.sumTotalData()
				this.accountingList.forEach((item) => {
					item.deleteFlag = true
				})
			},
			updateData(e) {
				this.$nextTick(() => {
					 this.weightNumber = e.detail.value.replace(/[^\d.]/g, '')
				})
				if(e.detail.value=='.'){
					this.$nextTick(() => {
						this.weightNumber = '';
					})
					return;
				}	else if((e.detail.value.split('.').length - 1)>1){//保留小数点后2位
					this.$nextTick(() => {
						this.weightNumber = this.weightNumber.substring(0,this.weightNumber.length-1);
					})
					return;
				}	else if(e.detail.value.split('.').length>1){//小数点只能输入一个
					if((e.detail.value.split('.')[1].length>2)){
						this.$nextTick(() => {
							this.weightNumber = this.weightNumber.substring(0,this.weightNumber.length-1);
						})
						return;
						}
				}
				let resultMoney = 0
				let moneyData = (this.selectWasteData.recyclingPrice * this.weightNumber)/100
				const numStr = moneyData.toString();
				const decimalIndex = numStr.indexOf('.');
				if (decimalIndex !== -1) {
				  // 截取小数点后一位
				  resultMoney = numStr.slice(0, decimalIndex + 3);
					// 如果是整数,直接返回 
				} else {
					resultMoney = numStr
				}
				this.totalAmount = resultMoney
				if (this.accountingList.length > 0) {
					this.accountingList.forEach((item) => {
						item.deleteFlag = false
					})
				}
			},
			getSelectType(data, index) {
				this.selectActive = -1
				if (!this.viewDetail) {
					this.currentActive = index
					this.showPopup = true
					this.selectlargeTypeData = data
					this.wasteList = data.recyclingPrices
					// 点击相同父级分类,选中 且未添加 
					if (this.previousSelect.categoryId == data.categoryId) {
						this.wasteList.forEach((item, index) => {
							if (item.categoryId == this.selectWasteData.categoryId) {
								this.selectActive = index
								// this.currentActive = index
							}
						})
					}
				}
			},
			getCurrent(data, index) {
				this.selectActive = index
				this.selectCurrentData = data
			},
			getSure() {
				// 蓝牙已连接
				if (this.bluetoothLinkStatus) {
					if (this.selectActive == -1) {
						uni.showToast({
							title: '请选择废品单价!',
							icon: "none",
							duration: 1500
						})
					} else {
						this.selectSureData()
						this.showPopup = false
						this.continueBtnFlag = false
						this.totalAmount = (this.selectWasteData.recyclingPrice * this.weightNumber)/100
					}
					this.sumAmount()
					this.finshRecycleFlag = true
				} else {
					if (this.selectActive == -1) {
						uni.showToast({
							title: '请选择废品单价!',
							icon: "none",
							duration: 1500
						})
					} else {
						this.selectSureData()
						this.weightNumber = ''
						this.totalAmount = 0
						this.weightInputFlag = false
						this.showPopup = false
					}
				}
			},
			selectSureData() {
				this.focusFlag = false
				// 上一次点击的父级数据
				this.historySelectIndex = this.currentActive
				// 上一次点击的子级数据
				this.previousSelect = this.selectlargeTypeData
				this.selectWasteData = this.selectCurrentData
				this.copyPopData = JSON.parse(JSON.stringify(this.selectCurrentData))
			},
			getCancel() {
				this.showPopup = false
			},
			getDelete(index) {
				this.accountingList.splice(index, 1)
				this.sumAmount()
			},
			sumAmount() {
				let sum = 0
				let result = 0
				this.accountingList.forEach((item) => {
					sum += Number(item.total)
				})
				result = (Math.round(Number(sum) * 100))/100
				this.totalEndAmount = result
			},
			sumTotalData() {
				let currentData = new Date()
				let hour = currentData.getHours()
				let minutes = currentData.getMinutes()
				let seconds = currentData.getSeconds()
				if (hour >= 0 && hour <= 9) {
					hour = '0' + hour
				}
				if (minutes >= 0 && minutes <= 9) {
					minutes = '0' + minutes
				}
				if (seconds >= 0 && seconds <= 9) {
					seconds = '0' + seconds
				}
				let currentTime = hour + ':' + minutes + ':' + seconds
				this.focusFlag = true
				this.accountingList.push({
					time: currentTime, name: this.selectWasteData.name, weight: this.weightNumber, total: this.totalAmount, unit: this.selectWasteData.unit, categoryName: this.selectWasteData.name, categoryId: this.selectWasteData.categoryId
				})
				// if(e.target.value && e.target.value.indexOf('.') <= 0) {
				  // this.weightNumber = e.target.value + '.00'
				// }
				this.historySelectIndex = -1
				this.currentActive = -1
				this.weightInputFlag = true
				this.selectlargeTypeData = {}
				this.selectWasteData = {}
				this.previousSelect = {}
				// 蓝牙为已连接状态
				if (this.bluetoothLinkStatus) {
					// 添加完成标记为禁状态
					this.continueBtnFlag = true
				}
				this.sumAmount()
			},
			getInitData() {
				this.focusFlag = false
				this.historySelectIndex = -1
				this.selectActive = -1
				this.viewDetail = false
				this.totalEndAmount = 0
				this.totalAmount = 0
				this.accountingList = []
				this.currentActive = -1
				this.selectActive = -1
				this.weightInputFlag = true
				this.selectlargeTypeData = {}
				this.selectCurrentData  = {}
				this.selectWasteData = {}
				this.previousSelect = {}
			},
			getOrderSure() {
				let sumbitData = []
				this.accountingList.forEach((item) => {
					sumbitData.push({
						weight: item.weight,
						categoryId: item.categoryId,
						categoryName: item.categoryName
					})
				})
				let onlineParams = {}
				// 线上
				if (this.optionsData.qrCodeUid) {
					onlineParams = {
						reqVoList: sumbitData,
						uid: this.optionsData.qrCodeUid,
						weightType: this.weightType
					}
				} else {
					onlineParams = {
						reqVoList: sumbitData,
						weightType: this.weightType,
						orderNo: this.optionsData.orderNo
					}
				}
				
				// 线下
				let OfflineParams = {
					reqVoList: sumbitData,
					money: this.totalEndAmount,
					weightType: this.weightType,
					settleType: 0
				}
				if (!this.finshRecycleFlag) {
					uni.setStorageSync('OnlineData', onlineParams)
					uni.setStorageSync('OfflineData', OfflineParams)
					uni.setStorageSync('recycleList', this.accountingList)
					uni.navigateTo({
						url: `/pages/confirmOrder/confirmOrder?pageType=${this.pageType}&operateType=${this.optionsData.operateType}`
					})
				}
			},
			getUserApi(api, params) {
				this.$H.post(api, params).then(res => {
					if (res.code == 0) {
						this.commonModel()
					} else {
							uni.showToast({
								title: res.msg,
								icon: "none",
								duration: 2000
							})
						}
				})
			},
			getMangerApi(api, params) {
				this.$H.post(api, params).then(res => {
					if (res.code == 0) {
						this.commonModel()
					} else {
							uni.showToast({
								title: res.msg,
								icon: "none",
								duration: 2000
							})
						}
				})
			},
			
			commonModel() {
				uni.showToast({
					title: '回收成功!',
					icon: "none",
					duration: 1500
				})
				this.viewDetail = true
				this.weightInputFlag = true
			},
			onClose() {
				this.showLogPopup = false
			}
		}
	};
</script>

<style lang="scss" scoped>
	/deep/.weight-placeholder {
		font-weight: 500!important;
		line-height: 90rpx!important;
		font-size: 32rpx!important;
	}
	.bottom-nav {
		.flex {
			height: 100rpx;
			.summarize {
				color: #7D7D7D;
				line-height: 52rpx;
				height: 50rpx;
			}
			.mount-unit {
				margin-left: 5rpx;
			}
			.total-number {
				font-weight: 600;
				font-size: 40rpx;
			}
		}
	}
	.over-btn {
		margin-left: 16rpx;
	}
	.main-container {
		height: 100%;
		.flex {
			display: flex;
			align-items: center;
		}
		.operator-info {
			.check-img {
				width: 45rpx;
				height: 45rpx;
			}
		}
		
		

		
		.common-box {
			padding: 24rpx;
			border-radius: 24rpx;
			margin: 16rpx 0 32rpx 0;
			background-color: #FFFFFF;
		}
		
		
		.large-category {
			.category-box-list {
				display: flex;
				flex-wrap: wrap;
				margin-top: 16rpx;
				.category-list {
					margin:0 24rpx 24rpx 0;
					.category-name {
						color: #000000;
						height: 64rpx;
						width: max-content;
						font-size: 28rpx;
						padding: 12rpx 32rpx;
						display: flex;
						align-items: center;
						border-radius: 88rpx;
						justify-content: center;
						background-color: #F4F4F4;
					}
				}
				.active-btn {
					.category-name {
						color: #FFFFFF;
						background-color: #32C25D;
					}
				}
			}
		}
		
		
		
		.accounting-box {
			padding: 20rpx 24rpx 5rpx 24rpx;
			margin: 16rpx 0 8rpx 0;
			background-color: #E9FFF0;
			.unit {
				width: 63rpx;
				line-height: 75rpx;
				height: 55rpx;
				font-size: 32rpx;
			}
			.flex {
				position: relative;
				.input-number {
					width: 180rpx;
					font-size: 68rpx;
					font-weight: 800;
					height: 68rpx;
					min-height: 68rpx;
				}
				.number-unit::after {
					top: -5rpx;
					right: -25rpx;
					width: 2rpx; 
					content: '';
					height: 80rpx;
					display: inline-block;
					position: absolute;
					background-color: #EEEEEE;
				}
				.customer-input-number {
					padding-top: 5rpx;
					width: 200rpx;
					font-size: 50rpx;
				}
			}
			.money-box {
				margin-left: 55rpx;
				height: 68rpx;
				line-height: 85rpx;
				.money {
					width: 240rpx;
					font-size: 68rpx;
					font-weight: 600;
					height: 68rpx;
					line-height: 75rpx;
				}
				.customer-input-money {
					font-size: 50rpx;
				}
			}
		}
		.link-btn {
			width: fit-content;
			padding: 0 24rpx;
			font-size: 22rpx;
			line-height: 44rpx;
			height: 44rpx;
			margin-top: 25rpx;
			background: #0A84FF;
		}
		.link-image {
			width: 180rpx;
			height: 34rpx;
			margin-top: 20rpx;
		}
		.customer-popup {
			.content-box {
				display: flex;
				flex-wrap: wrap;
				margin-top: 16rpx;
				.list-item {
					width: 219rpx;
					display: flex;
					padding: 12rpx 0;
					border-radius: 24rpx;
					margin-right: 16rpx;
					align-items: center;
					justify-content: center;
					margin-bottom: 16rpx;
					background-color: #F4F4F4;
					.item-name {
						text-align: center;
						font-size: 28rpx;
					}
					.price {
						font-size: 23rpx;
					}
				}
				.list-item:nth-child(3n) {
					margin-right: 0;
				}
				.select-btn {
					color: #FFFFFF;
					background-color: #32C25D;
				}
			}
			.popup-bottom {
				justify-content: flex-end;
			}
		}
		.flex-img {
			display: flex;
			align-items: center;
			margin: 10rpx 0;
			.link-image {
				width: 28rpx;
				height: 28rpx;
				margin-top: 0;
				margin-right: 4rpx;
			}
			.bluetooth-desc {
				color: #7D7D7D;
				font-size: 24rpx;
			}
		}
	}
	.scroll-box {
		height: 450rpx;
	}
	.customer-popup {
		.tiit {
			text-align: center;
			font-size: 33rpx;
			font-weight: 600;
			margin-bottom: 20rpx;
		}
		.titile-box {
			display: flex;
			align-items: center;
			padding: 0 0 10rpx 0;
			justify-content: flex-start;
			.arr-img {
				width: 27rpx;
				height: 16rpx;
				padding-right: 0;
				margin-right: 230rpx;
			}
			.popup-tit {
				color: #333333;
				font-size: 30rpx;
				font-weight: 600;
			}
			.common-btn {
				height: 50rpx;
				width: fit-content;
				padding: 0 20rpx;
				font-size: 28rpx;
			}
		}
		.scroll-box {
			height: 1120rpx;
			.item-status {
				font-size: 30rpx;
				padding: 10rpx 0;
				border-bottom: 2rpx solid #F6F6F6;
			}
		}
	}
	.empty-box-center {
		margin-top: 400rpx;
	}
</style>

    

Bluetooth.vue


<template>
	<lf-popup v-model="showLinkPopup" class="customer-popup">
		<view class="lanya-container">
			<view class="tit-flex">
				<view class="tit">当前可用蓝牙设备</view>
				<view class="button"></view>
				<button type="default" @click="getAdapter">搜索蓝牙</button>
			</view>
			<view v-if="bluetoothList.length == 0" class="empty-box-center" >
				<u-empty text="暂无设备" mode="favor"></u-empty>
			</view>
			<view v-else class="lanya-list" v-for="(item, index) in bluetoothList" :key="index" @click="getOperate(item.deviceId)">
				<view class="bluetooth-tittle">蓝牙名称:{{ item.name }}</view>
				<!-- <button type="default">{{ lanya.deviceId !== item.deviceId ? '连接' : '已连接' }} </button> -->
				<button type="default" v-if="lanya.deviceId !== item.deviceId">连接</button>
				<button class="warn-btn" type="warn" v-else>断开</button>
			</view>
			
		</view>
	</lf-popup>
</template>


<script>
	export default {
		data() {
			return {
				lanya: {},
				deviceId: '',
				lastValue: 0,
				bluetoothList: [],
				showLinkPopup: false,
				bluetoothErrorLog: [],
				linkCharacteristicId: ''
			};
		},
		methods: {
			getClear(type) {
				this.lanya = {}
				if (type !== 'parentClear') {
					this.bluetoothList = []
				}
			},
			getBluetoothStatus() {
				console.log(uni.getStorageSync('bluetoothLinkStatus'), 'aaaa')
				if (uni.getStorageSync('bluetoothLinkStatus')) {
					this.monitor()
				}
			},
			getLink() {
				this.showLinkPopup = true
				// 未连接
				// if (!uni.getStorageSync('bluetoothLinkStatus')) {
				// 	this.getAdapter()
				// }
			},
			getAdapter() {
				this.bluetoothList = []
				uni.openBluetoothAdapter({
						// 蓝牙初始化成功执行
						success: (res) => {
							this.bluetoothErrorLog.push({
								name: '蓝牙初始化成功...'
							})
						this.$emit('getStatusLog', this.bluetoothErrorLog)
						this.searchBlue()
					},
					// 蓝牙初始化失败执行
					fail: (err) => {
						if (err.errCode == 10001) {
							// 这种情况是未打开蓝牙 就需要提示用户检测蓝牙并打开
							uni.showToast({
								title: "请检查蓝牙是否开启!",
								icon: "none",
								duration: 2000,
							});
							this.bluetoothErrorLog.push({
								name: '蓝牙初始化失败...  请检查蓝牙是否开启', 
							})
							this.$emit('getStatusLog', this.bluetoothErrorLog)
						}
						this.bluetoothErrorLog.push({
							name: '蓝牙初始化失败...' + JSON.stringify(err)
						})
						this.$emit('getStatusLog', this.bluetoothErrorLog)
					}
				})
			},
			//搜索蓝牙
			searchBlue() {
				this.bluetoothList = []
				uni.showLoading({
				  title: '蓝牙搜索中...'
				});
				this.$emit('getStatusLog', this.bluetoothErrorLog)
				uni.startBluetoothDevicesDiscovery({
					success: () => {
						setTimeout(() => {
							uni.getBluetoothDevices({
								success: (res) => {
									// 过滤掉未知设备
									var arr = []
									res.devices.forEach((element) => {
										if (element.name !== "未知设备") {
											arr.push(element);
										}
									})
									if (arr.length == 0) {
										uni.showToast({
											title: "未查询到可用设备,请重新扫描",
											duration: 1000,
											icon: "none",
										});
										this.bluetoothErrorLog.push({
											name: '未查询到可用设备,请重新扫描'
										})
										this.$emit('getStatusLog', this.bluetoothErrorLog)
									} else {
										// 最终具有名称的设备列表
										let arr1 = []
										arr.map((i) => {
											if (i.name) {
												arr1.push(i);
											}
										})
										this.bluetoothList = arr1
										uni.hideLoading()
									}
								}
							})
						}, 2000)
					}
				})
			},
			//连接蓝牙
			getOperate(e) {
				this.deviceId = e
				// 已连接,断开连接
				if (this.lanya.deviceId == e) {
					this.closelanya(e)
				// 未连接,手动连接
				} else {
					uni.setStorageSync('linkDeviceId', e)
					uni.showLoading({
					  title: '蓝牙连接中...'
					});
					this.bluetoothErrorLog.push({
						name: '蓝牙连接中...'
					})
					this.$emit('getStatusLog', this.bluetoothErrorLog)
					setTimeout(() => {
						uni.hideLoading()
						this.bluetoothErrorLog.push({
							name: '蓝牙连接失败,请稍后重试...'
						})
						this.$emit('getStatusLog', this.bluetoothErrorLog)
					}, 4000)
					// 手环这一类的设备 可能会对程序造成干扰 会一直显示设备已经连接
					uni.getConnectedBluetoothDevices({
						success: (res) => {
							if (res.devices.length !== 0) {
								uni.hideLoading()
								// 这里就需要提示用户蓝牙已连接
								uni.showModal({
									title: "提示!",
									content: "当前蓝牙已于名称为" + res.devices[0].name + "的设备连接,是否断开连接?",
									success: (row) => {
										if (row.confirm) {
											// 断开当前蓝牙连接
											this.closelanya(res.devices[0].deviceId)
										} else {
											// 用户取消之后不需要做任何操作
											console.log('用户点击了取消')
										}
									}
								});
							} else {
								uni.createBLEConnection({
									deviceId: this.deviceId,
									timeout: 4000,
									success: (res) => {
										// 连接成功之后可以再次进行查询一下当前连接的蓝牙信息,保存下载,后面方便使用
										uni.getConnectedBluetoothDevices({
											success: (devicess) => {
												// devicess为当前已经连接的蓝牙列表
												// 在这判断一下有没有蓝牙,如果有就证明确实已经连接成功
												if (devicess.devices[0]) {
													// 到这里就可以提示用户成功,并且吧蓝牙信息保存下来方便后面使用
													this.lanya = devicess.devices[0]
													uni.setStorageSync('bluetoothLanyaData', devicess.devices[0])
													uni.setStorageSync('bluetoothLinkStatus', true)
													this.$emit('getInitBluetooth', true, 0, 'init')
													this.bluetoothErrorLog.push({
														name: '当前设备:' + JSON.stringify(devicess.devices[0])
													})
													this.$emit('getStatusLog', this.bluetoothErrorLog)
													setTimeout(() => {
														this.lianjielanya()
													}, 1000)
													this.stopBluetoothDevicesDiscovery();
												} else {
													uni.setStorageSync('bluetoothLanyaData', {})
													uni.setStorageSync('bluetoothLinkStatus', false)
												}
											}
										})
									},
									fail: (err) => {
										// 在这里查看连接失败的信息,判断为什么连接失败
										this.bluetoothErrorLog.push({
											name: '蓝牙连接失败' + JSON.stringify(err)
										})
										this.$emit('getStatusLog', this.bluetoothErrorLog)
										// uni.showToast({
										// 	title: "蓝牙连接失败,请稍后重新连接!",
										// 	icon: "none",
										// 	duration: 2000,
										// });
									}
								})
							}
						},fail: (err) => {
						// 在这里查看连接失败的信息,判断为什么连接失败
							this.bluetoothErrorLog.push({
								name: '蓝牙连接失败' + JSON.stringify(err)
							})
							this.$emit('getStatusLog', this.bluetoothErrorLog)
							uni.hideLoading()
						}
					})
				}
			},
			lianjielanya() {
				let notifyStatus = false
				this.bluetoothErrorLog.push({
					name: '正在自动连接中,连接成功后,自动启动监听设备deviceId...' + this.deviceId
				})
				this.$emit('getStatusLog', this.bluetoothErrorLog)
				var that = this;
				uni.getBLEDeviceServices({
					deviceId: that.deviceId,
					success(res) {
						let serviceId = "";
						that.bluetoothErrorLog.push({
							name: '我是服务值列表:' + JSON.stringify(res.services)
						})
						that.$emit('getStatusLog', that.bluetoothErrorLog)
						for (var s = 0; s < res.services.length; s++) {
							// 服务值uuid列表
							let serviceId = res.services[s].uuid
							uni.getBLEDeviceCharacteristics({
								deviceId: that.deviceId,
								serviceId: serviceId,
								success(ress) {
									that.bluetoothErrorLog.push({
										name: '我是特征值列表:' + JSON.stringify(ress.characteristics)
									})
									that.$emit('getStatusLog', that.bluetoothErrorLog)
									var re = JSON.parse(JSON.stringify(ress));
									for (var c = 0; c < re.characteristics.length; c++) {
										if (re.characteristics[c].properties.notify == true) {
											var uuid = re.characteristics[c].uuid
											for (var index in that.bluetoothList) {
												if (that.bluetoothList[index].deviceId == that.deviceId) {
													uni.setStorageSync('linkServiceId', serviceId)
													uni.setStorageSync('linkCharacteristicId', re.characteristics[c].uuid)
												}
											}
											setTimeout(() => {wx.hideLoading()}, 2000)
											uni.showToast({title: '连接成功',icon:'none'})
											break
										}
									}
									that.monitor()
								}
							})
						}
					},
					fail(res) {
						uni.showToast({title: '连接失败,请重试',icon:'none'})
					},
				})
			},
			//监听
			monitor() {
				let linkDeviceId = uni.getStorageSync('linkDeviceId')
				let linkServiceId = uni.getStorageSync('linkServiceId')
				let linkCharacteristicId = uni.getStorageSync('linkCharacteristicId')
				this.bluetoothErrorLog.push({
					name: '连接成功,正在启动实时监听...ServiceId:' + linkServiceId + ',' + 'linkCharacteristicId:' + linkCharacteristicId + 'deviceId:' + linkDeviceId
				})
				let that = this
				uni.notifyBLECharacteristicValueChange({
					deviceId: linkDeviceId,
					serviceId: linkServiceId,
					characteristicId: linkCharacteristicId,
					state: true,
					success: () => {
						this.stringArr = []
						this.bluetoothErrorLog.push({
							name: '蓝牙实时监听启动成功...当前连接设备deviceId:' + linkDeviceId + 'serviceId:' + linkServiceId + 'characteristicId:' + linkCharacteristicId
						})
						this.$emit('getStatusLog', this.bluetoothErrorLog)
						this.rxd()
					},
					fail: (err) => {
						this.bluetoothErrorLog.push({
							name: '蓝牙监听启动失败...ServiceId:' + linkServiceId + ',' + 'linkCharacteristicId:' + linkCharacteristicId + 'deviceId:' + linkDeviceId
						})
						this.bluetoothErrorLog.push({
							name: '蓝牙监听启动失败:' + JSON.stringify(err)
						})
						this.$emit('getStatusLog', this.bluetoothErrorLog)
					}
				});
			},
			
			//数据接受
			rxd() {
				let that = this
				let str1 = '';
				uni.onBLECharacteristicValueChange((res) => {
					// 这就是蓝牙发送的数据 但是现在的数据,还不能直观的看出来是什么,还需要进行一些列转换才能直观查看
					// ArrayBufer转16进制
					let ArrayStrBufer = that.buf2hex(res.value);
					//16进制转字符串
					if (that.hexToString(that.buf2hex(res.value))) {
						that.stringArr.push(that.hexToString(that.buf2hex(res.value)))
					}
					//16进制转字符串处理中文乱码
					if (that.utf8to16(that.hexToString(that.buf2hex(res.value)))) {
						that.stringArr.push(that.utf8to16(that.hexToString(that.buf2hex(res.value))))
					}
			/* 		this.bluetoothErrorLog.push({
						name: '原始值' + JSON.stringify(res.value) 
					})
					this.$emit('getStatusLog', this.bluetoothErrorLog) */
				});
			},
			// arraybuffer类型转16进制字符串
			buf2hex(buffer) {
				const hexArr = Array.prototype.map.call(
					new Uint8Array(buffer),
					function(bit) {
						return ('00' + bit.toString(16)).slice(-2)
					}
				)
				return hexArr.join('')
			 
			},
			// 16进制转字符串
			hexToString(hexCharCodeStr) {
				var trimedStr = hexCharCodeStr.trim();
				var rawStr =	trimedStr.substr(0, 2).toLowerCase() === "0x" ?	trimedStr.substr(2) :	trimedStr;
				var len = rawStr.length;
				if (len % 2 !== 0) {
					return "";
				}
				var curCharCode;
				var resultStr = [];
				for (var i = 0; i < len; i = i + 2) {
					curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value
					resultStr.push(String.fromCharCode(curCharCode));
				}
				return resultStr.join("");
			},
			//处理中文乱码问题
			utf8to16(str) {
				var out, i, len, c;
				var char2, char3;
				out = "";
				len = str.length;
				i = 0;
				while (i < len) {
					c = str.charCodeAt(i++);
					switch (c >> 4) {
						case 0:
						case 1:
						case 2:
						case 3:
						case 4:
						case 5:
						case 6:
						case 7:
							out += str.charAt(i - 1);
							break;
						case 12:
						case 13:
							char2 = str.charCodeAt(i++);
							out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
							break;
						case 14:
							char2 = str.charCodeAt(i++);
							char3 = str.charCodeAt(i++);
							out += String.fromCharCode(((c & 0x0F) << 12) |
								((char2 & 0x3F) << 6) |
								((char3 & 0x3F) << 0));
							break;
					}
				}
				const numStr = out.toString();
				const decimalIndex = numStr.indexOf('.');
				// if (decimalIndex !== -1) {
				//   // 截取小数点后一位
				//   this.lastValue = numStr.slice(0, decimalIndex + 2);
				// 	// 如果是整数,直接返回 
				// } else {
				// 	this.lastValue = numStr
				// }
				this.lastValue = out;
				this.getBluetoothAdapterState()
				return out;
			},
			getBluetoothAdapterState() {
				uni.getBluetoothAdapterState ({
				  success: (res) => {
						if (!res.available) {
							this.lastValue = 0
							uni.showToast({
								title: "当前蓝牙已断开连接!",
								icon: "none",
								duration: 2000,
							});
							this.$emit('getInitBluetooth', false, 0, 'init')
							this.bluetoothErrorLog.push({
								name: '当前蓝牙状态已断开,监听数据失败。。。。。。'
							})
							this.$emit('getStatusLog', this.bluetoothErrorLog)
						} else {
							this.$emit('getInitBluetooth', true, this.lastValue)
						}
				  },
				  fail: function (res) {
						this.bluetoothErrorLog.push({
							name: '监听获取蓝牙数据失败:' + JSON.stringify(res)
						})
						this.$emit('getStatusLog', this.bluetoothErrorLog)
				  }
				})
			},
			//断开蓝牙
			closelanya(currentDeviceId) {
				let bluetoothLanyaData = uni.getStorageSync('bluetoothLanyaData')
				uni.showModal({
					title: "提示!",
					content: "当前蓝牙已于名称为" + bluetoothLanyaData.name + "的设备连接,是否断开连接?",
					success: (row) => {
						if (row.confirm) {
							uni.showLoading({
							  title: '断开连接中...'
							});
							this.$emit('getDataClear', 'parentClear')
							uni.closeBLEConnection({
								// 一定要传入当前连接蓝牙的deviceId
								deviceId: currentDeviceId,
								success: () => {},
								fail: (err) => {}
							})
						} else {
							// 用户取消之后不需要做任何操作
							console.log('用户点击了取消')
						}
					}
				});
			},
			// 关闭蓝牙搜索
			stopBluetoothDevicesDiscovery() {
				uni.stopBluetoothDevicesDiscovery({
					success: e => {
						// console.log('停止搜索蓝牙设备:' + e);
					},
					fail: e => {
						// console.log('停止搜索蓝牙设备失败,错误码:' + e);
					}
				});
			},
		}
	};
</script>

<style lang="scss" scoped>
	.lanya-container {
		button {
			margin: 0;
			border-radius: 10rpx;
			height: 52rpx;
			line-height: 52rpx;
			text-align: center;
			color: #FFFFFF;
			font-size: 23rpx;
			background: #0A84FF;
			border-radius: 88rpx 88rpx 88rpx 88rpx;
		}
		.tit-flex {
			display: flex;
			margin-bottom: 40rpx;
			justify-content: space-between;
			.tit {
				font-size: 34rpx;
				font-weight: 600;
			}
		}
		.no-data {
			font-size: 28rpx;
			color: #898A8D;
			text-align: center;
			margin-top: 500rpx;
		}
		.lanya-list {
			display: flex;
			color: #898A8D;
			font-size: 28rpx;
			margin-bottom: 40rpx;
			justify-content: space-between;
			.warn-btn {
				background: #FD3643!important;
			}
		}
		.lanya-list:last-child {
			margin-bottom: 0;
		}
	}
</style>

蓝牙连接原模板,复制即用


<template>
	<view class="content">
		<button type="default" @click="initialize">初始化</button>
		<button type="default" @click="searchBlue">搜索</button>
		<view class="list">
			<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
			<view v-for="(item, index) in dataList" :key="item.deviceId">
				<view>{{item.name}}</view>---
				<view>{{item.deviceId}}</view>---
				<view>{{item.RSSI}}</view>
				<button type="default" @click="lianjie(item.deviceId)" v-if="lanya.deviceId!==item.deviceId">连接</button>
				<button type="warn" @click="closelanya(item.deviceId)" v-if="lanya.deviceId==item.deviceId">断开</button>
			</view>
		</view>
		<button type="default" @click="communication">通讯</button>
		<h3>服务值</h3>
		<view class="list1">
			<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
			<view v-for="(item1, index1) in service" :key="item1.uuid">
				<view>{{item1.uuid}}</view>
				<view>是否主要{{item1.isPrimary}}</view>
				<button type="default" @click="choose(item1.uuid)" v-if="serviceuuid!==item1.uuid">选择</button>
				<button type="warn" @click="choose()" v-if="serviceuuid==item1.uuid">清空</button>
			</view>
		</view>
		<h3>特征值</h3>
		<view class="list1">
			<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
			<view v-for="(item1, index1) in characteristic" :key="item1.uuid">
				<view>{{item1.uuid}}</view>
				<view>可监听{{item1.properties.notify}}</view>
				<view>可写{{item1.properties.write}}</view>
				<view>可读{{item1.properties.read}}</view>
				<button type="default" @click="monitor(item1.uuid)"
					v-if="characteristicId!==item1.uuid&&item1.properties.notify==true">监听</button>
				<button type="default" v-if="item1.properties.read==true" @click="readData(item1.uuid)">读取数据</button>
				<button type="default" v-if="item1.properties.write==true" @click="open(item1.uuid)">写入数据</button>
			</view>
		</view>
		<view v-for="(item, index) in stringArr" :key="index"
			style="display:flex;padding: 10rpx;box-sizing: border-box;width: 100%;border-bottom: 1rpx solid #000;justify-content: center;">
			<text
				style="width:90%;display:inline-block;white-space: pre-wrap; word-wrap: break-word;height: auto;">{{item}}</text>
		</view>
		<u-popup :show="show" @close="close" mode="center">
			<view style="padding: 20rpx;">
				<view>{{characteristicId1}}</view>
				<input placeholder="请输入数据" border="surround" v-model="writeDataValue"></input>
				<button @click="writeData()">确定</button>
			</view>
		</u-popup>
	</view>
</template>
 
<script>
	export default {
		data() {
			return {
				dataList: [],
				lanya: {},
				service: [],
				characteristic: [],
				stringArr: [],
				serviceuuid: undefined,
				characteristicId: undefined,
				characteristicId1: undefined,
				show: false,
				writeDataValue: "BB9AA90CEE",
				dataObject: {}
			}
		},
		onLoad() {
			this.initialize();
		},
		methods: {
			open(e) {
				this.show = true;
				this.characteristicId1 = e;
				// console.log('open');
			},
			close() {
				this.show = false;
				this.characteristicId1 = undefined;
				// this.writeDataValue = undefined
				// console.log('close');
			},
			//初始化
			initialize() {
				uni.openBluetoothAdapter({
					// 蓝牙初始化成功执行
					success: (res) => {
						// 这里成功之后就不用管了,直接执行就行
						uni.showToast({
							title: "初始化成功!",
							icon: "none",
							duration: 2000,
						});
					},
					// 蓝牙初始化失败执行
					fail: (err) => {
						// 初始化失败之后有需求的话可以进行一些处理
						// 没有需求的也不用管
						// 一般情况下,还是需要分析一下原因的,这用用户和自己就知道为什么失败了
						if (err.errCode == 10001) {
							// 这种情况是未打开蓝牙 就需要提示用户检测蓝牙并打开
							uni.showToast({
								title: "请检查蓝牙是否开启!",
								icon: "none",
								duration: 2000,
							});
						}
						// 我这里只演示这一个,其他的状态可进入官网进行查看
						//  uni-app  https://uniapp.dcloud.io/api/system/bluetooth.html
						// 微信原生  https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.openBluetoothAdapter.html
					}
				})
			},
			//搜索蓝牙
			searchBlue() {
				uni.startBluetoothDevicesDiscovery({
					success: () => {
						// 调用成功之后就开始查询附近蓝牙了
						// 成功之后可调用这个函数,每次查询到新的蓝牙设备就会调用
						// 这个函数使用不使用都可以,不影响查询的结果
						uni.onBluetoothDeviceFound((devices) => {
							console.log('蓝牙', devices) // 蓝牙设备信息
							// 返回的数据是ArrayBuffer格式的需要进行转换,不然咱也看不懂都是些啥
							// ArrayBuffer 的转换后面会详细写出来
						})
 
						setTimeout(() => {
							uni.getBluetoothDevices({
								success: (res) => {
									// res.devices 为 查询到的蓝牙设备列表
									// 拿到结果之后,进行处理
									// 首先进行过滤一下查询的列表,会出现很多未知设备这类的,这里就直接把它们给排除掉了,只留下有完整名称的设备
									var arr = []
									res.devices.forEach((element) => {
										if (element.name !== "位置设备") {
											arr.push(element);
										}
									})
									// 筛选之后在进行判断是否有正常的蓝牙设备  
									// 当然你也可以查看一下被筛选掉的设备是否有你所使用的设备,如果有你可以去掉筛选 或者自己定义筛选条件
									if (arr.length == 0) {
										uni.showToast({
											title: "未查询到可用设备,请重新扫描",
											duration: 1000,
											icon: "none",
										});
									}
									// 最后这个arr就是所查到的列表
									let arr1 = []
									arr.map((i) => {
										if (i.name) {
											arr1.push(i);
										}
									})
									this.dataList = arr1
									// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
									// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
									// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
								}
							})
						}, 2000)
					}
				})
			},
			// 关闭蓝牙搜索  
			stopBluetoothDevicesDiscovery() {
				uni.stopBluetoothDevicesDiscovery({
					success: e => {
						console.log('停止搜索蓝牙设备:' + e);
					},
					fail: e => {
						console.log('停止搜索蓝牙设备失败,错误码:' + e);
					}
				});
			},
			//连接蓝牙
			lianjie(e) {
				console.log('连接', e);
				// 为了能否顺利的连接蓝牙 可以先查询一下是否有设备已经连接蓝牙了
				// 手环这一类的设备 可能会对程序造成干扰 会一直显示设备已经连接
				uni.getConnectedBluetoothDevices({
					success: (res) => {
						// 因为我为了蓝牙的连接的稳定性,就做了这一步
						// 大家使用的过程中可以省略这一步直接进行蓝牙设备
						// 但是不确定是否可以正常进行蓝牙连接, 大家可以尝试一下
						// 如果返回的列表不等于空,就代表已经有设备连接
						if (res.devices.length !== 0) {
							// 这里就需要提示用户蓝牙已连接
							uni.showModal({
								title: "提示!",
								content: "当前蓝牙已于id为" + res.devices[0].deviceId + "的设备连接,是否断开连接",
								success: (row) => {
									// 用户点击确定执行
									if (row.confirm) {
										// 用户点击确定之后把当前连接的蓝牙断开
										uni.closeBLEConnection({
											// 一定要传入当前连接蓝牙的deviceId
											deviceId: res.devices[0].deviceId,
											success: () => {
												// 到这里就已经断开成功了,再次点击就可以进行连接了
												uni.showToast({
													title: "连接已断开!",
													icon: "none",
													duration: 2000,
												});
												this.lanya = {}
												this.service = []
												this.characteristic = []
												this.serviceuuid = undefined;
												this.characteristicId = undefined;
												this.stringArr = []
											},
											fail: (err) => {
												// 走到这就是断开失败了,可以进行操作提示用户或者自己查看
												uni.showToast({
													title: err.errMsg,
													icon: "none",
													duration: 2000,
												});
											}
										})
									} else {
										// 用户取消之后不需要做任何操作
										console.log('用户点击了取消')
									}
								}
							});
						} else {
							// 当前未处于已连接状态就开始进行连接,没有连接的情况下就可以直接连接蓝牙
							uni.createBLEConnection({
								// 连接的时候一定传入要连接蓝牙的deviceId
								deviceId: e,
								// 这里可以选择设置一个延迟,如果延迟时间过了没有连接成功就直接提示报错
								timeout: 5000,
								success: (res) => {
									// 连接成功之后可以再次进行查询一下当前连接的蓝牙信息,保存下载,后面方便使用
									uni.getConnectedBluetoothDevices({
										success: (devicess) => {
											// devicess就是当前已经连接的蓝牙列表
											// 在这判断一下有没有蓝牙,如果有就证明确实已经连接成功
											if (devicess.devices[0]) {
												// 到这里就可以提示用户成功,并且吧蓝牙信息保存下来方便后面使用
												console.log('连接成功', devicess.devices[
													0]) // 蓝牙信息
												uni.showToast({
													title: "连接成功!",
													icon: "none",
													duration: 2000,
												});
												this.lanya = devicess.devices[
													0]
												this.stopBluetoothDevicesDiscovery();
											} else {
												// 如果走这里面就是没有蓝牙设备,就要查看一下,看看是否真的连接成功了	
											}
										}
									})
								},
								fail: (err) => {
									// 在这里查看连接失败的信息,判断为什么连接失败
									console.log(err)
								}
							})
						}
					}
				})
 
			},
			//断开蓝牙
			closelanya() {
				uni.closeBLEConnection({
					// 一定要传入当前连接蓝牙的deviceId
					deviceId: this.lanya.deviceId,
					success: () => {
						// 到这里就已经断开成功了,再次点击就可以进行连接了
						uni.showToast({
							title: "连接已断开!",
							icon: "none",
							duration: 2000,
						});
						this.lanya = {}
						this.service = []
						this.characteristic = []
						this.serviceuuid = undefined;
						this.characteristicId = undefined;
						this.stringArr = []
					},
					fail: (err) => {
						// 走到这就是断开失败了,可以进行操作提示用户或者自己查看
						uni.showToast({
							title: err.errMsg,
							icon: "none",
							duration: 2000,
						});
					}
				})
			},
			//服务值获取
			communication() {
				// 蓝牙接收数据主要使用的api是开启监听(uni.notifyBLECharacteristicValueChange)
				// 但是开启监听是需要几个特殊的值才能开启
				// 所以开启之前我们需要获取这个值 
				// deviceId  蓝牙deviceId,蓝牙信息中包含的有
				// serviceId 蓝牙服务值,根据蓝牙deviceId获取
				// characteristicId 蓝牙特征值 根据serviceId 获取
				// 首先根据deviceId  获取到服务值 serviceId 
				uni.getBLEDeviceServices({
					deviceId: this.lanya.deviceId, // 获取服务值,需要传入deviceId
					success: (res) => {
						// res.services 返回一个数组,里面包含着支持不同的通讯方式的serviceId  一般为三个左右,也有可能更多
						console.log('服务值', res.services)
						this.service = res.services
						// 拿到之后根据自己所需要的去保存serviceId,在后面使用
						// 这里建议多试试,说不定那个可以用,又或者某个不能用
						if (res.services.length <= 0) {
							this.communication();
						} else {
							// this.choose(res.services[res.services.length - 2].uuid);
							// this.choose(res.services[0].uuid);
						}
					},
					fial: (err) => {
						// 一般来说只要	deviceId 对,就不会报错
					}
				})
			},
			//选择服务值
			choose(e) {
				this.characteristic = []
				this.stringArr = []
				this.characteristicId = undefined
				this.serviceuuid = e;
				this.characteristicget();
			},
			//特征值获取
			characteristicget() {
				//选第4个
				// 获取到之后就可以去拿着获取到的serviceId和deviceId去获取特征值
				uni.getBLEDeviceCharacteristics({
					deviceId: this.lanya.deviceId, // 传入获取到的deviceId
					serviceId: this.serviceuuid, // 传入获取到的serviceuuid
					success: (ress) => {
						// ress里面就是获取到的蓝牙特征值
						// 注意:根据传入serviceuuid的不同获取到的特征值也是不一样的,
						// 特征值分为,可读、可写、可通知等几个类型的,根据自己想要的操作选择特征值
						console.log('特征值', ress)
						this.characteristic = ress.characteristics
						// for (var i = 0; i < ress.characteristics.length; i++) {
						// 	var model = ress.characteristics[i];
 
						// 	if (model.properties.notify == true) {
						// 		this.monitor(model.uuid);
						// 	}
						// }
					},
					fial: (err) => {
						// 一般来说只要参数对,就不会报错
					}
				})
			},
			//监听
			monitor(e) {
				//选第1个
				// 这里所声明介绍一下所用到的东西
				// deviceId 就是上面蓝牙设备的deviceId
				// serviceuuid 就是上面根据蓝牙设备获取到的serviceuuid 
				// characteristics 就是上面根据 deviceId 和 serviceuuid 获取到的
				// characteristics 是一个数组里面包含着多个特征值, 根据使用去拿响应的特征值
 
				// 我这里没有直接使用,而是进行一个循环判断,判断这么多的特征值中那个是符合要求的
				// var characteristicId;
				// var i = 0
				// then.characteristiclist.forEach((element) => {
				// 	if (element.properties.notify == true) {
				// 		if (i == 0) {
				// 			characteristicId = element.uuid;
				// 			i++;
				// 		}
				// 	}
				// });
				// 为什么循环
				// 因为开启uni.notifyBLECharacteristicValueChange需要特征值是需要固定的,我就直接判断写入了
				let that = this
				uni.notifyBLECharacteristicValueChange({
					deviceId: that.lanya.deviceId,
					serviceId: that.serviceuuid,
					characteristicId: e,
					state: true,
					success: () => {
						that.characteristicId = e
						console.log('监听启动成功');
						this.stringArr = []
						// 启用成功之后就可以在uni.onBLECharacteristicValueChange 中获取到蓝牙设备发送的数据
						that.rxd()
					},
				});
 
			},
			//数据接受
			rxd() {
				console.log('监听返回');
				let that = this
				let str1 = '';
				uni.onBLECharacteristicValueChange((res) => {
					console.log('接收数据', res.value)
					// 这就是蓝牙发送的数据 但是现在的数据,还不能直观的看出来是什么,还需要进行一些列转换才能直观查看
					// ArrayBufer转16进制
					console.log('ArrayBufer转16进制', that.buf2hex(res.value))
					let str = that.buf2hex(res.value);
					console.log(str);
 
					//16进制转字符串
					// console.log('ArrayBufer转字符串',that.hexToString(that.buf2hex(res.value)))
					// if (that.hexToString(that.buf2hex(res.value))) {
					// 	that.stringArr.push(that.hexToString(that.buf2hex(res.value)))
					// }
					//16进制转字符串处理中文乱码
					// console.log('ArrayBufer转字符串处理乱码',that.utf8to16(that.hexToString(that.buf2hex(res.value))))
					// if (that.utf8to16(that.hexToString(that.buf2hex(res.value)))) {
					// 	that.stringArr.push(that.utf8to16(that.hexToString(that.buf2hex(res.value))))
					// }
 
					//ArrayBufer直接转字符串
					// console.log('ArrayBufer直接转字符串',that.utf8to16(that.buf2str(res.value)));
					// if (that.utf8to16(that.buf2str(res.value))) {
					// 	that.stringArr.push(that.utf8to16(that.buf2str(res.value)))
					// }
					// 经过 this.buf2str 转换之后就可以直观查看 蓝牙返回的信息
				});
 
			},
			// 读取数据
			readData(e) {
				//选第3个
				let that = this
				uni.readBLECharacteristicValue({
					deviceId: that.lanya.deviceId,
					serviceId: that.serviceuuid,
					characteristicId: e,
					success(res) {
						console.log('读取数据:', res.errCode)
					}
				})
			},
			//写入数据
			writeData() {
				//选第2个
				// 所用到的就是上面获取到的
				// 但是特征值跟上面不一样,需要支持可写
				// var characteristicId;
				// var i = 0;
				// then.characteristiclist.forEach((element) => {
				// 	if (element.properties.write == true) {
				// 		if (i == 0) {
				// 			characteristicId = element.uuid;
				// 			i++;
				// 		}
				// 	}
				// });	
				let that = this
				// console.log(that.writeDataValue);
				// console.log(that.hexToString(that.writeDataValue));
				// console.log(that.strToHexCharCode(that.writeDataValue));
				// console.log(that.hex2buf(that.strToHexCharCode(that.writeDataValue)));
				console.log('12进制转arraybuffer', that.hex2buf(that.writeDataValue));
				uni.writeBLECharacteristicValue({
					deviceId: that.lanya.deviceId,
					serviceId: that.serviceuuid,
					characteristicId: that.characteristicId1,
					value: that.hex2buf(that.writeDataValue),
					// value: that.hex2buf(that.strToHexCharCode(that.writeDataValue)),
					// writeType: "write",
					success: (res) => {
						console.log('指令写入成功');
						console.log(res);
						that.show = false
						that.characteristicId1 = undefined;
						// that.writeDataValue = undefined
						uni.showToast({
							title: "指令写入成功",
							icon: "none",
							duration: 2000,
						});
						// 写入成功之后在onBLECharacteristicValueChange里面应该是有反馈的
						that.rxd()
					},
					fail(err) {
						console.log(err);
						uni.showToast({
							title: err.message,
							icon: "none",
							duration: 2000,
						});
					}
				})
				// this.string2buffer 把16进制字符串转换成buffer之后进行写入,我也忘了直接传入字符串可不可以使用,应该是不行,可以尝试一下,不行就按着我的来也行
 
			},
			//蓝牙状态变化处理
			statesChange() {
				uni.onBLEConnectionStateChange(res => {
					// 该方法回调中可以用于处理连接意外断开等异常情况
					console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`);
 
				});
			},
			// arraybuffer类型转16进制字符串
			buf2hex(buffer) {
 
				const hexArr = Array.prototype.map.call(
					new Uint8Array(buffer),
					function(bit) {
						return ('00' + bit.toString(16)).slice(-2)
					}
				)
				console.log('arraybuffer类型转16进制', hexArr.join(''));
				return hexArr.join('')
 
			},
			// 16进制转arraybuffer类型
			hex2buf(e) {
				var hex = e
				var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function(h) {
					return parseInt(h, 16)
				}))
				var buffer = typedArray.buffer
				return buffer;
			},
			// 16进制转字符串
			hexToString(hexCharCodeStr) {
				var trimedStr = hexCharCodeStr.trim();
				var rawStr =
					trimedStr.substr(0, 2).toLowerCase() === "0x" ?
					trimedStr.substr(2) :
					trimedStr;
				var len = rawStr.length;
				if (len % 2 !== 0) {
					console.log("非法格式ASCII码!");
					return "";
				}
				var curCharCode;
				var resultStr = [];
				for (var i = 0; i < len; i = i + 2) {
					curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value
					resultStr.push(String.fromCharCode(curCharCode));
				}
				return resultStr.join("");
			},
			//字符串转16进制
			strToHexCharCode(str) {
				if (str === "")
					return "";
				var hexCharCode = [];
				hexCharCode.push("0x");
				for (var i = 0; i < str.length; i++) {
					hexCharCode.push((str.charCodeAt(i)).toString(16));
				}
				return hexCharCode.join("");
			},
			//处理中文乱码问题
			utf8to16(str) {
				// console.log(str);
				var out, i, len, c;
				var char2, char3;
				out = "";
				len = str.length;
				i = 0;
				while (i < len) {
					c = str.charCodeAt(i++);
					switch (c >> 4) {
						case 0:
						case 1:
						case 2:
						case 3:
						case 4:
						case 5:
						case 6:
						case 7:
							out += str.charAt(i - 1);
							break;
						case 12:
						case 13:
							char2 = str.charCodeAt(i++);
							out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
							break;
						case 14:
							char2 = str.charCodeAt(i++);
							char3 = str.charCodeAt(i++);
							out += String.fromCharCode(((c & 0x0F) << 12) |
								((char2 & 0x3F) << 6) |
								((char3 & 0x3F) << 0));
							break;
					}
				}
 
				console.log(out, 'out')
				return out;
			},
		},
	}
</script>
 
<style scoped lang="scss">
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}
 
	.list {
		padding: 20rpx;
 
		view {
			display: flex;
			justify-content: space-around;
		}
	}
</style>

END...