diff --git a/.gitignore b/.gitignore index 5c2506c..bd2d3fd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ ck 对比.md *.app .DS_Store frontend/dist/ +.workbuddy \ No newline at end of file diff --git a/app.go b/app.go index 0b16204..0410e7c 100644 --- a/app.go +++ b/app.go @@ -13,6 +13,7 @@ import ( "github.com/golang/freetype/truetype" + "douyu_go/internal/activity" "douyu_go/internal/client" "douyu_go/internal/config" "douyu_go/internal/constants" @@ -77,7 +78,13 @@ func (a *App) startup(ctx context.Context) { a.trace = tr a.closeTrace = closeTrace - a.client = client.New(a.cfg.TimeAPIURL, a.trace) + // 解析当前活动:配置中的 activity ID 决定使用哪套接口参数 + act := activity.Get(a.cfg.Activity) + if act == nil { + act = activity.Default() + logger.LogEvent("活动", "warn", "配置中的活动 %q 未识别,已回退到默认活动 %s", a.cfg.Activity, act.DisplayName) + } + a.client = client.New(act, a.cfg.TimeAPIURL, a.trace) a.font = imgpkg.LoadFont(a.paths.FontFiles) a.scheduler = seckill.New(a.client, a.paths, a.font, a.cfg.Concurrency, a.cfg.Attempts, a.cfg.RetryStages, a.cfg.SeckillMode) a.scheduler.LoadAccounts() @@ -595,6 +602,42 @@ func (a *App) GetVersion() string { return constants.Version } +// GetActivities 返回所有已注册的手册活动,供前端下拉选择。 +func (a *App) GetActivities() []types.ActivityInfo { + list := activity.List() + out := make([]types.ActivityInfo, 0, len(list)) + for _, act := range list { + out = append(out, types.ActivityInfo{ID: act.ID, DisplayName: act.DisplayName}) + } + return out +} + +// GetCurrentActivity 返回当前生效的活动。 +func (a *App) GetCurrentActivity() types.ActivityInfo { + act := a.client.Activity + return types.ActivityInfo{ID: act.ID, DisplayName: act.DisplayName} +} + +// SwitchActivity 切换当前活动并持久化到配置;切换后重建客户端与时间基准。 +func (a *App) SwitchActivity(id string) map[string]any { + act := activity.Get(id) + if act == nil { + return map[string]any{"success": false, "error": "未知活动: " + id} + } + a.cfg.Activity = act.ID + a.cfg.TimeAPIURL = act.TimeAPIURL + if err := config.SaveConfig(a.paths.ConfigFile, a.cfg); err != nil { + logger.LogEvent("活动", "error", "切换活动配置保存失败: %v", err) + return map[string]any{"success": false, "error": "配置保存失败: " + err.Error()} + } + a.client = client.New(act, a.cfg.TimeAPIURL, a.trace) + if sample, err := a.client.ServerTimeSample(); err == nil { + logger.SetDouyuOffset(sample.Offset) + } + logger.LogEvent("活动", "success", "已切换到活动: %s", act.DisplayName) + return map[string]any{"success": true, "activity": act.ID, "displayName": act.DisplayName} +} + // GetSettings 返回当前配置。 func (a *App) GetSettings() types.Config { return a.cfg @@ -694,13 +737,13 @@ func maskCookie(cookie string) string { if total > len(masked) { masked = append(masked, fmt.Sprintf("...共%d项", total)) } - return joinStrings(masked, "; ") + return strings.Join(masked, "; ") } func splitCookie(cookie string) []string { var result []string - for _, part := range splitString(cookie, ";") { - trimmed := trimSpace(part) + for _, part := range strings.Split(cookie, ";") { + trimmed := strings.TrimSpace(part) if trimmed != "" { result = append(result, trimmed) } @@ -709,15 +752,15 @@ func splitCookie(cookie string) []string { } func splitCookiePair(pair string) (string, string) { - idx := indexOf(pair, "=") + idx := strings.Index(pair, "=") if idx < 0 { - return trimSpace(pair), "" + return strings.TrimSpace(pair), "" } - return trimSpace(pair[:idx]), pair[idx+1:] + return strings.TrimSpace(pair[:idx]), pair[idx+1:] } func maskValue(value string) string { - value = trimSpace(value) + value = strings.TrimSpace(value) if value == "" { return "****" } @@ -727,50 +770,3 @@ func maskValue(value string) string { } return string(runes[:4]) + "****" + string(runes[len(runes)-4:]) } - -// 简单的字符串工具函数,避免引入额外包 -func splitString(s, sep string) []string { - var result []string - start := 0 - for i := 0; i+len(sep) <= len(s); { - if s[i:i+len(sep)] == sep { - result = append(result, s[start:i]) - start = i + len(sep) - i = start - } else { - i++ - } - } - result = append(result, s[start:]) - return result -} - -func trimSpace(s string) string { - for len(s) > 0 && (s[0] == ' ' || s[0] == '\t' || s[0] == '\n' || s[0] == '\r') { - s = s[1:] - } - for len(s) > 0 && (s[len(s)-1] == ' ' || s[len(s)-1] == '\t' || s[len(s)-1] == '\n' || s[len(s)-1] == '\r') { - s = s[:len(s)-1] - } - return s -} - -func indexOf(s, sub string) int { - for i := 0; i+len(sub) <= len(s); i++ { - if s[i:i+len(sub)] == sub { - return i - } - } - return -1 -} - -func joinStrings(parts []string, sep string) string { - if len(parts) == 0 { - return "" - } - result := parts[0] - for _, p := range parts[1:] { - result += sep + p - } - return result -} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 2f9b584..bd7f8e6 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,6 +5,7 @@ import { useProducts } from '@/composables/useProducts' import { useAccounts } from '@/composables/useAccounts' import { useScheduler } from '@/composables/useScheduler' import { useLogs } from '@/composables/useLogs' +import { useActivity } from '@/composables/useActivity' import AppShell from '@/components/layout/AppShell.vue' import ToastContainer from '@/components/ToastContainer.vue' @@ -14,6 +15,7 @@ const { loadAccounts, restoreSelection: restoreAccountSelection } = useAccounts( const { clearLogs, fetchServerLogs, checkResults } = useLogs() const { restoreSchedule, setRunState } = useScheduler() const { clearResults } = useLogs() +const { loadActivities } = useActivity() const version = ref('') const serverTime = ref('--') @@ -61,6 +63,7 @@ onMounted(async () => { // 加载数据 await loadAccounts() + await loadActivities() await loadProducts(false) // 获取版本号 diff --git a/frontend/src/components/layout/LeftSidebar.vue b/frontend/src/components/layout/LeftSidebar.vue index 863bf81..a465890 100644 --- a/frontend/src/components/layout/LeftSidebar.vue +++ b/frontend/src/components/layout/LeftSidebar.vue @@ -1,5 +1,6 @@