Przeglądaj źródła

Merge branch 'master' into dev

* master:
  登录接口增加平台类型参数;
  修改版本号:v0.0.14
  扦样详情页面增加查看二维码和查看签名,去掉重新录入按钮;
  修改扦样列表样品层级和检验指标为空时不显示;
  修复筛选页面的样品等级不回显问题
  增加扫描二维码;
  修改扦样单申请页底部按钮外边距;

# Conflicts:
#	lib/router/my_router.dart
周素华 1 rok temu
rodzic
commit
3e63d78d72

+ 4 - 0
lib/model/req/login_req.dart

@@ -7,9 +7,13 @@ class LoginReq {
7 7
   final String? username;
8 8
   final String? password;
9 9
 
10
+  /// 登录平台: 0=web 1=app
11
+  final int? platform;
12
+
10 13
   const LoginReq({
11 14
     this.username,
12 15
     this.password,
16
+    this.platform = 1,
13 17
   });
14 18
 
15 19
   factory LoginReq.fromJson(Map<String, dynamic> json) => _$LoginReqFromJson(json);

+ 2 - 0
lib/model/req/login_req.g.dart

@@ -9,9 +9,11 @@ part of 'login_req.dart';
9 9
 LoginReq _$LoginReqFromJson(Map<String, dynamic> json) => LoginReq(
10 10
       username: json['username'] as String?,
11 11
       password: json['password'] as String?,
12
+      platform: json['platform'] as int? ?? 1,
12 13
     );
13 14
 
14 15
 Map<String, dynamic> _$LoginReqToJson(LoginReq instance) => <String, dynamic>{
15 16
       'username': instance.username,
16 17
       'password': instance.password,
18
+      'platform': instance.platform,
17 19
     };

+ 1 - 1
lib/page/filter/filter_page.dart

@@ -64,7 +64,7 @@ class _FilterPageState extends BaseState<FilterPage> {
64 64
     ypdjList.value = (DictService.getDictList(DictType.ypdj) ?? []).map((e) => CardMenuData(e.label, e.value)).toList();
65 65
     if (vm.reqList.first.ypdj == null) return;
66 66
     for (var item in ypdjList.value) {
67
-      if (vm.reqList.first.ypdj == item.value) {
67
+      if (vm.reqList.first.ypdj == int.parse(item.value)) {
68 68
         ypdj.value = item;
69 69
         break;
70 70
       }

+ 13 - 0
lib/page/home/home_page.dart

@@ -48,6 +48,7 @@ class _HomePageState extends BaseState<HomePage> with AutomaticKeepAliveClientMi
48 48
           title: '质量安全检验监测',
49 49
           autoLeading: false,
50 50
           naviBarColor: Colors.white,
51
+          actions: [buildScan()],
51 52
         ),
52 53
         Expanded(
53 54
           child: SingleChildScrollView(
@@ -68,6 +69,18 @@ class _HomePageState extends BaseState<HomePage> with AutomaticKeepAliveClientMi
68 69
     );
69 70
   }
70 71
 
72
+  Widget buildScan() {
73
+    return GestureDetector(
74
+      onTap: MyRouter.startQrCodeScan,
75
+      behavior: HitTestBehavior.opaque,
76
+      child: Container(
77
+        padding: const EdgeInsets.symmetric(horizontal: 16),
78
+        alignment: Alignment.center,
79
+        child: const Text('扫一扫', style: TextStyle(fontSize: 14, color: Colors.white)),
80
+      ),
81
+    );
82
+  }
83
+
71 84
   Widget buildBanner() {
72 85
     return Container(
73 86
       margin: const EdgeInsets.symmetric(horizontal: 12),

+ 129 - 0
lib/page/qrcode_scan/qrcode_scan_page.dart

@@ -0,0 +1,129 @@
1
+import 'dart:io';
2
+
3
+import 'package:flutter/material.dart';
4
+import 'package:lszlgl/page/sample_task/reap_sample_detail/reap_sample_task_page.dart';
5
+import 'package:qr_code_scanner/qr_code_scanner.dart';
6
+
7
+import '../../base/base_lifecycle_state.dart';
8
+import '../../widget/button.dart';
9
+
10
+class QrCodeScanPage extends StatefulWidget {
11
+  const QrCodeScanPage({super.key});
12
+
13
+  @override
14
+  State<QrCodeScanPage> createState() => _QrCodeScanPageState();
15
+}
16
+
17
+class _QrCodeScanPageState extends BaseLifecycleState<QrCodeScanPage> {
18
+  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
19
+  Barcode? result;
20
+  QRViewController? controller;
21
+
22
+  void _onQRViewCreated(QRViewController controller) {
23
+    this.controller = controller;
24
+    controller.scannedDataStream.listen((scanData) {
25
+      debugPrint('code:${scanData.code} format:${scanData.format} rawBytes:${scanData.rawBytes}');
26
+      controller.pauseCamera();
27
+      parseCode(scanData);
28
+    });
29
+  }
30
+
31
+  void parseCode(Barcode data) {
32
+    String code = data.code ?? '';
33
+    if (code.isEmpty) {
34
+      showErrorDialog('内容为空,请扫描正确二维码');
35
+      return;
36
+    }
37
+    // 121.36.17.6:19090/admin-api/zj/code-sampling-task-details-sgjc/getSamplingTaskDetails?id=47106
38
+    List<String> split = code.split('?');
39
+    if (!code.contains('?') || split.length <= 1 || !split[0].contains('/')) {
40
+      showErrorDialog(code);
41
+      return;
42
+    }
43
+    String api = split[0].split('/').last;
44
+
45
+    Map<String, String> params = {};
46
+    for (String value in split[1].split('&')) {
47
+      List<String> kv = value.split('=');
48
+      params[kv[0]] = kv[1];
49
+    }
50
+
51
+    switch (api) {
52
+      case 'getSamplingTaskDetails':
53
+        // 收获扦样详情
54
+        if (params['id'] == null) {
55
+          showErrorDialog(code);
56
+          return;
57
+        }
58
+        MyRouter.startReapSampleTask(
59
+          args: ReapSampleTaskPageArgs(id: num.parse(params['id']!), detail: true),
60
+          replace: true,
61
+        );
62
+      default:
63
+        showErrorDialog(code);
64
+    }
65
+  }
66
+
67
+  void showErrorDialog(String msg) async {
68
+    await showDialog(
69
+      context: context,
70
+      builder: (_) => AlertDialog(
71
+        title: Text(msg),
72
+        actions: [MyButton('确定', alignment: null, onTap: () => MyNavigator.pop())],
73
+      ),
74
+    );
75
+    controller?.resumeCamera();
76
+  }
77
+
78
+  // In order to get hot reload to work we need to pause the camera if the platform
79
+  // is android, or resume the camera if the platform is iOS.
80
+  @override
81
+  void reassemble() {
82
+    super.reassemble();
83
+    if (Platform.isAndroid) {
84
+      controller!.pauseCamera();
85
+    } else if (Platform.isIOS) {
86
+      controller!.resumeCamera();
87
+    }
88
+  }
89
+
90
+  @override
91
+  void dispose() {
92
+    controller?.dispose();
93
+    super.dispose();
94
+  }
95
+
96
+  @override
97
+  Widget build(BuildContext context) {
98
+    return myScaffold(child: buildBody());
99
+  }
100
+
101
+  Widget buildBody() {
102
+    return Column(
103
+      children: [
104
+        myAppBar(
105
+          title: '扫一扫',
106
+          actions: [buildFlash()],
107
+        ),
108
+        Expanded(
109
+          child: QRView(
110
+            key: qrKey,
111
+            onQRViewCreated: _onQRViewCreated,
112
+          ),
113
+        ),
114
+      ],
115
+    );
116
+  }
117
+
118
+  Widget buildFlash() {
119
+    return GestureDetector(
120
+      onTap: () => controller?.toggleFlash(),
121
+      behavior: HitTestBehavior.opaque,
122
+      child: Container(
123
+        padding: const EdgeInsets.symmetric(horizontal: 16),
124
+        alignment: Alignment.center,
125
+        child: const Text('闪光灯', style: TextStyle(fontSize: 14, color: Colors.white)),
126
+      ),
127
+    );
128
+  }
129
+}

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

@@ -1,16 +1,22 @@
1 1
 import 'dart:async';
2 2
 import 'dart:convert';
3
+import 'dart:typed_data';
3 4
 import 'package:amap_flutter_location/amap_location_option.dart';
5
+import 'package:cached_network_image/cached_network_image.dart';
6
+import 'package:card_swiper/card_swiper.dart';
4 7
 import 'package:flutter/material.dart';
8
+import 'package:image_gallery_saver/image_gallery_saver.dart';
5 9
 import 'package:lszlgl/main.dart';
6 10
 import 'package:lszlgl/utils/input_formatter.dart';
7 11
 import 'package:lszlgl/utils/location_utils.dart';
8 12
 import 'package:lszlgl/widget/button.dart';
9 13
 import '../../../base/base_lifecycle_state.dart';
14
+import '../../../config/colors.dart';
10 15
 import '../../../model/rsp/dict_rsp.dart';
11 16
 import '../../../model/rsp/sample_task_rsp.dart';
12 17
 import '../../../network/my_api.dart';
13 18
 import '../../../service/user_service.dart';
19
+import '../../../utils/file_utils.dart';
14 20
 import '../../../widget/card_item.dart';
15 21
 
16 22
 /// 收获扦样-基础信息
@@ -274,6 +280,115 @@ class _ReapSampleBasicDetailPageState extends BaseLifecycleState<ReapSampleBasic
274 280
     name.value = names.toString();
275 281
   }
276 282
 
283
+  void showSignature() {
284
+    if (data?.filePictureList?.isEmpty ?? true) {
285
+      MyNavigator.showToast('没有电子签名');
286
+      return;
287
+    }
288
+    MyNavigator.showDialog(
289
+      builder: (_) => Container(
290
+        padding: const EdgeInsets.all(16),
291
+        margin: const EdgeInsets.all(24),
292
+        decoration: const BoxDecoration(
293
+          color: Colors.white,
294
+          borderRadius: BorderRadius.all(Radius.circular(24)),
295
+        ),
296
+        child: AspectRatio(
297
+          aspectRatio: 1 / 1,
298
+          child: Swiper(
299
+            itemCount: data!.filePictureList!.length,
300
+            itemBuilder: (_, index) => CachedNetworkImage(
301
+              fit: BoxFit.contain,
302
+              imageUrl: data!.filePictureList![index].url ?? '',
303
+              placeholder: (_, __) => const Center(child: CircularProgressIndicator()),
304
+              errorWidget: (context, url, error) => const Center(child: Icon(Icons.error)),
305
+            ),
306
+            viewportFraction: 0.8,
307
+            scale: 0.9,
308
+            fade: 0.0,
309
+            loop: false,
310
+            pagination: const FractionPaginationBuilder(color: Colors.grey),
311
+            // control: const SwiperControl(padding: EdgeInsets.zero, size: 25),
312
+          ),
313
+        ),
314
+      ),
315
+    );
316
+  }
317
+
318
+  GlobalKey ewmKey = GlobalKey();
319
+
320
+  /// 查看二维码
321
+  void showQRCode() {
322
+    if (data?.ewmfilePictureList?.isEmpty ?? true) {
323
+      MyNavigator.showToast('没有二维码');
324
+      return;
325
+    }
326
+    UrlItem picInfo = data!.ewmfilePictureList!.first;
327
+    MyNavigator.showDialog(
328
+      builder: (_) => Container(
329
+        padding: const EdgeInsets.all(16),
330
+        margin: const EdgeInsets.all(24),
331
+        decoration: const BoxDecoration(
332
+          color: Colors.white,
333
+          borderRadius: BorderRadius.all(Radius.circular(24)),
334
+        ),
335
+        child: Column(
336
+          mainAxisSize: MainAxisSize.min,
337
+          children: [
338
+            RepaintBoundary(
339
+              key: ewmKey,
340
+              child: Container(
341
+                color: Colors.white,
342
+                child: Column(
343
+                  children: [
344
+                    CachedNetworkImage(
345
+                      width: double.infinity,
346
+                      fit: BoxFit.cover,
347
+                      imageUrl: picInfo.url!,
348
+                      placeholder: (_, __) => const Center(child: CircularProgressIndicator()),
349
+                      errorWidget: (context, url, error) => const Center(child: Icon(Icons.error)),
350
+                    ),
351
+                    Padding(
352
+                      padding: const EdgeInsets.symmetric(vertical: 16),
353
+                      child: Text(
354
+                        picInfo.name ?? '',
355
+                        style: const TextStyle(fontSize: 18, color: MyColor.c_333333),
356
+                      ),
357
+                    ),
358
+                  ],
359
+                ),
360
+              ),
361
+            ),
362
+            Row(
363
+              children: [
364
+                const Expanded(child: MyButton('打印')),
365
+                const SizedBox(width: 16),
366
+                Expanded(child: MyButton('保存图片', onTap: () => savePic(picInfo.name))),
367
+              ],
368
+            ),
369
+          ],
370
+        ),
371
+      ),
372
+    );
373
+  }
374
+
375
+  void savePic(String? name) async {
376
+    MyNavigator.showLoading(msg: '保存中...');
377
+    Uint8List? bytes = await FileUtils.getBitmapFromContext(ewmKey.currentContext);
378
+    if (bytes == null) {
379
+      MyNavigator.dismiss();
380
+      MyNavigator.showToast('保存失败');
381
+      return;
382
+    }
383
+    final result = await ImageGallerySaver.saveImage(bytes, quality: 60, name: name);
384
+    MyNavigator.dismiss();
385
+    if (result['isSuccess']) {
386
+      MyNavigator.showToast('保存成功');
387
+    } else {
388
+      MyNavigator.showToast('保存失败');
389
+    }
390
+  }
391
+
277 392
   @override
278 393
   bool get wantKeepAlive => true;
279 394
 
@@ -358,6 +473,23 @@ class _ReapSampleBasicDetailPageState extends BaseLifecycleState<ReapSampleBasic
358 473
           trxx,
359 474
           (_, sel) => data?.trdllx = sel.value,
360 475
         ),
476
+        isDetail
477
+            ? CardItemWidget(
478
+                '电子签名',
479
+                rightText: '点击查看',
480
+                bottomLine: true,
481
+                onTap: showSignature,
482
+              )
483
+            : const SizedBox.shrink(),
484
+        isDetail
485
+            ? CardItemWidget(
486
+                '样品二维码',
487
+                rightText: '点击查看',
488
+                bottomLine: true,
489
+                onTap: showQRCode,
490
+              )
491
+            : const SizedBox.shrink(),
492
+
361 493
         // CardWidgets.buildEdit(
362 494
         //   isDetail,
363 495
         //   '被调查农户或合作社',

+ 4 - 9
lib/page/sample_task/reap_sample_detail/reap_sample_task_page.dart

@@ -149,7 +149,7 @@ class _ReapSampleTaskPageState extends BaseLifecycleState<ReapSampleTaskPage> wi
149 149
   GlobalKey ewmKey = GlobalKey();
150 150
 
151 151
   /// 查看二维码
152
-  void startQRCode() {
152
+  void showQRCode() {
153 153
     UrlItem picInfo = pageStatus.value.data!.ewmfilePictureList!.first;
154 154
     MyNavigator.showDialog(
155 155
       builder: (_) => Container(
@@ -370,17 +370,12 @@ class _ReapSampleTaskPageState extends BaseLifecycleState<ReapSampleTaskPage> wi
370 370
 
371 371
   Widget buildButton() {
372 372
     Widget child;
373
-    EdgeInsets margin = EdgeInsets.only(left: 8, right: 8, top: 12, bottom: getBottomPadding(12));
373
+    EdgeInsets margin = EdgeInsets.only(left: 4, right: 4, top: 12, bottom: getBottomPadding(12));
374 374
     Widget ewmWidget = (pageStatus.value.data?.ewmfilePictureList ?? []).isEmpty
375 375
         ? const SizedBox.shrink()
376
-        : Expanded(child: MyButton('查看二维码', onTap: startQRCode, margin: margin));
376
+        : Expanded(child: MyButton('查看二维码', onTap: showQRCode, margin: margin));
377 377
     if (args.detail) {
378
-      child = Row(
379
-        children: [
380
-          ewmWidget,
381
-          Expanded(child: MyButton('重新录入', onTap: startEdit, margin: margin)),
382
-        ],
383
-      );
378
+      child = Row(children: [ewmWidget]);
384 379
     } else {
385 380
       child = tabIndex.builder((v) {
386 381
         // 第一页

+ 9 - 7
lib/page/sample_task/sample_task_list_page.dart

@@ -203,12 +203,14 @@ class _SampleTaskListPageState extends BaseLifecycleState<SampleTaskListPage> wi
203 203
     List<Map<String, String?>> infoList = [];
204 204
     if (item.deliveryStatus != 2) {
205 205
       // 未扦样
206
-      infoList.addAll([
207
-        {'采样品种': item.cypzName},
208
-        {'检验指标': item.jyzb},
209
-        {'样品等级': DictService.getDict(DictType.ypdj, value: item.ypdj)?.label},
210
-        {'扦样地区': item.qydq},
211
-      ]);
206
+      infoList.add({'采样品种': item.cypzName});
207
+      if (item.jyzb != null) {
208
+        infoList.add({'检验指标': item.jyzb});
209
+      }
210
+      infoList.add({'扦样地区': item.qydq});
211
+      if (item.ypdj != null) {
212
+        infoList.add({'样品层级': DictService.getDict(DictType.ypdj, value: item.ypdj)?.label});
213
+      }
212 214
     } else {
213 215
       // 已扦样
214 216
       infoList.addAll([
@@ -216,7 +218,7 @@ class _SampleTaskListPageState extends BaseLifecycleState<SampleTaskListPage> wi
216 218
         {'扦样地区': item.qydq},
217 219
       ]);
218 220
       if (item.ypdj != null) {
219
-        infoList.add({'样品级': DictService.getDict(DictType.ypdj, value: item.ypdj)?.label});
221
+        infoList.add({'样品级': DictService.getDict(DictType.ypdj, value: item.ypdj)?.label});
220 222
       }
221 223
       if (item.jyzb != null) {
222 224
         infoList.add({'检验指标': item.jyzb});

+ 17 - 20
lib/page/sample_task/stock_sample_detail/stock_sample_task_page.dart

@@ -252,32 +252,29 @@ class _StockSampleTaskPageState extends BaseLifecycleState<StockSampleTaskPage>
252 252
   }
253 253
 
254 254
   Widget buildButton() {
255
-    Widget child;
256
-    if (args.detail) {
257
-      child = MyButton('重新录入', onTap: startEdit);
258
-    } else {
259
-      child = tabIndex.builder((v) {
260
-        // 第一页
261
-        if (v == 0) return MyButton('下一步', onTap: next);
262
-        // 最后一页
263
-        if (v == tabTextList.length - 1) {
264
-          return Row(
265
-            children: [
266
-              Expanded(flex: 2, child: MyButton('上一步', onTap: previous)),
267
-              const Spacer(flex: 1),
268
-              Expanded(flex: 2, child: MyButton('提交', onTap: submit)),
269
-            ],
270
-          );
271
-        }
255
+    if (args.detail) return const SizedBox.shrink();
256
+    Widget child = tabIndex.builder((v) {
257
+      // 第一页
258
+      if (v == 0) return MyButton('下一步', onTap: next);
259
+      // 最后一页
260
+      if (v == tabTextList.length - 1) {
272 261
         return Row(
273 262
           children: [
274 263
             Expanded(flex: 2, child: MyButton('上一步', onTap: previous)),
275 264
             const Spacer(flex: 1),
276
-            Expanded(flex: 2, child: MyButton('下一步', onTap: next)),
265
+            Expanded(flex: 2, child: MyButton('提交', onTap: submit)),
277 266
           ],
278 267
         );
279
-      });
280
-    }
268
+      }
269
+      return Row(
270
+        children: [
271
+          Expanded(flex: 2, child: MyButton('上一步', onTap: previous)),
272
+          const Spacer(flex: 1),
273
+          Expanded(flex: 2, child: MyButton('下一步', onTap: next)),
274
+        ],
275
+      );
276
+    });
277
+
281 278
     return Container(
282 279
       color: const Color(0xFFF1F7F6),
283 280
       padding: const EdgeInsets.all(12),

+ 15 - 2
lib/router/my_router.dart

@@ -1,3 +1,4 @@
1
+import 'package:lszlgl/base/base_lifecycle_state.dart';
1 2
 import 'package:lszlgl/page/login/login_page.dart';
2 3
 import 'package:lszlgl/page/main_tab_page.dart';
3 4
 import 'package:lszlgl/page/print/connect_print_page.dart';
@@ -9,6 +10,7 @@ import 'package:lszlgl/page/user_center/change_pwd_page.dart';
9 10
 import 'package:lszlgl/page/user_center/setting_page.dart';
10 11
 import 'package:lszlgl/router/my_navigator.dart';
11 12
 
13
+import '../page/qrcode_scan/qrcode_scan_page.dart';
12 14
 import '../page/sample_task/stock_sample_detail/stock_sample_task_page.dart';
13 15
 import '../page/signature/signature_page.dart';
14 16
 
@@ -34,6 +36,8 @@ const rSignaturePage = '/SignaturePage';
34 36
 const rPrintPage = '/PrintPage';
35 37
 // 连接
36 38
 const rConnectPrintPage = '/ConnectPrintPage';
39
+// 扫一扫
40
+const rQrCodeScanPage = '/QrCodeScanPage';
37 41
 
38 42
 final Map<String, MyNavigatorBuilder> rRouteMap = {
39 43
   // 根页面
@@ -48,6 +52,7 @@ final Map<String, MyNavigatorBuilder> rRouteMap = {
48 52
   rSignaturePage: (context, args) => SignaturePage(args: args as SignaturePageArgs),
49 53
   rPrintPage: (context, args) => PrintPage(args: args as PrintPageArgs),
50 54
   rConnectPrintPage: (context, args) => ConnectPrintPage(args: args as ConnectPrintPageArgs),
55
+  rQrCodeScanPage: (context, args) => const QrCodeScanPage(),
51 56
 };
52 57
 
53 58
 class MyRouter {
@@ -92,8 +97,13 @@ class MyRouter {
92 97
   }
93 98
 
94 99
   /// 收获扦样任务
95
-  static Future<dynamic> startReapSampleTask({ReapSampleTaskPageArgs? args}) async {
96
-    return MyNavigator.push(rReapSampleTaskPage, args: args ?? ReapSampleTaskPageArgs());
100
+  static Future<dynamic> startReapSampleTask({ReapSampleTaskPageArgs? args, bool replace = false}) async {
101
+    args ??= ReapSampleTaskPageArgs();
102
+    if (replace) {
103
+      return MyNavigator.pushReplace(rReapSampleTaskPage, args: args);
104
+    } else {
105
+      return MyNavigator.push(rReapSampleTaskPage, args: args);
106
+    }
97 107
   }
98 108
 
99 109
   /// 库存扦样任务
@@ -114,5 +124,8 @@ class MyRouter {
114 124
   /// 打印任务
115 125
   static Future<dynamic> startConnectPrint({ConnectPrintPageArgs? args}) {
116 126
     return MyNavigator.push(rConnectPrintPage, args: args ?? ConnectPrintPageArgs());
127
+  /// 扫一扫
128
+  static Future<dynamic> startQrCodeScan() {
129
+    return MyNavigator.push(rQrCodeScanPage);
117 130
   }
118 131
 }

+ 1 - 1
pubspec.yaml

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
16 16
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 17
 # In Windows, build-name is used as the major, minor, and patch parts
18 18
 # of the product and file versions while build-number is used as the build suffix.
19
-version: 0.0.13+13
19
+version: 0.0.14+14
20 20
 
21 21
 environment:
22 22
   sdk: '>=3.1.5 <4.0.0'