cage_change_page.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. import 'package:chicken_farm/core/utils/logger.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:chicken_farm/components/vb_app_bar.dart';
  4. import 'package:chicken_farm/core/utils/toast.dart';
  5. import 'package:chicken_farm/core/services/pda/scan_channel.dart';
  6. import 'package:chicken_farm/components/vb_rfid_field.dart';
  7. class CageChangePage extends StatefulWidget {
  8. const CageChangePage({super.key});
  9. @override
  10. State<CageChangePage> createState() => _CageChangePageState();
  11. }
  12. class _CageChangePageState extends State<CageChangePage> {
  13. final List<String> _rfids = [];
  14. // 添加TextEditingControllers用于控制输入框
  15. final TextEditingController _sourceCageController = TextEditingController();
  16. final TextEditingController _targetCageController = TextEditingController();
  17. // 添加FocusNode用于控制焦点
  18. final FocusNode _sourceCageFocusNode = FocusNode();
  19. final FocusNode _targetCageFocusNode = FocusNode();
  20. // 扫描状态
  21. bool _isScanningSource = false;
  22. bool _isScanningTarget = false;
  23. int _scanTag = 1;
  24. @override
  25. void initState() {
  26. super.initState();
  27. _sourceCageController.clear();
  28. _targetCageController.clear();
  29. // 监听焦点变化以更新_scanTag值
  30. _sourceCageFocusNode.addListener(() {
  31. if (_sourceCageFocusNode.hasFocus) {
  32. _scanTag = 1;
  33. }
  34. });
  35. _targetCageFocusNode.addListener(() {
  36. if (_targetCageFocusNode.hasFocus) {
  37. _scanTag = 2;
  38. }
  39. });
  40. ScanChannel.openScanHead().then((success) {
  41. if (success) {
  42. logger.d("扫描头已打开");
  43. } else {
  44. logger.d("扫描头打开失败");
  45. }
  46. });
  47. // 初始化监听
  48. ScanChannel.initScanListener(
  49. onScanResult: (result) {
  50. logger.d("扫码结果:$result tag: $_scanTag");
  51. if (_scanTag == 1) {
  52. // 源笼号
  53. setState(() {
  54. _sourceCageController.text = result;
  55. _isScanningSource = false;
  56. });
  57. if (_targetCageController.text.isEmpty) {
  58. // 源笼号扫描成功后自动延时扫描目标笼号
  59. ScanChannel.stopSingleScan();
  60. logger.d("自动扫描目标笼号");
  61. _targetCageFocusNode.requestFocus();
  62. _dealyScanCode(2);
  63. }
  64. } else if (_scanTag == 2) {
  65. // 目标笼号
  66. setState(() {
  67. _targetCageController.text = result;
  68. _isScanningTarget = false;
  69. });
  70. }
  71. // 扫码成功后停止解码(保留扫描头)
  72. ScanChannel.stopSingleScan();
  73. },
  74. onScanError: (error) {
  75. logger.d("扫码错误:$error");
  76. setState(() {
  77. _isScanningSource = false;
  78. });
  79. },
  80. );
  81. _scanTag = 1;
  82. // 进入页面默认使原笼号获取到焦点
  83. // WidgetsBinding.instance.addPostFrameCallback((_) {
  84. // _sourceCageFocusNode.requestFocus();
  85. // });
  86. }
  87. @override
  88. void dispose() {
  89. // 释放TextEditingControllers
  90. _sourceCageController.dispose();
  91. _targetCageController.dispose();
  92. // 释放FocusNode
  93. _sourceCageFocusNode.dispose();
  94. _targetCageFocusNode.dispose();
  95. // 手动关闭扫描头,立即释放资源
  96. ScanChannel.closeScanHead();
  97. // 释放通道
  98. ScanChannel.dispose();
  99. super.dispose();
  100. }
  101. @override
  102. Widget build(BuildContext context) {
  103. return Scaffold(
  104. appBar: const VberAppBar(title: '换笼管理', showLeftButton: true),
  105. body: SingleChildScrollView(
  106. child: Padding(
  107. padding: const EdgeInsets.all(16.0),
  108. child: Column(
  109. crossAxisAlignment: CrossAxisAlignment.start,
  110. children: [
  111. // 源笼号区域
  112. _buildCageSection(1),
  113. const SizedBox(height: 15),
  114. // 目标笼号区域
  115. _buildCageSection(2),
  116. const SizedBox(height: 15),
  117. // 电子编号区域
  118. _buildRfidSection(),
  119. const SizedBox(height: 15),
  120. // 提交按钮
  121. SizedBox(
  122. width: double.infinity,
  123. child: ElevatedButton(
  124. onPressed:
  125. _sourceCageController.text.isNotEmpty &&
  126. _targetCageController.text.isNotEmpty &&
  127. _rfids.isNotEmpty
  128. ? _handleSubmit
  129. : null,
  130. style: ElevatedButton.styleFrom(
  131. backgroundColor:
  132. (_sourceCageController.text.isNotEmpty &&
  133. _targetCageController.text.isNotEmpty &&
  134. _rfids.isNotEmpty)
  135. ? Colors.blue
  136. : Colors.grey,
  137. foregroundColor: Colors.white,
  138. ),
  139. child: const Text('提交'),
  140. ),
  141. ),
  142. const SizedBox(height: 20),
  143. // 已识别的电子编号列表
  144. if (_rfids.isNotEmpty) ...[
  145. const Text(
  146. '已识别的电子编号',
  147. style: TextStyle(fontWeight: FontWeight.bold),
  148. ),
  149. const SizedBox(height: 10),
  150. Container(
  151. padding: const EdgeInsets.all(10),
  152. decoration: BoxDecoration(
  153. border: Border.all(color: Colors.grey),
  154. borderRadius: BorderRadius.circular(8),
  155. ),
  156. child: ListView.builder(
  157. shrinkWrap: true,
  158. physics: const NeverScrollableScrollPhysics(),
  159. padding: EdgeInsets.zero,
  160. itemCount: _rfids.length,
  161. itemBuilder: (context, index) {
  162. return ListTile(
  163. visualDensity: VisualDensity.compact,
  164. contentPadding: const EdgeInsets.symmetric(
  165. horizontal: 2,
  166. vertical: 0,
  167. ),
  168. title: Text(_rfids[index]),
  169. trailing: IconButton(
  170. icon: const Icon(
  171. Icons.delete,
  172. size: 18,
  173. color: Colors.red,
  174. ),
  175. onPressed: () => _removeRfid(index),
  176. ),
  177. );
  178. },
  179. ),
  180. ),
  181. ],
  182. const SizedBox(height: 20),
  183. ],
  184. ),
  185. ),
  186. ),
  187. );
  188. }
  189. Widget _buildCageSection(int tag) {
  190. String title = '源笼号';
  191. TextEditingController controller = _sourceCageController;
  192. FocusNode focusNode = _sourceCageFocusNode;
  193. bool isScanning = _isScanningSource;
  194. if (tag != 1) {
  195. tag = 2;
  196. title = '目标笼号';
  197. controller = _targetCageController;
  198. focusNode = _targetCageFocusNode;
  199. isScanning = _isScanningTarget;
  200. }
  201. // 确保光标在文本末尾
  202. if (controller.text.isNotEmpty) {
  203. controller.selection = TextSelection.fromPosition(
  204. TextPosition(offset: controller.text.length),
  205. );
  206. }
  207. return Column(
  208. crossAxisAlignment: CrossAxisAlignment.start,
  209. children: [
  210. Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
  211. const SizedBox(height: 10),
  212. Container(
  213. padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
  214. decoration: BoxDecoration(
  215. border: Border.all(color: Colors.grey),
  216. borderRadius: BorderRadius.circular(8),
  217. ),
  218. child: Row(
  219. children: [
  220. Expanded(
  221. // 将Text替换为TextField
  222. child: TextField(
  223. focusNode: focusNode,
  224. controller: controller,
  225. decoration: InputDecoration(
  226. border: InputBorder.none,
  227. hintText: controller.text.isNotEmpty ? null : '未扫描',
  228. hintStyle: TextStyle(
  229. color: controller.text.isNotEmpty
  230. ? Colors.black
  231. : Colors.grey,
  232. fontSize: 16,
  233. ),
  234. ),
  235. style: TextStyle(
  236. color: controller.text.isNotEmpty
  237. ? Colors.black
  238. : Colors.grey,
  239. fontSize: 16,
  240. ),
  241. onChanged: (text) {
  242. setState(() {
  243. // 文本更改由controller自动处理
  244. });
  245. },
  246. ),
  247. ),
  248. if (controller.text.isNotEmpty) ...[
  249. IconButton(
  250. icon: const Icon(Icons.refresh, size: 20),
  251. onPressed: () {
  252. _handleChangeCageCode(tag);
  253. },
  254. ),
  255. ] else ...[
  256. IconButton(
  257. icon: isScanning
  258. ? const SizedBox(
  259. width: 20,
  260. height: 20,
  261. child: CircularProgressIndicator(strokeWidth: 2),
  262. )
  263. : const Icon(Icons.qr_code_scanner, size: 20),
  264. onPressed: () {
  265. _handleScanCageCode(tag);
  266. },
  267. ),
  268. ],
  269. ],
  270. ),
  271. ),
  272. ],
  273. );
  274. }
  275. Widget _buildRfidSection() {
  276. return VberRfidField(
  277. rfids: _rfids,
  278. onRfidScanned: (rfid) {
  279. // 检查是否已存在相同的RFID标签
  280. bool isDuplicate = _rfids.any((existingRfid) => existingRfid == rfid);
  281. if (isDuplicate) {
  282. ToastUtil.info("电子编号已存在");
  283. } else {
  284. // 如果不重复,添加到列表中
  285. setState(() {
  286. _rfids.insert(0, rfid);
  287. });
  288. ToastUtil.success("添加新的电子编号");
  289. }
  290. },
  291. multiple: true,
  292. multipleScan: false,
  293. label: '电子编号',
  294. multiplePlaceholder: '未识别',
  295. multipleFormat: '已识别 %d 枚电子编号',
  296. );
  297. }
  298. void _dealyScanCode(int scanTag, {int? dealy}) async {
  299. dealy ??= 800;
  300. _scanTag = scanTag;
  301. logger.d("开始扫码: $_scanTag");
  302. await Future.delayed(Duration(milliseconds: dealy));
  303. bool success = await ScanChannel.startScan();
  304. if (!success) {
  305. ToastUtil.error("扫码失败");
  306. }
  307. }
  308. void _handleScanCageCode(int tag) async {
  309. if (_isScanningTarget || _isScanningSource) return;
  310. setState(() {
  311. if (tag == 1) {
  312. _isScanningSource = true;
  313. } else if (tag == 2) {
  314. _isScanningTarget = true;
  315. }
  316. });
  317. _scanTag = tag;
  318. logger.d("开始扫码: $_scanTag");
  319. bool success = await ScanChannel.startScan();
  320. if (!success) {
  321. ToastUtil.error("扫码失败");
  322. }
  323. }
  324. void _handleChangeCageCode(int tag) {
  325. setState(() {
  326. if (tag == 1) {
  327. _sourceCageController.clear();
  328. } else {
  329. _targetCageController.clear();
  330. }
  331. });
  332. _dealyScanCode(tag);
  333. }
  334. // 移除指定索引的电子编号
  335. void _removeRfid(int index) {
  336. setState(() {
  337. _rfids.removeAt(index);
  338. });
  339. }
  340. // 提交数据
  341. void _handleSubmit() {
  342. // 在实际应用中,这里会发送数据到服务器
  343. ScaffoldMessenger.of(context).showSnackBar(
  344. const SnackBar(content: Text('换笼操作提交成功'), backgroundColor: Colors.green),
  345. );
  346. // 提交后重置表单
  347. setState(() {
  348. _sourceCageController.clear();
  349. _targetCageController.clear();
  350. _rfids.clear();
  351. });
  352. }
  353. }