uniapp、pc通过vue2实现连接蓝牙电子秤,实时获取重量

电子秤用的是香山的

一、效果展示

pc效果图(是在弹窗里)

截屏2026-02-02 17.07.48.png

uniapp效果图

截屏2026-02-02 17.14.05.png

二、pc端代码

1、电子秤组件代码bluetoothScale.vue

<template>
  <div class="bluetooth-scale">
    <div class="status">电子秤: {{ isConnected ? deviceName : '未连接' }}</div>

    <div class="controls">
      <el-button type="success" :disabled="scanning" @click="connect">
        {{ scanning ? '扫描中...' : '连接' }}
      </el-button>
      <el-button type="danger" :disabled="!isConnected" @click="disconnect">
        断开
      </el-button>
    </div>

    <div class="weight">
      <div class="value">{{ currentWeight }} kg</div>
      <div class="stable">稳定: {{ weightStable ? '是' : '否' }}</div>
    </div>
  </div>
</template>

<script>
// 蓝牙电子秤
export default {
  name: 'BluetoothScaleCore',
  data() {
    return {
      // 设备状态
      isConnected: false,
      deviceName: '',
      device: null,
      characteristic: null,
      // 扫描状态
      scanning: false,
      // 重量数据
      currentWeight: null,
      weightStable: false,
      dataBuffer: null
    }
  },
  mounted() {
    this.checkBluetoothSupport()
  },

  beforeDestroy() {
    this.disconnect()
  },

  methods: {
    // 检查蓝牙支持
    checkBluetoothSupport() {
      if (!navigator.bluetooth) {
        this.errorMessage = '浏览器不支持Web Bluetooth API'
        return false
      }
      return true
    },
    async connect() {
      if (!this.checkBluetoothSupport()) return

      this.scanning = true

      try {
        const SERVICE_UUID = '0000fff0-0000-1000-8000-00805f9b34fb'
        const CHAR_UUID = '0000fff1-0000-1000-8000-00805f9b34fb'

        this.device = await navigator.bluetooth.requestDevice({
          filters: [
            { services: [SERVICE_UUID] },
            { namePrefix: 'XS' },
            { namePrefix: '香山' }
          ],
          optionalServices: [SERVICE_UUID]
        })

        this.deviceName = this.device.name || '蓝牙电子秤'

        this.device.addEventListener('gattserverdisconnected', () => {
          this.isConnected = false
          this.device = null
          this.currentWeight = null
        })

        const server = await this.device.gatt.connect()
        this.isConnected = true

        const service = await server.getPrimaryService(SERVICE_UUID)
        this.characteristic = await service.getCharacteristic(CHAR_UUID)

        await this.characteristic.startNotifications()
        this.characteristic.addEventListener(
          'characteristicvaluechanged',
          this.handleData
        )
      } catch (error) {
        console.error('连接失败:', error)
        this.isConnected = false
      } finally {
        this.scanning = false
      }
    },

    handleData(event) {
      // console.log(8, '秤', event)
      const value = event.target.value
      if (!value?.buffer) return

      if (!this.dataBuffer) this.dataBuffer = new Uint8Array(0)

      const newData = new Uint8Array(value.buffer)
      const combined = new Uint8Array(this.dataBuffer.length + newData.length)
      combined.set(this.dataBuffer, 0)
      combined.set(newData, this.dataBuffer.length)
      this.dataBuffer = combined

      const PACKET_SIZE = 31
      while (this.dataBuffer.length >= PACKET_SIZE) {
        this.parsePacket(this.dataBuffer.slice(0, PACKET_SIZE).buffer)
        this.dataBuffer = this.dataBuffer.slice(PACKET_SIZE)
      }
    },

    parsePacket(buffer) {
      const view = new DataView(buffer)

      // 检查数据包格式
      const header1 = view.getUint32(0, false)
      const header2 =
        (view.getUint8(4) << 16) | (view.getUint8(5) << 8) | view.getUint8(6)

      if (header1 === 0x100000c5 && header2 === 0x1faa82) {
        const status = view.getUint8(22)
        const isZero = ((status >> 1) & 0x01) === 1
        const isStable = ((status >> 3) & 0x01) === 1

        // 解析重量(3字节大端序,单位克)
        let grams =
          (view.getUint8(23) << 16) |
          (view.getUint8(24) << 8) |
          view.getUint8(25)

        // 处理负数
        if ((status & 0x01) === 1 && grams & 0x800000) {
          grams = -(0x1000000 - grams)
        }

        let weight = grams / 1000.0
        if (isZero) weight = 0

        // 限制范围
        weight = Math.max(0, Math.min(weight, 150))

        this.currentWeight = weight
        this.weightStable = isStable
      }
    },

    async disconnect() {
      if (this.characteristic) {
        try {
          await this.characteristic.stopNotifications()
        } catch (e) {
          console.warn('停止通知失败:', e)
        }
      }

      if (this.device?.gatt.connected) {
        this.device.gatt.disconnect()
      }

      this.isConnected = false
      this.device = null
      this.characteristic = null
      this.currentWeight = null
      this.dataBuffer = null
    }
  }
}
</script>

