creditdaddy 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -286,6 +286,35 @@ export function normalizeMiniMaxQuota(data) {
286
286
  };
287
287
  }
288
288
 
289
+ /**
290
+ * 只查签到状态,不打 claim(10R 只读推送用,issue #33)。
291
+ * 刷新失败等凭据问题照常抛错;查询失败返回 null(推送侧降级为不带签到字段)。
292
+ * @returns {Promise<{checkedToday: boolean, streakDays: number, points: number, bonusPoints: number}|null>}
293
+ */
294
+ export async function fetchMiniMaxCheckinStatus(account, ctx = {}) {
295
+ try {
296
+ return await withMiniMaxAuth(account, async (token) => {
297
+ const statusRes = await fetchJsonRace(SIGNIN_STATUS_URL, {
298
+ headers: authHeaders(token),
299
+ timeoutMs: FETCH_TIMEOUT_MS,
300
+ });
301
+ if (!statusRes.ok) return null;
302
+ const statusData = await statusRes.json().catch(() => null);
303
+ if (!statusData || statusData.base_resp?.status_code !== 0) return null;
304
+ const today = (statusData.data?.days || []).find((d) => d.is_today);
305
+ if (!today) return null;
306
+ return {
307
+ checkedToday: today.status === 3,
308
+ streakDays: today.day_no || 0,
309
+ points: today.points || 0,
310
+ bonusPoints: today.bonus_points || 0,
311
+ };
312
+ }, ctx);
313
+ } catch {
314
+ return null; // 推送是旁路:凭据失效 / 网络抖动都不该炸整轮推送
315
+ }
316
+ }
317
+
289
318
  /**
290
319
  * 每日自动签到 (checkin)
291
320
  */
