huangxw 2 주 전
부모
커밋
e493ba3686

+ 55 - 0
src/components/ut-steps-title/ut-steps-title.vue

@@ -0,0 +1,55 @@
+<template>
+    <view class="ut-steps-title d-flex j-c a-c">
+        <view v-for="(step, index) in sortedSteps" :key="step.value" class="ut-steps-item d-flex a-c f-s-26" :class="{
+            'is-completed': step.status === 'completed',
+            'is-in-progress': step.status === 'in-progress',
+            'is-not-started': step.status === 'not-started',
+        }">
+            <!-- 步骤序号/图标 -->
+            <up-icon v-if="step.status === 'completed'" name="checkmark" size="26rpx" color="#333"></up-icon>
+            <!-- 步骤标题 -->
+            <text class="ut-steps-text" :class="{
+                'text-completed': step.status === 'completed',
+                'text-in-progress': step.status === 'in-progress',
+                'text-not-started': step.status === 'not-started',
+            }">{{ step.title }}</text>
+            <up-icon v-if="(index < sortedSteps.length - 1)" class="pd2-0-12" name="arrow-right" size="26rpx" :color="step.status === 'completed' ? '#333' : '#999'"></up-icon>
+        </view>
+    </view>
+</template>
+
+<script setup lang="ts">
+interface Step {
+    title: string;
+    value: string;
+    status: "not-started" | "in-progress" | "completed";
+    sort: number;
+}
+
+const props = defineProps<{
+    stepsObject: Record<string, Step>;
+}>();
+
+// 获取排序后的步骤数组
+const sortedSteps = computed(() => {
+    return Object.values(props.stepsObject).sort((a, b) => a.sort - b.sort);
+});
+</script>
+
+<style lang="scss" scoped>
+.ut-steps-text {
+    color: #999;
+
+    &.text-completed {
+        color: #333;
+        font-weight: 500;
+    }
+    &.text-in-progress {
+        color: #333;
+        font-weight: 500;
+    }
+    &.text-not-started {
+        color: #999;
+    }
+}
+</style>

+ 3 - 0
src/manifest.json

@@ -67,6 +67,9 @@
         "permission": {
             "scope.userLocation": {
                 "desc": "您的位置信息将用于基地位置选择"
+            },
+            "scope.bluetooth": {
+                "desc": "蓝牙权限将用于连接打印机"
             }
         },
         "requiredPrivateInfos": ["chooseAddress", "chooseLocation", "getLocation"]

+ 12 - 7
src/pages.json

@@ -445,13 +445,6 @@
                         "navigationBarTitleText": "包装任务详情"
                     }
                 },
-                // 打印追溯码
-                {
-                    "path": "print/index",
-                    "style": {
-                        "navigationBarTitleText": "打印追溯码"
-                    }
-                },
                 // 选择任务包装的对象
                 {
                     "path": "select-object/index",
@@ -461,6 +454,18 @@
                 }
             ]
         },
