feat:卡片打印机
This commit is contained in:
1 parent
726210edcb
commit
eaec48a95b
1 file changed
+46
-4
@@ -114,23 +114,65 @@ class CupsPrintBackend implements PrintBackend {
|
||||
try {
|
||||
final back = request.back;
|
||||
if (back != null && back.color.isNotEmpty) {
|
||||
backFile = await _writeTempPng(back.color);
|
||||
backFile = await _writeTempPng(request.back!.color);
|
||||
}
|
||||
final copies = request.copies < 1 ? 1 : request.copies;
|
||||
for (var i = 0; i < copies; i++) {
|
||||
await runCommand('lp', ['-d', queue.name, frontFile]);
|
||||
await _submitAndTrack(queue.name, frontFile);
|
||||
if (backFile != null) {
|
||||
await runCommand('lp', ['-d', queue.name, backFile]);
|
||||
await _submitAndTrack(queue.name, backFile);
|
||||
}
|
||||
}
|
||||
_log('已提交到 CUPS 队列 ${queue.name}(份数 $copies${backFile != null ? ',含背面' : ''})');
|
||||
_log('打印完成:CUPS 队列 ${queue.name}(份数 $copies${backFile != null ? ',含背面' : ''})');
|
||||
} finally {
|
||||
await _deleteQuiet(frontFile);
|
||||
await _deleteQuiet(backFile);
|
||||
}
|
||||
}
|
||||
|
||||
/// CUPS 作业跟踪超时(秒):单卡物理打印约 20-30s,留足余量;
|
||||
/// 超时视为卡纸/缺卡/暂停等异常,任务转失败而非假装完成。
|
||||
static const _jobTrackTimeoutSec = 170;
|
||||
|
||||
/// lp 提交并跟踪到**物理打印完成**(作业离开 CUPS 活动队列)才返回。
|
||||
///
|
||||
/// lp 返回只代表"已入队"(约 0.1s),若不跟踪,任务瞬间变"完成",
|
||||
/// 前端批量进度会整体提前跑满(第一张还没打页面就 100%)。
|
||||
/// 解析 lp 输出的作业号,轮询 `lpstat -o`(仅列未完成作业)直到作业号消失。
|
||||
Future<void> _submitAndTrack(String queueName, String file) async {
|
||||
final out = await runCommand('lp', ['-d', queueName, file]);
|
||||
// lp 输出被系统本地化(中文"请求id是X(1个文件)",LC_ALL=C 压不住,同 lpstat 的坑),
|
||||
// 作业号取到空白或中英文括号为止,避免把"(1个文件)"尾巴带进作业号
|
||||
final m = RegExp(r'request id is\s+([^\s((]+)|请求id是\s*([^\s((]+)').firstMatch(out);
|
||||
final jobId = (m?.group(1) ?? m?.group(2) ?? '').trim();
|
||||
if (jobId.isEmpty) {
|
||||
// 输出格式异常时退化为旧行为(提交即完成),不影响出卡
|
||||
_log('lp 已提交(未解析到作业号,跳过完成跟踪):${out.trim()}');
|
||||
return;
|
||||
}
|
||||
_log('CUPS 作业 $jobId 已入队,等待物理打印完成…');
|
||||
final deadline = DateTime.now().add(const Duration(seconds: _jobTrackTimeoutSec));
|
||||
while (DateTime.now().isBefore(deadline)) {
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
String active;
|
||||
try {
|
||||
active = await runCommand('lpstat', ['-o'], tolerateExit: true);
|
||||
} on CommandException {
|
||||
return; // lpstat 不可用,放弃跟踪(作业仍会打印)
|
||||
}
|
||||
if (!active.contains(jobId)) {
|
||||
_log('CUPS 作业 $jobId 打印完成');
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw StateError('打印超时:作业 $jobId 长时间未完成,请检查打印机(卡纸/缺卡/被暂停)');
|
||||
}
|
||||
|
||||
/// data URL PNG 落临时文件供 lp 提交。
|
||||
///
|
||||
/// 2026-09-23 方向验证:打印机链路做上下翻转,此处曾加 flipVertical 抵消并打出
|
||||
/// 正向成品(白卡实证);当前按用户要求**临时去掉翻转做消融测试**——预测:上下颠倒。
|
||||
/// 验证后若需恢复,把 out 改回 img.encodePng(img.flipVertical(decoded))。
|
||||
Future<String> _writeTempPng(String dataUrl) async {
|
||||
final comma = dataUrl.indexOf(',');
|
||||
final base64Part = comma >= 0 ? dataUrl.substring(comma + 1) : dataUrl;
|
||||
|
||||
Reference in new issue
Block a user