Skip to content

使用方法

介绍

安卓蓝牙操作UTS原生插件集成了常用的蓝牙开关,设备配对,已配对的设备列表,搜索设备,连接设备,发送数据,蓝牙文件传输等多项功能,插件支持uniapp和uniapp x

插件说明

  • 如果您在使用插件的过程中有任何问题,可以联系作者,作者将全力协助您只用插件
  • 本文档同时提供了uniapp的用法示例和uniappx的用法示例,插件市场导入的示例项目仅为uniapp的用法示例,如果您需要uniappx的示例项目可以通过下方的链接下载示例

    https://pan.baidu.com/s/1oaiubwdAffcHCWFHRu2TJQ?pwd=za1t

联系作者

关注微信公众号可联系作者

插件地址

https://ext.dcloud.net.cn/plugin?id=27995

权限

  • android.permission.BLUETOOTH
  • android.permission.BLUETOOTH_ADMIN
  • android.permission.ACCESS_FINE_LOCATION
  • android.permission.ACCESS_COARSE_LOCATION
  • android.permission.POST_NOTIFICATIONS
  • android.permission.READ_EXTERNAL_STORAGE
  • android.permission.WRITE_EXTERNAL_STORAGE
  • android.permission.MANAGE_EXTERNAL_STORAGE
  • android.permission.BLUETOOTH_CONNECT
  • android.permission.BLUETOOTH_SCAN
  • android.permission.BLUETOOTH_ADVERTISE

用法

在需要使用插件的页面加载以下代码

js
import * as module from "@/uni_modules/leven-uts-bluetooth"
js
import * as module from "@/uni_modules/leven-uts-bluetooth"

页面内容

