impress_asr_input_rust/src/app/mod.rs
impressionyang 66f4b7e0c4
Some checks are pending
Build Windows GUI / build-windows (push) Waiting to run
Build Windows GUI / release (push) Blocked by required conditions
fix: 增强错误处理,确保异常情况下也能显示页面
后端变更:
- src/app/mod.rs: 窗口 hide() 使用 _ 忽略错误,避免 unwrap() panic

前端变更:
- web/src/components/ErrorBoundary.tsx: 新增错误边界组件,捕获未处理的错误
- web/src/main.tsx: 使用 ErrorBoundary 包裹 App 组件
- web/src/App.tsx: 优化主题加载和监听器的错误处理,确保失败时使用默认主题

错误处理能力:
- 即使 Tauri 命令失败,页面也能正常显示
- 未捕获错误会显示友好的错误页面,提供刷新按钮
- 主题加载失败时自动使用 system 主题降级
- 事件监听器清理更安全,避免异步问题

构建结果:
- Windows 包:dist/impress-asr-windows-x64-20260521_190528.zip
- 文件大小:5.0MB
2026-05-21 19:10:11 +08:00

369 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Tauri 应用模块
//!
//! 增强诊断日志版本 - 详细记录窗口创建、前端加载和托盘初始化过程
use anyhow::Result;
use tauri::{
menu::{Menu, MenuItem, Submenu},
tray::TrayIconBuilder,
Manager, Emitter,
};
use tracing::{info, warn};
pub mod commands;
pub mod state;
/// 应用状态
pub use state::AppState;
pub use state::AppTheme;
/// 运行 Tauri 应用
pub fn run() -> Result<()> {
eprintln!();
eprintln!(" [应用] 开始构建 Tauri 应用...");
info!("启动 Tauri 应用...");
// 检查构建上下文
eprintln!(" [检查] 验证 Tauri 上下文...");
let context = tauri::generate_context!();
eprintln!(" ✓ 上下文生成成功");
eprintln!(" - 应用标识:{}", context.config().identifier);
eprintln!(" - 应用名称:{:?}", context.config().product_name);
let app = tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.manage(AppState::default())
.invoke_handler(tauri::generate_handler![
commands::start_recording,
commands::stop_recording,
commands::recognize_audio,
commands::get_config_cmd,
commands::save_config,
commands::get_history,
commands::clear_history,
commands::get_theme,
commands::set_theme,
commands::select_model_file,
])
.setup(|app| {
info!("[设置] 初始化应用插件...");
info!(" - tauri_plugin_shell: 已加载");
info!(" - tauri_plugin_dialog: 已加载");
info!(" - tauri_plugin_fs: 已加载");
// 获取主窗口(由 tauri.conf.json 自动创建)
info!("[设置] 获取主窗口...");
if let Some(window) = app.get_webview_window("main") {
info!(" ✓ 主窗口已存在 (由配置文件创建)");
// 获取窗口位置和大小
if let Ok(position) = window.outer_position() {
info!(" - 窗口位置:({}, {})", position.x, position.y);
}
if let Ok(size) = window.outer_size() {
info!(" - 窗口大小:{}x{}", size.width, size.height);
}
// 确保窗口可见并获得焦点
info!(" - 显示窗口并聚焦...");
let _ = window.show();
let _ = window.set_focus();
info!(" ✓ 窗口已显示并聚焦");
// 再次检查状态
match window.is_visible() {
Ok(true) => info!(" ✓ 窗口确认可见"),
Ok(false) => info!(" ⚠ 窗口仍然隐藏"),
Err(e) => info!(" ⚠ 可见性检查失败:{}", e),
}
// 获取前端 URL
if let Ok(url) = window.url() {
info!(" - 前端 URL: {}", url);
}
} else {
info!(" ⚠ 主窗口未找到,手动创建...");
let window_result = tauri::WebviewWindowBuilder::new(
app,
"main",
tauri::WebviewUrl::App("index.html".into()),
)
.title("impress ASR Input")
.inner_size(1000.0, 700.0)
.min_inner_size(800.0, 600.0)
.center()
.visible(true)
.build();
match window_result {
Ok(_window) => {
info!(" ✓ 窗口手动创建成功");
}
Err(e) => {
info!(" ❌ 窗口创建失败:{}", e);
warn!("窗口创建失败:{}", e);
}
}
}
// 设置系统托盘
info!(" [设置] 配置系统托盘...");
match setup_tray(app) {
Ok(_) => info!(" ✓ 系统托盘设置完成"),
Err(e) => {
info!(" ⚠ 系统托盘设置失败:{}", e);
warn!("托盘设置失败:{}", e);
}
}
// 在 setup 结束时检查窗口状态
info!(" [设置] setup 结束前检查窗口...");
let all_windows = app.webview_windows();
info!(" - 当前窗口数量:{}", all_windows.len());
for (label, _) in all_windows.iter() {
info!(" - 窗口label='{}'", label);
if let Some(win) = app.get_webview_window(label) {
let visible = win.is_visible().unwrap_or(false);
let pos = win.outer_position().ok();
let size = win.outer_size().ok();
let url = win.url().ok();
info!(" - 可见性:{}", if visible { "可见" } else { "隐藏" });
if let Some(p) = pos {
info!(" - 位置:({}, {})", p.x, p.y);
}
if let Some(s) = size {
info!(" - 大小:{}x{}", s.width, s.height);
}
if let Some(u) = url {
info!(" - URL: {}", u);
}
}
}
info!("Tauri 应用设置完成");
Ok(())
})
.on_window_event(|window, event| {
// 处理窗口事件
match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
info!(" [窗口] 关闭请求 - 隐藏窗口到托盘");
// 隐藏窗口而不是关闭
let _ = window.hide();
api.prevent_close();
}
tauri::WindowEvent::Focused(focused) => {
if *focused {
info!(" [窗口] 获得焦点");
} else {
info!(" [窗口] 失去焦点");
}
}
_ => {}
}
})
.on_page_load(|_window, payload| {
info!("[页面加载] URL: {}", payload.url());
match payload.event() {
tauri::webview::PageLoadEvent::Started => {
info!(" - 页面开始加载");
}
tauri::webview::PageLoadEvent::Finished => {
info!(" - 页面加载完成 ✓");
}
}
})
.build(context)
.expect("构建 Tauri 应用失败");
eprintln!(" ✓ Tauri 应用构建成功");
info!("Tauri 应用启动成功");
// 立即检查窗口 - 在 run() 之前
info!("========================================");
info!("[窗口] build 后立即检查窗口...");
let windows = app.webview_windows();
info!(" - 窗口数量:{}", windows.len());
for (label, _) in windows.iter() {
info!(" - 窗口标签label='{}'", label);
}
// 尝试获取主窗口
if let Some(window) = app.get_webview_window("main") {
info!("[窗口] 主窗口信息:");
match window.is_visible() {
Ok(true) => info!(" - 可见性:可见 ✓"),
Ok(false) => info!(" - 可见性:隐藏 ⚠"),
Err(e) => info!(" - 可见性:检查失败 ({})", e),
}
match window.is_minimized() {
Ok(true) => info!(" - 最小化:是 ⚠"),
Ok(false) => info!(" - 最小化:否 ✓"),
Err(e) => info!(" - 最小化:检查失败 ({})", e),
}
info!(" - 焦点:可设置 ✓");
} else {
info!(" ⚠ [窗口] 主窗口 (label='main') 未找到!");
warn!("主窗口未找到");
// 尝试获取任意窗口
if let Some(first_window) = windows.keys().next() {
info!(" - 但找到其他窗口label='{}'", first_window);
if let Some(win) = app.get_webview_window(first_window) {
let _ = win.show();
let _ = win.set_focus();
info!(" ✓ 已显示该窗口");
}
}
}
eprintln!();
eprintln!(" [运行] 进入事件循环...");
info!("进入应用事件循环");
app.run(|app_handle, event| {
// 处理全局事件
match event {
tauri::RunEvent::Exit => {
info!(" [事件] 应用退出");
}
tauri::RunEvent::ExitRequested { api, .. } => {
// 检查是否允许完全退出
let state = app_handle.state::<AppState>();
if state.is_exit_allowed() {
info!(" [事件] 退出请求 - 允许完全退出");
} else {
info!(" [事件] 退出请求 - 阻止退出(隐藏窗口到托盘)");
api.prevent_exit();
}
}
tauri::RunEvent::Ready => {
info!(" [事件] 应用已就绪");
}
_ => {}
}
});
Ok(())
}
/// 设置系统托盘
fn setup_tray(app: &tauri::App) -> Result<()> {
info!(" [托盘] 创建菜单项...");
let show = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?;
info!(" - '显示窗口' 菜单项已创建");
let record = MenuItem::with_id(app, "record", "开始录音", true, None::<&str>)?;
info!(" - '开始录音' 菜单项已创建");
let settings = MenuItem::with_id(app, "settings", "设置", true, None::<&str>)?;
info!(" - '设置' 菜单项已创建");
// 主题子菜单
let theme_light = MenuItem::with_id(app, "theme_light", "浅色主题", true, None::<&str>)?;
let theme_dark = MenuItem::with_id(app, "theme_dark", "深色主题", true, None::<&str>)?;
let theme_system = MenuItem::with_id(app, "theme_system", "跟随系统", true, None::<&str>)?;
let theme_submenu = Submenu::with_items(app, "主题", true, &[&theme_light, &theme_dark, &theme_system])?;
info!(" - '主题' 子菜单已创建(浅色/深色/跟随系统)");
// 完全退出选项
let quit_now = MenuItem::with_id(app, "quit_now", "完全退出", true, None::<&str>)?;
info!(" - '完全退出' 菜单项已创建");
info!(" [托盘] 组合菜单...");
let menu = Menu::with_items(app, &[&show, &record, &settings, &theme_submenu, &quit_now])?;
info!(" ✓ 菜单创建成功 (5 项 + 主题子菜单)");
info!(" [托盘] 加载图标...");
let icon = app.default_window_icon().cloned();
match icon {
Some(_) => info!(" ✓ 窗口图标加载成功"),
None => {
info!(" ⚠ 窗口图标未找到,使用默认图标");
warn!("窗口图标未找到");
}
}
info!(" [托盘] 创建托盘图标...");
let _tray = TrayIconBuilder::new()
.icon(app.default_window_icon().cloned().unwrap_or_else(|| {
warn!("使用空图标");
// 创建一个 1x1 的透明像素作为默认图标
tauri::image::Image::new_owned(vec![0u8; 4], 1, 1)
}))
.menu(&menu)
.show_menu_on_left_click(false)
.on_menu_event(|app, event| {
info!("托盘菜单事件:{:?}", event.id);
match event.id.as_ref() {
"show" => {
info!(" [托盘] '显示窗口' 被点击");
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
info!(" ✓ 窗口已显示并聚焦");
}
}
"record" => {
info!(" [托盘] '开始录音' 被点击");
info!("从托盘启动录音");
}
"settings" => {
info!(" [托盘] '设置' 被点击");
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
info!(" ✓ 窗口已显示并聚焦");
}
}
"theme_light" => {
info!(" [托盘] '浅色主题' 被点击");
let state = app.state::<AppState>();
state.set_theme(AppTheme::Light);
// 通知前端切换主题
if let Some(window) = app.get_webview_window("main") {
let _ = window.emit("theme-change", "light");
}
}
"theme_dark" => {
info!(" [托盘] '深色主题' 被点击");
let state = app.state::<AppState>();
state.set_theme(AppTheme::Dark);
if let Some(window) = app.get_webview_window("main") {
let _ = window.emit("theme-change", "dark");
}
}
"theme_system" => {
info!(" [托盘] '跟随系统' 被点击");
let state = app.state::<AppState>();
state.set_theme(AppTheme::System);
if let Some(window) = app.get_webview_window("main") {
let _ = window.emit("theme-change", "system");
}
}
"quit_now" => {
info!(" [托盘] '完全退出' 被点击");
// 允许完全退出
let state = app.state::<AppState>();
state.allow_exit();
// 关闭窗口
if let Some(window) = app.get_webview_window("main") {
let _ = window.close();
}
// 退出应用
app.exit(0);
}
_ => {}
}
})
.build(app)?;
info!(" ✓ 托盘图标创建成功");
info!("系统托盘设置完成");
Ok(())
}