初始化 Vue3+Vite 前端模板,适配 WebView2 桥接

新增项目基础结构与配置,集成 Vue3、Vite、TypeScript、ESLint 等开发环境。实现主页面、样式、图标组件,封装 http 请求,支持 WebView2 与普通浏览器统一 API 调用,便于与 C# 后端通信。完善类型声明与开发文档。
This commit is contained in:
2026-04-24 11:56:02 +08:00
parent 6f279fcae0
commit 7a5273dc56
66 changed files with 4307 additions and 402 deletions
+4
View File
@@ -0,0 +1,4 @@
# Copilot Instructions
## 项目指南
- 用户偏好:仅修改明确要求的内容,不要做额外改动(如未请求的 ViewModel DI 注册)。
+4 -5
View File
@@ -3,6 +3,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia_PC.ViewModels;
using Avalonia_PC.Views;
using Microsoft.Extensions.DependencyInjection;
namespace Avalonia_PC
{
@@ -17,13 +18,11 @@ namespace Avalonia_PC
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
desktop.MainWindow = Program.Services.GetRequiredService<MainWindow>();
desktop.MainWindow.DataContext = new MainWindowViewModel();
}
base.OnFrameworkInitializationCompleted();
}
}
}
}
+10 -1
View File
@@ -15,6 +15,10 @@
</Content>
</ItemGroup>
<ItemGroup>
<None Include=".github\copilot-instructions.md" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.0.1" />
<PackageReference Include="Avalonia.Desktop" Version="12.0.1" />
@@ -24,7 +28,12 @@
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
<PackageReference Include="Avalonia.Controls.WebView" Version="12.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia-Services\Avalonia-Services.csproj" />
</ItemGroup>
</Project>
+5 -1
View File
@@ -1,7 +1,11 @@
<Solution>
<Project Path="../Avalonia-API/Avalonia-API.csproj" Id="e33aba9a-a56b-4f6b-8eaa-3acbed65ebad" />
<Project Path="../Avalonia-Services/Avalonia-Services.csproj" Id="b8757cf9-5422-4c67-acae-3c967c95f866" />
<Project Path="../Avalonia-Web/avalonia-web.esproj">
<Project Path="../avalonia-web-react/avalonia-web-react.esproj">
<Build />
<Deploy />
</Project>
<Project Path="../Avalonia-Web/avalonia-web-vue.esproj">
<Build />
<Deploy />
</Project>
+14
View File
@@ -1,16 +1,23 @@
using Avalonia;
using Avalonia_PC.Views;
using Avalonia_Services.Services;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Avalonia_PC
{
internal sealed class Program
{
public static IServiceProvider Services { get; private set; } = null!;
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
ConfigureServices();
#if DEBUG
// 开启 WebView2 远程调试,启动后在 Edge 中访问 edge://inspect 调试网页
Environment.SetEnvironmentVariable(
@@ -20,7 +27,14 @@ namespace Avalonia_PC
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
private static void ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<WeatherForecastService>();
services.AddTransient<MainWindow>(sp => new MainWindow(sp));
Services = services.BuildServiceProvider();
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
@@ -0,0 +1,339 @@
namespace Avalonia_PC.Views
{
public partial class MainWindow
{
private const string BridgeScript = """
if (!window.__appBridgeInstalled) {
window.__appBridgeInstalled = true;
window.isWebView2 = true;
const pending = new Map();
const tryOpenDevTools = () => {
window.invokeCSharpAction(JSON.stringify({ kind: 'app-open-devtools' }));
};
window.__dispatchAppResponse = function(jsonStr) {
const payload = JSON.parse(jsonStr);
const responseId = payload.id ?? payload.Id;
const entry = pending.get(responseId);
if (!entry) return;
pending.delete(responseId);
entry.resolve(new Response(payload.body ?? payload.Body ?? '', {
status: payload.statusCode ?? payload.StatusCode ?? 200,
statusText: payload.statusMessage ?? payload.StatusMessage ?? 'OK',
headers: payload.headers ?? payload.Headers ?? { 'Content-Type': 'application/json' }
}));
};
const nativeFetch = window.fetch ? window.fetch.bind(window) : null;
const NativeXMLHttpRequest = window.XMLHttpRequest;
const sendAppBridgeRequest = ({ requestUrl, method, headers, body, timeoutMs = 30000 }) => {
const id = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
const responsePromise = new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
pending.delete(id);
reject(new Error(`Timed out waiting for ${requestUrl}`));
}, timeoutMs);
pending.set(id, {
resolve: response => { clearTimeout(timeoutId); resolve(response); },
reject: error => { clearTimeout(timeoutId); reject(error); }
});
});
window.invokeCSharpAction(JSON.stringify({
kind: 'app-request',
id,
url: requestUrl,
method,
headers,
body
}));
return responsePromise;
};
document.addEventListener('keydown', event => {
if (event.key === 'F12' || (event.ctrlKey && event.shiftKey && (event.key === 'I' || event.key === 'i'))) {
event.preventDefault();
tryOpenDevTools();
}
}, true);
document.addEventListener('contextmenu', event => {
if (event.shiftKey) {
event.preventDefault();
tryOpenDevTools();
}
}, true);
window.fetch = async (input, init) => {
const request = input instanceof Request ? input : null;
const requestUrl = typeof input === 'string' || input instanceof URL
? input.toString()
: request?.url;
if (!requestUrl || !requestUrl.startsWith('app://')) {
if (!nativeFetch) throw new Error('window.fetch is not available.');
return nativeFetch(input, init);
}
const combinedHeaders = new Headers(request?.headers);
if (init?.headers) {
new Headers(init.headers).forEach((value, key) => combinedHeaders.set(key, value));
}
const headers = {};
combinedHeaders.forEach((value, key) => headers[key] = value);
let body = init?.body;
if (body === undefined && request) {
body = await request.clone().text();
}
if (body && typeof body !== 'string') {
body = await new Response(body).text();
}
return sendAppBridgeRequest({
requestUrl,
method: init?.method ?? request?.method ?? 'GET',
headers,
body: body ?? null,
timeoutMs: 30000
});
};
class BridgeXMLHttpRequest {
constructor() {
this._native = new NativeXMLHttpRequest();
this._isAppRequest = false;
this._requestUrl = '';
this._method = 'GET';
this._headers = {};
this._responseHeaders = {};
this._responseHeadersRaw = '';
this._aborted = false;
this.readyState = 0;
this.status = 0;
this.statusText = '';
this.response = null;
this.responseText = '';
this.responseType = '';
this.responseURL = '';
this.timeout = 0;
this.withCredentials = false;
this.onreadystatechange = null;
this.onload = null;
this.onerror = null;
this.ontimeout = null;
this.onabort = null;
this.onloadend = null;
this.upload = {
addEventListener: () => {},
removeEventListener: () => {}
};
this._native.onreadystatechange = () => {
if (this._isAppRequest) {
return;
}
this.readyState = this._native.readyState;
this.status = this._native.status;
this.statusText = this._native.statusText;
this.responseURL = this._native.responseURL ?? '';
this.response = this._native.response;
this.responseText = this._native.responseText ?? '';
this._raiseReadyStateChange();
};
this._native.onload = event => {
if (!this._isAppRequest && typeof this.onload === 'function') {
this.onload(event);
}
};
this._native.onerror = event => {
if (!this._isAppRequest && typeof this.onerror === 'function') {
this.onerror(event);
}
};
this._native.ontimeout = event => {
if (!this._isAppRequest && typeof this.ontimeout === 'function') {
this.ontimeout(event);
}
};
this._native.onabort = event => {
if (!this._isAppRequest && typeof this.onabort === 'function') {
this.onabort(event);
}
};
this._native.onloadend = event => {
if (!this._isAppRequest && typeof this.onloadend === 'function') {
this.onloadend(event);
}
};
}
open(method, url, async = true, user, password) {
const requestUrl = typeof url === 'string' || url instanceof URL
? url.toString()
: `${url ?? ''}`;
this._requestUrl = requestUrl;
this._method = method ?? 'GET';
this._isAppRequest = requestUrl.startsWith('app://');
this._headers = {};
this._responseHeaders = {};
this._responseHeadersRaw = '';
this._aborted = false;
if (!this._isAppRequest) {
this._native.open(method, url, async, user, password);
return;
}
this.readyState = 1;
this._raiseReadyStateChange();
}
setRequestHeader(name, value) {
if (!this._isAppRequest) {
this._native.setRequestHeader(name, value);
return;
}
this._headers[name] = value;
}
getAllResponseHeaders() {
if (!this._isAppRequest) {
return this._native.getAllResponseHeaders();
}
return this._responseHeadersRaw;
}
getResponseHeader(name) {
if (!this._isAppRequest) {
return this._native.getResponseHeader(name);
}
return this._responseHeaders[name.toLowerCase()] ?? null;
}
overrideMimeType(mimeType) {
if (!this._isAppRequest && typeof this._native.overrideMimeType === 'function') {
this._native.overrideMimeType(mimeType);
}
}
abort() {
if (!this._isAppRequest) {
this._native.abort();
return;
}
this._aborted = true;
if (typeof this.onabort === 'function') {
this.onabort();
}
if (typeof this.onloadend === 'function') {
this.onloadend();
}
}
async send(body = null) {
if (!this._isAppRequest) {
this._native.send(body);
return;
}
let requestBody = body;
if (requestBody && typeof requestBody !== 'string') {
requestBody = await new Response(requestBody).text();
}
try {
const response = await sendAppBridgeRequest({
requestUrl: this._requestUrl,
method: this._method,
headers: this._headers,
body: requestBody ?? null,
timeoutMs: this.timeout > 0 ? this.timeout : 30000
});
if (this._aborted) {
return;
}
this.status = response.status;
this.statusText = response.statusText;
this.responseURL = this._requestUrl;
this._responseHeaders = {};
this._responseHeadersRaw = '';
response.headers.forEach((value, key) => {
this._responseHeaders[key.toLowerCase()] = value;
this._responseHeadersRaw += `${key}: ${value}\r\n`;
});
const text = await response.text();
this.responseText = text;
this.response = this.responseType === 'json'
? (text ? JSON.parse(text) : null)
: text;
this.readyState = 4;
this._raiseReadyStateChange();
if (typeof this.onload === 'function') {
this.onload();
}
if (typeof this.onloadend === 'function') {
this.onloadend();
}
} catch (error) {
if (this._aborted) {
return;
}
this.status = 0;
this.statusText = '';
this.readyState = 4;
this._raiseReadyStateChange();
const errorMessage = error?.message ?? '';
if (errorMessage.includes('Timed out waiting') && typeof this.ontimeout === 'function') {
this.ontimeout(error);
} else if (typeof this.onerror === 'function') {
this.onerror(error);
}
if (typeof this.onloadend === 'function') {
this.onloadend();
}
}
}
_raiseReadyStateChange() {
if (typeof this.onreadystatechange === 'function') {
this.onreadystatechange();
}
}
}
window.XMLHttpRequest = BridgeXMLHttpRequest;
}
""";
}
}
+49
View File
@@ -0,0 +1,49 @@
using Avalonia_Services.Services;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Avalonia_PC.Views
{
public partial class MainWindow
{
// 路由表:key = 接口路径(忽略大小写),value = 处理方法
// 新增接口:在此方法中添加一行 _routes["api/xxx"] = ctx => ...
private Dictionary<string, Func<RouteRequestContext, Task<object?>>> _routes = [];
// 服务容器,通过构造函数注入,路由注册时按需解析服务
private IServiceProvider _services = null!;
private void RegisterRoutes()
{
var weather = _services.GetRequiredService<WeatherForecastService>();
// 新增服务示例:var myService = _services.GetRequiredService<MyService>();
_routes = new Dictionary<string, Func<RouteRequestContext, Task<object?>>>(StringComparer.OrdinalIgnoreCase)
{
["api/getUser"] = _ => GetUserFromDatabaseAsync(),
["api/processData"] = ctx => ProcessDataAsync(ExtractInput(ctx)),
["api/wData"] = _ => Task.FromResult<object?>(weather.GetWeatherForecasts()),
};
}
/// <summary>
/// 示例:模拟读取用户数据。
/// </summary>
private static async Task<object?> GetUserFromDatabaseAsync()
{
await Task.Delay(100);
return new { id = 1, name = "张三", email = "zhangsan@example.com" };
}
/// <summary>
/// 示例:模拟处理输入数据。
/// </summary>
private static async Task<object?> ProcessDataAsync(string? input)
{
await Task.Delay(200);
return $"Processed: {input?.ToUpperInvariant()}";
}
}
}
+11 -390
View File
@@ -15,8 +15,8 @@ namespace Avalonia_PC.Views
public partial class MainWindow : Window
{
private const string AppScheme = "app";
//private const string? OnlineStartupUrl = "https://re.laitool.cn";
private const string? OnlineStartupUrl = null;
private const string? OnlineStartupUrl = "http://localhost:51240";
//private const string? OnlineStartupUrl = null;
private const string? LocalStartupPath = null;
private static readonly JsonSerializerOptions BridgeJsonSerializerOptions = new()
{
@@ -36,12 +36,14 @@ namespace Avalonia_PC.Views
/// <summary>
/// 初始化窗口并注册生命周期事件。
/// </summary>
public MainWindow()
public MainWindow(IServiceProvider services)
{
_services = services;
InitializeComponent();
Opened += OnOpened;
Closed += OnClosed;
RegisterRoutes();
}
/// <summary>
@@ -212,341 +214,7 @@ namespace Avalonia_PC.Views
return;
}
const string script = """
if (!window.__appBridgeInstalled) {
window.__appBridgeInstalled = true;
window.isWebView2 = true;
const pending = new Map();
const tryOpenDevTools = () => {
window.invokeCSharpAction(JSON.stringify({ kind: 'app-open-devtools' }));
};
window.__dispatchAppResponse = function(jsonStr) {
const payload = JSON.parse(jsonStr);
const responseId = payload.id ?? payload.Id;
const entry = pending.get(responseId);
if (!entry) return;
pending.delete(responseId);
entry.resolve(new Response(payload.body ?? payload.Body ?? '', {
status: payload.statusCode ?? payload.StatusCode ?? 200,
statusText: payload.statusMessage ?? payload.StatusMessage ?? 'OK',
headers: payload.headers ?? payload.Headers ?? { 'Content-Type': 'application/json' }
}));
};
const nativeFetch = window.fetch ? window.fetch.bind(window) : null;
const NativeXMLHttpRequest = window.XMLHttpRequest;
const sendAppBridgeRequest = ({ requestUrl, method, headers, body, timeoutMs = 30000 }) => {
const id = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
const responsePromise = new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
pending.delete(id);
reject(new Error(`Timed out waiting for ${requestUrl}`));
}, timeoutMs);
pending.set(id, {
resolve: response => { clearTimeout(timeoutId); resolve(response); },
reject: error => { clearTimeout(timeoutId); reject(error); }
});
});
window.invokeCSharpAction(JSON.stringify({
kind: 'app-request',
id,
url: requestUrl,
method,
headers,
body
}));
return responsePromise;
};
document.addEventListener('keydown', event => {
if (event.key === 'F12' || (event.ctrlKey && event.shiftKey && (event.key === 'I' || event.key === 'i'))) {
event.preventDefault();
tryOpenDevTools();
}
}, true);
document.addEventListener('contextmenu', event => {
if (event.shiftKey) {
event.preventDefault();
tryOpenDevTools();
}
}, true);
window.fetch = async (input, init) => {
const request = input instanceof Request ? input : null;
const requestUrl = typeof input === 'string' || input instanceof URL
? input.toString()
: request?.url;
if (!requestUrl || !requestUrl.startsWith('app://')) {
if (!nativeFetch) throw new Error('window.fetch is not available.');
return nativeFetch(input, init);
}
const combinedHeaders = new Headers(request?.headers);
if (init?.headers) {
new Headers(init.headers).forEach((value, key) => combinedHeaders.set(key, value));
}
const headers = {};
combinedHeaders.forEach((value, key) => headers[key] = value);
let body = init?.body;
if (body === undefined && request) {
body = await request.clone().text();
}
if (body && typeof body !== 'string') {
body = await new Response(body).text();
}
return sendAppBridgeRequest({
requestUrl,
method: init?.method ?? request?.method ?? 'GET',
headers,
body: body ?? null,
timeoutMs: 30000
});
};
class BridgeXMLHttpRequest {
constructor() {
this._native = new NativeXMLHttpRequest();
this._isAppRequest = false;
this._requestUrl = '';
this._method = 'GET';
this._headers = {};
this._responseHeaders = {};
this._responseHeadersRaw = '';
this._aborted = false;
this.readyState = 0;
this.status = 0;
this.statusText = '';
this.response = null;
this.responseText = '';
this.responseType = '';
this.responseURL = '';
this.timeout = 0;
this.withCredentials = false;
this.onreadystatechange = null;
this.onload = null;
this.onerror = null;
this.ontimeout = null;
this.onabort = null;
this.onloadend = null;
this.upload = {
addEventListener: () => {},
removeEventListener: () => {}
};
this._native.onreadystatechange = () => {
if (this._isAppRequest) {
return;
}
this.readyState = this._native.readyState;
this.status = this._native.status;
this.statusText = this._native.statusText;
this.responseURL = this._native.responseURL ?? '';
this.response = this._native.response;
this.responseText = this._native.responseText ?? '';
this._raiseReadyStateChange();
};
this._native.onload = event => {
if (!this._isAppRequest && typeof this.onload === 'function') {
this.onload(event);
}
};
this._native.onerror = event => {
if (!this._isAppRequest && typeof this.onerror === 'function') {
this.onerror(event);
}
};
this._native.ontimeout = event => {
if (!this._isAppRequest && typeof this.ontimeout === 'function') {
this.ontimeout(event);
}
};
this._native.onabort = event => {
if (!this._isAppRequest && typeof this.onabort === 'function') {
this.onabort(event);
}
};
this._native.onloadend = event => {
if (!this._isAppRequest && typeof this.onloadend === 'function') {
this.onloadend(event);
}
};
}
open(method, url, async = true, user, password) {
const requestUrl = typeof url === 'string' || url instanceof URL
? url.toString()
: `${url ?? ''}`;
this._requestUrl = requestUrl;
this._method = method ?? 'GET';
this._isAppRequest = requestUrl.startsWith('app://');
this._headers = {};
this._responseHeaders = {};
this._responseHeadersRaw = '';
this._aborted = false;
if (!this._isAppRequest) {
this._native.open(method, url, async, user, password);
return;
}
this.readyState = 1;
this._raiseReadyStateChange();
}
setRequestHeader(name, value) {
if (!this._isAppRequest) {
this._native.setRequestHeader(name, value);
return;
}
this._headers[name] = value;
}
getAllResponseHeaders() {
if (!this._isAppRequest) {
return this._native.getAllResponseHeaders();
}
return this._responseHeadersRaw;
}
getResponseHeader(name) {
if (!this._isAppRequest) {
return this._native.getResponseHeader(name);
}
return this._responseHeaders[name.toLowerCase()] ?? null;
}
overrideMimeType(mimeType) {
if (!this._isAppRequest && typeof this._native.overrideMimeType === 'function') {
this._native.overrideMimeType(mimeType);
}
}
abort() {
if (!this._isAppRequest) {
this._native.abort();
return;
}
this._aborted = true;
if (typeof this.onabort === 'function') {
this.onabort();
}
if (typeof this.onloadend === 'function') {
this.onloadend();
}
}
async send(body = null) {
if (!this._isAppRequest) {
this._native.send(body);
return;
}
let requestBody = body;
if (requestBody && typeof requestBody !== 'string') {
requestBody = await new Response(requestBody).text();
}
try {
const response = await sendAppBridgeRequest({
requestUrl: this._requestUrl,
method: this._method,
headers: this._headers,
body: requestBody ?? null,
timeoutMs: this.timeout > 0 ? this.timeout : 30000
});
if (this._aborted) {
return;
}
this.status = response.status;
this.statusText = response.statusText;
this.responseURL = this._requestUrl;
this._responseHeaders = {};
this._responseHeadersRaw = '';
response.headers.forEach((value, key) => {
this._responseHeaders[key.toLowerCase()] = value;
this._responseHeadersRaw += `${key}: ${value}\r\n`;
});
const text = await response.text();
this.responseText = text;
this.response = this.responseType === 'json'
? (text ? JSON.parse(text) : null)
: text;
this.readyState = 4;
this._raiseReadyStateChange();
if (typeof this.onload === 'function') {
this.onload();
}
if (typeof this.onloadend === 'function') {
this.onloadend();
}
} catch (error) {
if (this._aborted) {
return;
}
this.status = 0;
this.statusText = '';
this.readyState = 4;
this._raiseReadyStateChange();
const errorMessage = error?.message ?? '';
if (errorMessage.includes('Timed out waiting') && typeof this.ontimeout === 'function') {
this.ontimeout(error);
} else if (typeof this.onerror === 'function') {
this.onerror(error);
}
if (typeof this.onloadend === 'function') {
this.onloadend();
}
}
}
_raiseReadyStateChange() {
if (typeof this.onreadystatechange === 'function') {
this.onreadystatechange();
}
}
}
window.XMLHttpRequest = BridgeXMLHttpRequest;
}
""";
await _webView.InvokeScript(script);
await _webView.InvokeScript(BridgeScript);
}
#endregion
@@ -590,8 +258,6 @@ if (!window.__appBridgeInstalled) {
{
var uri = new Uri(rawUrl ?? throw new InvalidOperationException("请求地址不能为空。"));
var requestContext = CreateRouteRequestContext(uri, body);
var authorization = GetAuthorizationHeader(headers);
_ = authorization;
if (string.Equals(method, "OPTIONS", StringComparison.OrdinalIgnoreCase))
{
@@ -625,37 +291,14 @@ if (!window.__appBridgeInstalled) {
}
/// <summary>
/// 按请求前缀分发处理器(例如 api、sys、admin 等)
/// 按路由表匹配并调用对应处理器
/// </summary>
private async Task<RouteDispatchResult> DispatchByPrefixAsync(RouteRequestContext requestContext)
{
if (requestContext.PathSegments.Length > 0 &&
string.Equals(requestContext.PathSegments[0], "api", StringComparison.OrdinalIgnoreCase))
if (_routes.TryGetValue(requestContext.NormalizedPath, out var handler))
{
return await HandleApiPrefixAsync(requestContext);
}
return RouteDispatchResult.NotMatched();
}
/// <summary>
/// 处理 api 前缀下的具体业务路由。
/// </summary>
private static async Task<RouteDispatchResult> HandleApiPrefixAsync(RouteRequestContext requestContext)
{
if (string.Equals(requestContext.NormalizedPath, "api/getUser", StringComparison.OrdinalIgnoreCase))
{
var user = await GetUserFromDatabaseAsync();
return RouteDispatchResult.Success(user);
}
if (string.Equals(requestContext.NormalizedPath, "api/processData", StringComparison.OrdinalIgnoreCase) ||
(requestContext.PathSegments.Length > 1 &&
string.Equals(requestContext.PathSegments[1], "processData", StringComparison.OrdinalIgnoreCase)))
{
var input = ExtractInput(requestContext);
var result = await ProcessDataAsync(input);
return RouteDispatchResult.Success(result);
var data = await handler(requestContext);
return RouteDispatchResult.Success(data);
}
return RouteDispatchResult.NotMatched();
@@ -1000,28 +643,6 @@ if (!window.__appBridgeInstalled) {
#endregion
#region
/// <summary>
/// 示例:模拟读取用户数据。
/// </summary>
private static async Task<object> GetUserFromDatabaseAsync()
{
await Task.Delay(100);
return new { id = 1, name = "张三", email = "zhangsan@example.com" };
}
/// <summary>
/// 示例:模拟处理输入数据。
/// </summary>
private static async Task<string> ProcessDataAsync(string? input)
{
await Task.Delay(200);
return $"Processed: {input?.ToUpperInvariant()}";
}
#endregion
#region DTO /
private sealed class AppResponse
+2 -1
View File
@@ -46,5 +46,6 @@ async function callApi(endpoint, options = {}) {
window.api = {
getUser: () => callApi("getUser?t=1"),
processData: (input) => callApi("processData", { method: "POST", body: { input } })
processData: (input) => callApi("processData", { method: "POST", body: { input } }),
wData: (input) => callApi("wData", { method: "POST", body: { input } }),
};
+10
View File
@@ -8,6 +8,7 @@
<h1>WebView2 自定义协议演示</h1>
<button id="getUserBtn">获取用户信息</button>
<button id="processBtn">处理数据</button>
<button id="wBtn">天气数据</button>
<pre id="output"></pre>
<script src="./api.js"></script>
@@ -32,6 +33,15 @@
}
};
document.getElementById('wBtn').onclick = async () => {
try {
const result = await window.api.wData('hello world');
output.textContent = JSON.stringify(result, null, 2);
} catch (err) {
output.textContent = `错误: ${err.message}`;
}
};
const detectIsWebView2 = () => window.isWebView2 === true || typeof window.invokeCSharpAction === 'function';
const renderEnvironment = () => {