rntpc_react-native-bluetooth-printer:基于 React Native 的蓝牙打印库项目

蓝牙打印功能

分支1Tags1
当前项目代码仓暂无内容

文档模板:v0.4.1

react-native-bluetooth-printer

Supported platforms License

本项目基于 react-native-blue-printer@0.0.1 开发。

请到三方库的 Releases 发布地址查看配套的版本信息:

三方库名称 三方库版本 发布信息 支持RN版本 Autolink 编译API版本 社区基线版本 npm地址
@react-native-ohos/react-native-bluetooth-printer ~ 0.0.2 Gitcode Releases 0.72/0.77/0.82 API12+ 0.0.1 Npm Address

简介

bluetooth-printer 是 React Native 的蓝牙打印库。
它提供简洁的 API,支持手机与蓝牙打印设备的快速连接与数据通信,能够高效、稳定地完成打印任务,帮助开发者降低蓝牙集成的复杂度。

下载安装

进入到工程目录并输入以下命令:

npm

npm install @react-native-ohos/react-native-bluetooth-printer

纱线

yarn add @react-native-ohos/react-native-bluetooth-printer

链接

是否支持自动链接 RN框架版本
~ 0.0.2 0.72/0.77/0.82

使用自动链接的工程需要根据该文档配置,自动链接框架指导文档:https://gitcode.com/openharmony-sig/ohos_react_native/blob/master/docs/zh-cn/Autolinking.md

首先需要使用 DevEco Studio 打开项目里的 HarmonyOS 工程 harmony

1. 覆盖 RN SDK

为了让工程依赖同一个版本的 RN SDK,需要在工程根目录的 oh-package.json5 添加 overrides 字段,指向工程需要使用的 RN SDK 版本。替换的版本既可以是一个具体的版本号,也可以是一个模糊版本,还可以是本地存在的 HAR 包或源码目录。

关于该字段的作用请阅读官方说明

{
  "overrides": {
    "@rnoh/react-native-openharmony": "^0.72.38" // ohpm 在线版本
    // "@rnoh/react-native-openharmony" : "./react_native_openharmony.har" // 指向本地 har 包的路径
    // "@rnoh/react-native-openharmony" : "./react_native_openharmony" // 指向源码路径
  }
}

2. 引入原生端代码

目前有两种方法:

  • 通过 har 包引入;
  • 直接链接源码。

方法一:通过 har 包引入(推荐)

har 包位于三方库安装路径的 `harmony` 文件夹下。

打开 entry/oh-package.json5,添加以下依赖

"dependencies": {
    "@rnoh/react-native-openharmony": "file:../react_native_openharmony",
    "@react-native-ohos/react-native-bluetooth-printer": "file:../../node_modules/@react-native-ohos/react-native-bluetooth-printer/harmony/bluetooth_print.har"
}

点击右上角的 sync 按钮

或者在终端执行:

cd entry
ohpm install

方法二:直接链接源码

如需使用直接链接源码,请参考[直接链接源码说明](/zh-cn/link-source-code.md)

3. 配置 CMakeLists 和引入 BluetoothPrintPackage

打开 entry/src/main/cpp/CMakeLists.txt,添加:

project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")

# RNOH_BEGIN: manual_package_linking_1
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-bluetooth-printer/src/main/cpp" ./bluetooth_print)
# RNOH_END: manual_package_linking_1

# RNOH_BEGIN: manual_package_linking_2
+ target_link_libraries(rnoh_app PUBLIC rnoh_bluetooth_print)
# RNOH_END: manual_package_linking_2

打开 entry/src/main/cpp/PackageProvider.cpp,添加:

#include "RNOH/PackageProvider.h"
#include "SamplePackage.h"
+ #include "BluetoothPrintPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
      std::make_shared<SamplePackage>(ctx),
+     std::make_shared<BluetoothPrintPackage>(ctx)
    };
}

4. 在 ArkTs 侧引入 BluetoothPrintPackage

打开 entry/src/main/ets/RNPackagesFactory.ets,添加:

import type {RNPackageContext, RNPackage} from '@rnoh/react-native-openharmony/ts';
+ import { BluetoothPrintPackage } from '@react-native-ohos/react-native-bluetooth-printer';

export function createRNPackages(ctx: RNPackageContext): RNOHPackage[] {
  return [
+   new BluetoothPrintPackage(ctx),
  ];
}

运行

点击右上角的 sync 按钮

或者在命令行终端执行:

cd entry
ohpm install

然后编译、运行即可。

约束与限制

兼容性

本文档内容基于以下版本验证通过:

  1. RNOH: 0.72.96; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.0.858; ROM: 6.0.0.112;
  2. RNOH: 0.72.33; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.0.858; ROM: 6.0.0.112;
  3. RNOH: 0.77.18; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.0.858; ROM: 6.0.0.112;
  4. RNOH: 0.82.1; SDK: HarmonyOS 6.0.1 Release SDK; IDE: DevEco Studio 6.0.1 Release; ROM:6.0.0.120 SP7;

使用示例

