feat(): 重构界面
This commit is contained in:
+99
-23
@@ -1,23 +1,99 @@
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
env_logger::init();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_autostart::init(
|
||||
MacosLauncher::LaunchAgent,
|
||||
Some(vec![]),
|
||||
))
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
let _ = window.hide();
|
||||
}
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpListener;
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
|
||||
use tauri::{Emitter, Manager};
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
|
||||
mod notification;
|
||||
|
||||
const APP_NAME: &str = "工单系统监测";
|
||||
static CALLBACK_PORT: AtomicU16 = AtomicU16::new(0);
|
||||
|
||||
/// 启动本地 HTTP 服务器,用于接收 Toast 通知按钮的回调
|
||||
fn start_notification_callback_server(app_handle: tauri::AppHandle) {
|
||||
let listener =
|
||||
TcpListener::bind("127.0.0.1:0").expect("failed to bind notification callback server");
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
CALLBACK_PORT.store(port, Ordering::SeqCst);
|
||||
log::info!("Notification callback server on port {}", port);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for stream in listener.incoming().flatten() {
|
||||
handle_callback_request(&app_handle, stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_callback_request(app_handle: &tauri::AppHandle, mut stream: std::net::TcpStream) {
|
||||
let mut buf = [0u8; 1024];
|
||||
let n = stream.read(&mut buf).unwrap_or(0);
|
||||
let request = String::from_utf8_lossy(&buf[..n]);
|
||||
|
||||
let (action, html) = if request.contains("GET /postpone") {
|
||||
let _ = app_handle.emit("notification-action", "postpone_1h");
|
||||
if let Some(w) = app_handle.get_webview_window("main") {
|
||||
let _ = w.unminimize();
|
||||
let _ = w.show();
|
||||
let _ = w.set_focus();
|
||||
}
|
||||
("postpone", "已推迟1小时,监控将在1小时后恢复")
|
||||
} else {
|
||||
("unknown", "未知操作")
|
||||
};
|
||||
|
||||
log::info!("Notification callback: {}", action);
|
||||
|
||||
let body = format!(
|
||||
"<!DOCTYPE html><html><head><meta charset='utf-8'></head>\
|
||||
<body style='display:flex;justify-content:center;align-items:center;\
|
||||
height:80vh;font-family:system-ui;color:#333'>\
|
||||
<div style='text-align:center'><h2>{}</h2>\
|
||||
<p style='color:#888'>此标签页可以关闭</p></div></body>\
|
||||
<script>setTimeout(()=>window.close(),1500)</script></html>",
|
||||
html
|
||||
);
|
||||
|
||||
let response = format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||
body.len(),
|
||||
body
|
||||
);
|
||||
let _ = stream.write_all(response.as_bytes());
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn send_clickable_notification(title: String, body: String) -> Result<(), String> {
|
||||
let port = CALLBACK_PORT.load(Ordering::SeqCst);
|
||||
if port == 0 {
|
||||
return Err("Notification callback server not ready".into());
|
||||
}
|
||||
notification::send_interactive_toast(&title, &body, port, APP_NAME)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
env_logger::init();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_autostart::init(
|
||||
MacosLauncher::LaunchAgent,
|
||||
Some(vec![]),
|
||||
))
|
||||
.setup(|app| {
|
||||
start_notification_callback_server(app.handle().clone());
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![send_clickable_notification])
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
let _ = window.emit("close-requested", ());
|
||||
}
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/// Windows Toast 通知模块
|
||||
/// 通过本地 HTTP 端点桥接按钮点击事件,所有按钮均使用 protocol 激活(兼容非打包应用)
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows::Data::Xml::Dom::XmlDocument;
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows::UI::Notifications::{ToastNotification, ToastNotificationManager};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn xml_escape(s: &str) -> String {
|
||||
s.replace('&', "&")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
.replace('"', """)
|
||||
.replace('\'', "'")
|
||||
}
|
||||
|
||||
/// 发送带交互按钮的 Toast 通知
|
||||
///
|
||||
/// - 点击消息体 → 关闭通知(非打包应用不支持 foreground 激活)
|
||||
/// - 点击"推迟1小时" → 通过 localhost 回调暂停监控并弹出应用窗口
|
||||
/// - 点击"去处理" → 直接打开 CRM 网站
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn send_interactive_toast(
|
||||
title: &str,
|
||||
body: &str,
|
||||
port: u16,
|
||||
app_name: &str,
|
||||
) -> Result<(), String> {
|
||||
let toast_xml = format!(
|
||||
r#"<toast activationType="protocol">
|
||||
<visual>
|
||||
<binding template="ToastGeneric">
|
||||
<text>{title}</text>
|
||||
<text>{body}</text>
|
||||
</binding>
|
||||
</visual>
|
||||
<actions>
|
||||
<action content="推迟1小时" arguments="http://127.0.0.1:{port}/postpone" activationType="protocol"/>
|
||||
<action content="去处理" arguments="https://crm.yunvip123.com/index.html" activationType="protocol"/>
|
||||
</actions>
|
||||
</toast>"#,
|
||||
port = port,
|
||||
title = xml_escape(title),
|
||||
body = xml_escape(body),
|
||||
);
|
||||
|
||||
let xml = XmlDocument::new().map_err(|e| e.to_string())?;
|
||||
xml.LoadXml(&toast_xml.into()).map_err(|e| e.to_string())?;
|
||||
|
||||
let toast =
|
||||
ToastNotification::CreateToastNotification(&xml).map_err(|e| e.to_string())?;
|
||||
|
||||
let notifier =
|
||||
ToastNotificationManager::CreateToastNotifierWithId(&app_name.into())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
notifier.Show(&toast).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn send_interactive_toast(
|
||||
_title: &str,
|
||||
_body: &str,
|
||||
_port: u16,
|
||||
_app_name: &str,
|
||||
) -> Result<(), String> {
|
||||
Err("Toast 通知仅支持 Windows 平台".to_string())
|
||||
}
|
||||
Reference in New Issue
Block a user