From 3b590f9a1f66d7860c22edc95e1786ccc8f42158 Mon Sep 17 00:00:00 2001 From: dmy Date: Tue, 3 Mar 2026 14:26:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webui/src/components/Log.vue | 8 ++++- webui/src/components/ParameterForm.vue | 43 +++++++++++++++++++++++ webview_app.py | 47 ++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/webui/src/components/Log.vue b/webui/src/components/Log.vue index e8260c0..cbe3878 100644 --- a/webui/src/components/Log.vue +++ b/webui/src/components/Log.vue @@ -67,11 +67,17 @@ const clearLog = () => { logs.value = [] } +// 获取日志文本 +const getLogsText = (): string => { + return logs.value.map(log => `[${log.time}] [${log.level.toUpperCase()}] ${log.message}`).join('\n') +} + // 暴露方法给父组件 defineExpose({ addLog, clearLog, - lastTripRates + lastTripRates, + getLogsText }) diff --git a/webui/src/components/ParameterForm.vue b/webui/src/components/ParameterForm.vue index c8d28b6..561c271 100644 --- a/webui/src/components/ParameterForm.vue +++ b/webui/src/components/ParameterForm.vue @@ -287,6 +287,14 @@ @click="exportConfig" class="px-8" /> + @@ -585,6 +593,40 @@ const parseToml = (tomlStr: string): any => { return result } +// 导出日志 +const exportLog = async () => { + try { + const logText = logRef.value?.getLogsText() || '' + if (!logText) { + logRef.value?.addLog('warning', '暂无日志可导出') + return + } + + if (window.pywebview) { + const response = await window.pywebview.api.export_log(logText) + if (response.success) { + logRef.value?.addLog('info', response.message) + } else { + logRef.value?.addLog('warning', response.message) + } + } else { + // 开发模式下的模拟 + logRef.value?.addLog('info', '导出日志(开发模式,直接下载)') + const blob = new Blob([logText], { type: 'text/plain' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, '') + a.download = `egm_log_${timestamp}.txt` + a.click() + URL.revokeObjectURL(url) + } + } catch (e: any) { + error.value = e.message || '导出日志失败' + logRef.value?.addLog('error', e.message || '导出日志失败') + } +} + // 导出配置 const exportConfig = async () => { try { @@ -659,6 +701,7 @@ declare global { api: { calculate: (params: AllParameters) => Promise export_config: (params: AllParameters) => Promise + export_log: (logText: string) => Promise } } addLogFromBackend?: (log: { level: string; time: string; message: string }) => void diff --git a/webview_app.py b/webview_app.py index 3267d03..c76ae51 100644 --- a/webview_app.py +++ b/webview_app.py @@ -348,6 +348,53 @@ class EGMWebApp: "message": f"保存失败: {str(e)}" } + def export_log(self, log_text: str) -> Dict[str, Any]: + """ + 导出日志为 TXT 文件,弹出保存对话框 + + Args: + log_text: 日志文本内容 + + Returns: + 包含保存状态和路径的字典 + """ + try: + # 生成默认文件名 + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + default_filename = f'egm_log_{timestamp}.txt' + + # 打开保存文件对话框 + result = self.window.create_file_dialog( + webview.SAVE_DIALOG, + directory='', + save_filename=default_filename, + file_types=('Text Files (*.txt)', 'All files (*.*)') + ) + + if result and len(result) > 0: + file_path = result[0] + + # 写入文件 + with open(file_path, 'w', encoding='utf-8') as f: + f.write(log_text) + + return { + "success": True, + "message": f"日志已保存到: {file_path}", + "file_path": file_path + } + else: + return { + "success": False, + "message": "用户取消了保存操作" + } + except Exception as e: + logger.error(f"导出日志失败: {str(e)}") + return { + "success": False, + "message": f"保存失败: {str(e)}" + } + def get_default_config(self) -> Dict[str, Any]: """ 获取默认配置