feat(desktop): 迁移 Wails v2 → v3(架构/目录/绑定重写)[WIP]

从 wails/v2 v2.12.0 迁到 v3 v3.0.0-alpha2.117,采用 v3 工程规范:

Go 侧:
- main.go:wails.Run(options.App) → application.New(Options{Services,Assets,Mac})
  + app.Window.NewWithOptions(WebviewWindowOptions) + app.Run();窗口选项照搬
  (1440×900/min 1100×700/#0b0d12/Mac TitleBarHiddenInset)
- app.go:App 结构体绑定 → v3 Service(application.NewService);去 startup/ctx 注入;
  SaveFileDialog 改 v3 链式 API(application.Get().Dialog.SaveFile().SetFilename().
  AddFilter().PromptForSingleSelection());5 个原生方法保留
- go.mod wails/v2→v3、go 1.25

构建:
- wails.json → Taskfile.yml + build/(config.yml 填产品信息 + 各平台配置)
- 删移动端脚手架(ios/android),桌面端只留 darwin/windows/linux
- .gitignore 改为只忽略 build/bin(build/ 构建系统需提交)

前端:
- vite.config 接 @wailsio/runtime/plugins/vite 插件 + WAILS_VITE_PORT
- package.json 加 @wailsio/runtime;wails3 generate bindings → frontend/bindings/
- desktop.ts:window.go.main.App → import { App } from bindings;桌面态检测改
  window._wails.environment(原生注入);isMacDesktop 用 System.IsMac()
- version.ts:runtime.BrowserOpenURL → @wailsio/runtime Browser.OpenURL;测试同步更新
- tsconfig 加 allowJs(消费 JSDoc 绑定)

验证进度:Go 编译过 + tsc 干净 + 48 前端测试绿 + 前端 vite build 通过。
**未完成:整体 wails3 build/dev 原生窗口实跑 + 原生能力(另存为/打开/外链)联调**——下次续。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-13 18:00:45 +08:00
parent 8f1aefe13e
commit 1f487c3163
46 changed files with 2430 additions and 175 deletions
+14 -20
View File
@@ -1,50 +1,44 @@
// 桌面端原生能力桥 —— Wails 注入的 window.go.main.App 调 Go 方法。
// 关键:在浏览器预览(make web无 Wails 运行时)下 window.go 不存在,
// 所有方法自动降级为纯 Web 行为,保证两种模式都能跑
// 桌面端原生能力桥 —— Wails v3 Service 绑定(App调 Go 方法。
// 关键:在浏览器预览(无 Wails 运行时)下自动降级为纯 Web 行为,保证两种模式都能跑。
// v3 检测:原生启动时注入 window._wails.environment;浏览器没有,据此判定是否在桌面壳内
import { App } from "../../bindings/github.com/sundynix/sundynix-desktop";
import { System } from "@wailsio/runtime";
interface WailsApp {
SaveReportAs(url: string, filename: string): Promise<string>;
OpenReport(url: string, filename: string): Promise<void>;
Notify(title: string, body: string): Promise<void>;
}
function app(): WailsApp | null {
return ((window as unknown as { go?: { main?: { App?: WailsApp } } }).go?.main?.App as WailsApp) ?? null;
function inWails(): boolean {
return !!(window as unknown as { _wails?: { environment?: unknown } })._wails?.environment;
}
// isDesktop 是否运行在真实 Wails 桌面窗口(而非浏览器预览)。
export function isDesktop(): boolean {
return !!app();
return inWails();
}
// isMacDesktop 用于交通灯让位等 macOS 专属适配。
export function isMacDesktop(): boolean {
return isDesktop() && /Mac/i.test(navigator.userAgent);
return inWails() && System.IsMac();
}
// saveReportAs:桌面端弹原生"另存为"框落盘;浏览器降级为触发下载。返回保存路径(浏览器/取消为空)。
export async function saveReportAs(url: string, filename: string): Promise<string> {
const a = app();
if (!a) {
if (!inWails()) {
triggerDownload(url, filename);
return "";
}
return a.SaveReportAs(url, filename);
return App.SaveReportAs(url, filename);
}
// openReport:桌面端下载到临时目录并用系统默认应用打开;浏览器降级为新标签打开。
export async function openReport(url: string, filename: string): Promise<void> {
const a = app();
if (!a) {
if (!inWails()) {
window.open(url, "_blank");
return;
}
return a.OpenReport(url, filename);
await App.OpenReport(url, filename);
}
// notify:桌面端弹系统通知;浏览器为空操作(由应用内 Toast 兜底)。
export function notify(title: string, body: string): void {
app()?.Notify(title, body);
if (inWails()) void App.Notify(title, body);
}
// printReportHtml:把已渲染的报告 HTML 在打印视图里出 PDF(浏览器/Webview 的"打印→存为 PDF")。
@@ -1,6 +1,10 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { Browser } from "@wailsio/runtime";
import { isNewer, checkUpdate, openExternal, APP_VERSION } from "./version";
// mock v3 runtime 的 Browser.OpenURLopenExternal 桌面态走它)。
vi.mock("@wailsio/runtime", () => ({ Browser: { OpenURL: vi.fn() } }));
describe("isNewer(语义化版本比较)", () => {
it.each([
["1.0.1", "1.0.0", true],
@@ -60,20 +64,17 @@ describe("checkUpdate", () => {
describe("openExternal", () => {
afterEach(() => {
delete (window as unknown as { runtime?: unknown }).runtime;
vi.restoreAllMocks();
delete (window as unknown as { _wails?: unknown })._wails;
vi.clearAllMocks();
});
it("有 Wails runtime → 走 BrowserOpenURL", () => {
const spy = vi.fn();
(window as unknown as { runtime: { BrowserOpenURL: (u: string) => void } }).runtime = {
BrowserOpenURL: spy,
};
it("桌面态(_wails.environment 存在)→ 走 Browser.OpenURL", () => {
(window as unknown as { _wails: { environment: { OS: string } } })._wails = { environment: { OS: "darwin" } };
openExternal("https://a");
expect(spy).toHaveBeenCalledWith("https://a");
expect(Browser.OpenURL).toHaveBeenCalledWith("https://a");
});
it("无 runtime浏览器模式)→ 回退 window.open", () => {
it("浏览器模式(无 _wails.environment)→ 回退 window.open", () => {
const open = vi.spyOn(window, "open").mockImplementation(() => null);
openExternal("https://b");
expect(open).toHaveBeenCalledWith("https://b", "_blank");
+4 -4
View File
@@ -1,5 +1,6 @@
// 桌面端版本与"检查更新"——查 GitHub Releases 最新版,比当前版本新则提示下载。
// 发版流程:bump 此处 APP_VERSION= package.json version)→ 打 tag vX.Y.Z → release workflow 自动构建+发布。
import { Browser } from "@wailsio/runtime";
export const APP_VERSION = "0.1.0"; // 当前桌面端版本(与 git tag 去掉 v 前缀对齐)
@@ -27,11 +28,10 @@ export async function checkUpdate(): Promise<ReleaseInfo | null> {
}
}
// openExternal 在系统浏览器打开链接(Wails 用 runtime.BrowserOpenURL,浏览器模式回退 window.open)。
// openExternal 在系统浏览器打开链接(Wails v3 用 Browser.OpenURL,浏览器模式回退 window.open)。
export function openExternal(url: string): void {
const w = window as unknown as { runtime?: { BrowserOpenURL?: (u: string) => void } };
if (w.runtime?.BrowserOpenURL) {
w.runtime.BrowserOpenURL(url);
if ((window as unknown as { _wails?: { environment?: unknown } })._wails?.environment) {
void Browser.OpenURL(url);
} else {
window.open(url, "_blank");
}