diff --git a/module_base/src/main/java/com/lukouguoji/module_base/util/PrinterUtils.kt b/module_base/src/main/java/com/lukouguoji/module_base/util/PrinterUtils.kt index 2a77209..d679705 100644 --- a/module_base/src/main/java/com/lukouguoji/module_base/util/PrinterUtils.kt +++ b/module_base/src/main/java/com/lukouguoji/module_base/util/PrinterUtils.kt @@ -2,7 +2,6 @@ package com.lukouguoji.module_base.util import android.Manifest import android.graphics.Typeface -import android.util.Log import com.gainscha.sdk2.ConnectType import com.gainscha.sdk2.ConnectionListener import com.gainscha.sdk2.Printer @@ -18,23 +17,39 @@ import com.gprinter.io.PortManager import com.gprinter.utils.CallbackListener import com.gprinter.utils.Command import com.gprinter.utils.ConnMethod -import com.lukouguoji.module_base.bean.FlatcarBean +import com.lukouguoji.module_base.bean.GjcUldUseBean import com.lukouguoji.module_base.bean.GncFuBangBean import com.lukouguoji.module_base.bean.GncShouYunBean import com.lukouguoji.module_base.http.net.NetApply import com.lukouguoji.module_base.ktx.launchCollect -import com.lukouguoji.module_base.ktx.launchLoadingCollect import com.lukouguoji.module_base.ktx.loge import com.lukouguoji.module_base.ktx.permission import com.lukouguoji.module_base.ktx.showToast import com.lukouguoji.module_base.model.LoadingModel import dev.DevUtils -import dev.utils.app.SizeUtils import okio.internal.commonToUtf8String object PrinterUtils { + /** + * 打印单元格数据 + */ + private data class CellData( + val title: String, // 中文标签(大字) + val subtitle: String, // 英文标签(小字) + val value: String // 值(大字,垂直居中) + ) + + /** + * 打印表格行数据 + */ + private data class GridRow( + val left: CellData? = null, + val right: CellData? = null, + val merged: CellData? = null // 合并单元格数据 + ) + private val loading by lazy { LoadingModel() } @@ -133,7 +148,8 @@ object PrinterUtils { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, ) { - finder.searchPrinters(ConnectType.BLUETOOTH, + finder.searchPrinters( + ConnectType.BLUETOOTH, object : PrinterFinder.SimpleSearchPrinterResultListener() { override fun onSearchBluetoothPrinter(device: BluetoothPrinterDevice?) { if (device == null) return @@ -218,28 +234,28 @@ object PrinterUtils { var fDestinationCn = "" launchCollect({ NetApply.api.getAirportByCode(bean.dest1) - }){ + }) { onSuccess = { destinationCn = it.data?.nameCn ?: "" rowItem.add(listOf("日期", "航班号", "目的站")) - rowItem.add(listOf( bean.fdate, bean.fno, bean.dest1 + destinationCn )) - rowItem.add(listOf("斗号", "计重", "仓管")) - rowItem.add(listOf( bean.location, bean.username, "")) - rowItem.add(listOf("装机仓位", "机位", "特车司机")) - rowItem.add(listOf( "", "", "")) - rowItem.add(listOf("监装监卸", "中转信息","备注")) + rowItem.add(listOf(bean.fdate, bean.fno, bean.dest1 + destinationCn)) + rowItem.add(listOf("斗号", "计重", "仓管")) + rowItem.add(listOf(bean.location, bean.username, "")) + rowItem.add(listOf("装机仓位", "机位", "特车司机")) + rowItem.add(listOf("", "", "")) + rowItem.add(listOf("监装监卸", "中转信息", "备注")) if (bean.dest2 != "") { launchCollect({ NetApply.api.getAirportByCode(bean.dest2) - }){ + }) { onSuccess = { it2 -> fDestinationCn = it2.data?.nameCn ?: "" - rowItem.add(listOf( "", bean.dest2 + fDestinationCn, "")) + rowItem.add(listOf("", bean.dest2 + fDestinationCn, "")) printCommonGridNew(rowItem) } } } else { - rowItem.add(listOf( "", "", "")) + rowItem.add(listOf("", "", "")) printCommonGridNew(rowItem) } } @@ -259,35 +275,35 @@ object PrinterUtils { var fDestinationCn = "" launchCollect({ NetApply.api.getAirportByCode(bean.fdest) - }){ + }) { onSuccess = { fDestinationCn = it.data?.nameCn ?: "" if (bean.dest != "") { launchCollect({ NetApply.api.getAirportByCode(bean.dest) - }){ + }) { onSuccess = { it2 -> destinationCn = it2.data?.nameCn ?: "" rowItem.add(listOf("日期", "航班号", "目的站")) - rowItem.add(listOf( bean.fdate, bean.fno, bean.fdest + fDestinationCn )) - rowItem.add(listOf("斗号", "计重", "仓管")) - rowItem.add(listOf( bean.location, bean.username, "")) - rowItem.add(listOf("装机仓位", "机位", "特车司机")) - rowItem.add(listOf( "", "", "")) - rowItem.add(listOf("监装监卸", "中转信息","备注")) - rowItem.add(listOf( "", bean.dest + destinationCn, "")) + rowItem.add(listOf(bean.fdate, bean.fno, bean.fdest + fDestinationCn)) + rowItem.add(listOf("斗号", "计重", "仓管")) + rowItem.add(listOf(bean.location, bean.username, "")) + rowItem.add(listOf("装机仓位", "机位", "特车司机")) + rowItem.add(listOf("", "", "")) + rowItem.add(listOf("监装监卸", "中转信息", "备注")) + rowItem.add(listOf("", bean.dest + destinationCn, "")) printCommonGridNew(rowItem) } } } else { rowItem.add(listOf("日期", "航班号", "目的站")) - rowItem.add(listOf( bean.fdate, bean.fno, bean.fdest + fDestinationCn )) - rowItem.add(listOf("斗号", "计重", "仓管")) - rowItem.add(listOf( bean.location, bean.username, "")) - rowItem.add(listOf("装机仓位", "机位", "特车司机")) - rowItem.add(listOf( "", "", "")) - rowItem.add(listOf("监装监卸", "中转信息","备注")) - rowItem.add(listOf( "", "", "")) + rowItem.add(listOf(bean.fdate, bean.fno, bean.fdest + fDestinationCn)) + rowItem.add(listOf("斗号", "计重", "仓管")) + rowItem.add(listOf(bean.location, bean.username, "")) + rowItem.add(listOf("装机仓位", "机位", "特车司机")) + rowItem.add(listOf("", "", "")) + rowItem.add(listOf("监装监卸", "中转信息", "备注")) + rowItem.add(listOf("", "", "")) printCommonGridNew(rowItem) } @@ -295,6 +311,43 @@ object PrinterUtils { } } + /** + * 打印国际出港板箱过磅挂签(2列布局,支持合并单元格) + */ + fun printGjcBoxWeighing(bean: GjcUldUseBean) { + val rows = arrayListOf() + + // 第1行:日期 | 航班号 + rows.add(GridRow( + left = CellData("日期:", "DATA:", bean.fdate), + right = CellData("航班号:", "FLIGHT NO.:", bean.fno) + )) + + // 第2行:目的站 | 板型 + rows.add(GridRow( + left = CellData("目的站:", "DESTINATION:", bean.fdest), + right = CellData("板型:", "PALLET TYPE:", bean.boardType) + )) + + // 第3行:集装器编号(合并两列) + rows.add(GridRow( + merged = CellData("集装器编号:", "ULD NO.:", bean.uld) + )) + + // 第4行:件数 | 重量 + rows.add(GridRow( + left = CellData("件数:", "PIECES:", bean.pieces.ifEmpty { "0" }), + right = CellData("重量:", "GROSS WEIGHT:", bean.totalWeight.toInt().toString()) + )) + + // 第5行:备注(合并两列) + rows.add(GridRow( + merged = CellData("备注:", "NOTES:", bean.remark) + )) + + printMergedGrid(rows) + } + private fun printCommonGrid(rowItem: ArrayList>) { val gridStartX = 80 val gridStartY = 300 @@ -370,10 +423,10 @@ object PrinterUtils { if (index % 2 == 0) { padding = 15 } - if (str.length == 2){ + if (str.length == 2) { padding += 60 } - if (str.length == 3){ + if (str.length == 3) { padding += 30 } if (str.length == 7) { @@ -391,6 +444,127 @@ object PrinterUtils { portManager?.writeDataImmediately(bytes) } + /** + * 打印支持合并单元格的表格 + */ + private fun printMergedGrid(rows: ArrayList) { + val gridStartX = 30 + val gridStartY = 250 + val columnWidth = 570 + val rowHeight = 240 + val gridWidth = columnWidth * 2 + val gridHeight = rowHeight * rows.size + + val bytes = Tspl().apply { + addSize(100, 100) + addGap(3) + addCls() + addTextByBitmap(80, 80, 0, 130, "扬州泰州机场", Typeface.DEFAULT) + + // 绘制表格横线 + for (i in 0..rows.size) { + addBar(gridStartX, gridStartY + (i * rowHeight), gridWidth, 2) + } + + // 绘制表格竖线(需要根据是否合并单元格决定) + rows.forEachIndexed { rowIndex, row -> + val yTop = gridStartY + (rowIndex * rowHeight) + + // 左边线(总是绘制) + addBar(gridStartX, yTop, 2, rowHeight) + + // 中间分隔线(非合并单元格才绘制) + if (row.merged == null) { + addBar(gridStartX + columnWidth, yTop, 2, rowHeight) + } + + // 右边线(总是绘制) + if (rowIndex == rows.size - 1) { + addBar(gridStartX + gridWidth, yTop, 2, rowHeight) + } + } + + // 填充单元格内容 + rows.forEachIndexed { rowIndex, row -> + val yBase = gridStartY + (rowIndex * rowHeight) + + if (row.merged != null) { + // 合并单元格 + renderCell( + cell = row.merged, + x = gridStartX + 30, + y = yBase, + cellWidth = gridWidth, + cellHeight = rowHeight + ) + } else { + // 左列单元格 + row.left?.let { cell -> + renderCell( + cell = cell, + x = gridStartX + 30, + y = yBase, + cellWidth = columnWidth, + cellHeight = rowHeight + ) + } + // 右列单元格 + row.right?.let { cell -> + renderCell( + cell = cell, + x = gridStartX + columnWidth + 30, + y = yBase, + cellWidth = columnWidth, + cellHeight = rowHeight + ) + } + } + } + + addPrint(1) + }.bytes + + portManager?.writeDataImmediately(bytes) + } + + /** + * 渲染单元格内容 + * @param cell 单元格数据 + * @param x 起始X坐标 + * @param y 起始Y坐标 + * @param cellWidth 单元格宽度(用于计算右侧对齐) + * @param cellHeight 单元格高度(用于计算垂直居中) + */ + private fun Tspl.renderCell( + cell: CellData, + x: Int, + y: Int, + cellWidth: Int, + cellHeight: Int + ) { + // 左上角:中文标签和英文标签 + val titleY = y + 30 // 中文标签Y位置 + val subtitleY = y + 80 // 英文标签Y位置(中文下方) + + // 中文标签(大字,左上) + addText(x, titleY, Tspl.FONT_TSS24, 0, 3, 3, cell.title) + + // 英文标签(小字,左上) + addText(x, subtitleY, Tspl.FONT_TSS24, 0, 2, 2, cell.subtitle) + + // 右侧:值(大字,垂直居中) + // 计算垂直居中的Y坐标:y + (cellHeight / 2) - (文字高度 / 2) + // 假设size=3的文字高度约为80像素 + val valueY = y + (cellHeight / 2) - 40 // 垂直居中 + + // 计算右侧X坐标:中文标签宽度 + 间距 + // 假设"目的站:"宽度约为180像素,"DESTINATION:"宽度约为240像素 + val valueX = x + 280 // 右侧偏移量 + + // 值(大字,垂直居中,右侧) + addText(valueX, valueY, Tspl.FONT_TSS24, 0, 3, 3, cell.value) + } + fun printTest() { getConnectedPrinters().forEach { val bytes = v(Instruction.TSC.toString(), null).bytes diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/GjcBoxWeighingAddViewModel.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/GjcBoxWeighingAddViewModel.kt index a65a9fc..861143c 100644 --- a/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/GjcBoxWeighingAddViewModel.kt +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/GjcBoxWeighingAddViewModel.kt @@ -21,9 +21,11 @@ import com.lukouguoji.module_base.ktx.noNull import com.lukouguoji.module_base.ktx.showToast import com.lukouguoji.module_base.ktx.toRequestBody import com.lukouguoji.module_base.ktx.verifyNullOrEmpty +import com.lukouguoji.module_base.model.BluetoothDialogModel import com.lukouguoji.module_base.model.ScanModel import com.lukouguoji.module_base.util.Common import com.lukouguoji.module_base.util.DictUtils +import com.lukouguoji.module_base.util.PrinterUtils import dev.utils.app.info.KeyValue import kotlinx.coroutines.launch import java.util.Calendar @@ -333,6 +335,12 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() { onSuccess = { result -> if (result.verifySuccess()) { showToast("添加成功") + + // 如果勾选了打印挂签,则执行打印 + if (printTag.value == true) { + executePrint() + } + // 发送刷新事件 viewModelScope.launch { FlowBus.with(ConstantEvent.EVENT_REFRESH).emit("refresh") @@ -345,6 +353,19 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() { } } + /** + * 执行打印 + */ + private fun executePrint() { + val bean = dataBean.value ?: return + + // 使用 BluetoothDialogModel 选择打印机并打印 + BluetoothDialogModel() + .showCallBack { + PrinterUtils.printGjcBoxWeighing(bean) + } + } + /** * 处理扫码结果 */ diff --git a/module_gjc/src/main/res/layout/activity_gjc_box_weighing_add.xml b/module_gjc/src/main/res/layout/activity_gjc_box_weighing_add.xml index 1e6a099..a4629a3 100644 --- a/module_gjc/src/main/res/layout/activity_gjc_box_weighing_add.xml +++ b/module_gjc/src/main/res/layout/activity_gjc_box_weighing_add.xml @@ -389,7 +389,7 @@