|
|
@@ -7,6 +7,8 @@ import 'package:excel/excel.dart';
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
|
import 'package:open_filex/open_filex.dart';
|
|
|
+import 'package:device_info_plus/device_info_plus.dart';
|
|
|
+import 'package:media_scanner/media_scanner.dart';
|
|
|
|
|
|
/// Excel 导出工具类(仅支持 excel: ^4.0.0)
|
|
|
class ExcelExportUtil {
|
|
|
@@ -60,20 +62,10 @@ class ExcelExportUtil {
|
|
|
}
|
|
|
if (fileName.isEmpty) throw Exception('文件名不能为空');
|
|
|
|
|
|
- // 2. Android权限申请
|
|
|
- if (Platform.isAndroid) {
|
|
|
- final storagePermission = Permission.manageExternalStorage;
|
|
|
-
|
|
|
- // 检查是否有管理外部存储权限
|
|
|
- if (!await storagePermission.isGranted) {
|
|
|
- // 尝试请求权限
|
|
|
- var status = await storagePermission.request();
|
|
|
- if (!status.isGranted) {
|
|
|
- // 如果管理外部存储权限被拒绝,引导用户去设置页面
|
|
|
- await openAppSettings();
|
|
|
- throw Exception('存储权限被拒绝,请在设置中开启存储权限');
|
|
|
- }
|
|
|
- }
|
|
|
+ // 2. 检查权限
|
|
|
+ final hasPermission = await _checkAndRequestStoragePermission();
|
|
|
+ if (!hasPermission) {
|
|
|
+ throw Exception('存储权限被拒绝');
|
|
|
}
|
|
|
|
|
|
// 3. 获取表头和字段映射
|
|
|
@@ -131,7 +123,7 @@ class ExcelExportUtil {
|
|
|
sheet.setColumnWidth(col, 22);
|
|
|
}
|
|
|
|
|
|
- // 8. 处理保存路径(核心修改:创建自定义文件夹)
|
|
|
+ // 8. 处理保存路径
|
|
|
// 8.1 获取下载目录
|
|
|
final downloadDir = await _getSafeDownloadDirectory();
|
|
|
|
|
|
@@ -139,7 +131,7 @@ class ExcelExportUtil {
|
|
|
final customDir = Directory('${downloadDir.path}/$filePath');
|
|
|
if (!await customDir.exists()) {
|
|
|
await customDir.create(recursive: true);
|
|
|
- logger.d('自定义文件夹创建成功:${customDir.path}');
|
|
|
+ // logger.d('自定义文件夹创建成功:${customDir.path}');
|
|
|
}
|
|
|
|
|
|
// 8.3 处理文件名(过滤特殊字符)
|
|
|
@@ -149,6 +141,10 @@ class ExcelExportUtil {
|
|
|
// 8.4 保存文件
|
|
|
await File(fullFilePath).writeAsBytes(excel.save()!);
|
|
|
|
|
|
+ // 8.5. 触发媒体扫描(让文件管理器显示)
|
|
|
+ // await CommonChannel.scanFile(fullFilePath);
|
|
|
+ await MediaScanner.loadMedia(path: fullFilePath);
|
|
|
+
|
|
|
// 9. 打开文件
|
|
|
await OpenFilex.open(fullFilePath);
|
|
|
logger.d('导出成功:$fullFilePath');
|
|
|
@@ -159,30 +155,33 @@ class ExcelExportUtil {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// 安全地获取下载目录路径
|
|
|
+ static Future<bool> _checkAndRequestStoragePermission() async {
|
|
|
+ if (!Platform.isAndroid) return true;
|
|
|
+
|
|
|
+ final androidInfo = await DeviceInfoPlugin().androidInfo;
|
|
|
+ final sdkInt = androidInfo.version.sdkInt;
|
|
|
+
|
|
|
+ // Android 11+(API 30+):写入Download公共目录无需权限,无需请求manageExternalStorage
|
|
|
+ if (sdkInt >= 30) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // Android 10(API 29):依赖requestLegacyExternalStorage,请求普通存储权限
|
|
|
+ else if (sdkInt == 29) {
|
|
|
+ final status = await Permission.storage.request();
|
|
|
+ return status.isGranted;
|
|
|
+ }
|
|
|
+ // Android 9及以下(API ≤28):请求普通存储权限
|
|
|
+ else {
|
|
|
+ final status = await Permission.storage.request();
|
|
|
+ return status.isGranted;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 安全获取系统公共下载目录(修复Android路径问题)
|
|
|
static Future<Directory> _getSafeDownloadDirectory() async {
|
|
|
// 获取下载目录
|
|
|
Directory? downloadDir = await getDownloadsDirectory();
|
|
|
|
|
|
- // if (downloadDir == null) {
|
|
|
- // logger.e("获取下载目录失败,换种方法重新获取...");
|
|
|
- // if (Platform.isAndroid) {
|
|
|
- // // Android 下载目录
|
|
|
- // downloadDir = await getExternalStorageDirectory();
|
|
|
- // logger.i("Android 根目录: ${downloadDir?.path}");
|
|
|
- // if (downloadDir != null) {
|
|
|
- // downloadDir = Directory(
|
|
|
- // '${downloadDir.parent.parent.parent.parent}/Download',
|
|
|
- // );
|
|
|
- // logger.i("Android 下载目录: ${downloadDir.path}");
|
|
|
- // }
|
|
|
- // } else if (Platform.isIOS) {
|
|
|
- // // iOS 下载目录
|
|
|
- // downloadDir = await getApplicationSupportDirectory();
|
|
|
- // downloadDir = Directory('${downloadDir.path}/Download');
|
|
|
- // }
|
|
|
- // }
|
|
|
-
|
|
|
// 安全地处理 Android 下载目录路径
|
|
|
if (downloadDir != null) {
|
|
|
try {
|
|
|
@@ -205,23 +204,52 @@ class ExcelExportUtil {
|
|
|
// 如果成功向上访问了足够的层级,则构造新的下载目录路径
|
|
|
if (levelsUp >= 4) {
|
|
|
downloadDir = Directory('${parentDir!.path}/Download');
|
|
|
- logger.i("重构后的 Android 下载目录: ${downloadDir.path}");
|
|
|
+ // logger.i("重构后的 Android 下载目录: ${downloadDir.path}");
|
|
|
} else {
|
|
|
// 向上访问层级不够,使用原始下载目录
|
|
|
logger.w("无法安全重构下载目录路径,使用原始下载目录: ${downloadDir.path}");
|
|
|
}
|
|
|
} catch (e) {
|
|
|
- // 出现任何异常都回退到原始下载目录
|
|
|
+ // 处理异常
|
|
|
+ logger.e("获取下载目录异常:$e");
|
|
|
+ downloadDir = await getExternalStorageDirectory();
|
|
|
+ downloadDir = Directory('${downloadDir?.path}/Download');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- logger.i("下载目录: ${downloadDir?.path}");
|
|
|
+ // 确保目录存在
|
|
|
downloadDir ??= await getApplicationDocumentsDirectory();
|
|
|
- logger.i("下载目录2: ${downloadDir.path}");
|
|
|
+ if (!await downloadDir.exists()) {
|
|
|
+ await downloadDir.create(recursive: true);
|
|
|
+ }
|
|
|
|
|
|
+ // logger.i("最终下载目录:${downloadDir.path}");
|
|
|
return downloadDir;
|
|
|
}
|
|
|
|
|
|
+ // /// 安全地获取下载目录路径
|
|
|
+ // static Future<Directory> _getSafeDownloadDirectory() async {
|
|
|
+ // Directory? downloadDir;
|
|
|
+
|
|
|
+ // if (Platform.isAndroid) {
|
|
|
+ // // Android 下载目录(/storage/emulated/0/Download)
|
|
|
+ // downloadDir = await getExternalStorageDirectory();
|
|
|
+ // if (downloadDir != null) {
|
|
|
+ // downloadDir = Directory(
|
|
|
+ // '${downloadDir.parent.parent.parent.path}/Download',
|
|
|
+ // );
|
|
|
+ // }
|
|
|
+ // } else if (Platform.isIOS) {
|
|
|
+ // // iOS 下载目录(App 沙盒内的 Downloads,用户可通过文件 App 访问)
|
|
|
+ // downloadDir = await getApplicationDocumentsDirectory();
|
|
|
+ // downloadDir = Directory('${downloadDir.path}/Download');
|
|
|
+ // }
|
|
|
+ // downloadDir ??= await getApplicationDocumentsDirectory();
|
|
|
+ // logger.i("下载目录2: ${downloadDir.path}");
|
|
|
+
|
|
|
+ // return downloadDir;
|
|
|
+ // }
|
|
|
+
|
|
|
/// 获取已注册的模板列表
|
|
|
static List<String> getRegisteredTemplates() =>
|
|
|
_headerTemplates.keys.toList();
|