feat: 添加安装更新功能,支持下载并安装更新包;优化双屏控制逻辑,增强错误处理

This commit is contained in:
Pine
2026-08-19 13:40:00 +08:00
parent c9460c8d6e
commit fd6493f582
4 changed files with 132 additions and 68 deletions
+55 -22
View File
@@ -24,29 +24,36 @@ fn set_dual_screen(app: AppHandle, on: bool) -> Result<String, String> {
let _ = w.set_focus();
return Ok("on".into());
}
let mut builder = WebviewWindowBuilder::new(&app, "secondary", WebviewUrl::default())
.title("OPC 运营中心 · 双屏");
// 定位到第二块屏(非主屏;无第二屏则用第一块)
if let Ok(monitors) = app.available_monitors() {
eprintln!("[dual] available_monitors={}", monitors.len());
let primary_pos = app
.primary_monitor()
.ok()
.flatten()
.map(|pm| { let p = pm.position(); (p.x, p.y) });
eprintln!("[dual] primary_pos={:?}", primary_pos);
let target = monitors
.iter()
.find(|m| { let p = m.position(); Some((p.x, p.y)) != primary_pos })
.or_else(|| monitors.first());
if let Some(mon) = target {
let p = mon.position();
let s = mon.size();
eprintln!("[dual] target {}x{} @ {},{}", s.width, s.height, p.x, p.y);
builder = builder.position(p.x as f64, p.y as f64).inner_size(s.width as f64, s.height as f64);
// 主屏位置(用于排除主屏;必须把副窗放到主屏之外)
let primary_pos = app
.primary_monitor()
.ok()
.flatten()
.map(|pm| { let p = pm.position(); (p.x, p.y) });
// 获取全部显示器,寻找一块与主屏位置不同的副屏
let monitors = app.available_monitors().map_err(|e| format!("读取显示器失败: {e}"))?;
eprintln!("[dual] monitors={} primary_pos={:?}", monitors.len(), primary_pos);
let mut target: Option<(i32, i32, u32, u32)> = None; // (x, y, w, h)
for m in &monitors {
let p = m.position();
if Some((p.x, p.y)) != primary_pos {
let s = m.size();
target = Some((p.x, p.y, s.width, s.height));
break;
}
}
let win = builder.build().map_err(|e| e.to_string())?;
// 绝对不允许在主屏桌面上开启;没有副屏则拒绝
let (x, y, w, h) = match target {
Some(t) => t,
None => return Err("未检测到副屏(需连接第二块显示器),无法开启双屏".into()),
};
eprintln!("[dual] secondary {}x{} @ {},{}", w, h, x, y);
let win = WebviewWindowBuilder::new(&app, "secondary", WebviewUrl::default())
.title("OPC 运营中心 · 双屏")
.position(x as f64, y as f64)
.inner_size(w as f64, h as f64)
.build()
.map_err(|e| format!("创建副窗失败: {e}"))?;
// 等窗口内容就绪后再全屏(避免过早全屏导致白屏/布局异常)
let win2 = win.clone();
std::thread::spawn(move || {
@@ -57,6 +64,28 @@ fn set_dual_screen(app: AppHandle, on: bool) -> Result<String, String> {
Ok("on".into())
}
/// 安装更新:把前端已下载的安装包字节写入磁盘 → 启动安装程序 → 退出应用。
/// bytes 由前端 fetch /download 得到;启动安装程序后进程独立,应用退出不影响它。
#[tauri::command]
fn install_update(app: AppHandle, bytes: Vec<u8>, filename: String) -> Result<String, String> {
if bytes.len() < 1000 {
return Err("下载内容无效或为空".into());
}
let dest = std::env::temp_dir().join(filename);
std::fs::write(&dest, &bytes).map_err(|e| format!("写入安装包失败: {e}"))?;
// 启动安装程序(Windows 下 Command::spawn 为独立进程,应用退出后仍在运行)
std::process::Command::new(&dest)
.spawn()
.map_err(|e| format!("启动安装程序失败: {e}"))?;
// 稍后退出应用,让安装程序接管
let app2 = app.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(800));
app2.exit(0);
});
Ok(dest.to_string_lossy().into_owned())
}
/// 读取运行时配置(部署用,免重新打包):
/// 1) exe 同目录 config.json / dpm.config.json(最高优先)
/// 2) 应用配置目录 config.json
@@ -98,7 +127,11 @@ pub fn run() {
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
Some(vec![]),
))
.invoke_handler(tauri::generate_handler![read_runtime_config, set_dual_screen])
.invoke_handler(tauri::generate_handler![
read_runtime_config,
set_dual_screen,
install_update
])
.on_window_event(|window, event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
// 仅主窗口关闭=最小化(进程常驻);双屏副窗口可正常关闭