From 9ce46a0c7d334a1f8b2f44e30d02bd649bff73b2 Mon Sep 17 00:00:00 2001 From: userGyl Date: Tue, 16 Dec 2025 11:58:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E6=B0=A2=E7=AB=99=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E9=80=89=E6=8B=A9=EF=BC=8C=E6=BC=94=E7=A4=BA=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=20=E9=99=84=E4=BB=B6=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=EF=BC=8C=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ln_jq_app/ios/Podfile.lock | 2 +- .../attachment_viewer_controller.dart | 4 +- .../car_info/attachment_viewer_page.dart | 5 +- .../certificate_viewer_controller.dart | 70 ++++++++- .../car_info/certificate_viewer_page.dart | 135 +++++++++++++----- .../pages/c_page/reservation/controller.dart | 32 +++++ .../lib/pages/c_page/reservation/view.dart | 36 +++-- ln_jq_app/pubspec.lock | 4 +- ln_jq_app/pubspec.yaml | 2 +- 9 files changed, 231 insertions(+), 59 deletions(-) diff --git a/ln_jq_app/ios/Podfile.lock b/ln_jq_app/ios/Podfile.lock index a681750..799a4c7 100644 --- a/ln_jq_app/ios/Podfile.lock +++ b/ln_jq_app/ios/Podfile.lock @@ -95,7 +95,7 @@ SPEC CHECKSUMS: Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - flutter_pdfview: 54e283d5851b0b247b3cc57877d35f1a05a204de + flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb diff --git a/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_controller.dart b/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_controller.dart index 105494f..a9a4f45 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_controller.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_controller.dart @@ -48,9 +48,9 @@ class AttachmentViewerController extends GetxController { } }, ); - + localFilePath.value = savePath; - + } catch (e) { showErrorToast('PDF文件加载失败,请检查网络或文件链接'); print('PDF Download Error: $e'); diff --git a/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_page.dart b/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_page.dart index aaf07b9..666288c 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_page.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/attachment_viewer_page.dart @@ -40,11 +40,10 @@ class AttachmentViewerPage extends GetView { if (controller.fileType == 'pdf') { if (controller.localFilePath.isNotEmpty) { return PDFView( - key: ValueKey(controller.localFilePath.value), filePath: controller.localFilePath.value, enableSwipe: true, - swipeHorizontal: false, - autoSpacing: false, + swipeHorizontal: true, + autoSpacing: true, pageFling: true, onRender: (pages) { print("PDF rendered with $pages pages."); diff --git a/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_controller.dart b/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_controller.dart index 1ce93eb..b431d90 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_controller.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_controller.dart @@ -1,21 +1,77 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:get/get.dart'; import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:path_provider/path_provider.dart'; import 'attachment_viewer_page.dart'; -class CertificateViewerController extends GetxController { +class CertificateViewerController extends GetxController with BaseControllerMixin{ late final String title; late final List attachments; + // --- 新增: 状态管理 --- + /// 用于存储网络PDF的本地路径,key是网络url,value是本地路径 + final RxMap localPdfPaths = {}.obs; + + /// 用于跟踪每个附件的加载状态,key是网络url + final RxMap isLoading = {}.obs; + + @override + String get builderId => "certificateviewer"; + @override void onInit() { super.onInit(); - // 从 Get.to 的 arguments 中获取标题和附件列表 title = Get.arguments['title'] ?? '证件详情'; attachments = List.from(Get.arguments['attachments'] ?? []); + + // --- 新增: 初始化时开始加载所有PDF --- + _loadAllPdfs(); } - /// 导航到通用的附件查看器页面 + /// 遍历所有附件,如果是PDF则进行下载 + void _loadAllPdfs() { + for (var url in attachments) { + if (isPdf(url)) { + _downloadPdf(url); + } + } + } + + /// 下载单个PDF文件 + Future _downloadPdf(String url) async { + if (url.isEmpty) return; + + // 开始加载 + isLoading[url] = true; + + try { + final dio = Dio(); + final Directory tempDir = await getTemporaryDirectory(); + final String savePath = '${tempDir.path}/${url.split('/').last}'; + + // 检查文件是否已存在,避免重复下载 + if (await File(savePath).exists()) { + localPdfPaths[url] = savePath; + isLoading[url] = false; + return; + } + + await dio.download(url, savePath); + + // 下载成功后,更新本地路径 + localPdfPaths[url] = savePath; + } catch (e) { + print('PDF download error for $url: $e'); + // 出错时也可以更新状态,以便UI显示错误提示 + } finally { + // 结束加载 + isLoading[url] = false; + } + } + + /// 导航到通用的附件查看器页面 (此方法保持不变) void openAttachment(String url) { if (url.isEmpty) { showErrorToast('附件链接无效'); @@ -23,15 +79,17 @@ class CertificateViewerController extends GetxController { } Get.to( - () => const AttachmentViewerPage(), + () => const AttachmentViewerPage(), arguments: { - 'url': url, + 'url': url, }, ); } - /// 检查 URL 是否为 PDF,以便在视图中显示不同的图标 + /// 检查 URL 是否为 PDF (此方法保持不变) bool isPdf(String url) { return url.toLowerCase().endsWith('.pdf'); } + + } diff --git a/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_page.dart b/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_page.dart index 11a88e5..7bbc23e 100644 --- a/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_page.dart +++ b/ln_jq_app/lib/pages/c_page/car_info/certificate_viewer_page.dart @@ -1,51 +1,114 @@ import 'package:flutter/material.dart'; +import 'package:flutter_pdfview/flutter_pdfview.dart'; // 引入PDFView import 'package:get/get.dart'; +import 'package:getx_scaffold/common/common.dart'; import 'certificate_viewer_controller.dart'; class CertificateViewerPage extends GetView { - const CertificateViewerPage({Key? key}) : super(key: key); + const CertificateViewerPage({super.key}); @override Widget build(BuildContext context) { - Get.put(CertificateViewerController()); - - return Scaffold( - appBar: AppBar( - title: Text(controller.title), - centerTitle: true, - ), - body: ListView.builder( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), - itemCount: controller.attachments.length, - itemBuilder: (context, index) { - final url = controller.attachments[index]; - // 从 URL 中提取文件名用于显示 - final fileName = url.split('/').last; - - return Card( - elevation: 2, - margin: const EdgeInsets.only(bottom: 12), - child: ListTile( - leading: Icon( - controller.isPdf(url) - ? Icons.picture_as_pdf_rounded // PDF 图标 - : Icons.image_rounded, // 图片图标 - color: controller.isPdf(url) ? Colors.red.shade700 : Colors.blue.shade700, - size: 32, + return GetBuilder( + init: CertificateViewerController(), + id: 'certificateviewer', + builder: (_) { + return Scaffold( + appBar: AppBar(title: Text(controller.title)), + body: Column( + children: [ + SizedBox(height: 16,), + Text( + "点击可查看大图", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), - title: Text( - fileName, - style: const TextStyle(fontSize: 14), - maxLines: 2, - overflow: TextOverflow.ellipsis, + Expanded( + child: ListView.builder( + padding: const EdgeInsets.all(16.0), + itemCount: controller.attachments.length, + itemBuilder: (context, index) { + final url = controller.attachments[index]; + return _buildAttachmentItem(url); + }, + ), ), - trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16, color: Colors.grey), - onTap: () => controller.openAttachment(url), - ), - ); - }, + ], + ), + ); + }, + ); + } + + /// 构建单个附件的显示项 + Widget _buildAttachmentItem(String url) { + return GestureDetector( + onTap: () { + controller.openAttachment(url); + }, // 点击跳转到详情页 + child: Card( + margin: const EdgeInsets.only(bottom: 16.0), + clipBehavior: Clip.antiAlias, // 确保内容不会溢出Card的圆角 + elevation: 4, + // 等比缩放展示 + child: AspectRatio( + aspectRatio: 4 / 3, + child: controller.isPdf(url) + ? Obx(() { + final bool loading = controller.isLoading[url] ?? true; + final String? localPath = controller.localPdfPaths[url]; + + if (loading) { + return _buildLoadingIndicator(); + } else if (localPath != null && localPath.isNotEmpty) { + return IgnorePointer( + ignoring: true, // 设置为 true 来忽略所有指针事件 + child: PDFView( + fitEachPage: true, + filePath: localPath, + fitPolicy: FitPolicy.WIDTH, + // 适配宽度 + enableSwipe: false, + swipeHorizontal: false, + autoSpacing: false, + pageFling: false, + preventLinkNavigation: true, // 顺便禁用PDF内部链接的跳转 + ), + ); + } else { + // PDF加载失败 + return _buildErrorIndicator(); + } + }) + : Image.network( + url, + fit: BoxFit.contain, + // 图片加载时显示loading + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return _buildLoadingIndicator(); + }, + // 图片加载失败时显示错误 + errorBuilder: (context, error, stackTrace) { + return _buildErrorIndicator(); + }, + ), + ), ), ); } + + // 辅助Widget:加载中指示器 + Widget _buildLoadingIndicator() { + return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); + } + + // 辅助Widget:错误指示器 + Widget _buildErrorIndicator() { + return const SizedBox( + height: 200, + child: Center(child: Icon(Icons.error_outline, color: Colors.red, size: 48)), + ); + } } 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 fc95862..0361370 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/controller.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/controller.dart @@ -809,6 +809,25 @@ class C_ReservationController extends GetxController with BaseControllerMixin { } void getSiteList() async { + if(StorageService.to.phone == "13344444444"){ + //该账号给stationOptions手动添加一个数据 + final testStation = StationModel( + hydrogenId: '1142167389150920704', + name: '羚牛氢能演示加氢站', + address: '上海市嘉定区于田南路111号于田大厦', + price: '35.00', // 价格 + siteStatusName: '营运中', // 状态 + isSelect: 1, // 默认可选 + ); + // 使用 assignAll 可以确保列表只包含这个测试数据 + stationOptions.assignAll([testStation]); + + if (stationOptions.isNotEmpty) { + selectedStationId.value = stationOptions.first.hydrogenId; + } + return; + } + showLoading("加载中"); final originalHeaders = Map.from(HttpService.to.dio.options.headers); try { @@ -849,6 +868,19 @@ class C_ReservationController extends GetxController with BaseControllerMixin { } else { showToast('站点列表已刷新'); } + + // 找到第一个可选的站点作为默认值 + if (stationOptions.isNotEmpty) { + final firstSelectable = stationOptions.firstWhere( + (station) => station.isSelect == 1, + orElse: () => stationOptions.first, // 降级:如果没有可选的,就用第一个 + ); + selectedStationId.value = firstSelectable.hydrogenId; + } else { + // 如果列表为空,确保 selectedStationId 也为空 + selectedStationId.value = null; + } + } catch (e) { showToast('数据异常'); } diff --git a/ln_jq_app/lib/pages/c_page/reservation/view.dart b/ln_jq_app/lib/pages/c_page/reservation/view.dart index 787617a..f364712 100644 --- a/ln_jq_app/lib/pages/c_page/reservation/view.dart +++ b/ln_jq_app/lib/pages/c_page/reservation/view.dart @@ -437,20 +437,40 @@ class ReservationPage extends GetView { ), ) .toList(), - value: controller.selectedStationId.value, + value: + // 当前的站点 处理默认 + controller.selectedStationId.value ?? + (controller.stationOptions.isNotEmpty + ? controller.stationOptions.first.hydrogenId + : null), // 当前选中的是站点ID onChanged: (value) { if (value != null) { controller.selectedStationId.value = value; } }, - customButton: controller.selectedStationId.value == null - ? null // 未选择时,显示默认的 hint - : _buildSelectedStationButton( - controller.stationOptions.firstWhere( - (s) => s.hydrogenId == controller.selectedStationId.value, - ), - ), + customButton: Obx(() { + // 优先从已选中的 ID 查找 + var selectedStation = controller.stationOptions.firstWhereOrNull( + (s) => s.hydrogenId == controller.selectedStationId.value, + ); + + // 如果找不到已选中的(比如 ID 为空或列表里没有),并且列表不为空,则取第一个作为默认 + final stationToShow = + selectedStation ?? + (controller.stationOptions.isNotEmpty + ? controller.stationOptions.first + : null); + + // 如果有要显示的站点,就构建按钮 + if (stationToShow != null) { + return _buildSelectedStationButton(stationToShow); + } + + // 否则,返回一个空占位符,让 hint 生效 + // DropdownButton2 内部会判断,如果 customButton 返回的不是一个有效Widget(或根据其内部逻辑),就会显示 hint + return const SizedBox.shrink(); + }), buttonStyleData: ButtonStyleData( height: 40, // 增加高度以容纳两行文字 padding: const EdgeInsets.symmetric(horizontal: 12.0), diff --git a/ln_jq_app/pubspec.lock b/ln_jq_app/pubspec.lock index 18e7f11..018266c 100644 --- a/ln_jq_app/pubspec.lock +++ b/ln_jq_app/pubspec.lock @@ -399,10 +399,10 @@ packages: dependency: "direct main" description: name: flutter_pdfview - sha256: a9055bf920c7095bf08c2781db431ba23577aa5da5a056a7152dc89a18fbec6f + sha256: c0b2cc4ebf461a5a4bb9312a165222475a7d93845c7a0703f4abb7f442eb6d54 url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.2" + version: "1.4.3" flutter_plugin_android_lifecycle: dependency: transitive description: diff --git a/ln_jq_app/pubspec.yaml b/ln_jq_app/pubspec.yaml index 5ae1ee7..93d5242 100644 --- a/ln_jq_app/pubspec.yaml +++ b/ln_jq_app/pubspec.yaml @@ -47,7 +47,7 @@ dependencies: image_picker: ^1.2.1 # 用于从相册选择图片 image: ^4.5.4 zxing_lib: ^1.1.4 - flutter_pdfview: 1.3.2 #显示pdf + flutter_pdfview: 1.4.3 #显示pdf photo_view: ^0.15.0 #操作图片 flutter_inappwebview: ^6.1.5 # WebView插件 geolocator: ^14.0.2 # 获取精确定位