From 6bc653bb7442deff392065081e39641d74a92991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=A0=E9=97=BB=E9=A3=8E?= Date: Fri, 26 Jun 2026 19:52:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E5=92=8C=E7=89=A9=E5=93=81=E6=89=93=E5=8D=B0=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E4=B8=BA=E5=B7=A6=E5=8F=B3=E6=8E=92=E5=B8=83=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=BA=8C=E7=BB=B4=E7=A0=81=E5=92=8C=E6=9D=A1=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 工单模板:左侧二维码+客户,右侧工单ID/标题/描述 - 物品模板:左侧二维码,右侧物品ID/名称/数量/位置/客户,底部条码+序列号 - 打印标签设置:新增标签宽度/高度/打印前回退长度设置 - QR码JSON增加type=ops_sys字段区分系统来源 - WorkOrder/Item模型增加linkedCustomers等关联数据 - 使用Canvas绘制Bitmap实现自定义打印布局 --- .../ops_android/data/local/SessionManager.kt | 33 +++ .../example/ops_android/data/model/Item.kt | 6 +- .../ops_android/data/model/WorkOrder.kt | 4 +- .../data/repository/WarehouseRepository.kt | 7 +- .../data/repository/WorkOrderRepository.kt | 6 +- .../ops_android/printer/PrintHelper.kt | 253 ++++++++++++++++-- .../ops_android/ui/settings/SettingsScreen.kt | 43 +++ .../ui/settings/SettingsViewModel.kt | 46 +++- .../ui/warehouse/ItemDetailScreen.kt | 4 +- .../ui/workorder/WorkOrderDetailScreen.kt | 2 +- 10 files changed, 377 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/example/ops_android/data/local/SessionManager.kt b/app/src/main/java/com/example/ops_android/data/local/SessionManager.kt index 17fde9d..4622916 100644 --- a/app/src/main/java/com/example/ops_android/data/local/SessionManager.kt +++ b/app/src/main/java/com/example/ops_android/data/local/SessionManager.kt @@ -28,6 +28,9 @@ class SessionManager(private val context: Context) { private val KEY_COOKIE_REMEMBER = booleanPreferencesKey("cookie_remember") private val KEY_PRINTER_BLACK_MARK = booleanPreferencesKey("printer_black_mark") private val KEY_PRINTER_UNWIND_MM = intPreferencesKey("printer_unwind_mm") + private val KEY_LABEL_WIDTH_MM = intPreferencesKey("label_width_mm") + private val KEY_LABEL_HEIGHT_MM = intPreferencesKey("label_height_mm") + private val KEY_PRINT_RETRACT_MM = intPreferencesKey("print_retract_mm") } @Volatile @@ -46,6 +49,18 @@ class SessionManager(private val context: Context) { var cachedUnwindMm: Int = 40 private set + @Volatile + var cachedLabelWidthMm: Int = 53 + private set + + @Volatile + var cachedLabelHeightMm: Int = 40 + private set + + @Volatile + var cachedPrintRetractMm: Int = 10 + private set + fun clearCookieCache() { cachedCookieValue = "" } @@ -59,6 +74,9 @@ class SessionManager(private val context: Context) { cachedCookieValue = prefs[KEY_COOKIE_VALUE] ?: "" cachedBlackMarkEnabled = prefs[KEY_PRINTER_BLACK_MARK] ?: true cachedUnwindMm = prefs[KEY_PRINTER_UNWIND_MM] ?: 40 + cachedLabelWidthMm = prefs[KEY_LABEL_WIDTH_MM] ?: 53 + cachedLabelHeightMm = prefs[KEY_LABEL_HEIGHT_MM] ?: 40 + cachedPrintRetractMm = prefs[KEY_PRINT_RETRACT_MM] ?: 10 } suspend fun getApiBaseUrl(): String = cachedBaseUrl.ifEmpty { @@ -115,4 +133,19 @@ class SessionManager(private val context: Context) { cachedUnwindMm = mm context.dataStore.edit { it[KEY_PRINTER_UNWIND_MM] = mm } } + + suspend fun setLabelWidthMm(mm: Int) { + cachedLabelWidthMm = mm + context.dataStore.edit { it[KEY_LABEL_WIDTH_MM] = mm } + } + + suspend fun setLabelHeightMm(mm: Int) { + cachedLabelHeightMm = mm + context.dataStore.edit { it[KEY_LABEL_HEIGHT_MM] = mm } + } + + suspend fun setPrintRetractMm(mm: Int) { + cachedPrintRetractMm = mm + context.dataStore.edit { it[KEY_PRINT_RETRACT_MM] = mm } + } } diff --git a/app/src/main/java/com/example/ops_android/data/model/Item.kt b/app/src/main/java/com/example/ops_android/data/model/Item.kt index 55dd50f..a47df81 100644 --- a/app/src/main/java/com/example/ops_android/data/model/Item.kt +++ b/app/src/main/java/com/example/ops_android/data/model/Item.kt @@ -1,5 +1,6 @@ package com.example.ops_android.data.model +import com.example.ops_android.data.remote.api.LinkedCustomerInfo import com.google.gson.annotations.SerializedName data class Item( @@ -14,7 +15,10 @@ data class Item( @SerializedName("CreatorID") val user_id: Int? = null, @SerializedName("barcode") private val _barcode: String? = null, @SerializedName("spec") private val _spec: String? = null, - @SerializedName("move_history") private val _move_history: List? = null + @SerializedName("move_history") private val _move_history: List? = null, + // Filled from detail API + @kotlin.jvm.Transient val linkedCustomers: List? = null, + @kotlin.jvm.Transient val containerBreadcrumb: String? = null ) { val barcode: String? get() = _barcode val spec: String? get() = _spec diff --git a/app/src/main/java/com/example/ops_android/data/model/WorkOrder.kt b/app/src/main/java/com/example/ops_android/data/model/WorkOrder.kt index db95b15..edd00a3 100644 --- a/app/src/main/java/com/example/ops_android/data/model/WorkOrder.kt +++ b/app/src/main/java/com/example/ops_android/data/model/WorkOrder.kt @@ -1,5 +1,6 @@ package com.example.ops_android.data.model +import com.example.ops_android.data.remote.api.LinkedCustomer import com.google.gson.annotations.SerializedName data class WorkOrder( @@ -11,7 +12,8 @@ data class WorkOrder( @SerializedName("UpdatedAt") val updated_at: String? = null, @SerializedName("UserID") val user_id: Int? = null, // Filled from detail API - @kotlin.jvm.Transient val commits: List? = null + @kotlin.jvm.Transient val commits: List? = null, + @kotlin.jvm.Transient val linkedCustomers: List? = null ) data class WorkOrderCommit( diff --git a/app/src/main/java/com/example/ops_android/data/repository/WarehouseRepository.kt b/app/src/main/java/com/example/ops_android/data/repository/WarehouseRepository.kt index f3cdd2b..c3fac6f 100644 --- a/app/src/main/java/com/example/ops_android/data/repository/WarehouseRepository.kt +++ b/app/src/main/java/com/example/ops_android/data/repository/WarehouseRepository.kt @@ -101,7 +101,12 @@ class WarehouseRepository( return try { val response = warehouseApi.getItem(GetItemRequest(id)) if (response.errCode == 0 && response.data != null) { - Result.success(response.data) + val resp = response.data + val itm = resp.item?.copy( + linkedCustomers = resp.customers, + containerBreadcrumb = resp.container_breadcrumb + ) + Result.success(resp.copy(item = itm)) } else { Result.failure(Exception("获取物品详情失败")) } diff --git a/app/src/main/java/com/example/ops_android/data/repository/WorkOrderRepository.kt b/app/src/main/java/com/example/ops_android/data/repository/WorkOrderRepository.kt index 9f5cc17..95b29db 100644 --- a/app/src/main/java/com/example/ops_android/data/repository/WorkOrderRepository.kt +++ b/app/src/main/java/com/example/ops_android/data/repository/WorkOrderRepository.kt @@ -29,7 +29,11 @@ class WorkOrderRepository( return try { val response = workOrderApi.get(WorkOrderGetRequest(id)) if (response.errCode == 0 && response.data?.order != null) { - Result.success(response.data.order) + val detail = response.data + val wo = detail.order.copy( + linkedCustomers = detail.linkedCustomers + ) + Result.success(wo) } else { Result.failure(Exception("获取工单详情失败")) } diff --git a/app/src/main/java/com/example/ops_android/printer/PrintHelper.kt b/app/src/main/java/com/example/ops_android/printer/PrintHelper.kt index 7a7c3cd..2a206a4 100644 --- a/app/src/main/java/com/example/ops_android/printer/PrintHelper.kt +++ b/app/src/main/java/com/example/ops_android/printer/PrintHelper.kt @@ -1,16 +1,148 @@ package com.example.ops_android.printer +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.text.Layout +import android.text.StaticLayout +import android.text.TextPaint import com.example.ops_android.data.model.Item import com.example.ops_android.data.model.PurchaseOrder import com.example.ops_android.data.model.WorkOrder +import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType +import com.google.zxing.oned.Code128Writer +import com.google.zxing.qrcode.QRCodeWriter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import org.json.JSONObject +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale object PrintHelper { private val printMutex = Mutex() + private const val DOTS_PER_MM = 8 + + private fun mmToDots(mm: Int): Int = mm * DOTS_PER_MM + + private fun buildQrContent(module: String, id: Int): String { + val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + return JSONObject().apply { + put("v", "v1") + put("module", module) + put("id", id) + put("printedAt", sdf.format(Date())) + put("type", "ops_sys") + }.toString() + } + + private fun generateQRBitmap(content: String, size: Int): Bitmap { + val hints = mapOf(EncodeHintType.MARGIN to 1) + val bitMatrix = QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, size, size, hints) + val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + for (y in 0 until size) { + for (x in 0 until size) { + bitmap.setPixel(x, y, if (bitMatrix[x, y]) Color.BLACK else Color.WHITE) + } + } + return bitmap + } + + private fun drawTextBlock( + canvas: Canvas, + label: String, + value: String, + x: Float, + y: Float, + maxWidth: Float, + maxLines: Int = 1, + textSize: Float = 22f, + bold: Boolean = false, + lineSpacingExtra: Float = 4f + ): Float { + val text = "$label$value" + val paint = TextPaint().apply { + color = Color.BLACK + this.textSize = textSize + isAntiAlias = true + isFakeBoldText = bold + } + + val builder = StaticLayout.Builder.obtain(text, 0, text.length, paint, maxWidth.toInt()) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setMaxLines(maxLines) + .setEllipsize(android.text.TextUtils.TruncateAt.END) + .setLineSpacing(lineSpacingExtra, 1f) + + val layout = builder.build() + canvas.save() + canvas.translate(x, y) + layout.draw(canvas) + canvas.restore() + + return y + layout.height + } + + private fun buildWorkOrderLabelBitmap(wo: WorkOrder, widthDots: Int, heightDots: Int): Bitmap { + val bitmap = Bitmap.createBitmap(widthDots, heightDots, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + canvas.drawColor(Color.WHITE) + + val padding = 12 + val dividerX = (widthDots * 0.48).toInt() + val leftInnerWidth = dividerX - padding * 2 + val qrSize = (leftInnerWidth * 0.95).toInt().coerceAtLeast(140) + val qrX = (dividerX - qrSize) / 2 + val qrY = padding + + val qrContent = buildQrContent("workorder", wo.id) + val qrBitmap = generateQRBitmap(qrContent, qrSize) + canvas.drawBitmap(qrBitmap, qrX.toFloat(), qrY.toFloat(), null) + + val sepPaint = Paint().apply { + color = Color.BLACK + strokeWidth = 1.5f + isAntiAlias = true + } + canvas.drawLine(dividerX.toFloat(), padding.toFloat(), dividerX.toFloat(), (heightDots - padding).toFloat(), sepPaint) + + val textX = (dividerX + padding).toFloat() + val textMaxWidth = (widthDots - dividerX - padding * 2).toFloat() + var textY = (padding - 2).toFloat() + val maxTextY = (heightDots - padding).toFloat() + + textY = drawTextBlock(canvas, "", "工单:${wo.id}", textX, textY, textMaxWidth, textSize = 32f, bold = true, lineSpacingExtra = 6f) + + if (textY < maxTextY) { + textY = drawTextBlock(canvas, "", "标题:${wo.title ?: "-"}", textX, textY, textMaxWidth, maxLines = 3, textSize = 28f, lineSpacingExtra = 4f) + } + + if (textY < maxTextY) { + textY = drawTextBlock(canvas, "", "描述:${wo.description ?: "-"}", textX, textY, textMaxWidth, maxLines = 6, textSize = 28f, lineSpacingExtra = 4f) + } + + val customerNames = wo.linkedCustomers + ?.mapNotNull { c -> + listOfNotNull(c.first_name, c.last_name) + .joinToString(" ") + .ifBlank { null } + } + ?.joinToString(", ") + ?: "-" + val customerText = "客户:$customerNames" + val customerY = qrY + qrSize + 4 + val leftTextMaxWidth = (dividerX - padding * 2).toFloat() + if (customerY < heightDots - padding) { + drawTextBlock(canvas, "", customerText, padding.toFloat(), customerY.toFloat(), leftTextMaxWidth, maxLines = 2, textSize = 28f, lineSpacingExtra = 4f) + } + + return bitmap + } suspend fun printOrderLabel( printerManager: PrinterManager, @@ -45,20 +177,22 @@ object PrintHelper { suspend fun printWorkOrderLabel( printerManager: PrinterManager, workOrder: WorkOrder, + labelWidthMm: Int = 53, + labelHeightMm: Int = 40, blackMarkEnabled: Boolean = true, - unwindMm: Int = 40 + unwindMm: Int = 40, + printRetractMm: Int = 10 ): Result = printMutex.withLock { withContext(Dispatchers.IO) { try { printerManager.initDevice().onFailure { return@withContext Result.failure(it) } - printerManager.setFontSize(24) - printerManager.setBold(true) - printerManager.addText("工单: ${workOrder.title ?: "-"}\n") - printerManager.setFontSize(20) - printerManager.setBold(false) - if (workOrder.id > 0) { - printerManager.addBarcode("WO-${workOrder.id}") + if (printRetractMm > 0) { + printerManager.setUnwindLength(mmToDots(printRetractMm)) } + val widthDots = mmToDots(labelWidthMm) + val heightDots = mmToDots(labelHeightMm) + val bitmap = buildWorkOrderLabelBitmap(workOrder, widthDots, heightDots) + printerManager.printImage(bitmap, 1) if (blackMarkEnabled) { printerManager.addLineFeed(2) printerManager.enableMarkDetection(true) @@ -72,24 +206,109 @@ object PrintHelper { } } + private fun buildItemLabelBitmap(item: Item, widthDots: Int, heightDots: Int): Bitmap { + val bitmap = Bitmap.createBitmap(widthDots, heightDots, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + canvas.drawColor(Color.WHITE) + + val padding = 12 + val barcodeReserve = if (!item.serial_number.isNullOrBlank()) 70 else 0 + val dividerX = (widthDots * 0.48).toInt() + val leftInnerWidth = dividerX - padding * 2 + val qrSize = (leftInnerWidth * 0.95).toInt().coerceAtLeast(140) + val qrX = (dividerX - qrSize) / 2 + val qrY = padding + + val qrContent = buildQrContent("item", item.id) + val qrBitmap = generateQRBitmap(qrContent, qrSize) + canvas.drawBitmap(qrBitmap, qrX.toFloat(), qrY.toFloat(), null) + + val sepPaint = Paint().apply { + color = Color.BLACK + strokeWidth = 1.5f + isAntiAlias = true + } + + val textX = (dividerX + padding).toFloat() + val textMaxWidth = (widthDots - dividerX - padding * 2).toFloat() + var textY = (padding - 2).toFloat() + val maxTextY = heightDots - padding - barcodeReserve + + textY = drawTextBlock(canvas, "", "物品:${item.id}", textX, textY, textMaxWidth, textSize = 32f, bold = true, lineSpacingExtra = 6f) + + if (textY < maxTextY) { + textY = drawTextBlock(canvas, "", "名称:${item.name ?: "-"}", textX, textY, textMaxWidth, maxLines = 3, textSize = 25f, lineSpacingExtra = 4f) + } + + if (textY < maxTextY && item.quantity != 1) { + textY = drawTextBlock(canvas, "", "数量:${item.quantity}", textX, textY, textMaxWidth, textSize = 25f, lineSpacingExtra = 4f) + } + + if (textY < maxTextY && !item.containerBreadcrumb.isNullOrBlank()) { + textY = drawTextBlock(canvas, "", "位置:${item.containerBreadcrumb}", textX, textY, textMaxWidth, maxLines = 4, textSize = 25f, lineSpacingExtra = 4f) + } + + if (textY < maxTextY && !item.linkedCustomers.isNullOrEmpty()) { + val customerNames = item.linkedCustomers + .mapNotNull { c -> + listOfNotNull(c.first_name, c.last_name) + .joinToString(" ") + .ifBlank { null } + } + .joinToString(", ") + textY = drawTextBlock(canvas, "", "客户:$customerNames", textX, textY, textMaxWidth, maxLines = 2, textSize = 25f, lineSpacingExtra = 4f) + } + + canvas.drawLine(dividerX.toFloat(), padding.toFloat(), dividerX.toFloat(), maxTextY.toFloat(), sepPaint) + + if (!item.serial_number.isNullOrBlank()) { + canvas.drawLine(padding.toFloat(), maxTextY.toFloat(), (widthDots - padding).toFloat(), maxTextY.toFloat(), sepPaint) + + val snTextY = maxTextY + 4 + val snTextHeight = drawTextBlock(canvas, "", item.serial_number ?: "", + padding.toFloat(), snTextY.toFloat(), + (widthDots - padding * 2).toFloat(), + maxLines = 1, textSize = 22f, lineSpacingExtra = 0f + ) - snTextY + + val barcodeWidth = widthDots - padding * 4 + val barcodeHeight = 35 + val barcodeX = (widthDots - barcodeWidth) / 2 + val barcodeY = snTextY + snTextHeight + 4 + + val code128 = Code128Writer() + val barcodeMatrix = code128.encode(item.serial_number, BarcodeFormat.CODE_128, barcodeWidth, barcodeHeight) + val barcodeBitmap = Bitmap.createBitmap(barcodeWidth, barcodeHeight, Bitmap.Config.ARGB_8888) + for (y in 0 until barcodeHeight) { + for (x in 0 until barcodeWidth) { + barcodeBitmap.setPixel(x, y, if (barcodeMatrix[x, y]) Color.BLACK else Color.WHITE) + } + } + canvas.drawBitmap(barcodeBitmap, barcodeX.toFloat(), barcodeY.toFloat(), null) + } + + return bitmap + } + suspend fun printItemLabel( printerManager: PrinterManager, item: Item, + labelWidthMm: Int = 53, + labelHeightMm: Int = 40, blackMarkEnabled: Boolean = true, - unwindMm: Int = 40 + unwindMm: Int = 40, + printRetractMm: Int = 10 ): Result = printMutex.withLock { withContext(Dispatchers.IO) { try { printerManager.initDevice().onFailure { return@withContext Result.failure(it) } - printerManager.setFontSize(24) - printerManager.setBold(true) - printerManager.addText("${item.name ?: "-"}\n") - printerManager.setFontSize(18) - printerManager.setBold(false) - item.serial_number?.let { printerManager.addText("编号: $it\n") } - if (item.id > 0) { - printerManager.addBarcode("ITEM-${item.id}") + if (printRetractMm > 0) { + printerManager.setUnwindLength(mmToDots(printRetractMm)) } + val widthDots = mmToDots(labelWidthMm) + val heightDots = mmToDots(labelHeightMm) + val bitmap = buildItemLabelBitmap(item, widthDots, heightDots) + printerManager.printImage(bitmap, 1) if (blackMarkEnabled) { printerManager.addLineFeed(2) printerManager.enableMarkDetection(true) diff --git a/app/src/main/java/com/example/ops_android/ui/settings/SettingsScreen.kt b/app/src/main/java/com/example/ops_android/ui/settings/SettingsScreen.kt index feb90d9..c7765c2 100644 --- a/app/src/main/java/com/example/ops_android/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/example/ops_android/ui/settings/SettingsScreen.kt @@ -148,6 +148,49 @@ fun SettingsScreen( HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + Text("标签尺寸", style = MaterialTheme.typography.titleMedium) + + OutlinedTextField( + value = uiState.labelWidthMm, + onValueChange = viewModel::onLabelWidthChange, + label = { Text("标签宽度 (mm)") }, + suffix = { Text("mm") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = uiState.labelHeightMm, + onValueChange = viewModel::onLabelHeightChange, + label = { Text("标签高度 (mm)") }, + suffix = { Text("mm") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + + Button(onClick = { viewModel.saveLabelSize() }, modifier = Modifier.fillMaxWidth()) { + Text("保存标签尺寸") + } + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + + Text("打印校准", style = MaterialTheme.typography.titleMedium) + + OutlinedTextField( + value = uiState.printRetractMm, + onValueChange = viewModel::onPrintRetractMmChange, + label = { Text("打印前回退长度 (mm)") }, + suffix = { Text("mm") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + + Button(onClick = { viewModel.savePrintRetractMm() }, modifier = Modifier.fillMaxWidth()) { + Text("保存回退长度") + } + + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + Text( text = "关于", style = MaterialTheme.typography.titleMedium diff --git a/app/src/main/java/com/example/ops_android/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/example/ops_android/ui/settings/SettingsViewModel.kt index 38a35a3..a1d6c6f 100644 --- a/app/src/main/java/com/example/ops_android/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/example/ops_android/ui/settings/SettingsViewModel.kt @@ -25,7 +25,10 @@ data class SettingsUiState( val testSuccess: Boolean = false, val message: String? = null, val blackMarkEnabled: Boolean = true, - val unwindMm: String = "40" + val unwindMm: String = "40", + val labelWidthMm: String = "53", + val labelHeightMm: String = "40", + val printRetractMm: String = "10" ) class SettingsViewModel( @@ -56,7 +59,10 @@ class SettingsViewModel( private fun loadPrinterSettings() { _uiState.value = _uiState.value.copy( blackMarkEnabled = sessionManager.cachedBlackMarkEnabled, - unwindMm = sessionManager.cachedUnwindMm.toString() + unwindMm = sessionManager.cachedUnwindMm.toString(), + labelWidthMm = sessionManager.cachedLabelWidthMm.toString(), + labelHeightMm = sessionManager.cachedLabelHeightMm.toString(), + printRetractMm = sessionManager.cachedPrintRetractMm.toString() ) } @@ -83,6 +89,42 @@ class SettingsViewModel( } } + fun onLabelWidthChange(value: String) { + if (value.isEmpty() || value.all { it.isDigit() } && value.toIntOrNull()?.let { it in 10..200 } == true) { + _uiState.value = _uiState.value.copy(labelWidthMm = value) + } + } + + fun onLabelHeightChange(value: String) { + if (value.isEmpty() || value.all { it.isDigit() } && value.toIntOrNull()?.let { it in 10..200 } == true) { + _uiState.value = _uiState.value.copy(labelHeightMm = value) + } + } + + fun saveLabelSize() { + val width = _uiState.value.labelWidthMm.toIntOrNull() ?: 53 + val height = _uiState.value.labelHeightMm.toIntOrNull() ?: 40 + viewModelScope.launch { + sessionManager.setLabelWidthMm(width) + sessionManager.setLabelHeightMm(height) + _uiState.value = _uiState.value.copy(message = "标签尺寸已保存") + } + } + + fun onPrintRetractMmChange(value: String) { + if (value.isEmpty() || value.all { it.isDigit() } && value.toIntOrNull()?.let { it in 0..50 } == true) { + _uiState.value = _uiState.value.copy(printRetractMm = value) + } + } + + fun savePrintRetractMm() { + val mm = _uiState.value.printRetractMm.toIntOrNull() ?: 10 + viewModelScope.launch { + sessionManager.setPrintRetractMm(mm) + _uiState.value = _uiState.value.copy(message = "回退长度已保存") + } + } + fun saveUrl() { viewModelScope.launch { val url = _uiState.value.apiBaseUrl.trim() diff --git a/app/src/main/java/com/example/ops_android/ui/warehouse/ItemDetailScreen.kt b/app/src/main/java/com/example/ops_android/ui/warehouse/ItemDetailScreen.kt index a1bdc27..ddc6631 100644 --- a/app/src/main/java/com/example/ops_android/ui/warehouse/ItemDetailScreen.kt +++ b/app/src/main/java/com/example/ops_android/ui/warehouse/ItemDetailScreen.kt @@ -129,9 +129,7 @@ fun ItemDetailScreen(id: Int, onBack: () -> Unit, onCreateWorkOrder: (Int) -> Un val printerManager = app.printerManager if (printerManager != null) { IconButton(onClick = { - scope.launch { - PrintHelper.printItemLabel(printerManager, itm, app.sessionManager.cachedBlackMarkEnabled, app.sessionManager.cachedUnwindMm) - } + scope.launch { PrintHelper.printItemLabel(printerManager, itm, labelWidthMm = app.sessionManager.cachedLabelWidthMm, labelHeightMm = app.sessionManager.cachedLabelHeightMm, blackMarkEnabled = app.sessionManager.cachedBlackMarkEnabled, unwindMm = app.sessionManager.cachedUnwindMm, printRetractMm = app.sessionManager.cachedPrintRetractMm) } }) { Icon(Icons.Default.Print, "打印", tint = Color.White) } diff --git a/app/src/main/java/com/example/ops_android/ui/workorder/WorkOrderDetailScreen.kt b/app/src/main/java/com/example/ops_android/ui/workorder/WorkOrderDetailScreen.kt index 2f5909e..28415d2 100644 --- a/app/src/main/java/com/example/ops_android/ui/workorder/WorkOrderDetailScreen.kt +++ b/app/src/main/java/com/example/ops_android/ui/workorder/WorkOrderDetailScreen.kt @@ -69,7 +69,7 @@ fun WorkOrderDetailScreen(id: Int, onBack: () -> Unit) { if (printerManager != null) { val sm = com.example.ops_android.ui.LocalAppContainer.current.sessionManager IconButton(onClick = { - scope.launch { com.example.ops_android.printer.PrintHelper.printWorkOrderLabel(printerManager, wo_, sm.cachedBlackMarkEnabled, sm.cachedUnwindMm) } + scope.launch { com.example.ops_android.printer.PrintHelper.printWorkOrderLabel(printerManager, wo_, labelWidthMm = sm.cachedLabelWidthMm, labelHeightMm = sm.cachedLabelHeightMm, blackMarkEnabled = sm.cachedBlackMarkEnabled, unwindMm = sm.cachedUnwindMm, printRetractMm = sm.cachedPrintRetractMm) } }) { Icon(Icons.Default.Print, "打印", tint = Color.White) } } }