From b7caf58adfc75e0d178a3a4ddffef8bd8115af26 Mon Sep 17 00:00:00 2001 From: userGyl Date: Sat, 28 Feb 2026 15:00:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E6=B0=A2=E7=AB=99-=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E8=AF=81=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ln_jq_app/assets/images/ic_close@2x.png | Bin 0 -> 892 bytes .../lib/pages/b_page/site/controller.dart | 206 ++++++++++++++++-- ln_jq_app/lib/pages/b_page/site/view.dart | 19 +- .../pages/c_page/reservation/controller.dart | 2 + ln_jq_app/pubspec.lock | 8 + ln_jq_app/pubspec.yaml | 5 +- 6 files changed, 204 insertions(+), 36 deletions(-) create mode 100644 ln_jq_app/assets/images/ic_close@2x.png diff --git a/ln_jq_app/assets/images/ic_close@2x.png b/ln_jq_app/assets/images/ic_close@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c782ed6dded009daa19e4e380955a120ae1c45 GIT binary patch literal 892 zcmV-?1B3jDP)VQNtAZ5(^T6xH^Y~vqy!nQ#7d_LdZd-w6Xdk=qzL?T~EzJ^5r^h7YBDFAv7 z4Gn<+D0Ch=0g-2YTTWZfx2}0x7(Dbs_YkUrUjPJ(&;$ks3QaCzpwM~f1cZaE&%4Mxq z%h&7m3$t-{o-+{g5X8su)nRJnKnDY1nCHMy zE|+h&+pVgHn9dGC>0w5r(H(8QM=ZvzXn{QEqWOIOl*{F=?LZm9luD&Lw;()E3#7hT z?r>Y%fnpf80ndnlfj;3tP-+Dw69%@W070=4)Ob9;(gpyLo`Fq>G$1I~L*W^V3>fH^ z3IqdyTCdmtanJHXzrkRz&Oe7NfXl4Zi!w@5=OeS-VKDVL=LfCeF zj#{mjvRbVkyWOs0<{AA$8H%>*!(y>m_If=}FI5i&dw+3pBaKF58I4A-tJUhgllg#3 zr_;Cbc>IQvFkY&Y>F>rG0ZGZ=@2pB}7y-fM|0LR!s@ez$1|M(I;S)L# zPIdDxV^sOGH~|6yVBjg=CSIpN422v80U)0FM%)1L;^C$5)bJIU7a(F$3xnZ& zArT{hs2#_+hydbs17t!CfPJGHXANyY;u{P7(({=}AY#}TESqTZ&7!b%721!IgR09A^AG&o; SpQ|ST0000 drivingAttachments; // 行驶证图片列表 + final List hydrogenationAttachments; // 加氢证图片列表 ReservationModel({ required this.id, @@ -73,6 +82,8 @@ class ReservationModel { required this.hasDrivingAttachment, required this.hasHydrogenationAttachment, required this.isEdit, + required this.drivingAttachments, + required this.hydrogenationAttachments, }); /// 工厂构造函数,用于从JSON创建ReservationModel实例 @@ -146,6 +157,8 @@ class ReservationModel { hasDrivingAttachment: drivingList.isNotEmpty, hasHydrogenationAttachment: hydrogenationList.isNotEmpty, isEdit: json['isEdit']?.toString() ?? '0', + drivingAttachments: drivingList.map((e) => e.toString()).toList(), + hydrogenationAttachments: hydrogenationList.map((e) => e.toString()).toList(), ); } } @@ -348,6 +361,8 @@ class SiteController extends GetxController with BaseControllerMixin { hasDrivingAttachment: false, hasHydrogenationAttachment: false, isEdit: "0", + drivingAttachments: [], + hydrogenationAttachments: [], ); } else { item = reservationList.firstWhere( @@ -416,13 +431,13 @@ class SiteController extends GetxController with BaseControllerMixin { ), SizedBox(width: 16.w), if (item.plateNumber != "---" && item.hasDrivingAttachment) - _buildInfoTag('行驶证'), + buildInfoTag('行驶证', item.drivingAttachments), if (item.plateNumber != "---" && item.hasHydrogenationAttachment) - _buildInfoTag('加氢证'), + buildInfoTag('加氢证', item.hydrogenationAttachments), ], ), - SizedBox(height: 6.h), + SizedBox(height: 6.h), // 提示逻辑 if (isEdit) @@ -458,7 +473,7 @@ class SiteController extends GetxController with BaseControllerMixin { ], ), - SizedBox(height: 6.h), + SizedBox(height: 6.h), // 预定加氢量输入区 Container( @@ -590,7 +605,7 @@ class SiteController extends GetxController with BaseControllerMixin { 1, addHydAmount, "", - item, + item!, gunNumber: selectedGun.value, plateNumber: item.plateNumber, isEdit: true, @@ -600,7 +615,7 @@ class SiteController extends GetxController with BaseControllerMixin { } //订单确认 if (!isEdit && - (item.plateNumber == "---" || + (item!.plateNumber == "---" || item.isTruckAttachment == 0) && !isOfflineChecked.value) { showToast("车辆未上传加氢证 , 请确保线下登记后点击确认"); @@ -663,14 +678,14 @@ class SiteController extends GetxController with BaseControllerMixin { child: OutlinedButton( onPressed: () { Get.back(); - if (!isEdit) { + if (!isEdit && !isAdd) { upDataService( id, 0, 2, 0, "", - item, + item!, gunNumber: selectedGun.value, plateNumber: item.plateNumber, ); @@ -684,7 +699,7 @@ class SiteController extends GetxController with BaseControllerMixin { ), ), child: Text( - isEdit ? '取消' : '未加氢', + isEdit || isAdd ? '取消' : '未加氢', style: const TextStyle(color: Colors.grey, fontSize: 16), ), ), @@ -725,17 +740,170 @@ class SiteController extends GetxController with BaseControllerMixin { ); } - Widget _buildInfoTag(String label) { - return Container( - margin: const EdgeInsets.only(left: 4), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: const Color(0xFFF2F3F5), - borderRadius: BorderRadius.circular(4), + /// 保存图片到相册 + Future saveImageToLocal(String url) async { + try { + // 1. 权限请求 + if (Platform.isAndroid) { + var status = await Permission.storage.request(); + if (!status.isGranted) { + showErrorToast("请在系统设置中开启存储权限"); + return; + } + } else { + var status = await Permission.photos.request(); + if (!status.isGranted) { + showErrorToast("请在系统设置中开启相册权限"); + return; + } + } + + showLoading("正在保存..."); + + // 2. 下载图片 + var response = await Dio().get( + url, + options: Options(responseType: ResponseType.bytes), + ); + + // 3. 保存到相册 + final result = await ImageGallerySaver.saveImage( + Uint8List.fromList(response.data), + quality: 100, + name: "certificate_${DateTime.now().millisecondsSinceEpoch}", + ); + + dismissLoading(); + + if (result != null && result['isSuccess'] == true) { + showSuccessToast("图片已保存至相册"); + } else { + showErrorToast("保存失败"); + } + } catch (e) { + dismissLoading(); + showErrorToast("保存异常"); + } + } + + Widget buildInfoTag(String label, List images) { + return GestureDetector( + onTap: () { + showImagePreview(images); + }, + child: Container( + margin: const EdgeInsets.only(left: 4), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: const Color(0xFFF2F3F5), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + label, + style: TextStyle(color: Color(0xFF999999), fontSize: 11.sp), + ), ), - child: Text( - label, - style: TextStyle(color: Color(0xFF999999), fontSize: 11.sp), + ); + } + + /// 显示图片预览弹窗 + void showImagePreview(List images) { + if (images.isEmpty) return; + + final RxInt currentIndex = 0.obs; + final PageController pageController = PageController(); + + Get.dialog( + GestureDetector( + onTap: () => Get.back(), + child: Stack( + alignment: Alignment.center, + children: [ + Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 图片翻页 + SizedBox( + height: Get.height * 0.5, + child: PhotoViewGallery.builder( + scrollPhysics: const BouncingScrollPhysics(), + builder: (BuildContext context, int index) { + return PhotoViewGalleryPageOptions( + imageProvider: NetworkImage(images[index]), + initialScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes(tag: images[index]), + onTapDown: (context, details, controllerValue) { + _showSaveImageMenu(images[index]); + }, + ); + }, + itemCount: images.length, + loadingBuilder: (context, event) => const Center( + child: CircularProgressIndicator(color: Colors.white), + ), + backgroundDecoration: const BoxDecoration( + color: Colors.transparent, + ), + pageController: pageController, + onPageChanged: (index) => currentIndex.value = index, + ), + ), + SizedBox(height: 10.h), + // 页码指示器 + Center( + child: Text( + "${currentIndex.value + 1} / ${images.length}", + style: const TextStyle( + color: Colors.white, + fontSize: 16, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + // 关闭按钮 + Positioned( + top: 150.h, + right: 20, + child: IconButton( + icon: const Icon(Icons.close, color: Colors.white, size: 30), + onPressed: () => Get.back(), + ), + ), + ], + ), + ), + useSafeArea: false, + ); + } + + void _showSaveImageMenu(String url) { + Get.bottomSheet( + Container( + color: Colors.white, + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.download), + title: const Text('保存图片到相册'), + onTap: () { + Get.back(); + saveImageToLocal(url); + }, + ), + const Divider(height: 1), + ListTile( + title: const Text('取消', textAlign: TextAlign.center), + onTap: () => Get.back(), + ), + ], + ), + ), ), ); } diff --git a/ln_jq_app/lib/pages/b_page/site/view.dart b/ln_jq_app/lib/pages/b_page/site/view.dart index a6e5e31..1d146cf 100644 --- a/ln_jq_app/lib/pages/b_page/site/view.dart +++ b/ln_jq_app/lib/pages/b_page/site/view.dart @@ -550,10 +550,11 @@ class SitePage extends GetView { Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (item.hasDrivingAttachment) _buildInfoTag('行驶证'), + if (item.hasDrivingAttachment) + controller.buildInfoTag('行驶证',item.drivingAttachments), if (item.hasHydrogenationAttachment) ...[ SizedBox(width: 8.w), - _buildInfoTag('加氢证'), + controller.buildInfoTag('加氢证',item.hydrogenationAttachments) ], Spacer(), if (item.isEdit == "1") ...[ @@ -602,19 +603,7 @@ class SitePage extends GetView { ); } - Widget _buildInfoTag(String label) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: const Color.fromRGBO(242, 243, 245, 1), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - label, - style: TextStyle(color: Color.fromRGBO(78, 89, 105, 1), fontSize: 11.sp), - ), - ); - } + Widget _buildSmallButton( String text, { diff --git a/ln_jq_app/lib/pages/c_page/reservation/controller.dart b/ln_jq_app/lib/pages/c_page/reservation/controller.dart index 0b0a36a..c8599cf 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -227,6 +227,8 @@ class C_ReservationController extends GetxController with BaseControllerMixin { hasHydrogenationAttachment: true, hasDrivingAttachment: true, isEdit: '', + drivingAttachments: [], + hydrogenationAttachments: [], ); //打开预约列表 diff --git a/ln_jq_app/pubspec.lock b/ln_jq_app/pubspec.lock index feaf345..c6452f9 100644 --- a/ln_jq_app/pubspec.lock +++ b/ln_jq_app/pubspec.lock @@ -589,6 +589,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.7.2" + image_gallery_saver: + dependency: "direct main" + description: + name: image_gallery_saver + sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" image_picker: dependency: "direct main" description: diff --git a/ln_jq_app/pubspec.yaml b/ln_jq_app/pubspec.yaml index 00dd76d..9aae053 100644 --- a/ln_jq_app/pubspec.yaml +++ b/ln_jq_app/pubspec.yaml @@ -16,12 +16,12 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.2.3+6 +version: 1.2.2+5 environment: sdk: ^3.9.0 -# Dependencies specify other packages that your package needs in order to work. +# Dependencies specify other packages tha。。。t your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions # consider running `flutter pub upgrade --major-versions`. Alternatively, # dependencies can be manually updated by changing the version numbers below to @@ -53,6 +53,7 @@ dependencies: aliyun_push_flutter: ^1.3.6 pull_to_refresh: ^2.0.0 flutter_app_update: ^3.2.2 + image_gallery_saver: ^2.0.3 dev_dependencies: