individual_weighing_page.dart 8.0 KB

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