<style scoped lang="scss">
.bluetooth-scale {
  padding: 20px;
  max-width: 300px;
  margin: 0 auto;

  .status {
    margin-bottom: 15px;
    font-size: 14px;
  }

  .controls {
    display: flex;
    gap: 10px;
    margin-bottom: 15px;

    .el-button {
      flex: 1;
    }
  }

  .weight {
    padding: 15px;
    background: #f5f5f5;
    border-radius: 6px;
    text-align: center;
  }

  .value {
    font-size: 24px;
    font-weight: bold;
    color: #2196f3;
    margin-bottom: 5px;
  }

  .stable {
    font-size: 12px;
    color: #666;
  }
}
</style>

2、弹窗父组件代码

<template>
  <el-dialog
    v-diadrag
    append-to-body
    title="新增"
    :close-on-click-modal="false"
    :visible.sync="visible"
    width="500px"
  >
    <el-form
      label-width="100px"
      :model="dataForm"
      :rules="dataRule"
      ref="dataForm"
      class="asun-mes"
    >
      <el-form-item label="物料类型" prop="materialType">
        <el-select
          v-model="dataForm.materialType"
          placeholder="请选择物料类型"
          style="width: 100%"
        >
          <el-option label="胶料" value="胶料" />
          <el-option label="芯棒" value="芯棒" />
          <el-option label="球头" value="球头" />
          <el-option label="球窝" value="球窝" />
        </el-select>
      </el-form-item>
      <el-form-item label="数量" prop="quantity">
        <el-input
          type="number"
          :min="0"
          v-model="dataForm.quantity"
          placeholder="数量"
        />
      </el-form-item>
      <!-- 蓝牙电子秤 -->
      <BluetoothScale v-if="dataForm.materialType" ref="bluetoothScaleRef" />
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="dataFormSubmit">确定</el-button>
    </span>
  </el-dialog>
</template>
<script>
import BluetoothScale from '@/views/common/bluetoothScale'

const initDataForm = {
  materialType: null,
  quantity: null
}

export default {
  components: {
    BluetoothScale
  },
  data() {
    return {
      visible: false,
      dataForm: { ...initDataForm },
      dataRule: {}
    }
  },
  watch: {
    visible(val) {
      if (!val) {
        // 主要为了销毁电子秤组件,因为电子秤一直在给组件发送数据,会导致页面卡顿
        this.dataForm = { ...initDataForm }
      }
    }
  },
  created() {},
  methods: {
    init() {
      this.visible = true
      this.$nextTick(() => {
        this.dataForm = { ...initDataForm }
      })
    },
    // 表单提交
    dataFormSubmit() {
      this.$refs.dataForm.validate((valid) => {
        if (valid) {
          const params = {
            ...this.dataForm
          }
          const weight = this.$refs.bluetoothScaleRef.currentWeight
          if (weight) params.quantity = weight
          if (!params.quantity) {
            this.$message.error('请先输入数量或连接电子秤')
            return
          }

          console.log(3333, '表单数据', params)
        }
      })
    }
  }
}
</script>

三、uniapp代码

1、在uniapp里首先需要在manifest.json里配置下蓝牙权限"Bluetooth" : true,代码如下

{
    "name" : "JJ",
    "appid" : "__UNI__AABD66C",
    "description" : "pigcloud",
    "app-plus" : {
        "modules" : {
            "Bluetooth" : true // 蓝牙权限
        }
    }
}

2、电子秤组件代码bluetoothScale.vue

