From ec36300d922d62575917da2f5cad71ce0ba7dc96 Mon Sep 17 00:00:00 2001 From: kdletters Date: Thu, 18 Jun 2026 19:42:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A1=8C=E9=9D=A2=E5=A3=B3=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 Tauri 应用菜单并复用显示、刷新和退出窗口动作 收紧桌面壳菜单模块和结构门禁 同步宿主壳方案文档和团队决策记录 --- apps/desktop-shell/scripts/check-config.mjs | 10 ++ apps/desktop-shell/src-tauri/src/main.rs | 2 + .../desktop-shell/src-tauri/src/shell/menu.rs | 136 ++++++++++++++++++ apps/desktop-shell/src-tauri/src/shell/mod.rs | 1 + .../desktop-shell/src-tauri/src/shell/tray.rs | 2 +- .../shared-memory/decision-log.md | 7 + ...ExpoReactNative与Tauri宿主壳方案-2026-06-17.md | 8 +- scripts/check-native-shells.mjs | 1 + 8 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 apps/desktop-shell/src-tauri/src/shell/menu.rs diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 4b7ba7d52..3c3d25bc0 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -1079,6 +1079,7 @@ const requiredRustHostModules = [ 'shell/events.rs', 'shell/file_drop.rs', 'shell/lifecycle.rs', + 'shell/menu.rs', 'shell/mod.rs', 'shell/navigation.rs', 'shell/network.rs', @@ -1104,6 +1105,15 @@ const requiredRustHostSnippets = [ 'DESKTOP_DEEP_LINK_HOSTS', 'resolve_desktop_single_instance_action', 'tauri_plugin_clipboard_manager::init()', + 'register_desktop_app_menu(app)?', + 'DESKTOP_APP_MENU_SHOW', + 'DESKTOP_APP_MENU_RELOAD', + 'DESKTOP_APP_MENU_QUIT', + 'resolve_desktop_app_menu_action', + 'app.set_menu', + 'app.on_menu_event', + 'Submenu::with_items', + 'PredefinedMenuItem::copy', 'TrayIconBuilder::with_id', 'register_desktop_tray(app)', 'DESKTOP_TRAY_ID', diff --git a/apps/desktop-shell/src-tauri/src/main.rs b/apps/desktop-shell/src-tauri/src/main.rs index 81490c134..6e6e6b5e7 100644 --- a/apps/desktop-shell/src-tauri/src/main.rs +++ b/apps/desktop-shell/src-tauri/src/main.rs @@ -3,6 +3,7 @@ mod shell; use host_bridge::{host_bridge_request, DesktopShareState, HostBridgeReplayState}; use shell::deep_link::{register_desktop_deep_link_events, register_desktop_deep_link_schemes}; +use shell::menu::register_desktop_app_menu; use shell::tray::{ register_desktop_tray, register_desktop_window_close_events, resolve_desktop_single_instance_action, show_main_window, DesktopSingleInstanceAction, @@ -35,6 +36,7 @@ fn main() { .plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_opener::init()) .setup(|app| { + register_desktop_app_menu(app)?; let tray_registered = match register_desktop_tray(app) { Ok(()) => true, Err(error) => { diff --git a/apps/desktop-shell/src-tauri/src/shell/menu.rs b/apps/desktop-shell/src-tauri/src/shell/menu.rs new file mode 100644 index 000000000..7334c403a --- /dev/null +++ b/apps/desktop-shell/src-tauri/src/shell/menu.rs @@ -0,0 +1,136 @@ +use crate::shell::tray::{reload_main_window, show_main_window}; +use tauri::menu::{Menu, MenuItem, PredefinedMenuItem, Submenu}; + +pub(crate) const DESKTOP_APP_MENU_SHOW: &str = "app-menu-show-main-window"; +pub(crate) const DESKTOP_APP_MENU_RELOAD: &str = "app-menu-reload-main-window"; +pub(crate) const DESKTOP_APP_MENU_QUIT: &str = "app-menu-quit-desktop-shell"; + +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum DesktopAppMenuAction { + ShowMainWindow, + ReloadMainWindow, + QuitApp, + Ignore, +} + +pub(crate) fn resolve_desktop_app_menu_action(menu_id: &str) -> DesktopAppMenuAction { + match menu_id { + DESKTOP_APP_MENU_SHOW => DesktopAppMenuAction::ShowMainWindow, + DESKTOP_APP_MENU_RELOAD => DesktopAppMenuAction::ReloadMainWindow, + DESKTOP_APP_MENU_QUIT => DesktopAppMenuAction::QuitApp, + _ => DesktopAppMenuAction::Ignore, + } +} + +fn handle_desktop_app_menu_action(app: &tauri::AppHandle, action: DesktopAppMenuAction) { + match action { + DesktopAppMenuAction::ShowMainWindow => { + let _ = show_main_window(app); + } + DesktopAppMenuAction::ReloadMainWindow => { + let _ = reload_main_window(app); + } + DesktopAppMenuAction::QuitApp => app.exit(0), + DesktopAppMenuAction::Ignore => {} + } +} + +pub(crate) fn register_desktop_app_menu(app: &tauri::App) -> tauri::Result<()> { + let show_item = + MenuItem::with_id(app, DESKTOP_APP_MENU_SHOW, "显示主窗口", true, None::<&str>)?; + let reload_item = MenuItem::with_id( + app, + DESKTOP_APP_MENU_RELOAD, + "刷新", + true, + Some("CmdOrCtrl+R"), + )?; + let quit_item = MenuItem::with_id( + app, + DESKTOP_APP_MENU_QUIT, + "退出", + true, + Some("CmdOrCtrl+Q"), + )?; + let app_separator = PredefinedMenuItem::separator(app)?; + let app_menu = Submenu::with_items( + app, + "Genarrative", + true, + &[&show_item, &reload_item, &app_separator, &quit_item], + )?; + + let undo_item = PredefinedMenuItem::undo(app, Some("撤销"))?; + let redo_item = PredefinedMenuItem::redo(app, Some("重做"))?; + let edit_separator = PredefinedMenuItem::separator(app)?; + let cut_item = PredefinedMenuItem::cut(app, Some("剪切"))?; + let copy_item = PredefinedMenuItem::copy(app, Some("复制"))?; + let paste_item = PredefinedMenuItem::paste(app, Some("粘贴"))?; + let select_all_item = PredefinedMenuItem::select_all(app, Some("全选"))?; + let edit_menu = Submenu::with_items( + app, + "编辑", + true, + &[ + &undo_item, + &redo_item, + &edit_separator, + &cut_item, + ©_item, + &paste_item, + &select_all_item, + ], + )?; + + let minimize_item = PredefinedMenuItem::minimize(app, Some("最小化"))?; + let maximize_item = PredefinedMenuItem::maximize(app, Some("最大化"))?; + let window_separator = PredefinedMenuItem::separator(app)?; + let close_item = PredefinedMenuItem::close_window(app, Some("关闭窗口"))?; + let window_menu = Submenu::with_items( + app, + "窗口", + true, + &[ + &minimize_item, + &maximize_item, + &window_separator, + &close_item, + ], + )?; + + app.set_menu(Menu::with_items( + app, + &[&app_menu, &edit_menu, &window_menu], + )?)?; + app.on_menu_event(|app, event| { + let menu_id = event.id().0.as_str(); + handle_desktop_app_menu_action(app, resolve_desktop_app_menu_action(menu_id)); + }); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn desktop_app_menu_ids_map_to_window_actions() { + assert_eq!( + resolve_desktop_app_menu_action(DESKTOP_APP_MENU_SHOW), + DesktopAppMenuAction::ShowMainWindow + ); + assert_eq!( + resolve_desktop_app_menu_action(DESKTOP_APP_MENU_RELOAD), + DesktopAppMenuAction::ReloadMainWindow + ); + assert_eq!( + resolve_desktop_app_menu_action(DESKTOP_APP_MENU_QUIT), + DesktopAppMenuAction::QuitApp + ); + assert_eq!( + resolve_desktop_app_menu_action("unknown"), + DesktopAppMenuAction::Ignore + ); + } +} diff --git a/apps/desktop-shell/src-tauri/src/shell/mod.rs b/apps/desktop-shell/src-tauri/src/shell/mod.rs index a7aaabda4..2a5dd8bea 100644 --- a/apps/desktop-shell/src-tauri/src/shell/mod.rs +++ b/apps/desktop-shell/src-tauri/src/shell/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod deep_link; mod events; mod file_drop; mod lifecycle; +pub(crate) mod menu; mod navigation; mod network; mod runtime; diff --git a/apps/desktop-shell/src-tauri/src/shell/tray.rs b/apps/desktop-shell/src-tauri/src/shell/tray.rs index 7eb7aadd7..1ccec2f4d 100644 --- a/apps/desktop-shell/src-tauri/src/shell/tray.rs +++ b/apps/desktop-shell/src-tauri/src/shell/tray.rs @@ -68,7 +68,7 @@ pub(crate) fn show_main_window(app: &tauri::AppHandle) -> tauri::Result<()> { Ok(()) } -fn reload_main_window(app: &tauri::AppHandle) -> tauri::Result<()> { +pub(crate) fn reload_main_window(app: &tauri::AppHandle) -> tauri::Result<()> { if let Some(window) = app.get_webview_window("main") { window.reload()?; } diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 666eef386..871c56ad8 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2499,3 +2499,10 @@ - 决策:桌面壳接入 `tauri-plugin-window-state`,并把插件配置收口到 `apps/desktop-shell/src-tauri/src/shell/window_state.rs`。只保存 `SIZE`、`POSITION` 和 `MAXIMIZED`,不保存 `VISIBLE`、`FULLSCREEN` 或 `DECORATIONS`;该能力属于宿主壳自身体验,不进入 HostBridge capability,不暴露窗口状态插件 command 给 H5。插件注册顺序固定为 single-instance 优先,其后才是 window-state、deep-link 和其它系统插件。 - 影响范围:`apps/desktop-shell/src-tauri/Cargo.toml`、`apps/desktop-shell/src-tauri/Cargo.lock`、`apps/desktop-shell/src-tauri/src/main.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。 - 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run desktop-shell:build -- --no-bundle`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。 + +## 2026-06-18 桌面壳应用菜单 + +- 背景:桌面壳方案要求 Tauri 承接系统菜单,但当前桌面壳只有系统托盘菜单和 HostBridge 受控能力;可分发桌面包缺少常规应用菜单会让刷新、退出、系统编辑和窗口操作只能依赖 WebView 或托盘。 +- 决策:新增 `apps/desktop-shell/src-tauri/src/shell/menu.rs` 注册 Tauri 应用菜单。应用菜单只复用宿主壳级显示主窗口、刷新主窗口和退出应用动作;编辑菜单和窗口菜单使用 Tauri 原生预定义项承接剪切、复制、粘贴、全选、最小化、最大化和关闭窗口。该能力不进入 HostBridge capability,不开放菜单 API、shell API 或任意窗口控制给 H5;菜单注册失败直接阻断启动,避免生产桌面壳缺少系统菜单仍静默运行。 +- 影响范围:`apps/desktop-shell/src-tauri/src/main.rs`、`apps/desktop-shell/src-tauri/src/shell/menu.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档。 +- 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run desktop-shell:build -- --no-bundle`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index 7cf162b8f..b92b7a3c7 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -64,7 +64,7 @@ src/ 已落地:`packages/shared/src/contracts/hostBridge.ts` 保存消息 envelope、method、payload 和错误码,H5、Expo 壳与 Tauri 壳共享同一份协议类型。 -三端宿主桥接层按职责对齐命名:微信小程序页面路由仍保留在 `miniprogram/pages/*`,`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,不把微信小程序硬改成 Expo / Tauri 的 request 总线;Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂统一放在 `miniprogram/shell/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,页面入口只做 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 承接能力清单与 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`files.ts` / `share.ts` 分别承接文件和分享能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade;`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`capabilities.rs` 承接能力清单,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` 分别承接文件和分享能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、窗口状态持久化和 WebView 门面,`main.rs` 只保留 Tauri builder / plugin / window 装配。 +三端宿主桥接层按职责对齐命名:微信小程序页面路由仍保留在 `miniprogram/pages/*`,`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,不把微信小程序硬改成 Expo / Tauri 的 request 总线;Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂统一放在 `miniprogram/shell/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,页面入口只做 `Page(createWechat...Page())` 装配。Expo 移动壳使用 `apps/mobile-shell/src/host-bridge/protocol.ts` 承接 envelope、request 校验、ok / failure 响应和 replay 基础类型,`capabilities.ts` 承接能力清单与 iOS 差异能力,`dispatch.ts` 承接 method 分发和宿主能力调用,`files.ts` / `share.ts` 分别承接文件和分享能力,`bridge.ts` 只作为 WebView message 入口、request id replay 编排和对外 facade;`apps/mobile-shell/App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx`,由 `apps/mobile-shell/src/shell/*.ts(x)` 承接 WebView 容器、URL、导航、网络、生命周期、安全区和 WebView policy。Tauri 桌面壳使用 `apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs` 承接 envelope、method 白名单、request 校验和 replay 状态,`capabilities.rs` 承接能力清单,`dispatch.rs` 承接 method 分发和宿主能力调用,`files.rs` / `share.rs` 分别承接文件和分享能力,`mod.rs` 只保留模块声明、必要 re-export、`host_bridge_request` command facade 和 replay 编排;`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`menu.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、应用菜单、窗口状态持久化和 WebView 门面,`main.rs` 只保留 Tauri builder / plugin / window 装配。 ## HostBridge 消息协议 @@ -183,7 +183,7 @@ Tauri 壳同样只负责桌面宿主能力,不承接玩法业务。 - Rust 侧只暴露一个受控 `host_bridge_request` command,再在 Rust 内部按 method 白名单分发。 - Tauri capabilities 只授予主窗口所需命令;默认不开放文件系统、shell、全局剪贴板或任意插件能力。 - 桌面支付首期走现有 H5 / 二维码 / 外部浏览器路径,不在 Rust 侧保存支付凭据。 -- 文件导出、作品卡保存、图片拖拽导入、系统托盘、自动更新等桌面能力按后续需求逐项开放;其中系统托盘属于桌面壳自有能力,不作为 HostBridge method 暴露给 H5。 +- 文件导出、作品卡保存、图片拖拽导入、应用菜单、系统托盘、自动更新等桌面能力按后续需求逐项开放;其中应用菜单和系统托盘属于桌面壳自有能力,不作为 HostBridge method 暴露给 H5。 - 主 WebView 默认拒绝网页自动下载或 `` 触发的落盘动作;用户保存文本、图片、音频等内容必须走已声明的 `file.exportText`、`file.exportImage`、`file.exportAudio` HostBridge method,由 Rust 侧执行 MIME、大小、文件名清洗和系统保存对话框确认。 - 主 WebView 显式关闭 DevTools,Cargo 不启用 Tauri `devtools` feature;本地调试通过普通浏览器和 Vite 完成,不把可分发桌面壳变成调试容器。 - 崩溃上报、前端 analytics、桌面遥测日志、自动更新和渠道分发 SDK 都必须等真实端点、采集字段、用户同意、隐私策略、签名和发布流程确定后逐项接入;当前桌面壳不安装 Sentry、Datadog、PostHog、Segment、Amplitude、Bugsnag、OpenTelemetry、Tauri log / updater 等相关依赖。 @@ -350,6 +350,8 @@ GameBridge 禁止: 2026-06-18 追加:桌面壳启用 Tauri 真实系统托盘,并复用品牌图标。托盘菜单只提供宿主壳级动作:显示主窗口、刷新主窗口和退出应用;左键点击托盘图标恢复并聚焦主窗口。该能力不进入 HostBridge capability 清单,不向 H5 暴露托盘 API、菜单 API、shell API 或任意窗口控制;如果当前桌面环境无法注册托盘,壳会继续启动主窗口。托盘注册成功时,用户点击主窗口关闭按钮会隐藏到托盘,必须通过托盘“退出”动作结束应用;托盘注册失败时不拦截关闭,避免窗口消失后没有恢复入口。 +2026-06-18 追加:桌面壳启用 Tauri 应用菜单。`shell/menu.rs` 注册应用菜单、编辑菜单和窗口菜单,其中应用菜单只复用宿主壳级动作:显示主窗口、刷新主窗口和退出应用;编辑 / 窗口项使用 Tauri 原生预定义菜单项承接系统剪切、复制、粘贴、全选、最小化、最大化和关闭窗口。该能力不进入 HostBridge capability,不向 H5 暴露菜单 API、shell API 或任意窗口控制;应用菜单注册失败视为桌面壳启动失败,避免可分发桌面包缺少系统菜单仍静默运行。 + 2026-06-18 追加:桌面壳启用 Tauri 单实例。用户重复启动桌面 App 时,新实例会退出并唤醒已有主窗口;该回调只执行显示、取消最小化和聚焦主窗口,不把第二实例的命令行参数或工作目录作为事件透传给 H5。Windows / Linux 上由单实例插件的 `deep-link` feature 把二次实例 URL 交给 deep-link 插件,仍由 `shell/deep_link.rs` 做受控归一。 2026-06-18 追加:桌面壳启用 Tauri 窗口状态持久化。`shell/window_state.rs` 只配置 `tauri-plugin-window-state` 保存主窗口大小、位置和最大化状态,并排除可见性、全屏和装饰状态;插件注册顺序固定为 single-instance 优先,其后才是窗口状态、deep-link 和其它系统能力插件。该能力不进入 HostBridge 清单,不向 H5 暴露任意窗口状态读写,也不改变托盘关闭隐藏、单实例唤醒和托盘恢复主窗口的既有语义。 @@ -418,7 +420,7 @@ GameBridge 禁止: 2026-06-18 追加:移动壳 HostBridge 消息入口增加来源校验。`onMessage` 不只依赖导航拦截和 `originWhitelist`,还会读取 `event.nativeEvent.url`,只有同源主站页面才能进入 `handleMobileHostBridgeMessage`;`about:blank`、外域 URL、协议降级或危险协议页面发来的消息全部丢弃,不返回 HostBridge 错误细节。该校验与 `navigation.openNativePage` 共用同源规则,防止历史中间页或异常页面在带完整 HostBridge 的 WebView 中发起宿主能力请求。 -2026-06-18 追加:微信、移动端和桌面端桥接层文件结构按职责对齐。微信小程序的 `web-view`、支付、九宫切图和订阅消息桥接逻辑统一迁入 `miniprogram/host-bridge/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,页面目录只保留页面生命周期、WXML/WXSS 和装配;移动壳拆成 `apps/mobile-shell/src/host-bridge/protocol.ts`、`files.ts`、`share.ts` 和 facade `bridge.ts`,与桌面端 `host_bridge/protocol.rs`、`files.rs`、`share.rs`、`mod.rs` 对齐;移动壳根 `App.tsx` 也保持薄入口,只装配 `src/shell/ShellApp.tsx`,WebView 容器、深链、网络、生命周期和安全策略全部留在 `src/shell/`;桌面壳 Rust 源码拆成 `apps/desktop-shell/src-tauri/src/host_bridge/*.rs` 与 `apps/desktop-shell/src-tauri/src/shell/*.rs`,其中 `runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、窗口状态持久化和 WebView 门面,薄 `main.rs` 只声明两个模块并装配 Tauri builder / plugin / window。根级 `npm run check:native-shells` 会锁定三端桥接层目录清单,避免后续把能力逻辑重新散落到页面、移动入口或桌面入口。 +2026-06-18 追加:微信、移动端和桌面端桥接层文件结构按职责对齐。微信小程序的 `web-view`、支付、九宫切图和订阅消息桥接逻辑统一迁入 `miniprogram/host-bridge/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,页面目录只保留页面生命周期、WXML/WXSS 和装配;移动壳拆成 `apps/mobile-shell/src/host-bridge/protocol.ts`、`files.ts`、`share.ts` 和 facade `bridge.ts`,与桌面端 `host_bridge/protocol.rs`、`files.rs`、`share.rs`、`mod.rs` 对齐;移动壳根 `App.tsx` 也保持薄入口,只装配 `src/shell/ShellApp.tsx`,WebView 容器、深链、网络、生命周期和安全策略全部留在 `src/shell/`;桌面壳 Rust 源码拆成 `apps/desktop-shell/src-tauri/src/host_bridge/*.rs` 与 `apps/desktop-shell/src-tauri/src/shell/*.rs`,其中 `runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`menu.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、应用菜单、窗口状态持久化和 WebView 门面,薄 `main.rs` 只声明两个模块并装配 Tauri builder / plugin / window。根级 `npm run check:native-shells` 会锁定三端桥接层目录清单,避免后续把能力逻辑重新散落到页面、移动入口或桌面入口。 ### Phase 4:宿主能力扩展 diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index f3ea7a8e4..e14b894fd 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -74,6 +74,7 @@ const expectedDesktopShellRustFiles = [ 'events.rs', 'file_drop.rs', 'lifecycle.rs', + 'menu.rs', 'mod.rs', 'navigation.rs', 'network.rs',