commit 93cdc9b7b5ee44fbd687f4157746de6e796aa929 Author: 树萌芽 <3205788256@qq.com> Date: Sun Dec 14 16:17:15 2025 +0800 初始化提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c8b18c --- /dev/null +++ b/.gitignore @@ -0,0 +1,166 @@ +## Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +## Visual Studio cache/options directory +.vs/ +.vscode/ + +## Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +## Rider +.idea/ +*.sln.iml + +## User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +## Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +## .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +## NuGet Packages +*.nupkg +*.snupkg +**/packages/* +!**/packages/build/ +*.nuget.props +*.nuget.targets + +## MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +## NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +## Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +## .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +## Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +## Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +## Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +## ReSharper +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +## JetBrains Rider +.idea/ +*.sln.iml + +## WebView2 runtime cache +*.exe.WebView2/ +EBWebView/ + +## Temporary files +*.tmp +*.temp +*.swp +*~ +.DS_Store +Thumbs.db + +## User-specific project files +*.csproj.user +*.user + +## Build output +publish/ +*.rar +*.zip + +## Config files (if contains sensitive data, uncomment) +# config/config.json + diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..dc450d4 --- /dev/null +++ b/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..fccac2a --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,14 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace WebToApp +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } + +} diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100644 index 0000000..1abffa4 --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,13 @@ + + + + + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs new file mode 100644 index 0000000..12d25ac --- /dev/null +++ b/MainWindow.xaml.cs @@ -0,0 +1,564 @@ +using System; +using System.IO; +using System.Net; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using Microsoft.Web.WebView2.Core; +using Microsoft.Web.WebView2.Wpf; +using Microsoft.Win32; + +namespace WebToApp +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private AppConfig _config = new(); + private StaticFileServer? _server; + private int? _serverPort; + private WebView2? Web => this.FindName("WebView") as WebView2; + + public MainWindow() + { + InitializeComponent(); + // 提前加载配置并应用窗口设置,确保在窗口显示前生效(避免先显示尺寸再全屏的卡顿) + try { LoadConfig(); ApplyWindowSettings(); } catch { } + Loaded += MainWindow_Loaded; + Closing += MainWindow_Closing; + } + + //主窗口加载 + private async void MainWindow_Loaded(object sender, RoutedEventArgs e) + { + try + { + // 初始化 WebView2 + await InitializeWebView2Async(); + + // 计算并导航 URL + var url = await GetTargetUrlAsync(); + if (string.IsNullOrWhiteSpace(url)) + { + MessageBox.Show("无法获取有效的URL,程序退出", "提示", MessageBoxButton.OK, MessageBoxImage.Information); + Close(); + return; + } + + Web!.Source = new Uri(url); + } + catch (Exception ex) + { + MessageBox.Show($"启动失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + Close(); + } + } + + //主窗口关闭 + private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e) + { + try { _server?.Stop(); } catch { /* ignore */ } + } + + //加载配置 + private void LoadConfig() + { + try + { + // 优先使用开发环境相对路径: ./config/config.json + var baseDir = AppContext.BaseDirectory; + var devConfigPath = Path.Combine(baseDir, "config", "config.json"); + if (File.Exists(devConfigPath)) + { + var json = File.ReadAllText(devConfigPath, Encoding.UTF8); + _config = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }) ?? new AppConfig(); + return; + } + + // 默认配置(在线网页) + _config = new AppConfig + { + 软件名称 = "网页应用", + 网页类型 = "在线网页", + 在线网页 = new AppConfig.在线网页配置 { 链接地址 = "https://www.example.com" }, + 窗口设置 = new AppConfig.窗口设置配置() + }; + } + catch + { + _config = new AppConfig(); + } + } + + //将配置应用到窗口设置 + private void ApplyWindowSettings() + { + Title = string.IsNullOrWhiteSpace(_config.软件名称) ? "网页应用" : _config.软件名称; + if (_config.窗口设置 is not null) + { + // 先处理全屏:如果全屏为 true,则直接最大化并忽略宽高设置 + if (_config.窗口设置.全屏) + { + WindowState = WindowState.Maximized; + } + else + { + Width = _config.窗口设置.窗口宽度 > 0 ? _config.窗口设置.窗口宽度 + 16 : 1216; + Height = _config.窗口设置.窗口高度 > 0 ? _config.窗口设置.窗口高度 + 39 : 839; + WindowState = WindowState.Normal; + } + + Topmost = _config.窗口设置.窗口置顶; + ResizeMode = _config.窗口设置.窗口可调整大小 ? ResizeMode.CanResize : ResizeMode.NoResize; + + if (!string.IsNullOrWhiteSpace(_config.软件logo)) + { + var iconPath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, _config.软件logo)); + if (File.Exists(iconPath)) + { + try + { + var uri = new Uri(iconPath); + Icon = System.Windows.Media.Imaging.BitmapFrame.Create(uri); + } + catch { /* ignore bad icon */ } + } + } + } + } + + //初始化异步加载webview + private async Task InitializeWebView2Async() + { + await Web!.EnsureCoreWebView2Async(); + + var settings = Web!.CoreWebView2.Settings; + settings.IsScriptEnabled = true; + settings.AreDefaultContextMenusEnabled = false; // 禁用默认右键菜单,使用自定义 + settings.AreDevToolsEnabled = false; + + // 设置移动端 UA(与示例一致) + Web!.CoreWebView2.Settings.UserAgent = + "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1"; + + // 处理 JS 消息 + Web!.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived; + + // 处理下载(普通 a 链接下载) + Web!.CoreWebView2.DownloadStarting += CoreWebView2_DownloadStarting; + + // 文档创建即注入脚本(滚动条隐藏、右键菜单、下载拦截) + await Web!.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(BuildInjectedScript()); + // 再次在 DOMContentLoaded 时执行一次,确保注入在有些页面上生效 + Web!.CoreWebView2.DOMContentLoaded += async (_, __) => + { + try { await Web!.CoreWebView2.ExecuteScriptAsync(BuildInjectedScript()); } catch { } + }; + } + + + private async Task GetTargetUrlAsync() + { + if (_config.网页类型 == "本地网页" && _config.本地网页 is not null) + { + var webDir = _config.本地网页.网页目录 ?? "config/web"; + var entry = _config.本地网页.网页入口 ?? "index.html"; + var display = _config.本地网页.展示模式 ?? "http服务器"; + + var baseDir = AppContext.BaseDirectory; + var absWebDir = Path.GetFullPath(Path.Combine(baseDir, webDir)); + + if (display == "直接本地") + { + var entryPath = Path.GetFullPath(Path.Combine(absWebDir, entry)); + if (File.Exists(entryPath)) + { + var uri = new Uri(entryPath); + return uri.AbsoluteUri; // file:///... 路径 + } + else + { + MessageBox.Show($"网页文件不存在: {entryPath}"); + return null; + } + } + else + { + // 启动内置 HTTP 服务器 + _server = new StaticFileServer(absWebDir); + _serverPort = _server.StartOnAvailablePort(); + var url = $"http://0.0.0.0:{_serverPort}/{entry}"; + return url; + } + } + else if (_config.网页类型 == "在线网页" && _config.在线网页 is not null) + { + return _config.在线网页.链接地址; + } + + return null; + } + + private void CoreWebView2_DownloadStarting(object? sender, CoreWebView2DownloadStartingEventArgs e) + { + try + { + // 打开保存对话框 + var sfd = new SaveFileDialog + { + FileName = Path.GetFileName(e.ResultFilePath), + Filter = "所有文件 (*.*)|*.*" + }; + if (sfd.ShowDialog() == true) + { + e.ResultFilePath = sfd.FileName; + e.Handled = true; // 使用我们自己的保存,不显示默认 UI + } + else + { + e.Cancel = true; + } + } + catch + { + // 失败则走默认 + } + } + + private async void CoreWebView2_WebMessageReceived(object? sender, CoreWebView2WebMessageReceivedEventArgs e) + { + try + { + var json = e.WebMessageAsJson; + var msg = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + if (msg == null) return; + + switch (msg.Type) + { + case "download": + await HandleInterceptedDownloadAsync(msg); + break; + case "show_about": + ShowAboutPage(); + break; + case "open_in_browser": + if (!string.IsNullOrWhiteSpace(msg.Url)) + { + try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = msg.Url, UseShellExecute = true }); } + catch { } + } + break; + } + } + catch { /* ignore */ } + } + + private async Task HandleInterceptedDownloadAsync(WebMsg msg) + { + try + { + var fileName = string.IsNullOrWhiteSpace(msg.Filename) ? $"下载文件_{DateTime.Now:yyyyMMdd_HHmmss}" : SanitizeFileName(msg.Filename!); + var filter = "所有文件 (*.*)|*.*"; + if (!string.IsNullOrWhiteSpace(msg.ContentType) && msg.ContentType.StartsWith("text/")) + { + filter = "文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*"; + } + + var sfd = new SaveFileDialog + { + FileName = fileName, + Filter = filter + }; + if (sfd.ShowDialog() == true) + { + byte[] bytes; + if (msg.IsBase64) + { + bytes = Convert.FromBase64String(msg.Content ?? string.Empty); + } + else + { + bytes = Encoding.UTF8.GetBytes(msg.Content ?? string.Empty); + } + await File.WriteAllBytesAsync(sfd.FileName, bytes); + } + } + catch + { + // 忽略错误 + } + } + + private static string SanitizeFileName(string name) + { + var invalid = Path.GetInvalidFileNameChars(); + foreach (var ch in invalid) + { + name = name.Replace(ch, '_'); + } + return name.Trim(' ', '.'); + } + + private void ShowAboutPage() + { + try + { + var baseDir = AppContext.BaseDirectory; + var aboutPath = Path.Combine(baseDir, "config/aboutpage", "about.html"); + if (File.Exists(aboutPath)) + { + Web!.CoreWebView2.Navigate(new Uri(aboutPath).AbsoluteUri); + } + else + { + // 备用:简单 About 内容 + var html = "data:text/html;charset=utf-8," + Uri.EscapeDataString("关于

