wails v3 —— 用 Go 和 Vue 做桌面应用
这是一份面向新手的 Wails v3 Alpha 实操笔记,梳理安装、项目结构、Go 与 Vue 通信、多窗口、菜单、文件对话框和系统托盘等核心能力。

wails v3 —— 用 Go 和 Vue 做桌面应用

发布时间:2026-04-28 (1分钟前)

Wails v3 目前仍是 Alpha,API 比 v2 新,也可能继续调整。学习时以 wails3 doctor 和当前安装版本为准。

当前验证环境:Windows,Wails CLI v3.0.0-alpha.78,项目创建时间 2026-04-28

Wails 是什么

可以先把 Wails 理解成:

  • 用 Go 写桌面端能力,比如窗口、菜单、文件选择、系统托盘、读写本地文件。
  • 用 Vue / React / Svelte 等前端技术写界面。
  • Wails 把 Go 方法生成成 JS/TS 绑定,前端可以像调用普通异步函数一样调用 Go。
  • 打包时不会内置一个完整 Chrome,而是使用系统自带 WebView,所以体积通常比 Electron 小。

桌面应用里常说的“前端”和“后端”容易让新手误解。Wails 项目里的 Go 和 Vue 都跑在桌面应用内部,不是传统网站那种远程服务器后端。

v2 和 v3 的核心区别

与Wails v2 相比,v3 最值得先记住这些变化:

  • 命令从 wails 变成 wails3
  • Go 包从 github.com/wailsapp/wails/v2/... 变成 github.com/wailsapp/wails/v3/pkg/application
  • 应用入口从 wails.Run(&options.App{}) 变成 application.New(application.Options{})app.Run()
  • 绑定方法不再写 Bind: []interface{}{},而是 Services: []application.Service{ application.NewService(...) }
  • 前端调用 Go 的代码从 wailsjs/go/... 变成 frontend/bindings/...
  • v3 原生支持多窗口。
  • v3 原生集成菜单、系统托盘、对话框等能力,整体 API 更偏 app.Windowapp.Menuapp.Dialog 这种管理器风格。

一句话:v2 像“配置一个 App 然后运行”,v3 像“先创建 app 对象,再用 app 管理窗口、菜单、事件、对话框”。

安装和检查环境

官方安装命令:

go install github.com/wailsapp/wails/v3/cmd/wails3@latest

检查是否安装成功:

wails3 version
wails3 doctor

如果 PowerShell 提示找不到 wails3,通常是 Go 的 bin 目录没有加入环境变量:

$env:USERPROFILE\go\bin

把这个目录加入用户 Path 后,重启终端。

本机安装时,latest 拉到的是 v3.0.0-alpha.78,并且 Go 自动切换到了 go1.25.9 工具链。Wails v3 安装页当前写 Go 1.25+,所以建议直接安装新版本 Go。

创建项目

查看模板:

wails3 init -l

创建 Vue + TypeScript 项目:

wails3 init -n wails3_beginner_demo -t vue-ts

运行开发模式:

cd wails3_beginner_demo
wails3 dev

构建 exe:

wails3 build

示例项目结构

重点先看这些文件:

wails3_beginner_demo/
├── main.go                         # Go 程序入口,创建 app、窗口、菜单、事件
├── greetservice.go                 # 暴露给前端调用的 Go 服务
├── frontend/
│   ├── src/App.vue                 # Vue 主页面
│   ├── public/style.css            # 全局样式
│   ├── bindings/                   # wails3 自动生成的前端调用 Go 的代码
│   └── package.json
├── build/config.yml                # Wails 构建配置
├── Taskfile.yml                    # wails3 dev/build 背后的任务
└── go.mod

frontend/bindings 不要手写。你修改 Go 服务方法后,运行:

wails3 generate bindings

或者直接运行:

wails3 dev

Wails 通常会自动重新生成绑定。

Go 入口怎么写

示例入口在 wails3_beginner_demo/main.go

核心流程是:

demoService := &DemoService{}

app := application.New(application.Options{
    Name: "wails3_beginner_demo",
    Services: []application.Service{
        application.NewService(demoService),
    },
    Assets: application.AssetOptions{
        Handler: application.AssetFileServerFS(assets),
    },
})

