feat:查询菜谱适配小键盘硬件

This commit is contained in:
dzc committed 2026-09-20 11:43:33 +08:00
1 parent a179729e79
commit 8e9bcba3c3
11 files changed
+861 -7

No files matched your search

@@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:json_annotation/json_annotation.dart';
import '../api/hard_api_models.dart';
@@ -92,6 +93,103 @@ enum ReservationVerificationDateScope {
final String title;
}
/// 查询菜单外接快捷小键盘上可绑定的动作。
///
/// [code] 会保存到终端设置中,必须保持稳定;动作语义与查询菜单页面操作一一对应。
enum MenuQueryShortcutAction {
/// 取消当前识别流程并清空查询结果。
cancel('cancel', '取消'),
/// 对当前识别出的用户提交核销。
verification('verification', '核销'),
/// 切换到人脸识别并立即启动识别。
faceRecognition('face', '人脸识别'),
/// 切换到饭卡识别并立即启动识别。
cardRecognition('card', '饭卡识别'),
/// 重新打印最近一次小票。
reprint('reprint', '重新打印'),
;
/// 创建与稳定编码绑定的快捷动作。
const MenuQueryShortcutAction(this.code, this.title);
/// 终端设置中持久化的稳定动作编码。
final String code;
/// 设置页展示、查询菜单运行时复用的动作名称。
final String title;
}
/// 单个快捷动作与物理按键的绑定关系,保存在本机 sysJson 中,不涉及数据库结构。
@immutable
@JsonSerializable()
class MenuQueryShortcutKeyBinding {
/// 创建按键绑定;设备未提供物理键位时 [usbHidUsage] 允许为 0,仅以逻辑键兜底匹配。
const MenuQueryShortcutKeyBinding({
this.usbHidUsage = 0,
this.logicalKeyId = 0,
});
/// 从真实硬件键盘事件提取绑定;物理键位是首选匹配依据,逻辑键仅兜底。
factory MenuQueryShortcutKeyBinding.fromKeyEvent(KeyEvent event) {
return MenuQueryShortcutKeyBinding(
usbHidUsage: event.physicalKey.usbHidUsage,
logicalKeyId: event.logicalKey.keyId,
);
}
factory MenuQueryShortcutKeyBinding.fromJson(Map<String, dynamic> json) =>
_$MenuQueryShortcutKeyBindingFromJson(json);
/// 按键的 USB HID 物理键位;同一物理按键不随键盘布局变化,0 表示设备未提供。
final int usbHidUsage;
/// 按键的逻辑键 ID;仅作为物理键位缺失时的兜底匹配与展示依据,0 表示未记录。
final int logicalKeyId;
Map<String, dynamic> toJson() => _$MenuQueryShortcutKeyBindingToJson(this);
/// 判断两个绑定是否指向同一个按键。
///
/// 物理或逻辑键任一相同即视为冲突:物理键相同说明是同一个物理按键;
/// 物理键不同但逻辑键相同时,运行期匹配存在歧义风险,同样不允许重复绑定。
bool conflictsWith(MenuQueryShortcutKeyBinding other) {
if (usbHidUsage != 0 &&
other.usbHidUsage != 0 &&
usbHidUsage == other.usbHidUsage) {
return true;
}
return logicalKeyId != 0 &&
other.logicalKeyId != 0 &&
logicalKeyId == other.logicalKeyId;
}
/// 展示按键的可读名称;优先物理键名,其次逻辑键名,均缺失时回退到键值,便于现场排查。
String get displayLabel {
final physicalName =
PhysicalKeyboardKey.findKeyByCode(usbHidUsage)?.debugName;
if (physicalName != null && physicalName.isNotEmpty) return physicalName;
final logicalName =
LogicalKeyboardKey.findKeyByKeyId(logicalKeyId)?.debugName;
if (logicalName != null && logicalName.isNotEmpty) return logicalName;
return '键码 0x${usbHidUsage.toRadixString(16).padLeft(4, '0').toUpperCase()}';
}
/// 按全部持久化字段比较,用于恢复配置与测试断言。
@override
bool operator ==(Object other) =>
other is MenuQueryShortcutKeyBinding &&
other.usbHidUsage == usbHidUsage &&
other.logicalKeyId == logicalKeyId;
/// 与相等判断字段一致的哈希值。
@override
int get hashCode => Object.hash(usbHidUsage, logicalKeyId);
}
/// 查询菜单在本机保存的页面交互偏好,不改变预约接口和硬件协议。
@immutable
class ReservationQueryPagePreferences {
@@ -107,6 +205,7 @@ class ReservationQueryPagePreferences {
this.allowManualMealPeriodChange = true,
this.selectedMealSlot = ReservationMealSlot.morning,
this.verificationDateScope = ReservationVerificationDateScope.today,
this.shortcutKeyBindings = const {},
});
/// 未保存页面设置时使用的默认查询偏好。
@@ -121,6 +220,7 @@ class ReservationQueryPagePreferences {
allowManualMealPeriodChange: true,
selectedMealSlot: ReservationMealSlot.morning,
verificationDateScope: ReservationVerificationDateScope.today,
shortcutKeyBindings: {},
);
/// 查询方式的页面展示顺序。
@@ -154,6 +254,10 @@ class ReservationQueryPagePreferences {
/// 允许显示核销入口的预约日期范围。
final ReservationVerificationDateScope verificationDateScope;
/// 外接快捷小键盘的动作按键绑定;键为动作,值为绑定的物理按键,未配置时为空集合。
final Map<MenuQueryShortcutAction, MenuQueryShortcutKeyBinding>
shortcutKeyBindings;
}
/// 预约订单中的套餐组成展示模型,统一处理缺失名称和异常份数。
@@ -6,6 +6,20 @@ part of 'reservation_menu_page_models.dart';
// JsonSerializableGenerator
// **************************************************************************
MenuQueryShortcutKeyBinding _$MenuQueryShortcutKeyBindingFromJson(
Map<String, dynamic> json) =>
MenuQueryShortcutKeyBinding(
usbHidUsage: (json['usbHidUsage'] as num?)?.toInt() ?? 0,
logicalKeyId: (json['logicalKeyId'] as num?)?.toInt() ?? 0,
);
Map<String, dynamic> _$MenuQueryShortcutKeyBindingToJson(
MenuQueryShortcutKeyBinding instance) =>
<String, dynamic>{
'usbHidUsage': instance.usbHidUsage,
'logicalKeyId': instance.logicalKeyId,
};
ReservationMenuMealData _$ReservationMenuMealDataFromJson(
Map<String, dynamic> json) =>
ReservationMenuMealData(