feat: 重构工单和物品打印模板为左右排布,支持二维码和条码

- 工单模板:左侧二维码+客户,右侧工单ID/标题/描述
- 物品模板:左侧二维码,右侧物品ID/名称/数量/位置/客户,底部条码+序列号
- 打印标签设置:新增标签宽度/高度/打印前回退长度设置
- QR码JSON增加type=ops_sys字段区分系统来源
- WorkOrder/Item模型增加linkedCustomers等关联数据
- 使用Canvas绘制Bitmap实现自定义打印布局
This commit is contained in:
2026-06-26 19:52:52 +08:00
parent ae07e12200
commit 6bc653bb74
10 changed files with 377 additions and 27 deletions
@@ -28,6 +28,9 @@ class SessionManager(private val context: Context) {
private val KEY_COOKIE_REMEMBER = booleanPreferencesKey("cookie_remember") private val KEY_COOKIE_REMEMBER = booleanPreferencesKey("cookie_remember")
private val KEY_PRINTER_BLACK_MARK = booleanPreferencesKey("printer_black_mark") private val KEY_PRINTER_BLACK_MARK = booleanPreferencesKey("printer_black_mark")
private val KEY_PRINTER_UNWIND_MM = intPreferencesKey("printer_unwind_mm") 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 @Volatile
@@ -46,6 +49,18 @@ class SessionManager(private val context: Context) {
var cachedUnwindMm: Int = 40 var cachedUnwindMm: Int = 40
private set 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() { fun clearCookieCache() {
cachedCookieValue = "" cachedCookieValue = ""
} }
@@ -59,6 +74,9 @@ class SessionManager(private val context: Context) {
cachedCookieValue = prefs[KEY_COOKIE_VALUE] ?: "" cachedCookieValue = prefs[KEY_COOKIE_VALUE] ?: ""
cachedBlackMarkEnabled = prefs[KEY_PRINTER_BLACK_MARK] ?: true cachedBlackMarkEnabled = prefs[KEY_PRINTER_BLACK_MARK] ?: true
cachedUnwindMm = prefs[KEY_PRINTER_UNWIND_MM] ?: 40 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 { suspend fun getApiBaseUrl(): String = cachedBaseUrl.ifEmpty {
@@ -115,4 +133,19 @@ class SessionManager(private val context: Context) {
cachedUnwindMm = mm cachedUnwindMm = mm
context.dataStore.edit { it[KEY_PRINTER_UNWIND_MM] = 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 }
}
} }
@@ -1,5 +1,6 @@
package com.example.ops_android.data.model package com.example.ops_android.data.model
import com.example.ops_android.data.remote.api.LinkedCustomerInfo
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class Item( data class Item(
@@ -14,7 +15,10 @@ data class Item(
@SerializedName("CreatorID") val user_id: Int? = null, @SerializedName("CreatorID") val user_id: Int? = null,
@SerializedName("barcode") private val _barcode: String? = null, @SerializedName("barcode") private val _barcode: String? = null,
@SerializedName("spec") private val _spec: String? = null, @SerializedName("spec") private val _spec: String? = null,
@SerializedName("move_history") private val _move_history: List<MoveHistory>? = null @SerializedName("move_history") private val _move_history: List<MoveHistory>? = null,
// Filled from detail API
@kotlin.jvm.Transient val linkedCustomers: List<LinkedCustomerInfo>? = null,
@kotlin.jvm.Transient val containerBreadcrumb: String? = null
) { ) {
val barcode: String? get() = _barcode val barcode: String? get() = _barcode
val spec: String? get() = _spec val spec: String? get() = _spec
@@ -1,5 +1,6 @@
package com.example.ops_android.data.model package com.example.ops_android.data.model
import com.example.ops_android.data.remote.api.LinkedCustomer
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class WorkOrder( data class WorkOrder(
@@ -11,7 +12,8 @@ data class WorkOrder(
@SerializedName("UpdatedAt") val updated_at: String? = null, @SerializedName("UpdatedAt") val updated_at: String? = null,
@SerializedName("UserID") val user_id: Int? = null, @SerializedName("UserID") val user_id: Int? = null,
// Filled from detail API // Filled from detail API
@kotlin.jvm.Transient val commits: List<Commit>? = null @kotlin.jvm.Transient val commits: List<Commit>? = null,
@kotlin.jvm.Transient val linkedCustomers: List<LinkedCustomer>? = null
) )
data class WorkOrderCommit( data class WorkOrderCommit(
@@ -101,7 +101,12 @@ class WarehouseRepository(
return try { return try {
val response = warehouseApi.getItem(GetItemRequest(id)) val response = warehouseApi.getItem(GetItemRequest(id))
if (response.errCode == 0 && response.data != null) { 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 { } else {
Result.failure(Exception("获取物品详情失败")) Result.failure(Exception("获取物品详情失败"))
} }
@@ -29,7 +29,11 @@ class WorkOrderRepository(
return try { return try {
val response = workOrderApi.get(WorkOrderGetRequest(id)) val response = workOrderApi.get(WorkOrderGetRequest(id))
if (response.errCode == 0 && response.data?.order != null) { 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 { } else {
Result.failure(Exception("获取工单详情失败")) Result.failure(Exception("获取工单详情失败"))
} }
@@ -1,16 +1,148 @@
package com.example.ops_android.printer 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.Item
import com.example.ops_android.data.model.PurchaseOrder import com.example.ops_android.data.model.PurchaseOrder
import com.example.ops_android.data.model.WorkOrder 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.Dispatchers
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object PrintHelper { object PrintHelper {
private val printMutex = Mutex() 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( suspend fun printOrderLabel(
printerManager: PrinterManager, printerManager: PrinterManager,
@@ -45,20 +177,22 @@ object PrintHelper {
suspend fun printWorkOrderLabel( suspend fun printWorkOrderLabel(
printerManager: PrinterManager, printerManager: PrinterManager,
workOrder: WorkOrder, workOrder: WorkOrder,
labelWidthMm: Int = 53,
labelHeightMm: Int = 40,
blackMarkEnabled: Boolean = true, blackMarkEnabled: Boolean = true,
unwindMm: Int = 40 unwindMm: Int = 40,
printRetractMm: Int = 10
): Result<Unit> = printMutex.withLock { ): Result<Unit> = printMutex.withLock {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
printerManager.initDevice().onFailure { return@withContext Result.failure(it) } printerManager.initDevice().onFailure { return@withContext Result.failure(it) }
printerManager.setFontSize(24) if (printRetractMm > 0) {
printerManager.setBold(true) printerManager.setUnwindLength(mmToDots(printRetractMm))
printerManager.addText("工单: ${workOrder.title ?: "-"}\n")
printerManager.setFontSize(20)
printerManager.setBold(false)
if (workOrder.id > 0) {
printerManager.addBarcode("WO-${workOrder.id}")
} }
val widthDots = mmToDots(labelWidthMm)
val heightDots = mmToDots(labelHeightMm)
val bitmap = buildWorkOrderLabelBitmap(workOrder, widthDots, heightDots)
printerManager.printImage(bitmap, 1)
if (blackMarkEnabled) { if (blackMarkEnabled) {
printerManager.addLineFeed(2) printerManager.addLineFeed(2)
printerManager.enableMarkDetection(true) 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( suspend fun printItemLabel(
printerManager: PrinterManager, printerManager: PrinterManager,
item: Item, item: Item,
labelWidthMm: Int = 53,
labelHeightMm: Int = 40,
blackMarkEnabled: Boolean = true, blackMarkEnabled: Boolean = true,
unwindMm: Int = 40 unwindMm: Int = 40,
printRetractMm: Int = 10
): Result<Unit> = printMutex.withLock { ): Result<Unit> = printMutex.withLock {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
printerManager.initDevice().onFailure { return@withContext Result.failure(it) } printerManager.initDevice().onFailure { return@withContext Result.failure(it) }
printerManager.setFontSize(24) if (printRetractMm > 0) {
printerManager.setBold(true) printerManager.setUnwindLength(mmToDots(printRetractMm))
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}")
} }
val widthDots = mmToDots(labelWidthMm)
val heightDots = mmToDots(labelHeightMm)
val bitmap = buildItemLabelBitmap(item, widthDots, heightDots)
printerManager.printImage(bitmap, 1)
if (blackMarkEnabled) { if (blackMarkEnabled) {
printerManager.addLineFeed(2) printerManager.addLineFeed(2)
printerManager.enableMarkDetection(true) printerManager.enableMarkDetection(true)
@@ -148,6 +148,49 @@ fun SettingsScreen(
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) 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(
text = "关于", text = "关于",
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
@@ -25,7 +25,10 @@ data class SettingsUiState(
val testSuccess: Boolean = false, val testSuccess: Boolean = false,
val message: String? = null, val message: String? = null,
val blackMarkEnabled: Boolean = true, 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( class SettingsViewModel(
@@ -56,7 +59,10 @@ class SettingsViewModel(
private fun loadPrinterSettings() { private fun loadPrinterSettings() {
_uiState.value = _uiState.value.copy( _uiState.value = _uiState.value.copy(
blackMarkEnabled = sessionManager.cachedBlackMarkEnabled, 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() { fun saveUrl() {
viewModelScope.launch { viewModelScope.launch {
val url = _uiState.value.apiBaseUrl.trim() val url = _uiState.value.apiBaseUrl.trim()
@@ -129,9 +129,7 @@ fun ItemDetailScreen(id: Int, onBack: () -> Unit, onCreateWorkOrder: (Int) -> Un
val printerManager = app.printerManager val printerManager = app.printerManager
if (printerManager != null) { if (printerManager != null) {
IconButton(onClick = { IconButton(onClick = {
scope.launch { 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) }
PrintHelper.printItemLabel(printerManager, itm, app.sessionManager.cachedBlackMarkEnabled, app.sessionManager.cachedUnwindMm)
}
}) { }) {
Icon(Icons.Default.Print, "打印", tint = Color.White) Icon(Icons.Default.Print, "打印", tint = Color.White)
} }
@@ -69,7 +69,7 @@ fun WorkOrderDetailScreen(id: Int, onBack: () -> Unit) {
if (printerManager != null) { if (printerManager != null) {
val sm = com.example.ops_android.ui.LocalAppContainer.current.sessionManager val sm = com.example.ops_android.ui.LocalAppContainer.current.sessionManager
IconButton(onClick = { 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) } }) { Icon(Icons.Default.Print, "打印", tint = Color.White) }
} }
} }