demoService.setApp(app)

app.Window.NewWithOptions(application.WebviewWindowOptions{
    Title: "Wails v3 入门示例",
    Width: 1000,
    Height: 720,
    URL: "/",
})

app.Run()

新手先记住 4 件事:

  • application.New 创建应用。
  • Services 里注册 Go 服务,前端才能调用。
  • app.Window.NewWithOptions 创建窗口。
  • app.Run() 启动应用,并阻塞到应用退出。

前端调用 Go

Go 服务在 greetservice.go

type DemoService struct {
    app *application.App
}

func (s *DemoService) Greet(name string) string {
    if name == "" {
        name = "小白同学"
    }
    return fmt.Sprintf("你好,%s!这句话来自 Go。", name)
}

func (s *DemoService) Add(a int, b int) int {
    return a + b
}

前端在 frontend/src/App.vue 调用:

import { DemoService } from '../bindings/changeme'

const result = await DemoService.Greet(name.value)
const total = await DemoService.Add(1, 2)

注意:前端调用 Go 方法是异步的,所以要用 await.then()

Go 主动通知前端

适合这些场景:

  • Go 后台任务完成了,通知界面刷新。
  • 监听系统事件后,把结果推给界面。
  • 定时推送时间、状态、进度。

Go 里先注册事件类型:

application.RegisterEvent[string]("go-message")

Go 里发事件:

s.app.Event.Emit("go-message", "Go 主动发来一条事件消息")

Vue 里监听事件:

import { Events } from '@wailsio/runtime'

Events.On('go-message', (event: { data: string }) => {
  message.value = event.data
})

选择规则:

  • 前端需要一个明确返回值:用绑定方法。
  • Go 要主动推送状态:用事件。

文件选择对话框

示例里 PickTextFile 会打开系统文件选择框,并读取前 800 个字符:

path, err := s.app.Dialog.OpenFile().
    SetTitle("选择一个文本文件").
    AddFilter("Text Files", "*.txt;*.md;*.log").
    AddFilter("All Files", "*.*").
    PromptForSingleSelection()

这个能力来自 app.Dialog。新手不用自己写 Windows API,Wails 会调用系统原生对话框。

菜单

示例里创建了菜单:

menu := app.Menu.New()

fileMenu := menu.AddSubmenu("文件")
fileMenu.Add("打开文本文件").SetAccelerator("Ctrl+O").OnClick(func(ctx *application.Context) {
    demoService.PickTextFile()
})

fileMenu.Add("退出").SetAccelerator("Ctrl+Q").OnClick(func(ctx *application.Context) {
    app.Quit()
})

app.Menu.Set(menu)

窗口要显示应用菜单,建议加:

UseApplicationMenu: true

菜单项可以直接调用 Go 方法,也可以发事件给前端。

多窗口

v3 的一个重要升级就是原生多窗口。

示例里创建了主窗口和设置窗口:

app.Window.NewWithOptions(application.WebviewWindowOptions{
    Title: "Wails v3 入门示例",
    URL: "/",
})

settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
    Title:  "设置窗口",
    Width:  520,
    Height: 360,
    Hidden: true,
    URL:    "/?window=settings",
})

显示设置窗口:

settingsWindow.Show()
settingsWindow.Focus()

设置窗口点右上角关闭时,默认会销毁窗口。销毁后再 Show() 旧窗口对象就不可靠,所以示例里用 RegisterHook 拦截关闭事件,把“关闭”改成“隐藏”:

settingsWindow.RegisterHook(events.Common.WindowClosing, func(event *application.WindowEvent) {
    event.Cancel()
    settingsWindow.Hide()
})

以后再点“设置窗口”,其实是把这个隐藏的窗口重新显示出来。

在 Vue 里通过 URL 参数区分当前窗口显示什么:

const isSettingsWindow = computed(() =>
  new URLSearchParams(window.location.search).get('window') === 'settings'
)

这只是入门写法。真实项目里可以给不同窗口加载不同路由或不同页面。

系统托盘

Wails v3 内置系统托盘,这是 v3 相比 v2 很亮眼的升级点。v2 里通常要自己找第三方库,比如 systray,还要处理它和 Wails 生命周期的配合;v3 可以直接用 app.SystemTray

