cage_change_page.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. setState(() {
  280. _rfids.insert(0, rfid);
  281. });
  282. ToastUtil.success("标签已添加");
  283. },
  284. multiple: true,
  285. label: '鸡数量',
  286. multiplePlaceholder: '未扫描',
  287. multipleFormat: '已扫描 %d 只鸡',
  288. );
  289. }
  290. void _dealyScanCode(int scanTag, {int? dealy}) async {
  291. dealy ??= 800;
  292. _scanTag = scanTag;
  293. logger.d("开始扫码: $_scanTag");
  294. await Future.delayed(Duration(milliseconds: dealy));
  295. bool success = await ScanChannel.startScan();
  296. if (!success) {
  297. ToastUtil.error("扫码失败");
  298. }
  299. }
  300. void _handleScanCageCode(int tag) async {
  301. if (_isScanningTarget || _isScanningSource) return;
  302. setState(() {
  303. if (tag == 1) {
  304. _isScanningSource = true;
  305. } else if (tag == 2) {
  306. _isScanningTarget = true;
  307. }
  308. });
  309. _scanTag = tag;
  310. logger.d("开始扫码: $_scanTag");
  311. bool success = await ScanChannel.startScan();
  312. if (!success) {
  313. ToastUtil.error("扫码失败");
  314. }
  315. }
  316. void _handleChangeCageCode(int tag) {
  317. setState(() {
  318. if (tag == 1) {
  319. _sourceCageController.clear();
  320. } else {
  321. _targetCageController.clear();
  322. }
  323. });
  324. _dealyScanCode(tag);
  325. }
  326. // 移除指定索引的电子编号
  327. void _removeRfid(int index) {
  328. setState(() {
  329. _rfids.removeAt(index);
  330. });
  331. }
  332. // 提交数据
  333. void _handleSubmit() {
  334. // 在实际应用中,这里会发送数据到服务器
  335. ScaffoldMessenger.of(context).showSnackBar(
  336. const SnackBar(content: Text('换笼操作提交成功'), backgroundColor: Colors.green),
  337. );
  338. // 提交后重置表单
  339. setState(() {
  340. _sourceCageController.clear();
  341. _targetCageController.clear();
  342. _rfids.clear();
  343. });
  344. }
  345. }