From 1af4a2a548b06aafa6041beedbc371a2f5e78914 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Tue, 3 Mar 2026 16:15:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(meeting):=20=E5=A2=9E=E5=8A=A0=E4=BA=91?= =?UTF-8?q?=E7=AB=AF=E9=9F=B3=E9=A2=91=E4=B8=8B=E8=BD=BD=E5=8F=8A=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=88=86=E4=BA=AB=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增提示文案“正在从云端获取音频...”用于本地音频缺失时下载提示 - 在MeetingDetailsController中添加downloadAudioForShare方法,实现云端音频的异步下载并返回本地路径 - 在分享底部弹窗增加导出音频功能,优先使用本地文件,若缺失则尝试从云端下载 - 下载成功后自动更新本地文件路径并触发分享,失败则显示文件不存在提示 - 分享方法中增加本地文件有效性检查,提升分享流程的鲁棒性 - 优化日志输出格式,统一错误日志信息,便于排查问题 - 修改导出PDF等其他功能的错误日志格式,使日志信息更清晰明确 --- lib/core/translations/language/en_us.dart | 1 + lib/core/translations/language/zh_cn.dart | 1 + .../meeting_details_controller.dart | 41 +++++++++++++ .../views/bottomSheet/share_bottom_sheet.dart | 61 +++++++++++++++++-- 4 files changed, 98 insertions(+), 6 deletions(-) diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index 46eef9a7c..94fbae011 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -699,6 +699,7 @@ const Map enUS = { 'playerNotReady': 'Audio player is not ready, please wait', 'playFailed': 'Playback failed', 'audioFileNotFound': 'Audio file not found', + 'fetchingAudio': 'Fetching audio from cloud...', // Prompt shown when local audio file is missing and downloading from cloud 'playerLoadFailed': 'Audio player failed to load', // Meeting模块 - 录音语言相关 diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index 50e21b7d3..658e8886a 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -648,6 +648,7 @@ const Map zhCN = { 'playerNotReady': '音频播放器未准备好,请稍候', 'playFailed': '播放失败', 'audioFileNotFound': '音频文件不存在', + 'fetchingAudio': '正在从云端获取音频...', // 导出音频时本地文件不存在,尝试从云端下载的提示 'playerLoadFailed': '音频播放器加载失败', // Meeting模块 - 录音语言相关 diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index 841538c0f..92be8644e 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -306,6 +306,47 @@ class MeetingDetailsController extends GetxController isDownload.value = false; } + /// 从云端下载音频文件(用于分享/导出场景) + /// 与 [downloadAudio] 的区别:有返回值,便于调用方判断是否成功 + /// + /// 返回值:下载成功的本地文件路径;失败时返回 null + Future downloadAudioForShare() async { + final audioUrl = meetingData.value.audiourl; + if (audioUrl.isEmpty) { + Logger.error('云端音频URL为空,无法从云端下载'); + return null; + } + try { + final directory = await getApplicationDocumentsDirectory(); + final directoryDir = Directory('${directory.path}/DownloadAudio'); + if (!await directoryDir.exists()) { + await directoryDir.create(recursive: true); + } + final fileName = audioUrl.split('/').last; + final String filePath = '${directory.path}/DownloadAudio/$fileName'; + await Dio().download( + audioUrl, + filePath, + onReceiveProgress: (int received, int total) { + downloadProgress.value = total > 0 ? received / total : 0.0; + }, + ); + // 更新本地路径并持久化 + meetingData.value.filepath = filePath; + meetingData.refresh(); + SqfliteApi.editMeetingTitle(id, {'filePath': filePath}); + // 若播放器尚未初始化,顺带加载(异步,不阻塞分享流程) + if (!isPlayerReady.value) { + _loadPlayerController(); + } + Logger.d('MeetingDetails', '云端音频下载成功: $filePath'); + return filePath; + } catch (e) { + Logger.error('从云端下载音频失败: $e'); + return null; + } + } + void editSpeakerText() async { if (editSpeakerTextId.value != 0 && editSpeakerTextChange.isNotEmpty) { Map? foundElement = editSpeakerTextList.firstWhere( diff --git a/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart index 4fe9f8024..853c90106 100644 --- a/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/share_bottom_sheet.dart @@ -241,7 +241,7 @@ class _ShareBottomSheetState extends State { 'shareSuccess'.tr); // 对应中文:分享完成 } } catch (e) { - Logger.error('分享链接失败', e); // 技术日志 + Logger.error('分享链接失败: $e'); // 技术日志 EasyLoading.showToast( 'operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 } @@ -296,10 +296,9 @@ class _ShareBottomSheetState extends State { // - title: 分享时显示的标题文本 _shareItem( Icons.multitrack_audio, 'exportAudio'.tr, // 对应中文:导出音频 - onTap: () { + onTap: () async { Get.back(); - _share(_controller.meetingData.value.filepath, - 'shareMeetingAudio'.tr); // 对应中文:分享会议音频 + await _exportAudio(); }), // 【核心功能模块5:导出转写】 // 功能说明:将转写内容导出为TXT文件并分享 @@ -543,6 +542,48 @@ class _ShareBottomSheetState extends State { /// 核心功能方法 /// ========================================================================== + /// 导出音频方法 + /// 优先使用本地文件;本地不存在时尝试从云端下载,下载成功后继续分享 + /// + /// 执行流程: + /// 1. 检查本地文件是否存在 → 直接分享 + /// 2. 本地不存在 → 检查是否有云端 URL → 无 URL 则显示"文件不存在" + /// 3. 有云端 URL → 显示加载提示并执行下载 + /// 4. 下载成功 → 继续分享;下载失败 → 显示"文件不存在"提示 + Future _exportAudio() async { + final filepath = _controller.meetingData.value.filepath; + final audioUrl = _controller.meetingData.value.audiourl; + + // 本地文件存在,直接分享 + if (filepath.isNotEmpty && File(filepath).existsSync()) { + _share(filepath, 'shareMeetingAudio'.tr); // 对应中文:分享会议音频 + return; + } + + // 本地不存在,检查是否有云端 URL 可以下载 + if (audioUrl.isEmpty) { + Logger.error('导出音频失败: 本地文件不存在且云端URL为空'); + EasyLoading.showToast('audioFileNotFound'.tr); // 对应中文:音频文件不存在 + return; + } + + // 从云端下载 + EasyLoading.show(status: 'fetchingAudio'.tr); // 对应中文:正在从云端获取音频... + final downloadedPath = await _controller.downloadAudioForShare(); + EasyLoading.dismiss(); + + if (downloadedPath != null && + downloadedPath.isNotEmpty && + File(downloadedPath).existsSync()) { + // 下载成功,继续分享 + _share(downloadedPath, 'shareMeetingAudio'.tr); // 对应中文:分享会议音频 + } else { + // 下载失败,显示兜底提示 + Logger.error('导出音频失败: 云端下载失败 (url: $audioUrl)'); + EasyLoading.showToast('audioFileNotFound'.tr); // 对应中文:音频文件不存在 + } + } + /// 分享文件方法 /// 使用 SharePlus 插件分享文件到系统分享面板 /// @@ -558,6 +599,13 @@ class _ShareBottomSheetState extends State { /// 返回值:Future - 异步操作 Future _share(String filepath, String title) async { try { + // 检查文件路径是否为空或文件是否存在 + if (filepath.isEmpty || !File(filepath).existsSync()) { + Logger.error('分享文件失败: 文件不存在 (path: $filepath)'); + EasyLoading.showToast( + 'audioFileNotFound'.tr); // 对应中文:音频文件不存在 + return; + } final result = await SharePlus.instance.share(ShareParams( text: title, files: [XFile(filepath)], @@ -567,7 +615,8 @@ class _ShareBottomSheetState extends State { EasyLoading.showToast('shareSuccess'.tr); // 对应中文:分享完成! } } catch (e) { - Logger.error('分享文件失败: $filepath', e); // 技术日志 + // 将错误信息合并到单条日志消息中,避免多行日志被截断 + Logger.error('分享文件失败 (path: $filepath, 错误: $e)'); EasyLoading.showToast('operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 } } @@ -781,7 +830,7 @@ class _ShareBottomSheetState extends State { await file.writeAsBytes(await markdownPdf.save()); await _share(file.path, 'exportSummary'.tr); // 对应中文:分享会议总结 } catch (e) { - Logger.error('导出PDF失败', e); // 技术日志 + Logger.error('导出PDF失败: $e'); // 技术日志 EasyLoading.showToast('operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 } finally { EasyLoading.dismiss();