feat: 饭卡打印助手 Flutter 版(Phase 1/2:协议服务器 + macOS CUPS 真机打印)

- 协议与 C# 版 card-printer-agent 逐字段对齐(38200-38202 顺延、CORS *、串行任务队列、30 分钟保留)
- macOS CUPS 后端:lpoptions 队列发现 + lp 参数列表提交(LC_ALL=C 防本地化粘连坑)
- --mock 模拟模式 + bin/agent.dart headless 入口 + 测试卡生成工具
- 真机验收:HiTi CS-200e status 就绪 + 真实出卡;macOS 通用二进制构建通过
- App Sandbox 已关闭(服务需绑回环端口并调用 lp 子进程);zaxprint URL scheme 已注册
This commit is contained in:
dzc committed 2026-09-21 12:03:27 +08:00
commit 689a07ca44
46 files changed
+2986

No files matched your search

+134
View File
@@ -0,0 +1,134 @@
/// 与前端 `biz-pc-cloud-zax/src/api/plugin/cardPrinter.ts` 约定的协议 DTO。
///
/// 字段序列化一律 camelCase,响应统一信封 `{code, msg, data}`(code=0 成功),
/// 与 C# 版 card-printer-agent 的 ApiModels.cs 逐字段对齐。
library;
class ManifestPlugin {
ManifestPlugin({this.id = 'card-printer', this.name = '卡片打印机', this.version = '1.0.0'});
final String id;
final String name;
final String version;
Map<String, dynamic> toJson() => {'id': id, 'name': name, 'version': version};
}
class RibbonInfo {
RibbonInfo({this.type = '', this.remain = -1, this.remainCount = -1});
/// 色带类型名(如 YMCKO),未知空串
String type;
/// 色带余量百分比 0~100,未知 -1(按常见 100 张装色带折算)
int remain;
/// 色带剩余张数(设备原始值),未知 -1
int remainCount;
Map<String, dynamic> toJson() => {'type': type, 'remain': remain, 'remainCount': remainCount};
}
/// GET /api/card-printer/status 响应
class PrinterStatus {
PrinterStatus({
this.online = false,
this.model = '',
this.firmware = '',
this.connection = 'USB',
RibbonInfo? ribbon,
this.cardLow = false,
this.cardLeft = -1,
this.status = 0,
this.statusText = '',
}) : ribbon = ribbon ?? RibbonInfo();
bool online;
String model;
String firmware;
/// USB / IP
String connection;
RibbonInfo ribbon;
/// 卡量是否不足
bool cardLow;
/// 剩余卡量(张),未知 -1
int cardLeft;
/// HiTi 设备状态码,0 = 就绪
int status;
String statusText;
/// 打印机已断开/未连接的通用离线态
factory PrinterStatus.offline(String statusText) => PrinterStatus(
online: false,
status: 0x00000080,
statusText: statusText,
);
Map<String, dynamic> toJson() => {
'online': online,
'model': model,
'firmware': firmware,
'connection': connection,
'ribbon': ribbon.toJson(),
'cardLow': cardLow,
'cardLeft': cardLeft,
'status': status,
'statusText': statusText,
};
}
/// 单面打印图:color 必传,k 为可选树脂黑文字层
class CardSideImage {
CardSideImage({required this.color, this.k});
/// 1014×642 彩色层 PNG data URL
final String color;
/// 8bit 灰度树脂 K 文字层 PNG data URL,白底黑字
final String? k;
factory CardSideImage.fromJson(Map<String, dynamic> json) => CardSideImage(
color: json['color'] as String? ?? '',
k: json['k'] as String?,
);
}
/// POST /api/card-printer/print 请求
class PrintRequest {
PrintRequest({this.copies = 1, this.front, this.back});
final int copies;
final CardSideImage? front;
final CardSideImage? back;
factory PrintRequest.fromJson(Map<String, dynamic> json) {
CardSideImage? side(Object? raw) =>
raw is Map<String, dynamic> ? CardSideImage.fromJson(raw) : null;
return PrintRequest(
copies: (json['copies'] as num?)?.toInt() ?? 1,
front: side(json['front']),
back: side(json['back']),
);
}
}
/// GET /api/card-printer/task/{taskId} 响应
class PrintTaskDto {
PrintTaskDto({required this.taskId, this.state = 'printing', this.status = 0, this.statusText = ''});
final String taskId;
/// printing / done / error
final String state;
final int status;
/// error 态时携带错误文案
final String statusText;
Map<String, dynamic> toJson() =>
{'taskId': taskId, 'state': state, 'status': status, 'statusText': statusText};
}