vue
<template>
  <view class="content">
    <!-- 按钮组 -->
    <view>
      <view style="display: flex; flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="enableBluetooth">开启蓝牙</button>
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="disableBluetooth">关闭蓝牙</button>
      </view>
      <view style="display: flex; flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1;" type="primary" size="mini" @click="getPairedDevices">获取已配对设备</button>
      </view>
      <view style="display: flex; flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="startDiscovery">搜索设备</button>
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="stopDiscovery">停止搜索</button>
      </view>
      <view style="display: flex; flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="sendBytes">发送字节</button>
        <button style="flex: 2; margin-right: 8px;" type="primary" size="mini" @click="sendHexString">发送十六进制</button>
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="sendText">发送文本</button>
      </view>
      <view style="display: flex; flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="sendSelectFile">选择文件并发送</button>
        <button style="flex: 1;" type="primary" size="mini" @click="openBluetoothSettings">跳转到蓝牙设置界面</button>
      </view>
    </view>
    <!-- 设备列表 -->
    <view style="display: flex; flex-direction: column; border: 1px solid #F2F2F2;">
      <view style="height: 40px; display: flex; align-items: center; background-color: #F2F2F2; padding: 0 8px;">
        <text style="font-size: 16px;">设备列表</text>
      </view>
      <scroll-view class="scroll-Y" scroll-y="true" direction="vertical">
        <view v-for="(item, index) in devices" :key="index"
          style="display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 8px; font-size: 12px;">
          <view>
            <view>{{item.name}}</view>
            <view>{{item.address}}</view>
          </view>
          <view v-if="item.bondState == 12"><button type="primary" size="mini" @click="openDialog(item)">连接</button>
          </view>
          <view v-if="item.bondState == 10"><button type="primary" size="mini" @click="openDialog(item)">配对</button>
          </view>
        </view>
      </scroll-view>
    </view>
    <!-- 日志 -->
    <view style="display: flex; flex-direction: column; border: 1px solid #F2F2F2;">
      <view style="height: 40px; display: flex; align-items: center; background-color: #F2F2F2; padding: 0 8px;">
        <text style="font-size: 16px;">日志</text>
      </view>
      <scroll-view class="scroll-Y" scroll-y="true" direction="vertical">
        <view v-for="(item, index) in logs" :key="index" style="padding: 8px; font-size: 12px;">{{item}}</view>
      </scroll-view>
    </view>

    <!-- 配对或连接弹出层 -->
    <uni-popup ref="refConnectPop" background-color="#fff" border-radius="8px">
      <view style=" width: 600rpx;">
        <!-- 标题 -->
        <view
          style="height: 40px; border-bottom: 1px solid #F2F2F2; display: flex; flex-direction: row; justify-content: center; align-items: center;">
          {{device.name || ""}}
        </view>
        <view style="padding: 8px;">
          <view>地址:{{device.address}}</view>
          <view>状态:{{device.bondState == 12 ? "已配对" : "未配对"}}</view>
        </view>
        <!-- 按钮 -->
        <view
          style="display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 8px 8px 16px 8px;">
          <view style="flex: 1;">
            <button type="primary" size="mini" v-if="device.bondState == 12" @click="unpairDevice">取消配对</button>
          </view>
          <view
            style="flex: 1; display: flex; flex-direction: row; justify-content: space-between; align-items: center;">
            <button type="primary" size="mini" @click="closeDialog">取消</button>
            <button type="primary" size="mini" @click="connectOrPair">{{device.bondState == 12 ? "连接" : "配对"}}</button>
          </view>
        </view>
      </view>
    </uni-popup>

    <!-- 接收文件请求弹窗 -->
    <uni-popup ref="refFileRequestPop" background-color="#fff" border-radius="8px">
      <view style=" width: 600rpx;">
        <!-- 标题 -->
        <view
          style="height: 40px; border-bottom: 1px solid #F2F2F2; display: flex; flex-direction: row; justify-content: center; align-items: center;">
          接收文件请求</view>
        <!-- 内容 -->
        <view style="padding: 8px;">
          <view>发送设备:{{sendFileInfo.deviceName}}</view>
          <view>文件名:{{sendFileInfo.fileName}}</view>
          <view>文件大小:{{getFormatFileSize(sendFileInfo.fileSize)}}</view>
        </view>
        <!-- 按钮 -->
        <view
          style="display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 8px 8px 16px 8px;">
          <button type="primary" size="mini" @click="confirmFileReceive(false)">拒绝</button>
          <button type="primary" size="mini" @click="confirmFileReceive(true)">接受</button>
        </view>
      </view>
    </uni-popup>

    <!-- 文件发送或接收进度弹窗 -->
    <uni-popup ref="refFileProgressPop" background-color="#fff" border-radius="8px" :isMaskClick="false">
      <view style="width: 600rpx;">
        <!-- 标题 -->
        <view
          style="height: 40px; border-bottom: 1px solid #F2F2F2; display: flex; flex-direction: row; justify-content: center; align-items: center; margin-bottom: 16px;">
          {{filePop.title}}
        </view>
        <!-- 进度条 -->
        <view style="padding: 8px;">
          <view style="border-radius: 10px; overflow: hidden;">
            <progress :percent="filePop.percent" :stroke-width="20"></progress>
          </view>
        </view>
        <!-- 进度信息展示 -->
        <view
          style="display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 8px;">
          <view style="font-size: 14px;">{{getFormatFileSize(filePop.size)}} / {{getFormatFileSize(filePop.totalSize)}}
          </view>
          <view style="font-size: 14px;">{{filePop.percent}}%</view>
        </view>
      </view>
    </uni-popup>
  </view>
</template>

