cage_change_page.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. import 'package:chicken_farm/apis/index.dart';
  2. import 'package:chicken_farm/core/services/pda/scan_manager.dart';
  3. import 'package:chicken_farm/core/utils/datetime_util.dart';
  4. import 'package:chicken_farm/core/utils/logger.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:chicken_farm/components/vb_app_bar.dart';
  7. import 'package:chicken_farm/core/utils/toast.dart';
  8. import 'package:chicken_farm/components/vb_electronic_id_field.dart';
  9. class CageChangePage extends StatefulWidget {
  10. const CageChangePage({super.key});
  11. @override
  12. State<CageChangePage> createState() => _CageChangePageState();
  13. }
  14. class _CageChangePageState extends State<CageChangePage> {
  15. final List<String> _electronicIds = [];
  16. final TextEditingController _cageController = TextEditingController();
  17. bool _isScanningCode = false;
  18. @override
  19. void initState() {
  20. super.initState();
  21. _isScanningCode = false;
  22. _cageController.clear();
  23. logger.d("页面初始化");
  24. ScanManager.instance.registerCallbacks(
  25. onScanResult: (result) {
  26. setState(() {
  27. logger.d("扫描目标笼号:$result");
  28. _cageController.text = result;
  29. _isScanningCode = false;
  30. });
  31. ScanManager.instance.stopScan();
  32. },
  33. onScanError: (error) {
  34. logger.d("扫码错误:$error");
  35. setState(() {
  36. _isScanningCode = false;
  37. });
  38. },
  39. onKeyPress: (bool isDouble, String keyCode) {
  40. if (isDouble && keyCode == "KEY_619") {
  41. _cageController.clear();
  42. ScanManager.instance.startScan();
  43. } else {
  44. logger.d("按键:$isDouble $keyCode");
  45. }
  46. },
  47. onScanHeadStateChanged: (bool isOpen) {
  48. if (isOpen) {
  49. ToastUtil.show("扫描组件已打开", duration: 1, bgColor: Colors.green);
  50. } else {
  51. ToastUtil.show("扫描组件已关闭", duration: 0.5);
  52. }
  53. setState(() {});
  54. },
  55. );
  56. }
  57. @override
  58. void dispose() {
  59. _cageController.dispose();
  60. // 释放通道
  61. ScanManager.instance.disposeScan();
  62. super.dispose();
  63. }
  64. @override
  65. Widget build(BuildContext context) {
  66. return Scaffold(
  67. appBar: const VberAppBar(title: '换笼管理', showLeftButton: true),
  68. body: SingleChildScrollView(
  69. child: Padding(
  70. padding: const EdgeInsets.all(16.0),
  71. child: Column(
  72. crossAxisAlignment: CrossAxisAlignment.start,
  73. children: [
  74. // // 源笼号区域
  75. // _buildCageSection(1),
  76. // const SizedBox(height: 15),
  77. // // 目标笼号区域
  78. // _buildCageSection(2),
  79. // const SizedBox(height: 15),
  80. // 目标笼号区域
  81. _buildCageSection(),
  82. const SizedBox(height: 15),
  83. // 电子编号区域
  84. _buildElectronicIdSection(),
  85. const SizedBox(height: 15),
  86. // 提交按钮
  87. // SizedBox(
  88. // width: double.infinity,
  89. // child: ElevatedButton(
  90. // onPressed:
  91. // _sourceCageController.text.isNotEmpty &&
  92. // _targetCageController.text.isNotEmpty &&
  93. // _rfids.isNotEmpty
  94. // ? _handleSubmit
  95. // : null,
  96. // style: ElevatedButton.styleFrom(
  97. // backgroundColor:
  98. // (_sourceCageController.text.isNotEmpty &&
  99. // _targetCageController.text.isNotEmpty &&
  100. // _rfids.isNotEmpty)
  101. // ? Colors.blue
  102. // : Colors.grey,
  103. // foregroundColor: Colors.white,
  104. // ),
  105. // child: const Text('提交'),
  106. // ),
  107. // ),
  108. SizedBox(
  109. width: double.infinity,
  110. child: ElevatedButton(
  111. onPressed:
  112. _cageController.text.isNotEmpty &&
  113. _electronicIds.isNotEmpty
  114. ? _handleSubmit
  115. : null,
  116. style: ElevatedButton.styleFrom(
  117. backgroundColor:
  118. (_cageController.text.isNotEmpty &&
  119. _electronicIds.isNotEmpty)
  120. ? Colors.blue
  121. : Colors.grey,
  122. foregroundColor: Colors.white,
  123. ),
  124. child: const Text('提交'),
  125. ),
  126. ),
  127. const SizedBox(height: 20),
  128. // 已识别的电子编号列表
  129. if (_electronicIds.isNotEmpty) ...[
  130. Row(
  131. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  132. children: [
  133. const Text(
  134. '已识别的电子编号',
  135. style: TextStyle(fontWeight: FontWeight.bold),
  136. ),
  137. IconButton(
  138. icon: const Icon(Icons.clear, size: 18),
  139. onPressed: _clearElectronicIds,
  140. tooltip: '清空编号',
  141. ),
  142. ],
  143. ),
  144. const SizedBox(height: 10),
  145. Container(
  146. padding: const EdgeInsets.all(10),
  147. decoration: BoxDecoration(
  148. border: Border.all(color: Colors.grey),
  149. borderRadius: BorderRadius.circular(8),
  150. ),
  151. child: ListView.builder(
  152. shrinkWrap: true,
  153. physics: const NeverScrollableScrollPhysics(),
  154. padding: EdgeInsets.zero,
  155. itemCount: _electronicIds.length,
  156. itemBuilder: (context, index) {
  157. return ListTile(
  158. visualDensity: VisualDensity.compact,
  159. contentPadding: const EdgeInsets.symmetric(
  160. horizontal: 2,
  161. vertical: 0,
  162. ),
  163. title: Text(_electronicIds[index]),
  164. trailing: IconButton(
  165. icon: const Icon(
  166. Icons.delete,
  167. size: 18,
  168. color: Colors.red,
  169. ),
  170. onPressed: () => _removeElectronicId(index),
  171. ),
  172. );
  173. },
  174. ),
  175. ),
  176. ],
  177. const SizedBox(height: 20),
  178. ],
  179. ),
  180. ),
  181. ),
  182. );
  183. }
  184. Widget _buildCageSection() {
  185. // 确保光标在文本末尾
  186. if (_cageController.text.isNotEmpty) {
  187. _cageController.selection = TextSelection.fromPosition(
  188. TextPosition(offset: _cageController.text.length),
  189. );
  190. }
  191. return Column(
  192. crossAxisAlignment: CrossAxisAlignment.start,
  193. children: [
  194. Text('目标笼号', style: const TextStyle(fontWeight: FontWeight.bold)),
  195. const SizedBox(height: 10),
  196. Container(
  197. padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
  198. decoration: BoxDecoration(
  199. border: Border.all(color: Colors.grey),
  200. borderRadius: BorderRadius.circular(8),
  201. ),
  202. child: Row(
  203. children: [
  204. Expanded(
  205. // 将Text替换为TextField
  206. child: TextField(
  207. controller: _cageController,
  208. enabled: _cageController.text.isEmpty,
  209. decoration: InputDecoration(
  210. border: InputBorder.none,
  211. hintText: _cageController.text.isNotEmpty ? null : '未扫描',
  212. hintStyle: TextStyle(
  213. color: _cageController.text.isNotEmpty
  214. ? Colors.black
  215. : Colors.grey,
  216. fontSize: 16,
  217. ),
  218. ),
  219. style: TextStyle(
  220. color: _cageController.text.isNotEmpty
  221. ? Colors.black
  222. : Colors.grey,
  223. fontSize: 16,
  224. ),
  225. onChanged: (text) {
  226. setState(() {
  227. // 文本更改由controller自动处理
  228. });
  229. },
  230. ),
  231. ),
  232. if (_cageController.text.isNotEmpty) ...[
  233. IconButton(
  234. icon: const Icon(Icons.refresh, size: 20),
  235. onPressed: () {
  236. _handleScanCageCode();
  237. },
  238. ),
  239. ] else ...[
  240. IconButton(
  241. icon: _isScanningCode
  242. ? const SizedBox(
  243. width: 20,
  244. height: 20,
  245. child: CircularProgressIndicator(strokeWidth: 2),
  246. )
  247. : const Icon(Icons.qr_code_scanner, size: 20),
  248. onPressed: () {
  249. _handleScanCageCode();
  250. },
  251. ),
  252. ],
  253. ],
  254. ),
  255. ),
  256. ],
  257. );
  258. }
  259. Widget _buildElectronicIdSection() {
  260. return VberElectronicIdsField(
  261. electronicIds: _electronicIds,
  262. onIdScanned: (id) {
  263. // 检查是否已存在相同的ID标签
  264. bool isDuplicate = _electronicIds.any((v) => v == id);
  265. if (isDuplicate) {
  266. ScaffoldMessenger.of(context).showSnackBar(
  267. const SnackBar(
  268. content: Text('电子编号已存在'),
  269. backgroundColor: Colors.orange,
  270. ),
  271. );
  272. } else {
  273. // 如果不重复,添加到列表中
  274. setState(() {
  275. _electronicIds.insert(0, id);
  276. });
  277. ScaffoldMessenger.of(context).showSnackBar(
  278. const SnackBar(
  279. content: Text('成功添加新的电子编号'),
  280. backgroundColor: Colors.green,
  281. ),
  282. );
  283. }
  284. },
  285. multiple: true,
  286. multipleScan: false,
  287. label: '电子编号',
  288. multiplePlaceholder: '未识别',
  289. multipleFormat: '已识别 %d 枚电子编号',
  290. );
  291. }
  292. void _handleScanCageCode() async {
  293. if (_isScanningCode) return;
  294. setState(() {
  295. _isScanningCode = true;
  296. });
  297. bool success = await ScanManager.instance.startScan();
  298. if (!success) {
  299. ToastUtil.error("扫码失败");
  300. }
  301. }
  302. // 移除指定索引的电子编号
  303. void _removeElectronicId(int index) {
  304. setState(() {
  305. _electronicIds.removeAt(index);
  306. });
  307. }
  308. // 清空所有已识别的电子编号
  309. void _clearElectronicIds() {
  310. setState(() {
  311. _electronicIds.clear();
  312. });
  313. ToastUtil.info('已清空所有电子编号');
  314. }
  315. // 提交数据
  316. void _handleSubmit() {
  317. final data = {
  318. 'targetCage': _cageController.text,
  319. 'electronicIds': _electronicIds,
  320. 'date': DateTimeUtil.format(DateTime.now()),
  321. };
  322. apis.breeding.submitApi.cageChange(data).then((res) {
  323. if (res.success) {
  324. if (res.message.isNotEmpty) {
  325. ToastUtil.success(res.message.isEmpty ? '换笼成功' : res.message);
  326. }
  327. if (mounted) {
  328. // 提交后重置表单
  329. setState(() {
  330. _electronicIds.clear();
  331. });
  332. }
  333. } else {
  334. ToastUtil.errorAlert(res.message.isEmpty ? '换笼提交失败' : res.message);
  335. }
  336. });
  337. }
  338. }