diff --git a/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart index 171aa9e8e..4fe9f8024 100644 --- a/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:common_utils/common_utils.dart'; @@ -13,6 +14,7 @@ import 'package:share_plus/share_plus.dart'; import 'package:webview_flutter/webview_flutter.dart'; +import '../../../../core/utils/logger.dart'; import '../../../../data/services/network/dio_manager.dart'; import '../../controllers/meeting_details_controller.dart'; import '../../controllers/mind_map_controller.dart'; @@ -37,6 +39,15 @@ import 'second_level_bottom_sheet.dart'; /// - 复制/导出/思维导图功能需要 tasktype >= 5 且 tasktype < 10001 /// ============================================================================ +/// 思维导图操作类型常量 +class _MindMapActionType { + /// 保存到本地相册 + static const int save = 1; + + /// 分享给他人 + static const int share = 2; +} + /// 分享底部弹窗 - 有状态组件 /// 使用StatefulWidget以便在build方法中多次获取主题状态 class ShareBottomSheet extends StatefulWidget { @@ -73,6 +84,44 @@ class _ShareBottomSheetState extends State { ); } + /// 是否有转写权限(tasktype >= 3 且 tasktype < 10002) + bool get _hasTranscriptPermission { + final tasktype = _controller.meetingDetails.value.tasktype; + return tasktype >= 3 && tasktype < 10002; + } + + /// 是否有总结权限(tasktype >= 5 且 tasktype < 10001) + bool get _hasSummaryPermission { + final tasktype = _controller.meetingDetails.value.tasktype; + return tasktype >= 5 && tasktype < 10001; + } + + /// 清理文件名中的非法字符 + /// 过滤 / \ : * ? " < > | 等文件系统不允许的字符 + String _sanitizeFileName(String name) { + return name.replaceAll(RegExp(r'[/\\:*?"<>|]'), '_'); + } + + /// 处理思维导图操作(保存/分享) + /// [actionType] 操作类型,使用 [_MindMapActionType] 常量 + void _handleMindMapAction(int actionType) { + Get.back(); + final mindMapController = Get.find(); + mindMapController.imageType = actionType; + if (mindMapController.isWebViewReady) { + mindMapController.webViewController.runJavaScript('captureImage()'); + } else { + mindMapController.webViewController.setNavigationDelegate( + NavigationDelegate( + onPageFinished: (String url) { + mindMapController.isWebViewReady = true; + mindMapController.webViewController.runJavaScript('captureImage()'); + }, + ), + ); + } + } + /// ========================================================================== /// 构建UI界面 /// ========================================================================== @@ -192,8 +241,9 @@ class _ShareBottomSheetState extends State { 'shareSuccess'.tr); // 对应中文:分享完成 } } catch (e) { - EasyLoading.showToast('shareFailed'.tr.replaceAll( - '{error}', e.toString())); // 对应中文:分享失败 + Logger.error('分享链接失败', e); // 技术日志 + EasyLoading.showToast( + 'operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 } }, ), @@ -210,9 +260,7 @@ class _ShareBottomSheetState extends State { // 4. 调用 _copy() 方法复制到剪贴板 // 数据格式:说话人 + 时间戳 + 内容 _shareItem(Icons.copy, 'copyTranscript'.tr, // 对应中文:复制转写 - onTap: _controller.meetingDetails.value.tasktype >= 3 && - _controller.meetingDetails.value.tasktype < - 10002 + onTap: _hasTranscriptPermission ? () { Get.back(); _copy(_getAllText()); @@ -228,9 +276,7 @@ class _ShareBottomSheetState extends State { // 4. 调用 _copy() 方法复制到剪贴板 _shareItem(Icons.copy, 'copySummary'.tr, // 对应中文:复制总结 isBorder: false, - onTap: _controller.meetingDetails.value.tasktype >= 5 && - _controller.meetingDetails.value.tasktype < - 10001 + onTap: _hasSummaryPermission ? () { Get.back(); _copy(_markdownToPlainText( @@ -267,16 +313,17 @@ class _ShareBottomSheetState extends State { // 6. 调用 _share() 方法分享文件 _shareItem( Icons.message, 'exportTranscript'.tr, // 对应中文:导出转写 - onTap: _controller.meetingDetails.value.tasktype >= 3 && - _controller.meetingDetails.value.tasktype < - 10002 + onTap: _hasTranscriptPermission ? () async { Get.back(); final tempDir = await getTemporaryDirectory(); + final safeTitle = _sanitizeFileName( + _controller.meetingData.value.title); final file = File( - '${tempDir.path}/${_controller.meetingData.value.title}_speaker.txt'); + '${tempDir.path}/${safeTitle}_speaker.txt'); String allText = _getAllText(); - await file.writeAsString(allText); + await file.writeAsString(allText, + encoding: utf8); _share(file.path, 'exportTranscript'.tr); // 对应中文:分享会议转写 } @@ -292,9 +339,7 @@ class _ShareBottomSheetState extends State { // - TXT: 创建临时文件,写入纯文本内容,调用 _share() 分享 // - PDF: 调用 _exportMarkdownPdf() 方法生成PDF _shareItem(Icons.article, 'exportSummary'.tr, // 对应中文:导出总结 - onTap: _controller.meetingDetails.value.tasktype >= 5 && - _controller.meetingDetails.value.tasktype < - 10001 + onTap: _hasSummaryPermission ? () async { Get.back(); Get.bottomSheet( @@ -308,11 +353,15 @@ class _ShareBottomSheetState extends State { // 导出TXT格式 final tempDir = await getTemporaryDirectory(); + final safeTitle = _sanitizeFileName( + _controller + .meetingData.value.title); final file = File( - '${tempDir.path}/${_controller.meetingData.value.title}_summary.txt'); + '${tempDir.path}/${safeTitle}_summary.txt'); await file.writeAsString( _markdownToPlainText(_controller - .meetingDetails.value.summary)); + .meetingDetails.value.summary), + encoding: utf8); _share(file.path, 'exportSummary'.tr); // 对应中文:分享会议总结 } else { @@ -360,29 +409,8 @@ class _ShareBottomSheetState extends State { _shareItem( Icons.save_alt, 'saveMindMap'.tr, - onTap: _controller.meetingDetails.value.tasktype >= 5 && - _controller.meetingDetails.value.tasktype < 10001 - ? () { - Get.back(); - final mindMapController = - Get.find(); - mindMapController.imageType = 1; // 1=保存到本地 - if (mindMapController.isWebViewReady) { - mindMapController.webViewController - .runJavaScript('captureImage()'); - } else { - mindMapController.webViewController - .setNavigationDelegate( - NavigationDelegate( - onPageFinished: (String url) { - mindMapController.isWebViewReady = true; - mindMapController.webViewController - .runJavaScript('captureImage()'); - }, - ), - ); - } - } + onTap: _hasSummaryPermission + ? () => _handleMindMapAction(_MindMapActionType.save) : null, ), // 【核心功能模块8:分享思维导图】 @@ -401,29 +429,8 @@ class _ShareBottomSheetState extends State { Icons.lan, 'shareMindMap'.tr, // 对应中文:分享思维导图 isBorder: false, - onTap: _controller.meetingDetails.value.tasktype >= 5 && - _controller.meetingDetails.value.tasktype < 10001 - ? () { - Get.back(); - final mindMapController = - Get.find(); - mindMapController.imageType = 2; // 2=分享 - if (mindMapController.isWebViewReady) { - mindMapController.webViewController - .runJavaScript('captureImage()'); - } else { - mindMapController.webViewController - .setNavigationDelegate( - NavigationDelegate( - onPageFinished: (String url) { - mindMapController.isWebViewReady = true; - mindMapController.webViewController - .runJavaScript('captureImage()'); - }, - ), - ); - } - } + onTap: _hasSummaryPermission + ? () => _handleMindMapAction(_MindMapActionType.share) : null, ), ]), @@ -560,8 +567,8 @@ class _ShareBottomSheetState extends State { EasyLoading.showToast('shareSuccess'.tr); // 对应中文:分享完成! } } catch (e) { - EasyLoading.showToast( - 'shareFailed'.tr.replaceAll('{error}', e.toString())); // 对应中文:分享失败 + Logger.error('分享文件失败: $filepath', e); // 技术日志 + EasyLoading.showToast('operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 } } @@ -757,8 +764,8 @@ class _ShareBottomSheetState extends State { EasyLoading.show(status: 'exportingPDF'.tr); // 对应中文:正在导出PDF... try { final tempDir = await getTemporaryDirectory(); - final file = File( - '${tempDir.path}/${_controller.meetingData.value.title}_summary.pdf'); + final safeTitle = _sanitizeFileName(_controller.meetingData.value.title); + final file = File('${tempDir.path}/${safeTitle}_summary.pdf'); final List markdownWidgets = await pdf.HTMLToPdf().convertMarkdown( _controller.meetingDetails.value.summary, @@ -774,8 +781,8 @@ class _ShareBottomSheetState extends State { await file.writeAsBytes(await markdownPdf.save()); await _share(file.path, 'exportSummary'.tr); // 对应中文:分享会议总结 } catch (e) { - EasyLoading.showToast( - 'shareFailed'.tr.replaceAll('{error}', e.toString())); // 对应中文:分享失败 + Logger.error('导出PDF失败', e); // 技术日志 + EasyLoading.showToast('operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 } finally { EasyLoading.dismiss(); }