<script>
  // const module = uni.requireNativePlugin("leven-bluetooth-ToothModule");
  import * as module from "@/uni_modules/leven-uts-bluetooth"
  import {
    formatFileSize
  } from "@/utils/index.js"
  export default {
    data() {
      return {
        //设备列表
        devices: [],
        //日志
        logs: [],
        //连接或配对的设备信息
        device: {},
        //当前连接的设备编码
        connectDeviceAddress: "",
        //收到文件发送请求数据
        sendFileInfo: {},
        //发送或接收文件
        filePop: {
          //标题
          title: "文件接收中...",
          //进度
          percent: 30.25,
          //已接收/发送的大小
          size: 17924222,
          //文件总大小
          totalSize: 36526417
        },
        //文件弹窗是否展示
        filePopIsOpen: false
      }
    },
    onUnload() {
      module.release({
        complete: (res) => {
          console.log("释放资源" + JSON.stringify(res))
        }
      })
    },
    mounted() {
      this.$nextTick(() => {
        this.requestPermissions();

      })
    },
    methods: {
      //显示消息提示
      showMessage(res) {
        if (res.code != 0) {
          uni.showToast({
            title: res.message,
            icon: "none"
          })
        }
      },
      //跳转到蓝牙设置界面
      openBluetoothSettings() {
        module.openBluetoothSettings({
          success: (res) => {
            console.log(res)
          }
        })
      },
      getFormatFileSize(size) {
        return formatFileSize(size)
      },
      //拒绝或接受文件接收
      confirmFileReceive(accept) {
        module.confirmFileReceive({
          params: {
            accept: accept
          },
          complete: (res) => {
            this.$refs.refFileRequestPop.close();
          }
        })
      },
      //选择文件并发送
      sendSelectFile() {
        // this.$refs.refFileProgressPop.open();
        module.sendSelectFile({
          complete: (res) => {
            this.showMessage(res)
          }
        })
      },
      //发送文本
      sendText() {
        module.sendText({
          params: {
            content: "这是发送的文本数据",
          },
          complete: (res) => {
            this.showMessage(res)
          }
        })
      },
      //发送十六进制数据
      sendHexString() {
        module.sendHexString({
          params: {
            content: "48 65 6C 6C 6F",
          },
          complete: (res) => {
            this.showMessage(res)
          }
        })
      },
      //发送字节数据
      sendBytes() {
        module.sendBytes({
          params: {
            content: "这是发送的字节数据",
          },
          complete: (res) => {
            this.showMessage(res)
          }
        })
      },
      //连接或配对设备
      connectOrPair() {
        if (this.device.bondState == 12) {
          //已配对开始连接设备
          module.connect({
            params: {
              address: this.device.address
            },
            complete: (res) => {
              this.showMessage(res)
            }
          })
        } else {
          //配对
          module.pairDevice({
            params: {
              address: this.device.address
            },
            complete: (res) => {
              this.showMessage(res)
            }
          })
        }
      },
      //取消配对
      unpairDevice() {
        module.unpairDevice({
          params: {
            address: this.device.address
          },
          complete: (res) => {
            this.showMessage(res)
          }
        })
      },
      //关闭弹窗
      closeDialog() {
        this.$refs.refConnectPop.close()
      },
      //打开配对或连接弹窗
      openDialog(data) {
        this.device = data;
        this.$refs.refConnectPop.open()
      },
      //停止搜索设备
      stopDiscovery() {
        module.stopDiscovery({})
      },
      //搜索设备
      startDiscovery() {
        this.devices = [];
        module.startDiscovery({
          complete: (res) => {
            console.log("搜索设备:" + JSON.stringify(res))
          }
        })
      },
      //获取已配对设备
      getPairedDevices() {
        this.devices = [];
        module.getPairedDevices({
          complete: (res) => {
            if (res.code == 0) {
              let data = res.data;
              this.devices = data.list;
            }
          }
        })
      },
      //关闭蓝牙
      disableBluetooth() {
        module.disableBluetooth({})
      },
      //开启蓝牙
      enableBluetooth() {
        module.enableBluetooth({
          complete: (res) => {
            console.log(res)
          }
        })
      },
      //初始化蓝牙
      init() {
        module.init({
          complete: (res) => {
            console.log(res)
            let data = res.data || {};
            let type = data.type;
            if (type == "onDeviceFound") {
              //发现新设备
              let device = data.device;
              this.devices.push(device);
              this.logs.push("发现新设备:" + (device.name || "未知名称") + ":" + device.address)
            } else if (type == "onDiscoveryStopped") {
              //停止扫描
              this.logs.push("停止扫描")
            } else if (type == "onPairingCompleted") {
              //配对或取消配对完成
              this.getPairedDevices();
              this.closeDialog();
            } else if (type == "onConnectionStateChanged") {
              //连接状态更新
              this.closeDialog();
              let device = data.device;
              let state = data.state;
              let stateStr = "";
              switch (state) {
                case 0:
                  stateStr = "已断开";
                  break
                case 1:
                  stateStr = "连接中";
                  break
                case 2:
                  stateStr = "连接监听中";
                  break
                case 3:
                  stateStr = "已连接";
                  break
                default:
                  stateStr = "未知";
              }
              this.logs.push("连接状态: " + stateStr)
              this.logs.push("设备: " + device.name)
              if (state == 3 && device) {
                this.connectDeviceAddress = device.address
              }
            } else if (type == "onDataReceived") {
              //收到数据
              let dataString = data.dataString || ""
              this.logs.push("接收到数据:" + dataString)
            } else if (type == "onFileReceiveRequest") {
              //收到发送文件的请求
              this.sendFileInfo = data;
              this.$refs.refFileRequestPop.open()
            } else if (type == "onFileSendProgress") {
              this.filePop.title = "文件发送中";
              if (!this.filePopIsOpen) {
                this.$refs.refFileProgressPop.open();
                this.filePopIsOpen = true;
              }
              this.filePop.percent = ((data.sentSize / data.totalSize) * 100).toFixed(2);
              this.filePop.size = data.sentSize;
              this.filePop.totalSize = data.totalSize;
            } else if (type == "onFileReceiveProgress") {
              this.filePop.title = "文件接收中";
              if (!this.filePopIsOpen) {
                this.$refs.refFileProgressPop.open();
                this.filePopIsOpen = true;
              }
              this.filePop.percent = ((data.receivedSize / data.totalSize) * 100).toFixed(2);
              this.filePop.size = data.receivedSize;
              this.filePop.totalSize = data.totalSize;
            } else if (type == "onFileReceiveComplete" || type == "onFileSendComplete") {
              this.$refs.refFileProgressPop.close();
              this.filePopIsOpen = false;
              if (type == "onFileReceiveComplete") {
                uni.showToast({
                  title: "文件接收完毕",
                  icon: "none"
                })
              } else {
                uni.showToast({
                  title: "文件发送完毕",
                  icon: "none"
                })
              }
            }
          }
        })
      },
      requestPermissions() {
        //申请权限
        module.requestPermissions({
          params: {
            permissions: [
              'android.permission.BLUETOOTH_CONNECT',
              'android.permission.BLUETOOTH_SCAN',
              'android.permission.ACCESS_FINE_LOCATION'
            ]
          },
          complete: (res) => {
            if (res.code == 0) {
              //初始化蓝牙
              this.init();
            }
          }
        })
      }
    }
  }
