From 47d3b7b6b49cad874e09cca50b62fc264e1aacd2 Mon Sep 17 00:00:00 2001 From: dmy Date: Mon, 2 Mar 2026 22:18:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=86=E9=85=8D=E7=BD=AE=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=8A=9F=E8=83=BD=E4=BB=8EJSON=E6=94=B9=E4=B8=BATOML?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=B9=B6=E6=B7=BB=E5=8A=A0=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webui/src/components/ParameterForm.vue | 60 ++++++++++++++++--- webview_app.py | 80 ++++++++++++++++++++++++-- 2 files changed, 127 insertions(+), 13 deletions(-) diff --git a/webui/src/components/ParameterForm.vue b/webui/src/components/ParameterForm.vue index 3ea9a94..d145388 100644 --- a/webui/src/components/ParameterForm.vue +++ b/webui/src/components/ParameterForm.vue @@ -452,16 +452,57 @@ const resetParams = () => { error.value = null } +// 将参数转换为 TOML 格式 +const tomlStringify = (obj: any, indent: string = ''): string => { + let result = '' + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined) continue + + if (Array.isArray(value)) { + result += `${indent}${key} = [${value.join(', ')}]\n` + } else if (typeof value === 'object') { + result += `\n${indent}[${key}]\n` + result += tomlStringify(value, indent) + } else if (typeof value === 'string') { + result += `${indent}${key} = "${value}"\n` + } else if (typeof value === 'boolean') { + result += `${indent}${key} = ${value}\n` + } else { + result += `${indent}${key} = ${value}\n` + } + } + + return result +} + // 导出配置 -const exportConfig = () => { - const config = JSON.stringify(params, null, 2) - const blob = new Blob([config], { type: 'application/json' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = 'egm_config.json' - a.click() - URL.revokeObjectURL(url) +const exportConfig = async () => { + try { + if (window.pywebview) { + const response = await window.pywebview.api.export_config(params) + if (response.success) { + logRef.value?.addLog('info', response.message) + } else { + logRef.value?.addLog('warning', response.message) + } + } else { + // 开发模式下的模拟 + logRef.value?.addLog('info', '导出配置(开发模式,直接下载)') + const config = tomlStringify(params) + const blob = new Blob([config], { 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_config_${timestamp}.toml` + a.click() + URL.revokeObjectURL(url) + } + } catch (e: any) { + error.value = e.message || '导出失败' + logRef.value?.addLog('error', e.message || '导出失败') + } } // 声明 pywebview API 类型 @@ -470,6 +511,7 @@ declare global { pywebview?: { api: { calculate: (params: AllParameters) => Promise + export_config: (params: AllParameters) => Promise } } } diff --git a/webview_app.py b/webview_app.py index 165ad74..cfc092c 100644 --- a/webview_app.py +++ b/webview_app.py @@ -340,17 +340,86 @@ class EGMWebApp: } } - def export_config(self, params: Dict[str, Any]) -> str: + def dict_to_toml(self, obj: Dict[str, Any], indent: str = '') -> str: """ - 导出配置为 JSON 字符串 + 将字典转换为 TOML 格式字符串 + + Args: + obj: 参数字典 + indent: 缩进字符串 + + Returns: + TOML 格式字符串 + """ + result = '' + + for key, value in obj.items(): + if value is None: + continue + + if isinstance(value, list): + result += f'{indent}{key} = [{", ".join(str(v) for v in value)}]\n' + elif isinstance(value, dict): + result += f'\n{indent}[{key}]\n' + result += self.dict_to_toml(value, indent) + elif isinstance(value, str): + result += f'{indent}{key} = "{value}"\n' + elif isinstance(value, bool): + result += f'{indent}{key} = {str(value).lower()}\n' + else: + result += f'{indent}{key} = {value}\n' + + return result + + def export_config(self, params: Dict[str, Any]) -> Dict[str, Any]: + """ + 导出配置为 TOML 文件,弹出保存对话框 Args: params: 参数字典 Returns: - JSON 字符串 + 包含保存状态和路径的字典 """ - return json.dumps(params, indent=2, ensure_ascii=False) + try: + # 生成默认文件名 + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + default_filename = f'egm_config_{timestamp}.toml' + + # 打开保存文件对话框 + result = self.window.create_file_dialog( + webview.SAVE_DIALOG, + directory='', + save_filename=default_filename, + file_types=('TOML Files (*.toml)', 'All files (*.*)') + ) + + if result and len(result) > 0: + file_path = result[0] + + # 转换为 TOML 格式 + toml_content = self.dict_to_toml(params) + + # 写入文件 + with open(file_path, 'w', encoding='utf-8') as f: + f.write(toml_content) + + 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]: """ @@ -421,6 +490,9 @@ def start_webview(): resizable=True, min_size=(800, 600) ) + + # 将窗口对象传递给 API + api.window = window # 启动 logger.info("启动 EGM Web 界面...")