下面的代码展示了这个库的基本使用场景:

使用时 import 的库名不变。

 import React, { useMemo, useRef, useState } from 'react';
 import {
    Alert,
    Button,
    SafeAreaView,
    ScrollView,
    StyleSheet,
    Text,
    TouchableOpacity,
    View,
} from 'react-native';
 import BluetoothPrint, {
    type BluetoothDevice,
    type IDevices,
} from '@react-native-ohos/react-native-bluetooth-printer'

 function App() {
    const [isBluetoothOpen, setIsBluetoothOpen] = useState<boolean>(false);
    const [isScanning, setIsScanning] = useState<boolean>(false);
    const [devices, setDevices] = useState<BluetoothDevice[]>([]);
    const [selectedDeviceId, setSelectedDeviceId] = useState<string>('');
    const [connectedDevice, setConnectedDevice] = useState<IDevices | null>(null);
    const [connecting, setConnecting] = useState<boolean>(false);
    const [printing, setPrinting] = useState<boolean>(false);
    const connectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

    const currentClientNumber = useMemo(() => connectedDevice?.socketId ?? -1, [connectedDevice]);

    const syncBluetoothState = () => {
        const open = BluetoothPrint.isOpenBluetooth();
        setIsBluetoothOpen(Boolean(open));
        if (!open) {
            Alert.alert('提示', '蓝牙未打开,已尝试唤起系统蓝牙。请手动确认蓝牙已开启后再试。');
        }
    };

    const requestPermission = () => {
        BluetoothPrint.requestBluetoothPermission();
        Alert.alert('提示', '权限弹窗已触发,请在系统中授权蓝牙权限。');
    };

    const startScan = () => {
        setDevices([]);
        setSelectedDeviceId('');
        setIsScanning(true);
        BluetoothPrint.startScanBluetoothDevices((list) => {
            setDevices(list);
        });
    };

    const stopScan = () => {
        BluetoothPrint.stopScanBluetoothDevices();
        setIsScanning(false);
    };

    const connectSelectedDevice = () => {
        if (!selectedDeviceId) {
            Alert.alert('提示', '请先选择一个设备');
            return;
        }
        setConnecting(true);
        if (isScanning) {
            // Stop scanning before connecting to reduce BLE connection conflicts.
            stopScan();
        }

        if (connectTimeoutRef.current) {
            clearTimeout(connectTimeoutRef.current);
            connectTimeoutRef.current = null;
        }

        connectTimeoutRef.current = setTimeout(() => {
            setConnecting(false);
            Alert.alert('连接超时', '未收到连接成功回调,请确认设备可配对、蓝牙权限已授权。');
            connectTimeoutRef.current = null;
        }, 200000);

        try {
            BluetoothPrint.connect(selectedDeviceId, (device) => {
                if (connectTimeoutRef.current) {
                    clearTimeout(connectTimeoutRef.current);
                    connectTimeoutRef.current = null;
                }
                setConnectedDevice(device);
                setConnecting(false);
                Alert.alert('连接成功', `${device.deviceName} (${device.deviceId})`);
            });
        } catch (e) {
            if (connectTimeoutRef.current) {
                clearTimeout(connectTimeoutRef.current);
                connectTimeoutRef.current = null;
            }
            setConnecting(false);
            Alert.alert('连接失败', String(e));
        }
    };

    const printText = async () => {
        if (!selectedDeviceId || currentClientNumber === -1) {
            Alert.alert('提示', '请先完成设备连接');
            return;
        }
        try {
            setPrinting(true);
            await BluetoothPrint.printForFile(currentClientNumber, selectedDeviceId);
            Alert.alert('提示', '已触发文本打印');
        } catch (e) {
            Alert.alert('错误', `文本打印失败: ${String(e)}`);
        } finally {
            setPrinting(false);
        }
    };

    const printImage = async () => {
        if (!selectedDeviceId || currentClientNumber === -1) {
            Alert.alert('提示', '请先完成设备连接');
            return;
        }
        try {
            setPrinting(true);
            await BluetoothPrint.printForImage(currentClientNumber, selectedDeviceId);
            Alert.alert('提示', '已触发图片打印');
        } catch (e) {
            Alert.alert('错误', `图片打印失败: ${String(e)}`);
        } finally {
            setPrinting(false);
        }
    };

    return (
        <SafeAreaView style={styles.container}>
        <ScrollView contentContainerStyle={styles.content}>
        <Text style={styles.title}>RNBluetoothPrint Harmony Demo</Text>
        <Text style={styles.status}>蓝牙状态:{isBluetoothOpen ? '已开启' : '未开启'}</Text>
        <Text style={styles.status}>
        当前连接:{connectedDevice ? `${connectedDevice.deviceName} (${connectedDevice.socketId})` : '未连接'}
    </Text>
        <Text style={styles.status}>扫描状态:{isScanning ? '扫描中' : '未扫描'}</Text>

        <View style={styles.row}>
        <Button title="检查蓝牙状态" onPress={syncBluetoothState} />
        </View>
        <View style={styles.row}>
        <Button title="申请蓝牙权限" onPress={requestPermission} />
        </View>
        <View style={styles.row}>
        <Button title={isScanning ? '停止扫描' : '开始扫描'} onPress={isScanning ? stopScan : startScan} />
        </View>
        <View style={styles.row}>
        <Button
    title={connecting ? '连接中...' : '连接选中设备'}
    onPress={connectSelectedDevice}
    disabled={connecting}
    />
        </View>
        <View style={styles.row}>
        <Button title={printing ? '打印中...' : '打印文本'} onPress={printText} disabled={printing} />
        </View>
        <View style={styles.row}>
        <Button title={printing ? '打印中...' : '打印图片'} onPress={printImage} disabled={printing} />
        </View>

        <Text style={styles.subtitle}>扫描到的设备(点击选择)</Text>
        {devices.length === 0 ? (
            <Text style={styles.empty}>暂无设备,请先开始扫描</Text>
            ) : (
            devices.map((item) => {
            const selected = selectedDeviceId === item.deviceId;
            const isConnected = connectedDevice?.deviceId === item.deviceId;
            return (
            <TouchableOpacity
            key={item.deviceId}
            style={[styles.card, selected && styles.cardSelected]}
            onPress={() => setSelectedDeviceId(item.deviceId)}
                >
                <Text style={styles.deviceName}>{item.deviceName || '未知设备'}</Text>
            <Text style={styles.deviceId}>{item.deviceId}</Text>
            <Text style={styles.deviceRssi}>RSSI: {item.rssi ?? '--'}</Text>
        {isConnected ? <Text style={styles.connectedTag}>状态:已连接</Text> : null}
        </TouchableOpacity>
    );
})
 )}
 </ScrollView>
     </SafeAreaView>
 );
 }

 const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#f2f5f7',
    },
    content: {
        padding: 16,
        paddingBottom: 32,
    },
    title: {
        fontSize: 20,
        fontWeight: '700',
        color: '#1a1a1a',
        marginBottom: 8,
    },
    subtitle: {
        marginTop: 16,
        marginBottom: 8,
        fontSize: 16,
        fontWeight: '600',
        color: '#1a1a1a',
    },
    status: {
        fontSize: 14,
        color: '#333333',
        marginTop: 4,
    },
    row: {
        marginTop: 10,
    },
    empty: {
        marginTop: 8,
        color: '#666666',
    },
    card: {
        backgroundColor: '#ffffff',
        borderRadius: 8,
        padding: 12,
        marginTop: 10,
        borderWidth: 1,
        borderColor: '#d9d9d9',
    },
    cardSelected: {
        borderColor: '#2f80ed',
        backgroundColor: '#edf4ff',
    },
    deviceName: {
        fontSize: 15,
        fontWeight: '600',
        color: '#111111',
    },
    deviceId: {
        fontSize: 12,
        color: '#666666',
        marginTop: 4,
    },
    deviceRssi: {
        fontSize: 12,
        color: '#666666',
        marginTop: 4,
    },
    connectedTag: {
        marginTop: 6,
        color: '#0b8f3a',
        fontSize: 12,
        fontWeight: '600',
    },
});

 export const examples = [
    {
        title: 'react-native-bluetooth-printer',
        render: function (): any {
        return <App />;
    },
 },
 ];