关于

这是示例的关于页面。

"); + Web!.CoreWebView2.Navigate(html); + } + } + catch { /* ignore */ } + } + private string BuildInjectedScript() + { + var hideScroll = _config.注入设置?.隐藏网页滚动条 == true; + var menuEnabled = _config.注入设置?.自定义右键菜单 == true; + var interceptEnabled = _config.注入设置?.拦截下载链接 == true; + + var css = hideScroll ? @"/* 隐藏所有滚动条 */ +::-webkit-scrollbar { width:0px; height:0px; background:transparent; } +html { scrollbar-width: none; } +body { -ms-overflow-style: none; } +* { scrollbar-width: none; -ms-overflow-style: none; } +*::-webkit-scrollbar { width:0px; height:0px; background:transparent; }" : string.Empty; + + var sb = new StringBuilder(); + sb.AppendLine("(() => {"); + sb.AppendLine(" function setup(){"); + sb.AppendLine(" try {"); + + if (!string.IsNullOrEmpty(css)) + { + sb.AppendLine(" try { var style=document.createElement('style'); style.textContent=`" + css.Replace("`", "\\`") + "`; (document.head||document.documentElement).appendChild(style); } catch(e){ console.warn('样式注入失败', e);} "); + } + + if (menuEnabled) + { + sb.AppendLine(@" try { + const oldMenu = document.getElementById('custom-context-menu'); if (oldMenu) oldMenu.remove(); + const m = document.createElement('div'); + m.id = 'custom-context-menu'; + m.style.cssText = 'position:fixed;background:#fff;border:1px solid #ccc;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);padding:8px 0;z-index:10000;display:none;min-width:120px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px'; + function item(t,fn){ const b=document.createElement('button'); b.textContent=t; b.style.cssText='padding:8px 16px;cursor:pointer;border:none;background:none;width:100%;text-align:left;font-size:14px;color:#333'; b.onmouseenter=()=>b.style.background='#f0f0f0'; b.onmouseleave=()=>b.style.background='transparent'; b.onclick=()=>{ fn(); hide(); }; return b; } + function hide(){ m.style.display='none'; } + function show(x,y){ m.style.left=x+'px'; m.style.top=y+'px'; m.style.display='block'; const r=m.getBoundingClientRect(); if(r.right>innerWidth) m.style.left=(x-r.width)+'px'; if(r.bottom>innerHeight) m.style.top=(y-r.height)+'px'; } + m.appendChild(item('← 返回', ()=>history.back())); + m.appendChild(item('🔄 刷新', ()=>location.reload())); + m.appendChild(item('ℹ️ 关于', ()=>{ try{ if(window.chrome&&window.chrome.webview){ window.chrome.webview.postMessage({type:'show_about'}); } }catch(_){ } })); + (document.body||document.documentElement).appendChild(m); + document.addEventListener('click', hide, {passive:true}); + document.addEventListener('scroll', hide, {passive:true}); + addEventListener('resize', hide, {passive:true}); + document.addEventListener('contextmenu', function(e){ e.preventDefault(); show(e.clientX,e.clientY); }, false); + } catch(e) { console.warn('右键菜单设置失败', e); } + "); + } + + if (interceptEnabled) + { + sb.AppendLine(@" try { + function isDownloadLink(a){ if(!a||!a.href) return false; if(a.hasAttribute('download')) return true; const u=(a.href||'').toLowerCase(); const exts=['.txt','.pdf','.doc','.docx','.xls','.xlsx','.ppt','.pptx','.zip','.rar','.7z','.tar','.gz','.jpg','.jpeg','.png','.gif','.bmp','.svg','.webp','.mp3','.wav','.ogg','.mp4','.avi','.mov','.wmv','.json','.xml','.csv','.md','.log']; for(const e of exts){ if(u.includes(e)) return true; } if(u.startsWith('data:')||u.startsWith('blob:')) return true; const kws=['download','export','save','下载','导出','保存']; const t=(a.textContent||'').toLowerCase(); const ti=(a.title||'').toLowerCase(); return kws.some(k=>t.includes(k)||ti.includes(k)); } + async function toBase64FromBlob(b){ const ab=await b.arrayBuffer(); const bytes=new Uint8Array(ab); let bin=''; const size=0x8000; for(let i=0;i AcceptLoopAsync(_cts.Token)); + return port; + } + + public void Stop() + { + try { _cts?.Cancel(); } catch { } + try { _listener?.Stop(); } catch { } + } + + private async Task AcceptLoopAsync(CancellationToken ct) + { + if (_listener == null) return; + while (!ct.IsCancellationRequested) + { + HttpListenerContext? ctx = null; + try { ctx = await _listener.GetContextAsync(); } + catch { if (ct.IsCancellationRequested) break; } + if (ctx == null) continue; + _ = Task.Run(() => HandleRequestAsync(ctx)); + } + } + + private async Task HandleRequestAsync(HttpListenerContext ctx) + { + try + { + var req = ctx.Request; + var relPath = req.Url?.AbsolutePath ?? "/"; + relPath = WebUtility.UrlDecode(relPath).TrimStart('/'); + if (string.IsNullOrEmpty(relPath)) relPath = "index.html"; + + // 防止越权访问 + var fullPath = Path.GetFullPath(Path.Combine(_root, relPath.Replace('/', Path.DirectorySeparatorChar))); + if (!fullPath.StartsWith(Path.GetFullPath(_root), StringComparison.OrdinalIgnoreCase)) + { + ctx.Response.StatusCode = 403; + ctx.Response.Close(); + return; + } + + if (!File.Exists(fullPath)) + { + ctx.Response.StatusCode = 404; + ctx.Response.Close(); + return; + } + + var bytes = await File.ReadAllBytesAsync(fullPath); + ctx.Response.ContentType = GetContentType(fullPath); + ctx.Response.ContentLength64 = bytes.LongLength; + await ctx.Response.OutputStream.WriteAsync(bytes, 0, bytes.Length); + ctx.Response.OutputStream.Close(); + } + catch + { + try { ctx.Response.StatusCode = 500; ctx.Response.Close(); } catch { } + } + } + + private static string GetContentType(string path) + { + var ext = Path.GetExtension(path).ToLowerInvariant(); + return ext switch + { + ".html" or ".htm" => "text/html; charset=utf-8", + ".js" => "application/javascript; charset=utf-8", + ".css" => "text/css; charset=utf-8", + ".json" => "application/json; charset=utf-8", + ".png" => "image/png", + ".jpg" or ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".svg" => "image/svg+xml", + ".ico" => "image/x-icon", + ".txt" => "text/plain; charset=utf-8", + _ => "application/octet-stream" + }; + } + + private static int FindAvailablePort(int start) + { + for (var p = start; p < 65535; p++) + { + try + { + var l = new HttpListener(); + l.Prefixes.Add($"http://127.0.0.1:" + p + "/"); + l.Start(); + l.Stop(); + return p; + } + catch { /* try next */ } + } + throw new InvalidOperationException("无法找到可用的端口"); + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..be1f9a6 --- /dev/null +++ b/README.md @@ -0,0 +1,183 @@ +# WebToApp + +一个基于 WPF 和 WebView2 的网页转桌面应用工具,可以将在线网页或本地网页打包成 Windows 桌面应用程序。 + +## 功能特性 + +- 🌐 **支持在线网页**:直接加载远程网页地址 +- 📁 **支持本地网页**:支持本地 HTML 文件,可通过 HTTP 服务器或直接文件方式加载 +- 🎨 **自定义窗口设置**:可配置窗口大小、置顶、全屏等属性 +- 🖱️ **自定义右键菜单**:提供返回、刷新、关于等快捷操作 +- 📥 **下载拦截**:智能拦截下载链接,使用系统保存对话框 +- 🎯 **滚动条隐藏**:可隐藏网页滚动条,提供更沉浸的体验 +- 🖼️ **自定义图标和标题**:支持自定义应用图标和窗口标题 + +## 技术栈 + +- **.NET 8.0** - 跨平台开发框架 +- **WPF** - Windows Presentation Foundation +- **WebView2** - 基于 Chromium 的 WebView 控件 + +## 系统要求 + +- Windows 10/11 +- .NET 8.0 Runtime(如果使用独立部署则不需要) +- WebView2 Runtime(通常已预装或会自动下载) + +## 快速开始 + +### 1. 克隆项目 + +```bash +git clone https://github.com/yourusername/WebToApp.git +cd WebToApp +``` + +### 2. 配置应用 + +编辑 `config/config.json` 文件来配置你的应用: + +#### 在线网页配置示例 + +```json +{ + "软件名称": "我的应用", + "软件logo": "config/logo.png", + "网页类型": "在线网页", + "在线网页": { + "链接地址": "https://example.com" + }, + "注入设置": { + "隐藏网页滚动条": true, + "自定义右键菜单": true, + "拦截下载链接": true + }, + "窗口设置": { + "窗口宽度": 1200, + "窗口高度": 800, + "窗口可调整大小": true, + "窗口置顶": false, + "窗口阴影": true, + "全屏": false + } +} +``` + +#### 本地网页配置示例 + +```json +{ + "软件名称": "我的应用", + "软件logo": "config/logo.png", + "网页类型": "本地网页", + "本地网页": { + "网页目录": "config/web", + "网页入口": "index.html", + "展示模式": "http服务器" + }, + "注入设置": { + "隐藏网页滚动条": true, + "自定义右键菜单": true, + "拦截下载链接": true + }, + "窗口设置": { + "窗口宽度": 1200, + "窗口高度": 800, + "窗口可调整大小": true, + "窗口置顶": false, + "窗口阴影": true, + "全屏": false + } +} +``` + +### 3. 构建项目 + +使用 Visual Studio 或命令行构建: + +```bash +dotnet build +``` + +### 4. 运行应用 + +```bash +dotnet run +``` + +或者直接运行编译后的可执行文件。 + +### 5. 发布应用 + +发布为独立可执行文件: + +```bash +dotnet publish -c Release -r win-x64 --self-contained true +``` + +发布后的文件在 `bin/Release/net8.0-windows/win-x64/publish/` 目录下。 + +## 配置说明 + +### 窗口设置 + +- `窗口宽度` / `窗口高度`:设置窗口的初始大小(像素) +- `窗口可调整大小`:是否允许用户调整窗口大小 +- `窗口置顶`:窗口是否始终显示在其他窗口之上 +- `窗口阴影`:是否显示窗口阴影效果 +- `全屏`:是否以全屏模式启动 + +### 注入设置 + +- `隐藏网页滚动条`:隐藏网页内的滚动条 +- `自定义右键菜单`:启用自定义右键菜单(包含返回、刷新、关于等功能) +- `拦截下载链接`:拦截网页中的下载链接,使用系统保存对话框 + +### 本地网页展示模式 + +- `http服务器`:使用内置 HTTP 服务器提供网页服务(推荐) +- `直接本地`:直接使用 `file://` 协议加载本地文件 + +## 项目结构 + +``` +WebToApp/ +├── config/ # 配置文件目录 +│ ├── config.json # 主配置文件 +│ ├── logo.png # 应用图标 +│ └── aboutpage/ # 关于页面 +│ └── about.html +├── MainWindow.xaml # 主窗口 XAML +├── MainWindow.xaml.cs # 主窗口代码 +├── App.xaml # 应用程序 XAML +├── App.xaml.cs # 应用程序代码 +└── WebToApp.csproj # 项目文件 +``` + +## 开发 + +### 环境要求 + +- Visual Studio 2022 或更高版本 +- .NET 8.0 SDK + +### 依赖项 + +- Microsoft.Web.WebView2 (1.0.3595.46) + +## 许可证 + +本项目采用 MIT 许可证。 + +## 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 作者 + +[你的名字] + +--- + +如果这个项目对你有帮助,请给个 ⭐ Star 支持一下! + diff --git a/WebToApp.csproj b/WebToApp.csproj new file mode 100644 index 0000000..4df0708 --- /dev/null +++ b/WebToApp.csproj @@ -0,0 +1,45 @@ + + + + WinExe + net8.0-windows + enable + enable + true + + + + + + win-x64 + + + false + + + false + + + true + + + none + false + false + + + false + + + + + + + + + + PreserveNewest + + + + diff --git a/WebToApp.sln b/WebToApp.sln new file mode 100644 index 0000000..58d84fd --- /dev/null +++ b/WebToApp.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35527.113 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebToApp", "WebToApp.csproj", "{ED8C8E3D-BAB9-4937-A38B-E937B87F5CB3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED8C8E3D-BAB9-4937-A38B-E937B87F5CB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED8C8E3D-BAB9-4937-A38B-E937B87F5CB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED8C8E3D-BAB9-4937-A38B-E937B87F5CB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED8C8E3D-BAB9-4937-A38B-E937B87F5CB3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/config/aboutpage/about.html b/config/aboutpage/about.html new file mode 100644 index 0000000..4a3adab --- /dev/null +++ b/config/aboutpage/about.html @@ -0,0 +1,26 @@ + + + + + + 关于 - WebToApp 示例 + + + +

关于

+

这是用于演示的关于页面,展示从应用内打开本地 HTML 的能力。

+
+

本应用由 WPF + WebView2 构建,支持:

+
    +
  • 加载在线或本地网页
  • +
  • 自定义右键菜单
  • +
  • 拦截并保存 data/blob/同域下载
  • +
+
+ + diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..4aa257c --- /dev/null +++ b/config/config.json @@ -0,0 +1,27 @@ +{ + "软件名称": "灵创盘", + "软件logo": "config/logo.png", + "网页类型": "在线网页", + "在线网页": { + "链接地址": "https://pan.lcxm.site" + }, + "本地网页": { + "网页目录": "config/web", + "网页入口": "index.html", + "展示模式": "http服务器" + }, + "注入设置":{ + "隐藏网页滚动条": true, + "自定义右键菜单":true, + "拦截下载链接":true + }, + "窗口设置": { + "窗口宽度": 1200, + "窗口高度": 800, + "窗口可调整大小": true, + "窗口置顶": true, + "窗口阴影": true, + "全屏": false + } +} + \ No newline at end of file diff --git a/config/logo.png b/config/logo.png new file mode 100644 index 0000000..d6dbf1e Binary files /dev/null and b/config/logo.png differ diff --git a/dotnet b/dotnet new file mode 100644 index 0000000..e69de29 diff --git a/在线网页配置示例/aboutpage/about.html b/在线网页配置示例/aboutpage/about.html new file mode 100644 index 0000000..4a3adab --- /dev/null +++ b/在线网页配置示例/aboutpage/about.html @@ -0,0 +1,26 @@ + + + + + + 关于 - WebToApp 示例 + + + +

关于

+

这是用于演示的关于页面,展示从应用内打开本地 HTML 的能力。

+
+

本应用由 WPF + WebView2 构建,支持:

+
    +
  • 加载在线或本地网页
  • +
  • 自定义右键菜单
  • +
  • 拦截并保存 data/blob/同域下载
  • +
+
+ + diff --git a/在线网页配置示例/config.json b/在线网页配置示例/config.json new file mode 100644 index 0000000..4aa257c --- /dev/null +++ b/在线网页配置示例/config.json @@ -0,0 +1,27 @@ +{ + "软件名称": "灵创盘", + "软件logo": "config/logo.png", + "网页类型": "在线网页", + "在线网页": { + "链接地址": "https://pan.lcxm.site" + }, + "本地网页": { + "网页目录": "config/web", + "网页入口": "index.html", + "展示模式": "http服务器" + }, + "注入设置":{ + "隐藏网页滚动条": true, + "自定义右键菜单":true, + "拦截下载链接":true + }, + "窗口设置": { + "窗口宽度": 1200, + "窗口高度": 800, + "窗口可调整大小": true, + "窗口置顶": true, + "窗口阴影": true, + "全屏": false + } +} + \ No newline at end of file diff --git a/本地网页配置示例/aboutpage/about.html b/本地网页配置示例/aboutpage/about.html new file mode 100644 index 0000000..4a3adab --- /dev/null +++ b/本地网页配置示例/aboutpage/about.html @@ -0,0 +1,26 @@ + + + + + + 关于 - WebToApp 示例 + + + +

关于

+

这是用于演示的关于页面,展示从应用内打开本地 HTML 的能力。

+
+

本应用由 WPF + WebView2 构建,支持:

+
    +
  • 加载在线或本地网页
  • +
  • 自定义右键菜单
  • +
  • 拦截并保存 data/blob/同域下载
  • +
+
+ + diff --git a/本地网页配置示例/config.json b/本地网页配置示例/config.json new file mode 100644 index 0000000..d2fbf5b --- /dev/null +++ b/本地网页配置示例/config.json @@ -0,0 +1,27 @@ +{ + "软件名称": "灵创盘", + "软件logo": "config/logo.png", + "网页类型": "本地网页", + "在线网页": { + "链接地址": "https://pan.lcxm.site" + }, + "本地网页": { + "网页目录": "config/web", + "网页入口": "index.html", + "展示模式": "http服务器" + }, + "注入设置":{ + "隐藏网页滚动条": true, + "自定义右键菜单":true, + "拦截下载链接":true + }, + "窗口设置": { + "窗口宽度": 1200, + "窗口高度": 800, + "窗口可调整大小": true, + "窗口置顶": true, + "窗口阴影": true, + "全屏": false + } +} + \ No newline at end of file diff --git a/本地网页配置示例/web/images/sample.png b/本地网页配置示例/web/images/sample.png new file mode 100644 index 0000000..77d4e45 --- /dev/null +++ b/本地网页配置示例/web/images/sample.png @@ -0,0 +1 @@ +iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABh0RVh0Q3JlYXRpb24gVGltZQAyMDI1LTAxLTAxVDEyOjAwOjAwW7vWJQAAAQ1JREFUeNrs1kENgDAMQ9G9/5+S0YF4pG3lI1oIuJmYQ8NwJdC0xgq7r9q+3QAAACcWcH9yJwAAABgqY7tJwAAABgqY7tJwAAABgqY7tJwAAABgqY7tJwAAABgqY7tJwAAABgqY7tJwAAABgqY7tJwAAABgqY7tJwAAABgqY7tJwAAABi8BfC8YwAAAOw3oQIAAAD8fV8AAAAAANoYAwAAAPx9XwAAAADaGAMAAAD8fV8AAAAA2hgDAAAA/H1fAAAAANoYAwAAAPx9XwAAAADaGAMAAAD8fV8AAAAA2vgCkHfXfVQyAAAAAElFTkSuQmCC \ No newline at end of file diff --git a/本地网页配置示例/web/index.html b/本地网页配置示例/web/index.html new file mode 100644 index 0000000..5c220fe --- /dev/null +++ b/本地网页配置示例/web/index.html @@ -0,0 +1,83 @@ + + + + + + 本地网页示例 + + + +

本地网页示例

+

用于演示:右键菜单、下载拦截(data/blob/同域)、关于页面。

+ +
+

同域下载(静态文件)

+

+ 下载文本 sample.txt + 下载图片 sample.png +

+
+ +
+

Data URL 下载(内联文本)

+ + +
+ +
+

Blob URL 下载(运行时生成)

+ + +
+ +
+

右键菜单测试

+

在页面空白处点击右键,弹出自定义菜单(返回/刷新/关于)。

+
+ +
+

关于页面

+ 打开关于 + +
+ +
+

示例文件内容

+
+sample.txt 将在同目录下提供。
+images/sample.png 将作为占位图片。
+    
+
+ + diff --git a/本地网页配置示例/web/sample.txt b/本地网页配置示例/web/sample.txt new file mode 100644 index 0000000..ac151b2 --- /dev/null +++ b/本地网页配置示例/web/sample.txt @@ -0,0 +1,2 @@ +这是一个用于同域下载的示例文本文件。 +Hello from WebToApp (WPF + WebView2). diff --git a/构建命令.txt b/构建命令.txt new file mode 100644 index 0000000..fedd1e5 --- /dev/null +++ b/构建命令.txt @@ -0,0 +1,6 @@ + +dotnet publish -c Release -r win-x64 --self-contained false + +dotnet publish -c Release -r win-x64 --self-contained true + +dotnet build -c Debug \ No newline at end of file diff --git a/网页转Windows应用-CSharp b/网页转Windows应用-CSharp new file mode 100644 index 0000000..e69de29