feat: 初始化 Wails + React + Go 桌面应用模板

- Wails v2.16.0 + React 19 + TypeScript + Vite 7
- 插件系统(build tag 控制编译,plugin.json 运行时配置)
- 支付插件(授权、账号、订单、退款、账单)
- 高并发基础设施(任务池、批量处理器、限流器、环形缓冲、管道)
- Windows 系统托盘(Win32 API)
- CodeGraph 索引 + .memory 记忆包
- 默认图标 assets/icon.png
This commit is contained in:
2026-09-17 19:31:17 +08:00
commit 1ea8c9da9c
57 changed files with 6305 additions and 0 deletions

167
internal/tray/win/tray.go Normal file
View File

@@ -0,0 +1,167 @@
// Package tray Windows 系统托盘实现(使用 win32 API)
package tray
import (
"log"
"sync"
"unsafe"
"golang.org/x/sys/windows"
)
// Hook 托盘图标句柄
var hook windows.Handle
// MenuHandle 菜单句柄
type MenuHandle uintptr
// Tray 系统托盘管理器
type Tray struct {
mu sync.Mutex
visible bool
callback uintptr // WNDPROC 回调
hWnd windows.Handle
}
// MenuItem 菜单项
type MenuItem struct {
ID uint32
Text string
}
// NewTray 创建系统托盘
func NewTray(iconData []byte, tooltip string) *Tray {
t := &Tray{}
if err := t.init(iconData, tooltip); err != nil {
log.Printf("[tray] init failed: %v", err)
return nil
}
return t
}
func (t *Tray) init(iconData []byte, tooltip string) error {
// 获取当前进程窗口
hWnd, _ := windows.GetForegroundWindow()
if hWnd == 0 {
return windows.ERROR_ACCESS_DENIED
}
t.hWnd = hWnd
// 注册托盘图标
// NOTIFYICONDATA structure
// https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa
data := &notifyIconData{
CbSize: uint32(unsafe.Sizeof(notifyIconData{})),
HWnd: uint32(hWnd),
UID: 1000,
UFlags: NIF_MESSAGE | NIF_ICON | NIF_TIP,
UCallbackMessage: WM_USER + 1000,
HIcon: 0, // 使用默认图标
}
// 设置 tooltip
for i := 0; i < len(tooltip) && i < 128; i++ {
data.Tip[i] = uint8(tooltip[i])
}
data.Tip[len(tooltip)] = 0
// 添加托盘图标
ret, _, _ := procShellNotifyIcon.Call(
SHM_ADD,
uintptr(unsafe.Pointer(data)),
)
if ret == 0 {
return windows.GetLastError()
}
t.visible = true
return nil
}
// Destroy 销毁托盘图标
func (t *Tray) Destroy() {
t.mu.Lock()
defer t.mu.Unlock()
if !t.visible {
return
}
data := &notifyIconData{
CbSize: uint32(unsafe.Sizeof(notifyIconData{})),
HWnd: uint32(t.hWnd),
UID: 1000,
UFlags: NIF_MESSAGE,
}
procShellNotifyIcon.Call(SHM_DELETE, uintptr(unsafe.Pointer(data)))
t.visible = false
}
// Show 显示托盘
func (t *Tray) Show(iconData []byte, tooltip string) error {
t.mu.Lock()
defer t.mu.Unlock()
if t.visible {
return nil
}
return t.init(iconData, tooltip)
}
// Hide 隐藏托盘
func (t *Tray) Hide() {
t.mu.Lock()
defer t.mu.Unlock()
if !t.visible {
return
}
data := &notifyIconData{
CbSize: uint32(unsafe.Sizeof(notifyIconData{})),
HWnd: uint32(t.hWnd),
UID: 1000,
UFlags: NIF_MESSAGE,
}
procShellNotifyIcon.Call(SHM_DELETE, uintptr(unsafe.Pointer(data)))
t.visible = false
}
// IsVisible 是否可见
func (t *Tray) IsVisible() bool {
t.mu.Lock()
defer t.mu.Unlock()
return t.visible
}
// ─────────────────────────────────────────────
// Win32 API 定义
// ─────────────────────────────────────────────
const (
WM_USER = 0x0400
SHM_ADD uintptr = 0x00000000
SHM_DELETE uintptr = 0x00000001
NIF_MESSAGE = 0x00000001
NIF_ICON = 0x00000002
NIF_TIP = 0x00000004
)
// NOTIFYICONDATA struct
type notifyIconData struct {
CbSize uint32
HWnd uint32
UID uint32
UFlags uint32
UCallbackMessage uint32
HIcon uintptr
Tip [128]uint8
}
var (
procShellNotifyIcon = mustLoad("shell32.dll").MustFindProc("Shell_NotifyIconA")
)
func mustLoad(dllName string) *windows.DynLib {
dll, err := windows.LoadDLL(dllName)
if err != nil {
log.Fatalf("[tray] failed to load %s: %v", dllName, err)
}
return dll
}