individual_weighing_page.dart 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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_electronic_id_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? _electronicId;
  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: SingleChildScrollView(
  40. padding: const EdgeInsets.all(16.0),
  41. child: Column(
  42. crossAxisAlignment: CrossAxisAlignment.start,
  43. children: [
  44. // 电子编号区域
  45. _buildElectronicIdSection(),
  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: _electronicId != null && _weight != null
  55. ? _handleSubmit
  56. : null,
  57. style: ElevatedButton.styleFrom(
  58. backgroundColor: _electronicId != 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 _buildElectronicIdSection() {
  72. return VberElectronicIdsField(
  73. electronicId: _electronicId,
  74. onIdScanned: (id) {
  75. if (id == _electronicId) {
  76. ScaffoldMessenger.of(context).showSnackBar(
  77. const SnackBar(
  78. content: Text('电子编号未改变'),
  79. backgroundColor: Colors.orange,
  80. ),
  81. );
  82. } else {
  83. setState(() {
  84. _electronicId = id;
  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. // 修改键盘类型为none以防止键盘弹出,同时保持可编辑性
  120. keyboardType: TextInputType.none,
  121. decoration: InputDecoration(
  122. border: InputBorder.none,
  123. hintText: _weight != null
  124. ? null
  125. : _isReadingWeight
  126. ? "正在读取重量"
  127. : '请读取重量',
  128. hintStyle: TextStyle(
  129. color: _isReadingWeight
  130. ? Colors.red
  131. : _weight != null
  132. ? Colors.black
  133. : Colors.grey,
  134. fontSize: 16,
  135. ),
  136. ),
  137. style: TextStyle(
  138. color: _weight != null ? Colors.black : Colors.grey,
  139. fontSize: 16,
  140. ),
  141. onChanged: (text) {
  142. // 取消之前的定时器
  143. _inputDebounceTimer?.cancel();
  144. // 设置新的定时器,延时处理输入
  145. _inputDebounceTimer = Timer(
  146. const Duration(milliseconds: 500),
  147. () {
  148. if (text.isEmpty) {
  149. setState(() {
  150. _weight = null;
  151. });
  152. } else {
  153. try {
  154. final parsedValue = double.tryParse(text);
  155. if (parsedValue != null) {
  156. setState(() {
  157. _weight = parsedValue;
  158. });
  159. ScaffoldMessenger.of(context).showSnackBar(
  160. const SnackBar(
  161. content: Text('重量读取成功'),
  162. backgroundColor: Colors.green,
  163. ),
  164. );
  165. }
  166. _isReadingWeight = false;
  167. } catch (e) {
  168. // 输入无效时不更新_weight
  169. logger.e('解析重量值失败: $e');
  170. }
  171. }
  172. },
  173. );
  174. },
  175. ),
  176. ),
  177. if (_weight != null) ...[
  178. IconButton(
  179. icon: const Icon(Icons.refresh, size: 20),
  180. onPressed: _handleReadWeight,
  181. ),
  182. ] else ...[
  183. IconButton(
  184. icon: _isReadingWeight
  185. ? const SizedBox(
  186. width: 20,
  187. height: 20,
  188. child: CircularProgressIndicator(strokeWidth: 2),
  189. )
  190. : const Icon(Icons.sync, size: 20),
  191. onPressed: _handleReadWeight,
  192. ),
  193. ],
  194. ],
  195. ),
  196. ),
  197. ],
  198. );
  199. }
  200. void _handleReadWeight() {
  201. setState(() {
  202. _weight = null;
  203. _weightController.clear();
  204. _isReadingWeight = true;
  205. });
  206. // 重置后输入框获得焦点
  207. WidgetsBinding.instance.addPostFrameCallback((_) {
  208. if (mounted) {
  209. _weightFocusNode.requestFocus();
  210. }
  211. });
  212. // 设置3秒超时
  213. _timeoutTimer?.cancel(); // 取消之前的超时定时器
  214. _timeoutTimer = Timer(const Duration(seconds: 15), () {
  215. if (mounted && _isReadingWeight == true) {
  216. setState(() {
  217. _weight = null;
  218. _isReadingWeight = false;
  219. _weightFocusNode.unfocus();
  220. });
  221. ToastUtil.error('读取重量超时');
  222. }
  223. });
  224. }
  225. // 提交数据
  226. void _handleSubmit() {
  227. final data = {
  228. "electronicId": _electronicId,
  229. "weight": _weight,
  230. 'date': DateTimeUtil.format(DateTime.now()),
  231. };
  232. apis.breeding.submitApi.weight(data).then((res) {
  233. if (res.success) {
  234. ToastUtil.success(res.message.isNotEmpty ? res.message : '称重提交成功');
  235. if (mounted) {
  236. // 提交后重置表单
  237. setState(() {
  238. _electronicId = null;
  239. _weight = null;
  240. _weightController.clear();
  241. });
  242. }
  243. } else {
  244. ToastUtil.errorAlert(res.message.isNotEmpty ? res.message : '称重提交失败');
  245. }
  246. });
  247. }
  248. }