+        // 打印
+        {
+            "root": "plant/print",
+            "pages": [
+                {
+                    "path": "print-steps/index",
+                    "style": {
+                        "navigationBarTitleText": "打印步骤"
+                    }
+                }
+            ]
+        },
         // 往来单位管理
         {
             "root": "plant/contact-unit",

+ 1 - 1
src/plant/packaging/create/index.vue

@@ -167,7 +167,7 @@ const form = ref<PackTaskForm>({
     expireDate: '',
     expireDateUnit: '',
     refType: '',
-    packCodeType: '1',
+    packCodeType: '3',
 });
 
 const rules = reactive<Record<string, any>>({

+ 1 - 1
src/plant/packaging/list/index.vue

@@ -46,7 +46,7 @@ import FreshGoodsBottom from './model/fresh-goods-bottom.vue';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { pt_pack_code_type } = toRefs<any>(proxy?.useDict('pt_pack_code_type'));
 const list = ref<any[]>();
-const form = ref({ keyword: '', restFlag: '1', processType: '', storageType: '5' });
+const form = ref({ keyword: '' });
 const paging = ref();
 const showAddDialog = ref(false);
 const tabs = ref([

+ 0 - 3
src/plant/packaging/print/index.vue

@@ -1,3 +0,0 @@
-<template>
-    <view>xx</view>
-</template>

+ 50 - 0
src/plant/print/print-steps/index.vue

@@ -0,0 +1,50 @@
+<template>
+    <z-paging ref="paging" bgColor="#fff" safe-area-inset-bottom scroll-with-animation>
+        <template #top>
+            <ut-navbar :title="stepsObject[currentStep].title" :fixed="false" border :breadcrumb="false"></ut-navbar>
+            <view class=" pd-24">
+                <ut-steps-title :stepsObject="stepsObject"></ut-steps-title>
+            </view>
+            <view class="pd-5 bg-#f7f7f7"></view>
+        </template>
+        <view>
+            <template v-show="currentStep === 'connect'">
+                <!-- 业务组件链接打印机 -->
+                <connect-printer></connect-printer>
+            </template>
+        </view>
+    </z-paging>
+</template>
+<script setup lang="ts">
+import ConnectPrinter from './models/connect-printer.vue';
+const stepsObject = reactive<any>({
+    check: {
+        title: '核对信息',
+        value: 'check',
+        status: 'completed', // 为开始、进行中、已完成 三种状态分别值为 'not-started'、'in-progress'、'completed'
+        sort: 0
+    },
+    confirm: {
+        title: '确认数量',
+        value: 'confirm',
+        status: 'completed', // 为开始、进行中、已完成 三种状态分别值为 'not-started'、'in-progress'、'completed'
+        sort: 1
+    },
+    connect: {
+        title: '连接打印机',
+        value: 'connect',
+        status: 'in-progress', // 为开始、进行中、已完成 三种状态分别值为 'not-started'、'in-progress'、'completed'
+        sort: 2
+    },
+    print: {
+        title: '打印',
+        value: 'print',
+        status: 'not-started', // 为开始、进行中、已完成 三种状态分别值为 'not-started'、'in-progress'、'completed'
+        sort: 3
+    }
+})
+const currentStep = ref('connect');
+const paging = ref(null);
+onLoad((options: any) => {});
+</script>
+<style lang="scss" scoped></style>

+ 523 - 0
src/plant/print/print-steps/models/connect-printer.vue

@@ -0,0 +1,523 @@
+<template>
+    <view class="connect-printer-page">
+        <!-- 提示信息图片 - 搜索中显示 gif,搜索完成显示 jpg -->
+        <view class="info-images">
+            <image v-if="isSearching" class="w-750 h-311" src="@/static/images/print/print_dt_ssly.gif" mode="widthFix" />
+            <image v-else class="w-750 h-311" src="@/static/images/print/print_jt_ssly.jpg" mode="widthFix" />
+        </view>
+
+        <!-- 操作按钮区域 -->
+        <view class="pd-24 action-buttons">
+            <up-button 
+                @click="handleStartSearch" 
+                :loading="isSearching"
+                class="mb-30" 
+                type="primary"
+                :disabled="isConnected"
+            >
+                {{ isSearching ? '搜索中...' : '开始搜索' }}
+            </up-button>
+            <up-button 
+                v-if="isConnected"
+                @click="handleDisconnect" 
+                class="mb-30" 
+                type="warn"
+            >
+                断开连接
+            </up-button>
+            <up-button plain type="primary">上一步</up-button>
+        </view>
+
+        <!-- 设备列表区域 -->
+        <view class="device-list-section">
+            <view class="section-title">请选择设备</view>
+            
+            <!-- 搜索状态提示 -->
+            <view v-if="isSearching && discoveredDevices.length === 0" class="searching-tip">
+                <text>正在搜索附近的蓝牙设备...</text>
+            </view>
+
+            <!-- 已配对设备列表 -->
+            <view v-if="pairedDevices.length > 0 && !isSearching" class="paired-devices">
+                <view class="section-subtitle">已配对的设备</view>
+                <scroll-view scroll-y class="device-scroll">
+                    <view 
+                        v-for="device in pairedDevices" 
+                        :key="device.deviceId"
+                        class="device-item"
+                        :class="{ active: connectedDevice?.deviceId === device.deviceId }"
+                        @click="handleConnectDevice(device)"
+                    >
+                        <view class="device-info">
+                            <view class="device-name">{{ device.deviceName }}</view>
+                            <view class="device-address">{{ device.address }}</view>
+                        </view>
+                        <view class="device-status">
+                            <text v-if="connectedDevice?.deviceId === device.deviceId" class="status-connected">已连接</text>
+                            <text v-else class="status-paired">已配对</text>
+                        </view>
+                    </view>
+                </scroll-view>
+            </view>
+
+            <!-- 新发现设备列表 -->
+            <view v-if="discoveredDevices.length > 0 && !isSearching" class="new-devices">
+                <view class="section-subtitle">新发现的设备</view>
+                <scroll-view scroll-y class="device-scroll">
+                    <view 
+                        v-for="device in discoveredDevices" 
+                        :key="device.deviceId"
+                        class="device-item"
+                        :class="{ active: connectedDevice?.deviceId === device.deviceId }"
+                        @click="handleConnectDevice(device)"
+                    >
+                        <view class="device-info">
+                            <view class="device-name">{{ device.deviceName }}</view>
+                            <view class="device-address">{{ device.address }}</view>
+                            <view v-if="device.RSSI !== undefined" class="signal-strength">
+                                信号:{{ device.RSSI }} dBm
+                            </view>
+                        </view>
+                        <view class="device-status">
+                            <text v-if="connectedDevice?.deviceId === device.deviceId" class="status-connected">已连接</text>
+                            <text v-else class="status-new">新设备</text>
+                        </view>
+                    </view>
+                </scroll-view>
+            </view>
+
+            <!-- 无设备提示 -->
+            <view v-if="!isSearching && pairedDevices.length === 0 && discoveredDevices.length === 0" class="no-device-tip">
+                <ut-empty description="暂无可用设备" />
+            </view>
+        </view>
+
+    </view>
+</template>
+
+<script setup lang="ts">
+import { blueDeviceService, BluetoothDeviceInfo } from '@/utils/blue-device-services';
+
+// Props
+const props = defineProps<{
+    // 定义组件需要的 props
+}>();
+
+// Emits
+const emit = defineEmits<{
+    (e: 'connected', device: BluetoothDeviceInfo): void;
+    (e: 'disconnected'): void;
+}>();
+
+// 状态数据
+const isSearching = ref(false);
+const isConnected = ref(false);
+const connectedDevice = ref<BluetoothDeviceInfo | null>(null);
+const pairedDevices = ref<BluetoothDeviceInfo[]>([]);
+const discoveredDevices = ref<BluetoothDeviceInfo[]>([]);
+const errorMessage = ref('');
+
+// 初始化时获取已配对的设备
+onLoad(() => {
+    loadPairedDevices();
+});
+
+/**
+ * 加载已配对的设备列表
+ */
+async function loadPairedDevices(): Promise<void> {
+    try {
+        // 确保蓝牙模块已经初始化(首次进入页面可能还未初始化)
+        try {
+            await blueDeviceService.initializeBluetooth();
+        } catch (initErr: any) {
+            // 如果正在初始化中,则忽略(可能是其它地方已触发初始化)
+            if (typeof initErr === 'string' && initErr.includes('初始化中')) {
+                console.warn('蓝牙初始化进行中,跳过重复初始化');
+            } else {
+                throw initErr;
+            }
+        }
+
+        const devices = await blueDeviceService.getBluetoothDevices();
+        pairedDevices.value = devices.filter(d => d.isPaired);
+    } catch (err) {
+        console.error('加载已配对设备失败:', err);
+        showError('加载设备列表失败');
+    }
+}
+
+/**
+ * 开始搜索蓝牙设备
+ */
+async function handleStartSearch(): Promise<void> {
+    if (isSearching.value) return;
+    
+    resetSearchState();
+    isSearching.value = true;
+    discoveredDevices.value = [];
+    
+    try {
+        // 初始化蓝牙
+        await blueDeviceService.initializeBluetooth();
+        
+        // 设置回调
+        blueDeviceService.setOnDeviceFound(handleDeviceFound);
+        blueDeviceService.setOnConnectionChanged(handleConnectionChanged);
+        blueDeviceService.setOnError(handleError);
+        
+        // 开始搜索(可以指定要搜索的服务 UUID)
+        await blueDeviceService.startDiscoveryBluetoothDevices([
+            '000018f0-0000-1000-8000-00805f9b34fb', // 打印机服务
+            '6e400001-b5a3-f393-e0a9-e50e24dcca9e'  // BLE 打印机常用服务
+        ]);
+        
+        // 30 秒后自动停止搜索
+        setTimeout(() => {
+            stopSearch();
+        }, 30000);
+        
+    } catch (err) {
+        console.error('搜索设备失败:', err);
+        showError(`搜索失败:${err}`);
+        stopSearch();
+    }
+}
+
+/**
+ * 停止搜索
+ */
+function stopSearch(): void {
+    isSearching.value = false;
+    blueDeviceService.stopDiscoveryBluetoothDevices().catch(console.error);
+}
+
+/**
+ * 重置搜索状态
+ */
+function resetSearchState(): void {
+    errorMessage.value = '';
+    discoveredDevices.value = [];
+}
+
+/**
+ * 处理设备发现
+ */
+function handleDeviceFound(device: BluetoothDeviceInfo): void {
+    // 检查是否已经存在于列表中
+    const exists = pairedDevices.value.some(d => d.deviceId === device.deviceId) ||
+                   discoveredDevices.value.some(d => d.deviceId === device.deviceId);
+    
+    if (!exists) {
+        discoveredDevices.value.push(device);
+    }
+}
+
+/**
+ * 处理连接状态变化
+ */
+function handleConnectionChanged(connected: boolean, device?: BluetoothDeviceInfo): void {
+    if (connected && device) {
+        isConnected.value = true;
+        connectedDevice.value = device;
+        
+        // 从发现列表中移除,添加到已配对列表
+        discoveredDevices.value = discoveredDevices.value.filter(d => d.deviceId !== device.deviceId);
+        if (!pairedDevices.value.some(d => d.deviceId === device.deviceId)) {
+            pairedDevices.value.push(device);
+        }
+        
+        emit('connected', device);
+        uni.showToast({
+            title: '连接成功',
+            icon: 'success'
+        });
+    } else {
+        isConnected.value = false;
+        connectedDevice.value = null;
+        emit('disconnected');
+    }
+}
+
+/**
+ * 处理错误
+ */
+function handleError(error: string): void {
+    showError(error);
+}
+
+/**
+ * 显示错误信息
+ */
+function showError(message: string): void {
+    errorMessage.value = message;
+    uni.showToast({
+        title: message,
+        icon: 'none'
+    });
+}
+
+/**
+ * 连接设备
+ */
+async function handleConnectDevice(device: BluetoothDeviceInfo): Promise<void> {
+    if (isSearching.value) {
+        uni.showToast({
+            title: '搜索中,请稍后',
+            icon: 'none'
+        });
+        return;
+    }
+    
+    if (connectedDevice.value?.deviceId === device.deviceId) {
+        return; // 已经连接
+    }
+    
+    try {
+        // 确保蓝牙模块已初始化
+        try {
+            await blueDeviceService.initializeBluetooth();
+        } catch (initErr: any) {
+            if (typeof initErr === 'string' && initErr.includes('初始化中')) {
+                console.warn('蓝牙初始化进行中,跳过重复初始化');
+            } else {
+                throw initErr;
+            }
+        }
+
+        // 连接设备
+        const connectedInfo = await blueDeviceService.connectBluetoothDevice(device.deviceId, 10000);
+        connectedDevice.value = connectedInfo;
+
+        // 获取设备服务
+        await getDeviceServices(device.deviceId);
+
+    } catch (err) {
+        console.error('连接设备失败:', err);
+        showError(`连接失败:${err}`);
+    }
+}
+
+
+/**
+ * 获取设备服务
+ */
+async function getDeviceServices(deviceId: string): Promise<void> {
+    try {
+        const services = await blueDeviceService.getBluetoothDeviceServices(deviceId);
+        console.log('设备服务:', services);
+        
+        // 遍历服务并获取特征值
+        for (const service of services) {
+            if (service.isPrimary) {
+                const characteristics = await blueDeviceService.getBluetoothDeviceCharacteristics(
+                    deviceId,
+                    service.uuid
+                );
+                console.log(`服务 ${service.uuid} 的特征值:`, characteristics);
+                
+                // 可以根据需要启用通知或写入
+                for (const characteristic of characteristics) {
+                    if (characteristic.properties.notify) {
+                        // 启用通知
+                        await blueDeviceService.notifyCharacteristicValue(
+                            deviceId,
+                            service.uuid,
+                            characteristic.uuid,
+                            true
+                        );
+                    }
+                }
+            }
+        }
+    } catch (err) {
+        console.error('获取设备服务失败:', err);
+    }
+}
+
+/**
+ * 断开连接
+ */
+async function handleDisconnect(): Promise<void> {
+    try {
+        await blueDeviceService.disconnectBluetoothDevice();
+        isConnected.value = false;
+        connectedDevice.value = null;
+        emit('disconnected');
+        
+        uni.showToast({
+            title: '已断开连接',
+            icon: 'success'
+        });
+    } catch (err) {
+        console.error('断开连接失败:', err);
+        showError('断开连接失败');
+    }
+}
+
+/**
+ * 写入数据到设备
+ */
+async function writeToDevice(
+    serviceId: string,
+    characteristicId: string,
+    data: string | Uint8Array | ArrayBuffer
+): Promise<void> {
+    if (!connectedDevice.value) {
+        throw new Error('未连接设备');
+    }
+    
+    await blueDeviceService.writeCharacteristicValue(
+        connectedDevice.value.deviceId,
+        serviceId,
+        characteristicId,
+        data
+    );
+}
+
+/**
+ * 打印数据
+ */
+async function printData(data: string | Uint8Array | ArrayBuffer): Promise<void> {
+    if (!connectedDevice.value) {
+        throw new Error('未连接设备');
+    }
+    
+    await blueDeviceService.printData(data);
+}
+
+// 暴露方法给模板使用
+defineExpose({
+    writeToDevice,
+    printData,
+    isConnected,
+    connectedDevice
+});
+</script>
+
+<style lang="scss" scoped>
+.connect-printer-page {
+    min-height: 100vh;
+    background-color: #f5f5f5;
+    
+    .info-images {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        padding: 20rpx;
+        background-color: #fff;
+        
+        image {
+            margin-bottom: 20rpx;
+        }
+    }
+    
+    .action-buttons {
+        background-color: #fff;
+        padding: 20rpx;
+    }
+    
+    .device-list-section {
+        background-color: #fff;
+        padding: 20rpx;
+        margin-top: 20rpx;
+        
+        .section-title {
+            font-size: 32rpx;
+            font-weight: bold;
+            color: #333;
+            padding-bottom: 20rpx;
+            border-bottom: 1rpx solid #eee;
+        }
+        
+        .section-subtitle {
+            font-size: 28rpx;
+            color: #666;
+            margin: 20rpx 0;
+        }
+        
+        .searching-tip {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            padding: 60rpx 0;
+            color: #999;
+            
+            text {
+                margin-top: 20rpx;
+            }
+        }
+        
+        .no-device-tip {
+            padding: 60rpx 0;
+        }
+        
+        .device-scroll {
+            max-height: 600rpx;
+        }
+        
+        .device-item {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            padding: 30rpx;
+            margin-bottom: 10rpx;
+            background-color: #fafafa;
+            border-radius: 10rpx;
+            transition: all 0.3s;
+            
+            &.active {
+                background-color: #e8f4ff;
+                border: 2rpx solid #1989fa;
+            }
+            
+            &:active {
+                opacity: 0.8;
+            }
+            
+            .device-info {
+                flex: 1;
+                
+                .device-name {
+                    font-size: 30rpx;
+                    color: #333;
+                    font-weight: 500;
+                }
+                
+                .device-address {
+                    font-size: 24rpx;
+                    color: #999;
+                    margin-top: 8rpx;
+                }
+                
+                .signal-strength {
+                    font-size: 24rpx;
+                    color: #67c23a;
+                    margin-top: 8rpx;
+                }
+            }
+            
+            .device-status {
+                .status-connected {
+                    font-size: 24rpx;
+                    color: #67c23a;
+                }
+                
+                .status-paired {
+                    font-size: 24rpx;
+                    color: #909399;
+                }
+                
+                .status-new {
+                    font-size: 24rpx;
+                    color: #1989fa;
+                }
+            }
+        }
+    }
+    
+    .error-tip {
+        margin: 20rpx;
+    }
+}
+</style>

BIN
src/static/images/print/print_dt_ssly.gif


BIN
src/static/images/print/print_jt_ssly.jpg


+ 604 - 0
src/utils/blue-device-services.ts

@@ -0,0 +1,604 @@
+/**
+ * 低功耗蓝牙设备服务 (BLE - Bluetooth Low Energy)
+ * 提供蓝牙设备的初始化、搜索、连接、配对和数据写入等功能
+ * 使用 uni-app 的 BLE API
+ */
+
+// 蓝牙设备信息接口
+export interface BluetoothDeviceInfo {
+  deviceId: string;      // 设备 ID
+  deviceName: string;    // 设备名称
+  address: string;       // 设备地址
+  RSSI?: number;         // 信号强度
+  isPaired?: boolean;    // 是否已配对
+}
+
+// 蓝牙服务特征值接口
+export interface BluetoothServiceCharacteristic {
+  serviceId: string;     // 服务 UUID
+  characteristicId: string; // 特征值 UUID
+}
+
+// 蓝牙服务类
+export class BlueDeviceServices {
+  private bluetoothAdapter: any = null;
+  private connectedDeviceId: string = '';
+  private isConnected: boolean = false;
+  private isInitializing: boolean = false;
+  
+  // 回调函数
+  private onDeviceFound: ((device: BluetoothDeviceInfo) => void) | null = null;
+  private onConnectionChanged: ((connected: boolean, device?: BluetoothDeviceInfo) => void) | null = null;
+  private onReceiveData: ((data: ArrayBuffer) => void) | null = null;
+  private onError: ((error: string) => void) | null = null;
+
+  /**
+   * 初始化蓝牙管理器
+   */
+  async initializeBluetooth(): Promise<boolean> {
+    return new Promise((resolve, reject) => {
+      if (this.isInitializing) {
+        reject('蓝牙正在初始化中');
+        return;
+      }
+
+      this.isInitializing = true;
+
+      const ensureBluetoothScope = () => {
+        return new Promise<void>((resolveScope, rejectScope) => {
+          uni.getSetting({
+            success: (res: any) => {
+              const hasScope = res.authSetting?.['scope.bluetooth'];
+              if (hasScope) {
+                resolveScope();
+                return;
+              }
+
+              uni.authorize({
+                scope: 'scope.bluetooth',
+                success: () => {
+                  resolveScope();
+                },
+                fail: (authErr: any) => {
+                  console.warn('蓝牙权限授权失败:', authErr);
+                  rejectScope(authErr);
+                }
+              });
+            },
+            fail: (err: any) => {
+              console.warn('获取权限设置失败:', err);
+              rejectScope(err);
+            }
+          });
+        });
+      };
+
+      const handleInitFail = (err: any) => {
+        console.error('蓝牙适配器初始化失败:', err);
+        this.isInitializing = false;
+        this.onError?.(`蓝牙适配器初始化失败:${err.message || JSON.stringify(err)}`);
+        reject(err);
+      };
+
+      // 先检查权限
+      ensureBluetoothScope().then(() => {
+        uni.openBluetoothAdapter({
+          success: () => {
+            console.log('蓝牙适配器初始化成功');
+            this.isInitializing = false;
+            resolve(true);
+          },
+          fail: handleInitFail
+        });
+      }).catch((err) => {
+        // 权限被拒绝或获取失败,提示用户打开设置
+        this.isInitializing = false;
+        this.onError?.('蓝牙权限未授权,请到系统设置中开启');
+        reject(err);
+      });
+    });
+  }
+
+  /**
+   * 关闭蓝牙适配器
+   */
+  async closeBluetoothAdapter(): Promise<void> {
+    return new Promise((resolve, reject) => {
+      try {
+        uni.closeBluetoothAdapter({
+          success: () => {
+            console.log('蓝牙适配器已关闭');
+            this.resetState();
+            resolve();
+          },
+          fail: (err: any) => {
+            console.error('关闭蓝牙适配器失败:', err);
+            reject(err);
+          }
+        });
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  /**
+   * 重置蓝牙状态
+   */
+  private resetState(): void {
+    this.connectedDeviceId = '';
+    this.isConnected = false;
+    this.onConnectionChanged?.(false);
+  }
+
+  /**
+   * 设置设备发现回调
+   */
+  setOnDeviceFound(callback: (device: BluetoothDeviceInfo) => void): void {
+    this.onDeviceFound = callback;
+  }
+
+  /**
+   * 设置连接状态变化回调
+   */
+  setOnConnectionChanged(callback: (connected: boolean, device?: BluetoothDeviceInfo) => void): void {
+    this.onConnectionChanged = callback;
+  }
+
+  /**
+   * 设置数据接收回调
+   */
+  setOnReceiveData(callback: (data: ArrayBuffer) => void): void {
+    this.onReceiveData = callback;
+  }
+
+  /**
+   * 设置错误回调
+   */
+  setOnError(callback: (error: string) => void): void {
+    this.onError = callback;
+  }
+
+  /**
+   * 获取所有已配对的蓝牙设备列表
+   */
+  async getBluetoothDevices(): Promise<BluetoothDeviceInfo[]> {
+    return new Promise((resolve, reject) => {
+      uni.getBluetoothDevices({
+        success: (res: any) => {
+          const devices: BluetoothDeviceInfo[] = Object.values(res.devices).map((device: any) => ({
+            deviceId: device.deviceId,
+            deviceName: device.name || '未知设备',
+            address: device.address,
+            RSSI: device.RSSI,
+            isPaired: device.isPaired || false
+          }));
+          resolve(devices);
+        },
+        fail: (err: any) => {
+          console.error('获取蓝牙设备列表失败:', err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 开始搜索附近的蓝牙设备
+   * @param services 要搜索的服务 UUID 列表(可选)
+   */
+  async startDiscoveryBluetoothDevices(services?: string[]): Promise<boolean> {
+    return new Promise((resolve, reject) => {
+      if (!this.bluetoothAdapter) {
+        reject('请先初始化蓝牙模块');
+        return;
+      }
+
+      const options: Record<string, any> = {
+        success: () => {
+          console.log('开始搜索蓝牙设备');
+          resolve(true);
+        },
+        fail: (err: any) => {
+          console.error('搜索蓝牙设备失败:', err);
+          reject(err);
+        }
+      };
+
+      // 如果有指定服务 UUID,添加到选项中
+      if (services && services.length > 0) {
+        options.services = services;
+      }
+
+      // 监听设备发现
+      this.bluetoothAdapter.onDeviceFound((res: any) => {
+        const device: BluetoothDeviceInfo = {
+          deviceId: res.device.deviceId,
+          deviceName: res.device.name || '未知设备',
+          address: res.device.address,
+          RSSI: res.device.RSSI,
+          isPaired: res.device.isPaired || false
+        };
+        
+        console.log('发现新设备:', device);
+        this.onDeviceFound?.(device);
+      });
+
+      this.bluetoothAdapter.startDiscoveryBluetoothDevices(options);
+    });
+  }
+
+  /**
+   * 停止搜索蓝牙设备
+   */
+  async stopDiscoveryBluetoothDevices(): Promise<void> {
+    return new Promise((resolve, reject) => {
+      if (!this.bluetoothAdapter) {
+        resolve();
+        return;
+      }
+
+      this.bluetoothAdapter.stopDiscoveryBluetoothDevices({
+        success: () => {
+          console.log('停止搜索蓝牙设备');
+          resolve();
+        },
+        fail: (err: any) => {
+          console.error('停止搜索蓝牙设备失败:', err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 连接蓝牙设备(建立 BLE 连接)
+   * @param deviceId 设备 ID
+   * @param timeout 超时时间(毫秒),默认 10000
+   */
+  async connectBluetoothDevice(deviceId: string, timeout: number = 10000): Promise<BluetoothDeviceInfo> {
+    return new Promise((resolve, reject) => {
+      if (!this.bluetoothAdapter) {
+        reject('请先初始化蓝牙模块');
+        return;
+      }
+
+      const connectTimeout = setTimeout(() => {
+        reject(new Error('连接超时'));
+      }, timeout);
+
+      // 监听连接成功
+      this.bluetoothAdapter.onConnectSuccess(() => {
+        clearTimeout(connectTimeout);
+        console.log('连接蓝牙设备成功:', deviceId);
+        this.connectedDeviceId = deviceId;
+        this.isConnected = true;
+        
+        // 获取设备信息
+        this.getDeviceInfo(deviceId).then((info) => {
+          this.onConnectionChanged?.(true, info);
+          resolve(info);
+        }).catch(reject);
+      });
+
+      // 监听连接失败
+      this.bluetoothAdapter.onConnectFail(() => {
+        clearTimeout(connectTimeout);
+        console.error('连接蓝牙设备失败');
+        this.isConnected = false;
+        reject(new Error('连接失败'));
+      });
+
+      // 发起 BLE 连接
+      uni.createBLEConnection({
+        deviceId,
+        success: () => {
+          console.log('发起蓝牙连接请求');
+        },
+        fail: (err: any) => {
+          clearTimeout(connectTimeout);
+          console.error('发起蓝牙连接失败:', err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 断开蓝牙连接
+   */
+  async disconnectBluetoothDevice(): Promise<void> {
+    return new Promise((resolve, reject) => {
+      if (!this.connectedDeviceId) {
+        resolve();
+        return;
+      }
+
+      uni.closeBLEConnection({
+        deviceId: this.connectedDeviceId,
+        success: () => {
+          console.log('断开蓝牙连接成功');
+          this.resetState();
+          resolve();
+        },
+        fail: (err: any) => {
+          console.error('断开蓝牙连接失败:', err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 获取设备信息
+   */
+  private async getDeviceInfo(deviceId: string): Promise<BluetoothDeviceInfo> {
+    return {
+      deviceId,
+      deviceName: '未知设备',
+      address: deviceId,
+      isPaired: false
+    };
+  }
+
+  /**
+   * 获取设备支持的所有服务
+   */
+  async getBluetoothDeviceServices(deviceId: string): Promise<any[]> {
+    return new Promise((resolve, reject) => {
+      uni.getBLEDeviceServices({
+        deviceId,
+        success: (res: any) => {
+          console.log('获取设备服务成功:', res.services);
+          resolve(res.services);
+        },
+        fail: (err: any) => {
+          console.error('获取设备服务失败:', err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 获取指定服务的特征值
+   */
+  async getBluetoothDeviceCharacteristics(
+    deviceId: string, 
+    serviceId: string
+  ): Promise<any[]> {
+    return new Promise((resolve, reject) => {
+      uni.getBLEDeviceCharacteristics({
+        deviceId,
+        serviceId,
+        success: (res: any) => {
+          console.log('获取特征值成功:', res.characteristics);
+          resolve(res.characteristics);
+        },
+        fail: (err: any) => {
+          console.error('获取特征值失败:', err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 读取特征值
+   */
+  async readCharacteristicValue(
+    deviceId: string,
+    serviceId: string,
+    characteristicId: string
+  ): Promise<ArrayBuffer> {
+    return new Promise((resolve, reject) => {
+      uni.readBLECharacteristicValue({
+        deviceId,
+        serviceId,
+        characteristicId,
+        success: (res: any) => {
+          console.log('读取特征值成功');
+          resolve(res.value);
+        },
+        fail: (err: any) => {
+          console.error('读取特征值失败:', err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 订阅特征值变化
+   */
+  async notifyCharacteristicValue(
+    deviceId: string,
+    serviceId: string,
+    characteristicId: string,
+    enable: boolean = true
+  ): Promise<void> {
+    return new Promise((resolve, reject) => {
+      uni.notifyBLECharacteristicValueChange({
+        deviceId,
+        serviceId,
+        characteristicId,
+        state: enable,
+        success: () => {
+          console.log(`${enable ? '启用' : '禁用'}特征值通知成功`);
+          
+          if (enable) {
+            // 添加监听
+            this.bluetoothAdapter?.onBLECharacteristicValueChange((res: any) => {
+              if (res.deviceId === deviceId && 
+                  res.serviceId === serviceId && 
+                  res.characteristicId === characteristicId) {
+                this.onReceiveData?.(res.value);
+              }
+            });
+          }
+          
+          resolve();
+        },
+        fail: (err: any) => {
+          console.error(`${enable ? '启用' : '禁用'}特征值通知失败:`, err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 写入数据到特征值
+   * @param deviceId 设备 ID
+   * @param serviceId 服务 UUID
+   * @param characteristicId 特征值 UUID
+   * @param value 要写入的数据(ArrayBuffer 或 Uint8Array)
+   * @param needResponse 是否需要响应,默认 true
+   */
+  async writeCharacteristicValue(
+    deviceId: string,
+    serviceId: string,
+    characteristicId: string,
+    value: ArrayBuffer | Uint8Array,
+    needResponse: boolean = true
+  ): Promise<void> {
+    return new Promise((resolve, reject) => {
+      // 转换为 ArrayBuffer
+      let buffer: ArrayBuffer;
+      if (value instanceof Uint8Array) {
+        buffer = value.buffer as ArrayBuffer;
+      } else if (value instanceof ArrayBuffer) {
+        buffer = value;
+      } else {
+        // 如果是字符串,转换为 UTF-8 编码
+        const encoder = new TextEncoder();
+        buffer = encoder.encode(value as string).buffer as ArrayBuffer;
+      }
+
+      uni.writeBLECharacteristicValue({
+        deviceId,
+        serviceId,
+        characteristicId,
+        value: buffer,
+        success: () => {
+          console.log('写入特征值成功');
+          resolve();
+        },
+        fail: (err: any) => {
+          console.error('写入特征值失败:', err);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  /**
+   * 打印数据到蓝牙打印机
+   * @param data 要打印的数据
+   * @param printerServiceId 打印机服务 UUID
+   * @param printerCharacteristicId 打印机特征值 UUID
+   */
+  async printData(
+    data: string | Uint8Array,
+    printerServiceId: string = '000018f0-0000-1000-8000-00805f9b34fb',
+    printerCharacteristicId: string = '00002af1-0000-1000-8000-00805f9b34fb'
+  ): Promise<void> {
+    if (!this.connectedDeviceId) {
+      throw new Error('未连接蓝牙设备');
+    }
+
+    await this.writeCharacteristicValue(
+      this.connectedDeviceId,
+      printerServiceId,
+      printerCharacteristicId,
+      data
+    );
+  }
+
+  /**
+   * 检查设备是否已连接
+   */
+  isConnectedToDevice(): boolean {
+    return this.isConnected;
+  }
+
+  /**
+   * 获取当前连接的设备信息
+   */
+  getCurrentDevice(): BluetoothDeviceInfo | null {
+    if (!this.connectedDeviceId) {
+      return null;
+    }
+    
+    return {
+      deviceId: this.connectedDeviceId,
+      deviceName: '未知设备',
+      address: this.connectedDeviceId
+    };
+  }
+
+  /**
+   * 查找并连接指定的蓝牙打印机
+   * @param nameFilter 设备名称过滤器(部分匹配)
+   * @param timeout 超时时间
+   */
+  async findAndConnectPrinter(
+    nameFilter: string = '',
+    timeout: number = 15000
+  ): Promise<BluetoothDeviceInfo> {
+    return new Promise(async (resolve, reject) => {
+      try {
+        // 初始化蓝牙
+        await this.initializeBluetooth();
+        
+        // 开始搜索
+        await this.startDiscoveryBluetoothDevices();
+        
+        let foundDevice: BluetoothDeviceInfo | null = null;
+        
+        // 设置设备发现回调
+        this.setOnDeviceFound((device) => {
+          // 检查是否符合过滤条件
+          const matchesFilter = !nameFilter || 
+            device.deviceName.includes(nameFilter) ||
+            device.deviceName.toLowerCase().includes(nameFilter.toLowerCase());
+          
+          if (matchesFilter) {
+            foundDevice = device;
+            console.log('找到目标设备:', device);
+            
+            // 停止搜索
+            this.stopDiscoveryBluetoothDevices();
+            
+            // 连接设备
+            this.connectBluetoothDevice(device.deviceId, timeout)
+              .then(resolve)
+              .catch(reject);
+          }
+        });
+        
+        // 超时处理
+        const searchTimeout = setTimeout(() => {
+          this.stopDiscoveryBluetoothDevices();
+          if (!foundDevice) {
+            reject(new Error('未找到匹配的蓝牙设备'));
+          }
+        }, timeout);
+        
+        // 监听连接成功
+        this.setOnConnectionChanged((connected, device) => {
+          if (connected && device) {
+            clearTimeout(searchTimeout);
+            resolve(device);
+          }
+        });
+        
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+}
+
+// 导出单例实例
+export const blueDeviceService = new BlueDeviceServices();