diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..8ae99a3 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(cargo check *)" + ] + } +} diff --git a/app-icon.png b/app-icon.png new file mode 100644 index 0000000..022c255 Binary files /dev/null and b/app-icon.png differ diff --git a/frontend/index.html b/frontend/index.html index 55d6a2f..518fcf3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -27,35 +27,93 @@ } @keyframes spin { to { transform: rotate(360deg); } } p { color: #4a5568; font-size: 14px; } + .status { color: #718096; font-size: 13px; margin-top: 12px; } + .retry-btn { + display: none; + margin-top: 16px; + padding: 8px 24px; + background: #2563eb; + color: #fff; + border: none; + border-radius: 6px; + font-size: 14px; + cursor: pointer; + } + .retry-btn:hover { background: #1d4ed8; } + .retry-btn.show { display: inline-block; } diff --git a/geo_data.db b/geo_data.db index 330ad10..0ed494d 100644 Binary files a/geo_data.db and b/geo_data.db differ diff --git a/geo_tool.py b/geo_tool.py index a08dafb..dea13a4 100644 --- a/geo_tool.py +++ b/geo_tool.py @@ -128,7 +128,6 @@ def save_cfg_to_file(cfg: dict) -> None: - API Keys + 品牌信息 → .streamlit/secrets.toml """ import tomllib - import tomli_w # type: ignore # ── 1. 非敏感配置 → config.json ── config_path = Path(__file__).with_name("config.json") @@ -203,6 +202,7 @@ def save_cfg_to_file(cfg: dict) -> None: new_app_config["temperature"] = cfg["temperature"] # 写出 TOML + import tomli_w secrets_path.parent.mkdir(parents=True, exist_ok=True) with secrets_path.open("wb") as f: content = {"api_keys": new_api_keys, "app_config": new_app_config} diff --git a/geo_tool_app.spec b/geo_tool_app.spec index 7cc84d4..69a37c1 100644 --- a/geo_tool_app.spec +++ b/geo_tool_app.spec @@ -21,7 +21,6 @@ datas = [ ("config.json", "."), ("geo_data.db", "."), (".streamlit", ".streamlit"), - ("knowledge_base", "knowledge_base"), ("modules", "modules"), ("platform_sync", "platform_sync"), ] @@ -150,9 +149,10 @@ hiddenimports = [ "watchdog", "sqlite3", "importlib.metadata", - "json", - "csv", - "hashlib", + "tomli_w", + # ---- Streamlit 内部依赖(动态子模块多,显式声明)---- + "altair", + "pydeck", ] # ── 排除不必要的包(减小体积) ────────────────────────────────── diff --git a/pyproject.toml b/pyproject.toml index d7fa392..b7ff645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ requires = ["setuptools"] build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] -include = ["build_scripts*"] +include = ["scripts*"] [dependency-groups] dev = [ @@ -35,6 +35,6 @@ dev = [ package = true [project.scripts] -python-build = "build_scripts.build:main" -tauri-run = "build_scripts.runtauri:main" -tauri-build = "build_scripts.build:build_tauri" +python-build = "scripts.build:main" +tauri-run = "scripts.runtauri:main" +tauri-build = "scripts.build:build_tauri" diff --git a/build_scripts/__init__.py b/scripts/__init__.py similarity index 100% rename from build_scripts/__init__.py rename to scripts/__init__.py diff --git a/build_scripts/build.py b/scripts/build.py similarity index 100% rename from build_scripts/build.py rename to scripts/build.py diff --git a/build_scripts/runtauri.py b/scripts/runtauri.py similarity index 100% rename from build_scripts/runtauri.py rename to scripts/runtauri.py diff --git a/scripts/tauri.py b/scripts/tauri.py new file mode 100644 index 0000000..e69de29 diff --git a/src-tauri/.claude/settings.local.json b/src-tauri/.claude/settings.local.json new file mode 100644 index 0000000..32c0567 --- /dev/null +++ b/src-tauri/.claude/settings.local.json @@ -0,0 +1,19 @@ +{ + "permissions": { + "allow": [ + "Bash(cargo clean *)", + "Bash(cargo build *)", + "Bash(awk '{print $NF}')", + "Bash(xxd \"D:/MyCode/ChouJuGEO/build_scripts/tauri.py\")", + "Bash(cargo tauri *)", + "Bash(cargo install *)", + "Read(//c/Users/Administrator/AppData/Local/tauri/WixTools314/**)", + "Bash(powershell *)", + "Bash(dism /online /get-featureinfo /featurename:NetFx3)", + "Bash(reg query *)", + "Bash(cmd *)", + "Bash(\"C:/Users/Administrator/AppData/Local/tauri/WixTools314/light.exe\")", + "Bash(C:/Users/Administrator/AppData/Local/tauri/WixTools314/light.exe *)" + ] + } +} diff --git a/src-tauri/app-icon.png b/src-tauri/app-icon.png new file mode 100644 index 0000000..022c255 Binary files /dev/null and b/src-tauri/app-icon.png differ diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 77e7d23..0fb20e8 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index 0f7976f..7c14645 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index 98fda06..25cdef5 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000..a86715a Binary files /dev/null and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index f35d84f..a8090da 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index 1823bb2..de133ce 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index dc2b22c..d0e6c04 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index 0ed3984..05b4608 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 60bf0ea..206dd71 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index c8ca0ad..bc34db0 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index 8756459..b43e5db 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 2c8023c..d93aa6e 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index 2c5e603..8bd6b7d 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index 17d142c..df5f004 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..171ac3a Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8fedb4d Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..d3a2255 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..4c2dcfa Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..d8c4058 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..782d895 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..8e5706c Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ee55f11 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..c708a23 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..82c4552 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6de1146 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..6473a22 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b4c2e1b Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3854d46 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9a0cf7d Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index a2993ad..c4cf1e6 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index 06c23c8..cfa0086 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index d1756ce..fdd7d15 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..d403cb7 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..f4821c3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..f4821c3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..9d719da Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..d65429b Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..f6200d3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..f6200d3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..895acec Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..f4821c3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..0ec711f Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..0ec711f Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..d40034e Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..2ec92ea Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..d40034e Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..f01f328 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..e19d72b Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..d9ab4d8 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..a21b6b9 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f80c833..644f3fe 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2,7 +2,30 @@ use std::process::{Child, Command, Stdio}; use std::sync::Mutex; use tauri::Manager; -struct StreamlitProcess(Mutex>); +/// 包装 Child,在 Drop 时强制终止整个进程树(防止 panic/crash 时泄漏) +struct ProcessGuard { + child: Option, +} + +impl ProcessGuard { + fn new(child: Child) -> Self { + Self { child: Some(child) } + } + + fn take(&mut self) -> Option { + self.child.take() + } +} + +impl Drop for ProcessGuard { + fn drop(&mut self) { + if let Some(ref mut child) = self.child.take() { + kill_process_tree(child); + } + } +} + +struct StreamlitProcess(Mutex>); /// 等待指定端口可连接,最多等待 timeout_secs 秒 fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool { @@ -16,31 +39,135 @@ fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool { false } -/// 停止 Streamlit 子进程 +/// 终止整个进程树(跨平台) /// -/// - Unix: 先 SIGTERM(优雅退出),等待 3 秒无响应后 SIGKILL -/// - Windows: 直接 TerminateProcess -fn kill_streamlit(child: &mut Child) { - #[cfg(unix)] +/// - Windows: taskkill /F /T → 终止目标进程及其所有子进程 +/// - Unix: SIGTERM → 等待 3s → SIGKILL +fn kill_process_tree(child: &mut Child) { + #[cfg(windows)] { - // 先发 SIGTERM 请求优雅退出 - let _ = Command::new("kill") - .arg("-TERM") - .arg(child.id().to_string()) - .spawn(); - std::thread::sleep(std::time::Duration::from_secs(3)); - - // 尝试 wait(如果已退出则返回 Ok(Some(status))) - if let Ok(Some(_)) = child.try_wait() { - log::info!("Streamlit 进程已优雅退出"); - return; + let pid = child.id(); + // taskkill /T 递归终止整个进程树,确保 Streamlit worker 等孙进程也被清理 + match Command::new("taskkill") + .args(["/F", "/T", "/PID", &pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + { + Ok(mut tk) => { + let _ = tk.wait(); + log::info!("[清理] taskkill /T /PID {} 完成", pid); + } + Err(e) => { + log::warn!("[清理] taskkill 调用失败: {},回退到直接 kill", e); + let _ = child.kill(); + } } - log::warn!("Streamlit 进程未响应 SIGTERM,强制终止..."); + let _ = child.wait(); + log::info!("[清理] Streamlit 进程树已终止 (Windows, PID: {})", pid); } - // 强制终止 - let _ = child.kill(); - let _ = child.wait(); + #[cfg(unix)] + { + let pid = child.id(); + unsafe { + extern "C" { + fn kill(pid: i32, sig: i32) -> i32; + } + kill(pid as i32, 15); // SIGTERM + } + std::thread::sleep(std::time::Duration::from_secs(3)); + + if let Ok(Some(_)) = child.try_wait() { + log::info!("[清理] Streamlit 进程已优雅退出 (SIGTERM, PID: {})", pid); + return; + } + log::warn!("[清理] Streamlit 进程未响应 SIGTERM,强制终止..."); + + let _ = child.kill(); + let _ = child.wait(); + log::info!("[清理] Streamlit 进程已强制终止 (Unix, PID: {})", pid); + } + + #[cfg(not(any(windows, unix)))] + { + let _ = child.kill(); + let _ = child.wait(); + } +} + +/// 从 StreamlitProcess 状态中取出进程并终止 +fn cleanup_streamlit(state: &StreamlitProcess) { + if let Ok(mut guard) = state.0.lock() { + if let Some(ref mut pg) = *guard { + if let Some(ref mut child) = pg.take() { + kill_process_tree(child); + } + } + // 清除空壳,避免 Drop 时二次处理 + *guard = None; + } +} + +/// 在 debug 模式下,尝试多种方式启动 Streamlit +fn spawn_streamlit_debug(project_root: &std::path::Path) -> Result { + let script_path = project_root.join("geo_tool.py"); + let script = script_path.to_str().unwrap_or("geo_tool.py"); + + let args = [ + "run", + script, + "--server.port", + "8501", + "--server.address", + "127.0.0.1", + "--server.headless", + "true", + "--browser.gatherUsageStats", + "false", + "--logger.level", + "error", + ]; + + log::info!("[开发模式] 启动 Streamlit: {:?}", script_path); + + let result = Command::new("streamlit") + .args(&args) + .current_dir(project_root) + .stdout(Stdio::null()) + .stderr(Stdio::inherit()) + .spawn(); + + if let Ok(child) = result { + log::info!("[开发模式] 通过 'streamlit' 启动成功 (PID: {})", child.id()); + return Ok(child); + } + log::warn!("[开发模式] 'streamlit' 命令未找到,尝试 python -m streamlit ..."); + + Command::new("python") + .arg("-m") + .arg("streamlit") + .args(&args) + .current_dir(project_root) + .stdout(Stdio::null()) + .stderr(Stdio::inherit()) + .spawn() + .map(|child| { + log::info!("[开发模式] 通过 'python -m streamlit' 启动成功 (PID: {})", child.id()); + child + }) + .map_err(|e| { + let msg = format!( + "无法启动 Streamlit。已尝试:\n \ + 1) streamlit run ...\n \ + 2) python -m streamlit run ...\n \ + 请确认已安装 streamlit: pip install streamlit\n \ + 最终错误: {}", + e + ); + log::error!("[开发模式] {}", msg); + msg + }) } #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -55,39 +182,12 @@ pub fn run() { )?; } - let child = if cfg!(debug_assertions) { - // ── 开发模式 ── 使用系统安装的 streamlit ── + let child_result = if cfg!(debug_assertions) { let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); let project_root = manifest_dir.parent().unwrap_or(&manifest_dir); - let script_path = project_root.join("geo_tool.py"); - - log::info!( - "[开发模式] 启动 Streamlit: {:?}", - script_path - ); - - Command::new("streamlit") - .args([ - "run", - script_path.to_str().unwrap_or("geo_tool.py"), - "--server.port", - "8501", - "--server.address", - "127.0.0.1", - "--server.headless", - "true", - "--browser.gatherUsageStats", - "false", - "--logger.level", - "error", - ]) - .current_dir(project_root) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() + spawn_streamlit_debug(project_root) } else { - // ── 生产模式 ── 使用 PyInstaller 打包的独立可执行文件 ── let resource_dir = app .path() .resource_dir() @@ -98,12 +198,26 @@ pub fn run() { } else { "geo_tool_app" }; - let exe_path = resource_dir.join("_up_").join("dist_python").join(exe_name); - log::info!( - "[生产模式] 启动打包应用: {:?}", - exe_path - ); + let exe_path_legacy = + resource_dir.join("_up_").join("dist_python").join(exe_name); + let exe_path_direct = resource_dir.join("dist_python").join(exe_name); + + let exe_path = if exe_path_legacy.exists() { + exe_path_legacy + } else if exe_path_direct.exists() { + exe_path_direct + } else { + let msg = format!( + "未找到 Streamlit 打包应用:\n 尝试1: {:?}\n 尝试2: {:?}", + exe_path_legacy, exe_path_direct + ); + log::error!("[生产模式] {}", msg); + eprintln!("错误:{}", msg); + return Ok(()); + }; + + log::info!("[生产模式] 启动打包应用: {:?}", exe_path); let mut cmd = Command::new(&exe_path); cmd.current_dir(&resource_dir) @@ -111,7 +225,6 @@ pub fn run() { .stdout(Stdio::null()) .stderr(Stdio::null()); - // Windows: 不显示控制台窗口 #[cfg(target_os = "windows")] { use std::os::windows::process::CommandExt; @@ -119,26 +232,25 @@ pub fn run() { cmd.creation_flags(CREATE_NO_WINDOW); } - cmd.spawn() + cmd.spawn().map_err(|e| { + format!("无法启动打包应用 {:?}: {}", exe_path, e) + }) }; - match child { + match child_result { Ok(child) => { log::info!("Streamlit 进程已启动 (PID: {})", child.id()); - app.manage(StreamlitProcess(Mutex::new(Some(child)))); + let guard = ProcessGuard::new(child); + app.manage(StreamlitProcess(Mutex::new(Some(guard)))); } Err(e) => { log::error!("启动 Streamlit 失败: {}", e); - eprintln!( - "错误:无法启动 Streamlit 服务。请确保已安装 streamlit。\n {}", - e - ); + eprintln!("错误:{}", e); } } - // 等待 Streamlit 就绪(最多 15 秒) if !wait_for_port("127.0.0.1", 8501, 15) { - log::warn!("Streamlit 在 15 秒内未能就绪"); + log::warn!("Streamlit 在 15 秒内未能就绪,请检查是否已安装 streamlit"); } else { log::info!("Streamlit 已就绪 (127.0.0.1:8501)"); } @@ -149,19 +261,22 @@ pub fn run() { .expect("error while building tauri application"); app.run(|app_handle, event| { - // 窗口关闭 → 停止子进程 → 退出应用 - if let tauri::RunEvent::ExitRequested { .. } = event { - if let Some(state) = app_handle.try_state::() { - if let Ok(mut guard) = state.0.lock() { - if let Some(ref mut child) = *guard { - log::info!("正在停止 Streamlit 进程..."); - kill_streamlit(child); - log::info!("Streamlit 进程已停止"); + match event { + tauri::RunEvent::WindowEvent { event, .. } => { + if let tauri::WindowEvent::CloseRequested { .. } = event { + log::info!("窗口关闭请求,正在清理子进程..."); + if let Some(state) = app_handle.try_state::() { + cleanup_streamlit(&*state); } } } - // 确保应用进程完全退出 - std::process::exit(0); + tauri::RunEvent::Exit => { + log::info!("应用退出,执行最终清理..."); + if let Some(state) = app_handle.try_state::() { + cleanup_streamlit(&*state); + } + } + _ => {} } }); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3b5c574..28e4346 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -4,8 +4,7 @@ "version": "0.1.0", "identifier": "com.pinesound.geo-tool", "build": { - "frontendDist": "../frontend", - "devUrl": "http://localhost:8501" + "frontendDist": "../frontend" }, "app": { "windows": [ @@ -35,7 +34,12 @@ "icons/icon.ico" ], "resources": [ - "../dist_python/geo_tool_app" - ] + "../dist_python/geo_tool_app.exe" + ], + "windows": { + "wix": { + "language": "zh-CN" + } + } } }