<template>
  <view class="bluetooth-scale">
    <view class="status">
      电子秤:{{ lanya.deviceId ? lanya.name : "未连接" }}
    </view>

    <view class="controls">
      <u-button type="success" @click="openBleModal">连接</u-button>
      <u-button type="error" @click="closelanya" :disabled="!lanya.deviceId">
        断开
      </u-button>
    </view>

    <view class="weight">
      <view class="value">{{ currentWeight }} kg</view>
      <view class="stable">稳定:{{ weightStable ? "是" : "否" }}</view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      dataList: [], // 蓝牙设备列表
      lanya: {}, // 已连接设备
      service: [], // 保留
      characteristic: [], // 保留
      serviceuuid: undefined, // 保留
      characteristicId: undefined, // 保留
      dataBuffer: null, // 分包拼接缓冲区
      currentWeight: null, // 实时重量(kg)
      weightStable: false, // 重量稳定状态
      PACKET_SIZE: 31, // 秤固定数据包长度
      TARGET_SERVICE_UUID: "0000FFF0-0000-1000-8000-00805F9B34FB", // 固定服务UUID
      isModalOpen: false, // 弹窗是否开启标识
    };
  },
  mounted() {
    this.initialize();
  },
  beforeDestroy() {
    this.lanya.deviceId && this.closelanya();
  },
  methods: {
    // 初始化蓝牙适配器
    initialize() {
      uni.openBluetoothAdapter({
        success: () => this.$u.toast("蓝牙初始化成功!"),
        fail: (err) => {
          if (err.errCode === 10001) {
            this.$u.toast("请先开启手机蓝牙!");
          } else {
            this.$u.toast(`蓝牙初始化失败:${err.errCode}-${err.errMsg}`);
          }
        },
      });
    },

    // 打开蓝牙选择弹窗
    openBleModal() {
      if (this.isModalOpen) return;
      this.isModalOpen = true;
      this.searchBlue()
        .then(() => {
          if (this.dataList.length === 0) {
            this.$u.toast("未搜索到XS-BLE系列电子秤");
            this.isModalOpen = false;
            return;
          }
          const modalItems = this.dataList.map(
            (item) => `${item.name || "XS-BLE"} (${item.deviceId})`,
          );
          uni.showActionSheet({
            title: `选择要连接的蓝牙秤(已搜索到${this.dataList.length}台)`,
            itemList: modalItems,
            success: (res) =>
              this.lianjie(this.dataList[res.tapIndex].deviceId),
            fail: () => (this.isModalOpen = false),
            complete: () => (this.isModalOpen = false),
          });
        })
        .catch((err) => {
          this.$u.toast(`搜索失败:${err.msg || "未知错误"}`);
          this.isModalOpen = false;
        });
    },

    // 搜索蓝牙设备
    searchBlue() {
      return new Promise((resolve, reject) => {
        this.dataList = [];
        uni.startBluetoothDevicesDiscovery({
          success: () => {
            uni.onBluetoothDeviceFound((devices) => {
              const device = devices.devices[0];
              if (
                device.name &&
                device.name.includes("XS-BLE") &&
                !this.dataList.some((item) => item.deviceId === device.deviceId)
              ) {
                this.dataList.push(device);
              }
            });

            setTimeout(() => {
              uni.getBluetoothDevices({
                success: (res) => {
                  this.dataList = res.devices.filter(
                    (item) => item.name && item.name.includes("XS-BLE"),
                  );
                  this.stopBluetoothDevicesDiscovery();
                  resolve();
                },
                fail: (err) => {
                  this.stopBluetoothDevicesDiscovery();
                  reject({ msg: err.errMsg });
                },
              });
            }, 2000);
          },
          fail: (err) => reject({ msg: err.errMsg }),
        });
      });
    },

    // 关闭蓝牙搜索
    stopBluetoothDevicesDiscovery() {
      uni.stopBluetoothDevicesDiscovery({
        success: () => console.log("停止蓝牙搜索"),
        fail: () => console.log("停止蓝牙搜索失败"),
      });
    },

    // 连接蓝牙设备
    lianjie(deviceId) {
      console.log("连接设备ID:", deviceId);
      this.resetBleState();
      uni.getConnectedBluetoothDevices({
        success: (res) => {
          if (res.devices.length !== 0) {
            uni.showModal({
              title: "提示",
              content: "当前已连接其他蓝牙设备,是否断开并连接新设备?",
              success: (row) =>
                row.confirm &&
                this.closeOldConnThenCreate(res.devices[0].deviceId, deviceId),
            });
          } else {
            this.createBleConnection(deviceId);
          }
        },
        fail: (err) => {
          this.$u.toast(`检查连接状态失败:${err.errMsg}`);
          this.createBleConnection(deviceId);
        },
      });
    },

    // 断开旧连接后创建新连接
    closeOldConnThenCreate(oldDeviceId, newDeviceId) {
      uni.closeBLEConnection({
        deviceId: oldDeviceId,
        success: () => {
          this.$u.toast("已断开原有连接");
          this.createBleConnection(newDeviceId);
        },
        fail: (err) => this.$u.toast(`断开原有连接失败:${err.errMsg}`),
      });
    },

    // 封装蓝牙连接方法
    createBleConnection(deviceId) {
      uni.createBLEConnection({
        deviceId,
        timeout: 5000,
        success: () => {
          uni.getConnectedBluetoothDevices({
            success: (devicess) => {
              const targetDevice = devicess.devices.find(
                (item) => item.deviceId === deviceId,
              );
              if (targetDevice) {
                this.$u.toast("设备连接成功!");
                console.log("连接成功", targetDevice);
                this.lanya = targetDevice;
                this.stopBluetoothDevicesDiscovery();
                this.communication();
              } else {
                this.$u.toast("连接失败,未检测到设备");
              }
            },
            fail: (err) => this.$u.toast(`获取设备信息失败:${err.errMsg}`),
          });
        },
        fail: (err) => this.$u.toast(`连接失败:${err.errMsg}`),
      });
    },

    // 断开蓝牙连接
    closelanya() {
      if (!this.lanya.deviceId) {
        this.$u.toast("未连接任何设备");
        return;
      }
      uni.closeBLEConnection({
        deviceId: this.lanya.deviceId,
        success: () => {
          this.$u.toast("连接已断开!");
          this.resetBleState();
        },
        fail: (err) => this.$u.toast(`断开失败:${err.errMsg}`),
      });
    },

    // 重置蓝牙状态和数据
    resetBleState() {
      this.lanya = {};
      this.serviceuuid = undefined;
      this.characteristicId = undefined;
      this.dataBuffer = null;
      this.currentWeight = null;
      this.weightStable = false;
    },

    // 匹配固定服务UUID
    communication() {
      if (!this.lanya.deviceId) return;
      this.$u.toast("正在匹配电子秤服务...");
      uni.getBLEDeviceServices({
        deviceId: this.lanya.deviceId,
        success: (res) => {
          const targetService = res.services.find(
            (item) => item.uuid === this.TARGET_SERVICE_UUID,
          );
          if (targetService) {
            this.$u.toast("服务匹配成功,开始监听重量...");
            this.serviceuuid = targetService.uuid;
            this.characteristicget();
          } else {
            this.$u.toast("未找到电子秤核心服务");
          }
        },
        fail: (err) => this.$u.toast(`获取服务失败:${err.errMsg}`),
      });
    },

    // 获取特征值并筛选可监听项
    characteristicget() {
      uni.getBLEDeviceCharacteristics({
        deviceId: this.lanya.deviceId,
        serviceId: this.serviceuuid,
        success: (ress) => {
          const notifyChar = ress.characteristics.find(
            (item) => item.properties.notify === true,
          );
          notifyChar && this.monitor(notifyChar.uuid);
        },
        fail: (err) => this.$u.toast(`获取特征值失败:${err.errMsg}`),
      });
    },

    // 开启重量数据监听
    monitor(characteristicId) {
      this.dataBuffer = null;
      this.currentWeight = null;
      this.weightStable = false;
      uni.notifyBLECharacteristicValueChange({
        deviceId: this.lanya.deviceId,
        serviceId: this.serviceuuid,
        characteristicId,
        state: true,
        success: () => {
          this.characteristicId = characteristicId;
          uni.onBLECharacteristicValueChange((res) =>
            this.handleScaleData(res.value),
          );
        },
        fail: (err) => this.$u.toast(`监听开启失败:${err.errMsg}`),
      });
    },

    // 分包数据拼接处理
    handleScaleData(buffer) {
      if (!buffer) return;
      if (!this.dataBuffer) this.dataBuffer = new Uint8Array(0);
      console.log("原始数据:", buffer);
      const newData = new Uint8Array(buffer);
      const combined = new Uint8Array(this.dataBuffer.length + newData.length);
      combined.set(this.dataBuffer, 0);
      combined.set(newData, this.dataBuffer.length);
      this.dataBuffer = combined;

      while (this.dataBuffer.length >= this.PACKET_SIZE) {
        this.parseScalePacket(
          this.dataBuffer.slice(0, this.PACKET_SIZE).buffer,
        );
        this.dataBuffer = this.dataBuffer.slice(this.PACKET_SIZE);
      }
    },

    // 重量数据包解析
    parseScalePacket(buffer) {
      const view = new DataView(buffer);
      const header1 = view.getUint32(0, false);
      const header2 =
        (view.getUint8(4) << 16) | (view.getUint8(5) << 8) | view.getUint8(6);

      if (header1 === 0x100000c5 && header2 === 0x1faa82) {
        const status = view.getUint8(22);
        const isZero = ((status >> 1) & 0x01) === 1;
        const isStable = ((status >> 3) & 0x01) === 1;
        let grams =
          (view.getUint8(23) << 16) |
          (view.getUint8(24) << 8) |
          view.getUint8(25);

        if (status & 0x01 && grams & 0x800000) {
          grams = -(0x1000000 - grams);
        }

        let weight = grams / 1000.0;
        if (isZero) weight = 0;
        weight = Math.max(0, Math.min(weight, 150));

        this.currentWeight = weight;
        this.weightStable = isStable;
      }
    },
  },
};
</script>

