更新若干功能
This commit is contained in:
143
shell.go
143
shell.go
@@ -4,12 +4,14 @@ import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"view/service"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/notifications"
|
||||
)
|
||||
|
||||
//go:embed build/appicon.png
|
||||
@@ -26,6 +28,9 @@ type shellText struct {
|
||||
account, accountLogin, traySync, profile, logout string
|
||||
trayProjects, trayAllProjects string
|
||||
trayTodos, trayTickets, trayMessages, trayAutostart string
|
||||
trayToday, trayQuick, trayStatusSyncing string
|
||||
trayStatusOnline, trayStatusOffline, trayStatusSignedOut string
|
||||
trayStatusError, trayPending, trayUnread, trayDueToday string
|
||||
}
|
||||
|
||||
func shellTexts(locale string) shellText {
|
||||
@@ -38,6 +43,10 @@ func shellTexts(locale string) shellText {
|
||||
account: "Account", accountLogin: "Sign In / Register", traySync: "Sync Now", profile: "Profile", logout: "Sign Out",
|
||||
trayProjects: "Open Project", trayAllProjects: "All Projects…",
|
||||
trayTodos: "Todos", trayTickets: "Tickets", trayMessages: "Messages", trayAutostart: "Start at Login",
|
||||
trayToday: "Today", trayQuick: "Quick Open",
|
||||
trayStatusSyncing: "Syncing…", trayStatusOnline: "Online", trayStatusOffline: "Offline",
|
||||
trayStatusSignedOut: "Signed out", trayStatusError: "Sync error",
|
||||
trayPending: "Pending %d", trayUnread: "Unread %d", trayDueToday: "Due today %d",
|
||||
}
|
||||
}
|
||||
return shellText{
|
||||
@@ -48,6 +57,10 @@ func shellTexts(locale string) shellText {
|
||||
account: "账号", accountLogin: "登录 / 注册", traySync: "立即同步", profile: "个人中心", logout: "退出登录",
|
||||
trayProjects: "打开项目", trayAllProjects: "全部项目…",
|
||||
trayTodos: "待办事项", trayTickets: "需求工单", trayMessages: "消息中心", trayAutostart: "开机启动",
|
||||
trayToday: "今日任务", trayQuick: "快捷入口",
|
||||
trayStatusSyncing: "同步中…", trayStatusOnline: "在线", trayStatusOffline: "离线",
|
||||
trayStatusSignedOut: "未登录", trayStatusError: "同步异常",
|
||||
trayPending: "待推送 %d", trayUnread: "未读消息 %d", trayDueToday: "今日到期 %d",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +86,53 @@ func (a *App) SetupShell(wapp *application.App, win *application.WebviewWindow)
|
||||
})
|
||||
a.shell.tray = wapp.SystemTray.New()
|
||||
a.shell.tray.SetIcon(appIcon)
|
||||
a.shell.tray.OnClick(a.showMainWindow)
|
||||
a.shell.tray.OnDoubleClick(a.showMainWindow)
|
||||
a.wireNotificationClicks()
|
||||
a.RefreshShell()
|
||||
}
|
||||
|
||||
// wireNotificationClicks 点击系统通知时唤起窗口并跳到对应页面。
|
||||
func (a *App) wireNotificationClicks() {
|
||||
if a.notifier == nil {
|
||||
return
|
||||
}
|
||||
a.notifier.OnNotificationResponse(func(result notifications.NotificationResult) {
|
||||
if result.Error != nil {
|
||||
return
|
||||
}
|
||||
application.InvokeAsync(func() {
|
||||
a.showMainWindow()
|
||||
path := notificationNavPath(result.Response)
|
||||
if path != "" {
|
||||
a.emit("menu:navigate", path)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func notificationNavPath(r notifications.NotificationResponse) string {
|
||||
if r.UserInfo != nil {
|
||||
if p, ok := r.UserInfo["path"].(string); ok && p != "" {
|
||||
return p
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(r.ID, "cc-todo_due-"):
|
||||
return "/todos"
|
||||
case strings.HasPrefix(r.ID, "cc-ticket_due-"):
|
||||
return "/tickets"
|
||||
case strings.HasPrefix(r.ID, "cc-team-"):
|
||||
return "/team/tasks"
|
||||
case strings.HasPrefix(r.ID, "cc-sync-"):
|
||||
return "/messages"
|
||||
case strings.HasPrefix(r.ID, "cc-analysis-"):
|
||||
return "/logs"
|
||||
default:
|
||||
return "/messages"
|
||||
}
|
||||
}
|
||||
|
||||
// RefreshShell 按当前语言与登录态重建原生菜单与托盘菜单(设置/登录变更后调用)。
|
||||
func (a *App) RefreshShell() {
|
||||
if a.shell == nil {
|
||||
@@ -89,14 +146,36 @@ func (a *App) RefreshShell() {
|
||||
}
|
||||
t := shellTexts(locale)
|
||||
menu := a.buildAppMenu(t, theme, locale)
|
||||
// macOS 用全局应用菜单;Windows 已走无边框自定义顶栏,不再 SetMenu。
|
||||
a.shell.wapp.Menu.SetApplicationMenu(menu)
|
||||
// Windows/Linux 的菜单栏挂在窗口上(macOS 用全局应用菜单,SetMenu 是 no-op)。
|
||||
if a.shell.win != nil {
|
||||
a.shell.win.SetMenu(menu)
|
||||
a.refreshTray(t, locale)
|
||||
}
|
||||
|
||||
// refreshTray 更新托盘 tooltip / 状态色点图标 / 右键菜单。
|
||||
func (a *App) refreshTray(t shellText, locale string) {
|
||||
if a.shell == nil || a.shell.tray == nil {
|
||||
return
|
||||
}
|
||||
a.shell.tray.SetTooltip("年糕崽崽项目管理(PMS)")
|
||||
a.shell.tray.SetMenu(a.buildTrayMenu(t))
|
||||
st := a.collectTrayStatus()
|
||||
a.shell.tray.SetTooltip(st.tooltip(locale))
|
||||
a.shell.tray.SetIcon(trayIconWithBadge(appIcon, st.badgeColor()))
|
||||
a.shell.tray.SetMenu(a.buildTrayMenu(t, st))
|
||||
a.shell.tray.OnClick(a.showMainWindow)
|
||||
a.shell.tray.OnDoubleClick(a.showMainWindow)
|
||||
}
|
||||
|
||||
// RefreshTrayStatus 轻量刷新托盘状态(同步/消息变更时调用,不重建应用菜单)。
|
||||
func (a *App) RefreshTrayStatus() {
|
||||
if a.shell == nil {
|
||||
return
|
||||
}
|
||||
locale := "zh-CN"
|
||||
if a.store != nil {
|
||||
if st, e := a.store.Settings(); e == nil {
|
||||
locale = st.Locale
|
||||
}
|
||||
}
|
||||
a.refreshTray(shellTexts(locale), locale)
|
||||
}
|
||||
|
||||
func (a *App) buildAppMenu(t shellText, theme, locale string) *application.Menu {
|
||||
@@ -190,12 +269,24 @@ func (a *App) trayQuickSync() {
|
||||
}
|
||||
|
||||
// trayProjectLimit 是托盘「打开项目」子菜单最多列出的项目数,超出走「全部项目…」。
|
||||
const trayProjectLimit = 12
|
||||
const trayProjectLimit = 8
|
||||
|
||||
func (a *App) buildTrayMenu(t shellText) *application.Menu {
|
||||
func (a *App) buildTrayMenu(t shellText, st trayStatus) *application.Menu {
|
||||
menu := a.shell.wapp.Menu.New()
|
||||
menu.Add(t.trayShow).OnClick(func(*application.Context) { a.showMainWindow() })
|
||||
menu.AddSeparator()
|
||||
|
||||
// 状态区:点击状态行触发同步(或登录)。
|
||||
statusLabel := trayStatusLabel(t, st)
|
||||
menu.Add(statusLabel).OnClick(func(*application.Context) { a.trayQuickSync() })
|
||||
if st.unread > 0 {
|
||||
menu.Add(fmt.Sprintf(t.trayUnread, st.unread)).OnClick(func(*application.Context) { a.navigateTo("/messages") })
|
||||
}
|
||||
if st.todayDue > 0 {
|
||||
menu.Add(fmt.Sprintf(t.trayDueToday, st.todayDue)).OnClick(func(*application.Context) { a.navigateTo("/today") })
|
||||
}
|
||||
menu.AddSeparator()
|
||||
|
||||
if projects, e := a.ListProjects(); e == nil && len(projects) > 0 {
|
||||
sub := menu.AddSubmenu(t.trayProjects)
|
||||
for i, p := range projects {
|
||||
@@ -210,14 +301,19 @@ func (a *App) buildTrayMenu(t shellText) *application.Menu {
|
||||
sub.Add(t.trayAllProjects).OnClick(func(*application.Context) { a.navigateTo("/projects") })
|
||||
}
|
||||
}
|
||||
menu.Add(t.trayTodos).OnClick(func(*application.Context) { a.navigateTo("/todos") })
|
||||
menu.Add(t.trayTickets).OnClick(func(*application.Context) { a.navigateTo("/tickets") })
|
||||
menu.Add(t.trayMessages).OnClick(func(*application.Context) { a.navigateTo("/messages") })
|
||||
|
||||
quick := menu.AddSubmenu(t.trayQuick)
|
||||
quick.Add(t.trayToday).OnClick(func(*application.Context) { a.navigateTo("/today") })
|
||||
quick.Add(t.trayTodos).OnClick(func(*application.Context) { a.navigateTo("/todos") })
|
||||
quick.Add(t.trayTickets).OnClick(func(*application.Context) { a.navigateTo("/tickets") })
|
||||
quick.Add(t.trayMessages).OnClick(func(*application.Context) { a.navigateTo("/messages") })
|
||||
quick.Add(t.aiHub).OnClick(func(*application.Context) { a.navigateTo("/ai") })
|
||||
|
||||
menu.AddSeparator()
|
||||
menu.Add(t.trayBatch).OnClick(func(*application.Context) { _, _ = a.StartBatchAnalysis() })
|
||||
menu.Add(t.traySync).OnClick(func(*application.Context) { a.trayQuickSync() })
|
||||
menu.Add(t.trayBatch).OnClick(func(*application.Context) { _, _ = a.StartBatchAnalysis() })
|
||||
menu.AddSeparator()
|
||||
// 勾选态取自系统注册状态;点击写入后重建托盘,让勾选跟随真实结果。
|
||||
|
||||
autostart, autoErr := a.GetAutostart()
|
||||
menu.AddCheckbox(t.trayAutostart, autoErr == nil && autostart).OnClick(func(*application.Context) {
|
||||
_ = a.SetAutostart(!autostart)
|
||||
@@ -228,6 +324,29 @@ func (a *App) buildTrayMenu(t shellText) *application.Menu {
|
||||
return menu
|
||||
}
|
||||
|
||||
func trayStatusLabel(t shellText, st trayStatus) string {
|
||||
var core string
|
||||
switch {
|
||||
case st.syncing:
|
||||
core = t.trayStatusSyncing
|
||||
case !st.loggedIn:
|
||||
core = t.trayStatusSignedOut
|
||||
case st.lastErr != "":
|
||||
core = t.trayStatusError
|
||||
case st.online:
|
||||
core = t.trayStatusOnline
|
||||
if st.username != "" {
|
||||
core = st.username + " · " + core
|
||||
}
|
||||
default:
|
||||
core = t.trayStatusOffline
|
||||
}
|
||||
if st.pending > 0 {
|
||||
core += " · " + fmt.Sprintf(t.trayPending, st.pending)
|
||||
}
|
||||
return core
|
||||
}
|
||||
|
||||
// navigateTo 唤起主窗口并让前端路由跳到指定页面(托盘/原生菜单共用)。
|
||||
func (a *App) navigateTo(path string) {
|
||||
a.showMainWindow()
|
||||
|
||||
Reference in New Issue
Block a user