diff --git a/ln_jq_app/ios/Runner/Info.plist b/ln_jq_app/ios/Runner/Info.plist
index 0126237..629759d 100644
--- a/ln_jq_app/ios/Runner/Info.plist
+++ b/ln_jq_app/ios/Runner/Info.plist
@@ -76,5 +76,11 @@
en
+UIFileSharingEnabled
+
+
+LSSupportsOpeningDocumentsInPlace
+
+
diff --git a/ln_jq_app/lib/pages/b_page/site/controller.dart b/ln_jq_app/lib/pages/b_page/site/controller.dart
index 3324f68..30dc70b 100644
--- a/ln_jq_app/lib/pages/b_page/site/controller.dart
+++ b/ln_jq_app/lib/pages/b_page/site/controller.dart
@@ -3,12 +3,14 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:getx_scaffold/getx_scaffold.dart' as dio;
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:image_picker/image_picker.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/common/styles/theme.dart';
import 'package:ln_jq_app/storage_service.dart';
+import 'package:path_provider/path_provider.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
@@ -794,54 +796,83 @@ class SiteController extends GetxController with BaseControllerMixin {
}
/// 保存图片到相册
- Future saveImageToLocal(String url) async {
- // 1. 权限请求
- if (Platform.isAndroid) {
- dio.PermissionStatus status;
+ Future saveFileToLocal(String url) async {
+ try {
+ // 权限请求
+ if (Platform.isAndroid) {
+ dio.PermissionStatus status;
- final deviceInfo = await DeviceInfoPlugin().androidInfo;
- final sdkInt = deviceInfo.version.sdkInt;
+ final deviceInfo = await DeviceInfoPlugin().androidInfo;
+ final sdkInt = deviceInfo.version.sdkInt;
- if (sdkInt <= 32) {
- status = await Permission.storage.request();
+ if (sdkInt <= 32) {
+ status = await Permission.storage.request();
+ } else {
+ status = await Permission.photos.request();
+ }
+
+ if (!status.isGranted) {
+ showErrorToast("请在系统设置中开启存储权限");
+ return;
+ }
} else {
- status = await Permission.photos.request();
+ var status = await Permission.photos.request();
+ if (!status.isGranted) {
+ showErrorToast("请在系统设置中开启相册权限");
+ return;
+ }
}
- if (!status.isGranted) {
- showErrorToast("请在系统设置中开启存储权限");
- return;
+ showLoading("正在保存...");
+
+ // 下载文件
+ var response = await Dio().get(
+ url,
+ options: Options(responseType: ResponseType.bytes),
+ );
+
+ final Uint8List bytes = Uint8List.fromList(response.data);
+
+ if (url.toLowerCase().endsWith('.pdf')) {
+ String? savePath;
+
+ if (Platform.isAndroid) {
+ final directory = Directory('/storage/emulated/0/Download');
+ if (!await directory.exists()) {
+ await directory.create(recursive: true);
+ }
+ final String fileName = "certificate_${DateTime.now().millisecondsSinceEpoch}.pdf";
+ savePath = "${directory.path}/$fileName";
+ } else {
+ // iOS: 保存到文档目录
+ final directory = await getApplicationDocumentsDirectory();
+ final String fileName = "certificate_${DateTime.now().millisecondsSinceEpoch}.pdf";
+ savePath = "${directory.path}/$fileName";
+ }
+
+ final File file = File(savePath);
+ await file.writeAsBytes(bytes);
+
+ dismissLoading();
+ showSuccessToast(Platform.isAndroid ? "PDF已保存至系统下载目录" : "PDF已保存,请在'文件'App中查看");
+ } else {
+ // 保存图片到相册
+ final result = await SaverGallery.saveImage(
+ bytes,
+ quality: 100,
+ fileName: "certificate_${DateTime.now().millisecondsSinceEpoch}",
+ skipIfExists: false,
+ );
+ dismissLoading();
+ if (result.isSuccess) {
+ showSuccessToast("图片已保存至相册");
+ } else {
+ showErrorToast("保存失败");
+ }
}
- } 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 SaverGallery.saveImage(
- Uint8List.fromList(response.data),
- quality: 100,
- fileName: "certificate_${DateTime.now().millisecondsSinceEpoch}",
- skipIfExists: false,
- );
-
- dismissLoading();
-
- if (result.isSuccess) {
- showSuccessToast("图片已保存至相册");
- } else {
- showErrorToast("保存失败");
+ } catch (e) {
+ dismissLoading();
+ showErrorToast("保存异常");
}
}
@@ -888,12 +919,30 @@ class SiteController extends GetxController with BaseControllerMixin {
child: PhotoViewGallery.builder(
scrollPhysics: const BouncingScrollPhysics(),
builder: (BuildContext context, int index) {
+ final String url = images[index];
+ final bool isPdf = url.toLowerCase().endsWith('.pdf');
+
+ if (isPdf) {
+ return PhotoViewGalleryPageOptions.customChild(
+ child: GestureDetector(
+ onTap: (){
+ _showSaveMenu(url);
+ },
+ child: _buildPdfPreview(url),),
+ initialScale: PhotoViewComputedScale.contained,
+ heroAttributes: PhotoViewHeroAttributes(tag: url),
+ onTapDown: (context, details, controllerValue) {
+ _showSaveMenu(url);
+ },
+ );
+ }
+
return PhotoViewGalleryPageOptions(
- imageProvider: NetworkImage(images[index]),
+ imageProvider: NetworkImage(url),
initialScale: PhotoViewComputedScale.contained,
- heroAttributes: PhotoViewHeroAttributes(tag: images[index]),
+ heroAttributes: PhotoViewHeroAttributes(tag: url),
onTapDown: (context, details, controllerValue) {
- _showSaveImageMenu(images[index]);
+ _showSaveMenu(url);
},
);
},
@@ -939,7 +988,38 @@ class SiteController extends GetxController with BaseControllerMixin {
);
}
- void _showSaveImageMenu(String url) {
+ /// PDF 预览小部件
+ Widget _buildPdfPreview(String url) {
+ return FutureBuilder(
+ future: _downloadPdf(url),
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.waiting) {
+ return const Center(child: CircularProgressIndicator(color: Colors.white));
+ }
+ if (snapshot.hasError || snapshot.data == null) {
+ return const Center(child: Text("PDF 加载失败", style: TextStyle(color: Colors.white)));
+ }
+ return PDFView(
+ filePath: snapshot.data!,
+ enableSwipe: false,
+ swipeHorizontal: false,
+ autoSpacing: false,
+ pageFling: false,
+ );
+ },
+ );
+ }
+
+ Future _downloadPdf(String url) async {
+ final file = File('${(await getTemporaryDirectory()).path}/${url.hashCode}.pdf');
+ if (await file.exists()) return file.path;
+ var response = await Dio().get(url, options: Options(responseType: ResponseType.bytes));
+ await file.writeAsBytes(response.data);
+ return file.path;
+ }
+
+ void _showSaveMenu(String url) {
+ final bool isPdf = url.toLowerCase().endsWith('.pdf');
Get.bottomSheet(
Container(
color: Colors.white,
@@ -949,10 +1029,10 @@ class SiteController extends GetxController with BaseControllerMixin {
children: [
ListTile(
leading: const Icon(Icons.download),
- title: const Text('保存图片到相册'),
+ title: Text(isPdf ? '保存 PDF 文件' : '保存图片到相册'),
onTap: () {
Get.back();
- saveImageToLocal(url);
+ saveFileToLocal(url);
},
),
const Divider(height: 1),
diff --git a/ln_jq_app/pubspec.lock b/ln_jq_app/pubspec.lock
index ed02b5f..558172d 100644
--- a/ln_jq_app/pubspec.lock
+++ b/ln_jq_app/pubspec.lock
@@ -798,7 +798,7 @@ packages:
source: hosted
version: "1.1.0"
path_provider:
- dependency: transitive
+ dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
diff --git a/ln_jq_app/pubspec.yaml b/ln_jq_app/pubspec.yaml
index 6c575cf..6ef4730 100644
--- a/ln_jq_app/pubspec.yaml
+++ b/ln_jq_app/pubspec.yaml
@@ -54,7 +54,7 @@ dependencies:
pull_to_refresh: ^2.0.0
flutter_app_update: ^3.2.2
saver_gallery: ^4.0.0
-
+ path_provider: ^2.1.5
dev_dependencies:
flutter_test: