using Microsoft.Maui.Controls; using Hua.Todo.Maui.Models; using Hua.Todo.Maui.Services; namespace Hua.Todo.Maui.Views { /// /// 应用程序主页面。 /// 该页面承载 WebView(前端 UI),并通过 JavaScript 注入与事件机制实现与 MAUI 的通讯。 /// public partial class MainPage : ContentPage { private readonly AppSettings _appSettings; private readonly IEmbeddedWebServerService? _webServer; /// /// 创建 。 /// /// 应用配置。 /// 嵌入式 Web 服务器服务(在不同平台可能为不同实现)。 public MainPage(AppSettings appSettings, IEmbeddedWebServerService webServer) { InitializeComponent(); _appSettings = appSettings; _webServer = webServer; SetupWebViewSource(); SetupWebViewCommunication(); SetupKeyboardHandler(); } /// /// 设置 WebView 数据源。 /// 当启用嵌入式服务器且使用静态文件时,直接指向本地服务器;否则使用配置的前端 URL。 /// private void SetupWebViewSource() { if (_appSettings.WebServer.IsUsingStatic) { if (_webServer != null) { MainWebView.Source = _webServer.BaseUrl; return; } } MainWebView.Source = NormalizeUrl(_appSettings.WebServer.ForEndUrl); } /// /// 设置键盘处理器(平台差异通过分部类实现)。 /// private void SetupKeyboardHandler() { PlatformSetupKeyboardHandler(); } /// /// 当 Esc 键按下时的回调 /// private void OnEscKeyPressed(object? sender, EventArgs e) { var window = Microsoft.Maui.Controls.Application.Current?.Windows.FirstOrDefault(); if (window != null) { PlatformOnEscKeyPressed(window); } } /// /// 设置 WebView 通讯逻辑。 /// 约定: /// - 通过 window.__API_BASE_URL__ 注入后端 API 基地址(仅在嵌入式服务器运行时) /// - 通过自定义事件在 Web 与 MAUI 间传递热键配置等数据 /// private void SetupWebViewCommunication() { MainWebView.Navigated += async (s, e) => { #if DEBUG if (e.Result != WebNavigationResult.Success) { await DisplayAlertAsync("加载失败", $"{e.Url}\n{e.Result}", "OK"); } #endif if (_webServer is { IsRunning: true }) { var apiBase = $"{_webServer.BaseUrl.TrimEnd('/')}/api"; await MainWebView.EvaluateJavaScriptAsync($"window.__API_BASE_URL__ = '{apiBase}';"); } // 注入前端与 MAUI 的通讯桥(事件名/字段名属于协议的一部分,修改需同步前端)。 await MainWebView.EvaluateJavaScriptAsync(@" window.mauiInterop = { onHotKeyConfigUpdated: null, openHotKeySettings: function(config) { const event = new CustomEvent('openHotKeySettings', { detail: config }); window.dispatchEvent(event); }, updateHotKeyConfig: function(modifiers, key, isEnabled) { const event = new CustomEvent('updateHotKeyConfig', { detail: { modifiers, key, isEnabled } }); window.dispatchEvent(event); } }; window.addEventListener('hotKeyConfigChanged', function(e) { if (window.mauiInterop.onHotKeyConfigUpdated) { window.mauiInterop.onHotKeyConfigUpdated(e.detail); } }); "); }; } /// /// 规格化 URL(针对 Android 模拟器处理 localhost)。 /// Android 模拟器中 localhost 指向模拟器自身,需要替换为 10.0.2.2 才能访问宿主机服务。 /// private static string NormalizeUrl(string url) { if (string.IsNullOrWhiteSpace(url)) return url; if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) return url; var host = uri.Host; if (host != "localhost" && host != "127.0.0.1") return url; if (DeviceInfo.Platform == DevicePlatform.Android && DeviceInfo.DeviceType == DeviceType.Virtual) { var builder = new UriBuilder(uri) { Host = "10.0.2.2" }; return builder.Uri.ToString(); } return url; } // 平台特定分部方法声明(实现位于 Platforms/* 目录) /// /// 平台特定的键盘处理器初始化。 /// partial void PlatformSetupKeyboardHandler(); /// /// 平台特定的 Esc 键处理逻辑。 /// /// 当前窗口。 partial void PlatformOnEscKeyPressed(Window window); } }