From 16a5d28ed3c758a5315330f170a12b62485ff19e Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 26 Aug 2026 21:19:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20release=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E7=BD=91=E9=A1=B5=E5=8F=B3=E9=94=AE=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit release 客户端关闭 WebView 默认上下文菜单 保留 contextmenu 事件供业务组件处理 补充 release/debug 行为测试与技术方案约定 --- .../src-tauri/src/context_menu.rs | 84 +++++++++++++++++++ .../src-tauri/src/main.rs | 2 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 2 +- 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 apps/ai-game-creator-shell/src-tauri/src/context_menu.rs diff --git a/apps/ai-game-creator-shell/src-tauri/src/context_menu.rs b/apps/ai-game-creator-shell/src-tauri/src/context_menu.rs new file mode 100644 index 000000000..df6c4ccc2 --- /dev/null +++ b/apps/ai-game-creator-shell/src-tauri/src/context_menu.rs @@ -0,0 +1,84 @@ +use tauri::{ + plugin::{Builder, TauriPlugin}, + Runtime, +}; + +/// Release 客户端只取消浏览器的默认上下文菜单。 +/// +/// 这里刻意只调用 `preventDefault`:`contextmenu` 事件仍会继续沿 DOM +/// 传播,业务组件可以照常接收并打开自己的菜单。捕获阶段用于覆盖没有 +/// 自己右键处理的页面空白区域,但不会调用 `stopPropagation`。 +const RELEASE_CONTEXT_MENU_INIT_SCRIPT: &str = r#" +document.addEventListener( + 'contextmenu', + (event) => { + event.preventDefault(); + }, + true, +); +"#; + +fn context_menu_init_script(is_release: bool) -> Option<&'static str> { + is_release.then_some(RELEASE_CONTEXT_MENU_INIT_SCRIPT) +} + +#[cfg(all(windows, not(debug_assertions)))] +fn disable_windows_default_context_menus(webview: tauri::Webview) { + let _ = webview.with_webview(|platform_webview| { + // WebView2 的原生设置只关闭浏览器菜单,不会取消 DOM 的 + // `contextmenu` 事件,因此业务右键菜单仍然可以正常工作。 + // SAFETY: Tauri 在 WebView2 UI 线程上提供当前存活的 controller, + // 这些 COM 接口调用只使用该闭包期间有效的句柄。 + let _ = unsafe { + platform_webview + .controller() + .CoreWebView2() + .and_then(|webview| webview.Settings()) + .and_then(|settings| settings.SetAreDefaultContextMenusEnabled(false)) + }; + }); +} + +/// 注册 release-only 的默认网页上下文菜单策略。 +pub(crate) fn init() -> TauriPlugin { + let mut builder = Builder::new("release-context-menu"); + + if let Some(script) = context_menu_init_script(!cfg!(debug_assertions)) { + builder = builder.js_init_script_on_all_frames(script); + } + + #[cfg(all(windows, not(debug_assertions)))] + { + builder = builder.on_webview_ready(disable_windows_default_context_menus); + } + + builder.build() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn release_build_gets_a_context_menu_init_script() { + assert_eq!( + context_menu_init_script(true), + Some(RELEASE_CONTEXT_MENU_INIT_SCRIPT) + ); + } + + #[test] + fn debug_build_keeps_the_default_context_menu_strategy_untouched() { + assert_eq!(context_menu_init_script(false), None); + } + + #[test] + fn init_script_prevents_only_the_default_action() { + let script = RELEASE_CONTEXT_MENU_INIT_SCRIPT; + assert!(script.contains("contextmenu")); + assert!(script.contains("event.preventDefault()")); + assert!(script.contains("true")); + assert!(!script.contains("stopPropagation")); + assert!(!script.contains("stopImmediatePropagation")); + } +} diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index cc2fe26bb..98a2c8698 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -58,6 +58,7 @@ mod command_sandbox_trampoline; mod commands; mod config; mod context_compaction; +mod context_menu; #[cfg(all(debug_assertions, not(test)))] mod debug; mod delegation; @@ -2021,6 +2022,7 @@ fn main() { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_clipboard_manager::init()) + .plugin(context_menu::init()) .manage(game_creator_preview_registry()) .manage(ProjectResourcePreviewReadManager::default()) .setup(move |app| { diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index e19efaa26..4f5edc6b1 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -50,7 +50,7 @@ Runtime 确认卡与普通聊天确认卡必须共用“信息区 + 固定操作 此 seam 的纯测试覆盖嵌套 context、offset 反算、同树重挂载、子节点页面位置保持与 Inspector anchor preset 保位;几何修复应在此 Module 局部验证后由三个 adapter 复用。 -左侧 `UI Tree` 与预览画布复用同一个节点右键菜单组件和条目模型,顺序固定为“新增子节点 → 新增同级节点 → 删除节点及子节点”。页面根仅显示“新增子节点”;虚拟超级根不提供菜单。预览仅在“编辑叠加”模式中响应已渲染节点的鼠标右键,右键会选中该节点并阻止嵌套节点事件冒泡;“最终预览”以及画布空白处保留原有行为,空白处不接管浏览器原生菜单。 +左侧 `UI Tree` 与预览画布复用同一个节点右键菜单组件和条目模型,顺序固定为“新增子节点 → 新增同级节点 → 删除节点及子节点”。页面根仅显示“新增子节点”;虚拟超级根不提供菜单。预览仅在“编辑叠加”模式中响应已渲染节点的鼠标右键,右键会选中该节点并阻止嵌套节点事件冒泡;“最终预览”以及画布空白处不新增业务菜单。debug/dev 构建保留浏览器默认网页上下文菜单,release 客户端关闭 WebView 的默认网页菜单,但仍派发 `contextmenu` 事件给业务组件,不以停止事件传播的方式屏蔽右键交互。 菜单由页面级 portal 呈现,使用屏幕坐标且在视口边缘内收,避免受预览画布缩放和容器裁切影响。点击菜单外、按 Escape、窗口失焦、滚动或调整窗口大小均关闭菜单。全局 `controller.editor.isLocked` 时,节点仍可选中和查看菜单,但所有结构修改项禁用;状态层的 mutation 校验继续作为最终防线。点击可用项先关闭菜单再执行现有 controller 动作,不改变既有新增或删除后的选择策略。