很多刚接触 Go 的朋友都会有这样的困惑:
“Go 这么强大,但为什么没有官方的桌面 GUI 方案?”
“我想做个桌面工具,要选什么框架?”
确实,Go 官方从未把桌面开发当作核心路线,也完全没有提供 GUI 标准库。这就导致 Go 想做客户端应用时,往往不得不依赖第三方解决方案。
而这些方案,要么太臃肿,要么太硬核:
- Electron:开发简单,但打包出来一个上百 MB 的应用,谁用谁心痛
- Qt / GTK:跨平台强大,但学习成本极高,绑定也不够自然
- 传统 WebView 框架:能跑,但生态差、性能堪忧
直到近几年,Go 生态真正出现了两位“能打”的选手:
- Fyne —— 类 Flutter 的“纯 Go UI 框架”,想写纯后端式桌面开发,它很适合,团队没有前端的优选方案;
- Wails —— 类 Tauri / Electron 的模式,用 HTML + JS + CSS 做界面,用 Go 做逻辑,前后端分离,找专业的人做专业的事,后端人员再不用因为界面丑而被客户嫌弃了。
更重要的是:
谁说桌面应用就不能前后端分离?
谁说 Go 只能做后端?
Wails 2 把这个多年的困境彻底解决了。
它不仅能把 Vue / React / Svelte 等现代前端框架无缝嵌入桌面窗口,还提供了优雅的 Go ↔ JS 通信机制,让你可以:
- 用 Vue3 写界面
- 用 Go 写业务逻辑
- 像开发 Web 应用一样开发桌面应用
- 最终产物比 Electron 轻几十倍
这让 Go + Wails 成为当下最具潜力的桌面应用组合之一。
Wails 2 出现,将 Go 与前端技术栈(Vue/React/Svelte)完美串联——让你用:
- Go 写逻辑和系统 API
- Vue3 写界面、动画、UI 交互
- 最终产出一个 原生应用(Windows/Mac/Linux)
- 体积只有 10–20MB
- 启动速度不足 80ms
本文以示例项目:
为基础,通过大量真实源码解析,从入门安装到事件驱动架构,手把手带你构建一个 可复用、可扩展的桌面 GUI 框架。
这篇文章特别适合:
- 想用 Go 开发桌面应用
- 想构建工具类客户端(下载器、日志工具、运维面板)
- 想将 Vue3 应用迁移到桌面端
- 想建立属于自己的桌面 UI 框架
准备好,我们开始吧。
🟦 第 1 部分:安装和创建Wails项目(官方正确方式)
官网地址:wails.io/zh-Hans/ ,具体的安装要求不高,不同的系统安装方法也很简单。
go install github.com/wailsapp/wails/v2/cmd/wails@latest
// 安装后检测一下系统环境是否满足要求
wails doctor
Wails 支持多种前端模板(Vue3 / React 等),找自己适合的创建 Vue3 + Vite 项目:
wails init -n MyApp -t vue
目录格式类似:
MyApp/
├── frontend/
├── build/
└── wails.json
└── app.go
└── main.go
进入开发模式(竟然还支持watch,太爽了):
wails dev
生产构建:
wails build
🟥 第 2 部分:wails.json 配置详解(结合你的项目)
“可配置,才自由”,任何框架约束越多越没有朋友。
你仓库中的配置如下(截取几个自定义的核心字段):
{
"name": "wails-vue3-tmp",
"author": "louis-xie-programmer",
"assetdir": "",
"frontend": {
"dir": "frontend",
"install": "yarn install",
"build": "yarn build"
},
"devServerUrl": ""
}
🧩 assetdir: 前端资源文件路径,前端项目往往不由我。🧩 frontend:指定前端目录与构建命令;很多人喜欢npm, 但我喜欢yarn,还有人喜欢pnpm呢,习惯往往很难改变,有了它,我再也不用愁了,随便改,哪怕是是再命令行里定义环境变量呢,随你便,这才是真正的自由。🧩 devServerUrl这个更让自由飞了起来,默认为空,表示前端使用本地嵌入式调试服务器(Wails 自带)。如果你想让 Wails 使用你的 Vite 服务器就行。
🔄 第 3 部分:Go 后端代码详解(基于你的 backend/app.go)你的 backend/app.go 是整个后端的核心:
type App struct {
ctx context.Context
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
Wails 会自动调用:
startupbeforeCloseshutdown
让你在应用生命周期各阶段执行逻辑。
✔️ 你暴露的 RPC 示例:Greet
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s", name)
}
前端将自动生成 TS 类型:
import { Greet } from "@/wailsjs/go/main/App";
const msg = await Greet("张三");
console.log(msg);
你无须写 HTTP、WebSocket,就是 RPC 调用。
✔️ 你自定义的事件推送方法:Emit
你自己封装了一个 Go → 前端的推送:
func (a *App) PushEvent(name string, payload interface{}) {
if a.ctx == nil {
fmt.Println("Warning: Context is nil, cannot emit event '" + name + "'")
return
}
runtime.EventsEmit(a.ctx, name, payload) // 使用存储的上下文发送事件
}
后端可以像这样触发前端更新:
a.Emit("task_progress", i)
✔️ 后台任务示例
func (a *App) StartTestTimer() {
ticker := time.NewTicker(2 * time.Second)
go func() {
for {
select {
case <-ticker.C:
a.PushEvent("task_progress", rand.Intn(100))
a.PushEvent("log", fmt.Sprintf("Log entry at %v", time.Now().Format("15:04:05")))
a.PushEvent("notification", fmt.Sprintf("Notification received! (%d)", rand.Int31n(1000)))
}
}
}()
}
这是桌面应用最常见的:
- 文件处理
- 批量任务
- 后台执行
- 数据同步
也是 Wails 的优势,因为:
Go 的并发 + 前端事件响应 = 完美适配桌面应用。
🟦 第 4 部分:前端事件系统
你在 frontend/src/utils/eventBus.ts 封装了前端事件系统。
/**
* 定义事件回调函数类型
* 接收任意类型的 payload 数据
*/
type EventCallback = (payload: any) => void;
class EventBus {
// 存储所有事件监听器的映射表,键为事件名,值为回调函数数组
private listeners: Record<string, EventCallback[]> = {};
/**
* 注册事件监听器
* 同时尝试绑定到 Wails 运行时事件系统(如果可用)
* @param event 事件名称
* @param callback 回调函数
*/
on(event: string, callback: EventCallback) {
if (!this.listeners[event]) this.listeners[event] = [];
this.listeners[event].push(callback);
// 如果 Wails runtime 可用,则同时注册原生事件监听
if ((window as any).runtime?.EventsOn) {
(window as any).runtime.EventsOn(event, callback);
}
}
/**
* 移除事件监听器
* @param event 事件名称
* @param callback 要移除的回调函数
*/
off(event: string, callback: EventCallback) {
if (!this.listeners[event]) return;
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
}
/**
* 触发事件,通知所有监听者
* @param event 事件名称
* @param payload 携带的数据
*/
emit(event: string, payload: any) {
if (this.listeners[event]) {
this.listeners[event].forEach(cb => cb(payload));
}
}
}
// 导出单例事件总线实例,用于整个应用的事件通信
export const eventBus = new EventBus();
你实现了:
- 前端订阅 Go 事件
- 前端 emit 到 Go
- 同事件多监听器
- 自动分发数据
比 Wails 原生事件 API 更好用。
🟩 第 5 部分:Pinia 状态管理
你在 store/event.ts 中写了一个全局事件状态:
/**
* 定义事件存储(Event Store)
* 使用 Pinia 创建一个名为 'event' 的状态存储
* 包含任务进度、日志和通知等实时数据
*/
export const useEventStore = defineStore('event', {
state: () => ({
taskProgress: 0, // 当前任务完成进度百分比
logs: [] as string[], // 存储日志消息的数组
notifications: [] as string[] // 存储通知消息的数组
}),
actions: {
/**
* 初始化事件监听器
* 监听通过 eventBus 发送的各类事件,并更新状态
*/
initEventListeners() {
// 监听 'task_progress' 事件,更新任务进度
eventBus.on('task_progress', (progress: number) => { this.taskProgress = progress; });
// 监听 'log' 事件,将新日志添加到日志列表
eventBus.on('log', (message: string) => { this.logs.push(message); });
// 监听 'notification' 事件,将新通知添加到通知列表
eventBus.on('notification', (msg: string) => { this.notifications.push(msg); });
},
/**
* 清空所有日志
*/
clearLogs() { this.logs = []; },
/**
* 清空所有通知
*/
clearNotifications() { this.notifications = []; }
}
});
🟥 第 6 部分:在你的项目中如何构建 UI(Vue3)
由于朋友们大部分都是Go后端开发,vue3可能不熟悉,所以这里还是讲一下。
- components 目录是自定义组件:
- GreetBox.vue TaskProgress.vue;
- views 目录下面是页面文件;
- 而 router.ts 是路由文件;
App.vue 文件修改:
<script lang="ts" setup>
</script>
<template>
//修改走路由指定
<router-view />
</template>
<style>
</style>
Wails + Vue3 是 Go 桌面应用最强组合
Wails 让 Go 开发者第一次拥有:
- 现代 UI(Vue3)
- 高性能后端(Go)
- 轻量产物(<20MB)
- 跨平台支持
- 事件驱动架构
- 响应式 UI
而你这个 wails-vue3-tmp 项目是:
一个真正“可复用、可扩展、可维护”的桌面客户端开发基座。
放到企业内部完全可以成为主力模板。喜欢它的自由和纯粹。
Github 源码:
https://github.com/louis-xie-programmer/wails-vue3-tmp
Gitee 源码: