Browse Source

库存模块功能开发。

liujq 1 month ago
parent
commit
5ea6ec40cb

+ 3 - 0
lib/model/rsp/sample_task_rsp.dart

@@ -697,6 +697,7 @@ class SampleTaskItemKcjcRsp {
697 697
 
698 698
   /// 扦样人员姓名
699 699
   String? name;
700
+  String? dgryName; // 扦样多个人员名称: 张三,李四
700 701
 
701 702
   /// 任务类型
702 703
   num? rwlx;
@@ -806,6 +807,7 @@ class SampleTaskItemKcjcRsp {
806 807
     this.qyrwdh,
807 808
     this.ypbh,
808 809
     this.name,
810
+    this.dgryName,
809 811
     this.rwlx,
810 812
     this.dwmc,
811 813
     this.cypzmc,
@@ -862,6 +864,7 @@ class SampleTaskItemKcjcRsp {
862 864
     map['qyssclw'] = qyssclw;
863 865
     map['cljsss'] = cljsss;
864 866
     map['bz'] = bz;
867
+    map['dgryName'] = dgryName;
865 868
 
866 869
     return map;
867 870
   }

+ 2 - 0
lib/model/rsp/sample_task_rsp.g.dart

@@ -496,6 +496,7 @@ SampleTaskItemKcjcRsp _$SampleTaskItemKcjcRspFromJson(
496 496
       qyrwdh: const StringConverter().fromJson(json['qyrwdh']),
497 497
       ypbh: const StringConverter().fromJson(json['ypbh']),
498 498
       name: const StringConverter().fromJson(json['name']),
499
+      dgryName: const StringConverter().fromJson(json['dgryName']),
499 500
       rwlx: const NumConverter().fromJson(json['rwlx']),
500 501
       dwmc: const StringConverter().fromJson(json['dwmc']),
501 502
       cypzmc: const NumConverter().fromJson(json['cypzmc']),
@@ -569,6 +570,7 @@ Map<String, dynamic> _$SampleTaskItemKcjcRspToJson(
569 570
       'qyrwdh': const StringConverter().toJson(instance.qyrwdh),
570 571
       'ypbh': const StringConverter().toJson(instance.ypbh),
571 572
       'name': const StringConverter().toJson(instance.name),
573
+      'dgryName': const StringConverter().toJson(instance.dgryName),
572 574
       'rwlx': const NumConverter().toJson(instance.rwlx),
573 575
       'dwmc': const StringConverter().toJson(instance.dwmc),
574 576
       'cypzmc': const NumConverter().toJson(instance.cypzmc),

+ 2 - 2
lib/page/home/store_house_page.dart

@@ -46,9 +46,9 @@ class _StoreHousePageState extends BaseLifecycleState<StoreHousePage> {
46 46
       //       .toList()
47 47
       //       : List.empty(),
48 48
       // );
49
-      var res = await MyApi.get(baseUrl: 'http://101.36.160.117:28088')
49
+      var res = await MyApi.get(baseUrl: 'http://121.36.17.6:28088')
50 50
           .getStoreMessage(widget.houseNum, 'zhijian');
51
-      if (res.data == null) {
51
+      if (res.data?.first.tycfbm == null) {
52 52
         pageState.update(pageState.value.empty());
53 53
       } else {
54 54
         data = res.data!.first;

+ 9 - 3
lib/page/qrcode_scan/qrcode_scan_page.dart

@@ -9,7 +9,8 @@ import '../../base/base_lifecycle_state.dart';
9 9
 import '../../widget/button.dart';
10 10
 
11 11
 class QrCodeScanPage extends StatefulWidget {
12
-  const QrCodeScanPage({super.key});
12
+  final bool isKC;
13
+  const QrCodeScanPage({super.key, this.isKC = false, });
13 14
 
14 15
   @override
15 16
   State<QrCodeScanPage> createState() => _QrCodeScanPageState();
@@ -45,8 +46,13 @@ class _QrCodeScanPageState extends BaseLifecycleState<QrCodeScanPage> with Singl
45 46
         showErrorDialog(code);
46 47
         return;
47 48
       }else{
48
-        MyRouter.startStoreHouse(list.last);
49
-        return;
49
+        if(widget.isKC){
50
+          MyNavigator.pop(list.last);
51
+          return;
52
+        }else{
53
+          MyRouter.startStoreHouse(list.last);
54
+          return;
55
+        }
50 56
       }
51 57
     }
52 58
 

+ 1 - 0
lib/page/sample_task/reap_sample_detail/reap_sample_basic_detail_page.dart

@@ -640,6 +640,7 @@ class _ReapSampleBasicDetailPageState extends BaseLifecycleState<ReapSampleBasic
640 640
               CardItemWidget('扦样人员', rightText: data?.name, bottomLine: true),
641 641
               otherpeople.builder((v) => CardItemWidget(
642 642
                 '陪同人员',
643
+                rightRatio: 2,
643 644
                 rightText: v,
644 645
                 onTap: isDetail ? null : showPopView,
645 646
                 trailing: isDetail ? null : const Icon(Icons.keyboard_arrow_down, size: 24, color: Color(0xFF01B2C8),),

+ 0 - 1
lib/page/sample_task/reap_sample_detail/reap_sample_task_page.dart

@@ -685,7 +685,6 @@ class _ReapSampleTaskPageState extends BaseLifecycleState<ReapSampleTaskPage> wi
685 685
           margin: const EdgeInsets.symmetric(horizontal: 8),
686 686
             clipBehavior: Clip.hardEdge,
687 687
             decoration: const BoxDecoration(
688
-             // color: Colors.white,
689 688
               borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
690 689
             ),
691 690
             child: pageList[index]);

+ 27 - 1
lib/page/sample_task/sample_task_list_page.dart

@@ -94,6 +94,31 @@ class _SampleTaskListPageState extends BaseLifecycleState<SampleTaskListPage>
94 94
     }
95 95
   }
96 96
 
97
+  Widget testWidget(){
98
+    return Align(
99
+      child: Column(
100
+        mainAxisSize: MainAxisSize.min,
101
+        children: [
102
+          TextButton(
103
+            onPressed: (){
104
+              MyRouter.startStockSampleTask(
105
+                  args: StockSampleTaskPageArgs(detail: false, id: 3567)); // 3568  3567 3566
106
+            },
107
+            child: const Text('库存 - 编辑'),
108
+          ),
109
+          const SizedBox(height: 22),
110
+          TextButton(
111
+            onPressed: (){
112
+              MyRouter.startStockSampleTask(
113
+                  args: StockSampleTaskPageArgs(detail: true, id: 3567)); // 3568  3567 3566
114
+            },
115
+            child: const Text('库存 - 展示'),
116
+          ),
117
+        ],
118
+      ),
119
+    );
120
+  }
121
+
97 122
   @override
98 123
   void onInit() {
99 124
     vm = Inject.get<SampleListVM>()!;
@@ -109,7 +134,8 @@ class _SampleTaskListPageState extends BaseLifecycleState<SampleTaskListPage>
109 134
     super.build(context);
110 135
     return Stack(
111 136
       children: [
112
-        buildBody(),
137
+        widget.type == StepType.reap ? buildBody() : testWidget(),
138
+       // buildBody(),
113 139
         buildPrintBtn(),
114 140
       ],
115 141
     );

+ 167 - 55
lib/page/sample_task/stock_sample_detail/stock_sample_basic_page.dart

@@ -1,12 +1,12 @@
1
-import 'dart:convert';
2
-
3 1
 import 'package:flutter/material.dart';
4 2
 import 'package:flutter/services.dart';
5
-import 'package:lszlgl/utils/input_formatter.dart';
3
+import 'package:lszlgl/main.dart';
4
+import 'package:lszlgl/model/rsp/dict_rsp.dart';
5
+import 'package:lszlgl/network/my_api.dart';
6
+import 'package:lszlgl/service/user_service.dart';
7
+import 'package:lszlgl/widget/qypop_widget.dart';
6 8
 import '../../../base/base_lifecycle_state.dart';
7 9
 import '../../../model/rsp/sample_task_rsp.dart';
8
-import '../../../service/dict_service.dart';
9
-import '../../../utils/date_time_utils.dart';
10 10
 import '../../../widget/card_item.dart';
11 11
 
12 12
 /// 库存扦样-基础信息
@@ -27,13 +27,88 @@ class StockSampleBasicPage extends StatefulWidget {
27 27
 class _StockSampleBasicPageState extends BaseLifecycleState<StockSampleBasicPage> with AutomaticKeepAliveClientMixin {
28 28
   SampleTaskItemKcjcRsp? data;
29 29
   late bool isDetail;
30
-  final rksj = null.notifier<String?>();
31 30
 
32
-  final rkdjList = <CardMenuData>[].notifier<List<CardMenuData>>();
33
-  final rkdj = null.notifier<CardMenuData?>();
31
+  final otherpeople = null.notifier<String?>();
32
+  List<OtherPeopleModel> personList = [];
33
+  String otherStr = '';
34
+
35
+  final jyjgList = <CardMenuData>[].notifier<List<CardMenuData>>();
36
+  final jyjg = null.notifier<CardMenuData?>();
37
+
38
+  /// 检验机构数据
39
+  Future<void> getJyJgList() async {
40
+    try {
41
+      var rsp = await MyApi.get().trxxList();
42
+      List<CardMenuData> list = [];
43
+      if (rsp.data != null) {
44
+        for (TrxxRsp item in rsp.data!) {
45
+          var menuData = CardMenuData(item.tlmc, item.id);
46
+          list.add(menuData);
47
+          // 选中
48
+         // if (item.id == data?.trdllx) trxx.value = menuData;
49
+        }
50
+      }
51
+      jyjgList.value = list;
52
+    } catch (e) {
53
+      logger.e(e);
54
+    }
55
+    MyNavigator.dismissLoading();
56
+  }
34 57
 
35
-  final guoChandiList = <CardMenuData>[].notifier<List<CardMenuData>>();
36
-  final guoChandi = null.notifier<CardMenuData?>();
58
+  // 选择陪同人员
59
+  void showPopView() {
60
+    showModalBottomSheet(
61
+        isDismissible: false,
62
+        enableDrag: false,
63
+        isScrollControlled: true,
64
+        shape: const RoundedRectangleBorder(
65
+            borderRadius:
66
+            BorderRadius.only(topLeft: Radius.circular(14), topRight: Radius.circular(14))),
67
+        context: context,
68
+        builder: (context) {
69
+          return QyPopWidget(
70
+            pTitleList: personList,
71
+            otherStr: otherStr,
72
+            pTitleCallBack: (val, other) {
73
+              otherStr = other;
74
+              if (val.isEmpty) {
75
+                data?.dgryName = null;
76
+                otherpeople.value = null;
77
+              } else {
78
+                data?.dgryName = val;
79
+                otherpeople.value = val;
80
+              }
81
+            },
82
+          );
83
+        });
84
+  }
85
+
86
+  void getPersonData() async {
87
+    try {
88
+      // 获取扦样人员列表
89
+      var rsp = await MyApi.get().getPersonMenu(data?.jhcyjg ?? data?.zjBaseEnterpriseId ?? 0);
90
+      personList
91
+        ..clear()
92
+        ..addAll((rsp.data ?? []).map((e) => OtherPeopleModel(name: e.name)).toList());
93
+      personList.removeWhere((item) => item.name == data?.name || item.name ==null); // 移除可能存在的默认人员和名字为空的
94
+      // 选中扦样人员
95
+      var nameList = data?.dgryName?.split(',');
96
+      if (nameList != null) {
97
+        for (var person in personList) {
98
+          for (var name in nameList) {
99
+            if (person.name == name) {
100
+              person.isChose = true;
101
+              nameList.remove(name);
102
+              break;
103
+            }
104
+          }
105
+        }
106
+        otherStr = nameList.join(','); // 输入人员名字赋值
107
+      }
108
+    } catch (e) {
109
+      logger.e(e);
110
+    }
111
+  }
37 112
 
38 113
   @override
39 114
   bool get wantKeepAlive => true;
@@ -43,23 +118,25 @@ class _StockSampleBasicPageState extends BaseLifecycleState<StockSampleBasicPage
43 118
     data = widget.data;
44 119
     isDetail = widget.detail;
45 120
 
46
-    // 入库时间
47
-    rksj.value = data?.rksj;
48
-    // 入库等级
49
-    rkdjList.value = [1, 2, 3, 4, 5, 6].map((e) {
50
-      var value = CardMenuData(e.toString(), e);
51
-      if (e == data?.rkdj) rkdj.value = value;
52
-      return value;
53
-    }).toList();
54
-    // 产地国
55
-    rootBundle.loadString('assets/json/shijie.json').then((jsonValue) {
56
-      Map<String, dynamic> map = jsonDecode(jsonValue);
57
-      guoChandiList.value = map.values.map((e) {
58
-        var value = CardMenuData(e.toString(), e);
59
-        if (e == data?.guoChandi) guoChandi.value = value;
60
-        return value;
61
-      }).toList();
62
-    });
121
+    if ((data?.name ?? '0') == '0') {
122
+      data?.name = UserService.get().getUser()?.nickname;
123
+    }
124
+    var nameList = data?.dgryName?.split(',');
125
+    if (nameList != null) {
126
+      nameList.removeWhere((item) => item == data?.name);
127
+      if (nameList.isNotEmpty) {
128
+        data?.dgryName = nameList.join(',');
129
+      } else {
130
+        data?.dgryName = null;
131
+      }
132
+    }
133
+    otherpeople.value = data?.dgryName;
134
+
135
+    if(!isDetail){
136
+      getPersonData();
137
+      getJyJgList();
138
+    }
139
+
63 140
   }
64 141
 
65 142
   @override
@@ -71,36 +148,71 @@ class _StockSampleBasicPageState extends BaseLifecycleState<StockSampleBasicPage
71 148
   Widget buildList() {
72 149
     return Column(
73 150
       children: [
74
-        CardItemWidget('扦样任务单号', rightText: data?.qyrwdh, bottomLine: true),
75
-        CardItemWidget('扦样单位', rightText: data?.dwmc, bottomLine: true),
76
-        CardItemWidget('扦样人员', rightText: data?.name, bottomLine: true),
77
-        CardItemWidget('监测类别', rightText: DictService.getLabel(DictType.jclb, value: data?.jclb), bottomLine: true),
78
-        CardItemWidget('承储企业名称', rightText: data?.ccqymc, bottomLine: true),
79
-        CardItemWidget('扦样仓房', rightText: data?.qycf, bottomLine: true),
80
-        CardItemWidget('扦样货位', rightText: data?.qyhw, bottomLine: true),
81
-        CardWidgets.buildEdit(
82
-          isDetail,
83
-          '扦样区域',
84
-          data?.qyqy,
85
-          onChanged: (value) => data?.qyqy = value,
86
-        ),
87
-        CardItemWidget('仓型', rightText: data?.cangxing, bottomLine: true),
88
-        CardWidgets.buildDate(
89
-          isDetail,
90
-          '入库时间',
91
-          rksj,
92
-          onResult: (value) => data?.rksj = DateTimeUtils.yyyymmdd(date: value),
151
+        Container(
152
+          clipBehavior: Clip.hardEdge,
153
+          decoration: const BoxDecoration(
154
+            borderRadius: BorderRadius.all(Radius.circular(8)),
155
+          ),
156
+          child: Column(
157
+            children: [
158
+              CardItemWidget('样品单号', rightText: data?.qyrwdh, bottomLine: true, rightRatio: 3,
159
+                  onTap: () async{
160
+                    await Clipboard.setData(ClipboardData(text: data?.qyrwdh ?? '' ));
161
+                    MyNavigator.showToast('样品单号已复制');
162
+                  }
163
+              ),
164
+              CardItemWidget('检验样品单号', rightText: '${data?.qyrwdh}-1', bottomLine: true,leftRatio: 2,rightRatio: 3,),
165
+              CardItemWidget('复核样品单号', rightText: '${data?.qyrwdh}-2', bottomLine: true,leftRatio: 2,rightRatio: 3,),
166
+              CardItemWidget('备检样品单号', rightText: '${data?.qyrwdh}-3', bottomLine: true,leftRatio: 2,rightRatio: 3,),
167
+            ],
168
+          ),
93 169
         ),
94
-        CardWidgets.buildMenu(isDetail, '入库等级', rkdjList, rkdj, (_, sel) => data?.rkdj = sel.value),
95
-        CardWidgets.buildMenu(isDetail, '产地(进口国)', guoChandiList, guoChandi, (_, sel) => data?.guoChandi = sel.value),
96
-        CardWidgets.buildEdit(
97
-          isDetail,
98
-          '上层粮温(℃)',
99
-          data?.sclw?.toString(),
100
-          inputType: const TextInputType.numberWithOptions(decimal: true),
101
-          formatters: [XNumberTextInputFormatter()],
102
-          onChanged: (value) => data?.sclw = value.isEmpty ? null : num.parse(value),
170
+
171
+        const SizedBox(height: 12),
172
+
173
+        Container(
174
+          clipBehavior: Clip.hardEdge,
175
+          decoration: const BoxDecoration(
176
+            borderRadius: BorderRadius.all(Radius.circular(8)),
177
+          ),
178
+          child: Column(
179
+            children: [
180
+              CardItemWidget('扦样人员', rightText: data?.name, bottomLine: true),
181
+              otherpeople.builder((v) => CardItemWidget(
182
+                '陪同人员',
183
+                bottomLine: true,
184
+                rightRatio: 2,
185
+                rightText: v,
186
+                onTap: isDetail ? null : showPopView,
187
+                trailing: isDetail ? null : const Icon(Icons.keyboard_arrow_down, size: 24, color: Color(0xFF01B2C8),),
188
+              )),
189
+              CardWidgets.buildMenu(
190
+                isDetail,
191
+                '检测机构',
192
+                jyjgList,
193
+                jyjg,
194
+                    (_, sel) {
195
+                  // data?.trdllx = sel.value;
196
+                },
197
+              ),
198
+              CardItemWidget('承储企业名称', rightText: data?.ccqymc, bottomLine: true,leftRatio: 2,rightRatio: 3,),
199
+              CardItemWidget('扦样仓房', rightText: data?.qycf, bottomLine: true,rightRatio: 3,),
200
+              CardItemWidget('扦样货位', rightText: data?.qyhw, bottomLine: true,rightRatio: 3,),
201
+              CardItemWidget('仓型', rightText: data?.cangxing, bottomLine: true,rightRatio: 3,),
202
+              CardItemWidget('所在货位粮食总数(吨)', rightText: data?.szhwlszs, bottomLine: true,leftRatio: 2,),
203
+              CardWidgets.buildEdit(
204
+                isDetail,
205
+                '扦样区域',
206
+                rightRatio: 3,
207
+                data?.qyqy,
208
+                onChanged: (value) => data?.qyqy = value,
209
+              ),
210
+            ],
211
+          ),
103 212
         ),
213
+
214
+        // CardItemWidget('扦样单位', rightText: data?.dwmc, bottomLine: true),
215
+        //  CardItemWidget('监测类别', rightText: DictService.getLabel(DictType.jclb, value: data?.jclb), bottomLine: true),
104 216
       ],
105 217
     );
106 218
   }

+ 119 - 33
lib/page/sample_task/stock_sample_detail/stock_sample_store_page.dart

@@ -1,4 +1,8 @@
1
+import 'dart:io';
2
+
1 3
 import 'package:flutter/material.dart';
4
+import 'package:lszlgl/main.dart';
5
+import 'package:lszlgl/widget/photo_card_item.dart';
2 6
 import '../../../base/base_lifecycle_state.dart';
3 7
 import '../../../model/rsp/sample_task_rsp.dart';
4 8
 import '../../../utils/date_time_utils.dart';
@@ -21,7 +25,8 @@ class StockSampleStorePage extends StatefulWidget {
21 25
   State<StockSampleStorePage> createState() => _StockSampleStorePageState();
22 26
 }
23 27
 
24
-class _StockSampleStorePageState extends BaseLifecycleState<StockSampleStorePage> with AutomaticKeepAliveClientMixin {
28
+class _StockSampleStorePageState extends BaseLifecycleState<StockSampleStorePage>
29
+    with AutomaticKeepAliveClientMixin {
25 30
   SampleTaskItemKcjcRsp? data;
26 31
   late bool isDetail;
27 32
 
@@ -33,6 +38,7 @@ class _StockSampleStorePageState extends BaseLifecycleState<StockSampleStorePage
33 38
 
34 39
   final chfsqkList = <CardMenuData>[].notifier<List<CardMenuData>>();
35 40
   final chfsqk = null.notifier<CardMenuData?>();
41
+  late TextEditingController syyjmcCtrl;
36 42
 
37 43
   void setNumBoolMenuSelNotifier(ValueNotifier<CardMenuData?> notifier, num? value) {
38 44
     if (value == null) return;
@@ -56,6 +62,8 @@ class _StockSampleStorePageState extends BaseLifecycleState<StockSampleStorePage
56 62
       if (e == data?.chfsqk) chfsqk.value = value;
57 63
       return value;
58 64
     }).toList();
65
+
66
+    syyjmcCtrl = TextEditingController(text: data?.syyjmc);
59 67
   }
60 68
 
61 69
   @override
@@ -67,39 +75,117 @@ class _StockSampleStorePageState extends BaseLifecycleState<StockSampleStorePage
67 75
   Widget buildList() {
68 76
     return Column(
69 77
       children: [
70
-        CardWidgets.buildMenu(isDetail, '是否施药', sfList, sfsy, (_, sel) => data?.sfsy = sel.value),
71
-        CardWidgets.buildDate(
72
-          isDetail,
73
-          '最近施药时间',
74
-          zjsysj,
75
-          onResult: (value) => data?.zjsysj = DateTimeUtils.yyyymmdd(date: value),
76
-        ),
77
-        CardWidgets.buildEdit(
78
-          isDetail,
79
-          '使用药剂名称',
80
-          data?.syyjmc,
81
-          onChanged: (value) => data?.syyjmc = value,
82
-        ),
83
-        CardWidgets.buildMenu(isDetail, '虫害发生情况', chfsqkList, chfsqk, (_, sel) => data?.chfsqk = sel.value),
84
-        CardWidgets.buildEdit(
85
-          isDetail,
86
-          '扦样时上层粮温(℃)',
87
-          data?.qyssclw?.toString(),
88
-          inputType: const TextInputType.numberWithOptions(decimal: true),
89
-          formatters: [XNumberTextInputFormatter()],
90
-          onChanged: (value) => data?.qyssclw = value.isEmpty ? null : num.parse(value),
91
-        ),
92
-        CardWidgets.buildEdit(
93
-          isDetail,
94
-          '储粮技术设施',
95
-          data?.cljsss,
96
-          onChanged: (value) => data?.cljsss = value,
78
+        Container(
79
+          clipBehavior: Clip.hardEdge,
80
+          decoration: const BoxDecoration(
81
+            borderRadius: BorderRadius.all(Radius.circular(8)),
82
+          ),
83
+          child: Column(
84
+            children: [
85
+              CardWidgets.buildMenu(isDetail, '是否施药', sfList, sfsy, (_, sel) {
86
+                data?.sfsy = sel.value;
87
+                if (sel.value == 1) {
88
+                  zjsysj.value = null;
89
+                  data?.zjsysj = null;
90
+                  syyjmcCtrl.text = '';
91
+                  data?.syyjmc = null;
92
+                }
93
+              }),
94
+              sfsy.builder((v) => CardWidgets.buildDate(
95
+                    isDetail,
96
+                    '最近施药时间',
97
+                    zjsysj,
98
+                    warmString: data?.sfsy == 1 ? '请选择是否施药' : null,
99
+                    onResult: (value) => data?.zjsysj = DateTimeUtils.yyyymmdd(date: value),
100
+                  )),
101
+              CardWidgets.buildEdit(
102
+                isDetail,
103
+                '使用药剂名称',
104
+                data?.syyjmc,
105
+                ctrl: syyjmcCtrl,
106
+                leftRatio: 2,
107
+                rightRatio: 3,
108
+                onChanged: (value) {
109
+                  if (data?.sfsy == 1) {
110
+                    syyjmcCtrl.text = '';
111
+                    MyNavigator.showToast('请选择是否施药');
112
+                    return;
113
+                  }
114
+                  data?.syyjmc = value;
115
+                },
116
+              ),
117
+              CardWidgets.buildEdit(
118
+                isDetail,
119
+                '储粮技术设施',
120
+                leftRatio: 2,
121
+                rightRatio: 3,
122
+                data?.cljsss,
123
+                onChanged: (value) => data?.cljsss = value,
124
+              ),
125
+              CardWidgets.buildMenu(
126
+                isDetail,
127
+                '虫害发生情况',
128
+                leftRatio: 2,
129
+                rightRatio: 3,
130
+                chfsqkList,
131
+                chfsqk,
132
+                (_, sel) => data?.chfsqk = sel.value,
133
+              ),
134
+              CardWidgets.buildEdit(
135
+                isDetail,
136
+                '扦样时上层粮温(℃)',
137
+                leftRatio: 2,
138
+                data?.qyssclw?.toString(),
139
+                inputType: const TextInputType.numberWithOptions(decimal: true),
140
+                formatters: [XNumberTextInputFormatter()],
141
+                onChanged: (value) => data?.qyssclw = value.isEmpty ? null : num.parse(value),
142
+              ),
143
+              CardWidgets.buildEdit(
144
+                isDetail,
145
+                '备注',
146
+                rightRatio: 5,
147
+                data?.bz,
148
+                onChanged: (value) => data?.bz = value,
149
+              ),
150
+            ],
151
+          ),
97 152
         ),
98
-        CardWidgets.buildEdit(
99
-          isDetail,
100
-          '备注',
101
-          data?.bz,
102
-          onChanged: (value) => data?.bz = value,
153
+        const SizedBox(height: 12),
154
+        Container(
155
+          clipBehavior: Clip.hardEdge,
156
+          decoration: const BoxDecoration(
157
+            borderRadius: BorderRadius.all(Radius.circular(8)),
158
+          ),
159
+          child: Column(
160
+            children: [
161
+              PhotoCardItem(
162
+                title: '仓房照片',
163
+                isDetail: isDetail,
164
+                imgUrl: null,
165
+                filePathCallBack: (path) {
166
+                  final File imageFile = File(path);
167
+                  logger.d(imageFile);
168
+                },
169
+              ),
170
+              PhotoCardItem(
171
+                title: '样品照片',
172
+                isDetail: isDetail,
173
+              //  imgUrl: null,
174
+                filePathCallBack: (path) {
175
+
176
+                },
177
+              ),
178
+              PhotoCardItem(
179
+                title: '现场照片',
180
+                isDetail: isDetail,
181
+                imgUrl: 'http://121.36.17.6:19000/quality/app/zsys.png',
182
+                filePathCallBack: (path) {
183
+
184
+                },
185
+              ),
186
+
187
+            ],
188
+          ),
103 189
         ),
104 190
       ],
105 191
     );

+ 136 - 58
lib/page/sample_task/stock_sample_detail/stock_sample_task_page.dart

@@ -3,6 +3,7 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
3 3
 import 'package:lszlgl/base/base_lifecycle_state.dart';
4 4
 import 'package:lszlgl/config/colors.dart';
5 5
 import 'package:lszlgl/model/rsp/sample_task_rsp.dart';
6
+import 'package:lszlgl/model/rsp/storehouse_rsp.dart';
6 7
 
7 8
 import '../../../base/base_vm.dart';
8 9
 import '../../../main.dart';
@@ -116,6 +117,71 @@ class _StockSampleTaskPageState extends BaseLifecycleState<StockSampleTaskPage>
116 117
     ];
117 118
   }
118 119
 
120
+  void showBackDialog() {
121
+    showDialog<void>(
122
+      context: context,
123
+      builder: (BuildContext context) {
124
+        return AlertDialog(
125
+          titlePadding: const EdgeInsets.fromLTRB(8, 26, 8, 4),
126
+          actionsPadding:const EdgeInsets.symmetric(horizontal: 8.0,vertical: 4),
127
+          title: const Text('退出当前界面?',textAlign: TextAlign.center,),
128
+          actionsAlignment: MainAxisAlignment.spaceAround,
129
+          shape: RoundedRectangleBorder(
130
+            borderRadius: BorderRadius.circular(12.0),
131
+          ),
132
+          actions: <Widget>[
133
+            TextButton(
134
+              child: const Text('取消',style: TextStyle(color: Colors.grey),),
135
+              onPressed: () {
136
+                Navigator.pop(context);
137
+              },
138
+            ),
139
+            TextButton(
140
+              child: const Text('退出',style: TextStyle(color: MyColor.c_28A3ED,fontWeight: FontWeight.bold),),
141
+              onPressed: () {
142
+                Navigator.pop(context);
143
+                Navigator.pop(context);
144
+              },
145
+            ),
146
+          ],
147
+        );
148
+      },
149
+    );
150
+  }
151
+
152
+  Widget buildScan() {
153
+    return GestureDetector(
154
+      onTap: () async{
155
+        String? code = await MyRouter.startQrCodeScan(isKC: true);
156
+        if(code ==null){
157
+          MyNavigator.showToast('二维码错误');
158
+          return;
159
+        }
160
+        getScanData(code);
161
+      },
162
+      behavior: HitTestBehavior.opaque,
163
+      child: Container(
164
+        padding: const EdgeInsets.symmetric(horizontal: 16),
165
+        alignment: Alignment.center,
166
+        child: Image.asset(imgHomeScan, height: 24,width: 24),
167
+      ),
168
+    );
169
+  }
170
+
171
+  void getScanData(String code) async{
172
+    MyNavigator.showLoading(msg: '加载中');
173
+    var res = await MyApi.get(baseUrl: 'http://121.36.17.6:28088').getStoreMessage(code, 'zhijian');
174
+    MyNavigator.dismissLoading();
175
+    if (res.data?.first.tycfbm == null) {
176
+      MyNavigator.showToast('仓房信息错误');
177
+      return;
178
+    } else {
179
+      storehouseRsp? codeData = res.data!.first;
180
+      logger.d(codeData);
181
+
182
+    }
183
+  }
184
+
119 185
   @override
120 186
   void initState() {
121 187
     super.initState();
@@ -137,21 +203,33 @@ class _StockSampleTaskPageState extends BaseLifecycleState<StockSampleTaskPage>
137 203
 
138 204
   @override
139 205
   Widget build(BuildContext context) {
140
-    return myScaffold(
141
-      child: KeyboardDismissOnTap(
142
-        dismissOnCapturedTaps: true,
143
-        child: Column(
144
-          children: [
145
-            myAppBar(title: args.detail ? '扦样完成详情' : '扦样任务单'),
146
-            pageStatus.builder((v) {
147
-              return switch (v.status) {
148
-                DataStatus.loading => const PageLoadingWidget.loading(),
149
-                DataStatus.error => PageLoadingWidget.error(onTap: getDetailData),
150
-                DataStatus.success => buildBody(),
151
-                _ => const PageLoadingWidget.empty(),
152
-              };
153
-            }),
154
-          ],
206
+    return PopScope(
207
+      canPop: args.detail,
208
+      onPopInvoked: (bool didPop){
209
+        if(didPop){
210
+          return;
211
+        }
212
+        showBackDialog();
213
+      },
214
+      child: myScaffold(
215
+        child: KeyboardDismissOnTap(
216
+          dismissOnCapturedTaps: true,
217
+          child: Column(
218
+            children: [
219
+              myAppBar(
220
+                  title: args.detail ? '扦样完成详情' : '扦样任务单',
221
+                  actions: args.detail ? [] : [buildScan()]
222
+              ),
223
+              pageStatus.builder((v) {
224
+                return switch (v.status) {
225
+                  DataStatus.loading => const PageLoadingWidget.loading(),
226
+                  DataStatus.error => PageLoadingWidget.error(onTap: getDetailData),
227
+                  DataStatus.success => buildBody(),
228
+                  _ => const PageLoadingWidget.empty(),
229
+                };
230
+              }),
231
+            ],
232
+          ),
155 233
         ),
156 234
       ),
157 235
     );
@@ -159,19 +237,13 @@ class _StockSampleTaskPageState extends BaseLifecycleState<StockSampleTaskPage>
159 237
 
160 238
   Widget buildBody() {
161 239
     return Expanded(
162
-      child: Container(
163
-        clipBehavior: Clip.hardEdge,
164
-        decoration: const BoxDecoration(
165
-          color: Colors.white,
166
-          borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
167
-        ),
168
-        child: Column(
169
-          children: [
170
-            buildTab(),
171
-            Expanded(child: buildPage()),
172
-            buildButton(),
173
-          ],
174
-        ),
240
+      child: Column(
241
+        children: [
242
+          buildTab(),
243
+          const SizedBox(height: 12),
244
+          Expanded(child: buildPage()),
245
+          buildButton(),
246
+        ],
175 247
       ),
176 248
     );
177 249
   }
@@ -181,9 +253,11 @@ class _StockSampleTaskPageState extends BaseLifecycleState<StockSampleTaskPage>
181 253
       valueListenable: tabIndex,
182 254
       builder: (_, value, __) {
183 255
         return Container(
184
-          height: 40,
185
-          color: Colors.white,
186
-          padding: const EdgeInsets.symmetric(horizontal: 4),
256
+          margin: const EdgeInsets.symmetric(horizontal: 8),
257
+          decoration: const BoxDecoration(
258
+            color: Colors.white,
259
+            borderRadius: BorderRadius.all(Radius.circular(8)),
260
+          ),
187 261
           child: Row(
188 262
             children: List.generate(
189 263
               tabTextList.length,
@@ -201,25 +275,30 @@ class _StockSampleTaskPageState extends BaseLifecycleState<StockSampleTaskPage>
201 275
       child: GestureDetector(
202 276
         behavior: HitTestBehavior.opaque,
203 277
         onTap: () {
204
-          tabIndex.value = index;
205 278
           if ((pageCtrl.page?.toInt() ?? 0) == index) return;
206
-          pageCtrl.jumpToPage(index);
279
+          pageCtrl.animateToPage(index, duration: const Duration(milliseconds: 200), curve: Curves.linear);
207 280
         },
208
-        child: Container(
209
-          alignment: Alignment.center,
210
-          child: Stack(
211
-            children: [
212
-              buildTabLine(select),
213
-              Text(
214
-                tabTextList[index],
215
-                style: TextStyle(
216
-                  fontSize: select ? 14 : 12,
217
-                  color: select ? MyColor.c_333333 : MyColor.c_467F86,
218
-                  fontWeight: select ? FontWeight.w700 : FontWeight.w400,
281
+        child: Stack(
282
+          clipBehavior: Clip.none,
283
+          alignment: AlignmentDirectional.bottomCenter,
284
+          children: [
285
+            Container(
286
+              alignment: Alignment.center,
287
+              height: 30,
288
+              child: FittedBox(
289
+                fit: BoxFit.scaleDown,
290
+                child: Text(
291
+                  tabTextList[index],
292
+                  style: TextStyle(
293
+                    fontSize: select ? 14 : 12,
294
+                    color: select ? MyColor.c_333333 : MyColor.c_666666,
295
+                    fontWeight: select ? FontWeight.w700 : FontWeight.w400,
296
+                  ),
219 297
                 ),
220 298
               ),
221
-            ],
222
-          ),
299
+            ),
300
+            buildTabLine(select),
301
+          ],
223 302
         ),
224 303
       ),
225 304
     );
@@ -228,17 +307,8 @@ class _StockSampleTaskPageState extends BaseLifecycleState<StockSampleTaskPage>
228 307
   Widget buildTabLine(bool show) {
229 308
     if (!show) return const SizedBox.shrink();
230 309
     return Positioned(
231
-      left: 0,
232
-      right: 0,
233
-      bottom: 0,
234
-      child: Container(
235
-        height: 8,
236
-        decoration: const BoxDecoration(
237
-          gradient: LinearGradient(
238
-            colors: [Color(0xFF3BD2E5), Color(0xFF2379F8)],
239
-          ),
240
-        ),
241
-      ),
310
+      top: 27,
311
+      child: Image.asset(indicatordot, width: 20),
242 312
     );
243 313
   }
244 314
 
@@ -251,7 +321,15 @@ class _StockSampleTaskPageState extends BaseLifecycleState<StockSampleTaskPage>
251 321
         tabIndex.value = index;
252 322
       },
253 323
       itemCount: pageList.length,
254
-      itemBuilder: (_, index) => pageList[index],
324
+      itemBuilder: (_, index) {
325
+        return Container(
326
+            margin: const EdgeInsets.symmetric(horizontal: 8),
327
+            clipBehavior: Clip.hardEdge,
328
+            decoration: const BoxDecoration(
329
+              borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
330
+            ),
331
+            child: pageList[index]);
332
+      },
255 333
     );
256 334
   }
257 335
 

+ 66 - 4
lib/page/sample_task/stock_sample_detail/stock_sample_variety_page.dart

@@ -1,4 +1,7 @@
1
+import 'dart:convert';
2
+
1 3
 import 'package:flutter/material.dart';
4
+import 'package:flutter/services.dart';
2 5
 import 'package:lszlgl/utils/input_formatter.dart';
3 6
 import '../../../base/base_lifecycle_state.dart';
4 7
 import '../../../model/rsp/sample_task_rsp.dart';
@@ -25,6 +28,13 @@ class _StockSampleVarietyPageState extends BaseLifecycleState<StockSampleVariety
25 28
   late bool isDetail;
26 29
 
27 30
   final qysj = null.notifier<String?>();
31
+  final rksj = null.notifier<String?>();
32
+
33
+  final rkdjList = <CardMenuData>[].notifier<List<CardMenuData>>();
34
+  final rkdj = null.notifier<CardMenuData?>();
35
+
36
+  final guoChandiList = <CardMenuData>[].notifier<List<CardMenuData>>();
37
+  final guoChandi = null.notifier<CardMenuData?>();
28 38
 
29 39
   @override
30 40
   bool get wantKeepAlive => true;
@@ -35,6 +45,26 @@ class _StockSampleVarietyPageState extends BaseLifecycleState<StockSampleVariety
35 45
     isDetail = widget.detail;
36 46
 
37 47
     qysj.value = data?.qysj;
48
+    // 入库时间
49
+    rksj.value = data?.rksj;
50
+
51
+    // 入库等级
52
+    rkdjList.value = [1, 2, 3, 4, 5, 6].map((e) {
53
+      var value = CardMenuData(e.toString(), e);
54
+      if (e == data?.rkdj) rkdj.value = value;
55
+      return value;
56
+    }).toList();
57
+
58
+    // 产地国
59
+    rootBundle.loadString('assets/json/shijie.json').then((jsonValue) {
60
+      Map<String, dynamic> map = jsonDecode(jsonValue);
61
+      guoChandiList.value = map.values.map((e) {
62
+        var value = CardMenuData(e.toString(), e);
63
+        if (e == data?.guoChandi) guoChandi.value = value;
64
+        return value;
65
+      }).toList();
66
+    });
67
+
38 68
   }
39 69
 
40 70
   @override
@@ -46,9 +76,8 @@ class _StockSampleVarietyPageState extends BaseLifecycleState<StockSampleVariety
46 76
   Widget buildList() {
47 77
     return Column(
48 78
       children: [
49
-        CardItemWidget('采样品种', rightText: data?.cypzName, bottomLine: true),
50
-        CardItemWidget('样品编号', rightText: data?.ypbh, bottomLine: true),
51
-        CardItemWidget('库存性质', rightText: data?.lsxz, bottomLine: true),
79
+        CardItemWidget('采样品种', rightText: data?.cypzName, bottomLine: true,rightRatio: 2,),
80
+        CardItemWidget('粮食性质', rightText: data?.lsxz, bottomLine: true,rightRatio: 2,),
52 81
         CardWidgets.buildEdit(
53 82
           isDetail,
54 83
           '代表数量(吨)',
@@ -57,7 +86,6 @@ class _StockSampleVarietyPageState extends BaseLifecycleState<StockSampleVariety
57 86
           formatters: [XNumberTextInputFormatter()],
58 87
           onChanged: (value) => data?.dbsl = value.isEmpty ? null : num.parse(value),
59 88
         ),
60
-        CardItemWidget('所在货位粮食总数(吨)', rightText: data?.szhwlszs, bottomLine: true),
61 89
         CardWidgets.buildEdit(
62 90
           isDetail,
63 91
           '收获年度',
@@ -70,6 +98,40 @@ class _StockSampleVarietyPageState extends BaseLifecycleState<StockSampleVariety
70 98
           qysj,
71 99
           onResult: (value) => data?.qysj = DateTimeUtils.yyyymmdd(date: value),
72 100
         ),
101
+        CardWidgets.buildDate(
102
+          isDetail,
103
+          '入库时间',
104
+          rksj,
105
+          onResult: (value) => data?.rksj = DateTimeUtils.yyyymmdd(date: value),
106
+        ),
107
+        CardWidgets.buildMenu(isDetail, '入库等级', rkdjList, rkdj, (_, sel) => data?.rkdj = sel.value),
108
+        CardWidgets.buildMenu(isDetail, '产地/进口国', leftRatio: 2,rightRatio: 3,guoChandiList, guoChandi, (_, sel) => data?.guoChandi = sel.value),
109
+        CardWidgets.buildEdit(
110
+          isDetail,
111
+          '上层粮温(℃)',
112
+          data?.sclw?.toString(),
113
+          inputType: const TextInputType.numberWithOptions(decimal: true),
114
+          formatters: [XNumberTextInputFormatter()],
115
+          onChanged: (value) => data?.sclw = value.isEmpty ? null : num.parse(value),
116
+        ),
117
+
118
+        CardItemWidget('分区布点图', bottomLine: true,
119
+          rightChild: const Icon(Icons.keyboard_arrow_right,size: 24, color: Color(0xFF01B2C8)),
120
+          onTap: (){
121
+
122
+          },
123
+        ),
124
+
125
+        CardItemWidget('分层取样示意图', bottomLine: true,
126
+          rightChild: const Icon(Icons.keyboard_arrow_right,size: 24, color: Color(0xFF01B2C8)),
127
+          onTap: (){
128
+
129
+          },
130
+        ),
131
+
132
+
133
+        //  CardItemWidget('样品编号', rightText: data?.ypbh, bottomLine: true),
134
+
73 135
       ],
74 136
     );
75 137
   }

+ 3 - 3
lib/router/my_router.dart

@@ -58,7 +58,7 @@ final Map<String, MyNavigatorBuilder> rRouteMap = {
58 58
   rSignaturePage: (context, args) => SignaturePage(args: args as SignaturePageArgs),
59 59
   rPrintPage: (context, args) => PrintPage(args: args as PrintPageArgs),
60 60
   rConnectPrintPage: (context, args) => ConnectPrintPage(args: args as ConnectPrintPageArgs),
61
-  rQrCodeScanPage: (context, args) => const QrCodeScanPage(),
61
+  rQrCodeScanPage: (context, args) => QrCodeScanPage(isKC: args as bool),
62 62
   rForgetPasswordPage:(context,args) => const ForgetPasswordPage(),
63 63
   rStoreHousePage:(context,args) => StoreHousePage(houseNum: args as String),
64 64
 };
@@ -145,7 +145,7 @@ class MyRouter {
145 145
   }
146 146
 
147 147
   /// 扫一扫
148
-  static Future<dynamic> startQrCodeScan() {
149
-    return MyNavigator.push(rQrCodeScanPage);
148
+  static Future<dynamic> startQrCodeScan({bool? isKC = false}) {
149
+    return MyNavigator.push(rQrCodeScanPage, args: isKC);
150 150
   }
151 151
 }

+ 6 - 0
lib/widget/card_item.dart

@@ -372,6 +372,7 @@ class CardWidgets {
372 372
     String? Function(DateTime?)? onResult,
373 373
     DateTime? firstDate,
374 374
     DateTime? lastDate,
375
+        String? warmString, // 警告语,日期不可选择时 禁止点击并提示
375 376
   }) {
376 377
     return notifier.builder((v) {
377 378
       if (isDetail) {
@@ -389,6 +390,11 @@ class CardWidgets {
389 390
             : const SizedBox.shrink(),
390 391
         bottomLine: true,
391 392
         onTap: () async {
393
+          if(warmString != null){
394
+            MyNavigator.showToast(warmString);
395
+            return ;
396
+          }
397
+
392 398
           var date = await showDatePicker(
393 399
             context: MyNavigator.navigator.currentState!.context,
394 400
             firstDate: firstDate ?? DateTime(2020),

+ 101 - 0
lib/widget/gallery_photo_view_wrapper.dart

@@ -0,0 +1,101 @@
1
+import 'dart:io';
2
+
3
+import 'package:cached_network_image/cached_network_image.dart';
4
+import 'package:flutter/material.dart';
5
+import 'package:image_picker/image_picker.dart';
6
+import 'package:photo_view/photo_view.dart';
7
+import 'package:photo_view/photo_view_gallery.dart';
8
+
9
+class GalleryPhotoViewWrapper extends StatefulWidget {
10
+  final int initialIndex;
11
+  final PageController pageController;
12
+  /// 相册照片
13
+  final List<XFile>? galleryItems;
14
+  /// 网络图片
15
+  final List<String>? netImgList;
16
+
17
+  GalleryPhotoViewWrapper({
18
+    super.key,
19
+    this.initialIndex = 0,
20
+    this.galleryItems, this.netImgList,
21
+  }) : pageController = PageController(initialPage: initialIndex);
22
+
23
+  @override
24
+  State<GalleryPhotoViewWrapper> createState() => _GalleryPhotoViewWrapperState();
25
+}
26
+
27
+class _GalleryPhotoViewWrapperState extends State<GalleryPhotoViewWrapper> {
28
+  late ValueNotifier<int> indexVal = ValueNotifier(widget.initialIndex);
29
+
30
+  PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) {
31
+    return (widget.galleryItems ?? [] ).isNotEmpty
32
+        ? PhotoViewGalleryPageOptions(
33
+      imageProvider: FileImage(File(widget.galleryItems![index].path)) ,
34
+      initialScale: PhotoViewComputedScale.contained,
35
+      minScale: PhotoViewComputedScale.contained * (0.5 + index / 10),
36
+      maxScale: PhotoViewComputedScale.covered * 4.1,
37
+    )
38
+        : PhotoViewGalleryPageOptions(
39
+      imageProvider: CachedNetworkImageProvider(widget.netImgList![index]) ,
40
+      initialScale: PhotoViewComputedScale.contained,
41
+      minScale: PhotoViewComputedScale.contained * (0.5 + index / 10),
42
+      maxScale: PhotoViewComputedScale.covered * 4.1,
43
+    );
44
+
45
+  }
46
+
47
+  @override
48
+  Widget build(BuildContext context) {
49
+    return Scaffold(
50
+      body: Container(
51
+        decoration: const BoxDecoration(
52
+          color: Colors.black,
53
+        ),
54
+        constraints: BoxConstraints.expand(
55
+          height: MediaQuery.of(context).size.height,
56
+        ),
57
+        child: Stack(
58
+          alignment: Alignment.bottomCenter,
59
+          children: [
60
+            PhotoViewGallery.builder(
61
+              scrollPhysics: const BouncingScrollPhysics(),
62
+              builder: _buildItem,
63
+              itemCount:  widget.galleryItems?.length ?? widget.netImgList?.length,
64
+              pageController: widget.pageController,
65
+              onPageChanged: (index) {
66
+                indexVal.value = index;
67
+              },
68
+            ),
69
+            Padding(
70
+              padding: const EdgeInsets.only(bottom: 12),
71
+              child: ValueListenableBuilder(
72
+                  valueListenable: indexVal,
73
+                  builder: (context, index, child) {
74
+                    return Text(
75
+                      "${index + 1}/${widget.galleryItems?.length ?? widget.netImgList?.length}",
76
+                      style: const TextStyle(
77
+                        color: Colors.white,
78
+                        fontSize: 18.0,
79
+                      ),
80
+                    );
81
+                  }),
82
+            ),
83
+            Positioned(
84
+              top: 28,
85
+              left: 8,
86
+              child: IconButton(
87
+                  onPressed: () {
88
+                    Navigator.pop(context);
89
+                  },
90
+                  icon: const Icon(
91
+                    Icons.arrow_circle_left,
92
+                    color: Colors.white,
93
+                    size: 38,
94
+                  )),
95
+            ),
96
+          ],
97
+        ),
98
+      ),
99
+    );
100
+  }
101
+}

+ 167 - 0
lib/widget/photo_card_item.dart

@@ -0,0 +1,167 @@
1
+import 'dart:io';
2
+import 'dart:typed_data';
3
+
4
+import 'package:flutter/material.dart';
5
+import 'package:image_gallery_saver/image_gallery_saver.dart';
6
+import 'package:image_picker/image_picker.dart';
7
+import 'package:lszlgl/base/base_lifecycle_state.dart';
8
+import 'package:lszlgl/utils/permission_utils.dart';
9
+import 'package:lszlgl/widget/card_item.dart';
10
+import 'package:lszlgl/widget/gallery_photo_view_wrapper.dart';
11
+import 'package:permission_handler/permission_handler.dart';
12
+
13
+class PhotoCardItem extends StatefulWidget {
14
+  final String title;
15
+  final String? imgUrl;
16
+  final bool isDetail;
17
+  final Function(String filePath) filePathCallBack;
18
+  const PhotoCardItem({super.key, required this.title, required this.isDetail, this.imgUrl, required this.filePathCallBack});
19
+
20
+  @override
21
+  State<PhotoCardItem> createState() => _PhotoCardItemState();
22
+}
23
+
24
+class _PhotoCardItemState extends State<PhotoCardItem> {
25
+  final ImagePicker _picker = ImagePicker();
26
+  XFile? photoPic;
27
+
28
+  // 弹窗选择 照片或拍照
29
+  void showBotSheet() {
30
+    showModalBottomSheet(
31
+      context: context,
32
+      shape: const RoundedRectangleBorder(
33
+          borderRadius:
34
+              BorderRadius.only(topLeft: Radius.circular(14), topRight: Radius.circular(14))),
35
+      builder: (context) {
36
+        return Column(
37
+          mainAxisSize: MainAxisSize.min,
38
+          crossAxisAlignment: CrossAxisAlignment.stretch,
39
+          children: [
40
+            const SizedBox(height: 8),
41
+            TextButton(
42
+                child: const Text('相册'),
43
+                onPressed: () {
44
+                  Navigator.pop(context);
45
+                  pickImage(false);
46
+                }),
47
+            TextButton(
48
+                child: const Text('拍照'),
49
+                onPressed: () async {
50
+                  Navigator.pop(context);
51
+                  bool res = await PermissionHandler.handleWith(Permission.storage);
52
+                  if (res) {
53
+                    pickImage(true);
54
+                  }
55
+                }),
56
+            const SizedBox(height: 8),
57
+            const Divider(color: Color(0xFFF3F3F3), height: 0, thickness: 1),
58
+            TextButton(
59
+                child: const Text('取消', style: TextStyle(color: Colors.grey)),
60
+                onPressed: () {
61
+                  Navigator.pop(context);
62
+                }),
63
+            const SizedBox(height: 4),
64
+          ],
65
+        );
66
+      },
67
+    );
68
+  }
69
+
70
+  /// 保存照片到本地
71
+  void savePicture(String? path) async {
72
+    if (path == null) return;
73
+    final File imageFile = File(path);
74
+    final Uint8List imageBytes = await imageFile.readAsBytes();
75
+    await ImageGallerySaver.saveImage(imageBytes);
76
+  }
77
+
78
+  /// 选择照片或拍照
79
+  void pickImage(bool isCamera) async {
80
+    try {
81
+      XFile? photo = await _picker.pickImage(
82
+        source: isCamera ? ImageSource.camera : ImageSource.gallery,
83
+        imageQuality: 25,
84
+      );
85
+      if (photo == null) return;
86
+
87
+      if (isCamera) {
88
+        savePicture(photo.path); // 保存到本地
89
+      }
90
+
91
+      setState(() {
92
+        photoPic = photo;
93
+      });
94
+      widget.filePathCallBack.call(photo.path);
95
+
96
+    } catch (e) {
97
+      MyNavigator.showToast('$e');
98
+    }
99
+  }
100
+
101
+  void showBigImage({XFile? file}) {
102
+    FocusManager.instance.primaryFocus?.unfocus();
103
+    Navigator.push(
104
+      context,
105
+      MaterialPageRoute(
106
+        builder: (context) {
107
+          return file != null
108
+              ? GalleryPhotoViewWrapper(
109
+                  galleryItems: [file],
110
+                )
111
+              : GalleryPhotoViewWrapper(
112
+                  netImgList: [widget.imgUrl!],
113
+                );
114
+        },
115
+      ),
116
+    );
117
+  }
118
+
119
+  @override
120
+  Widget build(BuildContext context) {
121
+    return Stack(
122
+      alignment: Alignment.centerLeft,
123
+      children: [
124
+        CardItemWidget(
125
+          widget.title,
126
+          bottomLine: true,
127
+          // rightText: widget.isDetail ? '点击查看' : '点击上传',
128
+          rightChild: widget.isDetail || photoPic == null
129
+              ? const SizedBox.shrink()
130
+              : GestureDetector(
131
+                  child: Image.file(
132
+                    File(photoPic!.path),
133
+                    width: 44,
134
+                    height: 44,
135
+                    fit: BoxFit.cover,
136
+                  ),
137
+                  onTap: () => showBigImage(file: photoPic!),
138
+                ),
139
+          trailing: GestureDetector(
140
+            child: Text(
141
+              widget.isDetail
142
+                  ? '点击查看'
143
+                  : photoPic == null
144
+                      ? '点击选择'
145
+                      : '重新选择',
146
+              style: const TextStyle(fontSize: 14, color: Color(0xFF01B2C8)),
147
+            ),
148
+            onTap: () {
149
+              if (widget.isDetail) {
150
+                // 详情 查看照片
151
+                if (widget.imgUrl == null) {
152
+                  MyNavigator.showToast('地址错误');
153
+                  return;
154
+                }
155
+                showBigImage();
156
+              } else {
157
+                showBotSheet(); // 编辑-选择照片
158
+              }
159
+            },
160
+          ),
161
+          onTap: () {},
162
+        ),
163
+        Container(width: 4, height: 4, color: const Color(0xFF01B2C8))
164
+      ],
165
+    );
166
+  }
167
+}

+ 5 - 0
pubspec.yaml

@@ -99,7 +99,12 @@ dependencies:
99 99
   jpush_flutter: 3.0.3
100 100
   # 虚线边框
101 101
   dotted_border: ^2.1.0
102
+  # 短信框
102 103
   pin_code_fields: ^8.0.1
104
+  # 照片选择
105
+  image_picker: ^1.1.2
106
+  # 照片展示
107
+  photo_view: ^0.15.0
103 108
 
104 109
 dev_dependencies:
105 110
   flutter_test: