individual_weighing_page.dart 7.2 KB

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