From e4b6a7acc5184a5c672a090f8185b4c7c51938ec Mon Sep 17 00:00:00 2001 From: wxk66 <188673098+wxk66@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:03:21 +0000 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E8=B4=A6=E5=8F=B7=E8=A1=8C?= =?UTF-8?q?=E5=86=85=E6=96=B0=E5=A2=9E=E5=8D=95=E8=B4=A6=E5=8F=B7=E3=80=8C?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E8=B5=84=E6=A0=BC=E3=80=8D=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 每个账号操作区「查额度」左侧新增 ↻ 小按钮,悬停提示「刷新资格」, 点击仅对该账号执行既有 claim_refresh(激活活动资格 + 重查可领套餐), 无需再等左上角的全量刷新 - 刷新中图标旋转并禁用;复用全局刷新的 60 秒冷却(悬停显示剩余秒数), 与全部领取/自动领取/手动领取互斥,避免触发服务端频率限制 - 单账号刷新进行中时左上角按钮保持「刷新资格」文案,不显示 x/y 计数 - 刷新完成后按该账号当前是否有可领套餐给出对应 toast 提示 - 新增 zh/en 文案键(check-i18n-keys 校验通过) --- src/locales/en.js | 3 +++ src/locales/zh.js | 3 +++ src/main.js | 44 +++++++++++++++++++++++++++++++++++++++++++- src/styles.css | 3 +++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/locales/en.js b/src/locales/en.js index 38576f5..f443987 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -77,6 +77,8 @@ export const en = { "m.claimNextAt": " — available again after {time}", "m.refreshClaimDone": "Refreshed {n} accounts — {k} with claimable plans", "m.refreshClaimAcctErr": "\"{name}\" refresh failed: {err}", + "m.refreshClaimOneOk": "\"{name}\" refreshed — has claimable plans", + "m.refreshClaimOneNone": "\"{name}\" refreshed — no claimable plans", "m.autoClaimOn": "Auto claim enabled", "m.autoClaimOnDetail": "Checks immediately once enabled, then every 10 minutes", "m.autoClaimInteractive": "\"{name}\" needs manual verification — auto claim skipped", @@ -109,6 +111,7 @@ export const en = { "btn.refreshClaimTitle": "Activate event eligibility for every account and re-check claimable plans (rate-limited)", "btn.refreshClaimRunning": "Refresh {done}/{total}", "btn.refreshClaimCooldownTitle": "Too frequent — retry in {n}s", + "btn.refreshClaimOneTitle": "Activate event eligibility for this account and re-check claimable plans (rate-limited)", "btn.relogin": "Re-login needed", "btn.reloginTitle": "This account's snapshot lacks the login info needed for event eligibility — delete it, then click \"Save login\" to re-capture (your current login is not affected)", "btn.jwtExpired": "Login expired", diff --git a/src/locales/zh.js b/src/locales/zh.js index 432a763..2c94d5e 100644 --- a/src/locales/zh.js +++ b/src/locales/zh.js @@ -77,6 +77,8 @@ export const zh = { "m.claimNextAt": ",{time} 后可再领", "m.refreshClaimDone": "已刷新 {n} 个账号,{k} 个有可领套餐", "m.refreshClaimAcctErr": "「{name}」刷新失败:{err}", + "m.refreshClaimOneOk": "「{name}」资格已刷新,当前有可领套餐", + "m.refreshClaimOneNone": "「{name}」资格已刷新,暂无可领套餐", "m.autoClaimOn": "已开启自动领取", "m.autoClaimOnDetail": "开启后立即检测一次,此后每 10 分钟一次", "m.autoClaimInteractive": "「{name}」需人工验证,已跳过自动领取", @@ -109,6 +111,7 @@ export const zh = { "btn.refreshClaimTitle": "为全部账号激活活动资格并重新查询可领套餐(有频率限制)", "btn.refreshClaimRunning": "刷新资格 {done}/{total}", "btn.refreshClaimCooldownTitle": "操作太频繁,{n} 秒后可再次刷新", + "btn.refreshClaimOneTitle": "为该账号激活活动资格并重新查询可领套餐(有频率限制)", "btn.relogin": "需重新登录", "btn.reloginTitle": "该账号快照缺少活动资格所需的登录信息:删除该账号后点「保存登录」重新入库即可(不影响当前登录)", "btn.jwtExpired": "登录已过期", diff --git a/src/main.js b/src/main.js index d1c2ffb..27a0180 100644 --- a/src/main.js +++ b/src/main.js @@ -415,6 +415,40 @@ const actions = { toast(t("m.refreshClaimDone", { n: ids.length, k: okCount }), "ok"); }, + async refreshClaimOne(id) { + const now = Date.now(); + if (refreshClaim.running || claimAllRunning || claimActive) { + toast(t("m.claimBusy"), "warn"); + return; + } + if (now < refreshClaim.cooldownUntil) { + toast(t("btn.refreshClaimCooldownTitle", { n: Math.ceil((refreshClaim.cooldownUntil - now) / 1000) }), "warn"); + return; + } + const name = state?.accounts.find((a) => a.id === id)?.name || id; + refreshClaim = { running: true, done: 0, total: 1, cooldownUntil: 0 }; + let hasPlans = false; + try { + claimable[id] = { plans: claimable[id]?.plans || [], busy: true }; + if (!uiLocked()) render(); + try { + const r = await invoke("claim_refresh", { id }); + claimable[id] = { plans: r.plans || [], err: null, busy: false }; + hasPlans = (r.plans || []).length > 0; + if (r.activationError) toast(t("m.refreshClaimAcctErr", { name, err: stripErr(r.activationError) }), "warn"); + } catch (e) { + claimable[id] = { plans: claimable[id]?.plans || [], err: String(e), busy: false }; + toast(t("m.refreshClaimAcctErr", { name, err: stripErr(e) }), "err"); + } + scheduleNext(id); + } finally { + refreshClaim.running = false; + refreshClaim.cooldownUntil = Date.now() + REFRESH_CLAIM_COOLDOWN_MS; + if (!uiLocked()) render(); + } + toast(hasPlans ? t("m.refreshClaimOneOk", { name }) : t("m.refreshClaimOneNone", { name }), "ok"); + }, + async toggleAutoClaim() { if (autoToggleBusy) return; const next = !state.auto_claim; @@ -754,6 +788,13 @@ function render() { ? unsaved ? t("m.status.unsaved") : t("m.status.safe") : t("m.status.loggedOut"); + const rOneCooling = Date.now() < refreshClaim.cooldownUntil; + const rOneBusy = refreshClaim.running && refreshClaim.total === 1; + const rOneDisabled = refreshClaim.running || claimAllRunning || claimActive || autoClaimRunning || rOneCooling; + const rOneTitle = refreshClaim.running || !rOneCooling + ? t("btn.refreshClaimOneTitle") + : t("btn.refreshClaimCooldownTitle", { n: Math.ceil((refreshClaim.cooldownUntil - Date.now()) / 1000) }); + const rows = s.accounts.map((a) => { const isActive = a.is_active; if (renaming === a.id) { return ` @@ -788,6 +829,7 @@ function render() {