|
@@ -0,0 +1,226 @@
|
|
|
|
|
+import 'package:chicken_farm/core/utils/logger.dart';
|
|
|
|
|
+import 'package:chicken_farm/core/services/win/win_reader_manager.dart';
|
|
|
|
|
+import 'package:chicken_farm/core/utils/toast.dart';
|
|
|
|
|
+import 'package:flutter/material.dart';
|
|
|
|
|
+import 'dart:async';
|
|
|
|
|
+
|
|
|
|
|
+/// Windows平台电子标签字段组件,支持单个或多个标签显示
|
|
|
|
|
+///
|
|
|
|
|
+/// 当[multiple]为true时,显示已识别的标签数量
|
|
|
|
|
+/// 当[multiple]为false时,显示单个标签的具体值
|
|
|
|
|
+class VbWinElectronicIdField extends StatefulWidget {
|
|
|
|
|
+ final List<String>? electronicIds;
|
|
|
|
|
+ final String? electronicId;
|
|
|
|
|
+ final ValueChanged<String>? onIdScanned; // 电子标签识别成功回调
|
|
|
|
|
+ final ValueChanged<List<String>>? onIdsScanned; // 电子标签识别成功回调
|
|
|
|
|
+ final String label; // 显示的标签文本
|
|
|
|
|
+ final String placeholder; // 未识别时显示的占位符文本
|
|
|
|
|
+ final String multiplePlaceholder; // 多个模式下未识别时显示的占位符文本
|
|
|
|
|
+ final String multipleFormat; // 多个模式下有值时的显示格式,包含一个%d占位符表示数量
|
|
|
|
|
+ final bool multiple; // 是否为多标签模式
|
|
|
|
|
+ final bool? multipleScan; // 是否开启多标签扫描模式
|
|
|
|
|
+ final ValueChanged<int>? onDeleteTag; // 仅在multiple模式下使用
|
|
|
|
|
+
|
|
|
|
|
+ const VbWinElectronicIdField({
|
|
|
|
|
+ super.key,
|
|
|
|
|
+ this.electronicIds,
|
|
|
|
|
+ this.electronicId,
|
|
|
|
|
+ this.onIdScanned,
|
|
|
|
|
+ this.onIdsScanned,
|
|
|
|
|
+ this.label = '电子标签',
|
|
|
|
|
+ this.placeholder = '未识别',
|
|
|
|
|
+ this.multiplePlaceholder = '未识别',
|
|
|
|
|
+ this.multipleFormat = '已识别 %d 枚电子标签',
|
|
|
|
|
+ this.multiple = false,
|
|
|
|
|
+ this.multipleScan,
|
|
|
|
|
+ this.onDeleteTag,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ State<VbWinElectronicIdField> createState() => _VbWinElectronicIdFieldState();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class _VbWinElectronicIdFieldState extends State<VbWinElectronicIdField> {
|
|
|
|
|
+ bool _isScanning = false; // 内部管理扫描状态
|
|
|
|
|
+ bool _isMultiple = false; // 内部管理多标签扫描模式
|
|
|
|
|
+ final List<String> _scannedTags = []; // 存储扫描到的标签
|
|
|
|
|
+ Timer? _autoStopTimer; // 自动停止扫描的定时器
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ void initState() {
|
|
|
|
|
+ super.initState();
|
|
|
|
|
+ _isMultiple = widget.multipleScan ?? widget.multiple;
|
|
|
|
|
+
|
|
|
|
|
+ WinReaderManager.instance.registerCallbacks(
|
|
|
|
|
+ onDataReceived: _handleTagReceived,
|
|
|
|
|
+ onError: _handleScanError,
|
|
|
|
|
+ onConnect: (success) {
|
|
|
|
|
+ if (success) {
|
|
|
|
|
+ ToastUtil.success('读卡器连接成功');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ToastUtil.warning('读卡器连接断开');
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!WinReaderManager.instance.isConnected) {
|
|
|
|
|
+ WinReaderManager.instance.autoConnect();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ void dispose() {
|
|
|
|
|
+ // 取消自动停止定时器
|
|
|
|
|
+ _autoStopTimer?.cancel();
|
|
|
|
|
+ _autoStopTimer = null;
|
|
|
|
|
+
|
|
|
|
|
+ WinReaderManager.instance.clearCallbacks();
|
|
|
|
|
+ super.dispose();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void _handleTagReceived(String data) {
|
|
|
|
|
+ logger.d('电子标签识别成功,已识别标签:$data');
|
|
|
|
|
+
|
|
|
|
|
+ if (_isMultiple) {
|
|
|
|
|
+ // 多标签模式:添加到列表中,避免重复
|
|
|
|
|
+ if (!_scannedTags.contains(data)) {
|
|
|
|
|
+ _scannedTags.add(data);
|
|
|
|
|
+
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isScanning = false;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (widget.onIdsScanned != null) {
|
|
|
|
|
+ widget.onIdsScanned!(_scannedTags);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 单标签模式:直接使用接收到的数据
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isScanning = false;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (widget.onIdScanned != null) {
|
|
|
|
|
+ widget.onIdScanned!(data);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void _handleScanError(String error) {
|
|
|
|
|
+ ToastUtil.error(error);
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isScanning = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void _scanTag() async {
|
|
|
|
|
+ if (!WinReaderManager.instance.isConnected) {
|
|
|
|
|
+ ToastUtil.error("请先连接读卡器设备");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isScanning = true;
|
|
|
|
|
+ if (_isMultiple) {
|
|
|
|
|
+ _scannedTags.clear(); // 清空之前的标签列表
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ToastUtil.show('开始识别电子标签', duration: 1);
|
|
|
|
|
+
|
|
|
|
|
+ bool result = await WinReaderManager.instance.startRead();
|
|
|
|
|
+ if (!result) {
|
|
|
|
|
+ ToastUtil.errorAlert('读卡器读取失败,请检查读卡器是否正常');
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isScanning = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ _autoStopTimer = Timer(const Duration(seconds: 5), () {
|
|
|
|
|
+ if (_isScanning) {
|
|
|
|
|
+ // 确保仍在扫描状态
|
|
|
|
|
+ _stopScan();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void _stopScan() async {
|
|
|
|
|
+ ToastUtil.info("停止识别电子标签");
|
|
|
|
|
+ await WinReaderManager.instance.stopRead();
|
|
|
|
|
+ // 取消自动停止定时器
|
|
|
|
|
+ _autoStopTimer?.cancel();
|
|
|
|
|
+ _autoStopTimer = null;
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isScanning = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
|
+ return Column(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Text(widget.label, style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
|
|
|
+ const SizedBox(height: 10),
|
|
|
|
|
+ Container(
|
|
|
|
|
+ padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
|
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
|
+ border: Border.all(color: Colors.grey),
|
|
|
|
|
+ borderRadius: BorderRadius.circular(8),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ _getDisplayText(),
|
|
|
|
|
+ style: TextStyle(
|
|
|
|
|
+ color: _hasValue() ? Colors.black : Colors.grey,
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ IconButton(
|
|
|
|
|
+ icon: _isScanning
|
|
|
|
|
+ ? const SizedBox(
|
|
|
|
|
+ width: 20,
|
|
|
|
|
+ height: 20,
|
|
|
|
|
+ child: CircularProgressIndicator(strokeWidth: 2),
|
|
|
|
|
+ )
|
|
|
|
|
+ : Icon(
|
|
|
|
|
+ widget.multiple ? Icons.add : Icons.qr_code_scanner,
|
|
|
|
|
+ size: 20,
|
|
|
|
|
+ ),
|
|
|
|
|
+ onPressed: _isScanning
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : (_isScanning ? _stopScan : _scanTag),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 获取显示的文本
|
|
|
|
|
+ String _getDisplayText() {
|
|
|
|
|
+ if (widget.multiple) {
|
|
|
|
|
+ if (widget.electronicIds == null || widget.electronicIds!.isEmpty) {
|
|
|
|
|
+ return widget.multiplePlaceholder;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return widget.multipleFormat.replaceAll(
|
|
|
|
|
+ '%d',
|
|
|
|
|
+ widget.electronicIds!.length.toString(),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return widget.electronicId ?? widget.placeholder;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 判断是否有值
|
|
|
|
|
+ bool _hasValue() {
|
|
|
|
|
+ if (widget.multiple) {
|
|
|
|
|
+ return widget.electronicIds != null && widget.electronicIds!.isNotEmpty;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return widget.electronicId != null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|