(all):增加桌面程序

This commit is contained in:
Pine
2026-05-30 12:51:19 +08:00
parent 86abeeb5cc
commit 1790b496b3
39 changed files with 9167 additions and 185 deletions
+4
View File
@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/gen/schemas
+4941
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.5.3", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.9.5", features = [] }
tauri-plugin-log = "2"
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+167
View File
@@ -0,0 +1,167 @@
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use tauri::Manager;
struct StreamlitProcess(Mutex<Option<Child>>);
/// 等待指定端口可连接,最多等待 timeout_secs 秒
fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool {
let start = std::time::Instant::now();
while start.elapsed() < std::time::Duration::from_secs(timeout_secs) {
if std::net::TcpStream::connect((host, port)).is_ok() {
return true;
}
std::thread::sleep(std::time::Duration::from_millis(200));
}
false
}
/// 停止 Streamlit 子进程
///
/// - Unix: 先 SIGTERM(优雅退出),等待 3 秒无响应后 SIGKILL
/// - Windows: 直接 TerminateProcess
fn kill_streamlit(child: &mut Child) {
#[cfg(unix)]
{
// 先发 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;
}
log::warn!("Streamlit 进程未响应 SIGTERM,强制终止...");
}
// 强制终止
let _ = child.kill();
let _ = child.wait();
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let app = tauri::Builder::default()
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}
let child = if cfg!(debug_assertions) {
// ── 开发模式 ── 使用系统安装的 streamlit ──
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()
} else {
// ── 生产模式 ── 使用 PyInstaller 打包的独立可执行文件 ──
let resource_dir = app
.path()
.resource_dir()
.expect("无法获取资源目录");
let exe_name = if cfg!(target_os = "windows") {
"geo_tool_app.exe"
} else {
"geo_tool_app"
};
let exe_path = resource_dir.join("_up_").join("dist").join(exe_name);
log::info!(
"[生产模式] 启动打包应用: {:?}",
exe_path
);
let mut cmd = Command::new(&exe_path);
cmd.current_dir(&resource_dir)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
// Windows: 不显示控制台窗口
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000u32;
cmd.creation_flags(CREATE_NO_WINDOW);
}
cmd.spawn()
};
match child {
Ok(child) => {
log::info!("Streamlit 进程已启动 (PID: {})", child.id());
app.manage(StreamlitProcess(Mutex::new(Some(child))));
}
Err(e) => {
log::error!("启动 Streamlit 失败: {}", e);
eprintln!(
"错误:无法启动 Streamlit 服务。请确保已安装 streamlit。\n {}",
e
);
}
}
// 等待 Streamlit 就绪(最多 15 秒)
if !wait_for_port("127.0.0.1", 8501, 15) {
log::warn!("Streamlit 在 15 秒内未能就绪");
} else {
log::info!("Streamlit 已就绪 (127.0.0.1:8501)");
}
Ok(())
})
.build(tauri::generate_context!())
.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::<StreamlitProcess>() {
if let Ok(mut guard) = state.0.lock() {
if let Some(ref mut child) = *guard {
log::info!("正在停止 Streamlit 进程...");
kill_streamlit(child);
log::info!("Streamlit 进程已停止");
}
}
}
// 确保应用进程完全退出
std::process::exit(0);
}
});
}
+167
View File
@@ -0,0 +1,167 @@
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use tauri::Manager;
struct StreamlitProcess(Mutex<Option<Child>>);
/// 等待指定端口可连接,最多等待 timeout_secs 秒
fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool {
let start = std::time::Instant::now();
while start.elapsed() < std::time::Duration::from_secs(timeout_secs) {
if std::net::TcpStream::connect((host, port)).is_ok() {
return true;
}
std::thread::sleep(std::time::Duration::from_millis(200));
}
false
}
/// 停止 Streamlit 子进程
///
/// - Unix: 先 SIGTERM(优雅退出),等待 3 秒无响应后 SIGKILL
/// - Windows: 直接 TerminateProcess
fn kill_streamlit(child: &mut Child) {
#[cfg(unix)]
{
// 先发 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;
}
log::warn!("Streamlit 进程未响应 SIGTERM,强制终止...");
}
// 强制终止
let _ = child.kill();
let _ = child.wait();
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let app = tauri::Builder::default()
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}
let child = if cfg!(debug_assertions) {
// ── 开发模式 ── 使用系统安装的 streamlit ──
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()
} else {
// ── 生产模式 ── 使用 PyInstaller 打包的独立可执行文件 ──
let resource_dir = app
.path()
.resource_dir()
.expect("无法获取资源目录");
let exe_name = if cfg!(target_os = "windows") {
"geo_tool_app.exe"
} else {
"geo_tool_app"
};
let exe_path = resource_dir.join(exe_name);
log::info!(
"[生产模式] 启动打包应用: {:?}",
exe_path
);
let mut cmd = Command::new(&exe_path);
cmd.current_dir(&resource_dir)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
// Windows: 不显示控制台窗口
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000u32;
cmd.creation_flags(CREATE_NO_WINDOW);
}
cmd.spawn()
};
match child {
Ok(child) => {
log::info!("Streamlit 进程已启动 (PID: {})", child.id());
app.manage(StreamlitProcess(Mutex::new(Some(child))));
}
Err(e) => {
log::error!("启动 Streamlit 失败: {}", e);
eprintln!(
"错误:无法启动 Streamlit 服务。请确保已安装 streamlit。\n {}",
e
);
}
}
// 等待 Streamlit 就绪(最多 15 秒)
if !wait_for_port("127.0.0.1", 8501, 15) {
log::warn!("Streamlit 在 15 秒内未能就绪");
} else {
log::info!("Streamlit 已就绪 (127.0.0.1:8501)");
}
Ok(())
})
.build(tauri::generate_context!())
.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::<StreamlitProcess>() {
if let Ok(mut guard) = state.0.lock() {
if let Some(ref mut child) = *guard {
log::info!("正在停止 Streamlit 进程...");
kill_streamlit(child);
log::info!("Streamlit 进程已停止");
}
}
}
// 确保应用进程完全退出
std::process::exit(0);
}
});
}
+6
View File
@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
app_lib::run();
}
+41
View File
@@ -0,0 +1,41 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "\u4e11\u6a58GEO",
"version": "0.1.0",
"identifier": "com.pinesound.geo-tool",
"build": {
"frontendDist": "http://localhost:8501",
"devUrl": "http://localhost:8501"
},
"app": {
"windows": [
{
"title": "\u4e11\u6a58\u6587\u5316\u4f20\u5a92GEO\u5185\u5bb9\u4f18\u5316\u5e73\u53f0",
"width": 1400,
"height": 900,
"minWidth": 1000,
"minHeight": 700,
"resizable": true,
"fullscreen": false,
"center": true
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": [
"../dist_python/geo_tool_app"
]
}
}