individual_weighing_page.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import 'package:chicken_farm/apis/index.dart';
  2. import 'package:chicken_farm/core/utils/datetime_util.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/components/vb_rfid_field.dart';
  7. import 'package:chicken_farm/core/utils/logger.dart';
  8. import 'dart:async';
  9. class IndividualWeighingPage extends StatefulWidget {
  10. const IndividualWeighingPage({super.key});
  11. @override
  12. State<IndividualWeighingPage> createState() => _IndividualWeighingPageState();
  13. }
  14. class _IndividualWeighingPageState extends State<IndividualWeighingPage> {
  15. String? _rfid;
  16. double? _weight;
  17. bool _isReadingWeight = false;
  18. // 添加TextController和FocusNode用于重量输入框
  19. final TextEditingController _weightController = TextEditingController();
  20. final FocusNode _weightFocusNode = FocusNode();
  21. // 添加Timer用于延迟处理输入
  22. Timer? _inputDebounceTimer;
  23. // 添加超时Timer
  24. Timer? _timeoutTimer;
  25. @override
  26. void dispose() {
  27. // 释放TextEditingController和FocusNode
  28. _weightController.dispose();
  29. _weightFocusNode.dispose();
  30. // 取消定时器
  31. _inputDebounceTimer?.cancel();
  32. _timeoutTimer?.cancel();
  33. super.dispose();
  34. }
  35. @override
  36. Widget build(BuildContext context) {
  37. return Scaffold(
  38. appBar: const VberAppBar(title: '个体称重', showLeftButton: true),
  39. body: Padding(
  40. padding: const EdgeInsets.all(16.0),
  41. child: Column(
  42. crossAxisAlignment: CrossAxisAlignment.start,
  43. children: [
  44. // 电子编号区域
  45. _buildRfidSection(),
  46. const SizedBox(height: 20),
  47. // 重量区域
  48. _buildWeightSection(),
  49. const SizedBox(height: 30),
  50. // 提交按钮
  51. SizedBox(
  52. width: double.infinity,
  53. child: ElevatedButton(
  54. onPressed: _rfid != null && _weight != null
  55. ? _handleSubmit
  56. : null,
  57. style: ElevatedButton.styleFrom(
  58. backgroundColor: _rfid != null && _weight != null
  59. ? Colors.blue
  60. : Colors.grey,
  61. foregroundColor: Colors.white,
  62. ),
  63. child: const Text('提交'),
  64. ),
  65. ),
  66. ],
  67. ),
  68. ),
  69. );
  70. }
  71. Widget _buildRfidSection() {
  72. return VberRfidField(
  73. rfid: _rfid,
  74. onRfidScanned: (rfid) {
  75. if (rfid == _rfid) {
  76. ScaffoldMessenger.of(context).showSnackBar(
  77. const SnackBar(
  78. content: Text('电子编号未改变'),
  79. backgroundColor: Colors.orange,
  80. ),
  81. );
  82. } else {
  83. setState(() {
  84. _rfid = rfid;
  85. });
  86. // ToastUtil.success('电子编号识别成功');
  87. ScaffoldMessenger.of(context).showSnackBar(
  88. const SnackBar(
  89. content: Text('成功识别新的电子编号'),
  90. backgroundColor: Colors.green,
  91. ),
  92. );
  93. }
  94. _handleReadWeight();
  95. },
  96. label: '电子编号',
  97. placeholder: '未识别',
  98. );
  99. }
  100. Widget _buildWeightSection() {
  101. return Column(
  102. crossAxisAlignment: CrossAxisAlignment.start,
  103. children: [
  104. const Text('重量(kg)', style: TextStyle(fontWeight: FontWeight.bold)),
  105. const SizedBox(height: 10),
  106. Container(
  107. padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
  108. decoration: BoxDecoration(
  109. border: Border.all(color: Colors.grey),
  110. borderRadius: BorderRadius.circular(8),
  111. ),
  112. child: Row(
  113. children: [
  114. Expanded(
  115. child: TextField(
  116. focusNode: _weightFocusNode,
  117. controller: _weightController,
  118. enabled: _weight == null,
  119. decoration: InputDecoration(
  120. border: InputBorder.none,
  121. hintText: _weight != null
  122. ? null
  123. : _isReadingWeight
  124. ? "正在读取重量"
  125. : '请读取重量',
  126. hintStyle: TextStyle(
  127. color: _isReadingWeight
  128. ? Colors.red
  129. : _weight != null
  130. ? Colors.black
  131. : Colors.grey,
  132. fontSize: 16,
  133. ),
  134. ),
  135. style: TextStyle(
  136. color: _weight != null ? Colors.black : Colors.grey,
  137. fontSize: 16,
  138. ),
  139. keyboardType: TextInputType.numberWithOptions(decimal: true),
  140. onChanged: (text) {
  141. // 取消之前的定时器
  142. _inputDebounceTimer?.cancel();
  143. // 设置新的定时器,延时处理输入
  144. _inputDebounceTimer = Timer(
  145. const Duration(milliseconds: 500),
  146. () {
  147. if (text.isEmpty) {
  148. setState(() {
  149. _weight = null;
  150. });
  151. } else {
  152. try {
  153. final parsedValue = double.tryParse(text);
  154. if (parsedValue != null) {
  155. setState(() {
  156. _weight = parsedValue;
  157. });
  158. ScaffoldMessenger.of(context).showSnackBar(
  159. const SnackBar(
  160. content: Text('重量读取成功'),
  161. backgroundColor: Colors.green,
  162. ),
  163. );
  164. }
  165. _isReadingWeight = false;
  166. } catch (e) {
  167. // 输入无效时不更新_weight
  168. logger.e('解析重量值失败: $e');
  169. }
  170. }
  171. },
  172. );
  173. },
  174. ),
  175. ),
  176. if (_weight != null) ...[
  177. IconButton(
  178. icon: const Icon(Icons.refresh, size: 20),
  179. onPressed: _handleReadWeight,
  180. ),
  181. ] else ...[
  182. IconButton(
  183. icon: _isReadingWeight
  184. ? const SizedBox(
  185. width: 20,
  186. height: 20,
  187. child: CircularProgressIndicator(strokeWidth: 2),
  188. )
  189. : const Icon(Icons.sync, size: 20),
  190. onPressed: _handleReadWeight,
  191. ),
  192. ],
  193. ],
  194. ),
  195. ),
  196. ],
  197. );
  198. }
  199. void _handleReadWeight() {
  200. setState(() {
  201. _weight = null;
  202. _weightController.clear();
  203. _isReadingWeight = true;
  204. });
  205. // 重置后输入框获得焦点
  206. WidgetsBinding.instance.addPostFrameCallback((_) {
  207. if (mounted) {
  208. _weightFocusNode.requestFocus();
  209. }
  210. });
  211. // 设置3秒超时
  212. _timeoutTimer?.cancel(); // 取消之前的超时定时器
  213. _timeoutTimer = Timer(const Duration(seconds: 15), () {
  214. if (mounted && _isReadingWeight == true) {
  215. setState(() {
  216. _weight = null;
  217. _isReadingWeight = false;
  218. _weightFocusNode.unfocus();
  219. });
  220. ToastUtil.error('读取重量超时');
  221. }
  222. });
  223. }
  224. // 提交数据
  225. void _handleSubmit() {
  226. final data = {
  227. "rfid": _rfid,
  228. "weight": _weight,
  229. 'date': DateTimeUtil.format(DateTime.now()),
  230. };
  231. apis.breeding.submitApi.weight(data).then((res) {
  232. if (res.success) {
  233. if (res.message.isNotEmpty) {
  234. ToastUtil.success(res.message);
  235. }
  236. if (mounted) {
  237. ScaffoldMessenger.of(context).showSnackBar(
  238. const SnackBar(
  239. content: Text('称重提交成功'),
  240. backgroundColor: Colors.green,
  241. ),
  242. );
  243. // 提交后重置表单
  244. setState(() {
  245. _rfid = null;
  246. _weight = null;
  247. _weightController.clear();
  248. });
  249. }
  250. } else {
  251. ToastUtil.error('称重提交失败');
  252. if (mounted && res.message.isNotEmpty) {
  253. logger.e(res.message);
  254. ScaffoldMessenger.of(context).showSnackBar(
  255. SnackBar(content: Text(res.message), backgroundColor: Colors.red),
  256. );
  257. }
  258. }
  259. });
  260. }
  261. }