|
@@ -0,0 +1,343 @@
|
|
|
|
|
+import 'dart:async';
|
|
|
|
|
+import 'dart:io';
|
|
|
|
|
+import 'package:chicken_farm/components/vb_dict_label.dart';
|
|
|
|
|
+import 'package:chicken_farm/core/utils/logger.dart';
|
|
|
|
|
+import 'package:flutter/material.dart';
|
|
|
|
|
+import 'package:chicken_farm/apis/breeding/_query.dart';
|
|
|
|
|
+import 'package:chicken_farm/components/vb_electronic_id_field.dart';
|
|
|
|
|
+import 'package:chicken_farm/components/vb_win_electronic_id_field.dart';
|
|
|
|
|
+import 'package:chicken_farm/core/utils/toast.dart';
|
|
|
|
|
+import 'package:chicken_farm/modes/breeding/chicken.dart';
|
|
|
|
|
+import 'package:intl/intl.dart';
|
|
|
|
|
+
|
|
|
|
|
+class IndividualQueryPage extends StatefulWidget {
|
|
|
|
|
+ const IndividualQueryPage({super.key});
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ State<IndividualQueryPage> createState() => _IndividualQueryPageState();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class _IndividualQueryPageState extends State<IndividualQueryPage> {
|
|
|
|
|
+ final BreedQueryApi _breedQueryApi = BreedQueryApi();
|
|
|
|
|
+ ChickenModel? _chicken;
|
|
|
|
|
+ String? _electronicId;
|
|
|
|
|
+ bool _isLoading = false;
|
|
|
|
|
+ final TextEditingController _idController = TextEditingController();
|
|
|
|
|
+ Timer? _debounceTimer; // 添加Timer变量用于管理延时操作
|
|
|
|
|
+
|
|
|
|
|
+ // 添加手动模式状态
|
|
|
|
|
+ bool _isManualMode = false;
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ void initState() {
|
|
|
|
|
+ super.initState();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ void dispose() {
|
|
|
|
|
+ // 取消任何待处理的计时器
|
|
|
|
|
+ if (_debounceTimer != null) {
|
|
|
|
|
+ _debounceTimer!.cancel();
|
|
|
|
|
+ }
|
|
|
|
|
+ _idController.dispose();
|
|
|
|
|
+ super.dispose();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void _queryChickenOnce(String id) {
|
|
|
|
|
+ if (_debounceTimer != null) {
|
|
|
|
|
+ _debounceTimer!.cancel();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _debounceTimer = Timer(Duration(milliseconds: 800), () {
|
|
|
|
|
+ _queryChicken(id);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Future<void> _queryChicken(String id) async {
|
|
|
|
|
+ if (id.isEmpty) {
|
|
|
|
|
+ ToastUtil.warning('请输入电子编号');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ final chicken = await _breedQueryApi.queryChicken(id.trim());
|
|
|
|
|
+ if (chicken != null) {
|
|
|
|
|
+ if (mounted) {
|
|
|
|
|
+ // 遵循Flutter异步操作与UI更新安全规范
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _chicken = chicken;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (mounted) {
|
|
|
|
|
+ // 遵循Flutter异步操作与UI更新安全规范
|
|
|
|
|
+ ToastUtil.error('未找到该编号对应的个体信息');
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _chicken = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (mounted) {
|
|
|
|
|
+ // 遵循Flutter异步操作与UI更新安全规范
|
|
|
|
|
+ ToastUtil.error('查询失败: $e');
|
|
|
|
|
+ logger.e(e);
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _chicken = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (mounted) {
|
|
|
|
|
+ // 遵循Flutter异步操作与UI更新安全规范
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 手动查询方法
|
|
|
|
|
+ void _manualQueryChicken() {
|
|
|
|
|
+ if (_idController.text.isEmpty) {
|
|
|
|
|
+ ToastUtil.warning('请输入电子编号');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ _queryChicken(_idController.text);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
|
+ return Scaffold(
|
|
|
|
|
+ appBar: AppBar(title: const Text('个体查询')),
|
|
|
|
|
+ body: Padding(
|
|
|
|
|
+ padding: const EdgeInsets.all(16.0),
|
|
|
|
|
+ child: Column(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ // 按钮行 - 手动/自动切换按钮
|
|
|
|
|
+ Row(
|
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.end, // 靠右对齐
|
|
|
|
|
+ children: [
|
|
|
|
|
+ // 手动/自动切换按钮
|
|
|
|
|
+ SizedBox(
|
|
|
|
|
+ // 设置固定宽度
|
|
|
|
|
+ width: 120,
|
|
|
|
|
+ child: ElevatedButton(
|
|
|
|
|
+ onPressed: () {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isManualMode = !_isManualMode;
|
|
|
|
|
+ _idController.clear();
|
|
|
|
|
+ _chicken = null;
|
|
|
|
|
+ _electronicId = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ backgroundColor: _isManualMode
|
|
|
|
|
+ ? Colors.blue
|
|
|
|
|
+ : Colors.orange,
|
|
|
|
|
+ foregroundColor: Colors.white,
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Text(_isManualMode ? '识别查询' : '手动查询'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 8), // 添加一点间距
|
|
|
|
|
+ // 电子标签输入区域 - 根据模式动态显示
|
|
|
|
|
+ if (!_isManualMode) ...[
|
|
|
|
|
+ _buildPlatformSpecificField(),
|
|
|
|
|
+ ] else ...[
|
|
|
|
|
+ // 手动输入模式
|
|
|
|
|
+ Row(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: TextField(
|
|
|
|
|
+ controller: _idController,
|
|
|
|
|
+ decoration: const InputDecoration(
|
|
|
|
|
+ labelText: '电子编号',
|
|
|
|
|
+ border: OutlineInputBorder(),
|
|
|
|
|
+ hintText: '请输入电子编号',
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(width: 8),
|
|
|
|
|
+ ElevatedButton(
|
|
|
|
|
+ onPressed: _manualQueryChicken,
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ backgroundColor: Colors.green,
|
|
|
|
|
+ foregroundColor: Colors.white,
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: const Text('查询'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+ // 个体信息展示区域
|
|
|
|
|
+ if (_chicken != null) ...[
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: SingleChildScrollView(
|
|
|
|
|
+ child: Card(
|
|
|
|
|
+ child: Padding(
|
|
|
|
|
+ padding: const EdgeInsets.all(16.0),
|
|
|
|
|
+ child: Column(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ // 基本信息
|
|
|
|
|
+ const Text(
|
|
|
|
|
+ '基本信息',
|
|
|
|
|
+ style: TextStyle(
|
|
|
|
|
+ fontSize: 18,
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 10),
|
|
|
|
|
+ _buildInfoRow('电子编号:', _chicken!.electronicId),
|
|
|
|
|
+ _buildInfoRow('批次号:', _chicken!.batchNum),
|
|
|
|
|
+ _buildInfoRow('家系编号:', _chicken!.familyNum),
|
|
|
|
|
+ _buildInfoRow('笼号:', _chicken!.cageNum ?? '无'),
|
|
|
|
|
+ _buildInfoRow2(
|
|
|
|
|
+ '性别:',
|
|
|
|
|
+ VberDictLabel(
|
|
|
|
|
+ dictType: "chicken_gender",
|
|
|
|
|
+ value: _chicken!.gender.toString(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ _buildInfoRow(
|
|
|
|
|
+ '孵化日期:',
|
|
|
|
|
+ DateFormat(
|
|
|
|
|
+ 'yyyy-MM-dd',
|
|
|
|
|
+ ).format(_chicken!.hatchDate),
|
|
|
|
|
+ ),
|
|
|
|
|
+ if (_chicken!.currentAge != null)
|
|
|
|
|
+ _buildInfoRow('当前日龄:', '${_chicken!.currentAge} 天'),
|
|
|
|
|
+ _buildInfoRow2(
|
|
|
|
|
+ '养殖状态:',
|
|
|
|
|
+ VberDictLabel(
|
|
|
|
|
+ dictType: "chicken_status",
|
|
|
|
|
+ value: _chicken!.status.toString(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ if (_chicken!.cullReason != null)
|
|
|
|
|
+ _buildInfoRow2(
|
|
|
|
|
+ '淘汰原因:',
|
|
|
|
|
+ VberDictLabel(
|
|
|
|
|
+ dictType: "chicken_cull_reason",
|
|
|
|
|
+ value: _chicken!.cullReason.toString(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ if (_chicken!.disposalMethod != null)
|
|
|
|
|
+ _buildInfoRow2(
|
|
|
|
|
+ '处理方式:',
|
|
|
|
|
+ VberDictLabel(
|
|
|
|
|
+ dictType: "chicken_disposal_method",
|
|
|
|
|
+ value: _chicken!.disposalMethod.toString(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ if (_chicken!.cullTime != null)
|
|
|
|
|
+ _buildInfoRow(
|
|
|
|
|
+ '淘汰时间:',
|
|
|
|
|
+ DateFormat(
|
|
|
|
|
+ 'yyyy-MM-dd HH:mm:ss',
|
|
|
|
|
+ ).format(_chicken!.cullTime!),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 20),
|
|
|
|
|
+
|
|
|
|
|
+ // SOP信息
|
|
|
|
|
+ // const Text(
|
|
|
|
|
+ // 'SOP信息',
|
|
|
|
|
+ // style: TextStyle(
|
|
|
|
|
+ // fontSize: 18,
|
|
|
|
|
+ // fontWeight: FontWeight.bold,
|
|
|
|
|
+ // ),
|
|
|
|
|
+ // ),
|
|
|
|
|
+ // const SizedBox(height: 10),
|
|
|
|
|
+ // _buildInfoRow('饲料SOP:', _chicken!.feedSopName ?? '无'),
|
|
|
|
|
+ // _buildInfoRow('用药SOP:', _chicken!.drugSopName ?? '无'),
|
|
|
|
|
+ // _buildInfoRow(
|
|
|
|
|
+ // '疫苗SOP:',
|
|
|
|
|
+ // _chicken!.vaccineSopName ?? '无',
|
|
|
|
|
+ // ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ] else if (!_isLoading)
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: Center(
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ '请${_isManualMode ? "输入" : "识别"}电子编号查询',
|
|
|
|
|
+ style: Theme.of(
|
|
|
|
|
+ context,
|
|
|
|
|
+ ).textTheme.titleMedium?.copyWith(color: Colors.grey[600]),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 根据平台构建特定字段组件
|
|
|
|
|
+ Widget _buildPlatformSpecificField() {
|
|
|
|
|
+ return VberElectronicIdsField(
|
|
|
|
|
+ electronicId: _electronicId,
|
|
|
|
|
+ scanMilliseconds: 1000,
|
|
|
|
|
+ onIdScanned: (id) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _electronicId = id;
|
|
|
|
|
+ });
|
|
|
|
|
+ _queryChickenOnce(id);
|
|
|
|
|
+ },
|
|
|
|
|
+ label: '电子编号',
|
|
|
|
|
+ placeholder: '未识别',
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Widget _buildInfoRow(String label, String value) {
|
|
|
|
|
+ return Padding(
|
|
|
|
|
+ padding: const EdgeInsets.only(bottom: 8.0),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ SizedBox(
|
|
|
|
|
+ width: 80,
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ label,
|
|
|
|
|
+ style: const TextStyle(fontWeight: FontWeight.w500),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ Expanded(
|
|
|
|
|
+ child: Text(value, style: const TextStyle(color: Colors.black87)),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Widget _buildInfoRow2(String label, Widget value) {
|
|
|
|
|
+ return Padding(
|
|
|
|
|
+ padding: const EdgeInsets.only(bottom: 8.0),
|
|
|
|
|
+ child: Row(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ SizedBox(
|
|
|
|
|
+ width: 80,
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ label,
|
|
|
|
|
+ style: const TextStyle(fontWeight: FontWeight.w500),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ value,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|