@@ -154,6 +154,9 @@ export function normalizeMirasimLimits(limits, profile = null) {
154
154
  plan: profile?.plan ? String(profile.plan).toUpperCase() : (limits?.paid ? 'PRO' : 'FREE'),
155
155
  planExpiresAt: epochToIso(profile?.plan_exp),
156
156
  bottleneckLabel: isExhausted ? (bottleneck?.name || '额度') + '已用尽' : null,
157
+ // 结构化瓶颈(面板按词典分别 T() 窗口名与「已用尽」,替代拼串 bottleneckLabel——
158
+ // 上游新增窗口名(如 '30d')无词典映射时拼串会出「30d已用尽」混语)
159
+ bottleneck: isExhausted && bottleneck ? { name: bottleneck.name } : null,
157
160
  parts,
158
161
  empty: parts.length === 0,
159
162
  exceeded: limits?.suspended === true || isExhausted,
package/src/panel.html CHANGED
@@ -472,11 +472,6 @@
472
472
  </div>
473
473
  <div class="hint" data-i18n="作用于全部产品的定时签到轮(ZCode 活动领取在自身页签单独配置);修改立即生效,下一轮按新间隔执行。">作用于全部产品的定时签到轮(ZCode 活动领取在自身页签单独配置);修改立即生效,下一轮按新间隔执行。</div>
474
474
  </div>
475
- <div class="field">
476
- <label data-i18n="WorkBuddy 国际版 活跃请求">WorkBuddy 国际版 活跃请求</label>
477
- <label class="check"><input type="checkbox" id="cfg-wb-activity"> <span data-i18n="定期发送活跃请求(保持资格,不保证奖励)">定期发送活跃请求(保持资格,不保证奖励)</span></label>
478
- <div class="hint" data-i18n="关闭后本机不再对国际版账号自动/手动发活跃请求;卡片与仪表盘的「活跃」入口一同隐藏。">关闭后本机不再对国际版账号自动/手动发活跃请求;卡片与仪表盘的「活跃」入口一同隐藏。</div>
479
- </div>
480
475
  <div class="field">
481
476
  <label data-i18n="面板访问密码">面板访问密码</label>
482
477
  <div id="sec-block"></div>
@@ -769,6 +764,35 @@
769
764
  </div>
770
765
  </div>
771
766
 
767
+ <!-- 捐赠支持(面向国内用户:爱发电 / 支付宝 / 微信;渠道与 10Router 一致,UI 照 CD 设计语言) -->
768
+ <div class="mask" id="m-donate">
769
+ <div class="modal">
770
+ <div class="modal-title"><span data-i18n="捐赠支持">捐赠支持</span><button class="close" data-close>✕</button></div>
771
+ <div class="hint" data-i18n="如果 CreditDaddy 对你有帮助,欢迎支持一下开发——免费渠道的每一行代码都是咖啡驱动的。">如果 CreditDaddy 对你有帮助,欢迎支持一下开发——免费渠道的每一行代码都是咖啡驱动的。</div>
772
+ <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:14px">
773
+ <div style="display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 10px;border:1px solid var(--border-subtle);border-radius:10px">
774
+ <span style="font-size:26px">⚡</span>
775
+ <div style="font-weight:700">AiFaDian</div>
776
+ <div class="hint" data-i18n="爱发电 · Power me ⚡">爱发电 · Power me ⚡</div>
777
+ <img src="/assets/donate-ifdian.svg?v=3" alt="AiFaDian" style="width:140px;height:140px;margin-top:auto;border-radius:8px;padding:6px;flex:none">
778
+ </div>
779
+ <div style="display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 10px;border:1px solid var(--border-subtle);border-radius:10px">
780
+ <span style="font-size:26px">🅰️</span>
781
+ <div style="font-weight:700">Alipay</div>
782
+ <div class="hint" data-i18n="支付宝扫码打赏">支付宝扫码打赏</div>
783
+ <img src="/assets/donate-alipay.svg?v=3" alt="Alipay" style="width:140px;height:140px;margin-top:auto;border-radius:8px;padding:6px;flex:none">
784
+ </div>
785
+ <div style="display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 10px;border:1px solid var(--border-subtle);border-radius:10px">
786
+ <span style="font-size:26px">💚</span>
787
+ <div style="font-weight:700">WeChat Pay</div>
788
+ <div class="hint" data-i18n="微信扫码打赏">微信扫码打赏</div>
789
+ <img src="/assets/donate-wechat.svg?v=3" alt="WeChat Pay" style="width:140px;height:140px;margin-top:auto;border-radius:8px;padding:6px;flex:none">
790
+ </div>
791
+ </div>
792
+ <div class="modal-actions"><button class="btn primary" data-close data-i18n="关闭">关闭</button></div>
793
+ </div>
794
+ </div>
795
+
772
796
  <!-- 10Router 设置 -->
773
797
  <div class="mask" id="m-10r">
774
798
  <div class="modal">
@@ -807,9 +831,9 @@
807
831
  <!-- 免费额度池:推送设置 -->
808
832
  <div class="mask" id="m-free-settings">
809
833
  <div class="modal sm">
810
- <div class="modal-title"><span data-i18n="免费额度推送设置">免费额度推送设置</span><button class="close" data-close>✕</button></div>
834
+ <div class="modal-title"><span data-i18n="额度卡片推送设置">额度卡片推送设置</span><button class="close" data-close>✕</button></div>
811
835
  <div class="field">
812
- <label class="check"><input type="checkbox" id="free-push-auto"> <span data-i18n="自动推送免费额度卡到 10Router(Token Harbor / MiniMax)">自动推送免费额度卡到 10Router(Token Harbor / MiniMax)</span></label>
836
+ <label class="check"><input type="checkbox" id="free-push-auto"> <span data-i18n="自动推送额度卡片到 10Router(Token Harbor / MiniMax / LongCat 等)">自动推送额度卡片到 10Router(Token Harbor / MiniMax / LongCat 等)</span></label>
813
837
  </div>
814
838
  <div class="field">
815
839
  <label data-i18n="推送频率">推送频率</label>
@@ -821,7 +845,7 @@
821
845
  <option value="240" data-i18n="每 4 小时">每 4 小时</option>
822
846
  </select>
823
847
  </div>
824
- <div class="hint" data-i18n="把 CreditDaddy 解析的免费额度数字(不含凭据)推到 10Router 配额页出卡;卡片在 10Router 具 7 天有效期。点「立即同步一次」或 10Router 页「刷新额度」也会立即推送。">把 CreditDaddy 解析的免费额度数字(不含凭据)推到 10Router 配额页出卡;卡片在 10Router 具 7 天有效期。点「立即同步一次」或 10Router 页「刷新额度」也会立即推送。</div>
848
+ <div class="hint" data-i18n="把 CreditDaddy 解析的额度数字(不含凭据)推到 10Router 配额页出卡;卡片在 10Router 具 7 天有效期。点「立即同步一次」或 10Router 页「刷新额度」也会立即推送。">把 CreditDaddy 解析的额度数字(不含凭据)推到 10Router 配额页出卡;卡片在 10Router 具 7 天有效期。点「立即同步一次」或 10Router 页「刷新额度」也会立即推送。</div>
825
849
  <div class="hint" id="free-settings-status" style="margin-top:8px"></div>
826
850
  <div class="modal-actions">
827
851
  <button class="btn" style="margin-right:auto" id="free-settings-push-now" data-i18n="立即推送一次">立即推送一次</button>
@@ -969,6 +993,8 @@ const I = {
969
993
  play: svg('<polygon points="6 4 20 12 6 20 6 4"/>'),
970
994
  refresh: svg('<path d="M21 12a9 9 0 1 1-2.64-6.36L21 8"/><path d="M21 3v5h-5"/>'),
971
995
  trash: svg('<path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/>'),
996
+ eye: svg('<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>'),
997
+ eyeOff: svg('<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>'),
972
998
  plus: svg('<path d="M12 5v14M5 12h14"/>'),
973
999
  upload: svg('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M17 8l-5-5-5 5"/><path d="M12 3v12"/>'),
974
1000
  download: svg('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/>'),
@@ -1023,6 +1049,9 @@ const COMPONENTS = [
1023
1049
  { id: 'free', label: '网页会话' },
1024
1050
  { id: 'tenrouter', label: '10Router' },
1025
1051
  ];
1052
+ // 捐赠二维码随日夜主题切换(顶层定义:applyTheme 的调用早于原块级定义,块内 function 不可达)
1053
+ // 捐赠二维码统一低饱和金黄(透明底),日夜主题通用,无需随主题切换
1054
+ function applyDonateQrTheme() { /* 统一金码:无主题切换 */ }
1026
1055
  const HIDDEN_COMP_KEY = 'qd_hidden_components';
1027
1056
  const DEFAULT_HIDDEN_COMP = ['catpaw']; // 妙手默认收起,右上角设置里可打开
1028
1057
  state.hidden = (() => {
@@ -1250,7 +1279,6 @@ async function loadSettings() {
1250
1279
  try {
1251
1280
  const d = await api('/api/settings');
1252
1281
  state.sec = { enabled: Boolean(d.panelKeyEnabled), loaded: true };
1253
- state.wbActivity = d.workbuddyActivity !== false; // WB 国际版活跃请求总开关(默认开)
1254
1282
  if (!d.panelKeyEnabled) setKey(''); // 关闭后清掉本地缓存的旧密钥
1255
1283
  if (d.locale) renderLocale(d.locale);
1256
1284
  } catch (e) { /* 401(密码开启但未输入)等状态由 promptKey 流程处理 */ }
@@ -1283,15 +1311,6 @@ async function applyDictFromServer() {
1283
1311
  document.getElementById('set-locale')?.addEventListener('change', (ev) => setLocale(ev.target.value));
1284
1312
 
1285
1313
  // ── 通用设置:全局自动签到(开关 + 频率,落盘立刻重调度)、日志默认显示方式(本机偏好)──
1286
- const wbActOn = () => state.wbActivity !== false;
1287
- $('cfg-wb-activity').addEventListener('change', async (e) => {
1288
- try {
1289
- await api('/api/settings', { method: 'PUT', json: { workbuddyActivity: e.target.checked } });
1290
- await loadSettings();
1291
- render(); renderStatus();
1292
- toast(T('已保存'), true);
1293
- } catch (err) { toast(err.message, false); }
1294
- });
1295
1314
  function fillAutoCheckin() {
1296
1315
  const sc = state.status && state.status.scheduler;
1297
1316
  if (!sc) return;
@@ -1329,6 +1348,7 @@ applyLogsDisplay(logsDisplayDefault());
1329
1348
  // 由 renderThemeMode() 反映到菜单里的分段控件(主题只有那一个入口)。
1330
1349
  function applyTheme(t) {
1331
1350
  document.documentElement.dataset.theme = t;
1351
+ if (typeof applyDonateQrTheme === 'function') { try { applyDonateQrTheme(t); } catch {} }
1332
1352
  }
1333
1353
  function initTheme() {
1334
1354
  let t = null;
@@ -1425,6 +1445,7 @@ function initTheme() {
1425
1445
  // 需要跟随系统走菜单里的三态分段控件。
1426
1446
  const themeBtn = $('btn-theme');
1427
1447
  function resolvedTheme() { return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light'; }
1448
+
1428
1449
  function renderThemeButton() {
1429
1450
  if (!themeBtn) return;
1430
1451
  const cur = resolvedTheme();
@@ -1455,20 +1476,16 @@ function initTheme() {
1455
1476
  else window.open(url, '_blank');
1456
1477
  };
1457
1478
 
1458
- // 捐赠(桌面版走 IPC 桥;网页直接开浏览器)
1459
- $('donate-btn').onclick = () => {
1460
- closeHmenu();
1461
- if (window.creditdaddy && typeof window.creditdaddy.shellOpen === 'function')
1462
- window.creditdaddy.shellOpen('https://github.com/sponsors/techysy');
1463
- else window.open('https://github.com/sponsors/techysy', '_blank');
1464
- };
1479
+ // 捐赠:弹窗展示三渠道(爱发电 / 支付宝 / 微信——国内用户为主,与 10Router 一致);
1480
+ // Sponsors 按钮跳转(桌面版走 IPC 桥,网页直接开浏览器)
1481
+ $('donate-btn').onclick = () => { closeHmenu(); openModal('m-donate'); };
1465
1482
  }
1466
1483
 
1467
1484
  // ── 状态 / 渲染 ──
1468
- // 统一「领鸡蛋」语义:Qoder / WorkBuddy 国内/国际 = 领取积分;workbuddy-intl = 保持活跃(不保证奖励)
1485
+ // 统一「领鸡蛋」语义:全部签到产品线 = 领取积分(workbuddy-intl 的「活跃」语义随活跃领取线删除)
1469
1486
  function statusOf(a) {
1470
1487
  if (!CHECKIN_PRODUCTS.has(a.product)) return { cls: '', text: '', msg: '' }; // ZCode 无每日领取,不显示徽标
1471
- const intl = a.provider === 'workbuddy-intl';
1488
+ if (a.provider === 'workbuddy-intl') return { cls: '', text: '', msg: '' }; // 国际版无签到动作(活跃领取线已删),不显示徽标
1472
1489
  const byProd = (state.status && state.status.todayByProduct) || {};
1473
1490
  const today = byProd[a.product] || (state.status && state.status.today);
1474
1491
  const done = state.status && state.status.todayDone && state.status.todayDone[a.id] === today;
@@ -1476,15 +1493,13 @@ function statusOf(a) {
1476
1493
  const fresh = r && r.at && state.status && (Date.now() - new Date(r.at).getTime() < 26 * 3600e3);
1477
1494
  if (r && fresh && r.status === 'limited' && done) return { cls: 'limited', text: T('本机已领'), msg: r.message };
1478
1495
  if (done) {
1479
- if (intl) return { cls: 'ok', text: T('今日活跃'), msg: r && r.message ? r.message : '' };
1480
1496
  const amt = r && r.status === 'checked-in' && fresh && r.amount ? ' +' + fmtNum(r.amount) : '';
1481
- const streak = r && !intl && r.streakDays ? T(' · 连续 ') + r.streakDays + T(' 天') : '';
1497
+ const streak = r && r.streakDays ? T(' · 连续 ') + r.streakDays + T(' 天') : '';
1482
1498
  return { cls: 'ok', text: T('今日已领') + amt + streak, msg: r && r.message && !['checked-in', 'already'].includes(r.status) ? r.message : '' };
1483
1499
  }
1484
- const failText = intl ? T('活跃失败') : T('领取失败');
1485
- if (r && fresh && r.status === 'failed') return { cls: 'fail', text: failText, msg: r.message, err: true };
1486
- if (r && fresh && r.status === 'no-activity') return { cls: 'pending', text: intl ? T('待活跃') : T('待领取'), msg: r.message };
1487
- return { cls: 'pending', text: intl ? T('未活跃') : T('未领取'), msg: '' };
1500
+ if (r && fresh && r.status === 'failed') return { cls: 'fail', text: T('领取失败'), msg: r.message, err: true };
1501
+ if (r && fresh && r.status === 'no-activity') return { cls: 'pending', text: T('待领取'), msg: r.message };
1502
+ return { cls: 'pending', text: T('未领取'), msg: '' };
1488
1503
  }
1489
1504
 
1490
1505
  function expiryCell(a) {
@@ -1567,8 +1582,11 @@ function creditHtml(a) {
1567
1582
  const planBadge = q.plan ? '<span class="badge">' + esc(T(q.plan)) + T(' 套餐</span>') : '';
1568
1583
  const expLabel = q.planExpiresAt ? '<span class="dim">' + fmtTime(q.planExpiresAt).slice(0, 10) + T(' 到期</span>') : '';
1569
1584
  // 状态采用温和的徽章,避免整行大红字制造恐慌
1585
+ // pill 不再用 mirasimClient 拼好的 bottleneckLabel(上游窗口名原文 + '已用尽' 拼串,
1586
+ // 英文界面会出「30d已用尽」这类混语)——改面板侧按词典分别 T():窗口名走词条
1587
+ // ('5小时窗口'→'5h window' 等,上游新名如 '30d' 拉丁原样),「已用尽」单独走词条。
1570
1588
  const topStatus = isExhausted
1571
- ? '<span class="pill pending">' + esc(T(q.bottleneckLabel || '') || T('额度已用尽')) + '</span>'
1589
+ ? '<span class="pill pending">' + esc(q.bottleneck ? T(q.bottleneck.name) + T('已用尽') : T('额度已用尽')) + '</span>'
1572
1590
  : T('<span class="lbl">可用额度</span><b>') + fmtNum(q.remaining) + '%</b>';
1573
1591
 
1574
1592
  const fmtUnits = (n) => (n >= 10000 ? (n / 10000).toFixed(2) + T('万') : fmtNum(Math.round(n)));
@@ -1640,7 +1658,7 @@ function cardHtml(a) {
1640
1658
  + (st.text ? '<span class="pill ' + st.cls + '" title="' + esc(a.lastResult ? fmtTime(a.lastResult.at) + ' ' + (a.lastResult.message || a.lastResult.status) : '') + '">' + esc(st.text) + '</span>' : '')
1641
1659
  + '</div><div class="ops">'
1642
1660
  + (a.product === 'zcode' ? T('<button class="icon-btn" data-act="claim" title="活动领取(billing/preview → claim,可能需过一次验证码)">') + I.gift + '</button>' : '')
1643
- + (canCheckin && (a.provider !== 'workbuddy-intl' || wbActOn()) ? '<button class="icon-btn' + (state.busy[a.id] ? ' busy' : '') + '" data-act="checkin" title="' + (a.provider === 'workbuddy-intl' ? T('发送活跃请求') : T('立即领取')) + '">' + (state.busy[a.id] ? I.refresh : I.play) + '</button>' : '')
1661
+ + (canCheckin && a.provider !== 'workbuddy-intl' ? '<button class="icon-btn' + (state.busy[a.id] ? ' busy' : '') + '" data-act="checkin" title="' + T('立即领取') + '">' + (state.busy[a.id] ? I.refresh : I.play) + '</button>' : '')
1644
1662
  + (a.qoderWebDead ? '<button class="icon-btn" data-act="webrenew" title="' + T('网页会话已失效——打开登录窗口重新授权,关闭窗口时自动补 cookie(API token 不受影响)') + '">' + I.swap + '</button>' : '')
1645
1663
  + (a.canSwitch && !isCurrent && st.cls !== 'fail' ? T('<button class="icon-btn" data-act="switch" title="切换 ') + (CLIENT_LABEL[a.product] ? T(CLIENT_LABEL[a.product]) : prodLabel(a.product)) + T(' 客户端到此账号">') + I.swap + '</button>' : '')
1646
1664
  + T('<button class="icon-btn" data-act="quota" title="刷新积分">') + I.refresh + '</button>'
@@ -1752,11 +1770,9 @@ function renderProd() {
1752
1770
  $('s-count').textContent = acc.length;
1753
1771
  $('s-done-wrap').style.display = canCheckin ? 'contents' : 'none';
1754
1772
  if (prod === 'workbuddy') {
1755
- // 国内版 = 每日签到送积分;国际版 = 保持活跃(活跃不等于必有奖励)
1773
+ // 国内版 = 每日签到送积分;国际版无签到动作(活跃领取线已删),只统计国内
1756
1774
  const cnAcc = acc.filter((a) => a.provider === 'workbuddy');
1757
- const intlAcc = acc.filter((a) => a.provider === 'workbuddy-intl' && wbActOn());
1758
- $('s-done-wrap').innerHTML = (cnAcc.length ? T('<div class="stat"><span title="国内版每日签到送积分">领取(国内)</span><strong>') + cnAcc.filter(isDone).length + ' / ' + cnAcc.length + '</strong></div><div class="divider"></div>' : '')
1759
- + (intlAcc.length ? T('<div class="stat"><span title="国际版定期发送活跃请求保持资格;活跃不等于必有奖励">活跃(国际)</span><strong>') + intlAcc.filter(isDone).length + ' / ' + intlAcc.length + '</strong></div><div class="divider"></div>' : '');
1775
+ $('s-done-wrap').innerHTML = (cnAcc.length ? T('<div class="stat"><span title="国内版每日签到送积分">领取(国内)</span><strong>') + cnAcc.filter(isDone).length + ' / ' + cnAcc.length + '</strong></div><div class="divider"></div>' : '');
1760
1776
  } else {
1761
1777
  // 从 WorkBuddy 页切回来时 s-done 可能被 innerHTML 换没了,先重建结构再写值
1762
1778
  if (!$('s-done')) $('s-done-wrap').innerHTML = T('<div class="stat"><span>今日已领</span><strong id="s-done"></strong></div><div class="divider"></div>');
@@ -1773,8 +1789,7 @@ function renderProd() {
1773
1789
  document.querySelectorAll('#region button').forEach((x) => x.classList.toggle('active', x.dataset.r === state.region));
1774
1790
  $('btn-checkin').style.display = canCheckin ? '' : 'none';
1775
1791
  if (!$('btn-checkin').disabled) {
1776
- const hasIntl = acc.some((a) => a.provider === 'workbuddy-intl') && wbActOn();
1777
- $('btn-checkin').innerHTML = I.play + (prod === 'workbuddy' && hasIntl ? T('领取 / 活跃全部') : T('领取全部 ') + prodLabel(prod));
1792
+ $('btn-checkin').innerHTML = I.play + T('领取全部 ') + prodLabel(prod);
1778
1793
  }
1779
1794
  // 顶栏产品专属入口:ZCode 设置组、Trae 打开客户端,带状态灯(运行中绿点呼吸)
1780
1795
  const dot = (on, pulse) => '<span class="dot' + (on ? ' on' : '') + (on && pulse ? ' pulse' : '') + '"></span>';
@@ -1842,23 +1857,19 @@ function renderDash() {
1842
1857
  const credit = creditSum(state.accounts.filter((a) => a.product !== 'zcode'));
1843
1858
  const byProd = orderedVisibleComps().filter((id) => id !== 'tenrouter').map((id) => prodLabel(id) + ' ' + ofProduct(id).length).join(' · ');
1844
1859
  const creditByProd = ['qoder', 'workbuddy', 'minimax'].filter(compVisible).map((id) => { const c = creditSum(ofProduct(id)); return prodLabel(id) + ' ' + (c.known ? fmtNum(c.sum) : '-'); }).join(' · ');
1845
- // 今日进度拆「国内领取 / 国际活跃」:国际版 WorkBuddy 是保持活跃(活跃不等于必有奖励)
1846
- const intlAcc = signable.filter((a) => a.provider === 'workbuddy-intl' && wbActOn());
1847
- const domAcc = signable.filter((a) => a.provider !== 'workbuddy-intl');
1848
- const doneDom = domAcc.filter(isDone).length;
1849
- const doneIntl = intlAcc.filter(isDone).length;
1860
+ // 今日进度:国际版 WorkBuddy 无签到动作(活跃领取线已删),signable 天然只剩可领取账号
1850
1861
  $('kpis').innerHTML =
1851
1862
  kpi(T('账号'), state.accounts.length, byProd)
1852
- + kpi(T('今日领取') + (intlAcc.length ? T(' / 活跃') : ''), intlAcc.length
1853
- ? doneDom + ' <small>/ ' + domAcc.length + '</small><small class="kpi-side">' + T(' · 活跃 {n} / {m}', { n: doneIntl, m: intlAcc.length }) + '</small>'
1854
- : done + ' <small>/ ' + signable.length + '</small>',
1863
+ + kpi(T('今日领取'), done + ' <small>/ ' + signable.length + '</small>',
1855
1864
  !signable.length ? T('暂无可领取的账号')
1856
- : (doneDom === domAcc.length && doneIntl === intlAcc.length ? (intlAcc.length ? T('今天全部完成(国际为活跃请求,不保证奖励)') : T('今天全部签完'))
1865
+ : (done === signable.length ? T('今天全部签完')
1857
1866
  : T('待办 ') + (signable.length - done) + (failed ? T(' · 失败 ') + failed : '')),
1858
- T('领取 = 国内版每日签到;活跃 = 国际版定期活跃请求(保持资格,不保证奖励)'))
1867
+ T('领取 = 每日签到'))
1859
1868
  + kpi(T('剩余积分'), credit.loading ? '…' : (credit.known ? fmtNum(credit.sum) : '-'), credit.loading ? T('正在读取各账号额度…') : creditByProd)
1860
1869
  + kpi(T('下次自动领取'), sch.ticking ? T('领取中…') : sch.nextTickAt ? fmtTime(sch.nextTickAt).slice(11) : '-',
1861
- sch.lastTick ? T('上一轮 ') + fmtTime(sch.lastTick.at).slice(5) + ' · ' + esc(sch.lastTick.summary || '') : T('每 ~2 小时一轮'));
1870
+ sch.lastTick
1871
+ ? T('上一轮 ') + fmtTime(sch.lastTick.at).slice(5) + ' · ' + esc(sch.lastTick.summaryKey ? T(sch.lastTick.summaryKey, sch.lastTick.summaryArgs) : (sch.lastTick.summary || ''))
1872
+ : T('每 ~2 小时一轮'));
1862
1873
 
1863
1874
  // 仪表盘产品卡按组件设置里的顺序渲染(与标签栏同步),10Router 汇总卡按其顺序插入
1864
1875
  const prodCardHtml = (p) => {
@@ -1870,16 +1881,10 @@ function renderDash() {
1870
1881
  const d = list.filter(isDone).length;
1871
1882
  const c = creditSum(list);
1872
1883
  if (p.id === 'workbuddy') {
1873
- // 国内 = 每日领取;国际 = 保持活跃(活跃不等于必有奖励)——合并一行展示
1884
+ // 国内 = 每日领取;国际无签到动作(活跃领取线已删),统计只覆盖国内账号
1874
1885
  const cnList = list.filter((a) => a.provider === 'workbuddy');
1875
- const intlList = list.filter((a) => a.provider === 'workbuddy-intl');
1876
- const parts = [];
1877
- if (cnList.length) parts.push(cnList.filter(isDone).length + ' / ' + cnList.length + T('(国内)'));
1878
- if (intlList.length) parts.push(intlList.filter(isDone).length + ' / ' + intlList.length + T('(国际 · 不保证奖励)'));
1879
- rows += T('<span class="lbl">领取 / 活跃</span><span class="val" title="') + esc(parts.join(T(',')) + (intlList.length ? T('(国际为活跃请求,不保证奖励)') : '')) + '">'
1880
- + (cnList.length ? cnList.filter(isDone).length + ' / ' + cnList.length + T(' 国内') : '')
1881
- + (cnList.length && intlList.length ? ' · ' : '')
1882
- + (intlList.length ? intlList.filter(isDone).length + ' / ' + intlList.length + T(' 国际') : '')
1886
+ rows += T('<span class="lbl">领取</span><span class="val" title="') + esc(cnList.length ? cnList.filter(isDone).length + ' / ' + cnList.length + T('(国内)') : '') + '">'
1887
+ + (cnList.length ? cnList.filter(isDone).length + ' / ' + cnList.length + T(' 国内') : T('—'))
1883
1888
  + '</span>';
1884
1889
  } else if (p.id === 'minimax') {
1885
1890
  const planned = list.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length;
@@ -1944,7 +1949,7 @@ function renderDash() {
1944
1949
  if (a.verified === false) att.push(Object.assign({ pill: 'fail', text: T('token 无效') }, base));
1945
1950
  else if (ex.label === '已过期') att.push(Object.assign({ pill: 'fail', text: T('token 已过期') }, base));
1946
1951
  else if (ex.warn) att.push(Object.assign({ pill: 'limited', text: ex.label.slice(5, 10) + T(' 过期') }, base));
1947
- if (st.cls === 'fail') att.push(Object.assign({ pill: 'fail', text: a.provider === 'workbuddy-intl' ? T('活跃失败') : T('领取失败'), title: st.msg }, base));
1952
+ if (st.cls === 'fail') att.push(Object.assign({ pill: 'fail', text: T('领取失败'), title: st.msg }, base));
1948
1953
  }
1949
1954
  for (const c of (tr.quotas && tr.quotas.connections) || []) {
1950
1955
  if (!compVisible('tenrouter')) break;
@@ -1970,8 +1975,6 @@ function renderDash() {
1970
1975
  ? recent.map((a) => {
1971
1976
  const r = a.lastResult;
1972
1977
  let [cls, txt] = RES[r.status] || ['pending', r.status];
1973
- // 国际版 WorkBuddy 是保持活跃(不保证奖励),不叫「领取」
1974
- if (a.provider === 'workbuddy-intl' && r.status === 'checked-in') txt = T('活跃达成');
1975
1978
  const label = r.status === 'checked-in' && r.amount ? txt + ' +' + fmtNum(r.amount) : txt;
1976
1979
  return '<div class="item" data-view="' + a.product + '" title="' + esc(r.message || '') + '"><span class="item-main"><span class="item-name">' + esc(a.name || a.email || a.id) + '</span>' + provBadges(a.provider) + '</span>'
1977
1980
  + '<span class="item-side"><span class="pill ' + cls + '">' + esc(label) + '</span><span class="item-time">' + fmtTime(r.at).slice(5) + '</span></span></div>';
@@ -2001,7 +2004,7 @@ function renderStatus() {
2001
2004
  } else {
2002
2005
  chip.innerHTML = '';
2003
2006
  }
2004
- chip.title = sch.lastTick ? T('上一轮 ') + fmtTime(sch.lastTick.at) + T(':') + sch.lastTick.summary : '';
2007
+ chip.title = sch.lastTick ? T('上一轮 ') + fmtTime(sch.lastTick.at) + T(':') + (sch.lastTick.summaryKey ? T(sch.lastTick.summaryKey, sch.lastTick.summaryArgs) : sch.lastTick.summary || '') : '';
2005
2008
  // 产品相关提示只在对应标签页出现
2006
2009
  const hasIntl = state.view === 'qoder' && state.region !== 'cn' && state.accounts.some((a) => a.provider === 'qoder');
2007
2010
  const n = $('notice');
@@ -2082,6 +2085,30 @@ async function installUmid() {
2082
2085
  $('notice').addEventListener('click', (e) => { if (e.target.closest('#btn-umid')) installUmid(); });
2083
2086
  // 覆盖 #notice(各产品横幅)与 #z-actions(ZCode 工具栏按钮组)的点击
2084
2087
  document.addEventListener('click', async (e) => {
2088
+ // 免费额度页:站点卡隐藏 / 全部显示(拖拽排序在 renderFreePool 里经 bindCardDrag 绑定)
2089
+ const hideBtn = e.target.closest('[data-free-hide]');
2090
+ if (hideBtn) {
2091
+ state.hiddenFreeSites.add(hideBtn.dataset.freeHide);
2092
+ saveHiddenFreeSites();
2093
+ renderFreePool();
2094
+ return;
2095
+ }
2096
+ const doubaoBtn = e.target.closest('[data-open-doubao]');
2097
+ if (doubaoBtn) {
2098
+ if (window.creditdaddy && typeof window.creditdaddy.openDoubaoClient === 'function') {
2099
+ window.creditdaddy.openDoubaoClient().then((r) => {
2100
+ if (r && r.ok) toast(T('已启动豆包客户端'), true);
2101
+ else toast((r && r.error) || T('无法启动豆包客户端'), false);
2102
+ }).catch((err) => toast(String(err && err.message || err), false));
2103
+ } else toast(T('需要桌面版 CreditDaddy'), false);
2104
+ return;
2105
+ }
2106
+ if (e.target.closest('#free-show-all')) {
2107
+ state.hiddenFreeSites.clear();
2108
+ saveHiddenFreeSites();
2109
+ renderFreePool();
2110
+ return;
2111
+ }
2085
2112
  if (e.target.closest('#zt-open')) {
2086
2113
  try {
2087
2114
  const r = await window.creditdaddy.openZcodeClient();
@@ -2488,30 +2515,78 @@ async function loadCookieSites(force) {
2488
2515
  return state.cookieSites;
2489
2516
  }
2490
2517
 
2518
+ // 免费额度页站点卡:顺序 / 显隐(本机持久化;拖拽复用 bindCardDrag,眼睛按钮切换显隐)
2519
+ const FREE_ORDER_KEY = 'qd_free_site_order';
2520
+ const FREE_HIDDEN_KEY = 'qd_hidden_free_sites';
2521
+ state.freeOrder = (() => {
2522
+ let arr = [];
2523
+ try { arr = JSON.parse(localStorage.getItem(FREE_ORDER_KEY) || '[]'); } catch {}
2524
+ return Array.isArray(arr) ? arr : [];
2525
+ })();
2526
+ function saveFreeOrder() { try { localStorage.setItem(FREE_ORDER_KEY, JSON.stringify(state.freeOrder)); } catch {} }
2527
+ state.hiddenFreeSites = (() => {
2528
+ let list = [];
2529
+ try { list = JSON.parse(localStorage.getItem(FREE_HIDDEN_KEY) || '[]'); } catch {}
2530
+ return new Set(Array.isArray(list) ? list : []);
2531
+ })();
2532
+ function saveHiddenFreeSites() { try { localStorage.setItem(FREE_HIDDEN_KEY, JSON.stringify([...state.hiddenFreeSites])); } catch {} }
2533
+ const freeVisible = (id) => !state.hiddenFreeSites.has(id);
2534
+
2535
+ function orderedFreeSites(sites) {
2536
+ const ids = sites.map((s) => s.id);
2537
+ const arr = state.freeOrder.filter((id) => ids.includes(id));
2538
+ for (const id of ids) if (!arr.includes(id)) arr.push(id);
2539
+ state.freeOrder = arr;
2540
+ return arr.map((id) => sites.find((s) => s.id === id)).filter(Boolean);
2541
+ }
2542
+
2491
2543
  function renderFreePool() {
2492
2544
  const body = $('free-pool-body');
2493
2545
  if (!body) return;
2494
2546
  const sites = state.cookieSites;
2495
2547
  const canCapture = Boolean(window.creditdaddy && typeof window.creditdaddy.openAuthWindow === 'function');
2496
2548
  if (!sites) { body.innerHTML = T('<div class="dim">加载中…</div>'); loadCookieSites().then(() => renderFreePool()); return; }
2497
- const totalAccounts = sites.reduce((sum, s) => sum + (s.accounts?.length || 0), 0);
2549
+ const enabledSites = sites.filter((s) => s.disabled !== true);
2550
+ const totalAccounts = enabledSites.reduce((sum, s) => sum + (s.accounts?.length || 0), 0);
2498
2551
  if ($('free-total-count')) $('free-total-count').textContent = totalAccounts;
2499
- if ($('free-sites-count')) $('free-sites-count').textContent = sites.length;
2552
+ if ($('free-sites-count')) $('free-sites-count').textContent = enabledSites.length;
2500
2553
  if ($('cnt-free')) $('cnt-free').textContent = totalAccounts;
2501
2554
 
2502
- if (!sites.length) {
2555
+ if (!enabledSites.length) {
2503
2556
  body.innerHTML = T('<div class="muted-empty">暂无可捕获的供应商</div>');
2504
2557
  return;
2505
2558
  }
2506
2559
 
2507
- body.innerHTML = sites.map((site) => {
2560
+ const hiddenCount = enabledSites.filter((s) => !freeVisible(s.id)).length;
2561
+ const showHiddenBar = hiddenCount > 0;
2562
+ const visibleSites = orderedFreeSites(sites).filter((s) => freeVisible(s.id) && s.disabled !== true);
2563
+
2564
+ body.innerHTML = (showHiddenBar
2565
+ ? T('<div class="hint" style="grid-column:1/-1;display:flex;align-items:center;gap:8px">') + T('{n} 个站点已隐藏', { n: hiddenCount })
2566
+ + ' <button class="btn" id="free-show-all" style="padding:2px 10px;font-size:12px">' + T('全部显示') + '</button></div>'
2567
+ : '')
2568
+ + visibleSites.map((site) => {
2569
+ // entryOnly 站点(豆包):纯快捷入口卡——只出「打开」按钮与说明,无捕获/账号
2570
+ if (site.entryOnly === true) {
2571
+ const openBtn = canCapture
2572
+ ? '<button class="btn primary" data-open-doubao="1">' + I.play + T('打开豆包') + '</button>'
2573
+ : T('<button class="btn" disabled title="需要桌面版 CreditDaddy">打开豆包</button>');
2574
+ return '<div class="card" style="display:flex;flex-direction:column;justify-content:flex-start;gap:8px;padding:14px 16px;background:var(--surface);border:1px solid var(--border-subtle);border-radius:14px;box-shadow:var(--shadow)">'
2575
+ + '<div style="display:flex;align-items:center;justify-content:space-between;gap:10px">'
2576
+ + '<span style="font-size:14px;font-weight:700">' + esc(site.label || site.name) + '</span>' + openBtn + '</div>'
2577
+ + (site.hint ? '<div class="hint" style="margin:2px 0 0">' + esc(T(site.hint)) + '</div>' : '')
2578
+ + '</div>';
2579
+ }
2508
2580
  const rows = site.accounts || [];
2509
2581
  const stateTxt = rows.length
2510
2582
  ? T('<span class="badge ok">已捕获 ') + rows.length + T(' 个会话</span>')
2511
2583
  : T('<span class="badge limited">未捕获</span>');
2512
- const btn = canCapture
2513
- ? '<button class="btn primary" data-capture-site="' + esc(site.id) + '">' + I.plus + T('登录捕获') + '</button>'
2514
- : T('<button class="btn" disabled title="需要桌面版 CreditDaddy(无痕登录窗)">登录捕获</button>');
2584
+ const siteDisabled = site.disabled === true;
2585
+ const btn = siteDisabled
2586
+ ? T('<button class="btn" disabled title="网页 cookie 无法参与该站活动(活动校验客户端环境)">网页版(暂不可用)</button>')
2587
+ : canCapture
2588
+ ? '<button class="btn primary" data-capture-site="' + esc(site.id) + '">' + I.plus + T('网页版') + '</button>'
2589
+ : T('<button class="btn" disabled title="需要桌面版 CreditDaddy(无痕登录窗)">网页版</button>');
2515
2590
  // 「粘贴捕获」:部分站点(小米账号 SSO)登录态挂设备指纹,CD 无痕窗也会被静默 SSO
2516
2591
  // 拉回同一账号——多号并存走系统浏览器登录新号后手动粘 cookie(不依赖登录窗)。
2517
2592
  const pasteBtn = ' <button class="btn" data-paste-capture="' + esc(site.id) + '" title="' + T('在其他浏览器登录该站点后,把会话 cookie 粘贴进来捕获(多账号并存的可靠路径)') + '">' + T('粘贴捕获') + '</button>';
@@ -2605,28 +2680,44 @@ function renderFreePool() {
2605
2680
  + '</div>'
2606
2681
  + '<div style="display:flex;align-items:center;gap:8px;flex:none">'
2607
2682
  + '<span class="dim" style="font-size:11.5px">' + (acc.capturedAt ? esc(fmtTime(acc.capturedAt)) : '–') + '</span>'
2683
+ + (site.id === 'doubao' && canCapture ? '<button class="icon-btn" data-open-doubao="1" title="' + T('打开豆包客户端(签到请在客户端内完成)') + '">' + I.play + '</button>' : '')
2608
2684
  + '<button class="icon-btn" data-act="reprobe" data-site="' + esc(site.id) + '" data-id="' + esc(acc.id) + '" title="' + T('就地重探(免登录,用已存会话刷新身份与额度)') + '">' + I.refresh + '</button>'
2609
2685
  + '<button class="icon-btn del" data-act="del" data-id="' + esc(acc.id) + '" title="' + T('删除此账号') + '">' + I.trash + '</button>'
2610
2686
  + '</div></div>'
2611
2687
  + (metricHtml ? '<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding-left:14px">' + metricHtml + '</div>' : '')
2612
2688
  + '</div>';
2613
2689
  }).join('') + '</div>'
2614
- : T('<div class="muted-empty" style="padding:16px 0 6px">还没有捕获该站点的账号,点右上角「登录捕获」开始</div>');
2690
+ : T('<div class="muted-empty" style="padding:16px 0 6px">还没有该站点的账号,点右上角「网页版」登录后自动捕获</div>');
2615
2691
 
2616
2692
  // justify-content 不用 space-between:网格行高被最高卡撑齐后,单行账号会被推到卡底(沉底)
2617
- return '<div class="card" style="display:flex;flex-direction:column;justify-content:flex-start;gap:8px;padding:14px 16px;background:var(--surface);border:1px solid var(--border-subtle);border-radius:14px;box-shadow:var(--shadow)">'
2693
+ const eyeBtn = '<button class="icon-btn" data-free-hide="' + esc(site.id) + '" title="' + T('隐藏此站点卡片(「全部显示」可恢复)') + '">' + I.eyeOff + '</button>';
2694
+ return '<div class="card" draggable="true" data-id="' + esc(site.id) + '" style="display:flex;flex-direction:column;justify-content:flex-start;gap:8px;padding:14px 16px;background:var(--surface);border:1px solid var(--border-subtle);border-radius:14px;box-shadow:var(--shadow)">'
2618
2695
  + '<div><div style="display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px">'
2619
2696
  + '<div style="display:flex;align-items:center;gap:8px">'
2620
2697
  + '<span style="font-size:14px;font-weight:700">' + esc(site.label || site.name) + '</span>'
2621
2698
  + stateTxt
2622
2699
  + '</div>'
2623
- + '<div style="display:flex;align-items:center;gap:6px">' + btn + pasteBtn + clearBtn + '</div>'
2700
+ + '<div style="display:flex;align-items:center;gap:6px">' + btn + pasteBtn + clearBtn + eyeBtn + '</div>'
2624
2701
  + '</div>'
2625
2702
  + hint
2626
2703
  + '</div>'
2627
2704
  + accRows
2628
2705
  + '</div>';
2629
2706
  }).join('');
2707
+ bindCardDrag(body, {
2708
+ kind: 'free-site',
2709
+ onReorder: (drag, targetId) => {
2710
+ if (!drag.id) return;
2711
+ const ids = state.freeOrder.slice();
2712
+ const from = ids.indexOf(drag.id);
2713
+ if (from >= 0) ids.splice(from, 1);
2714
+ const ti = ids.indexOf(targetId);
2715
+ ids.splice(ti < 0 ? ids.length : ti, 0, drag.id);
2716
+ state.freeOrder = ids;
2717
+ saveFreeOrder();
2718
+ renderFreePool();
2719
+ },
2720
+ });
2630
2721
  }
2631
2722
 
2632
2723
  function renderCookieSites() {
@@ -2642,8 +2733,8 @@ function renderCookieSites() {
2642
2733
  ? T('<span class="dim">已捕获 ') + rows.length + T(' 个会话</span>')
2643
2734
  : T('<span class="dim">未捕获</span>');
2644
2735
  const btn = canCapture
2645
- ? '<button class="btn" data-capture-site="' + esc(site.id) + '">' + T('登录捕获') + '</button>'
2646
- : T('<button class="btn" disabled title="需要桌面版 CreditDaddy(无痕登录窗)">登录捕获</button>');
2736
+ ? '<button class="btn" data-capture-site="' + esc(site.id) + '">' + T('网页版') + '</button>'
2737
+ : T('<button class="btn" disabled title="需要桌面版 CreditDaddy(无痕登录窗)">网页版</button>');
2647
2738
  const hint = site.hint ? T('<div class="hint">') + esc(site.hint) + '</div>' : '';
2648
2739
  const accRows = rows.map((acc) =>
2649
2740
  '<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12px">'
@@ -2736,7 +2827,7 @@ $('free-settings-save')?.addEventListener('click', async () => {
2736
2827
  },
2737
2828
  });
2738
2829
  closeModal('m-free-settings');
2739
- toast(T('免费额度推送设置已保存'));
2830
+ toast(T('额度卡片推送设置已保存'));
2740
2831
  } catch (e) { toast(e.message, false); }
2741
2832
  });
2742
2833
  $('free-settings-push-now')?.addEventListener('click', async () => {
@@ -2780,7 +2871,10 @@ async function cookieSitePasteCapture(siteId) {
2780
2871
  try {
2781
2872
  const r = await api('/api/cookie-sites/' + encodeURIComponent(siteId) + '/capture', { json: { cookie: cookie.trim() } });
2782
2873
  if (r && r.ok) {
2783
- toast(T('{name} cookie 捕获成功,同步到 10Router 后卡片点亮', { name: site.name }), true);
2874
+ // 豆包不推 10R(无外部配额卡),文案不带「同步 10Router」以免误导
2875
+ toast(site.no10rSync
2876
+ ? T('{name} cookie 捕获成功,额度卡片已就绪', { name: site.name })
2877
+ : T('{name} cookie 捕获成功,同步到 10Router 后卡片点亮', { name: site.name }), true);
2784
2878
  state.cookieSites = null;
2785
2879
  loadCookieSites(true).then(() => { if (state.view === 'free') renderFreePool(); });
2786
2880
  } else toast(T('捕获失败'), false);
@@ -2796,7 +2890,10 @@ if (window.creditdaddy && typeof window.creditdaddy.onCookieCaptured === 'functi
2796
2890
  window.creditdaddy.onCookieCaptured((payload) => {
2797
2891
  if (payload && payload.ok === false) { toast(payload.error || T('cookie 捕获失败'), false); return; }
2798
2892
  const name = payload && payload.name;
2799
- toast(T('{name} cookie 捕获成功,同步到 10Router 后卡片点亮', { name: name || '' }), true);
2893
+ const site2 = (state.cookieSites || []).find((s) => s.name === name) || null;
2894
+ toast(site2?.no10rSync
2895
+ ? T('{name} cookie 捕获成功,额度卡片已就绪', { name: name || '' })
2896
+ : T('{name} cookie 捕获成功,同步到 10Router 后卡片点亮', { name: name || '' }), true);
2800
2897
  state.cookieSites = null;
2801
2898
  Promise.allSettled([loadCookieSites(true), refreshAccountList(), loadTrQuotas(true)])
2802
2899
  .then(() => {
@@ -2804,6 +2901,11 @@ if (window.creditdaddy && typeof window.creditdaddy.onCookieCaptured === 'functi
2804
2901
  if (state.view === 'free') renderFreePool();
2805
2902
  renderCookieSites();
2806
2903
  renderDash();
2904
+ // 收割时余额快照已随账号落库——账号卡上的余额读 state.quotas 缓存,
2905
+ // 不重拉就显示旧数字(实测:小米收割成功后卡面 ¥0.00 挂到手动刷新)。
2906
+ // 非 live 读快照零网络,成本可忽略;只刷 cookie-site 型账号。
2907
+ const ids = (state.accounts || []).filter((a) => a.meta?.cookieSiteId).map((a) => a.id);
2908
+ if (ids.length) loadQuotas(ids).then(renderDash);
2807
2909
  });
2808
2910
  });
2809
2911
  }
@@ -3657,7 +3759,7 @@ function openChangelogModal() {
3657
3759
  + '<div style="font-weight:700;margin:0 0 4px">v' + esc(s.version)
3658
3760
  + (s.date ? ' <span class="dim" style="font-weight:400;font-size:11px">' + esc(s.date) + '</span>' : '') + '</div>'
3659
3761
  + (s.theme ? '<div class="hint">' + strip(s.theme) + '</div>' : '')
3660
- + (s.topics && s.topics.length ? '<div class="hint" style="margin-top:4px">' + s.topics.map(strip).join(' · ') + '</div>' : '')
3762
+ + (s.topics && s.topics.length ? '<div class="hint" style="margin-top:4px">' + s.topics.map((t) => strip(T(t.trim()))).join(' · ') + '</div>' : '')
3661
3763
  + '</div>').join('')
3662
3764
  : T('<div class="hint">暂无本地更新日志,请到 GitHub Releases 查看。</div>');
3663
3765
  }).catch(openReleases);
@@ -3767,7 +3869,7 @@ $('znet-ok').addEventListener('click', async () => {
3767
3869
  state.zProxyFirst = r.proxyFirst === true;
3768
3870
  await loadZNet();
3769
3871
  $('znet-url').value = '';
3770
- $('znet-url').placeholder = state.zProxyUrl || (state.zHasEnvProxy ? T('跟随环境变量 HTTPS_PROXY') : 'http://192.168.31.101:7890');
3872
+ $('znet-url').placeholder = state.zProxyUrl ? T('当前代理:{url}(输入新值可更换,留空清除)', { url: state.zProxyUrl }) : (state.zHasEnvProxy ? T('跟随环境变量 HTTPS_PROXY') : 'http://192.168.31.101:7890');
3771
3873
  $('znet-state').textContent = T('已保存:') + (state.zProxyFirst === true ? T('代理优先') : T('直连优先'))
3772
3874
  + (state.zProxyUrl ? T(' · 代理 ') + state.zProxyUrl : '');
3773
3875
  toast(T('网络出口:') + (r.proxyFirst ? T('代理优先(直连兜底)') : T('直连优先(代理兜底)')) + (body.proxyUrl === '' ? T(',代理已清除') : T(',代理已保存')));
@@ -3844,10 +3946,11 @@ async function localScan() {
3844
3946
  const sub = c.kind === 'app'
3845
3947
  ? esc(c.source) + (c.email ? ' · ' + esc(mask(c.email)) : c.phone ? ' · ' + esc(c.phone) : '') + (c.expiresAt ? T(' · 有效期至 ') + esc(fmtTime(c.expiresAt)) : '')
3846
3948
  : esc(c.source) + ' · ' + esc(String(c.file || '').split(/[\\/]/).pop()) + T('(') + c.hits + T(' 处)');
3949
+ const intl = c.intlWarning ? '<div class="hint" style="color:var(--warn,#d4b83a);margin-top:4px">⚠ ' + esc(c.intlWarning) + '</div>' : '';
3847
3950
  const btns = c.imported ? T('<span class="pill ok">已导入</span>') + (c.provider ? '<button class="btn" data-cid="' + esc(c.id) + T('" title="用本机凭据更新 token / 有效期 / 邮箱">同步信息</button>') : '')
3848
3951
  : c.provider ? '<button class="btn primary" data-cid="' + esc(c.id) + T('">导入</button>')
3849
3952
  : '<button class="btn" data-cid="' + esc(c.id) + T('" data-prov="qoder">导入·国际</button><button class="btn" data-cid="') + esc(c.id) + T('" data-prov="qoder-cn">导入·国内</button>');
3850
- html += '<div class="cand"><div class="cand-main"><div class="cand-name">' + title + prov + '</div><div class="cand-sub">' + sub + '</div></div><div class="row">' + btns + '</div></div>';
3953
+ html += '<div class="cand"><div class="cand-main"><div class="cand-name">' + title + prov + '</div><div class="cand-sub">' + sub + '</div>' + intl + '</div><div class="row">' + btns + '</div></div>';
3851
3954
  }
3852
3955
  for (const e of r.errors || []) {
3853
3956
  html += T('<div class="hint" style="color:var(--err)">读取失败:') + esc(e.file) + T(':') + esc(e.error) + '</div>';
@@ -4308,7 +4411,6 @@ $('btn-comp').addEventListener('click', async () => {
4308
4411
  renderCompMenu();
4309
4412
  fillAutoCheckin();
4310
4413
  $('cfg-logs-display').value = logsDisplayDefault();
4311
- $('cfg-wb-activity').checked = wbActOn();
4312
4414
  // 网络出口(全局):预填当前顺序与脱敏代理地址
4313
4415
  await loadZNet();
4314
4416
  (state.zProxyFirst === true ? $('znet-proxy') : $('znet-direct')).checked = true;