接口说明

API

“Platform”列表示该属性在原三方库上支持的平台。

“OpenHarmony Support”列为 yes 表示 OpenHarmony平台支持该属性;no 则表示不支持;partially 表示部分支持。使用方法跨平台一致,效果对标 iOS 或 Android 的效果。

名称 类型 参数类型 返回值 必填 平台 OpenHarmony平台支持 描述
connect function mac: string, cb: Callback void No Android Yes 初始化蓝牙服务,连接蓝牙打印机
check function cb: Callback void No Android Yes 检查蓝牙打印机连接可用性
print function imgurl: string, pixelWidth: number, cb: Callback void No Android Yes 打印指定本地路径(imgurl)的图片
print_p function imgurl: string, pixelWidth: number void No Android Yes 异步打印指定本地路径(imgurl)的图片
check_p function / void No Android Yes 异步检查蓝牙打印机连接可用性
list_p function / void No Android Yes 异步获取已配对的蓝牙设备列表
connect_p function mac: string void No Android Yes 异步连接蓝牙打印机

遗留问题

其他

目录结构

/react-native-bluetooth-printer  # 项目根目录
├── harmony              # 鸿蒙适配代码
│    └─ bluetooth_print.har          # har包
│    └─ bluetooth_print                  # 鸿蒙适配核心代码
│          └─ Index.ets    # 鸿蒙适配代码入口    
│          └─ src/main/ets  
│              └─ BluetoothPrintPackage.ts  # bluetooth-printer包封装   
├── src                  # RN代码
│    └─ index.ts  # 入口文件 
├── README.md           # 安装使用方法                     

贡献代码

使用过程中发现任何问题都可以提交 Issue,当然,也非常欢迎提交 PR

开源协议

本项目基于 MIT 协议,敬请自由享受并参与开源。