individual_weighing_page.dart 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. setState(() {
  76. _rfid = rfid;
  77. });
  78. ToastUtil.success('电子编号识别成功');
  79. _handleReadWeight();
  80. },
  81. label: '电子编号',
  82. placeholder: '未识别',
  83. );
  84. }
  85. Widget _buildWeightSection() {
  86. return Column(
  87. crossAxisAlignment: CrossAxisAlignment.start,
  88. children: [
  89. const Text('重量(kg)', style: TextStyle(fontWeight: FontWeight.bold)),
  90. const SizedBox(height: 10),
  91. Container(
  92. padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
  93. decoration: BoxDecoration(
  94. border: Border.all(color: Colors.grey),
  95. borderRadius: BorderRadius.circular(8),
  96. ),
  97. child: Row(
  98. children: [
  99. Expanded(
  100. child: TextField(
  101. focusNode: _weightFocusNode,
  102. controller: _weightController,
  103. enabled: _weight == null,
  104. decoration: InputDecoration(
  105. border: InputBorder.none,
  106. hintText: _weight != null
  107. ? null
  108. : _isReadingWeight
  109. ? "正在读取重量"
  110. : '请读取重量',
  111. hintStyle: TextStyle(
  112. color: _isReadingWeight
  113. ? Colors.red
  114. : _weight != null
  115. ? Colors.black
  116. : Colors.grey,
  117. fontSize: 16,
  118. ),
  119. ),
  120. style: TextStyle(
  121. color: _weight != null ? Colors.black : Colors.grey,
  122. fontSize: 16,
  123. ),
  124. keyboardType: TextInputType.numberWithOptions(decimal: true),
  125. onChanged: (text) {
  126. // 取消之前的定时器
  127. _inputDebounceTimer?.cancel();
  128. // 设置新的定时器,延时处理输入
  129. _inputDebounceTimer = Timer(
  130. const Duration(milliseconds: 500),
  131. () {
  132. if (text.isEmpty) {
  133. setState(() {
  134. _weight = null;
  135. });
  136. } else {
  137. try {
  138. final parsedValue = double.tryParse(text);
  139. if (parsedValue != null) {
  140. setState(() {
  141. _weight = parsedValue;
  142. });
  143. ToastUtil.success('重量读取成功');
  144. }
  145. _isReadingWeight = false;
  146. } catch (e) {
  147. // 输入无效时不更新_weight
  148. logger.e('解析重量值失败: $e');
  149. }
  150. }
  151. },
  152. );
  153. },
  154. ),
  155. ),
  156. if (_weight != null) ...[
  157. IconButton(
  158. icon: const Icon(Icons.refresh, size: 20),
  159. onPressed: _handleReadWeight,
  160. ),
  161. ] else ...[
  162. IconButton(
  163. icon: _isReadingWeight
  164. ? const SizedBox(
  165. width: 20,
  166. height: 20,
  167. child: CircularProgressIndicator(strokeWidth: 2),
  168. )
  169. : const Icon(Icons.sync, size: 20),
  170. onPressed: _handleReadWeight,
  171. ),
  172. ],
  173. ],
  174. ),
  175. ),
  176. ],
  177. );
  178. }
  179. void _handleReadWeight() {
  180. setState(() {
  181. _weight = null;
  182. _weightController.clear();
  183. _isReadingWeight = true;
  184. });
  185. // 重置后输入框获得焦点
  186. WidgetsBinding.instance.addPostFrameCallback((_) {
  187. if (mounted) {
  188. _weightFocusNode.requestFocus();
  189. }
  190. });
  191. // 设置3秒超时
  192. _timeoutTimer?.cancel(); // 取消之前的超时定时器
  193. _timeoutTimer = Timer(const Duration(seconds: 15), () {
  194. if (mounted && _isReadingWeight == true) {
  195. setState(() {
  196. _weight = null;
  197. _isReadingWeight = false;
  198. _weightFocusNode.unfocus();
  199. });
  200. ToastUtil.error('读取重量超时');
  201. }
  202. });
  203. }
  204. // 提交数据
  205. void _handleSubmit() {
  206. final data = {
  207. "rfid": _rfid,
  208. "weight": _weight,
  209. 'date': DateTimeUtil.format(DateTime.now()),
  210. };
  211. apis.breeding.submitApi
  212. .weight(data)
  213. .then((_) {
  214. if (mounted) {
  215. ScaffoldMessenger.of(context).showSnackBar(
  216. const SnackBar(
  217. content: Text('称重提交成功'),
  218. backgroundColor: Colors.green,
  219. ),
  220. );
  221. // 提交后重置表单
  222. setState(() {
  223. _rfid = null;
  224. _weight = null;
  225. _weightController.clear();
  226. });
  227. }
  228. })
  229. .catchError((err) {
  230. ToastUtil.error('称重提交失败');
  231. if (mounted && err != null) {
  232. String errorMessage = err.toString();
  233. if (err is Exception) {
  234. errorMessage = err.toString();
  235. }
  236. ScaffoldMessenger.of(context).showSnackBar(
  237. SnackBar(
  238. content: Text(errorMessage),
  239. backgroundColor: Colors.red,
  240. ),
  241. );
  242. }
  243. });
  244. }
  245. }