cage_change_page.dart 11 KB

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