WPF和web泛型通信
This commit is contained in:
@@ -9,6 +9,7 @@ using System.Windows.Media.Imaging;
|
|||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Microsoft.Web.WebView2.Core;
|
using Microsoft.Web.WebView2.Core;
|
||||||
|
using WebView2Demo.WebInterop;
|
||||||
|
|
||||||
namespace WebView2Demo
|
namespace WebView2Demo
|
||||||
{
|
{
|
||||||
@@ -17,6 +18,8 @@ namespace WebView2Demo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
|
private MainSystemGateway _gateway = new();
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -33,11 +36,14 @@ namespace WebView2Demo
|
|||||||
// 2. 注入调试开启指令 (可选)
|
// 2. 注入调试开启指令 (可选)
|
||||||
webView.CoreWebView2.Settings.AreDevToolsEnabled = true;
|
webView.CoreWebView2.Settings.AreDevToolsEnabled = true;
|
||||||
|
|
||||||
// 3. 监听消息
|
// 3. 注入网关 Host Object (供前端调用)
|
||||||
|
webView.CoreWebView2.AddHostObjectToScript("gateway", _gateway);
|
||||||
|
|
||||||
|
// 4. 监听消息
|
||||||
webView.WebMessageReceived += WebView_WebMessageReceived;
|
webView.WebMessageReceived += WebView_WebMessageReceived;
|
||||||
|
|
||||||
// 4. 初始化完成后再导航,确保 window.chrome.webview 注入成功
|
// 5. 初始化完成后再导航
|
||||||
webView.Source = new Uri("http://localhost:5173");
|
webView.Source = new Uri("http://localhost:5174");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WebView2Demo.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 标准 API 请求信封 (泛型版本,方便内部使用)
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">具体的业务数据类型</typeparam>
|
||||||
|
public class StandardApiRequest<T>
|
||||||
|
{
|
||||||
|
[JsonPropertyName("action")]
|
||||||
|
public string Action { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("data")]
|
||||||
|
public T? Data { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基础请求信封 (用于初次解析 Action)
|
||||||
|
/// </summary>
|
||||||
|
public class StandardApiRequest : StandardApiRequest<JsonElement> { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标准 API 响应信封
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">返回的业务数据类型</typeparam>
|
||||||
|
public class StandardApiResponse<T>
|
||||||
|
{
|
||||||
|
[JsonPropertyName("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("message")]
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("data")]
|
||||||
|
public T? Data { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("timestamp")]
|
||||||
|
public long Timestamp { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基础响应信封 (默认返回对象类型)
|
||||||
|
/// </summary>
|
||||||
|
public class StandardApiResponse : StandardApiResponse<object> { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 示例用的业务参数模型
|
||||||
|
/// </summary>
|
||||||
|
public class CalculationParams
|
||||||
|
{
|
||||||
|
[JsonPropertyName("a")]
|
||||||
|
public int A { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("b")]
|
||||||
|
public int B { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WebView2Demo.WebInterop.Attributes;
|
||||||
|
|
||||||
|
namespace WebView2Demo.WebInterop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API 路由管理器:负责扫描 [WebApiControl] 和 [WebAction] 并执行分发
|
||||||
|
/// </summary>
|
||||||
|
public class ApiRouteManager
|
||||||
|
{
|
||||||
|
// 缓存映射: ActionName -> (Instance, MethodInfo)
|
||||||
|
private readonly Dictionary<string, (object Instance, MethodInfo Method)> _routeTable = new();
|
||||||
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
|
||||||
|
public ApiRouteManager(JsonSerializerOptions jsonOptions)
|
||||||
|
{
|
||||||
|
_jsonOptions = jsonOptions;
|
||||||
|
ScanAndRegisterRoutes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScanAndRegisterRoutes()
|
||||||
|
{
|
||||||
|
// 扫描当前程序集所有标记了 [WebApiControl] 的类
|
||||||
|
var types = Assembly.GetExecutingAssembly().GetTypes()
|
||||||
|
.Where(t => t.GetCustomAttribute<WebApiControlAttribute>() != null);
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
// 创建业务类实例 (生产环境建议从 DI 容器获取)
|
||||||
|
var instance = Activator.CreateInstance(type);
|
||||||
|
if (instance == null) continue;
|
||||||
|
|
||||||
|
// 扫描所有标记了 [WebAction] 的方法
|
||||||
|
var methods = type.GetMethods()
|
||||||
|
.Where(m => m.GetCustomAttribute<WebActionAttribute>() != null);
|
||||||
|
|
||||||
|
foreach (var method in methods)
|
||||||
|
{
|
||||||
|
var attr = method.GetCustomAttribute<WebActionAttribute>();
|
||||||
|
// 如果没指定 ActionName,则使用方法名
|
||||||
|
string actionName = (attr?.ActionName ?? method.Name).ToLower();
|
||||||
|
|
||||||
|
if (!_routeTable.TryAdd(actionName, (instance, method)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"重复的 API 路由注册: {actionName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 核心分发逻辑
|
||||||
|
/// </summary>
|
||||||
|
public async Task<(object? Data, string Message)> DispatchActionAsync(string action, JsonElement rawData)
|
||||||
|
{
|
||||||
|
if (!_routeTable.TryGetValue(action.ToLower(), out var route))
|
||||||
|
{
|
||||||
|
return (null, $"未找到对应的业务逻辑: {action}");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var method = route.Method;
|
||||||
|
var instance = route.Instance;
|
||||||
|
var parameters = method.GetParameters();
|
||||||
|
object?[]? invokeArgs = null;
|
||||||
|
|
||||||
|
// 自动参数解析 (支持泛型)
|
||||||
|
if (parameters.Length > 0)
|
||||||
|
{
|
||||||
|
var paramType = parameters[0].ParameterType;
|
||||||
|
|
||||||
|
// 安全检查:如果 rawData 是空的或未定义的,尝试传递 null 或默认值
|
||||||
|
if (rawData.ValueKind == JsonValueKind.Undefined || rawData.ValueKind == JsonValueKind.Null)
|
||||||
|
{
|
||||||
|
invokeArgs = new object?[] { null };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 将原始 JsonElement 转换为方法所需的参数类型
|
||||||
|
var arg = JsonSerializer.Deserialize(rawData.GetRawText(), paramType, _jsonOptions);
|
||||||
|
invokeArgs = new[] { arg };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行方法
|
||||||
|
object? result = method.Invoke(instance, invokeArgs);
|
||||||
|
|
||||||
|
// 处理 Task 返回值
|
||||||
|
if (result is Task task)
|
||||||
|
{
|
||||||
|
await task;
|
||||||
|
// 尝试获取 Task<T> 的结果 (Result 属性)
|
||||||
|
var resultProperty = task.GetType().GetProperty("Result");
|
||||||
|
if (resultProperty != null)
|
||||||
|
{
|
||||||
|
return (resultProperty.GetValue(task), "操作成功");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 普通 Task,没有返回值
|
||||||
|
return (null, "操作成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result, "操作成功");
|
||||||
|
}
|
||||||
|
catch (TargetInvocationException ex)
|
||||||
|
{
|
||||||
|
return (null, $"业务执行异常: {ex.InnerException?.Message ?? ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return (null, $"网关解析异常: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace WebView2Demo.WebInterop.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 标记业务类为 API 控制器
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class WebApiControlAttribute : Attribute { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标记方法为可被前端调用的 Action
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class WebActionAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string? ActionName { get; }
|
||||||
|
public WebActionAttribute(string? actionName = null) => ActionName = actionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WebView2Demo.Models;
|
||||||
|
|
||||||
|
namespace WebView2Demo.WebInterop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 前后端互操作主网关类 [反射分发, 统一回包]
|
||||||
|
/// </summary>
|
||||||
|
[ClassInterface(ClassInterfaceType.None)]
|
||||||
|
[ComVisible(true)]
|
||||||
|
public class MainSystemGateway
|
||||||
|
{
|
||||||
|
private readonly ApiRouteManager _routeManager;
|
||||||
|
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||||
|
{
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
WriteIndented = false
|
||||||
|
};
|
||||||
|
|
||||||
|
public MainSystemGateway()
|
||||||
|
{
|
||||||
|
// 初始化路由管理器
|
||||||
|
_routeManager = new ApiRouteManager(_jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统一 API 调用入口 (前端唯一调用的方法)
|
||||||
|
/// 遵循 COM 规范,接收 string 并返回 string
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string> InvokeAction(string requestJson)
|
||||||
|
{
|
||||||
|
// 这里我们调用内部泛型处理逻辑,T 使用 JsonElement 作为通用基类
|
||||||
|
return await ProcessRequestInternal<JsonElement>(requestJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内部泛型处理逻辑:在这里进行真正的协议解析和分发
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">请求数据的泛型类型</typeparam>
|
||||||
|
private async Task<string> ProcessRequestInternal<T>(string requestJson)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 解析请求信封 (进入泛型世界)
|
||||||
|
var request = JsonSerializer.Deserialize<StandardApiRequest<T>>(requestJson, _jsonOptions);
|
||||||
|
if (request == null || string.IsNullOrWhiteSpace(request.Action))
|
||||||
|
{
|
||||||
|
return MakeErrorResponse("请求 JSON 格式错误或 Action 缺失");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 路由分发 (通过反射查找 Service 和 Method 并执行)
|
||||||
|
// 注意:由于 ApiRouteManager 内部处理了 JsonElement 的动态解析,这里我们传入 Data 即可
|
||||||
|
JsonElement rawData = request.Data is JsonElement element ? element : default;
|
||||||
|
var (data, msg) = await _routeManager.DispatchActionAsync(request.Action, rawData);
|
||||||
|
|
||||||
|
// 3. 根据结果统一回包
|
||||||
|
if (data == null && msg.Contains("未找到"))
|
||||||
|
{
|
||||||
|
return MakeErrorResponse(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeSuccessResponse(data!, msg);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return MakeErrorResponse($"网关内部崩溃: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region [ 响应封装辅助 ]
|
||||||
|
|
||||||
|
private string MakeSuccessResponse(object data, string msg = "操作成功")
|
||||||
|
{
|
||||||
|
var response = new StandardApiResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = msg,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
return JsonSerializer.Serialize(response, _jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MakeErrorResponse(string msg)
|
||||||
|
{
|
||||||
|
var response = new StandardApiResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = msg,
|
||||||
|
Data = null
|
||||||
|
};
|
||||||
|
return JsonSerializer.Serialize(response, _jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using WebView2Demo.Models;
|
||||||
|
using WebView2Demo.WebInterop.Attributes;
|
||||||
|
|
||||||
|
namespace WebView2Demo.WebInterop.Services
|
||||||
|
{
|
||||||
|
[WebApiControl]
|
||||||
|
public class SystemBusinessService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统配置
|
||||||
|
/// </summary>
|
||||||
|
[WebAction("GetSystemInfo")]
|
||||||
|
public object GetInfo()
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
OS = Environment.OSVersion.ToString(),
|
||||||
|
Runtime = "WebView2 .NET 10",
|
||||||
|
IsAdmin = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行原生弹窗
|
||||||
|
/// </summary>
|
||||||
|
[WebAction("ShowDialog")]
|
||||||
|
public bool ShowDialog(string message)
|
||||||
|
{
|
||||||
|
// 确保在 UI 线程执行
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
MessageBox.Show(message, "WPF 业务 Service 弹窗");
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步计算演示
|
||||||
|
/// </summary>
|
||||||
|
[WebAction("Calculate")]
|
||||||
|
public async Task<int> CalculateAsync(CalculationParams p)
|
||||||
|
{
|
||||||
|
// 模拟业务逻辑耗时
|
||||||
|
await Task.Delay(500);
|
||||||
|
return (p?.A ?? 0) + (p?.B ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
-67
@@ -1,84 +1,52 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { useAppLogic } from './AppLogic'
|
||||||
|
|
||||||
const receivedMsg = ref('等待消息...')
|
// 所有的业务逻辑都在 AppLogic.js 中实现
|
||||||
const inputMsg = ref('')
|
const {
|
||||||
|
receivedMsg,
|
||||||
const handleMessage = (event) => {
|
systemInfo,
|
||||||
console.log("收到来自 WPF 的消息:", event.data);
|
calcResult,
|
||||||
receivedMsg.value = event.data
|
fetchSystemInfo,
|
||||||
}
|
triggerNativeAlert,
|
||||||
|
runCalculation
|
||||||
onMounted(() => {
|
} = useAppLogic()
|
||||||
if (window.chrome && window.chrome.webview) {
|
|
||||||
window.chrome.webview.addEventListener('message', handleMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (window.chrome && window.chrome.webview) {
|
|
||||||
window.chrome.webview.removeEventListener('message', handleMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const sendMessageToWPF = () => {
|
|
||||||
if (window.chrome && window.chrome.webview) {
|
|
||||||
window.chrome.webview.postMessage(inputMsg.value)
|
|
||||||
} else {
|
|
||||||
alert('未在 WebView2 环境中运行')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Vue 3 + WebView2 交互演示</h1>
|
<h1>WebView2 标准化交互网关</h1>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>接收来自 WPF 的消息:</h3>
|
<h3>1. 属性同步 (WebMessage)</h3>
|
||||||
<p class="msg-box">{{ receivedMsg }}</p>
|
<p>来自 WPF 的推送: {{ receivedMsg }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>发送消息给 WPF:</h3>
|
<h3>2. 动态 API 调用 (Host Objects + 信封)</h3>
|
||||||
<input v-model="inputMsg" placeholder="输入消息" />
|
<div class="btn-group">
|
||||||
<button @click="sendMessageToWPF">发送到 WPF</button>
|
<button @click="fetchSystemInfo">获取 C# 系统信息</button>
|
||||||
|
<button @click="triggerNativeAlert" class="btn-warning">触发原生弹窗</button>
|
||||||
|
<button @click="runCalculation" class="btn-info">计算 10 + 20</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="systemInfo" class="result-box">
|
||||||
|
<b>系统信息:</b> {{ systemInfo.OS }} ({{ systemInfo.Runtime }})
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="calcResult !== null" class="result-box">
|
||||||
|
<b>计算结果:</b> {{ calcResult }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.container { padding: 20px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
|
||||||
font-family: Arial, sans-serif;
|
.card { border: 1px solid #eee; padding: 15px; margin-bottom: 15px; border-radius: 8px; background: #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
||||||
padding: 20px;
|
.btn-group { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
|
||||||
text-align: center;
|
button { padding: 8px 16px; border: none; border-radius: 4px; background: #42b883; color: white; cursor: pointer; }
|
||||||
}
|
button:hover { background: #3aa876; }
|
||||||
.card {
|
.btn-warning { background: #f39c12; }
|
||||||
border: 1px solid #ddd;
|
.btn-info { background: #3498db; }
|
||||||
border-radius: 8px;
|
.result-box { margin-top: 15px; padding: 10px; background: #f8f9fa; border-left: 4px solid #42b883; font-size: 0.9em; }
|
||||||
padding: 15px;
|
|
||||||
margin: 10px 0;
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
.msg-box {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #2c3e50;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
padding: 8px;
|
|
||||||
width: 200px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding: 8px 15px;
|
|
||||||
background-color: #42b883;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: #3aa876;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { InteropHelper } from './utils/interop'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App 页面的业务逻辑 Hook
|
||||||
|
*/
|
||||||
|
export function useAppLogic() {
|
||||||
|
const receivedMsg = ref('等待消息...')
|
||||||
|
const systemInfo = ref(null)
|
||||||
|
const calcResult = ref(null)
|
||||||
|
|
||||||
|
// 1. 获取系统信息 (演示带返回值的标准化调用)
|
||||||
|
const fetchSystemInfo = async () => {
|
||||||
|
try {
|
||||||
|
const data = await InteropHelper.callApi('GetSystemInfo')
|
||||||
|
systemInfo.value = data
|
||||||
|
} catch (err) {
|
||||||
|
alert('获取失败: ' + err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 发送弹窗指令 (演示带参数的标准化调用)
|
||||||
|
const triggerNativeAlert = async () => {
|
||||||
|
try {
|
||||||
|
await InteropHelper.callApi('ShowDialog', '这是来自 Vue 的标准化信封消息!')
|
||||||
|
} catch (err) {
|
||||||
|
console.error('弹窗指令发送失败:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 泛型计算演示 (演示复杂对象参数)
|
||||||
|
const runCalculation = async () => {
|
||||||
|
try {
|
||||||
|
const result = await InteropHelper.callApi('Calculate', { a: 10, b: 20 })
|
||||||
|
calcResult.value = result
|
||||||
|
} catch (err) {
|
||||||
|
console.error('计算失败:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有的 WebMessage 接收逻辑
|
||||||
|
const handleMessage = (event) => {
|
||||||
|
receivedMsg.value = event.data
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (window.chrome && window.chrome.webview) {
|
||||||
|
window.chrome.webview.addEventListener('message', handleMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
receivedMsg,
|
||||||
|
systemInfo,
|
||||||
|
calcResult,
|
||||||
|
fetchSystemInfo,
|
||||||
|
triggerNativeAlert,
|
||||||
|
runCalculation
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* 前后端互操作工具类
|
||||||
|
*/
|
||||||
|
export class InteropHelper {
|
||||||
|
/**
|
||||||
|
* 调用原生 API (通过 StandardApiRequest/Response 协议)
|
||||||
|
* @param {string} action 行为名称
|
||||||
|
* @param {any} data 业务数据
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
static async callApi(action, data = null) {
|
||||||
|
if (!window.chrome?.webview?.hostObjects?.gateway) {
|
||||||
|
const errorMsg = 'WebView2 环境未就绪 (hostObjects.gateway 缺失)';
|
||||||
|
console.error(errorMsg);
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gateway = window.chrome.webview.hostObjects.gateway;
|
||||||
|
|
||||||
|
// 1. 封装标准信封
|
||||||
|
const requestEnvelope = {
|
||||||
|
action: action,
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2. 调用 C# 网关方法 (使用新的方法名 InvokeAction)
|
||||||
|
const responseJson = await gateway.InvokeAction(JSON.stringify(requestEnvelope));
|
||||||
|
console.log(`API [${action}] 原始响应:`, responseJson);
|
||||||
|
|
||||||
|
// 3. 解析标准回包
|
||||||
|
const response = JSON.parse(responseJson);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
const errorMsg = response.message || '未知错误';
|
||||||
|
console.error(`API [${action}] 返回失败:`, errorMsg);
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`API 调用崩溃 [${action}]:`, err);
|
||||||
|
// 如果是在控制台看到的报错,通常是因为 C# 端发生了未捕获异常
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user