<style scoped lang="scss">
.bluetooth-scale {
  padding: 30rpx;
  margin: 0 auto;

  .status {
    margin-bottom: 30rpx;
    font-size: 14px;
  }

  .controls {
    display: flex;
    gap: 40rpx;
    margin-bottom: 20rpx;
    .u-btn {
      flex: 1;
    }
  }

  .weight {
    padding: 30rpx;
    background: #f5f5f5;
    border-radius: 6px;
    text-align: center;
  }

  .value {
    font-size: 24px;
    font-weight: bold;
    color: #2196f3;
    margin-bottom: 10rpx;
  }

  .stable {
    font-size: 12px;
    color: #666;
  }
}
</style>

3、页面父组件代码

<template>
  <view class="form-page fixed-footer">
    <u-form
      :model="formData"
      ref="uForm"
      label-position="left"
      label-width="160"
    >
      <u-form-item label="物料类型" prop="materialType">
        <u-input
          type="select"
          :value="formData.materialType"
          placeholder="请选择"
          @click="openMaterialType"
        />
        <u-select
          value-name="value"
          label-name="label"
          v-model="materialTypeShow"
          :list="materialTypeOps"
          @confirm="materialTypeChg"
        />
      </u-form-item>
      <u-form-item label="数量" prop="quantity">
        <u-input v-model="formData.quantity" />
      </u-form-item>
      <BluetoothScale ref="bluetoothScaleRef" />

      <view class="form-footer">
        <u-button type="primary" @click="onSubmit">提交</u-button>
      </view>
    </u-form>
  </view>
