feat: 接入托盘 / 添加图标生成工具 / 重命名并完善前端

- 托盘:用 getlantern/systray 重写 internal/tray,支持显示/隐藏窗口与退出
- 图标:新增 pkg/icongen 工具,支持 png/jpg/gif/bmp/webp,输出 ico/png 多个尺寸
- 重命名:项目默认名改为「青梧映星」,wails.json/productname/companyname/copyright 同步更新
- 顶栏:新增顶部导航栏(主题切换、设置、用户),iconfont 自托管 fa-solid-900.woff2
- 主题:CSS 变量 + html[data-theme] 控制白昼/黑夜切换,同时调用 WindowSetDarkTheme/LightTheme
- 底部:版权栏包含工作室、开发者与点击跳转客服链接
- 打包:wails build + exe 版本更新至 1.20 MB
This commit is contained in:
2026-09-18 01:30:13 +08:00
parent d8789e9034
commit a2a1161f10
19 changed files with 755 additions and 259 deletions

125
internal/tray/tray.go Normal file
View File

@@ -0,0 +1,125 @@
// Package tray 系统托盘管理器,基于 getlantern/systray 跨平台实现。
// Windows 下图标需为 .ico 数据,macOS/Linux 下可为 .png/.jpg/.ico。
package tray
import (
"context"
"sync"
"github.com/getlantern/systray"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// Options 托盘初始化参数
type Options struct {
// Icon 托盘图标数据
Icon []byte
// Tooltip 悬停提示文本
Tooltip string
// OnShow 点击"显示窗口"时回调,为空时默认调用 runtime.WindowShow
OnShow func()
// OnHide 点击"隐藏窗口"时回调,为空时默认调用 runtime.WindowHide
OnHide func()
// OnQuit 点击"退出"时回调,为空时默认调用 runtime.Quit
OnQuit func()
}
// Manager 系统托盘管理器
type Manager struct {
mu sync.Mutex
opts Options
started bool
ready chan struct{}
quitItem *systray.MenuItem
}
// New 创建托盘管理器,Run 之前不会显示图标
func New(opts Options) *Manager {
return &Manager{
opts: opts,
ready: make(chan struct{}),
}
}
// Run 在后台 goroutine 中启动托盘事件循环(非阻塞)。
// ctx 用于窗口显示/隐藏/退出。
func (m *Manager) Run(ctx context.Context) {
m.mu.Lock()
if m.started {
m.mu.Unlock()
return
}
m.started = true
m.mu.Unlock()
go func() {
systray.Run(func() {
m.onReady(ctx)
close(m.ready)
}, func() {
m.mu.Lock()
m.started = false
m.mu.Unlock()
})
}()
}
// WaitReady 阻塞直到托盘初始化完成(可选)
func (m *Manager) WaitReady() {
<-m.ready
}
// Active 托盘是否已成功启动
func (m *Manager) Active() bool {
m.mu.Lock()
defer m.mu.Unlock()
return m.started
}
// Quit 移除托盘图标并停止托盘 goroutine
func (m *Manager) Quit() {
systray.Quit()
}
func (m *Manager) onReady(ctx context.Context) {
if len(m.opts.Icon) > 0 {
systray.SetIcon(m.opts.Icon)
}
if m.opts.Tooltip != "" {
systray.SetTooltip(m.opts.Tooltip)
}
show := systray.AddMenuItem("显示窗口", "Show the main window")
hide := systray.AddMenuItem("隐藏窗口", "Hide the main window")
systray.AddSeparator()
quitItem := systray.AddMenuItem("退出", "Quit the application")
m.quitItem = quitItem
m.handleMenuClicks(ctx, show, hide, quitItem)
}
func (m *Manager) handleMenuClicks(ctx context.Context, show, hide, quit *systray.MenuItem) {
for {
select {
case <-show.ClickedCh:
if m.opts.OnShow != nil {
m.opts.OnShow()
} else {
runtime.WindowShow(ctx)
}
case <-hide.ClickedCh:
if m.opts.OnHide != nil {
m.opts.OnHide()
} else {
runtime.WindowHide(ctx)
}
case <-quit.ClickedCh:
if m.opts.OnQuit != nil {
m.opts.OnQuit()
} else {
runtime.Quit(ctx)
}
}
}
}