
背景
在物流领域中,"把枪" 通常指手持终端设备(Handheld Terminal) ,也被称为 "物流扫码枪""数据采集器" 等。最近做的物流项目,把枪上的应用也是必要环节。
"把枪" 对物流行业的价值
- 提升效率:替代人工录入,单票货物信息处理时间从分钟级缩短至秒级,分拣效率提升 50% 以上。
- 降低误差:条码扫描准确率接近 100%,减少人工记录导致的错单、漏单问题。
- 数据可视化:实时数据同步帮助企业监控物流全流程,优化库存管理、运输路线等。
- 成本控制:减少人力投入(如仓储盘点人员可减少 30%-50%),长期降低运营成本。
Flutter业务项目
把枪终端 现在基本外观已经类手机化,能承载更为复杂的功能。Flutter上开发基本和普通APP开发一致,只是业务重点全部在 "扫码" 业务。如下图,支持了扫码上车、卸车、揽货、共享。
终端按钮触发扫码动作,扫码运单号后直接发接口对接业务,为了避免扫码遗漏,需要增加清晰的反馈音和记录。

记录
扫码记录直接存在终端存储里,使用SharedPreferences
管理存储。当然也可以持久化在数据中进行管理,目前业务上把枪操作员的把枪隶属个人,数据不做采集监控。
js
// 初始化
void _initErrorScanResult() async {
SharedPreferences _prefs = await SharedPreferences.getInstance();
String loadErrorStr = _prefs.get('loadResult') != null ? _prefs.get('loadResult').toString() : '[]';
var loadErrors = json.decode(loadErrorStr);
List<ScanResData> _allRecords = (loadErrors as List<dynamic>).map((e) => ScanResData.fromJson(e)).toList();
setState(() {
_successRecords = _allRecords.where((v) => v.status == 'success').toList();
_errRecords = _allRecords.where((v) => v.status == 'error').toList();
});
}
// 移除
void _remove(String target, String status, int index) async {
setState(() {
if (status == 'error') {
_errRecords = _errRecords.asMap().entries.where((indexValue) => indexValue.key != index).map((v) => v.value).toList();
} else {
_successRecords = _successRecords.asMap().entries.where((indexValue) => indexValue.key != index).map((v) => v.value).toList();
}
_reset();
});
}
// 重置
Future<void> _reset() async {
SharedPreferences _prefs = await SharedPreferences.getInstance();
List<ScanResData> _mergeRecords = [];
_mergeRecords.addAll(_errRecords);
_mergeRecords.addAll(_successRecords);
_prefs.setString('unloadResult', json.encode(_mergeRecords));
}
扫码防干扰
如下图物流包裹上贴的快递单,在扫码时上面的一维码以及下面的二维码都会触发把枪扫描成功,因为扫码距离的原因,有一定概率扫上二维码,影响操作效率。在事件回调中排除掉二维码链接。
js
@override
void onEvent(Object event) {
G.print.info(event.toString(), '扫码结果');
String? code = event as String;
if (Utils.isLink(code)) return;
if (code.isNotEmpty && !_saving) {
setState(() {
formData.waybillNo = code;
formData.waybillNoList = [code];
_save();
});
} else {}
}

扫码插件
扫码插件使用的是pda_scanner 0.2.9
,This is a scanning plug-in for PDA to listen the scanned events and get scanning results.
pda_scanner
插件是用于 Flutter 应用与 PDA(掌上电脑)扫码设备对接的中间件。它通过特定的通信机制实现应用与硬件设备的交互。因为flutter升级缘故,拉到本地进行管理。
js
pda_scanner:
path: ../packages/logistic-pda-plugin
使用也很简单
1. with PdaListenerMixin<PdaScanLoad>
的作用
Mixin 混入机制
-
概念:Mixin 是 Dart 中的一种代码复用方式,允许类在不继承的情况下使用其他类的方法和属性。
-
语法 :通过
with
关键字将 Mixin 添加到类中。 -
示例代码分析:
dart
scalaclass _PdaScanLoadState extends State<PdaScanLoad> with PdaListenerMixin<PdaScanLoad> { // ... }
_PdaScanLoadState
类继承自State<PdaScanLoad>
,同时混入了PdaListenerMixin
。PdaListenerMixin<PdaScanLoad>
是一个泛型 Mixin,它提供了监听 PDA 扫描设备的功能(如扫码回调、错误处理)。- 优势 :无需继承复杂的类层次结构,直接复用
PdaListenerMixin
的功能。
2. @override
注解的作用
方法重写(Override)
-
概念:子类重新实现父类或 Mixin 中已有的方法。
-
语法 :使用
@override
注解标记重写的方法(非强制,但推荐)。 -
示例代码分析:
dart
less@override void onEvent(Object event) { // 处理扫码事件 } @override void onError(Object error) { // 处理扫码异常 }
onEvent
和onError
是PdaListenerMixin
中定义的抽象或具体方法。@override
明确告诉编译器:这个方法是重写父类或 Mixin 的方法,避免拼写错误。
3. 结合场景:PDA 扫码功能
-
Mixin 的功能 :
PdaListenerMixin
提供了监听 PDA 设备的能力,可能包含:- 初始化 PDA 设备连接
- 接收扫码事件的回调机制
- 错误处理逻辑
-
重写的目的:
onEvent
:处理扫码结果(如保存运单号)。onError
:处理扫码异常(如播放提示音)。
js
import 'package:pda_scanner/pda_listener_mixin.dart';
class _PdaScanLoadState extends State<PdaScanLoad> with PdaListenerMixin<PdaScanLoad> {
@override
void onEvent(Object event) {
G.print.info(event.toString(), '扫码结果');
String? code = event as String;
if (Utils.isLink(code)) return;
if (code.isNotEmpty && !_saving) {
setState(() {
formData.waybillNo = code;
formData.waybillNoList = [code];
_save();
});
} else {}
}
@override
void onError(Object error) {
G.print.info(error.toString(), '扫码异常日志');
Utils.speakToast('扫码异常');
}
}
后记
刚开始接触时,因为是新型终端的缘故,心理上还是比较有压力。实际接触扫码枪实物并使用后,发现在场景相对单一的终端上难度很小。因为扫码机型号的统一装配使用,也没有各类型手机应用带来的诸多兼容性问题。扫码机相对比较有质感,看起来很皮实,虽然基本功能类手机,不过应用做的越简单越好。