</script>

<style>
  .content {
    padding: 16px;
  }

  .scroll-Y {
    height: 200px;
  }
</style>
vue
<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex:1; padding: 8px;">
  <!-- #endif -->
    <!-- 按钮组 -->
    <view>
      <view style="flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="enableBluetooth">开启蓝牙</button>
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="disableBluetooth">关闭蓝牙</button>
      </view>
      <view style="flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1;" type="primary" size="mini" @click="getPairedDevices">获取已配对设备</button>
      </view>
      <view style="flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="startDiscovery">搜索设备</button>
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="stopDiscovery">停止搜索</button>
      </view>
      <view style="display: flex; flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="sendBytes">发送字节</button>
        <button style="flex: 2; margin-right: 8px;" type="primary" size="mini" @click="sendHexString">发送十六进制</button>
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="sendText">发送文本</button>
      </view>
      <view style="display: flex; flex-direction: row; margin-bottom: 8px;">
        <button style="flex: 1; margin-right: 8px;" type="primary" size="mini" @click="sendSelectFile">选择文件并发送</button>
        <button style="flex: 1;" type="primary" size="mini" @click="openBluetoothSettings">跳转到蓝牙设置界面</button>
      </view>
    </view>
    <!-- 设备列表 -->
    <view style="flex-direction: column; border: 1px solid #F2F2F2;">
      <view style="height: 40px; justify-content: center; background-color: #F2F2F2; padding: 0 8px;">
        <text style="font-size: 16px;">设备列表</text>
      </view>
      <scroll-view direction="vertical" class="scroll-Y" :show-scrollbar="false">
        <view v-for="(item, index) in devices" :key="index"
          style="flex-direction: row; justify-content: space-between; align-items: center; padding: 8px;">
          <view>
            <view><text class="text-style">{{item.name}}</text></view>
            <view><text class="text-style">{{item.address}}</text></view>
          </view>
          <view v-if="item.bondState == 12"><button type="primary" size="mini" @click="openDialog(item)">连接</button>
          </view>
          <view v-else-if="item.bondState == 10"><button type="primary" size="mini"
              @click="openDialog(item)">配对</button></view>
        </view>
      </scroll-view>
    </view>
    <!-- 日志 -->
    <view style="flex-direction: column; border: 1px solid #F2F2F2;">
      <view style="height: 40px; justify-content: center; background-color: #F2F2F2; padding: 0 8px;">
        <text style="font-size: 16px;">日志</text>
      </view>
      <scroll-view class="scroll-Y" direction="vertical" :show-scrollbar="false">
        <view v-for="(item, index) in logs" :key="index" style="padding: 8px;">
          <text class="text-style">{{item}}</text>
        </view>
      </scroll-view>
    </view>
  <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->

  <!-- 配对或连接弹出层 -->
  <rice-popup v-model:show="connectPopShow" position="center" :closeable="false" bgColor="#FFFFFF" radius="8px">
    <view class="popup-center">
      <!-- 标题 -->
      <view
        style="height: 40px; border-bottom: 1px solid #F2F2F2; flex-direction: row; justify-content: center; align-items: center;">
        <text class="text-title-style">{{device.name ?? ""}}</text>
      </view>
      <view style="padding: 8px;">
        <view><text class="text-style">地址:{{device.address}}</text></view>
        <view><text class="text-style">状态:{{device.bondState == 12 ? "已配对" : "未配对"}}</text></view>
      </view>
      <!-- 按钮 -->
      <view
        style="flex-direction: row; justify-content: space-between; align-items: center; padding: 8px 8px 16px 8px;">
        <view style="flex: 1;">
          <button type="primary" size="mini" v-if="device.bondState == 12" @click="unpairDevice">取消配对</button>
        </view>
        <view style="flex: 1; display: flex; flex-direction: row; justify-content: space-between; align-items: center;">
          <button type="primary" size="mini" @click="connectPopShow = false">取消</button>
          <button type="primary" size="mini" @click="connectOrPair">{{device.bondState == 12 ? "连接" : "配对"}}</button>
        </view>
      </view>
    </view>
  </rice-popup>

  <!-- 接收文件请求弹窗 -->
  <rice-popup v-model:show="fileRequestPopShow" position="center" :closeable="false" bgColor="#FFFFFF" radius="8px"
    :closeOnClickOverlay="false">
    <view class="popup-center">
      <!-- 标题 -->
      <view
        style="height: 40px; border-bottom: 1px solid #F2F2F2; flex-direction: row; justify-content: center; align-items: center;">
        <text class="text-title-style">接收文件请求</text>
      </view>
      <!-- 内容 -->
      <view style="padding: 8px;">
        <view><text class="text-style">发送设备:{{sendFileInfo.deviceName}}</text></view>
        <view><text class="text-style">文件名:{{sendFileInfo.fileName}}</text></view>
        <view><text class="text-style">文件大小:{{getFormatFileSize(sendFileInfo.fileSize)}}</text></view>
      </view>
      <!-- 按钮 -->
      <view
        style="flex-direction: row; justify-content: space-between; align-items: center; padding: 8px 8px 16px 8px;">
        <button type="primary" size="mini" @click="confirmFileReceive(false)">拒绝</button>
        <button type="primary" size="mini" @click="confirmFileReceive(true)">接受</button>
      </view>
    </view>
  </rice-popup>

  <!-- 文件发送或接收进度弹窗 -->
  <rice-popup v-model:show="fileProgressPopShow" position="center" :closeable="false" bgColor="#FFFFFF" radius="8px"
    :closeOnClickOverlay="false">
    <view class="popup-center">
      <!-- 标题 -->
      <view
        style="height: 40px; border-bottom: 1px solid #F2F2F2; flex-direction: row; justify-content: center; align-items: center; margin-bottom: 16px;">
        {{filePop.title}}
      </view>
      <!-- 进度条 -->
      <view style="padding: 8px;">
        <rice-progress :percentage="filePop.percent" :show-text="false" stroke-width="16px"></rice-progress>
      </view>
      <!-- 进度信息展示 -->
      <view style="flex-direction: row; justify-content: space-between; align-items: center; padding: 8px;">
        <view>
          <text class="text-style">{{getFormatFileSize(filePop.size)}} / {{getFormatFileSize(filePop.totalSize)}}</text>
        </view>
        <view><text class="text-style">{{filePop.percent}}%</text></view>
      </view>
    </view>
  </rice-popup>