示例里已经加了托盘功能:

  • 单击托盘图标:显示 / 隐藏主窗口。
  • 右键托盘图标:打开托盘菜单。
  • 托盘菜单支持:显示主窗口、隐藏到托盘、打开设置窗口、发事件给前端、退出应用。
  • 前端页面里也有“隐藏到托盘”按钮。

Go 里嵌入托盘图标:

//go:embed build/windows/icon.ico
var trayIcon []byte

创建托盘菜单:

trayMenu := app.Menu.New()

trayMenu.Add("显示主窗口").OnClick(func(ctx *application.Context) {
    mainWindow.Show().Focus()
})

trayMenu.Add("隐藏到托盘").OnClick(func(ctx *application.Context) {
    demoService.HideMainWindowToTray()
})

trayMenu.Add("设置窗口").OnClick(func(ctx *application.Context) {
    demoService.ShowSettingsWindow()
})

trayMenu.AddSeparator()

trayMenu.Add("退出").OnClick(func(ctx *application.Context) {
    app.Quit()
})

创建系统托盘:

tray := app.SystemTray.New()
tray.SetIcon(trayIcon).
    SetMenu(trayMenu).
    AttachWindow(mainWindow)

tray.SetTooltip("Wails v3 入门示例:单击显示/隐藏,右键打开菜单")

这里最重要的是 AttachWindow(mainWindow)。绑定窗口后,Wails 会给托盘一个默认行为:点击托盘图标时切换窗口显示和隐藏。如果设置了菜单,右键会显示菜单。

隐藏主窗口的方法在 greetservice.go

func (s *DemoService) HideMainWindowToTray() {
    if s.mainWindow == nil {
        return
    }
    s.mainWindow.Hide()
    s.app.Event.Emit("go-message", "主窗口已隐藏,可以从系统托盘图标恢复")
}

前端调用:

function hideToTray() {
  DemoService.HideMainWindowToTray()
}

需要注意:托盘不是“关闭窗口”。如果你点窗口右上角关闭,应用可能会退出;如果你想做微信、QQ 那种“点关闭只是隐藏到托盘”,还需要额外监听窗口关闭事件并拦截关闭行为。这个示例先演示最核心的托盘 API。

开发时的常用命令

在示例项目目录运行:

cd wails3_beginner_demo

安装前端依赖:

cd frontend
npm install
cd ..

生成绑定:

wails3 generate bindings

开发运行:

wails3 dev

构建:

wails3 build

只检查 Go 是否能编译:

go test ./...

只检查前端:

cd frontend
npm run build

小白学习路线

建议按这个顺序学:

  1. 先跑起来:wails3 dev
  2. App.vue 上的一段文字,看热更新是否生效。
  3. DemoService.Greet 的返回值,看前端调用 Go 的结果变化。
  4. 新增一个 Go 方法,比如 Multiply(a, b int) int
  5. 运行 wails3 generate bindings
  6. 在 Vue 里调用 DemoService.Multiply
  7. 学事件:让 Go 每隔几秒发一个消息给前端。
  8. 学系统能力:文件选择、保存文件、菜单、系统托盘、窗口。
  9. 最后再做复杂功能:SQLite、本地配置、关闭到托盘、自动更新。

不要一开始就上 Pinia、路由、UI 组件库、SQLite。先把“Vue 调 Go”和“Go 通知 Vue”这两条线打通。

常见坑

wails3 找不到

Go bin 没进 Path。Windows 常见路径:

C:\Users\你的用户名\go\bin

修改 Go 方法后前端找不到

重新生成绑定:

wails3 generate bindings

然后重启前端类型检查或 wails3 dev

frontend/dist 不存在

Go 里有:

//go:embed all:frontend/dist
var assets embed.FS

所以直接 go test ./...go build 前,要先让前端构建出 dist

cd frontend
npm run build
cd ..
go test ./...

或者直接用:

wails3 build

文档和当前版本 API 不一致

v3 仍是 Alpha,确实会遇到。处理方式:

  • 先看本机 wails3 version
  • 再看 go/pkg/mod/github.com/wailsapp/wails/v3@版本号/pkg/application 里的源码。
  • wails3 generate bindingswails3 build 的实际结果为准。

参考文档