</template>
<script>
// 废料登记
import BluetoothScale from "@/pages/common/bluetoothScale.vue";

export default {
  components: {
    BluetoothScale,
  },
  data() {
    return {
      formData: {
        materialType: null,
        quantity: null,
      },
      list: [],
      materialTypeShow: false,
      materialTypeOps: [
        {
          label: "胶料",
          value: "胶料",
        },
        {
          label: "芯棒",
          value: "芯棒",
        },
        {
          label: "球头",
          value: "球头",
        },
        {
          label: "球窝",
          value: "球窝",
        },
      ],
    };
  },
  computed: {},
  onHide() {},
  onLoad(query) {},
  methods: {
    onSubmit() {
      this.$refs.uForm.validate().then((valid) => {
        if (valid) {
          const params = {
            ...this.formData,
          };
          const weight = this.$refs.bluetoothScaleRef.currentWeight;
          if (weight) params.quantity = weight;
          if (!params.quantity) {
            uni.$u.toast("请先输入数量或连接电子秤");
            return;
          }

          console.log(888, "页面数据", params);
        }
      });
    },
    // 物料类型
    openMaterialType() {
      this.materialTypeShow = true;
    },
    materialTypeChg(e) {
      this.formData.materialType = e[0].value;
      this.materialTypeShow = false;
    },
  },
};
</script>
<style lang="scss" scoped></style>

四、参考

uniapp的代码参考的是:https://article.juejin.cn/post/7437148772575395849
怕大佬的文档不能查看,所以把大佬的文档粘过来了。

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


image.png
image.png
蓝牙电子秤连接步骤:

初始化蓝牙---搜索蓝牙---连接蓝牙(蓝牙设备值)---获取服务值(蓝牙设备值)---获取特征值(蓝牙设备值+蓝牙服务值)---监听---处理蓝牙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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容