</template>

<script setup>
  import * as module from "@/uni_modules/leven-uts-bluetooth"
  import { formatFileSize } from "@/utils/bluetooth"
  import { LevenResult } from "@/uni_modules/leven-uts-bluetooth"
  //设备列表属性
  type deviceItem = {
    //设备名称
    name ?: string,
    //设备地址
    address ?: string,
    //设备配对状态
    bondState ?: number
  }
  //文件属性
  type fileItem = {
    //设备名称
    deviceName ?: string,
    //文件名称
    fileName ?: string,
    //文件大小
    fileSize ?: number
  };
  //发送或接收文件属性
  type filePopItem = {
    //标题
    title : string,
    //进度
    percent : number,
    //已接收/发送的大小
    size : number,
    //文件总大小
    totalSize : number
  }
  //配对或连接弹出层
  const connectPopShow = ref(false)
  //接收文件请求弹窗
  const fileRequestPopShow = ref(false)
  //文件发送或接收进度弹窗
  const fileProgressPopShow = ref(false)
  //设备列表
  const devices = ref<deviceItem[]>([])
  //连接或配对的设备信息
  const device = ref<deviceItem>({})
  //日志列表
  const logs = ref<string[]>([])
  //收到文件发送请求数据
  const sendFileInfo = ref<fileItem>({})
  //发送或接收文件
  const filePop = ref<filePopItem>({
    title: "文件接收中...",
    percent: 0,
    size: 0,
    totalSize: 0
  })
  //当前连接的设备编码
  const connectDeviceAddress = ref("")

  //转换文件大小
  function getFormatFileSize(size : number | null) {
    return formatFileSize(size)
  }

  //显示消息提示
  function showMessage(res : LevenResult) {
    if (res.code != 0) {
      uni.showToast({
        title: res.message,
        icon: "none"
      })
    }
  }

  //跳转到蓝牙设置界面
  function openBluetoothSettings() {
    module.openBluetoothSettings({
      success: (res) => {
        console.log(res)
      }
    })
  }

  //拒绝或接受文件接收
  function confirmFileReceive(accept : boolean) {
    module.confirmFileReceive({
      params: {
        accept: accept
      },
      complete: (res) => {
        fileRequestPopShow.value = false
        showMessage(res)
      }
    })
  }

  //选择文件并发送
  function sendSelectFile() {
    module.sendSelectFile({
      complete: (res) => {
        showMessage(res)
      }
    })
  }

  //发送文本
  function sendText() {
    module.sendText({
      params: {
        content: "这是发送的文本数据",
      },
      complete: (res) => {
        showMessage(res)
      }
    })
  }

  //发送十六进制数据
  function sendHexString() {
    module.sendHexString({
      params: {
        content: "48 65 6C 6C 6F",
      },
      complete: (res) => {
        showMessage(res)
      }
    })
  }

  //发送字节数据
  function sendBytes() {
    module.sendBytes({
      params: {
        content: "这是发送的字节数据",
      },
      complete: (res) => {
        showMessage(res)
      }
    })
  }

  //连接或配对设备
  function connectOrPair() {
    if (device.value?.bondState == 12) {
      //已配对开始连接设备
      module.connect({
        params: {
          address: device.value?.address
        },
        complete: (res) => {
          showMessage(res)
        }
      })
    } else {
      //配对
      module.pairDevice({
        params: {
          address: device.value?.address
        },
        complete: (res) => {
          showMessage(res)
        }
      })
    }
  }

  //取消配对
  function unpairDevice() {
    module.unpairDevice({
      params: {
        address: device.value?.address
      },
      complete: (res) => {
        showMessage(res)
      }
    })
  }

  //打开连接或配对弹窗
  function openDialog(data : deviceItem) {
    device.value = data
    connectPopShow.value = true
  }

  //停止搜索
  function stopDiscovery() {
    module.stopDiscovery({
      complete: (res) => {
        console.log("停止搜索:" + JSON.stringify(res))
      }
    })
  }

  //搜索设备
  function startDiscovery() {
    devices.value = [];
    module.startDiscovery({
      complete: (res) => {
        console.log("搜索设备:" + JSON.stringify(res))
      }
    })
  }

  //获取已配对设备
  function getPairedDevices() {
    devices.value = [];
    module.getPairedDevices({
      complete: (res) => {
        if (res.code == 0) {
          let data = res.data;
          let list : UTSJSONObject[] = data.getArray("list") as UTSJSONObject[]
          if (list.length > 0) {
            list.forEach((item : UTSJSONObject | null) => {
              let deviceData : deviceItem = {
                name: item?.getString("name"),
                address: item?.getString("address"),
                bondState: item?.getNumber("bondState")
              }
              devices.value.push(deviceData)
            })
          }
        }
      }
    })
  }

  //关闭蓝牙
  function disableBluetooth() {
    module.disableBluetooth({
      complete: (res) => {
        console.log(res)
      }
    })
  }

  //开启蓝牙
  function enableBluetooth() {
    module.enableBluetooth({
      complete: (res) => {
        console.log(res)
      }
    })
  }

  //初始化蓝牙
  function init() {
    module.init({
      complete: (res) => {
        console.log(res)
        let data = res.data;
        let type = data.getString("type");
        if (type == "onInit") {
          if (res.code == 0) {
            logs.value.push("初始化成功")
          } else {
            logs.value.push("初始化失败")
          }
        } else if (type == "onDeviceFound") {
          //发现新设备
          let device = data.getJSON("device");
          devices.value.push({
            name: device?.getString("name"),
            address: device?.getString("address"),
            bondState: device?.getNumber("bondState")
          } as deviceItem)
          logs.value.push("发现新设备:" + (device?.getString("name") ?? "未知名称") + ":" + device?.getString("address"))
        } else if (type == "onDiscoveryStopped") {
          //停止扫描
          logs.value.push("停止扫描")
        } else if (type == "onPairingCompleted") {
          //配对或取消配对完成
          getPairedDevices()
          connectPopShow.value = false;
        } else if (type == "onConnectionStateChanged") {
          //连接状态更新
          connectPopShow.value = false;
          let deviceInfo = data.getJSON("device");
          let state = data.getNumber("state");
          let stateStr = "";
          switch (state) {
            case 0:
              stateStr = "已断开";
              break
            case 1:
              stateStr = "连接中";
              break
            case 2:
              stateStr = "连接监听中";
              break
            case 3:
              stateStr = "已连接";
              break
            default:
              stateStr = "未知";
          }
          logs.value.push("连接状态: " + stateStr)
          logs.value.push("设备: " + deviceInfo?.getString("name"))
          if (state == 3 && deviceInfo != null) {
            connectDeviceAddress.value = deviceInfo?.getString("address") ?? ""
          }
        } else if (type == "onDataReceived") {
          //收到数据
          let dataString = data.getString("dataString")
          logs.value.push("接收到数据:" + dataString)
        } else if (type == "onFileReceiveRequest") {
          //收到发送文件的请求
          sendFileInfo.value = {
            fileName: data.getString("fileName"),
            deviceName: data.getString("deviceName"),
            fileSize: data.getNumber("fileSize")
          } as fileItem;
          fileRequestPopShow.value = true
        } else if (type == "onFileSendProgress") {
          filePop.value.title = "文件发送中";
          if (!fileProgressPopShow.value) {
            fileProgressPopShow.value = true
          }
          let sentSize : number = data.getNumber("sentSize")!
          let totalSize : number = data.getNumber("totalSize")!
          filePop.value.percent = parseFloat(((sentSize / totalSize) * 100).toFixed(2));
          filePop.value.size = sentSize;
          filePop.value.totalSize = totalSize;
        } else if (type == "onFileReceiveProgress") {
          filePop.value.title = "文件接收中";
          if (!fileProgressPopShow.value) {
            fileProgressPopShow.value = true
          }
          let receivedSize : number = data.getNumber("receivedSize")!;
          let totalSize : number = data.getNumber("totalSize")!
          filePop.value.percent = parseFloat(((receivedSize / totalSize) * 100).toFixed(2));
          filePop.value.size = receivedSize;
          filePop.value.totalSize = totalSize;
        } else if (type == "onFileReceiveComplete" || type == "onFileSendComplete") {
          fileProgressPopShow.value = false;
          if (type == "onFileReceiveComplete") {
            uni.showToast({
              title: "文件接收完毕",
              icon: "none"
            })
          } else {
            uni.showToast({
              title: "文件发送完毕",
              icon: "none"
            })
          }
        }
      }
    })
  }

  //申请权限
  function requestPermissions() {
    module.requestPermissions({
      params: {
        permissions: [
          'android.permission.BLUETOOTH_CONNECT',
          'android.permission.BLUETOOTH_SCAN',
          'android.permission.ACCESS_FINE_LOCATION'
        ]
      },
      complete: (res) => {
        if (res.code == 0) {
          //初始化蓝牙
          init();
        }
      }
    })
  }

  //页面加载
  onMounted(() => {
    //申请权限
    requestPermissions();
  })

  //页面卸载
  onUnload(() => {
    module.release({
      complete: (res) => {
        console.log("释放资源" + JSON.stringify(res))
      }
    })
  })
</script>

<style>
  .scroll-Y {
    height: 200px;
  }

  .text-title-style {
    font-size: 16px;
  }

  .text-style {
    font-size: 12px;
  }

  .popup-center {
    width: 600rpx;
  }
</style>