Browse Source

refactor(share): 优化分享底部弹窗代码结构与权限校验

- 新增思维导图操作类型常量类,提升代码可读性
- 提取转写与总结权限判定逻辑,简化权限判断
- 实现文件名非法字符过滤,避免文件创建异常
- 抽象思维导图保存与分享操作为统一方法,减少重复代码
- 统一分享和导出失败提示,增加详细日志记录
- 替换硬编码权限判断为封装属性
- 对导出文件命名应用安全文件名处理,防止路径错误
- 优化WebView截图异步处理逻辑,提升稳定性
weicu
lxm 7 months ago
parent
commit
a3cb11570d
  1. 147
      lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart

147
lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart

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

Loading…
Cancel
Save