新增: 关窗托盘化一次性说明

- 首次关窗最小化到托盘时系统通知说明,标记文件防重发
This commit is contained in:
lxy
2026-10-03 16:27:37 +08:00
parent ed678262b6
commit 8710a09be2
+28
View File
@@ -9,6 +9,8 @@ mod local_proxy_gateway;
mod tray;
use tauri::{Emitter, Listener, Manager};
// UX-P2 走查⑥:首次关窗托盘化提示走 OS 系统通知(应用内气泡窗口即隐看不见),需 NotificationExt
use tauri_plugin_notification::NotificationExt;
use state::AppState;
@@ -31,6 +33,11 @@ use df_tunnel::TunnelClient;
/// 关窗进行中提示哨兵:tray-hide-notice 每进程只发一次(反复关窗不重复弹气泡)。
static CLOSE_NOTICE_SENT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
/// 首次关窗托盘化说明的持久化标记文件名(app_data_dir 下;存在 = 历史上已提示过,不再弹)。
/// 用文件而非进程哨兵:解释一次即够,重启后不必再打扰;写入与通知成败解耦(先占坑语义),
/// 防通知通道异常(如无系统通知权限)导致每次关窗都尝试弹。
const TRAY_NOTICE_SENT_MARKER: &str = "tray-hide-notice-sent";
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// 初始化文件日志:追加到 %TEMP%/devflow-trace.log,RUST_LOG 控制级别(默认 info)
@@ -521,6 +528,9 @@ pub fn run() {
// 真正关闭,否则 close 被吞成隐藏僵尸窗,浮窗「正在关闭…」卡死、新审批推给不可见窗口。
// P1 bg 化:有 AI 会话运行中时,首次关窗 emit tray-hide-notice 提示后台仍在跑(每进程一次),
// 异步等 2 秒再 hide(同步 sleep 卡主线程渲染,气泡画不出来);无运行会话保持立即 hide 零回归。
// UX-P2 走查⑥:无会话关窗历史上零提示,用户误以为已退出。窗口随即隐藏,应用内气泡画了也
// 看不见,且前端气泡通道仅 running>0 才渲染,故走 OS 系统通知(托盘旁 toast)补位,
// 以持久化标记全生命周期只解释一次(解释性提示,与上方每进程一次的会话提醒互不替代)。
.on_window_event(|window, event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
if window.label() != "main" {
@@ -530,6 +540,24 @@ pub fn run() {
let app = window.app_handle().clone();
let active = app.state::<AppState>().conv_states.active_convs().len();
if active == 0 {
// 首次(全历史一次)关窗托盘化:弹一次系统通知说明「已最小化到托盘」
match app.path().app_data_dir() {
Ok(dir) => {
let marker = dir.join(TRAY_NOTICE_SENT_MARKER);
if !marker.exists() {
let _ = app
.notification()
.builder()
.title("DevFlow")
.body("已最小化到系统托盘,程序仍在后台运行;托盘图标可随时唤起,托盘菜单「退出」才真正结束")
.show();
let _ = std::fs::write(&marker, b"1");
}
}
Err(e) => {
tracing::warn!("[tray] app_data_dir 解析失败,跳过首次关窗托盘提示: {}", e);
}
}
let _ = window.hide(); // 无运行会话:隐藏到托盘(原行为)
return;
}