creditdaddy 1.3.0 → 1.4.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.
- package/README.md +87 -13
- package/bin/creditdaddy.js +158 -8
- package/package.json +32 -29
- package/src/accounts.js +10 -0
- package/src/authDevice.js +5 -4
- package/src/bgdaemon.js +341 -0
- package/src/checkin.js +72 -41
- package/src/daemon.js +1266 -997
- package/src/i18n/en.json +1490 -0
- package/src/i18n/ja.json +1490 -0
- package/src/i18n/ko.json +1490 -0
- package/src/i18n/zh-TW.json +1490 -0
- package/src/i18n.js +47 -0
- package/src/logger.js +162 -20
- package/src/minimaxClient.js +28 -22
- package/src/minimaxGateway.js +74 -50
- package/src/minimaxLocal.js +61 -16
- package/src/mirasimClient.js +6 -1
- package/src/panel.html +1342 -591
- package/src/qoderClient.js +24 -4
- package/src/qoderUmid.js +4 -3
- package/src/refreshGuard.js +69 -0
- package/src/store.js +27 -1
- package/src/tenrouter.js +14 -7
- package/src/tenrouterAccounts.js +31 -3
- package/src/traeClient.js +6 -1
- package/src/traeGateway.js +698 -0
- package/src/transfer.js +44 -6
- package/src/workbuddyClient.js +14 -7
- package/src/zcodeAutoClaim.js +47 -7
- package/src/zcodeClient.js +76 -13
- package/src/zcodeGateway.js +41 -22
- package/src/zcodePlanShape.json +3 -3
package/src/panel.html
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9IjEwMjQiPjxwYXRoIGQ9Ik0zNzIuNDggMTM4LjY2NjY2N0ExMjggMTI4IDAgMCAwIDI5OC42NjY2NjcgMTAxLjU0NjY2N2ExMTcuNzYgMTE3Ljc2IDAgMCAwLTYyLjA4IDAgMTAuODggMTAuODggMCAwIDAtNS4xMiAxNi44NTMzMzNsNzEuMjUzMzMzIDkxLjUyLTgxLjI4IDI4LjM3MzMzM2ExMC42NjY2NjcgMTAuNjY2NjY3IDAgMCAwLTIuMzQ2NjY3IDE4Ljk4NjY2NyAyNDIuNTYgMjQyLjU2IDAgMCAwIDY5Ljc2IDMyLjg1MzMzMyAyNzMuNzA2NjY3IDI3My43MDY2NjcgMCAwIDAgNTYuOTYgOC4zMnoiIGZpbGw9IiNGRjhBMTQiPjwvcGF0aD48cGF0aCBkPSJNMjQ1Ljc2IDEzOC4wMjY2NjdhMTMwLjk4NjY2NyAxMzAuOTg2NjY3IDAgMCAxIDUyLjA1MzMzMyAyLjEzMzMzM0ExNDguMDUzMzMzIDE0OC4wNTMzMzMgMCAwIDEgMzY2LjcyIDE3MC42NjY2NjdsNS43Ni0zMi44NTMzMzRBMTI4IDEyOCAwIDAgMCAyOTguNjY2NjY3IDEwMS41NDY2NjdhMTE3Ljk3MzMzMyAxMTcuOTczMzMzIDAgMCAwLTYyLjA4IDAgMTAuODggMTAuODggMCAwIDAtNS4xMiAxNi44NTMzMzN6IiBmaWxsPSIjRkZBQTU0Ij48L3BhdGg+PHBhdGggZD0iTTIzNC42NjY2NjcgOTcwLjY2NjY2N2EyNzcuMzMzMzMzIDMyIDAgMSAwIDU1NC42NjY2NjYgMCAyNzcuMzMzMzMzIDMyIDAgMSAwLTU1NC42NjY2NjYgMFoiIGZpbGw9IiM0NTQxM0MiIG9wYWNpdHk9Ii4xNSI+PC9wYXRoPjxwYXRoIGQ9Ik02MjAuMzczMzMzIDQ2MC41ODY2NjdzMTUxLjA0LTkwLjY2NjY2NyAxODEuMzMzMzM0LTc0LjY2NjY2Ny0xMi41ODY2NjcgMTc0LjI5MzMzMy0xMDYuNjY2NjY3IDIxMy4zMzMzMzMtNzQuNjY2NjY3LTEzOC42NjY2NjctNzQuNjY2NjY3LTEzOC42NjY2NjZ6IiBmaWxsPSIjRkZFNTAwIj48L3BhdGg+PHBhdGggZD0iTTYyMC4zNzMzMzMgNTEzLjkyczE1MS4wNC05MC42NjY2NjcgMTgxLjMzMzMzNC03NC42NjY2NjdhMTQuNzIgMTQuNzIgMCAwIDEgNS4zMzMzMzMgNS45NzMzMzRjNi44MjY2NjctMjkuODY2NjY3IDUuMzMzMzMzLTUzLjU0NjY2Ny01LjMzMzMzMy01OS4zMDY2NjctMzAuMjkzMzMzLTE2LTE4MS4zMzMzMzMgNzQuNjY2NjY3LTE4MS4zMzMzMzQgNzQuNjY2NjY3YTM4Ni45ODY2NjcgMzg2Ljk4NjY2NyAwIDAgMCAwIDYxLjg2NjY2NmMtMC40MjY2NjctNS4zMzMzMzMgMC04LjUzMzMzMyAwLTguNTMzMzMzeiIgZmlsbD0iI0ZGRjQ4QyI+PC9wYXRoPjxwYXRoIGQ9Ik00MDMuNjI2NjY3IDQ2MC41ODY2NjdzLTE1MS4wNC05MC42NjY2NjctMTgxLjMzMzMzNC03NC42NjY2NjcgMTIuMzczMzMzIDE3NC4yOTMzMzMgMTA2LjY2NjY2NyAyMTMuMzMzMzMzIDc0LjY2NjY2Ny0xMzguNjY2NjY3IDc0LjY2NjY2Ny0xMzguNjY2NjY2eiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik0yMjIuMjkzMzMzIDQzOS4yNTMzMzNjMzAuMjkzMzMzLTE2IDE4MS4zMzMzMzMgNzQuNjY2NjY3IDE4MS4zMzMzMzQgNzQuNjY2NjY3djguNTMzMzMzYTM3My41NDY2NjcgMzczLjU0NjY2NyAwIDAgMCAwLTYxLjg2NjY2NnMtMTUxLjA0LTkwLjY2NjY2Ny0xODEuMzMzMzM0LTc0LjY2NjY2N2MtMTAuNjY2NjY3IDUuNzYtMTIuMTYgMjkuNDQtNS4zMzMzMzMgNTkuMzA2NjY3YTEzLjQ0IDEzLjQ0IDAgMCAxIDUuMzMzMzMzLTUuOTczMzM0eiIgZmlsbD0iI0ZGRjQ4QyI+PC9wYXRoPjxwYXRoIGQ9Ik0yODggNjUwLjY2NjY2N2EyNjYuNjY2NjY3IDIyNCA5MCAxIDAgNDQ4IDAgMjY2LjY2NjY2NyAyMjQgOTAgMSAwLTQ0OCAwWiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik02ODAuMTA2NjY3IDQ3NC4yNGEyMjQgMjI0IDAgMCAxLTMzNi4yMTMzMzQgMEEyMTAuNTYgMjEwLjU2IDAgMCAxIDUxMiAzODRhMjEwLjU2IDIxMC41NiAwIDAgMSAxNjguMTA2NjY3IDkwLjI0eiIgZmlsbD0iI0VCQ0IwMCI+PC9wYXRoPjxwYXRoIGQ9Ik01MTIgMjY2LjY2NjY2N20tMjI0IDBhMjI0IDIyNCAwIDEgMCA0NDggMCAyMjQgMjI0IDAgMSAwLTQ0OCAwWiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik01MTIgMTAxLjk3MzMzM2EyMjQuMjEzMzMzIDIyNC4yMTMzMzMgMCAwIDEgMjIxLjg2NjY2NyAxOTQuMzQ2NjY3IDIyNCAyMjQgMCAxIDAtNDQzLjczMzMzNCAwQTIyMy43ODY2NjcgMjIzLjc4NjY2NyAwIDAgMSA1MTIgMTAxLjk3MzMzM3oiIGZpbGw9IiNGRkY0OEMiPjwvcGF0aD48cGF0aCBkPSJNODMzLjQ5MzMzMyA1OTguODI2NjY3bC03MC40IDcwLjRhMTguMzQ2NjY3IDE4LjM0NjY2NyAwIDAgMS0yNS44MTMzMzMgMEw2NDQuMDUzMzMzIDU3NkExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAwIDYxOC42NjY2NjcgNTc2bC05My4wMTMzMzQgOTMuMjI2NjY3YTE4Ljc3MzMzMyAxOC43NzMzMzMgMCAwIDEtMjYuMDI2NjY2IDBMNDA1LjMzMzMzMyA1NzZhMTguMTMzMzMzIDE4LjEzMzMzMyAwIDAgMC0yNS44MTMzMzMgMGwtOTMuMjI2NjY3IDkzLjIyNjY2N2ExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAxLTI1LjgxMzMzMyAwbC03MC40LTcwLjRhOS4xNzMzMzMgOS4xNzMzMzMgMCAwIDAtMTUuNzg2NjY3IDYuODI2NjY2QzE4OS44NjY2NjcgODQxLjgxMzMzMyAzMzQuOTMzMzMzIDk2MCA1MTIgOTYwczMyMi4xMzMzMzMtMTE4LjE4NjY2NyAzMzcuMjgtMzU0LjM0NjY2N2E5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjctNi44MjY2NjZ6IiBmaWxsPSIjRjBGMEYwIj48L3BhdGg+PHBhdGggZD0iTTE5MC41MDY2NjcgNjY1LjgxMzMzM2w3MC40IDYxLjIyNjY2N2EyMS4zMzMzMzMgMjEuMzMzMzMzIDAgMCAwIDI1LjgxMzMzMyAwbDkzLjIyNjY2Ny04MS4wNjY2NjdhMjEuMzMzMzMzIDIxLjMzMzMzMyAwIDAgMSAyNS44MTMzMzMgMGw5My4yMjY2NjcgODEuMDY2NjY3YTIxLjMzMzMzMyAyMS4zMzMzMzMgMCAwIDAgMjYuMDI2NjY2IDBsOTMuNjUzMzM0LTgxLjA2NjY2N2EyMS4zMzMzMzMgMjEuMzMzMzMzIDAgMCAxIDI2LjAyNjY2NiAwbDkzLjIyNjY2NyA4MS4wNjY2NjdhMjEuMzMzMzMzIDIxLjMzMzMzMyAwIDAgMCAyNS44MTMzMzMgMGw3MC40LTYxLjIyNjY2N2E5LjgxMzMzMyA5LjgxMzMzMyAwIDAgMSA4LjUzMzMzNC0xLjkyIDUzNi4xMDY2NjcgNTM2LjEwNjY2NyAwIDAgMCA3LjI1MzMzMy01OC4yNCA5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjctNi44MjY2NjZsLTcwLjQgNzAuNGExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAxLTI1LjgxMzMzMyAwTDY0NC4wNTMzMzMgNTc2QTE4LjM0NjY2NyAxOC4zNDY2NjcgMCAwIDAgNjE4LjY2NjY2NyA1NzZsLTkzLjAxMzMzNCA5My4yMjY2NjdhMTguNzczMzMzIDE4Ljc3MzMzMyAwIDAgMS0yNi4wMjY2NjYgMEw0MDUuMzMzMzMzIDU3NmExOC4xMzMzMzMgMTguMTMzMzMzIDAgMCAwLTI1LjgxMzMzMyAwbC05My4yMjY2NjcgOTMuMjI2NjY3YTE4LjM0NjY2NyAxOC4zNDY2NjcgMCAwIDEtMjUuODEzMzMzIDBsLTcwLjQtNzAuNGE5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjcgNi44MjY2NjYgNTM3LjE3MzMzMyA1MzcuMTczMzMzIDAgMCAwIDcuMDQgNTguMjQgMTAuMDI2NjY3IDEwLjAyNjY2NyAwIDAgMSA5LjE3MzMzNCAxLjkyeiIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPjxwYXRoIGQ9Ik00OTguMTMzMzMzIDE4NC41MzMzMzNtLTI5LjAxMzMzMyAwYTI5LjAxMzMzMyAyOS4wMTMzMzMgMCAxIDAgNTguMDI2NjY3IDAgMjkuMDEzMzMzIDI5LjAxMzMzMyAwIDEgMC01OC4wMjY2NjcgMFoiIGZpbGw9IiM0NTQxM0MiPjwvcGF0aD48cGF0aCBkPSJNNTc2IDI5MC45ODY2NjdjMCAxMS43MzMzMzMtMTUuNzg2NjY3IDIxLjMzMzMzMy0zNC45ODY2NjcgMjEuMzMzMzMzcy0zNS4yLTkuNi0zNS4yLTIxLjMzMzMzMyAxNS43ODY2NjctMjEuMzMzMzMzIDM1LjItMjEuMzMzMzM0IDM0Ljk4NjY2NyA5LjYgMzQuOTg2NjY3IDIxLjMzMzMzNHoiIGZpbGw9IiNGRkY0OEMiPjwvcGF0aD48L3N2Zz4=">
|
|
8
8
|
<style>
|
|
9
9
|
:root {
|
|
10
|
+
color-scheme: light;
|
|
10
11
|
--bg: #f5f6f8; --surface: #ffffff; --surface-2: #fafafa; --tertiary: #f0f0f0; --hover: #f5f6f8; --active: #eef0f3;
|
|
11
12
|
--border-subtle: #eeeeee; --border: #e5e5e5; --border-strong: #cccccc;
|
|
12
13
|
--text: #1f1f1f; --text-2: #5f6368; --text-3: #999999;
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
--mask: rgba(0, 0, 0, .4);
|
|
18
19
|
}
|
|
19
20
|
html[data-theme="dark"] {
|
|
21
|
+
color-scheme: dark;
|
|
20
22
|
--bg: #0a0a0c; --surface: #131316; --surface-2: #17171b; --tertiary: #1b1b20; --hover: rgba(255,255,255,.06); --active: rgba(255,255,255,.1);
|
|
21
23
|
--border-subtle: #202025; --border: rgba(255,255,255,.13); --border-strong: #3a3a42;
|
|
22
24
|
--text: #e6e6e9; --text-2: rgba(230,230,233,.72); --text-3: rgba(230,230,233,.5);
|
|
@@ -32,7 +34,7 @@
|
|
|
32
34
|
font-size: 13px; color: var(--text); background: var(--bg); min-height: 100vh;
|
|
33
35
|
-webkit-font-smoothing: antialiased; transition: background .2s;
|
|
34
36
|
}
|
|
35
|
-
button, input, select { font-family: inherit; font-size: inherit; color: inherit; }
|
|
37
|
+
button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }
|
|
36
38
|
.wrap { max-width: 1120px; margin: 0 auto; padding: 18px 22px 40px; }
|
|
37
39
|
.num { font-variant-numeric: tabular-nums; }
|
|
38
40
|
|
|
@@ -41,11 +43,56 @@
|
|
|
41
43
|
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
42
44
|
.logo { width: 36px; height: 36px; flex-shrink: 0; display: block; }
|
|
43
45
|
.title { font-family: "Avenir Next", Avenir, "Segoe UI Variable Display", "Segoe UI", sans-serif; font-size: 17px; font-weight: 750; letter-spacing: -.5px; line-height: 1.15; }
|
|
44
|
-
.ver { color: var(--text-3); font-size: 11px; line-height: 1.
|
|
45
|
-
.head-right { display: flex; align-items: center; gap:
|
|
46
|
+
.ver { color: var(--text-3); font-size: 11px; line-height: 1.55; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
47
|
+
.head-right { display: flex; align-items: center; gap: 8px; color: var(--text-3); font-size: 12px; }
|
|
48
|
+
/* 状态胶囊 + 分组分隔线:把「下一次调度」这类只读状态与右侧操作按钮区分开 */
|
|
49
|
+
.sched-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px 5px 9px; border-radius: 999px; background: var(--tertiary); color: var(--text-3); font-size: 12px; white-space: nowrap; }
|
|
50
|
+
.sched-chip svg { width: 13px; height: 13px; flex-shrink: 0; opacity: .8; }
|
|
51
|
+
.sched-chip b { font-weight: 650; color: var(--text-2); font-variant-numeric: tabular-nums; }
|
|
52
|
+
.sched-chip.busy { background: rgba(var(--accent-rgb), .14); color: var(--accent); }
|
|
53
|
+
.sched-chip.busy b { color: var(--accent); }
|
|
54
|
+
.sched-chip:empty { display: none; }
|
|
55
|
+
.head-sep { width: 1px; height: 18px; background: var(--border); flex: none; }
|
|
56
|
+
/* 右上角聚合菜单:锚定在按钮下方右对齐的下拉(不是居中弹窗)。
|
|
57
|
+
宽高固定、内部分区(工作区 / 外观 / 关于)——一眼能分清「设置值」与「一次性动作」。 */
|
|
58
|
+
.hmenu { position: relative; display: inline-flex; }
|
|
59
|
+
/* 面板设置弹出层:与聚合菜单同一视觉语言,紧贴按钮右对齐展开,不再是全屏遮罩的居中弹窗 */
|
|
60
|
+
.head-setpop { position: relative; display: inline-flex; }
|
|
61
|
+
.setpop { position: absolute; right: 0; top: calc(100% + 10px); z-index: 60; width: min(560px, calc(100vw - 24px)); display: none; }
|
|
62
|
+
.setpop.show { display: block; }
|
|
63
|
+
.setpop .modal { width: 100%; max-height: calc(100vh - 120px); border: 1px solid var(--border-subtle); padding: 16px 18px; }
|
|
64
|
+
.hmenu-pop { position: absolute; right: 0; top: calc(100% + 10px); z-index: 60; width: 252px; background: var(--surface); border: 1px solid var(--border-subtle); border-radius: 14px; padding: 7px; box-shadow: var(--shadow); display: none; }
|
|
65
|
+
.hmenu-pop.show { display: block; }
|
|
66
|
+
/* 分区:块级流,不用 flex —— 否则分段控件会被当成 flex 子项压扁(曾出现与下一分区重叠) */
|
|
67
|
+
.hmenu-group { padding: 3px 0; }
|
|
68
|
+
.hmenu-group + .hmenu-group { margin-top: 4px; padding-top: 7px; border-top: 1px solid var(--border-subtle); }
|
|
69
|
+
.hmenu-title { padding: 4px 10px 5px; font-size: 10.5px; font-weight: 700; letter-spacing: .6px; color: var(--text-3); text-transform: uppercase; }
|
|
70
|
+
.hmenu-item { display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 10px; border: 0; border-radius: 8px; background: transparent; color: var(--text-2); font-size: 13px; font-weight: 600; cursor: pointer; text-align: left; font-family: inherit; text-decoration: none; }
|
|
71
|
+
.hmenu-item:hover { background: var(--hover); color: var(--text); }
|
|
72
|
+
.hmenu-item svg { width: 15px; height: 15px; flex-shrink: 0; }
|
|
73
|
+
.hmenu-item.danger { color: var(--err); }
|
|
74
|
+
.hmenu-item.danger:hover { background: var(--err-soft); color: var(--err); }
|
|
75
|
+
.hmenu-clip { margin-left: auto; color: var(--text-3); font-size: 11px; font-weight: 600; max-width: 116px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
76
|
+
/* 外观 / 语言:三态分段控件(跟随系统 / 亮色 / 暗色),当前态高亮 —— 主题只有这一个真相源。
|
|
77
|
+
inline-flex + 外层留白:分段是块级流里的一个整体,高度由按钮撑开(不能是 flex 子项被压扁)。
|
|
78
|
+
类名必须是 mode-seg:.seg 已被额度水位条的分段占用(见下方 .bar 段),同名会互相覆盖——
|
|
79
|
+
分段控件会被压成 5px 绿条,水位条的分段则会各带 12px 横向 margin 而溢出裁切。 */
|
|
80
|
+
.mode-seg { display: inline-flex; gap: 3px; margin: 2px 6px 4px; padding: 3px; border-radius: 9px; background: var(--tertiary); }
|
|
81
|
+
/* 语言五档:等宽铺满弹窗宽度,避免中/英/日/韩标签长短不一导致右侧留白 */
|
|
82
|
+
.lang-seg { display: flex; width: calc(100% - 12px); }
|
|
83
|
+
.lang-seg .seg-btn { flex: 1 1 0; padding: 6px 0; }
|
|
84
|
+
.seg-btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px; padding: 6px 9px; border: 0; border-radius: 7px; background: transparent; color: var(--text-3); font-size: 12px; font-weight: 600; line-height: 1; cursor: pointer; font-family: inherit; white-space: nowrap; }
|
|
85
|
+
.seg-btn svg { width: 13px; height: 13px; flex-shrink: 0; display: block; }
|
|
86
|
+
.seg-btn:hover { color: var(--text); }
|
|
87
|
+
.seg-btn.on { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }
|
|
88
|
+
html[data-theme="dark"] .seg-btn.on { background: var(--active); box-shadow: none; }
|
|
89
|
+
.hmenu-ver { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 7px 10px 4px; margin-top: 4px; border-top: 1px solid var(--border-subtle); color: var(--text-3); font-size: 11px; }
|
|
90
|
+
.hmenu-ver a { color: var(--text-3); text-decoration: none; }
|
|
91
|
+
.hmenu-ver a:hover { color: var(--text); }
|
|
92
|
+
@media (max-width: 720px) { .sched-chip { display: none; } .head-sep { display: none; } }
|
|
46
93
|
/* 面板设置(右上角齿轮 → 弹窗):访问密码 + 组件显隐与排序 + 账号卡片排序 */
|
|
47
94
|
.comp-menu { display: flex; flex-direction: column; gap: 8px; }
|
|
48
|
-
.comp-menu-title { font-size:
|
|
95
|
+
.comp-menu-title { font-size: 12px; color: var(--text-2); font-weight: 650; margin: 12px 0 4px; padding-top: 10px; border-top: 1px dashed var(--border-subtle); }
|
|
49
96
|
.comp-menu-title:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
|
|
50
97
|
.comp-menu .check { margin-right: 0; font-size: 12.5px; color: var(--text); }
|
|
51
98
|
/* 产品页 notice 的功能分区行 */
|
|
@@ -63,12 +110,25 @@
|
|
|
63
110
|
.comp-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
|
|
64
111
|
.comp-row.off .check { opacity: .45; }
|
|
65
112
|
.comp-ops { display: inline-flex; gap: 2px; flex: none; }
|
|
113
|
+
/* 横向组件 chips:一行放下全部产品,不再一条一行占半屏 */
|
|
114
|
+
.comp-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
115
|
+
.comp-chip { display: inline-flex; align-items: center; gap: 0; padding: 3px 4px 3px 10px; border-radius: 999px; background: var(--hover); font-size: 12px; font-weight: 600; color: var(--text); }
|
|
116
|
+
.comp-chip.off { opacity: .5; filter: grayscale(.9); }
|
|
117
|
+
.comp-chip[data-comp-toggle] { cursor: pointer; }
|
|
118
|
+
.comp-chip .sort-name { max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
119
|
+
.comp-chip .icon-btn.mini { width: 20px; height: 20px; }
|
|
120
|
+
.comp-chip[draggable] { cursor: grab; }
|
|
121
|
+
.comp-chip.dragging { opacity: .4; }
|
|
122
|
+
.comp-chip.drop-before { box-shadow: -3px 0 0 0 var(--ok); }
|
|
123
|
+
.comp-chip.drop-after { box-shadow: 3px 0 0 0 var(--ok); }
|
|
124
|
+
.drag-grip { color: var(--text-3); display: inline-flex; }
|
|
125
|
+
.drag-grip svg { width: 12px; height: 12px; }
|
|
66
126
|
.icon-btn.mini { width: 20px; height: 20px; border-radius: 6px; font-size: 11px; line-height: 1; padding: 0; }
|
|
67
127
|
.icon-btn.mini svg { width: 12px; height: 12px; }
|
|
68
128
|
.sort-sec { border-top: 1px dashed var(--border-subtle); padding-top: 6px; font-size: 12px; }
|
|
69
129
|
.sort-sec summary { cursor: pointer; color: var(--text-2); user-select: none; display: flex; align-items: center; gap: 6px; }
|
|
70
130
|
.sort-sec summary::-webkit-details-marker { display: none; }
|
|
71
|
-
.sort-hint { font-size: 11px; color: var(--text-3); margin:
|
|
131
|
+
.sort-hint { font-size: 11px; color: var(--text-3); line-height: 1.65; margin: 4px 0 3px; }
|
|
72
132
|
.sort-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; padding: 2px 0; }
|
|
73
133
|
.sort-row .sort-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
74
134
|
.card.dragging, .tr-card.dragging { opacity: .45; }
|
|
@@ -96,6 +156,8 @@
|
|
|
96
156
|
.btn svg, .icon-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
|
|
97
157
|
.icon-btn { width: 28px; height: 28px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid transparent; background: var(--hover); color: var(--text-2); cursor: pointer; transition: all .15s; padding: 0; }
|
|
98
158
|
.icon-btn:hover { background: var(--active); color: var(--text); }
|
|
159
|
+
.icon-btn:active { transform: scale(.9); }
|
|
160
|
+
.btn:active, .seg-btn:active, .hmenu-item:active { transform: translateY(1px); }
|
|
99
161
|
.icon-btn.del:hover { background: #ffecec; color: #f53f3f; }
|
|
100
162
|
html[data-theme="dark"] .icon-btn.del:hover { background: rgba(245,63,63,.15); }
|
|
101
163
|
.icon-btn.del.armed { background: #f53f3f; color: #fff; }
|
|
@@ -116,6 +178,8 @@
|
|
|
116
178
|
.badge.intl { color: #3b82f6; background: rgba(59,130,246,.1); }
|
|
117
179
|
.badge.cn { color: #d97706; background: rgba(245,158,11,.12); }
|
|
118
180
|
.badge.prod { font-weight: 600; }
|
|
181
|
+
.badge.web { font-weight: 600; cursor: help; }
|
|
182
|
+
.badge.web-warn { color: var(--warn); background: var(--warn-soft); }
|
|
119
183
|
.badge.prod.qoder { color: #6d28d9; background: rgba(124,58,237,.1); }
|
|
120
184
|
.badge.prod.workbuddy { color: #0f766e; background: rgba(20,184,166,.12); }
|
|
121
185
|
html[data-theme="dark"] .badge.prod.qoder { color: #b8a4ff; }
|
|
@@ -182,13 +246,32 @@
|
|
|
182
246
|
.pcard.open .fold-body { display: block; }
|
|
183
247
|
.logs { margin-top: 8px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 11px; line-height: 1.65; max-height: 240px; overflow-y: auto; background: var(--tertiary); border-radius: 9px; padding: 8px 10px; }
|
|
184
248
|
.logs .info { color: var(--text-2); } .logs .warn { color: var(--warn); } .logs .error { color: var(--err); } .logs .debug { color: var(--text-3); }
|
|
249
|
+
.logs .lvl-ico { margin-right: 5px; font-weight: 700; }
|
|
250
|
+
.logs .warn .lvl-ico { color: var(--warn); }
|
|
251
|
+
.logs .error .lvl-ico { color: var(--err); }
|
|
252
|
+
.log-filter { display: inline-flex; gap: 2px; padding: 2px; border-radius: 9px; background: var(--hover); }
|
|
253
|
+
.log-filter button { border: none; background: transparent; color: var(--text-2); font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 7px; cursor: pointer; }
|
|
254
|
+
.log-filter button.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
|
255
|
+
html[data-theme="dark"] .log-filter button.active { background: rgba(255,255,255,.1); }
|
|
185
256
|
.collapsed .logs { display: none; }
|
|
186
257
|
|
|
258
|
+
/* 首次加载态:HTML 静态可见(无需等 JS/词条),数据首屏后淡出 */
|
|
259
|
+
#boot-busy { position: fixed; inset: 0; z-index: 95; display: flex; align-items: center; justify-content: center; background: var(--bg); transition: opacity .25s; }
|
|
260
|
+
#boot-busy.fade { opacity: 0; pointer-events: none; }
|
|
261
|
+
.lb-card.boot { padding: 18px 26px; font-size: 15px; }
|
|
262
|
+
|
|
263
|
+
/* 语言切换加载态:点击分段按钮立即给反馈,落盘 + 整页刷新期间盖住 */
|
|
264
|
+
#locale-busy { position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center; background: var(--mask); }
|
|
265
|
+
#locale-busy[hidden] { display: none; }
|
|
266
|
+
.lb-card { display: flex; align-items: center; gap: 10px; background: var(--surface); border: 1px solid var(--border-subtle); border-radius: 14px; padding: 14px 20px; box-shadow: var(--shadow); font-size: 13px; font-weight: 600; color: var(--text); }
|
|
267
|
+
.lb-spin { width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--border-subtle); border-top-color: var(--ok); animation: spin .8s linear infinite; }
|
|
268
|
+
|
|
187
269
|
/* modal */
|
|
188
270
|
.mask { position: fixed; inset: 0; z-index: 50; background: var(--mask); display: none; align-items: center; justify-content: center; padding: 16px; }
|
|
189
271
|
.mask.show { display: flex; }
|
|
190
272
|
.modal { width: 560px; max-width: 100%; max-height: 90vh; overflow-y: auto; background: var(--surface); border-radius: 14px; padding: 16px 18px; box-shadow: var(--shadow); }
|
|
191
273
|
.modal.sm { width: 360px; }
|
|
274
|
+
.changelog-sec + .changelog-sec { border-top: 1px dashed var(--border-subtle); margin-top: 10px; padding-top: 10px; }
|
|
192
275
|
.modal-title { display: flex; align-items: center; justify-content: space-between; font-size: 14px; font-weight: 700; margin-bottom: 12px; }
|
|
193
276
|
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 14px; }
|
|
194
277
|
.close { border: none; background: none; color: var(--text-3); font-size: 16px; cursor: pointer; padding: 2px 6px; border-radius: 6px; }
|
|
@@ -198,13 +281,15 @@
|
|
|
198
281
|
.tab:hover { color: var(--text); }
|
|
199
282
|
.tab.active { background: var(--btn-bg); color: var(--btn-fg); }
|
|
200
283
|
.pane { display: none; } .pane.active { display: block; }
|
|
201
|
-
.field { display: flex; flex-direction: column; gap:
|
|
202
|
-
.field label { font-size:
|
|
203
|
-
.input { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2); outline: none; transition: border-color .15s; }
|
|
284
|
+
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
|
|
285
|
+
.field label { font-size: 12.5px; color: var(--text-2); font-weight: 650; }
|
|
286
|
+
.input { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface-2); color: var(--text); outline: none; transition: border-color .15s; }
|
|
287
|
+
.input::placeholder { color: var(--text-3); opacity: .7; }
|
|
204
288
|
.input:focus { border-color: #4f86ff; }
|
|
289
|
+
.input:disabled { opacity: .6; cursor: not-allowed; }
|
|
205
290
|
.input.mono { font-family: ui-monospace, Consolas, monospace; }
|
|
206
291
|
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
207
|
-
.hint { font-size:
|
|
292
|
+
.hint { font-size: 11px; color: var(--text-3); line-height: 1.65; }
|
|
208
293
|
.hint a { color: #4f86ff; word-break: break-all; }
|
|
209
294
|
.box { border: 1px solid var(--border-subtle); border-radius: 10px; padding: 10px 12px; margin-top: 10px; background: var(--surface-2); }
|
|
210
295
|
.cand { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border: 1px solid var(--border-subtle); border-radius: 10px; background: var(--surface); }
|
|
@@ -340,30 +425,132 @@
|
|
|
340
425
|
</style>
|
|
341
426
|
</head>
|
|
342
427
|
<body>
|
|
428
|
+
<!-- 首次加载态:数据首屏后由 boot 隐藏 -->
|
|
429
|
+
<div id="boot-busy"><div class="lb-card boot"><span class="lb-spin"></span><strong>CreditDaddy</strong></div></div>
|
|
343
430
|
<div class="wrap">
|
|
344
431
|
<header class="head">
|
|
345
432
|
<div class="brand">
|
|
346
433
|
<img class="logo" alt="" src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9IjEwMjQiPjxwYXRoIGQ9Ik0zNzIuNDggMTM4LjY2NjY2N0ExMjggMTI4IDAgMCAwIDI5OC42NjY2NjcgMTAxLjU0NjY2N2ExMTcuNzYgMTE3Ljc2IDAgMCAwLTYyLjA4IDAgMTAuODggMTAuODggMCAwIDAtNS4xMiAxNi44NTMzMzNsNzEuMjUzMzMzIDkxLjUyLTgxLjI4IDI4LjM3MzMzM2ExMC42NjY2NjcgMTAuNjY2NjY3IDAgMCAwLTIuMzQ2NjY3IDE4Ljk4NjY2NyAyNDIuNTYgMjQyLjU2IDAgMCAwIDY5Ljc2IDMyLjg1MzMzMyAyNzMuNzA2NjY3IDI3My43MDY2NjcgMCAwIDAgNTYuOTYgOC4zMnoiIGZpbGw9IiNGRjhBMTQiPjwvcGF0aD48cGF0aCBkPSJNMjQ1Ljc2IDEzOC4wMjY2NjdhMTMwLjk4NjY2NyAxMzAuOTg2NjY3IDAgMCAxIDUyLjA1MzMzMyAyLjEzMzMzM0ExNDguMDUzMzMzIDE0OC4wNTMzMzMgMCAwIDEgMzY2LjcyIDE3MC42NjY2NjdsNS43Ni0zMi44NTMzMzRBMTI4IDEyOCAwIDAgMCAyOTguNjY2NjY3IDEwMS41NDY2NjdhMTE3Ljk3MzMzMyAxMTcuOTczMzMzIDAgMCAwLTYyLjA4IDAgMTAuODggMTAuODggMCAwIDAtNS4xMiAxNi44NTMzMzN6IiBmaWxsPSIjRkZBQTU0Ij48L3BhdGg+PHBhdGggZD0iTTIzNC42NjY2NjcgOTcwLjY2NjY2N2EyNzcuMzMzMzMzIDMyIDAgMSAwIDU1NC42NjY2NjYgMCAyNzcuMzMzMzMzIDMyIDAgMSAwLTU1NC42NjY2NjYgMFoiIGZpbGw9IiM0NTQxM0MiIG9wYWNpdHk9Ii4xNSI+PC9wYXRoPjxwYXRoIGQ9Ik02MjAuMzczMzMzIDQ2MC41ODY2NjdzMTUxLjA0LTkwLjY2NjY2NyAxODEuMzMzMzM0LTc0LjY2NjY2Ny0xMi41ODY2NjcgMTc0LjI5MzMzMy0xMDYuNjY2NjY3IDIxMy4zMzMzMzMtNzQuNjY2NjY3LTEzOC42NjY2NjctNzQuNjY2NjY3LTEzOC42NjY2NjZ6IiBmaWxsPSIjRkZFNTAwIj48L3BhdGg+PHBhdGggZD0iTTYyMC4zNzMzMzMgNTEzLjkyczE1MS4wNC05MC42NjY2NjcgMTgxLjMzMzMzNC03NC42NjY2NjdhMTQuNzIgMTQuNzIgMCAwIDEgNS4zMzMzMzMgNS45NzMzMzRjNi44MjY2NjctMjkuODY2NjY3IDUuMzMzMzMzLTUzLjU0NjY2Ny01LjMzMzMzMy01OS4zMDY2NjctMzAuMjkzMzMzLTE2LTE4MS4zMzMzMzMgNzQuNjY2NjY3LTE4MS4zMzMzMzQgNzQuNjY2NjY3YTM4Ni45ODY2NjcgMzg2Ljk4NjY2NyAwIDAgMCAwIDYxLjg2NjY2NmMtMC40MjY2NjctNS4zMzMzMzMgMC04LjUzMzMzMyAwLTguNTMzMzMzeiIgZmlsbD0iI0ZGRjQ4QyI+PC9wYXRoPjxwYXRoIGQ9Ik00MDMuNjI2NjY3IDQ2MC41ODY2NjdzLTE1MS4wNC05MC42NjY2NjctMTgxLjMzMzMzNC03NC42NjY2NjcgMTIuMzczMzMzIDE3NC4yOTMzMzMgMTA2LjY2NjY2NyAyMTMuMzMzMzMzIDc0LjY2NjY2Ny0xMzguNjY2NjY3IDc0LjY2NjY2Ny0xMzguNjY2NjY2eiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik0yMjIuMjkzMzMzIDQzOS4yNTMzMzNjMzAuMjkzMzMzLTE2IDE4MS4zMzMzMzMgNzQuNjY2NjY3IDE4MS4zMzMzMzQgNzQuNjY2NjY3djguNTMzMzMzYTM3My41NDY2NjcgMzczLjU0NjY2NyAwIDAgMCAwLTYxLjg2NjY2NnMtMTUxLjA0LTkwLjY2NjY2Ny0xODEuMzMzMzM0LTc0LjY2NjY2N2MtMTAuNjY2NjY3IDUuNzYtMTIuMTYgMjkuNDQtNS4zMzMzMzMgNTkuMzA2NjY3YTEzLjQ0IDEzLjQ0IDAgMCAxIDUuMzMzMzMzLTUuOTczMzM0eiIgZmlsbD0iI0ZGRjQ4QyI+PC9wYXRoPjxwYXRoIGQ9Ik0yODggNjUwLjY2NjY2N2EyNjYuNjY2NjY3IDIyNCA5MCAxIDAgNDQ4IDAgMjY2LjY2NjY2NyAyMjQgOTAgMSAwLTQ0OCAwWiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik02ODAuMTA2NjY3IDQ3NC4yNGEyMjQgMjI0IDAgMCAxLTMzNi4yMTMzMzQgMEEyMTAuNTYgMjEwLjU2IDAgMCAxIDUxMiAzODRhMjEwLjU2IDIxMC41NiAwIDAgMSAxNjguMTA2NjY3IDkwLjI0eiIgZmlsbD0iI0VCQ0IwMCI+PC9wYXRoPjxwYXRoIGQ9Ik01MTIgMjY2LjY2NjY2N20tMjI0IDBhMjI0IDIyNCAwIDEgMCA0NDggMCAyMjQgMjI0IDAgMSAwLTQ0OCAwWiIgZmlsbD0iI0ZGRTUwMCI+PC9wYXRoPjxwYXRoIGQ9Ik01MTIgMTAxLjk3MzMzM2EyMjQuMjEzMzMzIDIyNC4yMTMzMzMgMCAwIDEgMjIxLjg2NjY2NyAxOTQuMzQ2NjY3IDIyNCAyMjQgMCAxIDAtNDQzLjczMzMzNCAwQTIyMy43ODY2NjcgMjIzLjc4NjY2NyAwIDAgMSA1MTIgMTAxLjk3MzMzM3oiIGZpbGw9IiNGRkY0OEMiPjwvcGF0aD48cGF0aCBkPSJNODMzLjQ5MzMzMyA1OTguODI2NjY3bC03MC40IDcwLjRhMTguMzQ2NjY3IDE4LjM0NjY2NyAwIDAgMS0yNS44MTMzMzMgMEw2NDQuMDUzMzMzIDU3NkExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAwIDYxOC42NjY2NjcgNTc2bC05My4wMTMzMzQgOTMuMjI2NjY3YTE4Ljc3MzMzMyAxOC43NzMzMzMgMCAwIDEtMjYuMDI2NjY2IDBMNDA1LjMzMzMzMyA1NzZhMTguMTMzMzMzIDE4LjEzMzMzMyAwIDAgMC0yNS44MTMzMzMgMGwtOTMuMjI2NjY3IDkzLjIyNjY2N2ExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAxLTI1LjgxMzMzMyAwbC03MC40LTcwLjRhOS4xNzMzMzMgOS4xNzMzMzMgMCAwIDAtMTUuNzg2NjY3IDYuODI2NjY2QzE4OS44NjY2NjcgODQxLjgxMzMzMyAzMzQuOTMzMzMzIDk2MCA1MTIgOTYwczMyMi4xMzMzMzMtMTE4LjE4NjY2NyAzMzcuMjgtMzU0LjM0NjY2N2E5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjctNi44MjY2NjZ6IiBmaWxsPSIjRjBGMEYwIj48L3BhdGg+PHBhdGggZD0iTTE5MC41MDY2NjcgNjY1LjgxMzMzM2w3MC40IDYxLjIyNjY2N2EyMS4zMzMzMzMgMjEuMzMzMzMzIDAgMCAwIDI1LjgxMzMzMyAwbDkzLjIyNjY2Ny04MS4wNjY2NjdhMjEuMzMzMzMzIDIxLjMzMzMzMyAwIDAgMSAyNS44MTMzMzMgMGw5My4yMjY2NjcgODEuMDY2NjY3YTIxLjMzMzMzMyAyMS4zMzMzMzMgMCAwIDAgMjYuMDI2NjY2IDBsOTMuNjUzMzM0LTgxLjA2NjY2N2EyMS4zMzMzMzMgMjEuMzMzMzMzIDAgMCAxIDI2LjAyNjY2NiAwbDkzLjIyNjY2NyA4MS4wNjY2NjdhMjEuMzMzMzMzIDIxLjMzMzMzMyAwIDAgMCAyNS44MTMzMzMgMGw3MC40LTYxLjIyNjY2N2E5LjgxMzMzMyA5LjgxMzMzMyAwIDAgMSA4LjUzMzMzNC0xLjkyIDUzNi4xMDY2NjcgNTM2LjEwNjY2NyAwIDAgMCA3LjI1MzMzMy01OC4yNCA5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjctNi44MjY2NjZsLTcwLjQgNzAuNGExOC4zNDY2NjcgMTguMzQ2NjY3IDAgMCAxLTI1LjgxMzMzMyAwTDY0NC4wNTMzMzMgNTc2QTE4LjM0NjY2NyAxOC4zNDY2NjcgMCAwIDAgNjE4LjY2NjY2NyA1NzZsLTkzLjAxMzMzNCA5My4yMjY2NjdhMTguNzczMzMzIDE4Ljc3MzMzMyAwIDAgMS0yNi4wMjY2NjYgMEw0MDUuMzMzMzMzIDU3NmExOC4xMzMzMzMgMTguMTMzMzMzIDAgMCAwLTI1LjgxMzMzMyAwbC05My4yMjY2NjcgOTMuMjI2NjY3YTE4LjM0NjY2NyAxOC4zNDY2NjcgMCAwIDEtMjUuODEzMzMzIDBsLTcwLjQtNzAuNGE5LjE3MzMzMyA5LjE3MzMzMyAwIDAgMC0xNS43ODY2NjcgNi44MjY2NjYgNTM3LjE3MzMzMyA1MzcuMTczMzMzIDAgMCAwIDcuMDQgNTguMjQgMTAuMDI2NjY3IDEwLjAyNjY2NyAwIDAgMSA5LjE3MzMzNCAxLjkyeiIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPjxwYXRoIGQ9Ik00OTguMTMzMzMzIDE4NC41MzMzMzNtLTI5LjAxMzMzMyAwYTI5LjAxMzMzMyAyOS4wMTMzMzMgMCAxIDAgNTguMDI2NjY3IDAgMjkuMDEzMzMzIDI5LjAxMzMzMyAwIDEgMC01OC4wMjY2NjcgMFoiIGZpbGw9IiM0NTQxM0MiPjwvcGF0aD48cGF0aCBkPSJNNTc2IDI5MC45ODY2NjdjMCAxMS43MzMzMzMtMTUuNzg2NjY3IDIxLjMzMzMzMy0zNC45ODY2NjcgMjEuMzMzMzMzcy0zNS4yLTkuNi0zNS4yLTIxLjMzMzMzMyAxNS43ODY2NjctMjEuMzMzMzMzIDM1LjItMjEuMzMzMzM0IDM0Ljk4NjY2NyA5LjYgMzQuOTg2NjY3IDIxLjMzMzMzNHoiIGZpbGw9IiNGRkY0OEMiPjwvcGF0aD48L3N2Zz4=">
|
|
347
434
|
<div>
|
|
348
435
|
<div class="title">CreditDaddy</div>
|
|
349
|
-
<div class="ver" id="ver">Qoder / WorkBuddy / ZCode 多账号管理 · 每日积分自动领取</div>
|
|
436
|
+
<div class="ver" id="ver" data-i18n="Qoder / WorkBuddy / ZCode 多账号管理 · 每日积分自动领取">Qoder / WorkBuddy / ZCode 多账号管理 · 每日积分自动领取</div>
|
|
350
437
|
</div>
|
|
351
438
|
</div>
|
|
352
439
|
<div class="head-right">
|
|
353
|
-
|
|
354
|
-
<
|
|
355
|
-
<
|
|
356
|
-
<button class="icon-btn" id="btn-theme" title="
|
|
440
|
+
<!-- 只读状态:下一次自动领取(领取中变强调色)。窄屏自动隐藏,操作按钮优先 -->
|
|
441
|
+
<span id="sched" class="sched-chip"></span>
|
|
442
|
+
<span class="head-sep"></span>
|
|
443
|
+
<button class="icon-btn" id="btn-theme" title="主题"></button>
|
|
444
|
+
<div class="head-setpop">
|
|
445
|
+
<button class="icon-btn" id="btn-comp" title="面板设置:访问密码、组件显隐与排序、账号卡片排序"></button>
|
|
446
|
+
<!-- 面板设置(右上角齿轮) -->
|
|
447
|
+
<div class="setpop" id="m-settings">
|
|
448
|
+
<div class="modal">
|
|
449
|
+
<div class="modal-title"><span data-i18n="面板设置">面板设置</span><button class="close" data-close>✕</button></div>
|
|
450
|
+
<div class="tabs" id="set-tabs">
|
|
451
|
+
<button class="tab active" data-set-tab="net" data-i18n="通用">通用</button>
|
|
452
|
+
<button class="tab" data-set-tab="comp" data-i18n="组件与排序">组件与排序</button>
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<div class="pane active" data-set-pane="net">
|
|
456
|
+
<div class="field">
|
|
457
|
+
<label data-i18n="全局自动签到">全局自动签到</label>
|
|
458
|
+
<label class="check"><input type="checkbox" id="cfg-auto-checkin"> <span data-i18n="开启自动签到">开启自动签到</span></label>
|
|
459
|
+
<div style="display:flex;gap:8px;align-items:center;margin-top:6px">
|
|
460
|
+
<span class="hint" data-i18n="签到频率">签到频率</span>
|
|
461
|
+
<select class="input" id="cfg-checkin-interval" style="width:auto">
|
|
462
|
+
<option value="60" data-i18n="每 1 小时">每 1 小时</option>
|
|
463
|
+
<option value="120" data-i18n="每 2 小时">每 2 小时</option>
|
|
464
|
+
<option value="240" data-i18n="每 4 小时">每 4 小时</option>
|
|
465
|
+
<option value="480" data-i18n="每 8 小时">每 8 小时</option>
|
|
466
|
+
</select>
|
|
467
|
+
</div>
|
|
468
|
+
<div class="hint" data-i18n="作用于全部产品的定时签到轮(ZCode 活动领取在自身页签单独配置);修改立即生效,下一轮按新间隔执行。">作用于全部产品的定时签到轮(ZCode 活动领取在自身页签单独配置);修改立即生效,下一轮按新间隔执行。</div>
|
|
469
|
+
</div>
|
|
470
|
+
<div class="field">
|
|
471
|
+
<label data-i18n="WorkBuddy 国际版 活跃请求">WorkBuddy 国际版 活跃请求</label>
|
|
472
|
+
<label class="check"><input type="checkbox" id="cfg-wb-activity"> <span data-i18n="定期发送活跃请求(保持资格,不保证奖励)">定期发送活跃请求(保持资格,不保证奖励)</span></label>
|
|
473
|
+
<div class="hint" data-i18n="关闭后本机不再对国际版账号自动/手动发活跃请求;卡片与仪表盘的「活跃」入口一同隐藏。">关闭后本机不再对国际版账号自动/手动发活跃请求;卡片与仪表盘的「活跃」入口一同隐藏。</div>
|
|
474
|
+
</div>
|
|
475
|
+
<div class="field">
|
|
476
|
+
<label data-i18n="面板访问密码">面板访问密码</label>
|
|
477
|
+
<div id="sec-block"></div>
|
|
478
|
+
</div>
|
|
479
|
+
<div class="field">
|
|
480
|
+
<label data-i18n="日志显示">日志显示</label>
|
|
481
|
+
<select class="input" id="cfg-logs-display" style="width:auto">
|
|
482
|
+
<option value="expanded" data-i18n="默认展开">默认展开</option>
|
|
483
|
+
<option value="collapsed" data-i18n="默认收起">默认收起</option>
|
|
484
|
+
</select>
|
|
485
|
+
</div>
|
|
486
|
+
<div class="field">
|
|
487
|
+
<label data-i18n="网络出口(全局)">网络出口(全局)</label>
|
|
488
|
+
<label class="check"><input type="radio" name="znet-order" id="znet-direct"> 直连优先(代理兜底)</label>
|
|
489
|
+
<label class="check"><input type="radio" name="znet-order" id="znet-proxy"> 代理优先(直连兜底)</label>
|
|
490
|
+
<label style="margin-top:6px" data-i18n="出口代理(http://host:port,支持口令 http://user:pass@host:port)">出口代理(http://host:port,支持口令 http://user:pass@host:port)</label>
|
|
491
|
+
<input class="input" id="znet-url" placeholder="http://192.168.31.101:7890">
|
|
492
|
+
<div class="hint" data-i18n="作用于所有出网请求:ZCode 上游、Qoder 国际版登录与凭据探测(按 IP 识别地区,国内直连会被拦)等。留空 = 清除代理,退回 HTTPS_PROXY 环境变量。">作用于所有出网请求:ZCode 上游、Qoder 国际版登录与凭据探测(按 IP 识别地区,国内直连会被拦)等。留空 = 清除代理,退回 HTTPS_PROXY 环境变量。</div>
|
|
493
|
+
<div style="display:flex;gap:8px;align-items:center;margin-top:6px"><button class="btn primary" id="znet-ok" data-i18n="保存出口">保存出口</button><span class="hint" id="znet-state"></span></div>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<div class="pane" data-set-pane="comp">
|
|
498
|
+
<div class="comp-menu" id="comp-menu"></div>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
<div class="modal-actions"><button class="btn" data-close>关闭</button></div>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
</div>
|
|
506
|
+
<!-- 聚合菜单(最右侧):工作区 / 外观 / 语言 / 关于四分区,下拉紧贴按钮(图标由 JS 注入,Lucide 描边同款)。
|
|
507
|
+
主题两处入口联动:菜单分段控件(跟随系统/亮色/暗色) + 头部独立按钮一键循环,都经 applyThemeMode 同一存储 -->
|
|
508
|
+
<div class="hmenu">
|
|
509
|
+
<button class="icon-btn" id="btn-header-menu" title="更多操作:更新、主题、重启、反馈"></button>
|
|
510
|
+
<div class="hmenu-pop" id="hmenu-pop">
|
|
511
|
+
<div class="hmenu-group">
|
|
512
|
+
<div class="hmenu-title" data-i18n="外观">外观</div>
|
|
513
|
+
<div class="mode-seg" id="theme-seg">
|
|
514
|
+
<button class="seg-btn" data-theme-mode="auto" title="跟随系统亮色 / 暗色设置" data-i18n="跟随系统">跟随系统</button>
|
|
515
|
+
<button class="seg-btn" data-theme-mode="light" title="始终使用亮色" data-i18n="亮色">亮色</button>
|
|
516
|
+
<button class="seg-btn" data-theme-mode="dark" title="始终使用暗色" data-i18n="暗色">暗色</button>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
<div class="hmenu-group">
|
|
520
|
+
<div class="hmenu-title" data-i18n="语言">语言</div>
|
|
521
|
+
<div class="mode-seg lang-seg" id="locale-seg">
|
|
522
|
+
<button class="seg-btn" data-locale="zh-CN" title="简体中文">简</button>
|
|
523
|
+
<button class="seg-btn" data-locale="zh-TW" title="繁體中文">繁</button>
|
|
524
|
+
<button class="seg-btn" data-locale="en" title="English">EN</button>
|
|
525
|
+
<button class="seg-btn" data-locale="ja" title="日本語">日</button>
|
|
526
|
+
<button class="seg-btn" data-locale="ko" title="한국어">한</button>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
<div class="hmenu-group">
|
|
530
|
+
<div class="hmenu-title" data-i18n="工作区">工作区</div>
|
|
531
|
+
<button class="hmenu-item" id="hm-update">检查更新</button>
|
|
532
|
+
<button class="hmenu-item" id="changelog-brief-btn">更新日志</button>
|
|
533
|
+
</div>
|
|
534
|
+
<div class="hmenu-group">
|
|
535
|
+
<div class="hmenu-title" data-i18n="关于">关于</div>
|
|
536
|
+
<a class="hmenu-item" id="hm-home" href="https://github.com/techysy/CreditDaddy" target="_blank" rel="noopener" data-i18n="项目主页">项目主页</a>
|
|
537
|
+
<button class="hmenu-item" id="feedback-btn" data-i18n="建议反馈">建议反馈</button>
|
|
538
|
+
<button class="hmenu-item" id="donate-btn" data-i18n="捐赠支持">捐赠支持</button>
|
|
539
|
+
<button class="hmenu-item danger" id="hm-restart" data-i18n="重启 CreditDaddy">重启 CreditDaddy</button>
|
|
540
|
+
</div>
|
|
541
|
+
<div class="hmenu-ver"><span id="hmenu-version">CreditDaddy</span><a id="hmenu-channel" href="https://github.com/techysy/CreditDaddy/releases/latest" target="_blank" rel="noopener" hidden>Releases</a></div>
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
357
544
|
</div>
|
|
358
545
|
</header>
|
|
359
546
|
|
|
360
547
|
<nav class="nav" id="nav">
|
|
361
|
-
<button data-view="dash" class="active">仪表盘</button>
|
|
548
|
+
<button data-view="dash" class="active" data-i18n="仪表盘">仪表盘</button>
|
|
362
549
|
<button data-view="qoder">Qoder<span class="cnt" id="cnt-qoder">0</span></button>
|
|
363
550
|
<button data-view="workbuddy">WorkBuddy<span class="cnt" id="cnt-workbuddy">0</span></button>
|
|
364
551
|
<button data-view="zcode">ZCode<span class="cnt" id="cnt-zcode">0</span></button>
|
|
365
552
|
<button data-view="mirasim">mirasim<span class="cnt" id="cnt-mirasim">0</span></button>
|
|
366
|
-
<button data-view="catpaw"
|
|
553
|
+
<button data-view="catpaw"><span data-i18n="妙手">妙手</span><span class="cnt" id="cnt-catpaw">0</span></button>
|
|
367
554
|
<button data-view="trae">Trae<span class="cnt" id="cnt-trae">0</span></button>
|
|
368
555
|
<button data-view="minimax">MiniMax<span class="cnt" id="cnt-minimax">0</span></button>
|
|
369
556
|
<button data-view="tenrouter">10Router<span class="cnt" id="cnt-tenrouter">–</span></button>
|
|
@@ -373,7 +560,7 @@
|
|
|
373
560
|
<section class="view active" id="view-dash">
|
|
374
561
|
<div class="panel dash-panel">
|
|
375
562
|
<div class="toolbar">
|
|
376
|
-
<div class="summary"><span class="dash-title">总览</span></div>
|
|
563
|
+
<div class="summary"><span class="dash-title" data-i18n="总览">总览</span></div>
|
|
377
564
|
<div class="actions">
|
|
378
565
|
<button class="btn primary" id="btn-checkin-all"></button>
|
|
379
566
|
<button class="btn" id="btn-add-dash"></button>
|
|
@@ -385,8 +572,8 @@
|
|
|
385
572
|
</div>
|
|
386
573
|
<div class="prods" id="prods"></div>
|
|
387
574
|
<div class="dash-cols">
|
|
388
|
-
<section class="pcard" data-fold="recent"><div class="pcard-title fold-head"><span>最近记录</span><span class="fold-side"><span class="hint" id="last-tick"></span><span class="fold-ico"></span></span></div><div class="fold-body"><div id="recent"></div></div></section>
|
|
389
|
-
<section class="pcard" data-fold="attention"><div class="pcard-title fold-head"><span>需要处理</span><span class="fold-side"><span class="hint" id="att-count"></span><span class="fold-ico"></span></span></div><div class="fold-body"><div id="attention"></div></div></section>
|
|
575
|
+
<section class="pcard" data-fold="recent"><div class="pcard-title fold-head"><span data-i18n="最近记录">最近记录</span><span class="fold-side"><span class="hint" id="last-tick"></span><span class="fold-ico"></span></span></div><div class="fold-body"><div id="recent"></div></div></section>
|
|
576
|
+
<section class="pcard" data-fold="attention"><div class="pcard-title fold-head"><span data-i18n="需要处理">需要处理</span><span class="fold-side"><span class="hint" id="att-count"></span><span class="fold-ico"></span></span></div><div class="fold-body"><div id="attention"></div></div></section>
|
|
390
577
|
</div>
|
|
391
578
|
</section>
|
|
392
579
|
|
|
@@ -395,27 +582,27 @@
|
|
|
395
582
|
<div class="panel">
|
|
396
583
|
<div class="toolbar">
|
|
397
584
|
<div class="summary">
|
|
398
|
-
<div class="stat"><span>账号</span><strong id="s-count">0</strong></div>
|
|
585
|
+
<div class="stat"><span data-i18n="账号">账号</span><strong id="s-count">0</strong></div>
|
|
399
586
|
<div class="divider"></div>
|
|
400
587
|
<div id="s-done-wrap" style="display:contents">
|
|
401
|
-
<div class="stat"><span>今日已领</span><strong id="s-done">0</strong></div>
|
|
588
|
+
<div class="stat"><span data-i18n="今日已领">今日已领</span><strong id="s-done">0</strong></div>
|
|
402
589
|
<div class="divider"></div>
|
|
403
590
|
</div>
|
|
404
|
-
<div class="stat"><span id="s-credits-label">剩余积分</span><strong id="s-credits">-</strong></div>
|
|
591
|
+
<div class="stat"><span id="s-credits-label" data-i18n="剩余积分">剩余积分</span><strong id="s-credits">-</strong></div>
|
|
405
592
|
<div class="divider" id="region-div"></div>
|
|
406
593
|
<div class="seg-ctl" id="region">
|
|
407
|
-
<button data-r="" class="active">全部</button><button data-r="intl">国际版</button><button data-r="cn">国内版</button>
|
|
594
|
+
<button data-r="" class="active">全部</button><button data-r="intl" data-i18n="国际版">国际版</button><button data-r="cn" data-i18n="国内版">国内版</button>
|
|
408
595
|
</div>
|
|
409
596
|
</div>
|
|
410
597
|
<div class="actions">
|
|
411
598
|
<button class="btn primary" id="btn-checkin"></button>
|
|
412
599
|
<span id="z-actions" style="display:none">
|
|
413
600
|
<button class="btn" id="zt-ac"></button>
|
|
414
|
-
<button class="btn" id="zt-net"></button>
|
|
415
601
|
<button class="btn" id="zt-gw"></button>
|
|
416
602
|
<button class="btn" id="zt-open" style="display:none"></button>
|
|
417
603
|
</span>
|
|
418
604
|
<span id="t-actions" style="display:none">
|
|
605
|
+
<button class="btn" id="tt-gw"></button>
|
|
419
606
|
<button class="btn" id="tt-open" style="display:none"></button>
|
|
420
607
|
</span>
|
|
421
608
|
<span id="m-actions" style="display:none">
|
|
@@ -428,57 +615,65 @@
|
|
|
428
615
|
</div>
|
|
429
616
|
</div>
|
|
430
617
|
<div class="notice" id="notice" style="display:none"></div>
|
|
431
|
-
<div class="list" id="list"><div class="empty">加载中…</div></div>
|
|
618
|
+
<div class="list" id="list"><div class="empty" data-i18n="加载中…">加载中…</div></div>
|
|
432
619
|
</div>
|
|
433
620
|
</section>
|
|
434
621
|
|
|
435
622
|
<!-- 10Router:其他供应商额度 + 用量同步 -->
|
|
436
623
|
<section class="view" id="view-10r">
|
|
437
|
-
<div class="panel" id="tr-panel"><div class="empty">加载中…</div></div>
|
|
624
|
+
<div class="panel" id="tr-panel"><div class="empty" data-i18n="加载中…">加载中…</div></div>
|
|
438
625
|
</section>
|
|
439
626
|
|
|
440
627
|
<!-- 日志卡:运行日志 + 各网关调用日志,卡内标签切换(默认跟随当前页,可手动切) -->
|
|
441
628
|
<section class="pcard collapsed" id="log-card">
|
|
442
629
|
<div class="pcard-title">
|
|
443
630
|
<div class="seg-ctl" id="log-tabs">
|
|
444
|
-
<button data-log-tab="run" class="active">运行日志</button>
|
|
445
|
-
<button data-log-tab="zcode">ZCode 网关</button>
|
|
446
|
-
<button data-log-tab="minimax">MiniMax 网关</button>
|
|
631
|
+
<button data-log-tab="run" class="active" data-i18n="运行日志">运行日志</button>
|
|
632
|
+
<button data-log-tab="zcode" data-i18n="ZCode 网关">ZCode 网关</button>
|
|
633
|
+
<button data-log-tab="minimax" data-i18n="MiniMax 网关">MiniMax 网关</button>
|
|
634
|
+
<button data-log-tab="trae" data-i18n="Trae 网关">Trae 网关</button>
|
|
635
|
+
</div>
|
|
636
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
637
|
+
<div class="log-filter" id="log-filter">
|
|
638
|
+
<button data-log-level="all" class="active">全部</button>
|
|
639
|
+
<button data-log-level="warn" data-i18n="只看提醒">只看提醒</button>
|
|
640
|
+
</div>
|
|
641
|
+
<button class="btn" id="btn-logs-history" data-i18n="加载历史">加载历史</button>
|
|
642
|
+
<button class="btn" id="btn-logs" data-i18n="展开">展开</button>
|
|
447
643
|
</div>
|
|
448
|
-
<button class="btn" id="btn-logs">展开</button>
|
|
449
644
|
</div>
|
|
450
|
-
<div class="logs" id="logs">(暂无)</div>
|
|
645
|
+
<div class="logs" id="logs" data-i18n="(暂无)">(暂无)</div>
|
|
451
646
|
</section>
|
|
452
647
|
</div>
|
|
453
648
|
|
|
454
649
|
<!-- 添加账号 -->
|
|
455
650
|
<div class="mask" id="m-add">
|
|
456
651
|
<div class="modal">
|
|
457
|
-
<div class="modal-title"><span>添加账号</span><button class="close" data-close>✕</button></div>
|
|
652
|
+
<div class="modal-title"><span data-i18n="添加账号">添加账号</span><button class="close" data-close>✕</button></div>
|
|
458
653
|
<div class="tabs">
|
|
459
|
-
<button class="tab active" data-tab="local">本机导入</button>
|
|
460
|
-
<button class="tab" data-tab="device">浏览器登录</button>
|
|
461
|
-
<button class="tab" data-tab="token">粘贴 Token</button>
|
|
654
|
+
<button class="tab active" data-tab="local" data-i18n="本机导入">本机导入</button>
|
|
655
|
+
<button class="tab" data-tab="device" data-i18n="浏览器登录">浏览器登录</button>
|
|
656
|
+
<button class="tab" data-tab="token" data-i18n="粘贴 Token">粘贴 Token</button>
|
|
462
657
|
</div>
|
|
463
658
|
|
|
464
659
|
<div class="pane active" data-pane="local">
|
|
465
660
|
<div class="hint" id="local-hint"></div>
|
|
466
|
-
<div class="row" style="margin-top:10px"><button class="btn primary" id="btn-scan">重新扫描</button><span class="hint" id="scan-status"></span></div>
|
|
467
|
-
<div class="box" id="scan-result"><div class="hint">点击「重新扫描」</div></div>
|
|
661
|
+
<div class="row" style="margin-top:10px"><button class="btn primary" id="btn-scan" data-i18n="重新扫描">重新扫描</button><span class="hint" id="scan-status"></span></div>
|
|
662
|
+
<div class="box" id="scan-result"><div class="hint" data-i18n="点击「重新扫描」">点击「重新扫描」</div></div>
|
|
468
663
|
</div>
|
|
469
664
|
|
|
470
665
|
<div class="pane" data-pane="device">
|
|
471
666
|
<div class="hint" id="dev-hint" style="margin-bottom:10px"></div>
|
|
472
667
|
<div class="field">
|
|
473
|
-
<label>登录到</label>
|
|
668
|
+
<label data-i18n="登录到">登录到</label>
|
|
474
669
|
<select class="input" id="dev-provider">
|
|
475
670
|
<option value="qoder">Qoder 国际版</option><option value="qoder-cn">Qoder 国内版</option>
|
|
476
|
-
<option value="workbuddy">WorkBuddy / CodeBuddy 国内版</option><option value="workbuddy-intl">WorkBuddy / CodeBuddy 国际版</option>
|
|
477
|
-
<option value="zcode-bigmodel">ZCode · BigModel(智谱)</option><option value="zcode-zai">ZCode · Z.ai(国际)</option>
|
|
671
|
+
<option value="workbuddy" data-i18n="WorkBuddy / CodeBuddy 国内版">WorkBuddy / CodeBuddy 国内版</option><option value="workbuddy-intl" data-i18n="WorkBuddy / CodeBuddy 国际版">WorkBuddy / CodeBuddy 国际版</option>
|
|
672
|
+
<option value="zcode-bigmodel" data-i18n="ZCode · BigModel(智谱)">ZCode · BigModel(智谱)</option><option value="zcode-zai" data-i18n="ZCode · Z.ai(国际)">ZCode · Z.ai(国际)</option>
|
|
478
673
|
<option value="minimax">MiniMax Code</option>
|
|
479
674
|
</select>
|
|
480
675
|
</div>
|
|
481
|
-
<div class="row"><button class="btn primary" id="btn-device">生成授权链接</button><button class="btn" id="dev-cancel" style="display:none">取消</button></div>
|
|
676
|
+
<div class="row"><button class="btn primary" id="btn-device" data-i18n="生成授权链接">生成授权链接</button><button class="btn" id="dev-cancel" style="display:none">取消</button></div>
|
|
482
677
|
<div class="box" id="device-box" style="display:none">
|
|
483
678
|
<div class="hint" id="dev-status"></div>
|
|
484
679
|
<div class="hint" style="margin-top:6px"><a id="dev-link" href="#" target="_blank" rel="noopener"></a></div>
|
|
@@ -487,12 +682,12 @@
|
|
|
487
682
|
|
|
488
683
|
<div class="pane" data-pane="token">
|
|
489
684
|
<div class="field">
|
|
490
|
-
<label>版本</label>
|
|
491
|
-
<select class="input" id="f-provider"><option value="qoder">Qoder 国际版</option><option value="qoder-cn">Qoder 国内版</option><option value="workbuddy">WorkBuddy 国内版</option><option value="workbuddy-intl">WorkBuddy 国际版</option><option value="minimax">MiniMax</option></select>
|
|
685
|
+
<label data-i18n="版本">版本</label>
|
|
686
|
+
<select class="input" id="f-provider"><option value="qoder">Qoder 国际版</option><option value="qoder-cn">Qoder 国内版</option><option value="workbuddy" data-i18n="WorkBuddy 国内版">WorkBuddy 国内版</option><option value="workbuddy-intl" data-i18n="WorkBuddy 国际版">WorkBuddy 国际版</option><option value="minimax">MiniMax</option></select>
|
|
492
687
|
</div>
|
|
493
|
-
<div class="field"><label>备注名(可选)</label><input class="input" id="f-name" placeholder="留空则自动读取昵称"></div>
|
|
688
|
+
<div class="field"><label data-i18n="备注名(可选)">备注名(可选)</label><input class="input" id="f-name" placeholder="留空则自动读取昵称"></div>
|
|
494
689
|
<div class="field"><label>Token</label><input class="input mono" id="f-token" placeholder="dt-… / jt-… 或 PAT(pt-…,自动兑换)"></div>
|
|
495
|
-
<div class="modal-actions"><button class="btn primary" id="btn-add-token">添加</button></div>
|
|
690
|
+
<div class="modal-actions"><button class="btn primary" id="btn-add-token" data-i18n="添加">添加</button></div>
|
|
496
691
|
</div>
|
|
497
692
|
</div>
|
|
498
693
|
</div>
|
|
@@ -500,40 +695,28 @@
|
|
|
500
695
|
<!-- 导出 -->
|
|
501
696
|
<div class="mask" id="m-export">
|
|
502
697
|
<div class="modal sm">
|
|
503
|
-
<div class="modal-title"><span>导出账号</span><button class="close" data-close>✕</button></div>
|
|
698
|
+
<div class="modal-title"><span data-i18n="导出账号">导出账号</span><button class="close" data-close>✕</button></div>
|
|
504
699
|
<div class="field">
|
|
505
|
-
<label>范围</label>
|
|
506
|
-
<select class="input" id="ex-provider"><option value="">全部账号</option><option value="p:qoder">Qoder(全部)</option><option value="qoder">仅 Qoder 国际版</option><option value="qoder-cn">仅 Qoder 国内版</option><option value="p:workbuddy">WorkBuddy(全部)</option><option value="workbuddy">仅 WorkBuddy 国内版</option><option value="workbuddy-intl">仅 WorkBuddy 国际版</option><option value="zcode">ZCode</option><option value="p:mirasim">mirasim</option><option value="p:catpaw">妙手</option><option value="p:trae">Trae</option><option value="p:minimax">MiniMax</option></select>
|
|
700
|
+
<label data-i18n="范围">范围</label>
|
|
701
|
+
<select class="input" id="ex-provider"><option value="" data-i18n="全部账号">全部账号</option><option value="p:qoder" data-i18n="Qoder(全部)">Qoder(全部)</option><option value="qoder" data-i18n="仅 Qoder 国际版">仅 Qoder 国际版</option><option value="qoder-cn" data-i18n="仅 Qoder 国内版">仅 Qoder 国内版</option><option value="p:workbuddy" data-i18n="WorkBuddy(全部)">WorkBuddy(全部)</option><option value="workbuddy" data-i18n="仅 WorkBuddy 国内版">仅 WorkBuddy 国内版</option><option value="workbuddy-intl" data-i18n="仅 WorkBuddy 国际版">仅 WorkBuddy 国际版</option><option value="zcode">ZCode</option><option value="p:mirasim">mirasim</option><option value="p:catpaw" data-i18n="妙手">妙手</option><option value="p:trae">Trae</option><option value="p:minimax">MiniMax</option></select>
|
|
507
702
|
</div>
|
|
508
703
|
<div class="field">
|
|
509
|
-
<label>加密口令(必填)</label>
|
|
704
|
+
<label data-i18n="加密口令(必填)">加密口令(必填)</label>
|
|
510
705
|
<input class="input" id="ex-password" type="password" placeholder="至少 4 位" autocomplete="new-password">
|
|
511
706
|
</div>
|
|
512
|
-
<div class="hint">导出文件一律加密(10router 迁移格式,scrypt + AES-256-GCM),可直接在 10router 对应提供商页导入(WorkBuddy 对应 10router 的 CodeBuddy)。10router 按提供商导入,建议按单个版本导出。<b>口令不会被保存</b>,请牢记——遗失后文件无法解开、无法导入。</div>
|
|
513
|
-
<div class="
|
|
707
|
+
<div class="hint">导出文件一律加密(10router 迁移格式,scrypt + AES-256-GCM),可直接在 10router 对应提供商页导入(WorkBuddy 对应 10router 的 CodeBuddy)。10router 按提供商导入,建议按单个版本导出。<b data-i18n="口令不会被保存">口令不会被保存</b>,请牢记——遗失后文件无法解开、无法导入。</div>
|
|
708
|
+
<div class="notice" id="ex-session-note" style="display:none"></div>
|
|
709
|
+
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="btn-do-export" data-i18n="导出">导出</button></div>
|
|
514
710
|
</div>
|
|
515
711
|
</div>
|
|
516
712
|
|
|
517
713
|
<!-- 口令输入 -->
|
|
518
714
|
<div class="mask" id="m-pass">
|
|
519
715
|
<div class="modal sm">
|
|
520
|
-
<div class="modal-title"><span>输入口令</span><button class="close" data-close>✕</button></div>
|
|
521
|
-
<div class="hint" id="pass-hint">该文件已加密,请输入导出时设置的口令。</div>
|
|
716
|
+
<div class="modal-title"><span data-i18n="输入口令">输入口令</span><button class="close" data-close>✕</button></div>
|
|
717
|
+
<div class="hint" id="pass-hint" data-i18n="该文件已加密,请输入导出时设置的口令。">该文件已加密,请输入导出时设置的口令。</div>
|
|
522
718
|
<div class="field" style="margin-top:10px"><input class="input" id="pass-input" type="password"></div>
|
|
523
|
-
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="btn-pass-ok">导入</button></div>
|
|
524
|
-
</div>
|
|
525
|
-
</div>
|
|
526
|
-
|
|
527
|
-
<!-- 面板设置(右上角齿轮) -->
|
|
528
|
-
<div class="mask" id="m-settings">
|
|
529
|
-
<div class="modal">
|
|
530
|
-
<div class="modal-title"><span>面板设置</span><button class="close" data-close>✕</button></div>
|
|
531
|
-
<div class="comp-menu" id="comp-menu"></div>
|
|
532
|
-
<div class="field" id="upd-entry" style="display:none;margin-top:12px">
|
|
533
|
-
<label>版本与更新</label>
|
|
534
|
-
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"><span class="dim" id="upd-ver"></span><button class="btn" id="btn-update">检查更新</button></div>
|
|
535
|
-
</div>
|
|
536
|
-
<div class="modal-actions"><button class="btn" data-close>关闭</button></div>
|
|
719
|
+
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="btn-pass-ok" data-i18n="导入">导入</button></div>
|
|
537
720
|
</div>
|
|
538
721
|
</div>
|
|
539
722
|
|
|
@@ -546,24 +729,33 @@
|
|
|
546
729
|
</div>
|
|
547
730
|
</div>
|
|
548
731
|
|
|
732
|
+
<!-- 更新日志(简要版):解析随包 CHANGELOG.md,主题行 + 板块标题 -->
|
|
733
|
+
<div class="mask" id="m-changelog">
|
|
734
|
+
<div class="modal sm">
|
|
735
|
+
<div class="modal-title"><span>更新日志</span><button class="close" data-close>✕</button></div>
|
|
736
|
+
<div id="changelog-body" style="max-height:56vh;overflow-y:auto"></div>
|
|
737
|
+
<div class="modal-actions"><button class="btn" id="btn-changelog-full" data-i18n="GitHub 完整日志">GitHub 完整日志</button><button class="btn primary" data-close>关闭</button></div>
|
|
738
|
+
</div>
|
|
739
|
+
</div>
|
|
740
|
+
|
|
549
741
|
<!-- 10Router 设置 -->
|
|
550
742
|
<div class="mask" id="m-10r">
|
|
551
743
|
<div class="modal">
|
|
552
|
-
<div class="modal-title"><span>10Router 连接设置</span><button class="close" data-close>✕</button></div>
|
|
553
|
-
<div class="hint">填 10Router 的访问地址和仪表盘里创建的<b>虚拟 key(sk-…)</b>。key 只保存在 CreditDaddy 守护进程的数据目录(0600),面板里只显示脱敏值。</div>
|
|
554
|
-
<div class="field" style="margin-top:12px"><label>10Router 地址</label><input class="input mono" id="tr-endpoint" placeholder="http://127.0.0.1:20128(站点根地址,不要带 /v1)"></div>
|
|
555
|
-
<div class="field"><label>虚拟 key</label><input class="input mono" id="tr-key" placeholder="sk-…" autocomplete="off"></div>
|
|
556
|
-
<div class="field"><label>10Router 面板密码(可选)</label><input class="input mono" id="tr-admin-pw" type="password" placeholder="配置后「同步账号」走 OAuth 通道(带 refreshToken 自动续期);留空保持不变" autocomplete="new-password"></div>
|
|
557
|
-
<div class="field"><label>账号同步</label>
|
|
558
|
-
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"><button class="btn" id="tr-sync-accounts">同步账号到 10R</button><span class="hint">把本机 Qoder / WorkBuddy 账号推为 10Router 上游连接;zcode 待反代就绪后支持</span></div>
|
|
744
|
+
<div class="modal-title"><span data-i18n="10Router 连接设置">10Router 连接设置</span><button class="close" data-close>✕</button></div>
|
|
745
|
+
<div class="hint">填 10Router 的访问地址和仪表盘里创建的<b data-i18n="虚拟 key(sk-…)">虚拟 key(sk-…)</b>。key 只保存在 CreditDaddy 守护进程的数据目录(0600),面板里只显示脱敏值。</div>
|
|
746
|
+
<div class="field" style="margin-top:12px"><label data-i18n="10Router 地址">10Router 地址</label><input class="input mono" id="tr-endpoint" placeholder="http://127.0.0.1:20128(站点根地址,不要带 /v1)"></div>
|
|
747
|
+
<div class="field"><label data-i18n="虚拟 key">虚拟 key</label><input class="input mono" id="tr-key" placeholder="sk-…" autocomplete="off"></div>
|
|
748
|
+
<div class="field"><label data-i18n="10Router 面板密码(可选)">10Router 面板密码(可选)</label><input class="input mono" id="tr-admin-pw" type="password" placeholder="配置后「同步账号」走 OAuth 通道(带 refreshToken 自动续期);留空保持不变" autocomplete="new-password"></div>
|
|
749
|
+
<div class="field"><label data-i18n="账号同步">账号同步</label>
|
|
750
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"><button class="btn" id="tr-sync-accounts" data-i18n="同步账号到 10R">同步账号到 10R</button><span class="hint" data-i18n="把本机 Qoder / WorkBuddy 账号推为 10Router 上游连接;zcode 待反代就绪后支持">把本机 Qoder / WorkBuddy 账号推为 10Router 上游连接;zcode 待反代就绪后支持</span></div>
|
|
559
751
|
</div>
|
|
560
|
-
<div class="field"><label>显示</label>
|
|
752
|
+
<div class="field"><label data-i18n="显示">显示</label>
|
|
561
753
|
<div><label class="check"><input type="checkbox" id="tr-hide-empty"> 「全部」视图隐藏没有额度 / 余额为 0 的供应商汇总卡</label></div>
|
|
562
754
|
</div>
|
|
563
755
|
<div class="hint" id="tr-test-msg"></div>
|
|
564
756
|
<div class="modal-actions">
|
|
565
|
-
<button class="btn" id="tr-disconnect" style="margin-right:auto">断开</button>
|
|
566
|
-
<button class="btn" id="tr-test">测试连接</button>
|
|
757
|
+
<button class="btn" id="tr-disconnect" style="margin-right:auto" data-i18n="断开">断开</button>
|
|
758
|
+
<button class="btn" id="tr-test" data-i18n="测试连接">测试连接</button>
|
|
567
759
|
<button class="btn primary" id="tr-save">保存</button>
|
|
568
760
|
</div>
|
|
569
761
|
</div>
|
|
@@ -572,20 +764,20 @@
|
|
|
572
764
|
<!-- 10Router 用量同步 -->
|
|
573
765
|
<div class="mask" id="m-trsync">
|
|
574
766
|
<div class="modal sm">
|
|
575
|
-
<div class="modal-title"><span>用量同步(同 10router-sync 插件)</span><button class="close" data-close>✕</button></div>
|
|
767
|
+
<div class="modal-title"><span data-i18n="用量同步(同 10router-sync 插件)">用量同步(同 10router-sync 插件)</span><button class="close" data-close>✕</button></div>
|
|
576
768
|
<div class="field"><label class="check"><input type="checkbox" id="tr-auto"> 每小时自动同步本机用量到 10Router</label></div>
|
|
577
|
-
<div class="field"><label>同步来源</label><div id="tr-sources"></div></div>
|
|
578
|
-
<div class="hint">ZCode 只同步官方渠道;经 10Router 中转的 mirasim 调用会跳过,避免重复计数。「10Router 本机」只把实例原生产生的行标记为网关同步。10Router 按行签名去重,重复同步不会产生重复数据。</div>
|
|
769
|
+
<div class="field"><label data-i18n="同步来源">同步来源</label><div id="tr-sources"></div></div>
|
|
770
|
+
<div class="hint" data-i18n="ZCode 只同步官方渠道;经 10Router 中转的 mirasim 调用会跳过,避免重复计数。「10Router 本机」只把实例原生产生的行标记为网关同步。10Router 按行签名去重,重复同步不会产生重复数据。">ZCode 只同步官方渠道;经 10Router 中转的 mirasim 调用会跳过,避免重复计数。「10Router 本机」只把实例原生产生的行标记为网关同步。10Router 按行签名去重,重复同步不会产生重复数据。</div>
|
|
579
771
|
<div class="hint" id="tr-sync-status" style="margin-top:8px"></div>
|
|
580
|
-
<div class="modal-actions"><button class="btn" style="margin-right:auto" id="tr-sync-now">立即同步一次</button><button class="btn" data-close>取消</button><button class="btn primary" id="tr-sync-save">保存</button></div>
|
|
772
|
+
<div class="modal-actions"><button class="btn" style="margin-right:auto" id="tr-sync-now" data-i18n="立即同步一次">立即同步一次</button><button class="btn" data-close>取消</button><button class="btn primary" id="tr-sync-save">保存</button></div>
|
|
581
773
|
</div>
|
|
582
774
|
</div>
|
|
583
775
|
|
|
584
776
|
<!-- ZCode 活动领取 -->
|
|
585
777
|
<div class="mask" id="m-claim">
|
|
586
778
|
<div class="modal">
|
|
587
|
-
<div class="modal-title"><span>活动领取</span><button class="close" data-close>✕</button></div>
|
|
588
|
-
<div class="hint">从 ZCode 官方活动接口领取(billing/preview → billing/claim)。领取时通常静默通过人机验证;触发风控时会弹出验证码,按提示完成即可。</div>
|
|
779
|
+
<div class="modal-title"><span data-i18n="活动领取">活动领取</span><button class="close" data-close>✕</button></div>
|
|
780
|
+
<div class="hint" data-i18n="从 ZCode 官方活动接口领取(billing/preview → billing/claim)。领取时通常静默通过人机验证;触发风控时会弹出验证码,按提示完成即可。">从 ZCode 官方活动接口领取(billing/preview → billing/claim)。领取时通常静默通过人机验证;触发风控时会弹出验证码,按提示完成即可。</div>
|
|
589
781
|
<div id="claim-body" style="margin-top:10px"></div>
|
|
590
782
|
</div>
|
|
591
783
|
</div>
|
|
@@ -596,59 +788,41 @@
|
|
|
596
788
|
<div class="modal-title"><span id="dlg-title"></span><button class="close" data-close>✕</button></div>
|
|
597
789
|
<div class="hint" id="dlg-msg" style="white-space:pre-wrap"></div>
|
|
598
790
|
<div class="field" id="dlg-field" style="margin-top:10px"><input class="input" id="dlg-input"></div>
|
|
599
|
-
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="dlg-ok">确定</button></div>
|
|
791
|
+
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="dlg-ok" data-i18n="确定">确定</button></div>
|
|
600
792
|
</div>
|
|
601
793
|
</div>
|
|
602
794
|
|
|
603
795
|
<!-- ZCode 自动领取计划 -->
|
|
604
796
|
<div class="mask" id="m-zac">
|
|
605
797
|
<div class="modal sm">
|
|
606
|
-
<div class="modal-title"><span>ZCode 自动领取计划</span><button class="close" data-close>✕</button></div>
|
|
798
|
+
<div class="modal-title"><span data-i18n="ZCode 自动领取计划">ZCode 自动领取计划</span><button class="close" data-close>✕</button></div>
|
|
607
799
|
<div class="field">
|
|
608
|
-
<label>轮询周期(分钟,1–720)</label>
|
|
800
|
+
<label data-i18n="轮询周期(分钟,1–720)">轮询周期(分钟,1–720)</label>
|
|
609
801
|
<input class="input" id="zac-interval" type="number" min="1" max="720" step="1">
|
|
610
802
|
</div>
|
|
611
803
|
<div class="field">
|
|
612
|
-
<label>单次运行时长(分钟,0 = 一直运行直到手动关闭)</label>
|
|
804
|
+
<label data-i18n="单次运行时长(分钟,0 = 一直运行直到手动关闭)">单次运行时长(分钟,0 = 一直运行直到手动关闭)</label>
|
|
613
805
|
<input class="input" id="zac-window" type="number" min="0" max="1440" step="5">
|
|
614
806
|
</div>
|
|
615
|
-
<div class="hint">开启后按此周期检查活动资格并自动领取(首次检查在开启 2 分钟后开始)。活动包由服务端在每日 0 点左右刷新放出——若运行时段没有覆盖 0 点,会错过当次领取,建议把运行时长设为 0,或让时段跨过 0 点。需要验证码时无法自动通过,卡片上会提示手动领取。</div>
|
|
616
|
-
<div class="modal-actions"><button class="btn" id="zac-off" style="display:none">停用</button><button class="btn" data-close>取消</button><button class="btn primary" id="zac-ok">保存并启用</button></div>
|
|
617
|
-
</div>
|
|
618
|
-
</div>
|
|
619
|
-
|
|
620
|
-
<!-- ZCode 网络出口 -->
|
|
621
|
-
<div class="mask" id="m-znet">
|
|
622
|
-
<div class="modal sm">
|
|
623
|
-
<div class="modal-title"><span>ZCode 网络出口</span><button class="close" data-close>✕</button></div>
|
|
624
|
-
<div class="field">
|
|
625
|
-
<label>上游接口访问顺序(失败 / 被拦时自动回退另一条)</label>
|
|
626
|
-
<label class="check"><input type="radio" name="znet-order" id="znet-direct"> 直连优先(代理兜底)</label>
|
|
627
|
-
<label class="check"><input type="radio" name="znet-order" id="znet-proxy"> 代理优先(直连兜底)</label>
|
|
628
|
-
</div>
|
|
629
|
-
<div class="field">
|
|
630
|
-
<label>出口代理(http://host:port,支持口令 http://user:pass@host:port)</label>
|
|
631
|
-
<input class="input" id="znet-url" placeholder="http://192.168.31.101:7890">
|
|
632
|
-
</div>
|
|
633
|
-
<div class="hint">留空 = 清除已保存的代理,退回 HTTPS_PROXY 环境变量;取消 = 不改动。当前代理只显示脱敏后的地址。</div>
|
|
634
|
-
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="znet-ok">保存</button></div>
|
|
807
|
+
<div class="hint" data-i18n="开启后按此周期检查活动资格并自动领取(首次检查在开启 2 分钟后开始)。活动包由服务端在每日 0 点左右刷新放出——若运行时段没有覆盖 0 点,会错过当次领取,建议把运行时长设为 0,或让时段跨过 0 点。需要验证码时无法自动通过,卡片上会提示手动领取。">开启后按此周期检查活动资格并自动领取(首次检查在开启 2 分钟后开始)。活动包由服务端在每日 0 点左右刷新放出——若运行时段没有覆盖 0 点,会错过当次领取,建议把运行时长设为 0,或让时段跨过 0 点。需要验证码时无法自动通过,卡片上会提示手动领取。</div>
|
|
808
|
+
<div class="modal-actions"><button class="btn" id="zac-off" style="display:none" data-i18n="停用">停用</button><button class="btn" data-close>取消</button><button class="btn primary" id="zac-ok" data-i18n="保存并启用">保存并启用</button></div>
|
|
635
809
|
</div>
|
|
636
810
|
</div>
|
|
637
811
|
|
|
638
812
|
<!-- ZCode 体验包接口 -->
|
|
639
813
|
<div class="mask" id="m-zgw">
|
|
640
814
|
<div class="modal sm">
|
|
641
|
-
<div class="modal-title"><span>ZCode 体验包接口</span><button class="close" data-close>✕</button></div>
|
|
815
|
+
<div class="modal-title"><span data-i18n="ZCode 体验包接口">ZCode 体验包接口</span><button class="close" data-close>✕</button></div>
|
|
642
816
|
<div class="hint" id="zgw-url" style="margin-bottom:10px"></div>
|
|
643
817
|
<div class="field">
|
|
644
|
-
<label class="check"><input type="checkbox" id="zgw-on"> 启用体验包接口(/gateway/v1/messages)</label>
|
|
818
|
+
<label class="check"><input type="checkbox" id="zgw-on"> 启用体验包接口(/gateway/zcode/v1/messages)</label>
|
|
645
819
|
</div>
|
|
646
820
|
<div class="field" id="zgw-lan-fields">
|
|
647
821
|
<label class="check"><input type="checkbox" id="zgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
|
|
648
822
|
<label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
|
|
649
823
|
<textarea class="input" id="zgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
|
|
650
824
|
</div>
|
|
651
|
-
<div class="hint">开启后,领到的体验包额度变成本机的一个补全接口——10Router 建一个 anthropic-compatible 节点指向本机 /
|
|
825
|
+
<div class="hint">开启后,领到的体验包额度变成本机的一个补全接口——10Router 建一个 anthropic-compatible 节点指向本机 <code>/gateway/zcode/v1/messages</code> 即可调用(需要桌面版,隐藏窗口过验证码)。白名单内的机器<b>免密直连</b>:10Router 的 zcode-free 本就是免授权供应商,把 10Router 服务地址的 IP 加白即可;白名单外的请求一律拒绝。本机回环(127.0.0.1)始终放行。</div>
|
|
652
826
|
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="zgw-ok">保存</button></div>
|
|
653
827
|
</div>
|
|
654
828
|
</div>
|
|
@@ -656,13 +830,13 @@
|
|
|
656
830
|
<!-- MiniMax 本地接口 -->
|
|
657
831
|
<div class="mask" id="m-mgw">
|
|
658
832
|
<div class="modal sm">
|
|
659
|
-
<div class="modal-title"><span>MiniMax 本地接口</span><button class="close" data-close>✕</button></div>
|
|
833
|
+
<div class="modal-title"><span data-i18n="MiniMax 本地接口">MiniMax 本地接口</span><button class="close" data-close>✕</button></div>
|
|
660
834
|
<div class="hint" id="mgw-url" style="margin-bottom:10px"></div>
|
|
661
835
|
<div class="field">
|
|
662
836
|
<label class="check"><input type="checkbox" id="mgw-on"> 启用本地接口(/gateway/minimax/v1/messages)</label>
|
|
663
837
|
</div>
|
|
664
838
|
<div class="field" id="mgw-lan-fields">
|
|
665
|
-
<label class="check"><input type="checkbox" id="mgw-lan">
|
|
839
|
+
<label class="check"><input type="checkbox" id="mgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
|
|
666
840
|
<label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
|
|
667
841
|
<textarea class="input" id="mgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
|
|
668
842
|
</div>
|
|
@@ -671,12 +845,57 @@
|
|
|
671
845
|
</div>
|
|
672
846
|
</div>
|
|
673
847
|
|
|
848
|
+
<!-- Trae 本地接口 -->
|
|
849
|
+
<div class="mask" id="m-tgw">
|
|
850
|
+
<div class="modal sm">
|
|
851
|
+
<div class="modal-title"><span data-i18n="Trae 本地接口">Trae 本地接口</span><button class="close" data-close>✕</button></div>
|
|
852
|
+
<div class="hint" id="tgw-url" style="margin-bottom:10px"></div>
|
|
853
|
+
<div class="field">
|
|
854
|
+
<label class="check"><input type="checkbox" id="tgw-on"> 启用本地接口(/gateway/trae/v1/messages)</label>
|
|
855
|
+
</div>
|
|
856
|
+
<div class="field" id="tgw-lan-fields">
|
|
857
|
+
<label class="check"><input type="checkbox" id="tgw-lan"> 对局域网开放(重启后绑定 0.0.0.0)</label>
|
|
858
|
+
<label>IP 白名单(每行一个,支持 192.168.31.* 通配)</label>
|
|
859
|
+
<textarea class="input" id="tgw-allow" rows="4" placeholder="192.168.31.101"></textarea>
|
|
860
|
+
</div>
|
|
861
|
+
<div class="hint">开启后,Trae SOLO 积分与模型能力(官方目录持续更新,网关透传任意合法模型 ID——如 GLM-5.3 / DeepSeek-V4.1-Flash / MiMo-V2.6 / Kimi-K3)变成本机的一个原生 Anthropic Messages 补全接口——在 10Router 或 Claude Code 等工具中建一个 anthropic-compatible 节点指向本机 <code>/gateway/trae/v1/messages</code> 即可调用。支持多账号自动加权轮转、429 限流自动冷却 5 分钟、流式增量与完整思考过程(thinking_delta)。白名单内的机器<b>免密直连</b>;本机回环(127.0.0.1)始终放行。</div>
|
|
862
|
+
<div class="modal-actions"><button class="btn" data-close>取消</button><button class="btn primary" id="tgw-ok">保存</button></div>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
|
|
674
866
|
<input type="file" id="file-input" accept=".json,application/json" style="display:none">
|
|
675
867
|
<div id="toast"></div>
|
|
676
868
|
|
|
677
869
|
<script>
|
|
678
870
|
'use strict';
|
|
679
871
|
|
|
872
|
+
let I18N_DICT = {};
|
|
873
|
+
function T(text, vars) {
|
|
874
|
+
const s = I18N_DICT[text] ?? text;
|
|
875
|
+
if (!vars) return s;
|
|
876
|
+
return s.replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// 语言:顶栏菜单的五档分段与面板设置弹窗的下拉共用一个真相源。
|
|
880
|
+
// 切换必须先落盘(settings.locale 由守护进程读取,日志也按它实时重渲染),再刷新页面。
|
|
881
|
+
async function setLocale(locale) {
|
|
882
|
+
const busy = $('locale-busy');
|
|
883
|
+
if (busy) busy.hidden = false;
|
|
884
|
+
try {
|
|
885
|
+
await api('/api/settings', { method: 'PUT', json: { locale } });
|
|
886
|
+
location.reload();
|
|
887
|
+
} catch (e) { if (busy) busy.hidden = true; /* 失败时保留原选项 */ }
|
|
888
|
+
}
|
|
889
|
+
function renderLocale(cur) {
|
|
890
|
+
if (cur) state.locale = cur;
|
|
891
|
+
const l = state.locale || 'zh-CN';
|
|
892
|
+
const seg = $('locale-seg');
|
|
893
|
+
if (seg) for (const btn of seg.querySelectorAll('[data-locale]')) btn.classList.toggle('on', btn.dataset.locale === l);
|
|
894
|
+
const sel = $('set-locale');
|
|
895
|
+
if (sel && sel.value !== l) sel.value = l;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
|
|
680
899
|
// ── 图标(Lucide 风格,currentColor) ──
|
|
681
900
|
const svg = (d) => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + d + '</svg>';
|
|
682
901
|
const I = {
|
|
@@ -693,14 +912,24 @@ const I = {
|
|
|
693
912
|
credit: svg('<path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"/>'),
|
|
694
913
|
gift: svg('<rect x="3" y="8" width="18" height="4"/><path d="M12 8v13"/><path d="M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7"/><path d="M7.5 8a2.5 2.5 0 0 1 0-5C11 3 12 8 12 8s1-5 4.5-5a2.5 2.5 0 0 1 0 5"/>'),
|
|
695
914
|
tune: svg('<path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3"/><path d="M1 14h6M9 8h6M17 16h6"/>'),
|
|
915
|
+
// 齿轮:面板设置(比「推子」更通用地表达“设置”)
|
|
916
|
+
settings: svg('<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>'),
|
|
696
917
|
chevUp: svg('<path d="M18 15l-6-6-6 6"/>'),
|
|
918
|
+
grip: svg('<circle cx="9" cy="5" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="19" r="1"/>'),
|
|
697
919
|
chevDown: svg('<path d="M6 9l6 6 6-6"/>'),
|
|
698
920
|
chevRight: svg('<path d="M9 18l6-6-6-6"/>'),
|
|
699
921
|
check: svg('<path d="M20 6L9 17l-5-5"/>'),
|
|
922
|
+
grid: svg('<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>'),
|
|
923
|
+
book: svg('<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>'),
|
|
924
|
+
power: svg('<path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/>'),
|
|
925
|
+
monitor: svg('<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>'),
|
|
926
|
+
clock: svg('<circle cx="12" cy="12" r="9"/><path d="M12 7.5v5l3 2"/>'),
|
|
927
|
+
msg: svg('<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>'),
|
|
928
|
+
heart: svg('<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>'),
|
|
700
929
|
};
|
|
701
930
|
|
|
702
931
|
const $ = (id) => document.getElementById(id);
|
|
703
|
-
const state = { accounts: [], quotas: {}, status: null, busy: {}, view: 'dash', region: '', addProduct: '', logTab: 'run' };
|
|
932
|
+
const state = { accounts: [], quotas: {}, status: null, busy: {}, view: 'dash', region: '', addProduct: '', logTab: 'run', logLevel: 'all', locale: 'zh-CN' };
|
|
704
933
|
const VIEWS = ['dash', 'qoder', 'workbuddy', 'zcode', 'mirasim', 'catpaw', 'trae', 'minimax', 'tenrouter'];
|
|
705
934
|
// 10Router 集成:config 来自 /api/tenrouter,quotas 来自 /api/tenrouter/quotas
|
|
706
935
|
const tr = { config: null, quotas: null, error: null, loading: false, syncing: false };
|
|
@@ -713,7 +942,7 @@ const PRODUCTS = [
|
|
|
713
942
|
{ id: 'trae', label: 'Trae' },
|
|
714
943
|
{ id: 'minimax', label: 'MiniMax' },
|
|
715
944
|
];
|
|
716
|
-
const prodLabel = (id) => (PRODUCTS.find((x) => x.id === id) || { label: id }).label;
|
|
945
|
+
const prodLabel = (id) => T((PRODUCTS.find((x) => x.id === id) || { label: id }).label);
|
|
717
946
|
|
|
718
947
|
// ── 全局组件开关:不用某个产品 / 10Router 时整块收起(偏好存浏览器本地) ──
|
|
719
948
|
const COMPONENTS = [
|
|
@@ -853,7 +1082,7 @@ const CLIENT_LABEL = { qoder: 'Qoder', workbuddy: 'WorkBuddy', zcode: 'ZCode', m
|
|
|
853
1082
|
// 额度单位为 Credits/时长而非「积分」口径的产品(不计入剩余积分合计)
|
|
854
1083
|
const CREDIT_VIEWS = new Set(['zcode', 'mirasim', 'catpaw']);
|
|
855
1084
|
// 网关调用日志:tag → 所属产品页(无网关的产品不展示日志卡)
|
|
856
|
-
const GW_LOG_TAG = { zcode: 'ZCODE-GW', minimax: 'MINIMAX-GW' };
|
|
1085
|
+
const GW_LOG_TAG = { zcode: 'ZCODE-GW', minimax: 'MINIMAX-GW', trae: 'TRAE-GW' };
|
|
857
1086
|
const provOf = (p) => PROV[p] || { prod: 'qoder', prodLabel: p, region: '', cls: '' };
|
|
858
1087
|
const provBadges = (p, regionOnly, flavor) => {
|
|
859
1088
|
const v = provOf(p);
|
|
@@ -862,8 +1091,8 @@ const provBadges = (p, regionOnly, flavor) => {
|
|
|
862
1091
|
: flavor === 'zai' ? '<span class="badge intl">Z.ai</span>'
|
|
863
1092
|
: '<span class="badge cn">BigModel</span>')
|
|
864
1093
|
: '';
|
|
865
|
-
return (regionOnly ? '' : '<span class="badge prod ' + v.prod + '">' + esc(v.prodLabel) + '</span>')
|
|
866
|
-
+ (v.region ? '<span class="badge ' + v.cls + '">' + v.region + '</span>' : '')
|
|
1094
|
+
return (regionOnly ? '' : '<span class="badge prod ' + v.prod + '">' + esc(T(v.prodLabel)) + '</span>')
|
|
1095
|
+
+ (v.region ? '<span class="badge ' + v.cls + '">' + T(v.region) + '</span>' : '')
|
|
867
1096
|
+ zBadge;
|
|
868
1097
|
};
|
|
869
1098
|
|
|
@@ -891,7 +1120,7 @@ let toastTimer = null;
|
|
|
891
1120
|
const armed = {};
|
|
892
1121
|
function toast(msg, ok) {
|
|
893
1122
|
const t = $('toast');
|
|
894
|
-
t.textContent = msg;
|
|
1123
|
+
t.textContent = T(msg);
|
|
895
1124
|
t.classList.toggle('err', ok === false);
|
|
896
1125
|
t.classList.add('show');
|
|
897
1126
|
clearTimeout(toastTimer);
|
|
@@ -914,11 +1143,11 @@ let keyPrompt = null;
|
|
|
914
1143
|
/** 弹一次密码输入框(并发的请求共用同一个);返回 true = 已输入 */
|
|
915
1144
|
function promptKey(invalid) {
|
|
916
1145
|
if (!keyPrompt) {
|
|
917
|
-
keyPrompt = uiPrompt('输入面板访问密码', '', {
|
|
918
|
-
type: 'password', okText: '进入',
|
|
919
|
-
message: (invalid ? '密码不正确,请重新输入。\n' : '')
|
|
920
|
-
+ '面板访问密码在右上角「设置」里管理。\n'
|
|
921
|
-
+ 'fnOS:安装向导设置的密码(忘了可在 fnOS「应用设置」里重设)。\n命令行部署:环境变量 CREDITDADDY_PASSWORD 的值。',
|
|
1146
|
+
keyPrompt = uiPrompt(T('输入面板访问密码'), '', {
|
|
1147
|
+
type: 'password', okText: T('进入'),
|
|
1148
|
+
message: (invalid ? T('密码不正确,请重新输入。\n') : '')
|
|
1149
|
+
+ T('面板访问密码在右上角「设置」里管理。\n')
|
|
1150
|
+
+ T('fnOS:安装向导设置的密码(忘了可在 fnOS「应用设置」里重设)。\n命令行部署:环境变量 CREDITDADDY_PASSWORD 的值。'),
|
|
922
1151
|
}).then((k) => { keyPrompt = null; if (k === null || !k.trim()) return false; setKey(k.trim()); return true; });
|
|
923
1152
|
}
|
|
924
1153
|
return keyPrompt;
|
|
@@ -952,24 +1181,217 @@ async function loadSettings() {
|
|
|
952
1181
|
try {
|
|
953
1182
|
const d = await api('/api/settings');
|
|
954
1183
|
state.sec = { enabled: Boolean(d.panelKeyEnabled), loaded: true };
|
|
1184
|
+
state.wbActivity = d.workbuddyActivity !== false; // WB 国际版活跃请求总开关(默认开)
|
|
955
1185
|
if (!d.panelKeyEnabled) setKey(''); // 关闭后清掉本地缓存的旧密钥
|
|
1186
|
+
if (d.locale) renderLocale(d.locale);
|
|
956
1187
|
} catch (e) { /* 401(密码开启但未输入)等状态由 promptKey 流程处理 */ }
|
|
957
1188
|
}
|
|
958
1189
|
|
|
1190
|
+
|
|
1191
|
+
async function applyDictFromServer() {
|
|
1192
|
+
try {
|
|
1193
|
+
const { dict, locale } = await api('/api/i18n');
|
|
1194
|
+
I18N_DICT = dict || {};
|
|
1195
|
+
renderLocale(locale); // 语言分段的高亮态以服务端为准(比 /api/settings 更早可用)
|
|
1196
|
+
document.querySelectorAll('[data-i18n]').forEach((el) => {
|
|
1197
|
+
const src = el.getAttribute('data-i18n');
|
|
1198
|
+
if (dict[src]) el.textContent = dict[src];
|
|
1199
|
+
});
|
|
1200
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
1201
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
1202
|
+
const key = n.nodeValue.trim();
|
|
1203
|
+
if (key && dict[key] && !n.parentElement?.closest('script,style')) n.nodeValue = n.nodeValue.replace(key, dict[key]);
|
|
1204
|
+
}
|
|
1205
|
+
document.querySelectorAll('[title],[placeholder]').forEach((el) => {
|
|
1206
|
+
for (const attr of ['title', 'placeholder']) {
|
|
1207
|
+
const v = el.getAttribute(attr);
|
|
1208
|
+
if (v && dict[v]) el.setAttribute(attr, dict[v]);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
} catch (e) { /* 字典不可用时保留中文原文 */ }
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
document.getElementById('set-locale')?.addEventListener('change', (ev) => setLocale(ev.target.value));
|
|
1215
|
+
|
|
1216
|
+
// ── 通用设置:全局自动签到(开关 + 频率,落盘立刻重调度)、日志默认显示方式(本机偏好)──
|
|
1217
|
+
const wbActOn = () => state.wbActivity !== false;
|
|
1218
|
+
$('cfg-wb-activity').addEventListener('change', async (e) => {
|
|
1219
|
+
try {
|
|
1220
|
+
await api('/api/settings', { method: 'PUT', json: { workbuddyActivity: e.target.checked } });
|
|
1221
|
+
await loadSettings();
|
|
1222
|
+
render(); renderStatus();
|
|
1223
|
+
toast(T('已保存'), true);
|
|
1224
|
+
} catch (err) { toast(err.message, false); }
|
|
1225
|
+
});
|
|
1226
|
+
function fillAutoCheckin() {
|
|
1227
|
+
const sc = state.status && state.status.scheduler;
|
|
1228
|
+
if (!sc) return;
|
|
1229
|
+
$('cfg-auto-checkin').checked = sc.enabled !== false;
|
|
1230
|
+
$('cfg-checkin-interval').value = String(sc.intervalMin || 120);
|
|
1231
|
+
}
|
|
1232
|
+
function logsDisplayDefault() {
|
|
1233
|
+
try { return localStorage.getItem('qd_logs_expanded') !== '0' ? 'expanded' : 'collapsed'; } catch { return 'expanded'; }
|
|
1234
|
+
}
|
|
1235
|
+
function applyLogsDisplay(mode) {
|
|
1236
|
+
const collapsed = mode !== 'expanded';
|
|
1237
|
+
$('log-card').classList.toggle('collapsed', collapsed);
|
|
1238
|
+
$('btn-logs').textContent = collapsed ? T('展开') : T('收起');
|
|
1239
|
+
try { localStorage.setItem('qd_logs_expanded', collapsed ? '0' : '1'); } catch {}
|
|
1240
|
+
}
|
|
1241
|
+
$('cfg-auto-checkin').addEventListener('change', async (e) => {
|
|
1242
|
+
try {
|
|
1243
|
+
await api('/api/settings', { method: 'PUT', json: { autoCheckin: e.target.checked } });
|
|
1244
|
+
await loadStatus();
|
|
1245
|
+
toast(T('已保存'), true);
|
|
1246
|
+
} catch (err) { toast(err.message, false); fillAutoCheckin(); }
|
|
1247
|
+
});
|
|
1248
|
+
$('cfg-checkin-interval').addEventListener('change', async (e) => {
|
|
1249
|
+
try {
|
|
1250
|
+
await api('/api/settings', { method: 'PUT', json: { checkinIntervalMin: Number(e.target.value) } });
|
|
1251
|
+
await loadStatus();
|
|
1252
|
+
toast(T('已保存'), true);
|
|
1253
|
+
} catch (err) { toast(err.message, false); fillAutoCheckin(); }
|
|
1254
|
+
});
|
|
1255
|
+
$('cfg-logs-display').addEventListener('change', (e) => applyLogsDisplay(e.target.value));
|
|
1256
|
+
applyLogsDisplay(logsDisplayDefault());
|
|
1257
|
+
|
|
959
1258
|
// ── 主题 ──
|
|
1259
|
+
// 应用的是「解析后的」亮/暗;用户选的三态(跟随系统 / 亮色 / 暗色)另存于 qd_theme,
|
|
1260
|
+
// 由 renderThemeMode() 反映到菜单里的分段控件(主题只有那一个入口)。
|
|
960
1261
|
function applyTheme(t) {
|
|
961
1262
|
document.documentElement.dataset.theme = t;
|
|
962
|
-
$('btn-theme').innerHTML = t === 'dark' ? I.sun : I.moon;
|
|
963
1263
|
}
|
|
964
1264
|
function initTheme() {
|
|
965
1265
|
let t = null;
|
|
966
1266
|
try { t = localStorage.getItem('qd_theme'); } catch (e) {}
|
|
967
|
-
|
|
1267
|
+
const mq = window.matchMedia ? matchMedia('(prefers-color-scheme: dark)') : null;
|
|
1268
|
+
if (!t) t = mq && mq.matches ? 'dark' : 'light';
|
|
968
1269
|
applyTheme(t);
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1270
|
+
// 自动跟随系统:用户没手动切过(localStorage 无值)时,系统换深色/浅色实时响应,
|
|
1271
|
+
// 不用刷新页面;手动切过则尊重用户选择,不再跟系统跑。
|
|
1272
|
+
const onSysTheme = (e) => {
|
|
1273
|
+
let saved = null;
|
|
1274
|
+
try { saved = localStorage.getItem('qd_theme'); } catch (err) {}
|
|
1275
|
+
if (!saved) applyTheme(e.matches ? 'dark' : 'light');
|
|
1276
|
+
};
|
|
1277
|
+
if (mq) {
|
|
1278
|
+
if (mq.addEventListener) mq.addEventListener('change', onSysTheme);
|
|
1279
|
+
else if (mq.addListener) mq.addListener(onSysTheme); // 旧 Safari
|
|
1280
|
+
}
|
|
1281
|
+
// 右上角菜单按钮:打开聚合操作弹窗(openModal/遮罩点击/ESC/✕ 由通用弹窗机制处理)
|
|
1282
|
+
$('btn-header-menu').innerHTML = I.grid;
|
|
1283
|
+
const hmenuPop = $('hmenu-pop');
|
|
1284
|
+
const closeHmenu = () => hmenuPop.classList.remove('show');
|
|
1285
|
+
$('btn-header-menu').addEventListener('click', (e) => {
|
|
1286
|
+
e.stopPropagation();
|
|
1287
|
+
closeModal('m-settings'); // 设置弹层与聚合菜单同处一处,开一个收一个
|
|
1288
|
+
const willOpen = !hmenuPop.classList.contains('show');
|
|
1289
|
+
if (willOpen) renderThemeMode(); // 打开时同步一次当前主题态(系统可能已变过)
|
|
1290
|
+
hmenuPop.classList.toggle('show', willOpen);
|
|
1291
|
+
});
|
|
1292
|
+
// 点菜单外任意处 / ESC 收起下拉
|
|
1293
|
+
document.addEventListener('click', (e) => { if (!e.target.closest('.hmenu')) closeHmenu(); });
|
|
1294
|
+
document.addEventListener('click', (e) => {
|
|
1295
|
+
const st = $('m-settings');
|
|
1296
|
+
// target 已脱离 DOM = 点击者所在区域刚被重渲染(如组件显隐切换重建 comp-menu),不算「点外面」
|
|
1297
|
+
if (st && st.classList.contains('show') && e.target.isConnected && !e.target.closest('#m-settings') && !e.target.closest('.head-setpop')) closeModal('m-settings');
|
|
1298
|
+
});
|
|
1299
|
+
window.addEventListener('scroll', closeHmenu, true);
|
|
1300
|
+
// 菜单项图标:与头部按钮同款 Lucide 描边(静态文字前插图标)
|
|
1301
|
+
const withIcon = (id, icon) => { const b = $(id); if (b) b.innerHTML = icon + b.textContent; };
|
|
1302
|
+
withIcon('hm-update', I.refresh);
|
|
1303
|
+
withIcon('changelog-brief-btn', I.book);
|
|
1304
|
+
withIcon('hm-home', I.github); // 由头部独立图标收进「关于」分区
|
|
1305
|
+
withIcon('hm-restart', I.power);
|
|
1306
|
+
withIcon('feedback-btn', I.msg);
|
|
1307
|
+
withIcon('donate-btn', I.heart);
|
|
1308
|
+
// 检查更新:桌面版走应用内更新;纯浏览器(无 updater 桥)也保留入口——点开给「去 Releases 手动下载」的承接。
|
|
1309
|
+
$('hm-update').onclick = () => { closeHmenu(); openUpdateModal(true); };
|
|
1310
|
+
$('hm-restart').onclick = async () => {
|
|
1311
|
+
closeHmenu();
|
|
1312
|
+
const ok = await uiConfirm(T('确定要重启 CreditDaddy 服务吗?正在进行的任务可能会中断。'), T('重启服务'), T('重启'));
|
|
1313
|
+
if (ok) restartCreditDaddy();
|
|
1314
|
+
};
|
|
1315
|
+
$('changelog-brief-btn').onclick = () => { closeHmenu(); openChangelogModal(); };
|
|
1316
|
+
// 主题:菜单里的三态分段控件(跟随系统 / 亮色 / 暗色),当前态高亮。
|
|
1317
|
+
// 「跟随系统」= 清掉手动选择,matchMedia 实时跟跑;手动选过亮/暗则固定不再跟系统
|
|
1318
|
+
// (与 initTheme 里 onSysTheme 判定同一存储键 qd_theme)。菜单保持展开,连点可连续比较观感。
|
|
1319
|
+
const THEME_MODES = [
|
|
1320
|
+
['auto', T('跟随系统'), I.monitor],
|
|
1321
|
+
['light', T('亮色'), I.sun],
|
|
1322
|
+
['dark', T('暗色'), I.moon],
|
|
1323
|
+
];
|
|
1324
|
+
function currentThemeMode() {
|
|
1325
|
+
try { const t = localStorage.getItem('qd_theme'); return t === 'light' || t === 'dark' ? t : 'auto'; } catch (e) { return 'auto'; }
|
|
1326
|
+
}
|
|
1327
|
+
function applyThemeMode(mode) {
|
|
1328
|
+
try {
|
|
1329
|
+
if (mode === 'auto') localStorage.removeItem('qd_theme');
|
|
1330
|
+
else localStorage.setItem('qd_theme', mode);
|
|
1331
|
+
} catch (e) {}
|
|
1332
|
+
const sysDark = window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches;
|
|
1333
|
+
applyTheme(mode === 'auto' ? (sysDark ? 'dark' : 'light') : mode);
|
|
1334
|
+
renderThemeMode();
|
|
1335
|
+
}
|
|
1336
|
+
// 分段控件:注入图标 + 绑定一次点击;renderThemeMode 只切换 .on,不重建节点(保住焦点)
|
|
1337
|
+
// 与菜单项一样,点击不收起菜单——切主题本来就该能连着试。
|
|
1338
|
+
function renderThemeMode() {
|
|
1339
|
+
const seg = $('theme-seg');
|
|
1340
|
+
if (seg) {
|
|
1341
|
+
const cur = currentThemeMode();
|
|
1342
|
+
for (const btn of seg.querySelectorAll('[data-theme-mode]')) {
|
|
1343
|
+
const mode = btn.dataset.themeMode;
|
|
1344
|
+
if (!btn.dataset.ready) {
|
|
1345
|
+
const def = THEME_MODES.find((m) => m[0] === mode);
|
|
1346
|
+
btn.innerHTML = (def ? def[2] : '') + btn.textContent.trim();
|
|
1347
|
+
btn.dataset.ready = '1';
|
|
1348
|
+
}
|
|
1349
|
+
btn.classList.toggle('on', mode === cur);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
renderThemeButton(); // 所有改主题的通路(头部按钮 / 菜单分段 / 系统主题变化兜底)统一同步图标
|
|
1353
|
+
}
|
|
1354
|
+
// 头部独立主题按钮:只做亮↔暗一键反转(每次点击必有视觉反馈)。
|
|
1355
|
+
// 「跟随系统」不进循环——跟随系统与系统主题一致时,点那一档观感=没反应(用户反馈);
|
|
1356
|
+
// 需要跟随系统走菜单里的三态分段控件。
|
|
1357
|
+
const themeBtn = $('btn-theme');
|
|
1358
|
+
function resolvedTheme() { return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light'; }
|
|
1359
|
+
function renderThemeButton() {
|
|
1360
|
+
if (!themeBtn) return;
|
|
1361
|
+
const cur = resolvedTheme();
|
|
1362
|
+
themeBtn.innerHTML = cur === 'dark' ? I.moon : I.sun;
|
|
1363
|
+
themeBtn.title = T('主题:{mode}(点击切换)', { mode: T(cur === 'dark' ? '暗色' : '亮色') });
|
|
1364
|
+
}
|
|
1365
|
+
themeBtn.addEventListener('click', () => applyThemeMode(resolvedTheme() === 'dark' ? 'light' : 'dark'));
|
|
1366
|
+
// 字典到达后需要重渲染主题按钮(标题/图标):暴露出脚本级入口供 boot 调
|
|
1367
|
+
window.__syncTheme = renderThemeMode;
|
|
1368
|
+
renderThemeMode();
|
|
1369
|
+
$('theme-seg').addEventListener('click', (e) => {
|
|
1370
|
+
const btn = e.target.closest('[data-theme-mode]');
|
|
1371
|
+
if (btn) applyThemeMode(btn.dataset.themeMode);
|
|
1372
|
+
});
|
|
1373
|
+
renderLocale();
|
|
1374
|
+
$('locale-seg').addEventListener('click', (e) => {
|
|
1375
|
+
const btn = e.target.closest('[data-locale]');
|
|
1376
|
+
if (btn && btn.dataset.locale !== state.locale) setLocale(btn.dataset.locale);
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
// 建议反馈:直达 GitHub issue 模板选择页(🐞 Bug 反馈 / ✨ 功能建议 / 🔒 安全问题,
|
|
1380
|
+
// 表单已含版本 / 安装渠道 / 平台字段,与 10Router 仓库同一套结构)
|
|
1381
|
+
$('feedback-btn').onclick = () => {
|
|
1382
|
+
closeHmenu();
|
|
1383
|
+
const url = 'https://github.com/techysy/CreditDaddy/issues/new/choose';
|
|
1384
|
+
if (window.creditdaddy && typeof window.creditdaddy.shellOpen === 'function')
|
|
1385
|
+
window.creditdaddy.shellOpen(url);
|
|
1386
|
+
else window.open(url, '_blank');
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
// 捐赠(桌面版走 IPC 桥;网页直接开浏览器)
|
|
1390
|
+
$('donate-btn').onclick = () => {
|
|
1391
|
+
closeHmenu();
|
|
1392
|
+
if (window.creditdaddy && typeof window.creditdaddy.shellOpen === 'function')
|
|
1393
|
+
window.creditdaddy.shellOpen('https://github.com/sponsors/techysy');
|
|
1394
|
+
else window.open('https://github.com/sponsors/techysy', '_blank');
|
|
973
1395
|
};
|
|
974
1396
|
}
|
|
975
1397
|
|
|
@@ -983,17 +1405,17 @@ function statusOf(a) {
|
|
|
983
1405
|
const done = state.status && state.status.todayDone && state.status.todayDone[a.id] === today;
|
|
984
1406
|
const r = a.lastResult;
|
|
985
1407
|
const fresh = r && r.at && state.status && (Date.now() - new Date(r.at).getTime() < 26 * 3600e3);
|
|
986
|
-
if (r && fresh && r.status === 'limited' && done) return { cls: 'limited', text: '本机已领', msg: r.message };
|
|
1408
|
+
if (r && fresh && r.status === 'limited' && done) return { cls: 'limited', text: T('本机已领'), msg: r.message };
|
|
987
1409
|
if (done) {
|
|
988
|
-
if (intl) return { cls: 'ok', text: '今日活跃', msg: r && r.message ? r.message : '' };
|
|
1410
|
+
if (intl) return { cls: 'ok', text: T('今日活跃'), msg: r && r.message ? r.message : '' };
|
|
989
1411
|
const amt = r && r.status === 'checked-in' && fresh && r.amount ? ' +' + fmtNum(r.amount) : '';
|
|
990
|
-
const streak = r && !intl && r.streakDays ? ' · 连续 ' + r.streakDays + ' 天' : '';
|
|
991
|
-
return { cls: 'ok', text: '今日已领' + amt + streak, msg: r && r.message && !['checked-in', 'already'].includes(r.status) ? r.message : '' };
|
|
1412
|
+
const streak = r && !intl && r.streakDays ? T(' · 连续 ') + r.streakDays + T(' 天') : '';
|
|
1413
|
+
return { cls: 'ok', text: T('今日已领') + amt + streak, msg: r && r.message && !['checked-in', 'already'].includes(r.status) ? r.message : '' };
|
|
992
1414
|
}
|
|
993
|
-
const failText = intl ? '活跃失败' : '领取失败';
|
|
1415
|
+
const failText = intl ? T('活跃失败') : T('领取失败');
|
|
994
1416
|
if (r && fresh && r.status === 'failed') return { cls: 'fail', text: failText, msg: r.message, err: true };
|
|
995
|
-
if (r && fresh && r.status === 'no-activity') return { cls: 'pending', text: intl ? '待活跃' : '待领取', msg: r.message };
|
|
996
|
-
return { cls: 'pending', text: intl ? '未活跃' : '未领取', msg: '' };
|
|
1417
|
+
if (r && fresh && r.status === 'no-activity') return { cls: 'pending', text: intl ? T('待活跃') : T('待领取'), msg: r.message };
|
|
1418
|
+
return { cls: 'pending', text: intl ? T('未活跃') : T('未领取'), msg: '' };
|
|
997
1419
|
}
|
|
998
1420
|
|
|
999
1421
|
function expiryCell(a) {
|
|
@@ -1002,11 +1424,11 @@ function expiryCell(a) {
|
|
|
1002
1424
|
if (q && q.planExpiresAt) {
|
|
1003
1425
|
const msPlan = new Date(q.planExpiresAt).getTime() - Date.now();
|
|
1004
1426
|
const planName = q.plan ? esc(q.plan) + ' ' : '';
|
|
1005
|
-
if (msPlan <= 0) return { label: planName + '已到期', warn: true, title: '套餐已到期' };
|
|
1427
|
+
if (msPlan <= 0) return { label: planName + T('已到期'), warn: true, title: T('套餐已到期') };
|
|
1006
1428
|
return {
|
|
1007
1429
|
label: planName + fmtTime(q.planExpiresAt).slice(0, 10),
|
|
1008
1430
|
warn: msPlan < 3 * 86400e3,
|
|
1009
|
-
title: '套餐有效期至 ' + fmtTime(q.planExpiresAt),
|
|
1431
|
+
title: T('套餐有效期至 ') + fmtTime(q.planExpiresAt),
|
|
1010
1432
|
isPlan: true,
|
|
1011
1433
|
};
|
|
1012
1434
|
}
|
|
@@ -1014,17 +1436,17 @@ function expiryCell(a) {
|
|
|
1014
1436
|
// 具有 refreshToken 的账号会自动在后台续期,短期 exp 属于正常轮换,不应显示刺眼红字误导用户
|
|
1015
1437
|
if (a.hasRefreshToken) {
|
|
1016
1438
|
return {
|
|
1017
|
-
label: '凭据有效 · 自动续期',
|
|
1439
|
+
label: T('凭据有效 · 自动续期'),
|
|
1018
1440
|
warn: false,
|
|
1019
|
-
title: a.expiresAt ? '本次登录凭据有效期至 ' + fmtTime(a.expiresAt) + '(过期将自动通过 refreshToken 刷新续期,无需手动干预)' : '支持自动刷新续期',
|
|
1441
|
+
title: a.expiresAt ? T('本次登录凭据有效期至 ') + fmtTime(a.expiresAt) + T('(过期将自动通过 refreshToken 刷新续期,无需手动干预)') : T('支持自动刷新续期'),
|
|
1020
1442
|
isAuto: true,
|
|
1021
1443
|
};
|
|
1022
1444
|
}
|
|
1023
1445
|
|
|
1024
|
-
if (!a.expiresAt) return { label: a.isPat ? 'PAT 长期有效' : '—', warn: false };
|
|
1446
|
+
if (!a.expiresAt) return { label: a.isPat ? T('PAT 长期有效') : '—', warn: false };
|
|
1025
1447
|
const ms = new Date(a.expiresAt).getTime() - Date.now();
|
|
1026
|
-
if (ms <= 0) return { label: '已过期', warn: true, title: '登录凭据已过期' };
|
|
1027
|
-
return { label: fmtTime(a.expiresAt), warn: ms < 48 * 3600e3, title: '有效期至 ' + fmtTime(a.expiresAt) };
|
|
1448
|
+
if (ms <= 0) return { label: T('已过期'), warn: true, title: T('登录凭据已过期') };
|
|
1449
|
+
return { label: fmtTime(a.expiresAt), warn: ms < 48 * 3600e3, title: T('有效期至 ') + fmtTime(a.expiresAt) };
|
|
1028
1450
|
}
|
|
1029
1451
|
|
|
1030
1452
|
// 通用相对重置标签:「3 天 后重置」「45 分钟 后到期」;一小时内标红,悬停看绝对时间
|
|
@@ -1033,11 +1455,11 @@ function relReset(q) {
|
|
|
1033
1455
|
if (!at) return null;
|
|
1034
1456
|
const ms = new Date(at).getTime() - Date.now();
|
|
1035
1457
|
if (!Number.isFinite(ms)) return null;
|
|
1036
|
-
if (ms <= 0) return { text: '即将' + (q.recurring === false ? '到期' : '重置'), soon: true };
|
|
1458
|
+
if (ms <= 0) return { text: T('即将') + (q.recurring === false ? T('到期') : T('重置')), soon: true };
|
|
1037
1459
|
const m = Math.ceil(ms / 60000);
|
|
1038
1460
|
const d = Math.floor(m / 1440), h = Math.floor((m % 1440) / 60), mm = m % 60;
|
|
1039
|
-
const dur = d > 0 ? d + ' 天' + (h ? h + ' 小时' : '') : h > 0 ? h + ' 小时' + (mm ? mm + ' 分' : '') : Math.max(1, mm) + ' 分钟';
|
|
1040
|
-
return { text: dur + ' 后' + (q.recurring === false ? '到期' : '重置'), soon: ms < 3600e3 };
|
|
1461
|
+
const dur = d > 0 ? d + T(' 天') + (h ? h + T(' 小时') : '') : h > 0 ? h + T(' 小时') + (mm ? mm + T(' 分') : '') : Math.max(1, mm) + T(' 分钟');
|
|
1462
|
+
return { text: dur + T(' 后') + (q.recurring === false ? T('到期') : T('重置')), soon: ms < 3600e3 };
|
|
1041
1463
|
}
|
|
1042
1464
|
function relResetHtml(q) {
|
|
1043
1465
|
const r = relReset(q);
|
|
@@ -1057,38 +1479,38 @@ function trLine(name, valHtml, title) {
|
|
|
1057
1479
|
|
|
1058
1480
|
function creditHtml(a) {
|
|
1059
1481
|
const q = state.quotas[a.id];
|
|
1060
|
-
if (!q) return '<div class="credit-line"><span class="dim">读取积分中…</span></div><div class="bar"></div>';
|
|
1061
|
-
if (q.error) return '<div class="credit-line"><span class="dim" title="' + esc(q.error) + '"
|
|
1062
|
-
if (q.empty) return '<div class="credit-line"><span class="dim">无有效套餐(仅免费额度)</span></div><div class="bar"></div>';
|
|
1482
|
+
if (!q) return T('<div class="credit-line"><span class="dim">读取积分中…</span></div><div class="bar"></div>');
|
|
1483
|
+
if (q.error) return '<div class="credit-line"><span class="dim" title="' + esc(T(q.error)) + '">' + T('积分读取失败:') + esc(q.error.slice(0, 40)) + '</span></div><div class="bar"></div>';
|
|
1484
|
+
if (q.empty) return T('<div class="credit-line"><span class="dim">无有效套餐(仅免费额度)</span></div><div class="bar"></div>');
|
|
1063
1485
|
|
|
1064
1486
|
// 妙手:Credits 钱包余额 + 当前版本(体验版 / 专业版),无「总量」口径,不画水位条
|
|
1065
1487
|
if (a.product === 'catpaw') {
|
|
1066
1488
|
const planBadge = q.plan ? '<span class="badge">' + esc(q.plan) + '</span>' : '';
|
|
1067
|
-
const expHtml = q.planExpiresAt ? '<span class="dim">' + fmtTime(q.planExpiresAt).slice(0, 10) + ' 到期</span>'
|
|
1068
|
-
: q.nextRefreshAt ? '<span class="dim">' + fmtTime(q.nextRefreshAt).slice(0, 10) + ' 下次刷新</span>' : '';
|
|
1069
|
-
return '<div class="credit-line"><span class="credit"><span class="lbl">可用额度</span><b>' + fmtNum(q.remaining) + '</b>Credits</span>'
|
|
1489
|
+
const expHtml = q.planExpiresAt ? '<span class="dim">' + fmtTime(q.planExpiresAt).slice(0, 10) + T(' 到期</span>')
|
|
1490
|
+
: q.nextRefreshAt ? '<span class="dim">' + fmtTime(q.nextRefreshAt).slice(0, 10) + T(' 下次刷新</span>') : '';
|
|
1491
|
+
return T('<div class="credit-line"><span class="credit"><span class="lbl">可用额度</span><b>') + fmtNum(q.remaining) + '</b>Credits</span>'
|
|
1070
1492
|
+ '<span class="credit dim">' + planBadge + (planBadge && expHtml ? ' · ' : '') + expHtml + '</span></div>';
|
|
1071
1493
|
}
|
|
1072
1494
|
|
|
1073
1495
|
// 多窗口配额体系(mirasim 等):每个窗口平等等权,独立一行带分散对齐的名称、数值与三档细水位条
|
|
1074
1496
|
if (a.product === 'mirasim' && q.parts && q.parts.length) {
|
|
1075
1497
|
const isExhausted = q.exceeded || (q.remaining <= 0);
|
|
1076
|
-
const planBadge = q.plan ? '<span class="badge">' + esc(q.plan) + ' 套餐</span>' : '';
|
|
1077
|
-
const expLabel = q.planExpiresAt ? '<span class="dim">' + fmtTime(q.planExpiresAt).slice(0, 10) + ' 到期</span>' : '';
|
|
1498
|
+
const planBadge = q.plan ? '<span class="badge">' + esc(T(q.plan)) + T(' 套餐</span>') : '';
|
|
1499
|
+
const expLabel = q.planExpiresAt ? '<span class="dim">' + fmtTime(q.planExpiresAt).slice(0, 10) + T(' 到期</span>') : '';
|
|
1078
1500
|
// 状态采用温和的徽章,避免整行大红字制造恐慌
|
|
1079
1501
|
const topStatus = isExhausted
|
|
1080
|
-
? '<span class="pill pending">' + esc(q.bottleneckLabel || '额度已用尽') + '</span>'
|
|
1081
|
-
: '<span class="lbl">可用额度</span><b>' + fmtNum(q.remaining) + '%</b>';
|
|
1502
|
+
? '<span class="pill pending">' + esc(T(q.bottleneckLabel || '') || T('额度已用尽')) + '</span>'
|
|
1503
|
+
: T('<span class="lbl">可用额度</span><b>') + fmtNum(q.remaining) + '%</b>';
|
|
1082
1504
|
|
|
1083
|
-
const fmtUnits = (n) => (n >= 10000 ? (n / 10000).toFixed(2) + '万' : fmtNum(Math.round(n)));
|
|
1505
|
+
const fmtUnits = (n) => (n >= 10000 ? (n / 10000).toFixed(2) + T('万') : fmtNum(Math.round(n)));
|
|
1084
1506
|
const rows = q.parts.map((p) => {
|
|
1085
1507
|
const pct = p.remainingPercentage ?? (p.total > 0 ? Math.max(0, Math.min(100, (p.remaining / p.total) * 100)) : 0);
|
|
1086
1508
|
const isZero = pct <= 0;
|
|
1087
1509
|
const numTxt = p.total > 0 ? fmtUnits(p.remaining) + ' / ' + fmtUnits(p.total) : '';
|
|
1088
1510
|
// 数值保持中性温和排版,不用鲜红大字恐吓用户
|
|
1089
1511
|
const pctTxt = isZero
|
|
1090
|
-
? '<span class="dim">剩 0%</span>'
|
|
1091
|
-
: '<span>剩 ' + fmtNum(pct) + '%</span>';
|
|
1512
|
+
? T('<span class="dim">剩 0%</span>')
|
|
1513
|
+
: T('<span>剩 ') + fmtNum(pct) + '%</span>';
|
|
1092
1514
|
const valHtml = pctTxt + (numTxt ? ' <small class="dim">(' + numTxt + ')</small>' : '') + ' ' + relResetHtml(p);
|
|
1093
1515
|
return trLine(p.name, valHtml) + trMeter(pct);
|
|
1094
1516
|
}).join('');
|
|
@@ -1098,16 +1520,16 @@ function creditHtml(a) {
|
|
|
1098
1520
|
+ '<div class="mirasim-windows">' + rows + '</div>';
|
|
1099
1521
|
}
|
|
1100
1522
|
|
|
1101
|
-
const unit = q.unit ? ' ' + q.unit : '';
|
|
1523
|
+
const unit = q.unit ? ' ' + T(q.unit) : '';
|
|
1102
1524
|
if (q.unit) {
|
|
1103
1525
|
const items = q.parts.map((x) => {
|
|
1104
|
-
const base = esc(x.name) + ' ' + fmtNum(x.remaining) + '/' + fmtNum(x.total) + (x.unit ? ' ' + esc(x.unit) : '');
|
|
1105
|
-
if (x.pending && x.startsAt) return base + '(' + fmtTime(x.startsAt).slice(5, 16) + ' 生效)';
|
|
1106
|
-
return base + (x.expiresAt ? '(' + fmtTime(x.expiresAt).slice(5, 16) + (x.recurring ? ' 重置)' : ' 到期)') : '');
|
|
1526
|
+
const base = esc(T(x.name)) + ' ' + fmtNum(x.remaining) + '/' + fmtNum(x.total) + (x.unit ? ' ' + esc(T(x.unit)) : '');
|
|
1527
|
+
if (x.pending && x.startsAt) return base + T('(') + fmtTime(x.startsAt).slice(5, 16) + T(' 生效)');
|
|
1528
|
+
return base + (x.expiresAt ? T('(') + fmtTime(x.expiresAt).slice(5, 16) + (x.recurring ? T(' 重置)') : T(' 到期)')) : '');
|
|
1107
1529
|
}).join(' · ');
|
|
1108
1530
|
const pct = q.total > 0 ? Math.min(1, q.remaining / q.total) : 0;
|
|
1109
1531
|
// 满额(100%)不再渲染灰色「已使用」尾段——它只占 0 宽也会造成几像素的灰边
|
|
1110
|
-
return '<div class="credit-line"><span class="credit"><span class="lbl">剩余</span><b>' + fmtNum(q.remaining) + '</b>' + esc(unit) + '</span>'
|
|
1532
|
+
return T('<div class="credit-line"><span class="credit"><span class="lbl">剩余</span><b>') + fmtNum(q.remaining) + '</b>' + esc(unit) + '</span>'
|
|
1111
1533
|
+ '<span class="credit dim" title="' + items + '">' + (q.plan ? esc(q.plan) + ' · ' : '') + items + '</span></div>'
|
|
1112
1534
|
+ '<div class="bar"><span class="seg" style="flex:' + pct.toFixed(4) + ' 1 0"></span>'
|
|
1113
1535
|
+ (pct < 0.9999 ? '<span class="seg used" style="flex:' + (1 - pct).toFixed(4) + ' 1 0"></span>' : '')
|
|
@@ -1118,14 +1540,14 @@ function creditHtml(a) {
|
|
|
1118
1540
|
if (total > 0) {
|
|
1119
1541
|
q.parts.filter((x) => x.remaining > 0).forEach((x, i) => {
|
|
1120
1542
|
bar += '<span class="seg" style="flex:' + (x.remaining / total).toFixed(4) + ' 1 0;--a:' + (x.recurring ? 1 : Math.max(.45, .85 - i * .05)) + '" title="'
|
|
1121
|
-
+ esc(x.name) + ':剩余 ' + fmtNum(x.remaining) + ' / ' + fmtNum(x.total) + (x.expiresAt ? (x.recurring ? ',' + fmtTime(x.expiresAt).slice(0, 10) + ' 重置' : ',' + fmtTime(x.expiresAt).slice(0, 10) + ' 到期') : '') + '"></span>';
|
|
1543
|
+
+ esc(T(x.name)) + T(':剩余 ') + fmtNum(x.remaining) + ' / ' + fmtNum(x.total) + (x.expiresAt ? (x.recurring ? T(',') + fmtTime(x.expiresAt).slice(0, 10) + T(' 重置') : T(',') + fmtTime(x.expiresAt).slice(0, 10) + T(' 到期')) : '') + '"></span>';
|
|
1122
1544
|
});
|
|
1123
|
-
if (q.used > 0) bar += '<span class="seg used" style="flex:' + (q.used / total).toFixed(4) + ' 1 0" title="已使用 ' + fmtNum(q.used) + '"></span>';
|
|
1545
|
+
if (q.used > 0) bar += '<span class="seg used" style="flex:' + (q.used / total).toFixed(4) + ' 1 0" title="' + T('已使用 ') + fmtNum(q.used) + '"></span>';
|
|
1124
1546
|
}
|
|
1125
1547
|
const soon = q.parts.filter((x) => x.remaining > 0 && !x.recurring && x.expiresAt).sort((x, y) => x.expiresAt.localeCompare(y.expiresAt))[0];
|
|
1126
1548
|
return '<div class="credit-line">'
|
|
1127
|
-
+ '<span class="credit"><span class="lbl">剩余</span>' + I.credit + '<b>' + fmtNum(q.remaining) + '</b></span>'
|
|
1128
|
-
+ '<span class="credit dim">' + (soon ? fmtNum(soon.remaining) + ' 于 ' + fmtTime(soon.expiresAt).slice(5, 10) + ' 到期 · ' : '') + '已用 ' + fmtNum(q.used) + ' / 共 ' + fmtNum(total) + (q.exceeded ? ' · <span style="color:var(--err)">已超额</span>' : '') + '</span>'
|
|
1549
|
+
+ T('<span class="credit"><span class="lbl">剩余</span>') + I.credit + '<b>' + fmtNum(q.remaining) + '</b></span>'
|
|
1550
|
+
+ '<span class="credit dim">' + (soon ? fmtNum(soon.remaining) + T(' 于 ') + fmtTime(soon.expiresAt).slice(5, 10) + T(' 到期 · ') : '') + T('已用 ') + fmtNum(q.used) + T(' / 共 ') + fmtNum(total) + (q.exceeded ? T(' · <span style="color:var(--err)">已超额</span>') : '') + '</span>'
|
|
1129
1551
|
+ '</div><div class="bar">' + bar + '</div>';
|
|
1130
1552
|
}
|
|
1131
1553
|
|
|
@@ -1137,29 +1559,31 @@ function cardHtml(a) {
|
|
|
1137
1559
|
const canCheckin = CHECKIN_PRODUCTS.has(a.product);
|
|
1138
1560
|
return '<div class="card" draggable="true" data-id="' + esc(a.id) + '">'
|
|
1139
1561
|
+ '<div class="row1"><div class="name-group">'
|
|
1140
|
-
+ '<span class="name" data-act="rename" title="点击修改备注名">' + esc(a.name || '(未命名)') + '</span>'
|
|
1562
|
+
+ T('<span class="name" data-act="rename" title="点击修改备注名">') + esc(a.name || T('(未命名)')) + '</span>'
|
|
1141
1563
|
+ provBadges(a.provider, true, a.flavor)
|
|
1142
|
-
+ (isCurrent ? '<span class="pill cur" title="' + esc(CLIENT_LABEL[a.product]
|
|
1143
|
-
+ (a.product === 'zcode' && isGatewayCurrent(a) ? '<span class="pill gw" title="体验包接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>' : '')
|
|
1144
|
-
+ (a.product === 'minimax' && isMiniMaxGatewayCurrent(a) ? '<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>' : '')
|
|
1564
|
+
+ (isCurrent ? '<span class="pill cur" title="' + esc(CLIENT_LABEL[a.product] ? T(CLIENT_LABEL[a.product]) : '') + T(' 客户端当前登录的账号">客户端当前</span>') : '')
|
|
1565
|
+
+ (a.product === 'zcode' && isGatewayCurrent(a) ? T('<span class="pill gw" title="体验包接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>') : '')
|
|
1566
|
+
+ (a.product === 'minimax' && isMiniMaxGatewayCurrent(a) ? T('<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>') : '')
|
|
1567
|
+
+ (a.product === 'trae' && isTraeGatewayCurrent(a) ? T('<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>') : '')
|
|
1145
1568
|
+ (a.isPat ? '<span class="badge">PAT</span>' : '')
|
|
1146
|
-
+ (a
|
|
1569
|
+
+ qoderWebBadge(a)
|
|
1570
|
+
+ (a.verified === false ? T('<span class="pill fail">token 无效</span>') : '')
|
|
1147
1571
|
+ (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>' : '')
|
|
1148
1572
|
+ '</div><div class="ops">'
|
|
1149
|
-
+ (a.product === 'zcode' ? '<button class="icon-btn" data-act="claim" title="活动领取(billing/preview → claim,可能需过一次验证码)">' + I.gift + '</button>' : '')
|
|
1150
|
-
+ (canCheckin ? '<button class="icon-btn' + (state.busy[a.id] ? ' busy' : '') + '" data-act="checkin" title="' + (a.provider === 'workbuddy-intl' ? '发送活跃请求' : '立即领取') + '">' + (state.busy[a.id] ? I.refresh : I.play) + '</button>' : '')
|
|
1151
|
-
+ (a.canSwitch && !isCurrent && st.cls !== 'fail' ? '<button class="icon-btn" data-act="switch" title="切换 ' + (CLIENT_LABEL[a.product]
|
|
1152
|
-
+ '<button class="icon-btn" data-act="quota" title="刷新积分">' + I.refresh + '</button>'
|
|
1153
|
-
+ '<button class="icon-btn del" data-act="del" title="删除(点两次确认)">' + I.trash + '</button>'
|
|
1573
|
+
+ (a.product === 'zcode' ? T('<button class="icon-btn" data-act="claim" title="活动领取(billing/preview → claim,可能需过一次验证码)">') + I.gift + '</button>' : '')
|
|
1574
|
+
+ (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>' : '')
|
|
1575
|
+
+ (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>' : '')
|
|
1576
|
+
+ T('<button class="icon-btn" data-act="quota" title="刷新积分">') + I.refresh + '</button>'
|
|
1577
|
+
+ T('<button class="icon-btn del" data-act="del" title="删除(点两次确认)">') + I.trash + '</button>'
|
|
1154
1578
|
+ '</div></div>'
|
|
1155
1579
|
+ '<div class="meta">'
|
|
1156
|
-
+ '<div class="mi"><span class="lbl"
|
|
1157
|
-
+ '<div class="mi right"><span class="lbl">' + (ex.isPlan ? '套餐到期' : ex.isAuto ? '凭据状态' : '有效期至') + '</span>'
|
|
1580
|
+
+ '<div class="mi"><span class="lbl">' + T('账号') + '</span><span class="val" title="' + esc(a.email || a.phone || a.uid || '') + '">' + esc(a.phone && !a.email ? ident : mask(ident)) + '</span></div>'
|
|
1581
|
+
+ '<div class="mi right"><span class="lbl">' + (ex.isPlan ? T('套餐到期') : ex.isAuto ? T('凭据状态') : T('有效期至')) + '</span>'
|
|
1158
1582
|
+ '<span class="val' + (ex.warn ? ' warn' : '') + '" title="' + esc(ex.title || '') + '">' + esc(ex.label) + '</span></div>'
|
|
1159
1583
|
+ '</div>'
|
|
1160
1584
|
+ creditHtml(a)
|
|
1161
1585
|
+ (st.msg && !(state.quotas[a.id] && state.quotas[a.id].error && state.quotas[a.id].error.includes(st.msg.slice(-24)))
|
|
1162
|
-
? '<div class="msg' + (st.err ? ' err' : '') + '" title="' + esc(st.msg) + '">' + esc(st.msg) + '</div>' : '')
|
|
1586
|
+
? '<div class="msg' + (st.err ? ' err' : '') + '" title="' + esc(T(st.msg)) + '">' + esc(T(st.msg)) + '</div>' : '')
|
|
1163
1587
|
+ '</div>';
|
|
1164
1588
|
}
|
|
1165
1589
|
|
|
@@ -1183,6 +1607,11 @@ function isMiniMaxGatewayCurrent(a) {
|
|
|
1183
1607
|
const last = state.mGatewayStats && state.mGatewayStats.lastAccount;
|
|
1184
1608
|
return Boolean(last) && (a.name === last || a.uid === last || a.id === last);
|
|
1185
1609
|
}
|
|
1610
|
+
/** Trae 本地接口使用中判定 */
|
|
1611
|
+
function isTraeGatewayCurrent(a) {
|
|
1612
|
+
const last = state.tGatewayStats && state.tGatewayStats.lastAccount;
|
|
1613
|
+
return Boolean(last) && (a.name === last || a.uid === last || a.id === last);
|
|
1614
|
+
}
|
|
1186
1615
|
|
|
1187
1616
|
// 客户端当前登录判定:WorkBuddy / mirasim / 妙手 / MiniMax 按 uid;ZCode 按 uid 或 email(早期导入的账号可能只存了其中一个)
|
|
1188
1617
|
function isClientCurrent(a) {
|
|
@@ -1235,7 +1664,7 @@ const EMPTY_TIP = {
|
|
|
1235
1664
|
mirasim: '还没有 mirasim 账号。<br>点「添加账号」可读取本机 mirasim 客户端当前登录的账号(本地解密,token 不出本机)。',
|
|
1236
1665
|
catpaw: '还没有妙手账号。<br>点「添加账号」可读取本机妙手(美团 CatPaw)客户端当前登录的账号(本地解密凭据,token 不出本机)。',
|
|
1237
1666
|
trae: '还没有 Trae 账号。<br>点「添加账号 → 本机导入」可读取本机 Trae 客户端当前登录的账号(本地解密 storage.json 凭据,token 不出本机)。多个账号需先在 Trae 里分别登录并逐个导入。',
|
|
1238
|
-
minimax: '还没有 MiniMax 账号。<br>点「添加账号 → 本机导入」可读取本机 MiniMax Code
|
|
1667
|
+
minimax: '还没有 MiniMax 账号。<br>点「添加账号 → 本机导入」可读取本机 MiniMax Code 客户端当前登录的账号,或用浏览器登录 / 粘贴 Bearer Token 添加。',
|
|
1239
1668
|
};
|
|
1240
1669
|
|
|
1241
1670
|
function renderProd() {
|
|
@@ -1243,7 +1672,7 @@ function renderProd() {
|
|
|
1243
1672
|
const all = ofProduct(prod);
|
|
1244
1673
|
const acc = all.filter((a) => !state.region || provOf(a.provider).cls === state.region);
|
|
1245
1674
|
const list = $('list');
|
|
1246
|
-
if (!acc.length) list.innerHTML = '<div class="empty">' + (all.length ? '该地区下没有账号。' : EMPTY_TIP[prod]) + '</div>';
|
|
1675
|
+
if (!acc.length) list.innerHTML = '<div class="empty">' + (all.length ? T('该地区下没有账号。') : T(EMPTY_TIP[prod])) + '</div>';
|
|
1247
1676
|
else list.innerHTML = sortedAccounts(prod, acc).map(cardHtml).join('');
|
|
1248
1677
|
const canCheckin = CHECKIN_PRODUCTS.has(prod);
|
|
1249
1678
|
$('s-count').textContent = acc.length;
|
|
@@ -1251,16 +1680,16 @@ function renderProd() {
|
|
|
1251
1680
|
if (prod === 'workbuddy') {
|
|
1252
1681
|
// 国内版 = 每日签到送积分;国际版 = 保持活跃(活跃不等于必有奖励)
|
|
1253
1682
|
const cnAcc = acc.filter((a) => a.provider === 'workbuddy');
|
|
1254
|
-
const intlAcc = acc.filter((a) => a.provider === 'workbuddy-intl');
|
|
1255
|
-
$('s-done-wrap').innerHTML = (cnAcc.length ? '<div class="stat"><span title="国内版每日签到送积分">领取(国内)</span><strong>' + cnAcc.filter(isDone).length + ' / ' + cnAcc.length + '</strong></div><div class="divider"></div>' : '')
|
|
1256
|
-
+ (intlAcc.length ? '<div class="stat"><span title="国际版定期发送活跃请求保持资格;活跃不等于必有奖励">活跃(国际)</span><strong>' + intlAcc.filter(isDone).length + ' / ' + intlAcc.length + '</strong></div><div class="divider"></div>' : '');
|
|
1683
|
+
const intlAcc = acc.filter((a) => a.provider === 'workbuddy-intl' && wbActOn());
|
|
1684
|
+
$('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>' : '')
|
|
1685
|
+
+ (intlAcc.length ? T('<div class="stat"><span title="国际版定期发送活跃请求保持资格;活跃不等于必有奖励">活跃(国际)</span><strong>') + intlAcc.filter(isDone).length + ' / ' + intlAcc.length + '</strong></div><div class="divider"></div>' : '');
|
|
1257
1686
|
} else {
|
|
1258
1687
|
// 从 WorkBuddy 页切回来时 s-done 可能被 innerHTML 换没了,先重建结构再写值
|
|
1259
|
-
if (!$('s-done')) $('s-done-wrap').innerHTML = '<div class="stat"><span>今日已领</span><strong id="s-done"></strong></div><div class="divider"></div>';
|
|
1688
|
+
if (!$('s-done')) $('s-done-wrap').innerHTML = T('<div class="stat"><span>今日已领</span><strong id="s-done"></strong></div><div class="divider"></div>');
|
|
1260
1689
|
$('s-done').textContent = acc.filter(isDone).length + ' / ' + acc.length;
|
|
1261
1690
|
}
|
|
1262
1691
|
const c = creditSum(acc);
|
|
1263
|
-
$('s-credits-label').textContent = CREDIT_VIEWS.has(prod) ? '有效套餐' : '剩余积分';
|
|
1692
|
+
$('s-credits-label').textContent = CREDIT_VIEWS.has(prod) ? T('有效套餐') : T('剩余积分');
|
|
1264
1693
|
$('s-credits').textContent = CREDIT_VIEWS.has(prod)
|
|
1265
1694
|
? acc.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length + ' / ' + acc.length
|
|
1266
1695
|
: c.loading ? '…' : (c.known ? fmtNum(c.sum) : '-');
|
|
@@ -1270,8 +1699,8 @@ function renderProd() {
|
|
|
1270
1699
|
document.querySelectorAll('#region button').forEach((x) => x.classList.toggle('active', x.dataset.r === state.region));
|
|
1271
1700
|
$('btn-checkin').style.display = canCheckin ? '' : 'none';
|
|
1272
1701
|
if (!$('btn-checkin').disabled) {
|
|
1273
|
-
const hasIntl = acc.some((a) => a.provider === 'workbuddy-intl');
|
|
1274
|
-
$('btn-checkin').innerHTML = I.play + (prod === 'workbuddy' && hasIntl ? '领取 / 活跃全部' : '领取全部 ' + prodLabel(prod));
|
|
1702
|
+
const hasIntl = acc.some((a) => a.provider === 'workbuddy-intl') && wbActOn();
|
|
1703
|
+
$('btn-checkin').innerHTML = I.play + (prod === 'workbuddy' && hasIntl ? T('领取 / 活跃全部') : T('领取全部 ') + prodLabel(prod));
|
|
1275
1704
|
}
|
|
1276
1705
|
// 顶栏产品专属入口:ZCode 设置组、Trae 打开客户端,带状态灯(运行中绿点呼吸)
|
|
1277
1706
|
const dot = (on, pulse) => '<span class="dot' + (on ? ' on' : '') + (on && pulse ? ' pulse' : '') + '"></span>';
|
|
@@ -1279,41 +1708,42 @@ function renderProd() {
|
|
|
1279
1708
|
if (prod === 'zcode') {
|
|
1280
1709
|
const acOn = state.zAutoClaim === true;
|
|
1281
1710
|
const gwOn = state.zGateway === true;
|
|
1282
|
-
$('zt-ac').innerHTML = dot(acOn) + '自动领取';
|
|
1283
|
-
$('zt-ac').title = acOn ? '自动领取运行中——点我调整轮询周期 / 运行时长' : '自动领取已关闭——点我设置轮询周期与运行时长并开启';
|
|
1284
|
-
$('zt-
|
|
1285
|
-
$('zt-
|
|
1286
|
-
$('zt-gw').innerHTML = dot(gwOn) + '接口' + (gwOn && state.zGatewayLan === true ? '·局域网' : '');
|
|
1287
|
-
$('zt-gw').title = '体验包接口:开关、局域网开放与 IP 白名单';
|
|
1711
|
+
$('zt-ac').innerHTML = dot(acOn) + T('自动领取');
|
|
1712
|
+
$('zt-ac').title = acOn ? T('自动领取运行中——点我调整轮询周期 / 运行时长') : T('自动领取已关闭——点我设置轮询周期与运行时长并开启');
|
|
1713
|
+
$('zt-gw').innerHTML = dot(gwOn) + T('接口') + (gwOn && state.zGatewayLan === true ? T('·局域网') : '');
|
|
1714
|
+
$('zt-gw').title = T('体验包接口:开关、局域网开放与 IP 白名单');
|
|
1288
1715
|
const zc = (state.status && state.status.zcodeClient) || {};
|
|
1289
1716
|
const canOpen = zc.installed && window.creditdaddy && typeof window.creditdaddy.openZcodeClient === 'function';
|
|
1290
1717
|
$('zt-open').style.display = canOpen ? '' : 'none';
|
|
1291
1718
|
if (canOpen) {
|
|
1292
|
-
$('zt-open').innerHTML = dot(zc.running === true, true) + '打开客户端';
|
|
1293
|
-
$('zt-open').title = zc.running ? 'ZCode 客户端运行中,点我唤到前台' : 'ZCode 客户端未运行,点我启动';
|
|
1719
|
+
$('zt-open').innerHTML = dot(zc.running === true, true) + T('打开客户端');
|
|
1720
|
+
$('zt-open').title = zc.running ? T('ZCode 客户端运行中,点我唤到前台') : T('ZCode 客户端未运行,点我启动');
|
|
1294
1721
|
}
|
|
1295
1722
|
}
|
|
1296
1723
|
$('t-actions').style.display = prod === 'trae' ? 'contents' : 'none';
|
|
1297
1724
|
if (prod === 'trae') {
|
|
1725
|
+
const gwOn = state.tGateway === true;
|
|
1726
|
+
$('tt-gw').innerHTML = dot(gwOn) + T('接口') + (gwOn && state.tGatewayLan === true ? T('·局域网') : '');
|
|
1727
|
+
$('tt-gw').title = T('Trae 本地接口:开关、局域网开放与 IP 白名单');
|
|
1298
1728
|
const tc = (state.status && state.status.traeClient) || {};
|
|
1299
1729
|
const canOpen = tc.installed && window.creditdaddy && typeof window.creditdaddy.openTraeClient === 'function';
|
|
1300
1730
|
$('tt-open').style.display = canOpen ? '' : 'none';
|
|
1301
1731
|
if (canOpen) {
|
|
1302
|
-
$('tt-open').innerHTML = dot(tc.running === true, true) + '打开客户端';
|
|
1303
|
-
$('tt-open').title = tc.running ? 'Trae 客户端运行中,点我唤到前台' : 'Trae 客户端未运行,点我启动';
|
|
1732
|
+
$('tt-open').innerHTML = dot(tc.running === true, true) + T('打开客户端');
|
|
1733
|
+
$('tt-open').title = tc.running ? T('Trae 客户端运行中,点我唤到前台') : T('Trae 客户端未运行,点我启动');
|
|
1304
1734
|
}
|
|
1305
1735
|
}
|
|
1306
1736
|
$('m-actions').style.display = prod === 'minimax' ? 'contents' : 'none';
|
|
1307
1737
|
if (prod === 'minimax') {
|
|
1308
1738
|
const gwOn = state.mGateway === true;
|
|
1309
|
-
$('mt-gw').innerHTML = dot(gwOn) + '接口' + (gwOn && state.mGatewayLan === true ? '·局域网' : '');
|
|
1310
|
-
$('mt-gw').title = 'MiniMax 本地接口:开关、局域网开放与 IP 白名单';
|
|
1739
|
+
$('mt-gw').innerHTML = dot(gwOn) + T('接口') + (gwOn && state.mGatewayLan === true ? T('·局域网') : '');
|
|
1740
|
+
$('mt-gw').title = T('MiniMax 本地接口:开关、局域网开放与 IP 白名单');
|
|
1311
1741
|
const mc = (state.status && state.status.minimaxClient) || {};
|
|
1312
1742
|
const canOpen = mc.installed && window.creditdaddy && typeof window.creditdaddy.openMiniMaxClient === 'function';
|
|
1313
1743
|
$('mt-open').style.display = canOpen ? '' : 'none';
|
|
1314
1744
|
if (canOpen) {
|
|
1315
|
-
$('mt-open').innerHTML = dot(mc.running === true, true) + '打开客户端';
|
|
1316
|
-
$('mt-open').title = mc.running ? 'MiniMax Code 客户端运行中,点我唤到前台' : 'MiniMax Code 客户端未运行,点我启动';
|
|
1745
|
+
$('mt-open').innerHTML = dot(mc.running === true, true) + T('打开客户端');
|
|
1746
|
+
$('mt-open').title = mc.running ? T('MiniMax Code 客户端运行中,点我唤到前台') : T('MiniMax Code 客户端未运行,点我启动');
|
|
1317
1747
|
}
|
|
1318
1748
|
}
|
|
1319
1749
|
}
|
|
@@ -1334,29 +1764,29 @@ function renderDash() {
|
|
|
1334
1764
|
const byProd = orderedVisibleComps().filter((id) => id !== 'tenrouter').map((id) => prodLabel(id) + ' ' + ofProduct(id).length).join(' · ');
|
|
1335
1765
|
const creditByProd = ['qoder', 'workbuddy', 'minimax'].filter(compVisible).map((id) => { const c = creditSum(ofProduct(id)); return prodLabel(id) + ' ' + (c.known ? fmtNum(c.sum) : '-'); }).join(' · ');
|
|
1336
1766
|
// 今日进度拆「国内领取 / 国际活跃」:国际版 WorkBuddy 是保持活跃(活跃不等于必有奖励)
|
|
1337
|
-
const intlAcc = signable.filter((a) => a.provider === 'workbuddy-intl');
|
|
1767
|
+
const intlAcc = signable.filter((a) => a.provider === 'workbuddy-intl' && wbActOn());
|
|
1338
1768
|
const domAcc = signable.filter((a) => a.provider !== 'workbuddy-intl');
|
|
1339
1769
|
const doneDom = domAcc.filter(isDone).length;
|
|
1340
1770
|
const doneIntl = intlAcc.filter(isDone).length;
|
|
1341
1771
|
$('kpis').innerHTML =
|
|
1342
|
-
kpi('账号', state.accounts.length, byProd)
|
|
1343
|
-
+ kpi('今日领取' + (intlAcc.length ? ' / 活跃' : ''), intlAcc.length
|
|
1344
|
-
? doneDom + ' <small>/ ' + domAcc.length + '</small><small class="kpi-side"> · 活跃
|
|
1772
|
+
kpi(T('账号'), state.accounts.length, byProd)
|
|
1773
|
+
+ kpi(T('今日领取') + (intlAcc.length ? T(' / 活跃') : ''), intlAcc.length
|
|
1774
|
+
? doneDom + ' <small>/ ' + domAcc.length + '</small><small class="kpi-side">' + T(' · 活跃 {n} / {m}', { n: doneIntl, m: intlAcc.length }) + '</small>'
|
|
1345
1775
|
: done + ' <small>/ ' + signable.length + '</small>',
|
|
1346
|
-
!signable.length ? '暂无可领取的账号'
|
|
1347
|
-
: (doneDom === domAcc.length && doneIntl === intlAcc.length ? (intlAcc.length ? '今天全部完成(国际为活跃请求,不保证奖励)' : '今天全部签完')
|
|
1348
|
-
: '待办 ' + (signable.length - done) + (failed ? ' · 失败 ' + failed : '')),
|
|
1349
|
-
'领取 = 国内版每日签到;活跃 = 国际版定期活跃请求(保持资格,不保证奖励)')
|
|
1350
|
-
+ kpi('剩余积分', credit.loading ? '…' : (credit.known ? fmtNum(credit.sum) : '-'), credit.loading ? '正在读取各账号额度…' : creditByProd)
|
|
1351
|
-
+ kpi('下次自动领取', sch.ticking ? '领取中…' : sch.nextTickAt ? fmtTime(sch.nextTickAt).slice(11) : '-',
|
|
1352
|
-
sch.lastTick ? '上一轮 ' + fmtTime(sch.lastTick.at).slice(5) + ' · ' + esc(sch.lastTick.summary || '') : '每 ~2 小时一轮');
|
|
1776
|
+
!signable.length ? T('暂无可领取的账号')
|
|
1777
|
+
: (doneDom === domAcc.length && doneIntl === intlAcc.length ? (intlAcc.length ? T('今天全部完成(国际为活跃请求,不保证奖励)') : T('今天全部签完'))
|
|
1778
|
+
: T('待办 ') + (signable.length - done) + (failed ? T(' · 失败 ') + failed : '')),
|
|
1779
|
+
T('领取 = 国内版每日签到;活跃 = 国际版定期活跃请求(保持资格,不保证奖励)'))
|
|
1780
|
+
+ kpi(T('剩余积分'), credit.loading ? '…' : (credit.known ? fmtNum(credit.sum) : '-'), credit.loading ? T('正在读取各账号额度…') : creditByProd)
|
|
1781
|
+
+ kpi(T('下次自动领取'), sch.ticking ? T('领取中…') : sch.nextTickAt ? fmtTime(sch.nextTickAt).slice(11) : '-',
|
|
1782
|
+
sch.lastTick ? T('上一轮 ') + fmtTime(sch.lastTick.at).slice(5) + ' · ' + esc(sch.lastTick.summary || '') : T('每 ~2 小时一轮'));
|
|
1353
1783
|
|
|
1354
1784
|
// 仪表盘产品卡按组件设置里的顺序渲染(与标签栏同步),10Router 汇总卡按其顺序插入
|
|
1355
1785
|
const prodCardHtml = (p) => {
|
|
1356
1786
|
const list = ofProduct(p.id);
|
|
1357
1787
|
let rows = '';
|
|
1358
1788
|
if (!list.length) {
|
|
1359
|
-
rows = '<span class="lbl"
|
|
1789
|
+
rows = '<span class="lbl">' + T('账号') + '</span><span class="val dim">' + T('还没有,点击进入添加') + '</span>';
|
|
1360
1790
|
} else if (CHECKIN_PRODUCTS.has(p.id)) {
|
|
1361
1791
|
const d = list.filter(isDone).length;
|
|
1362
1792
|
const c = creditSum(list);
|
|
@@ -1365,57 +1795,57 @@ function renderDash() {
|
|
|
1365
1795
|
const cnList = list.filter((a) => a.provider === 'workbuddy');
|
|
1366
1796
|
const intlList = list.filter((a) => a.provider === 'workbuddy-intl');
|
|
1367
1797
|
const parts = [];
|
|
1368
|
-
if (cnList.length) parts.push(cnList.filter(isDone).length + ' / ' + cnList.length + '(国内)');
|
|
1369
|
-
if (intlList.length) parts.push(intlList.filter(isDone).length + ' / ' + intlList.length + '(国际 · 不保证奖励)');
|
|
1370
|
-
rows += '<span class="lbl">领取 / 活跃</span><span class="val" title="' + esc(parts.join(',') + (intlList.length ? '(国际为活跃请求,不保证奖励)' : '')) + '">'
|
|
1371
|
-
+ (cnList.length ? cnList.filter(isDone).length + ' / ' + cnList.length + ' 国内' : '')
|
|
1798
|
+
if (cnList.length) parts.push(cnList.filter(isDone).length + ' / ' + cnList.length + T('(国内)'));
|
|
1799
|
+
if (intlList.length) parts.push(intlList.filter(isDone).length + ' / ' + intlList.length + T('(国际 · 不保证奖励)'));
|
|
1800
|
+
rows += T('<span class="lbl">领取 / 活跃</span><span class="val" title="') + esc(parts.join(T(',')) + (intlList.length ? T('(国际为活跃请求,不保证奖励)') : '')) + '">'
|
|
1801
|
+
+ (cnList.length ? cnList.filter(isDone).length + ' / ' + cnList.length + T(' 国内') : '')
|
|
1372
1802
|
+ (cnList.length && intlList.length ? ' · ' : '')
|
|
1373
|
-
+ (intlList.length ? intlList.filter(isDone).length + ' / ' + intlList.length + ' 国际' : '')
|
|
1803
|
+
+ (intlList.length ? intlList.filter(isDone).length + ' / ' + intlList.length + T(' 国际') : '')
|
|
1374
1804
|
+ '</span>';
|
|
1375
1805
|
} else if (p.id === 'minimax') {
|
|
1376
1806
|
const planned = list.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length;
|
|
1377
|
-
rows = '<span class="lbl">今日领取</span><span class="val">' + d + ' / ' + list.length + '</span>'
|
|
1807
|
+
rows = T('<span class="lbl">今日领取</span><span class="val">') + d + ' / ' + list.length + '</span>'
|
|
1378
1808
|
+ '<span style="grid-column:1 / -1">' + meter(d, list.length) + '</span>'
|
|
1379
|
-
+ '<span class="lbl">剩余积分</span><span class="val">' + (c.known ? fmtNum(c.sum) : '-') + '</span>';
|
|
1809
|
+
+ T('<span class="lbl">剩余积分</span><span class="val">') + (c.known ? fmtNum(c.sum) : '-') + '</span>';
|
|
1380
1810
|
} else {
|
|
1381
|
-
rows = '<span class="lbl">今日领取</span><span class="val">' + d + ' / ' + list.length + '</span>';
|
|
1811
|
+
rows = T('<span class="lbl">今日领取</span><span class="val">') + d + ' / ' + list.length + '</span>';
|
|
1382
1812
|
}
|
|
1383
1813
|
if (p.id !== 'minimax') {
|
|
1384
1814
|
rows += '<span style="grid-column:1 / -1">' + meter(d, list.length) + '</span>'
|
|
1385
|
-
+ '<span class="lbl">剩余积分</span><span class="val">' + (c.known ? fmtNum(c.sum) : '-') + '</span>';
|
|
1815
|
+
+ T('<span class="lbl">剩余积分</span><span class="val">') + (c.known ? fmtNum(c.sum) : '-') + '</span>';
|
|
1386
1816
|
}
|
|
1387
1817
|
} else if (p.id === 'mirasim') {
|
|
1388
1818
|
const planned = list.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length;
|
|
1389
|
-
rows = '<span class="lbl">额度体系</span><span class="val dim">5h / 7d 滚动窗口</span>'
|
|
1390
|
-
+ '<span class="lbl">有效套餐</span><span class="val">' + planned + ' / ' + list.length + '</span>';
|
|
1819
|
+
rows = T('<span class="lbl">额度体系</span><span class="val dim">5h / 7d 滚动窗口</span>')
|
|
1820
|
+
+ T('<span class="lbl">有效套餐</span><span class="val">') + planned + ' / ' + list.length + '</span>';
|
|
1391
1821
|
} else if (p.id === 'catpaw') {
|
|
1392
1822
|
const withPlan = list.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && q.plan; });
|
|
1393
1823
|
const credits = withPlan.reduce((s, a) => s + (Number(state.quotas[a.id].remaining) || 0), 0);
|
|
1394
|
-
rows = '<span class="lbl">额度体系</span><span class="val dim">Credits 余额 + 套餐版本</span>'
|
|
1395
|
-
+ (withPlan.length ? '<span class="lbl">可用额度</span><span class="val">' + fmtNum(credits) + ' Credits(' + esc(withPlan.map((a) => state.quotas[a.id].plan).join(' / ')) + ')</span>' : '');
|
|
1824
|
+
rows = T('<span class="lbl">额度体系</span><span class="val dim">Credits 余额 + 套餐版本</span>')
|
|
1825
|
+
+ (withPlan.length ? T('<span class="lbl">可用额度</span><span class="val">') + fmtNum(credits) + T(' Credits(') + esc(withPlan.map((a) => state.quotas[a.id].plan).join(' / ')) + T(')</span>') : '');
|
|
1396
1826
|
} else {
|
|
1397
1827
|
const planned = list.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length;
|
|
1398
1828
|
// ZCode 没有每日签到:不展示签到语义的行
|
|
1399
|
-
rows = '<span class="lbl">活动领取</span><span class="val dim">手动领取,或在标签页开自动轮询</span>'
|
|
1400
|
-
+ '<span class="lbl">有效套餐</span><span class="val">' + planned + ' / ' + list.length + '</span>';
|
|
1829
|
+
rows = T('<span class="lbl">活动领取</span><span class="val dim">手动领取,或在标签页开自动轮询</span>')
|
|
1830
|
+
+ T('<span class="lbl">有效套餐</span><span class="val">') + planned + ' / ' + list.length + '</span>';
|
|
1401
1831
|
}
|
|
1402
1832
|
if (p.id === 'qoder' && list.some((a) => a.provider === 'qoder')) {
|
|
1403
|
-
rows += '<span class="lbl">国际版风控身份</span><span class="val' + (s.riskIdentity ? '' : ' warn') + '">' + (s.riskIdentity ? (s.riskSource === 'cli' ? '已就绪(设备身份组件)' : '已就绪') : umidOffer(s) ? '需安装设备身份组件' : '未检测到客户端') + '</span>';
|
|
1833
|
+
rows += T('<span class="lbl">国际版风控身份</span><span class="val') + (s.riskIdentity ? '' : ' warn') + '">' + (s.riskIdentity ? (s.riskSource === 'cli' ? T('已就绪(设备身份组件)') : T('已就绪')) : umidOffer(s) ? T('需安装设备身份组件') : T('未检测到客户端')) + '</span>';
|
|
1404
1834
|
}
|
|
1405
1835
|
if (CLIENT_LABEL[p.id]) {
|
|
1406
1836
|
const cur = list.find((a) => a.product === p.id && isClientCurrent(a));
|
|
1407
1837
|
const zid = p.id === 'zcode' ? s.zcodeCurrentIdentity : null;
|
|
1408
1838
|
const live = cur ? esc(cur.name || cur.email || cur.uid)
|
|
1409
|
-
: zid && (zid.email || zid.uid || zid.username) ? '未导入的账号(' + esc(zid.email || zid.username || zid.uid) + ')'
|
|
1410
|
-
: p.id === 'mirasim' && s.mirasimCurrentUid ? '未导入的账号'
|
|
1411
|
-
: p.id === 'catpaw' && s.catpawClient && s.catpawClient.signedIn && !list.some((a) => isClientCurrent(a)) ? '未导入的账号'
|
|
1412
|
-
: p.id === 'trae' && s.traeClient && s.traeClient.signedIn && !list.some((a) => isClientCurrent(a)) ? '未导入的账号'
|
|
1413
|
-
: p.id === 'minimax' && s.minimaxClient && s.minimaxClient.signedIn && !list.some((a) => isClientCurrent(a)) ? '未导入的账号'
|
|
1414
|
-
: (p.id === 'workbuddy' && s.workbuddyCurrentUid ? '未导入的账号' : '—');
|
|
1415
|
-
rows += '<span class="lbl">客户端当前</span><span class="val">' + live + '</span>';
|
|
1839
|
+
: zid && (zid.email || zid.uid || zid.username) ? T('未导入的账号(') + esc(zid.email || zid.username || zid.uid) + T(')')
|
|
1840
|
+
: p.id === 'mirasim' && s.mirasimCurrentUid ? T('未导入的账号')
|
|
1841
|
+
: p.id === 'catpaw' && s.catpawClient && s.catpawClient.signedIn && !list.some((a) => isClientCurrent(a)) ? T('未导入的账号')
|
|
1842
|
+
: p.id === 'trae' && s.traeClient && s.traeClient.signedIn && !list.some((a) => isClientCurrent(a)) ? T('未导入的账号')
|
|
1843
|
+
: p.id === 'minimax' && s.minimaxClient && s.minimaxClient.signedIn && !list.some((a) => isClientCurrent(a)) ? T('未导入的账号')
|
|
1844
|
+
: (p.id === 'workbuddy' && s.workbuddyCurrentUid ? T('未导入的账号') : '—');
|
|
1845
|
+
rows += T('<span class="lbl">客户端当前</span><span class="val">') + live + '</span>';
|
|
1416
1846
|
}
|
|
1417
1847
|
return '<div class="prodcard" data-view="' + p.id + '"><div class="prod-head"><span class="prod-name"><span class="dot ' + p.id + '"></span>' + p.label
|
|
1418
|
-
+ '<span class="badge">' + list.length + ' 个账号</span></span><span class="prod-go">查看' + I.chevRight + '</span></div><div class="prod-rows">' + rows + '</div></div>';
|
|
1848
|
+
+ '<span class="badge">' + list.length + T(' 个账号</span></span><span class="prod-go">查看') + I.chevRight + '</span></div><div class="prod-rows">' + rows + '</div></div>';
|
|
1419
1849
|
};
|
|
1420
1850
|
$('prods').innerHTML = orderedVisibleComps().map((id) => {
|
|
1421
1851
|
if (id === 'tenrouter') return trDashCard();
|
|
@@ -1426,101 +1856,112 @@ function renderDash() {
|
|
|
1426
1856
|
// 需要处理:token 失效 / 即将过期、领取失败、国际版缺风控身份、10Router 额度告急 / 查询失败
|
|
1427
1857
|
const att = [];
|
|
1428
1858
|
if (!s.riskIdentity && state.accounts.some((a) => a.provider === 'qoder')) {
|
|
1429
|
-
att.push({ view: 'qoder', name: 'Qoder 国际版', badges: '', pill: 'limited', text: umidOffer(s) ? '安装设备身份组件后才能领取' : '未检测到 Qoder 客户端,国际版无法领取' });
|
|
1859
|
+
att.push({ view: 'qoder', name: T('Qoder 国际版'), badges: '', pill: 'limited', text: umidOffer(s) ? T('安装设备身份组件后才能领取') : T('未检测到 Qoder 客户端,国际版无法领取') });
|
|
1430
1860
|
}
|
|
1431
1861
|
for (const a of state.accounts) {
|
|
1432
1862
|
const ex = expiryCell(a);
|
|
1433
1863
|
const st = statusOf(a);
|
|
1434
1864
|
const base = { view: a.product, name: a.name || a.email || a.id, badges: provBadges(a.provider) };
|
|
1435
|
-
if (a.verified === false) att.push(Object.assign({ pill: 'fail', text: 'token 无效' }, base));
|
|
1436
|
-
else if (ex.label === '已过期') att.push(Object.assign({ pill: 'fail', text: 'token 已过期' }, base));
|
|
1437
|
-
else if (ex.warn) att.push(Object.assign({ pill: 'limited', text: ex.label.slice(5, 10) + ' 过期' }, base));
|
|
1438
|
-
if (st.cls === 'fail') att.push(Object.assign({ pill: 'fail', text: a.provider === 'workbuddy-intl' ? '活跃失败' : '领取失败', title: st.msg }, base));
|
|
1865
|
+
if (a.verified === false) att.push(Object.assign({ pill: 'fail', text: T('token 无效') }, base));
|
|
1866
|
+
else if (ex.label === '已过期') att.push(Object.assign({ pill: 'fail', text: T('token 已过期') }, base));
|
|
1867
|
+
else if (ex.warn) att.push(Object.assign({ pill: 'limited', text: ex.label.slice(5, 10) + T(' 过期') }, base));
|
|
1868
|
+
if (st.cls === 'fail') att.push(Object.assign({ pill: 'fail', text: a.provider === 'workbuddy-intl' ? T('活跃失败') : T('领取失败'), title: st.msg }, base));
|
|
1439
1869
|
}
|
|
1440
1870
|
for (const c of (tr.quotas && tr.quotas.connections) || []) {
|
|
1441
1871
|
if (!compVisible('tenrouter')) break;
|
|
1442
1872
|
const base = { view: 'tenrouter', name: c.name || c.email || c.providerName, badges: '<span class="badge prod tenrouter">' + esc(c.providerName) + '</span>' };
|
|
1443
|
-
if (c.error) att.push(Object.assign({ pill: 'fail', text: '额度查询失败', title: c.error }, base));
|
|
1444
|
-
else if (c.isActive && quotaLow(c)) att.push(Object.assign({ pill: 'limited', text: '额度告急' }, base));
|
|
1873
|
+
if (c.error) att.push(Object.assign({ pill: 'fail', text: T('额度查询失败'), title: c.error }, base));
|
|
1874
|
+
else if (c.isActive && quotaLow(c)) att.push(Object.assign({ pill: 'limited', text: T('额度告急') }, base));
|
|
1445
1875
|
}
|
|
1446
1876
|
// 隐藏掉的组件不进「需要处理」(如收起 10Router 后不再展示其额度告急)
|
|
1447
1877
|
const attVisible = att.filter((x) => compVisible(x.view));
|
|
1448
|
-
$('att-count').textContent = attVisible.length ? attVisible.length + ' 项' : '';
|
|
1878
|
+
$('att-count').textContent = attVisible.length ? attVisible.length + T(' 项') : '';
|
|
1449
1879
|
$('attention').innerHTML = attVisible.length
|
|
1450
1880
|
? attVisible.slice(0, 8).map((x) => '<div class="item" data-view="' + x.view + '" title="' + esc(x.title || '') + '"><span class="item-main"><span class="item-name">' + esc(x.name) + '</span>' + x.badges + '</span>'
|
|
1451
1881
|
+ '<span class="item-side"><span class="pill ' + x.pill + '">' + esc(x.text) + '</span></span></div>').join('')
|
|
1452
|
-
+ (attVisible.length > 8 ? '<div class="muted-empty">还有 ' + (attVisible.length - 8) + ' 项</div>' : '')
|
|
1453
|
-
: '<div class="muted-empty">' + (state.accounts.length ? '<span class="ok-glyph">' + I.check + '</span>一切正常' : '还没有账号') + '</div>';
|
|
1882
|
+
+ (attVisible.length > 8 ? T('<div class="muted-empty">还有 ') + (attVisible.length - 8) + T(' 项</div>') : '')
|
|
1883
|
+
: '<div class="muted-empty">' + (state.accounts.length ? '<span class="ok-glyph">' + I.check + T('</span>一切正常') : T('还没有账号')) + '</div>';
|
|
1454
1884
|
|
|
1455
1885
|
// 最近领取记录
|
|
1456
|
-
const RES = { 'checked-in': ['ok', '领取成功'], already: ['ok', '已领过'], limited: ['limited', '本机已领'], 'no-activity': ['pending', '无可领活动'], failed: ['fail', '失败'] };
|
|
1886
|
+
const RES = { 'checked-in': ['ok', T('领取成功')], already: ['ok', T('已领过')], limited: ['limited', T('本机已领')], 'no-activity': ['pending', T('无可领活动')], failed: ['fail', T('失败')] };
|
|
1457
1887
|
const recent = state.accounts.filter((a) => a.lastResult && a.lastResult.at && compVisible(a.product))
|
|
1458
1888
|
.sort((x, y) => String(y.lastResult.at).localeCompare(String(x.lastResult.at))).slice(0, 8);
|
|
1459
|
-
$('last-tick').textContent = sch.lastTick ? '上一轮 ' + fmtTime(sch.lastTick.at).slice(5) : '';
|
|
1889
|
+
$('last-tick').textContent = sch.lastTick ? T('上一轮 ') + fmtTime(sch.lastTick.at).slice(5) : '';
|
|
1460
1890
|
$('recent').innerHTML = recent.length
|
|
1461
1891
|
? recent.map((a) => {
|
|
1462
1892
|
const r = a.lastResult;
|
|
1463
1893
|
let [cls, txt] = RES[r.status] || ['pending', r.status];
|
|
1464
1894
|
// 国际版 WorkBuddy 是保持活跃(不保证奖励),不叫「领取」
|
|
1465
|
-
if (a.provider === 'workbuddy-intl' && r.status === 'checked-in') txt = '活跃达成';
|
|
1895
|
+
if (a.provider === 'workbuddy-intl' && r.status === 'checked-in') txt = T('活跃达成');
|
|
1466
1896
|
const label = r.status === 'checked-in' && r.amount ? txt + ' +' + fmtNum(r.amount) : txt;
|
|
1467
1897
|
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>'
|
|
1468
1898
|
+ '<span class="item-side"><span class="pill ' + cls + '">' + esc(label) + '</span><span class="item-time">' + fmtTime(r.at).slice(5) + '</span></span></div>';
|
|
1469
1899
|
}).join('')
|
|
1470
|
-
: '<div class="muted-empty"
|
|
1900
|
+
: '<div class="muted-empty">' + T('还没有领取记录') + '</div>';
|
|
1471
1901
|
}
|
|
1472
1902
|
|
|
1473
1903
|
function renderStatus() {
|
|
1474
1904
|
const s = state.status;
|
|
1475
1905
|
if (!s) return;
|
|
1476
|
-
$('ver').textContent = 'v' + s.version + ' · 数据目录 ' + s.dataDir;
|
|
1906
|
+
$('ver').textContent = 'v' + s.version + T(' · 数据目录 ') + s.dataDir;
|
|
1477
1907
|
document.title = 'CreditDaddy v' + s.version;
|
|
1478
|
-
if (s.homepage) $('
|
|
1908
|
+
if (s.homepage) $('hm-home').href = s.homepage;
|
|
1909
|
+
// 菜单脚注版本:面板版本 + 数据目录(与品牌区同一个口径,换行折行由 CSS 收敛)
|
|
1910
|
+
$('hmenu-version').textContent = 'CreditDaddy v' + s.version;
|
|
1911
|
+
$('hmenu-version').title = s.dataDir ? T('数据目录 ') + s.dataDir : '';
|
|
1912
|
+
// 状态胶囊:领取中显示强调色文字,空闲显示「下次自动领取 HH:MM」(时间用 <b> 提权重)
|
|
1479
1913
|
const sch = s.scheduler || {};
|
|
1480
|
-
$('sched')
|
|
1481
|
-
|
|
1482
|
-
|
|
1914
|
+
const chip = $('sched');
|
|
1915
|
+
chip.classList.toggle('busy', Boolean(sch.ticking));
|
|
1916
|
+
if (sch.ticking) {
|
|
1917
|
+
chip.innerHTML = I.refresh + T('正在领取…');
|
|
1918
|
+
} else if (sch.nextTickAt) {
|
|
1919
|
+
chip.innerHTML = I.clock + T('下次自动领取 <b class="num">') + fmtTime(sch.nextTickAt).slice(11) + '</b>';
|
|
1920
|
+
} else {
|
|
1921
|
+
chip.innerHTML = '';
|
|
1922
|
+
}
|
|
1923
|
+
chip.title = sch.lastTick ? T('上一轮 ') + fmtTime(sch.lastTick.at) + T(':') + sch.lastTick.summary : '';
|
|
1483
1924
|
// 产品相关提示只在对应标签页出现
|
|
1484
1925
|
const hasIntl = state.view === 'qoder' && state.region !== 'cn' && state.accounts.some((a) => a.provider === 'qoder');
|
|
1485
1926
|
const n = $('notice');
|
|
1486
1927
|
n.classList.toggle('info', state.view === 'zcode' || state.view === 'mirasim' || state.view === 'catpaw' || state.view === 'trae' || state.view === 'minimax' || (hasIntl && Boolean(s.riskIdentity)));
|
|
1487
1928
|
if (state.view === 'minimax') {
|
|
1488
1929
|
n.style.display = '';
|
|
1489
|
-
n.innerHTML = '<div class="z-row"><span class="z-label">每日签到</span><span class="z-text">每日签到领 800~2000 积分 + 400 连签加成。支持自动检测本机 MiniMax Code 凭据与 Bearer Token 导入,自带标准 Anthropic 本地网关。</span></div>';
|
|
1930
|
+
n.innerHTML = T('<div class="z-row"><span class="z-label">每日签到</span><span class="z-text">每日签到领 800~2000 积分 + 400 连签加成。支持自动检测本机 MiniMax Code 凭据与 Bearer Token 导入,自带标准 Anthropic 本地网关。</span></div>');
|
|
1490
1931
|
} else if (state.view === 'trae') {
|
|
1491
1932
|
n.style.display = '';
|
|
1492
|
-
n.innerHTML = '<div class="z-row"><span class="z-label">每日签到</span><span class="z-text">每日签到领约 150 通用积分(7 天有效,多资源包自动归并)。凭据由本机 Trae 客户端解密导入,支持多账号快照一键切换。</span></div>';
|
|
1933
|
+
n.innerHTML = T('<div class="z-row"><span class="z-label">每日签到</span><span class="z-text">每日签到领约 150 通用积分(7 天有效,多资源包自动归并)。凭据由本机 Trae 客户端解密导入,支持多账号快照一键切换。</span></div>');
|
|
1493
1934
|
} else if (state.view === 'catpaw') {
|
|
1494
1935
|
n.style.display = '';
|
|
1495
|
-
n.innerHTML = '妙手(美团 CatPaw)按套餐发放 Credits(体验版 / 专业版…),没有每日签到。凭据由妙手客户端登录管理:读取额度用本机解密出的登录 token 直连桌面网关,token 失效时到妙手客户端重新登录后再「本机导入」即可。'
|
|
1496
|
-
+ '多账号切换:写入本机凭据后重启妙手客户端即生效(切换会自动保存当前登录,不丢号)。'
|
|
1936
|
+
n.innerHTML = T('妙手(美团 CatPaw)按套餐发放 Credits(体验版 / 专业版…),没有每日签到。凭据由妙手客户端登录管理:读取额度用本机解密出的登录 token 直连桌面网关,token 失效时到妙手客户端重新登录后再「本机导入」即可。')
|
|
1937
|
+
+ T('多账号切换:写入本机凭据后重启妙手客户端即生效(切换会自动保存当前登录,不丢号)。')
|
|
1497
1938
|
+ catpawClientChip(s);
|
|
1498
1939
|
} else if (state.view === 'mirasim') {
|
|
1499
1940
|
n.style.display = '';
|
|
1500
|
-
n.innerHTML = 'mirasim(原生 AI 编程开发环境)额度按「平台额度」(5h 滚动窗口、7d 全局上限与分模型上限)分配。Token 约 1 小时过期,读取额度时会自动用 refreshToken 静默续期并同步回客户端,无需手动处理。'
|
|
1501
|
-
+ '切换账号需要至少两个已导入的账号(当前登录 + 目标账号):先在「添加账号 → 本机导入」把各账号分别导入,切换时当前登录会自动保存、目标账号写入客户端,桌面版切完自动重启 mirasim。'
|
|
1941
|
+
n.innerHTML = T('mirasim(原生 AI 编程开发环境)额度按「平台额度」(5h 滚动窗口、7d 全局上限与分模型上限)分配。Token 约 1 小时过期,读取额度时会自动用 refreshToken 静默续期并同步回客户端,无需手动处理。')
|
|
1942
|
+
+ T('切换账号需要至少两个已导入的账号(当前登录 + 目标账号):先在「添加账号 → 本机导入」把各账号分别导入,切换时当前登录会自动保存、目标账号写入客户端,桌面版切完自动重启 mirasim。')
|
|
1502
1943
|
+ mirasimClientChip(s);
|
|
1503
1944
|
} else if (state.view === 'zcode') {
|
|
1504
1945
|
n.style.display = '';
|
|
1505
1946
|
const ac = state.zAutoClaim === true;
|
|
1506
1947
|
const ziv = state.zClaimIntervalMin || 2;
|
|
1507
1948
|
const zwin = state.zClaimWindowMin === 0 ? 0 : (state.zClaimWindowMin || 60);
|
|
1508
|
-
n.innerHTML = '<div class="z-row"><span class="z-label">活动领取</span><span class="z-text">活动包积分,每日 0 点左右刷新(无每日签到)。自动领取'
|
|
1509
|
-
+ (ac ? '每 ' + ziv + ' 分钟检查资格' + (zwin > 0 ? ',本次运行 ' + zwin + ' 分钟' : ',持续运行') : '未开启(右上角设置轮询计划)')
|
|
1510
|
-
+ ';礼物图标 = 手动临时领。</span></div>';
|
|
1949
|
+
n.innerHTML = T('<div class="z-row"><span class="z-label">活动领取</span><span class="z-text">活动包积分,每日 0 点左右刷新(无每日签到)。自动领取')
|
|
1950
|
+
+ (ac ? T('每 ') + ziv + T(' 分钟检查资格') + (zwin > 0 ? T(',本次运行 ') + zwin + T(' 分钟') : T(',持续运行')) : T('未开启(右上角设置轮询计划)'))
|
|
1951
|
+
+ T(';礼物图标 = 手动临时领。</span></div>');
|
|
1511
1952
|
} else if (hasIntl && !s.riskIdentity && umidOffer(s)) {
|
|
1512
1953
|
n.style.display = '';
|
|
1513
1954
|
const busy = s.umid.installing || umidBusy;
|
|
1514
|
-
n.innerHTML = 'Qoder 国际版只向带设备风控身份的请求下发「每天领 100 Credits」。这台 Linux / NAS 上没有 Qoder 客户端,'
|
|
1515
|
-
+ '可以安装「设备身份组件」:从 npm 官方包 @qoder-ai/qodercli 提取其内置的 UMID 程序(约 30MB 下载,校验 npm 完整性),装好后国际版即可领取(本机每天限领一个账号)。'
|
|
1516
|
-
+ ' <button class="btn primary" id="btn-umid" style="margin-left:6px"' + (busy ? ' disabled' : '') + '>' + (busy ? '安装中…' : '安装设备身份组件') + '</button>';
|
|
1955
|
+
n.innerHTML = T('Qoder 国际版只向带设备风控身份的请求下发「每天领 100 Credits」。这台 Linux / NAS 上没有 Qoder 客户端,')
|
|
1956
|
+
+ T('可以安装「设备身份组件」:从 npm 官方包 @qoder-ai/qodercli 提取其内置的 UMID 程序(约 30MB 下载,校验 npm 完整性),装好后国际版即可领取(本机每天限领一个账号)。')
|
|
1957
|
+
+ ' <button class="btn primary" id="btn-umid" style="margin-left:6px"' + (busy ? ' disabled' : '') + '>' + (busy ? T('安装中…') : T('安装设备身份组件')) + '</button>';
|
|
1517
1958
|
} else if (hasIntl && !s.riskIdentity) {
|
|
1518
1959
|
n.style.display = '';
|
|
1519
|
-
n.textContent = 'Qoder 国际版领取需要本机安装 Qoder 客户端:Qoder 只向带设备风控身份(由客户端生成)的请求下发「每天领 100 Credits」。当前未检测到客户端,国际版账号将无法领取。';
|
|
1960
|
+
n.textContent = T('Qoder 国际版领取需要本机安装 Qoder 客户端:Qoder 只向带设备风控身份(由客户端生成)的请求下发「每天领 100 Credits」。当前未检测到客户端,国际版账号将无法领取。');
|
|
1520
1961
|
} else if (hasIntl) {
|
|
1521
1962
|
n.style.display = '';
|
|
1522
|
-
n.textContent = '提示:Qoder 国际版每台设备每天只能有一个账号领取每日 100 Credits(服务端按设备限领),其余国际版账号会显示「本机已领」。'
|
|
1523
|
-
+ (s.riskSource === 'cli' && s.umid && s.umid.installed ? '(风控身份来自设备身份组件 qodercli ' + s.umid.installed.version + ')' : '');
|
|
1963
|
+
n.textContent = T('提示:Qoder 国际版每台设备每天只能有一个账号领取每日 100 Credits(服务端按设备限领),其余国际版账号会显示「本机已领」。')
|
|
1964
|
+
+ (s.riskSource === 'cli' && s.umid && s.umid.installed ? T('(风控身份来自设备身份组件 qodercli ') + s.umid.installed.version + T(')') : '');
|
|
1524
1965
|
} else {
|
|
1525
1966
|
n.style.display = 'none';
|
|
1526
1967
|
}
|
|
@@ -1529,20 +1970,20 @@ function renderStatus() {
|
|
|
1529
1970
|
// mirasim 客户端状态芯片 + 「打开客户端」按钮
|
|
1530
1971
|
function mirasimClientChip(s) {
|
|
1531
1972
|
const c = s.mirasimClient || {};
|
|
1532
|
-
if (!c.installed) return ' <span class="dim">客户端:未安装</span>';
|
|
1973
|
+
if (!c.installed) return T(' <span class="dim">客户端:未安装</span>');
|
|
1533
1974
|
const canOpen = window.creditdaddy && typeof window.creditdaddy.openMirasimClient === 'function';
|
|
1534
|
-
return ' <span class="dim">客户端:' + (c.running ? '运行中' : '未运行') + '</span>'
|
|
1535
|
-
+ (canOpen ? ' <button class="btn" id="btn-miraopen" style="margin-left:4px" title="打开本机 Mirasim 客户端">打开客户端</button>' : '');
|
|
1975
|
+
return T(' <span class="dim">客户端:') + (c.running ? T('运行中') : T('未运行')) + '</span>'
|
|
1976
|
+
+ (canOpen ? T(' <button class="btn" id="btn-miraopen" style="margin-left:4px" title="打开本机 Mirasim 客户端">打开客户端</button>') : '');
|
|
1536
1977
|
}
|
|
1537
1978
|
|
|
1538
1979
|
|
|
1539
1980
|
// 妙手客户端状态芯片 + 「打开客户端」按钮
|
|
1540
1981
|
function catpawClientChip(s) {
|
|
1541
1982
|
const c = s.catpawClient || {};
|
|
1542
|
-
if (!c.installed) return ' <span class="dim">客户端:未安装(本机导入 / 切换需要 Windows 妙手客户端)</span>';
|
|
1983
|
+
if (!c.installed) return T(' <span class="dim">客户端:未安装(本机导入 / 切换需要 Windows 妙手客户端)</span>');
|
|
1543
1984
|
const canOpen = window.creditdaddy && typeof window.creditdaddy.openCatpawClient === 'function';
|
|
1544
|
-
return ' <span class="dim">客户端:' + (c.running ? '运行中' : '未运行') + (c.signedIn ? '' : ' · 未登录') + '</span>'
|
|
1545
|
-
+ (canOpen ? ' <button class="btn" id="btn-cpopen" style="margin-left:4px" title="打开本机妙手客户端">打开客户端</button>' : '');
|
|
1985
|
+
return T(' <span class="dim">客户端:') + (c.running ? T('运行中') : T('未运行')) + (c.signedIn ? '' : T(' · 未登录')) + '</span>'
|
|
1986
|
+
+ (canOpen ? T(' <button class="btn" id="btn-cpopen" style="margin-left:4px" title="打开本机妙手客户端">打开客户端</button>') : '');
|
|
1546
1987
|
}
|
|
1547
1988
|
|
|
1548
1989
|
// Qoder 设备身份组件(Linux / fnOS)
|
|
@@ -1552,7 +1993,7 @@ async function installUmid() {
|
|
|
1552
1993
|
umidBusy = true; renderStatus();
|
|
1553
1994
|
try {
|
|
1554
1995
|
const r = await api('/api/qoder/umid/install', { method: 'POST' });
|
|
1555
|
-
toast('设备身份组件已安装(qodercli ' + r.installed.version + '),Qoder 国际版可以领取了');
|
|
1996
|
+
toast(T('设备身份组件已安装(qodercli ') + r.installed.version + T('),Qoder 国际版可以领取了'));
|
|
1556
1997
|
} catch (e) { toast(e.message, false); }
|
|
1557
1998
|
umidBusy = false;
|
|
1558
1999
|
await loadStatus(); render(); loadLogs();
|
|
@@ -1563,40 +2004,55 @@ document.addEventListener('click', async (e) => {
|
|
|
1563
2004
|
if (e.target.closest('#zt-open')) {
|
|
1564
2005
|
try {
|
|
1565
2006
|
const r = await window.creditdaddy.openZcodeClient();
|
|
1566
|
-
if (r && r.ok) toast('已启动 ZCode 客户端');
|
|
1567
|
-
else toast((r && r.error) || '无法打开 ZCode 客户端', false);
|
|
2007
|
+
if (r && r.ok) toast(T('已启动 ZCode 客户端'));
|
|
2008
|
+
else toast((r && r.error) || T('无法打开 ZCode 客户端'), false);
|
|
1568
2009
|
} catch (err) { toast(String(err && err.message || err), false); }
|
|
1569
2010
|
return;
|
|
1570
2011
|
}
|
|
1571
2012
|
if (e.target.closest('#btn-miraopen')) {
|
|
1572
2013
|
try {
|
|
1573
2014
|
const r = await window.creditdaddy.openMirasimClient();
|
|
1574
|
-
if (r && r.ok) toast('已启动 Mirasim 客户端');
|
|
1575
|
-
else toast((r && r.error) || '无法打开 Mirasim 客户端', false);
|
|
2015
|
+
if (r && r.ok) toast(T('已启动 Mirasim 客户端'));
|
|
2016
|
+
else toast((r && r.error) || T('无法打开 Mirasim 客户端'), false);
|
|
1576
2017
|
} catch (err) { toast(String(err && err.message || err), false); }
|
|
1577
2018
|
return;
|
|
1578
2019
|
}
|
|
1579
2020
|
if (e.target.closest('#btn-cpopen')) {
|
|
1580
2021
|
try {
|
|
1581
2022
|
const r = await window.creditdaddy.openCatpawClient();
|
|
1582
|
-
if (r && r.ok) toast('已启动妙手客户端');
|
|
1583
|
-
else toast((r && r.error) || '无法打开妙手客户端', false);
|
|
2023
|
+
if (r && r.ok) toast(T('已启动妙手客户端'));
|
|
2024
|
+
else toast((r && r.error) || T('无法打开妙手客户端'), false);
|
|
1584
2025
|
} catch (err) { toast(String(err && err.message || err), false); }
|
|
1585
2026
|
return;
|
|
1586
2027
|
}
|
|
1587
2028
|
if (e.target.closest('#tt-open')) {
|
|
1588
2029
|
try {
|
|
1589
2030
|
const r = await window.creditdaddy.openTraeClient();
|
|
1590
|
-
if (r && r.ok) toast('已启动 Trae 客户端');
|
|
1591
|
-
else toast((r && r.error) || '无法打开 Trae 客户端', false);
|
|
2031
|
+
if (r && r.ok) toast(T('已启动 Trae 客户端'));
|
|
2032
|
+
else toast((r && r.error) || T('无法打开 Trae 客户端'), false);
|
|
1592
2033
|
} catch (err) { toast(String(err && err.message || err), false); }
|
|
1593
2034
|
return;
|
|
1594
2035
|
}
|
|
2036
|
+
if (e.target.closest('#tt-gw')) {
|
|
2037
|
+
// Trae 本地接口弹窗:开关 + 局域网 + 白名单设置
|
|
2038
|
+
$('tgw-on').checked = state.tGateway === true;
|
|
2039
|
+
$('tgw-lan').checked = state.tGatewayLan === true;
|
|
2040
|
+
const allow = Array.isArray(state.tGatewayAllow) ? state.tGatewayAllow : [];
|
|
2041
|
+
$('tgw-allow').value = (allow.length ? allow : (state.tGatewaySuggest ? [state.tGatewaySuggest] : [])).join('\n');
|
|
2042
|
+
const st = state.tGatewayStats;
|
|
2043
|
+
$('tgw-url').innerHTML = T('接口地址 <code>POST http://127.0.0.1:') + location.port + T('/gateway/trae/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)')
|
|
2044
|
+
+ (st && st.lastCallAt
|
|
2045
|
+
? T('<br>最近调用 ') + esc(String(st.lastCallAt).slice(11, 19)) + T(' · 共 ') + st.calls + T(' 次') + (st.lastAccount ? T(' · 最后用号 ') + esc(st.lastAccount) : '')
|
|
2046
|
+
: T('<br>尚未有调用'));
|
|
2047
|
+
syncTgwLanFields();
|
|
2048
|
+
openModal('m-tgw');
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
1595
2051
|
if (e.target.closest('#mt-open')) {
|
|
1596
2052
|
try {
|
|
1597
2053
|
const r = await window.creditdaddy.openMiniMaxClient();
|
|
1598
|
-
if (r && r.ok) toast('已启动 MiniMax Code 客户端');
|
|
1599
|
-
else toast((r && r.error) || '无法打开 MiniMax Code 客户端', false);
|
|
2054
|
+
if (r && r.ok) toast(T('已启动 MiniMax Code 客户端'));
|
|
2055
|
+
else toast((r && r.error) || T('无法打开 MiniMax Code 客户端'), false);
|
|
1600
2056
|
} catch (err) { toast(String(err && err.message || err), false); }
|
|
1601
2057
|
return;
|
|
1602
2058
|
}
|
|
@@ -1607,10 +2063,10 @@ document.addEventListener('click', async (e) => {
|
|
|
1607
2063
|
const allow = Array.isArray(state.mGatewayAllow) ? state.mGatewayAllow : [];
|
|
1608
2064
|
$('mgw-allow').value = (allow.length ? allow : (state.mGatewaySuggest ? [state.mGatewaySuggest] : [])).join('\n');
|
|
1609
2065
|
const st = state.mGatewayStats;
|
|
1610
|
-
$('mgw-url').innerHTML = '接口地址 <code>POST http://127.0.0.1:' + location.port + '/gateway/minimax/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)'
|
|
2066
|
+
$('mgw-url').innerHTML = T('接口地址 <code>POST http://127.0.0.1:') + location.port + T('/gateway/minimax/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)')
|
|
1611
2067
|
+ (st && st.lastCallAt
|
|
1612
|
-
? '<br>最近调用 ' + esc(String(st.lastCallAt).slice(11, 19)) + ' · 共 ' + st.calls + ' 次' + (st.lastAccount ? ' · 最后用号 ' + esc(st.lastAccount) : '')
|
|
1613
|
-
: '<br>尚未有调用');
|
|
2068
|
+
? T('<br>最近调用 ') + esc(String(st.lastCallAt).slice(11, 19)) + T(' · 共 ') + st.calls + T(' 次') + (st.lastAccount ? T(' · 最后用号 ') + esc(st.lastAccount) : '')
|
|
2069
|
+
: T('<br>尚未有调用'));
|
|
1614
2070
|
syncMgwLanFields();
|
|
1615
2071
|
openModal('m-mgw');
|
|
1616
2072
|
return;
|
|
@@ -1620,7 +2076,7 @@ document.addEventListener('click', async (e) => {
|
|
|
1620
2076
|
$('zac-interval').value = state.zClaimIntervalMin || 2;
|
|
1621
2077
|
$('zac-window').value = state.zClaimWindowMin === 0 ? 0 : (state.zClaimWindowMin || 60);
|
|
1622
2078
|
$('zac-off').style.display = state.zAutoClaim === true ? '' : 'none';
|
|
1623
|
-
$('zac-ok').textContent = state.zAutoClaim === true ? '保存' : '保存并启用';
|
|
2079
|
+
$('zac-ok').textContent = state.zAutoClaim === true ? T('保存') : T('保存并启用');
|
|
1624
2080
|
openModal('m-zac');
|
|
1625
2081
|
return;
|
|
1626
2082
|
}
|
|
@@ -1631,22 +2087,14 @@ document.addEventListener('click', async (e) => {
|
|
|
1631
2087
|
const allow = Array.isArray(state.zGatewayAllow) ? state.zGatewayAllow : [];
|
|
1632
2088
|
$('zgw-allow').value = (allow.length ? allow : (state.zGatewaySuggest ? [state.zGatewaySuggest] : [])).join('\n');
|
|
1633
2089
|
const st = state.zGatewayStats;
|
|
1634
|
-
$('zgw-url').innerHTML = '接口地址 <code>POST http://127.0.0.1:' + location.port + '/gateway/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)'
|
|
2090
|
+
$('zgw-url').innerHTML = T('接口地址 <code>POST http://127.0.0.1:') + location.port + T('/gateway/zcode/v1/messages</code>(局域网开放时把 127.0.0.1 换成本机 IP)')
|
|
1635
2091
|
+ (st && st.lastCallAt
|
|
1636
|
-
? '<br>最近调用 ' + esc(String(st.lastCallAt).slice(11, 19)) + ' · 共 ' + st.calls + ' 次' + (st.lastAccount ? ' · 最后用号 ' + esc(st.lastAccount) : '')
|
|
1637
|
-
: '<br>尚未有调用');
|
|
2092
|
+
? T('<br>最近调用 ') + esc(String(st.lastCallAt).slice(11, 19)) + T(' · 共 ') + st.calls + T(' 次') + (st.lastAccount ? T(' · 最后用号 ') + esc(st.lastAccount) : '')
|
|
2093
|
+
: T('<br>尚未有调用'));
|
|
1638
2094
|
syncZgwLanFields();
|
|
1639
2095
|
openModal('m-zgw');
|
|
1640
2096
|
return;
|
|
1641
2097
|
}
|
|
1642
|
-
if (e.target.closest('#zt-net')) {
|
|
1643
|
-
// 出口弹窗:顺序 + 代理地址一起保存;代理只回填到 placeholder(脱敏),留空即清除
|
|
1644
|
-
(state.zProxyFirst === true ? $('znet-proxy') : $('znet-direct')).checked = true;
|
|
1645
|
-
$('znet-url').value = '';
|
|
1646
|
-
$('znet-url').placeholder = state.zProxyUrl || (state.zHasEnvProxy ? '跟随环境变量 HTTPS_PROXY' : 'http://192.168.31.101:7890');
|
|
1647
|
-
openModal('m-znet');
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
2098
|
});
|
|
1651
2099
|
async function loadZNet() {
|
|
1652
2100
|
try {
|
|
@@ -1685,6 +2133,18 @@ async function loadMGateway() {
|
|
|
1685
2133
|
applyLogTabs(); // 网关开关变化影响日志标签显隐
|
|
1686
2134
|
} catch (e) {}
|
|
1687
2135
|
}
|
|
2136
|
+
async function loadTGateway() {
|
|
2137
|
+
try {
|
|
2138
|
+
const d = await api('/api/trae-gateway');
|
|
2139
|
+
state.tGateway = d.enabled === true;
|
|
2140
|
+
state.tGatewayLan = d.lan === true;
|
|
2141
|
+
state.tGatewayAllow = d.allow || [];
|
|
2142
|
+
state.tGatewaySuggest = d.suggestAllow || '';
|
|
2143
|
+
state.tGatewayStats = d.stats || null;
|
|
2144
|
+
if (state.view === 'trae') { renderStatus(); render(); }
|
|
2145
|
+
applyLogTabs(); // 网关开关变化影响日志标签显隐
|
|
2146
|
+
} catch (e) {}
|
|
2147
|
+
}
|
|
1688
2148
|
|
|
1689
2149
|
// ── 10Router ──
|
|
1690
2150
|
function quotaPct(q) {
|
|
@@ -1716,11 +2176,11 @@ function quotaLow(c) {
|
|
|
1716
2176
|
|
|
1717
2177
|
function quotaValue(q) {
|
|
1718
2178
|
const pct = quotaPct(q);
|
|
1719
|
-
if (q.percentScale || (!(q.total > 0) && pct !== null)) return { big: fmtNum(pct) + '%', small: '剩余', pct };
|
|
1720
|
-
if (q.total > 0) return { big: fmtNum(quotaRemaining(q)), small: '/ ' + fmtNum(q.total) + (q.unit ? ' ' + q.unit : ''), pct };
|
|
1721
|
-
return { big: fmtNum(q.used || 0), small: '已用', pct };
|
|
2179
|
+
if (q.percentScale || (!(q.total > 0) && pct !== null)) return { big: fmtNum(pct) + '%', small: T('剩余'), pct };
|
|
2180
|
+
if (q.total > 0) return { big: fmtNum(quotaRemaining(q)), small: '/ ' + fmtNum(q.total) + (q.unit ? ' ' + T(q.unit) : ''), pct };
|
|
2181
|
+
return { big: fmtNum(q.used || 0), small: T('已用'), pct };
|
|
1722
2182
|
}
|
|
1723
|
-
const resetLabel = (q) => (q.resetAt ? fmtTime(q.resetAt).slice(5) + (q.recurring === false ? ' 到期' : ' 重置') : '');
|
|
2183
|
+
const resetLabel = (q) => (q.resetAt ? fmtTime(q.resetAt).slice(5) + (q.recurring === false ? T(' 到期') : T(' 重置')) : '');
|
|
1724
2184
|
|
|
1725
2185
|
// 明细整行:名称 + 数值/相对重置 + 水位条(CLIProxyAPI quotaRow 版式)
|
|
1726
2186
|
function trRow(q) {
|
|
@@ -1729,11 +2189,12 @@ function trRow(q) {
|
|
|
1729
2189
|
|
|
1730
2190
|
function lineVal(q) {
|
|
1731
2191
|
const pct = quotaPct(q);
|
|
1732
|
-
//
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2192
|
+
// 数值优先:有 total 就显示「剩 x / y」——remainingPercentage 是上游四舍五入的
|
|
2193
|
+
// 粗略值(3.99/4 → 100%),只在没有绝对量时才退回百分比口径;
|
|
2194
|
+
// percentScale(Antigravity 这类限时窗口平等关系)保持百分比。
|
|
2195
|
+
const txt = (q.percentScale || !(q.total > 0))
|
|
2196
|
+
? T('剩 ') + (pct !== null ? fmtNum(pct) + '%' : fmtNum(quotaRemaining(q)))
|
|
2197
|
+
: T('剩 ') + fmtNum(quotaRemaining(q)) + ' / ' + fmtNum(q.total || 0) + (q.unit ? ' ' + T(q.unit) : '');
|
|
1737
2198
|
return '<span>' + esc(txt) + '</span> ' + relResetHtml(q);
|
|
1738
2199
|
}
|
|
1739
2200
|
|
|
@@ -1742,7 +2203,7 @@ function lineVal(q) {
|
|
|
1742
2203
|
function trPrimaryHtml(q) {
|
|
1743
2204
|
if (q.percentScale) return trRow(q);
|
|
1744
2205
|
const v = quotaValue(q);
|
|
1745
|
-
return '<div class="tr-primary"><div class="tr-line tr-primary-line"><span class="tr-q-name" title="' + esc(q.name) + '">' + esc(q.name) + '</span>'
|
|
2206
|
+
return '<div class="tr-primary"><div class="tr-line tr-primary-line"><span class="tr-q-name" title="' + esc(T(q.name)) + '">' + esc(T(q.name)) + '</span>'
|
|
1746
2207
|
+ '<span class="tr-q-val"><b>' + esc(v.big) + '</b> <small class="dim">' + esc(v.small) + '</small> ' + relResetHtml(q) + '</span></div>'
|
|
1747
2208
|
+ (v.pct !== null ? trMeter(v.pct) : '')
|
|
1748
2209
|
+ '</div>';
|
|
@@ -1751,15 +2212,15 @@ function trPrimaryHtml(q) {
|
|
|
1751
2212
|
function trQuotaHtml(q, { dimWhenEmpty = false } = {}) {
|
|
1752
2213
|
const v = quotaValue(q);
|
|
1753
2214
|
const used = v.pct !== null && v.pct <= 0;
|
|
1754
|
-
const val = used && dimWhenEmpty ? '已用完' : (q.percentScale || !(q.total > 0) ? '剩余 ' + v.big : '剩余 ' + v.big + ' ' + v.small);
|
|
1755
|
-
return '<div class="tr-q' + (used && dimWhenEmpty ? ' spent' : '') + '"><div class="tr-q-line"><span class="tr-q-name" title="' + esc(q.name) + '">' + esc(q.name) + '</span>'
|
|
2215
|
+
const val = used && dimWhenEmpty ? T('已用完') : (q.percentScale || !(q.total > 0) ? T('剩余 ') + v.big : T('剩余 ') + v.big + ' ' + v.small);
|
|
2216
|
+
return '<div class="tr-q' + (used && dimWhenEmpty ? ' spent' : '') + '"><div class="tr-q-line"><span class="tr-q-name" title="' + esc(T(q.name)) + '">' + esc(T(q.name)) + '</span>'
|
|
1756
2217
|
+ '<span class="tr-q-val">' + esc(val) + relResetHtml(q) + '</span></div>'
|
|
1757
2218
|
+ (!(used && dimWhenEmpty) ? trMeter(v.pct) : '')
|
|
1758
2219
|
+ '</div>';
|
|
1759
2220
|
}
|
|
1760
2221
|
|
|
1761
2222
|
// 积分包按名称系列聚合("Bonus Pack 18/19/20" → "Bonus Pack ×3"),去掉尾部序号作键
|
|
1762
|
-
const packKey = (name) => String(name || '').replace(/[\s#(]*\d+\s*[)]?\s*$/, '').trim() || '积分包';
|
|
2223
|
+
const packKey = (name) => String(name || '').replace(/[\s#(]*\d+\s*[)]?\s*$/, '').trim() || T('积分包');
|
|
1763
2224
|
function trPacksHtml(c, packs) {
|
|
1764
2225
|
if (!packs.length) return '';
|
|
1765
2226
|
const alive = packs.filter((q) => quotaRemaining(q) > 0);
|
|
@@ -1778,13 +2239,13 @@ function trPacksHtml(c, packs) {
|
|
|
1778
2239
|
const gs = [...groups.values()].sort((a, b) => String(a.resetAt || '9999').localeCompare(String(b.resetAt || '9999')));
|
|
1779
2240
|
const aliveSum = alive.reduce((n, q) => n + quotaRemaining(q), 0);
|
|
1780
2241
|
const open = Boolean(state.trOpen && state.trOpen[c.id]);
|
|
1781
|
-
const head = '<div class="tr-packs-head">积分包 <b>' + alive.length + '</b> 个可用'
|
|
1782
|
-
+ (alive.length ? ',共剩 ' + fmtNum(aliveSum) : '')
|
|
1783
|
-
+ (spent.length ? ' · ' + spent.length + ' 个已用完' : '') + '</div>';
|
|
2242
|
+
const head = T('<div class="tr-packs-head">积分包 <b>') + alive.length + T('</b> 个可用')
|
|
2243
|
+
+ (alive.length ? T(',共剩 ') + fmtNum(aliveSum) : '')
|
|
2244
|
+
+ (spent.length ? ' · ' + spent.length + T(' 个已用完') : '') + '</div>';
|
|
1784
2245
|
const lines = gs.map((g) => trLine(
|
|
1785
2246
|
g.name + (g.count > 1 ? ' ×' + g.count : ''),
|
|
1786
|
-
esc('剩 ' + fmtNum(g.rem) + (g.tot > 0 ? ' / ' + fmtNum(g.tot) : ''))
|
|
1787
|
-
+ (g.resetAt ? '<span class="tr-q-when"> · 最早 ' + esc(fmtTime(g.resetAt).slice(5, 10)) + ' 到期</span>' : ''),
|
|
2247
|
+
esc(T('剩 ') + fmtNum(g.rem) + (g.tot > 0 ? ' / ' + fmtNum(g.tot) : ''))
|
|
2248
|
+
+ (g.resetAt ? T('<span class="tr-q-when"> · 最早 ') + esc(fmtTime(g.resetAt).slice(5, 10)) + T(' 到期</span>') : ''),
|
|
1788
2249
|
)).join('');
|
|
1789
2250
|
const detail = open
|
|
1790
2251
|
? [...alive.sort((a, b) => String(a.resetAt || '9999').localeCompare(String(b.resetAt || '9999'))), ...spent]
|
|
@@ -1792,7 +2253,7 @@ function trPacksHtml(c, packs) {
|
|
|
1792
2253
|
: '';
|
|
1793
2254
|
const collapsible = alive.length > gs.length || spent.length > 0;
|
|
1794
2255
|
return '<div class="tr-packs">' + head + (open ? detail : lines)
|
|
1795
|
-
+ (collapsible ? '<button class="tr-more" data-tr="toggle" data-id="' + esc(c.id) + '">' + (open ? '收起' : '逐包明细') + '</button>' : '')
|
|
2256
|
+
+ (collapsible ? '<button class="tr-more" data-tr="toggle" data-id="' + esc(c.id) + '">' + (open ? T('收起') : T('逐包明细')) + '</button>' : '')
|
|
1796
2257
|
+ '</div>';
|
|
1797
2258
|
}
|
|
1798
2259
|
|
|
@@ -1803,11 +2264,11 @@ function trCardHtml(c) {
|
|
|
1803
2264
|
const showPlan = c.plan && !norm(c.plan).includes(norm(c.providerName)) && !norm(c.providerName).includes(norm(c.plan));
|
|
1804
2265
|
let body;
|
|
1805
2266
|
if (c.error) {
|
|
1806
|
-
body = '<div class="msg err" title="' + esc(c.error) + '"
|
|
2267
|
+
body = '<div class="msg err" title="' + esc(T(c.error)) + '">' + T('查询失败:') + esc(c.error.slice(0, 80)) + '</div>';
|
|
1807
2268
|
} else {
|
|
1808
2269
|
const { primary, windows, packs } = splitQuotas(c);
|
|
1809
2270
|
if (!(primary || windows.length || packs.length)) {
|
|
1810
|
-
body = '<div class="msg">' + esc(/no separate quota|billed at standard/i.test(c.message || '') ? '按量计费,无额度可显示' : (c.message || '该供应商未返回额度数据')) + '</div>';
|
|
2271
|
+
body = '<div class="msg">' + esc(/no separate quota|billed at standard/i.test(c.message || '') ? T('按量计费,无额度可显示') : (c.message || T('该供应商未返回额度数据'))) + '</div>';
|
|
1811
2272
|
} else {
|
|
1812
2273
|
// 与汇总卡同款默认视图:主额度 + 每个周 / 月窗口额度一条(带进度条);积分包走聚合行,「逐包明细」展开单包
|
|
1813
2274
|
body = (primary ? trPrimaryHtml(primary) : '')
|
|
@@ -1817,12 +2278,12 @@ function trCardHtml(c) {
|
|
|
1817
2278
|
}
|
|
1818
2279
|
return '<div class="tr-card' + (c.isActive ? '' : ' off') + (body && body.startsWith('<div class="msg') ? ' flat' : '') + '" draggable="true" data-id="' + esc(c.id) + '" data-vendor="' + esc(c.providerName || c.provider || '') + '"><div class="row1"><div class="name-group">'
|
|
1819
2280
|
+ '<span class="badge prod tenrouter">' + esc(c.providerName) + '</span>'
|
|
1820
|
-
+ (TR_REGION[c.provider] ? '<span class="badge ' + TR_REGION[c.provider][1] + '">' + TR_REGION[c.provider][0] + '</span>' : '')
|
|
2281
|
+
+ (TR_REGION[c.provider] ? '<span class="badge ' + TR_REGION[c.provider][1] + '">' + T(TR_REGION[c.provider][0]) + '</span>' : '')
|
|
1821
2282
|
+ '<span class="tr-name" title="' + esc([c.name, c.email].filter(Boolean).join(' · ')) + '">' + esc(who) + '</span>'
|
|
1822
2283
|
+ (showPlan ? '<span class="badge">' + esc(c.plan) + '</span>' : '')
|
|
1823
|
-
+ (c.isActive ? '' : '<span class="pill pending">已停用</span>')
|
|
1824
|
-
+ (c.limitReached ? '<span class="pill fail">已达上限</span>' : '')
|
|
1825
|
-
+ (c.isActive && !c.error && quotaLow(c) ? '<span class="pill limited">告急</span>' : '')
|
|
2284
|
+
+ (c.isActive ? '' : T('<span class="pill pending">已停用</span>'))
|
|
2285
|
+
+ (c.limitReached ? T('<span class="pill fail">已达上限</span>') : '')
|
|
2286
|
+
+ (c.isActive && !c.error && quotaLow(c) ? T('<span class="pill limited">告急</span>') : '')
|
|
1826
2287
|
+ '</div></div>' + body + '</div>';
|
|
1827
2288
|
}
|
|
1828
2289
|
|
|
@@ -1834,7 +2295,7 @@ const trSorted = (list) => list.slice().sort((a, b) => (b.isActive - a.isActive)
|
|
|
1834
2295
|
function trGroups(list) {
|
|
1835
2296
|
const map = new Map();
|
|
1836
2297
|
for (const c of trSorted(list)) {
|
|
1837
|
-
const k = c.providerName || c.provider || '其他';
|
|
2298
|
+
const k = c.providerName || c.provider || T('其他');
|
|
1838
2299
|
if (!map.has(k)) map.set(k, []);
|
|
1839
2300
|
map.get(k).push(c);
|
|
1840
2301
|
}
|
|
@@ -1877,7 +2338,9 @@ function trVendorCardHtml(g) {
|
|
|
1877
2338
|
if (!q || isAgg(q) || isDetail(q)) continue;
|
|
1878
2339
|
if (!rowMap.has(q.name)) rowMap.set(q.name, { name: q.name, total: 0, rem: 0, anyTotal: false, pctScale: false, resetAt: null, pcts: [], unit: '' });
|
|
1879
2340
|
const w = rowMap.get(q.name);
|
|
1880
|
-
|
|
2341
|
+
// 有绝对量(total>0)就不算百分比行:remainingPercentage 是上游四舍五入值,
|
|
2342
|
+
// 拿它当口径会把 3.99/4 显示成「剩 100%」,与 10Router 自己的 3.99/4 对不齐
|
|
2343
|
+
const isPctRow = q.percentScale || !(q.total > 0);
|
|
1881
2344
|
if (isPctRow) w.pctScale = true;
|
|
1882
2345
|
const pct = quotaPct(q);
|
|
1883
2346
|
if (pct !== null) w.pcts.push(pct);
|
|
@@ -1902,14 +2365,14 @@ function trVendorCardHtml(g) {
|
|
|
1902
2365
|
const alivePacks = packs.filter((q) => quotaRemaining(q) > 0);
|
|
1903
2366
|
const packRem = alivePacks.reduce((n, q) => n + quotaRemaining(q), 0);
|
|
1904
2367
|
const briefs = [];
|
|
1905
|
-
if (alivePacks.length) briefs.push('积分包×' + alivePacks.length + ' 剩 ' + fmtNum(packRem));
|
|
1906
|
-
else if (packs.length) briefs.push('积分包已用完');
|
|
2368
|
+
if (alivePacks.length) briefs.push(T('积分包×') + alivePacks.length + T(' 剩 ') + fmtNum(packRem));
|
|
2369
|
+
else if (packs.length) briefs.push(T('积分包已用完'));
|
|
1907
2370
|
const hasQuotas = Boolean(primary || others.length || packs.length);
|
|
1908
2371
|
let quotaHtml;
|
|
1909
2372
|
if (errs === items.length) {
|
|
1910
|
-
quotaHtml = '<div class="msg err">全部连接查询失败</div>';
|
|
2373
|
+
quotaHtml = T('<div class="msg err">全部连接查询失败</div>');
|
|
1911
2374
|
} else if (!hasQuotas) {
|
|
1912
|
-
quotaHtml = '<div class="msg">该供应商没有可汇总的额度</div>';
|
|
2375
|
+
quotaHtml = T('<div class="msg">该供应商没有可汇总的额度</div>');
|
|
1913
2376
|
} else {
|
|
1914
2377
|
// 「全部」视角不显示「最早到期」:每行自带相对重置时间,足够判断
|
|
1915
2378
|
quotaHtml = (primary ? trPrimaryHtml(primary) : '')
|
|
@@ -1920,30 +2383,30 @@ function trVendorCardHtml(g) {
|
|
|
1920
2383
|
}
|
|
1921
2384
|
return '<div class="tr-card' + (quotaHtml && quotaHtml.startsWith('<div class="msg') ? ' flat' : '') + '" draggable="true" data-vendor="' + esc(g.name) + '"><div class="row1"><div class="name-group">'
|
|
1922
2385
|
+ '<span class="badge prod tenrouter">' + esc(g.name) + '</span>'
|
|
1923
|
-
+ (TR_REGION[prov] ? '<span class="badge ' + TR_REGION[prov][1] + '">' + TR_REGION[prov][0] + '</span>' : '')
|
|
1924
|
-
+ '<span class="badge">' + items.length + ' 个账号</span>'
|
|
1925
|
-
+ (lows ? '<span class="pill limited">告急 ×' + lows + '</span>' : '')
|
|
1926
|
-
+ (errs ? '<span class="pill fail" title="部分连接查询失败">失败 ×' + errs + '</span>' : '')
|
|
2386
|
+
+ (TR_REGION[prov] ? '<span class="badge ' + TR_REGION[prov][1] + '">' + T(TR_REGION[prov][0]) + '</span>' : '')
|
|
2387
|
+
+ '<span class="badge">' + items.length + T(' 个账号</span>')
|
|
2388
|
+
+ (lows ? T('<span class="pill limited">告急 ×') + lows + '</span>' : '')
|
|
2389
|
+
+ (errs ? T('<span class="pill fail" title="部分连接查询失败">失败 ×') + errs + '</span>' : '')
|
|
1927
2390
|
+ '</div></div>' + quotaHtml + '</div>';
|
|
1928
2391
|
}
|
|
1929
2392
|
|
|
1930
2393
|
function trSetupHtml() {
|
|
1931
|
-
return '<div class="setup"><h3>接入 10Router</h3>'
|
|
1932
|
-
+ '<div class="hint">填 10Router 的地址和一个<b>虚拟 key</b>(10Router 仪表盘 → API Keys 创建的 sk-…),就能在这里:</div>'
|
|
1933
|
-
+ '<div class="hint" style="margin:6px 0 12px">· 查看 10Router 里其他供应商(Claude / Codex / Kiro / GLM …)的额度卡片(需要 10Router 1.2.1+)<br>'
|
|
1934
|
-
+ '· 把本机 ZCode / OpenCode / mirasim / 小米 MiMo / 10Router 的用量同步进 10Router 的用量统计(同 10router-sync 插件,自动每小时一次)<br>'
|
|
1935
|
-
+ '· 「10Router 本机」= 本机 10Router 实例自己记的账(如 zcode-free 经它中转的行)同步到聚合端;地址指向本机时自动跳过,避免自己导自己</div>'
|
|
1936
|
-
+ '<button class="btn primary" data-tr="settings">' + I.plus + '配置 10Router</button></div>';
|
|
2394
|
+
return T('<div class="setup"><h3>接入 10Router</h3>')
|
|
2395
|
+
+ T('<div class="hint">填 10Router 的地址和一个<b>虚拟 key</b>(10Router 仪表盘 → API Keys 创建的 sk-…),就能在这里:</div>')
|
|
2396
|
+
+ T('<div class="hint" style="margin:6px 0 12px">· 查看 10Router 里其他供应商(Claude / Codex / Kiro / GLM …)的额度卡片(需要 10Router 1.2.1+)<br>')
|
|
2397
|
+
+ T('· 把本机 ZCode / OpenCode / mirasim / 小米 MiMo / 10Router 的用量同步进 10Router 的用量统计(同 10router-sync 插件,自动每小时一次)<br>')
|
|
2398
|
+
+ T('· 「10Router 本机」= 本机 10Router 实例自己记的账(如 zcode-free 经它中转的行)同步到聚合端;地址指向本机时自动跳过,避免自己导自己</div>')
|
|
2399
|
+
+ '<button class="btn primary" data-tr="settings">' + I.plus + T('配置 10Router</button></div>');
|
|
1937
2400
|
}
|
|
1938
2401
|
|
|
1939
2402
|
function renderTenrouter() {
|
|
1940
2403
|
const box = $('tr-panel');
|
|
1941
2404
|
const c = tr.config;
|
|
1942
|
-
if (!c) { box.innerHTML = '<div class="empty"
|
|
2405
|
+
if (!c) { box.innerHTML = '<div class="empty">' + T('加载中…') + '</div>'; return; }
|
|
1943
2406
|
const gwLine = state.zGateway === true
|
|
1944
|
-
? '<div class="gw-line"><span class="dot on pulse"></span> ZCode 体验包接口运行中'
|
|
1945
|
-
+ (state.zGatewayStats && state.zGatewayStats.lastCallAt ? ' · 最近调用 ' + esc(String(state.zGatewayStats.lastCallAt).slice(11, 19)) + ' · ' + state.zGatewayStats.calls + ' 次 · 最后用号 ' + esc(state.zGatewayStats.lastAccount || '-') : ' · 尚未有调用')
|
|
1946
|
-
+ ' · 模型 <code>zcode-free/glm-5.3-flash</code></div>'
|
|
2407
|
+
? T('<div class="gw-line"><span class="dot on pulse"></span> ZCode 体验包接口运行中')
|
|
2408
|
+
+ (state.zGatewayStats && state.zGatewayStats.lastCallAt ? T(' · 最近调用 ') + esc(String(state.zGatewayStats.lastCallAt).slice(11, 19)) + ' · ' + state.zGatewayStats.calls + T(' 次 · 最后用号 ') + esc(state.zGatewayStats.lastAccount || '-') : T(' · 尚未有调用'))
|
|
2409
|
+
+ T(' · 模型 <code>zcode-free/glm-5.3-flash</code></div>')
|
|
1947
2410
|
: '';
|
|
1948
2411
|
if (!c.configured) { box.innerHTML = gwLine + trSetupHtml(); return; }
|
|
1949
2412
|
const list = (tr.quotas && tr.quotas.connections) || [];
|
|
@@ -1970,36 +2433,36 @@ function renderTenrouter() {
|
|
|
1970
2433
|
const activeVendor = vendor && shown.some((g) => g.name === vendor) ? vendor : '';
|
|
1971
2434
|
if (groups.length) {
|
|
1972
2435
|
tabStrip = '<div class="tr-tabs">'
|
|
1973
|
-
+ '<button class="btn' + (!activeVendor ? ' primary' : '') + '" data-tr="vendor" data-v="">全部</button>'
|
|
2436
|
+
+ '<button class="btn' + (!activeVendor ? ' primary' : '') + T('" data-tr="vendor" data-v="">全部</button>')
|
|
1974
2437
|
+ shown.map((g) => '<button class="btn' + (activeVendor === g.name ? ' primary' : '') + '" data-tr="vendor" data-v="' + esc(g.name) + '">' + esc(g.name) + ' ' + g.items.length + '</button>').join('')
|
|
1975
2438
|
+ '</div>';
|
|
1976
2439
|
}
|
|
1977
|
-
if (tr.error) grid = '<div class="empty">' + esc(tr.error) + '</div>';
|
|
1978
|
-
else if (!tr.quotas) grid = '<div class="empty">正在读取 10Router 供应商额度…</div>';
|
|
1979
|
-
else if (!list.length) grid = '<div class="empty">10Router 里还没有可查额度的供应商连接(OAuth 或支持额度查询的 API Key)。</div>';
|
|
2440
|
+
if (tr.error) grid = '<div class="empty">' + esc(T(tr.error)) + '</div>';
|
|
2441
|
+
else if (!tr.quotas) grid = T('<div class="empty">正在读取 10Router 供应商额度…</div>');
|
|
2442
|
+
else if (!list.length) grid = T('<div class="empty">10Router 里还没有可查额度的供应商连接(OAuth 或支持额度查询的 API Key)。</div>');
|
|
1980
2443
|
else if (activeVendor) {
|
|
1981
2444
|
const g = groups.find((x) => x.name === activeVendor) || { items: [] };
|
|
1982
2445
|
grid = '<div class="tr-vcards">' + sortedConnections(activeVendor, g.items).map(trCardHtml).join('') + '</div>';
|
|
1983
2446
|
} else {
|
|
1984
2447
|
grid = shown.length
|
|
1985
2448
|
? '<div class="tr-vcards">' + sortedVendorGroups(shown).map(trVendorCardHtml).join('') + '</div>'
|
|
1986
|
-
: '<div class="empty">没有可显示的供应商(可在「设置」里关掉隐藏)</div>';
|
|
2449
|
+
: T('<div class="empty">没有可显示的供应商(可在「设置」里关掉隐藏)</div>');
|
|
1987
2450
|
}
|
|
1988
2451
|
const h = tr.health;
|
|
1989
|
-
const healthStrong = !h ? '…' : h.ok ? '正常' : '异常';
|
|
2452
|
+
const healthStrong = !h ? '…' : h.ok ? T('正常') : T('异常');
|
|
1990
2453
|
const healthDetail = !h ? '' : h.ok
|
|
1991
|
-
? (h.driver ? '存储驱动 ' + h.driver : '') + (h.lastDriverError ? '(驱动降级:' + h.lastDriverError + ')' : '')
|
|
2454
|
+
? (h.driver ? T('存储驱动 ') + h.driver : '') + (h.lastDriverError ? T('(驱动降级:') + h.lastDriverError + T(')') : '')
|
|
1992
2455
|
: (h.error || '');
|
|
1993
2456
|
box.innerHTML = '<div class="toolbar"><div class="summary">'
|
|
1994
|
-
+ '<div class="stat" title="' + esc(healthDetail) + '"><span>10Router 健康</span><strong' + (h && !h.ok ? ' style="color:var(--err)"' : (h && h.lastDriverError ? ' style="color:var(--warn,#b45309)"' : '')) + '>' + healthStrong + '</strong></div><div class="divider"></div>'
|
|
1995
|
-
+ '<div class="stat"><span>供应商连接</span><strong>' + (tr.quotas ? list.length : '-') + '</strong></div><div class="divider"></div>'
|
|
1996
|
-
+ '<div class="stat"><span>额度告急</span><strong>' + (tr.quotas ? low : '-') + '</strong></div><div class="divider"></div>'
|
|
1997
|
-
+ '<div class="stat"><span>查询失败</span><strong>' + (tr.quotas ? failed : '-') + '</strong></div>'
|
|
2457
|
+
+ '<div class="stat" title="' + esc(healthDetail) + T('"><span>10Router 健康</span><strong') + (h && !h.ok ? ' style="color:var(--err)"' : (h && h.lastDriverError ? ' style="color:var(--warn,#b45309)"' : '')) + '>' + healthStrong + '</strong></div><div class="divider"></div>'
|
|
2458
|
+
+ T('<div class="stat"><span>供应商连接</span><strong>') + (tr.quotas ? list.length : '-') + '</strong></div><div class="divider"></div>'
|
|
2459
|
+
+ T('<div class="stat"><span>额度告急</span><strong>') + (tr.quotas ? low : '-') + '</strong></div><div class="divider"></div>'
|
|
2460
|
+
+ T('<div class="stat"><span>查询失败</span><strong>') + (tr.quotas ? failed : '-') + '</strong></div>'
|
|
1998
2461
|
+ '</div><div class="actions">'
|
|
1999
|
-
+ '<button class="btn" data-tr="refresh"' + (tr.loading ? ' disabled' : '') + '>' + (tr.loading ? '刷新中…' : '刷新额度') + '</button>'
|
|
2000
|
-
+ '<button class="btn" data-tr="sync" title="用量同步:自动开关、同步来源与手动同步(点我打开)"' + (tr.syncing || c.syncing ? ' disabled' : '') + '>'
|
|
2001
|
-
+ '<span class="dot' + (c.sync.enabled ? ' on' : '') + '"></span>' + (tr.syncing || c.syncing ? '同步中…' : '用量同步') + '</button>'
|
|
2002
|
-
+ '<button class="btn" data-tr="settings">设置</button>'
|
|
2462
|
+
+ '<button class="btn" data-tr="refresh"' + (tr.loading ? ' disabled' : '') + '>' + (tr.loading ? T('刷新中…') : T('刷新额度')) + '</button>'
|
|
2463
|
+
+ T('<button class="btn" data-tr="sync" title="用量同步:自动开关、同步来源与手动同步(点我打开)"') + (tr.syncing || c.syncing ? ' disabled' : '') + '>'
|
|
2464
|
+
+ '<span class="dot' + (c.sync.enabled ? ' on' : '') + '"></span>' + (tr.syncing || c.syncing ? T('同步中…') : T('用量同步')) + '</button>'
|
|
2465
|
+
+ T('<button class="btn" data-tr="settings">设置</button>')
|
|
2003
2466
|
+ '</div></div>'
|
|
2004
2467
|
+ tabStrip
|
|
2005
2468
|
+ '<div class="tr-grid">' + grid + '</div>';
|
|
@@ -2011,11 +2474,11 @@ function trDashCard() {
|
|
|
2011
2474
|
const list = (tr.quotas && tr.quotas.connections) || [];
|
|
2012
2475
|
const low = list.filter((x) => x.isActive && !x.error && quotaLow(x)).length;
|
|
2013
2476
|
const ls = c.lastSync;
|
|
2014
|
-
const rows = '<span class="lbl">供应商连接</span><span class="val">' + (tr.quotas ? list.length : tr.error ? '读取失败' : '…') + '</span>'
|
|
2015
|
-
+ '<span class="lbl">额度告急</span><span class="val' + (low ? ' warn' : '') + '">' + (tr.quotas ? low : '-') + '</span>'
|
|
2016
|
-
+ '<span class="lbl">用量同步</span><span class="val">' + (ls ? esc(fmtTime(ls.at).slice(5)) : c.sync.enabled ? '等待首次同步' : '手动') + '</span>';
|
|
2477
|
+
const rows = T('<span class="lbl">供应商连接</span><span class="val">') + (tr.quotas ? list.length : tr.error ? T('读取失败') : '…') + '</span>'
|
|
2478
|
+
+ T('<span class="lbl">额度告急</span><span class="val') + (low ? ' warn' : '') + '">' + (tr.quotas ? low : '-') + '</span>'
|
|
2479
|
+
+ T('<span class="lbl">用量同步</span><span class="val">') + (ls ? esc(fmtTime(ls.at).slice(5)) : c.sync.enabled ? T('等待首次同步') : T('手动')) + '</span>';
|
|
2017
2480
|
return '<div class="prodcard" data-view="tenrouter"><div class="prod-head"><span class="prod-name"><span class="dot tenrouter"></span>10Router</span>'
|
|
2018
|
-
+ '<span class="prod-go">查看' + I.chevRight + '</span></div><div class="prod-rows">' + rows + '</div></div>';
|
|
2481
|
+
+ T('<span class="prod-go">查看') + I.chevRight + '</span></div><div class="prod-rows">' + rows + '</div></div>';
|
|
2019
2482
|
}
|
|
2020
2483
|
|
|
2021
2484
|
async function loadTrConfig() {
|
|
@@ -2047,9 +2510,9 @@ function openTrSettings() {
|
|
|
2047
2510
|
const c = tr.config || { sync: { enabled: true, sources: [] }, sources: [] };
|
|
2048
2511
|
$('tr-endpoint').value = c.endpoint || '';
|
|
2049
2512
|
$('tr-key').value = '';
|
|
2050
|
-
$('tr-key').placeholder = c.keyMasked ? '已保存 ' + c.keyMasked + '(留空保持不变)' : 'sk-…';
|
|
2513
|
+
$('tr-key').placeholder = c.keyMasked ? T('已保存 ') + c.keyMasked + T('(留空保持不变)') : 'sk-…';
|
|
2051
2514
|
$('tr-admin-pw').value = '';
|
|
2052
|
-
$('tr-admin-pw').placeholder = c.hasAdminPassword ? '已保存(留空保持不变)' : '可选:10Router 面板密码';
|
|
2515
|
+
$('tr-admin-pw').placeholder = c.hasAdminPassword ? T('已保存(留空保持不变)') : T('可选:10Router 面板密码');
|
|
2053
2516
|
$('tr-hide-empty').checked = state.trHideEmpty === true;
|
|
2054
2517
|
$('tr-disconnect').style.display = c.configured ? '' : 'none';
|
|
2055
2518
|
$('tr-test-msg').textContent = '';
|
|
@@ -2062,16 +2525,16 @@ function openTrSync() {
|
|
|
2062
2525
|
$('tr-auto').checked = c.configured ? c.sync.enabled : true;
|
|
2063
2526
|
$('tr-sources').innerHTML = (c.sources || []).map((x) => '<label class="check"><input type="checkbox" data-src="' + x.id + '"'
|
|
2064
2527
|
+ ((c.configured ? c.sync.sources.includes(x.id) : true) ? ' checked' : '') + (x.supported ? '' : ' disabled') + '> ' + esc(x.label)
|
|
2065
|
-
+ '<span class="dim">' + (x.found ? '' : '(本机未检测到)') + (x.supported ? '' : '(需要 Node 22.5+)') + '</span></label>').join('');
|
|
2528
|
+
+ '<span class="dim">' + (x.found ? '' : T('(本机未检测到)')) + (x.supported ? '' : T('(需要 Node 22.5+)')) + '</span></label>').join('');
|
|
2066
2529
|
const ls = c.lastSync;
|
|
2067
|
-
$('tr-sync-status').textContent = ls ? '上次同步 ' + fmtTime(ls.at).slice(5) + ':' + ls.summary : '还没有同步过';
|
|
2530
|
+
$('tr-sync-status').textContent = ls ? T('上次同步 ') + fmtTime(ls.at).slice(5) + T(':') + ls.summary : T('还没有同步过');
|
|
2068
2531
|
$('tr-sync-now').disabled = tr.syncing || c.syncing === true;
|
|
2069
2532
|
openModal('m-trsync');
|
|
2070
2533
|
}
|
|
2071
2534
|
async function trSyncSave(close) {
|
|
2072
2535
|
try {
|
|
2073
2536
|
tr.config = await api('/api/tenrouter', { method: 'PUT', json: { syncEnabled: $('tr-auto').checked, sources: [...document.querySelectorAll('#tr-sources input[data-src]:checked')].map((x) => x.dataset.src) } });
|
|
2074
|
-
if (close) { closeModal('m-trsync'); toast('用量同步设置已保存'); render(); }
|
|
2537
|
+
if (close) { closeModal('m-trsync'); toast(T('用量同步设置已保存')); render(); }
|
|
2075
2538
|
} catch (e) { toast(e.message, false); throw e; }
|
|
2076
2539
|
}
|
|
2077
2540
|
function trForm() {
|
|
@@ -2085,36 +2548,36 @@ function trForm() {
|
|
|
2085
2548
|
async function trTest() {
|
|
2086
2549
|
const f = trForm();
|
|
2087
2550
|
$('tr-test-msg').style.color = '';
|
|
2088
|
-
$('tr-test-msg').textContent = '测试中…';
|
|
2551
|
+
$('tr-test-msg').textContent = T('测试中…');
|
|
2089
2552
|
try {
|
|
2090
2553
|
const r = await api('/api/tenrouter/test', { json: { endpoint: f.endpoint, key: f.key } });
|
|
2091
2554
|
$('tr-test-msg').style.color = r.ok ? 'var(--ok)' : 'var(--err)';
|
|
2092
2555
|
$('tr-test-msg').textContent = !r.ok ? '✗ ' + r.error
|
|
2093
|
-
: r.quotas ? '✓ 连接成功:' + r.connections + ' 个可查额度的供应商连接' : '✓ 已连接,但' + r.warning + '(用量同步可以正常使用)';
|
|
2094
|
-
} catch (e) { $('tr-test-msg').style.color = 'var(--err)'; $('tr-test-msg').textContent = '✗ ' + e.message; }
|
|
2556
|
+
: r.quotas ? T('✓ 连接成功:') + r.connections + T(' 个可查额度的供应商连接') : T('✓ 已连接,但') + r.warning + T('(用量同步可以正常使用)');
|
|
2557
|
+
} catch (e) { $('tr-test-msg').style.color = 'var(--err)'; $('tr-test-msg').textContent = '✗ ' + T(e.message); }
|
|
2095
2558
|
}
|
|
2096
2559
|
async function trSave() {
|
|
2097
2560
|
const f = trForm();
|
|
2098
|
-
if (!f.endpoint) return toast('请填写 10Router 地址', false);
|
|
2099
|
-
if (!f.key && !(tr.config && tr.config.keyMasked)) return toast('请填写虚拟 key', false);
|
|
2561
|
+
if (!f.endpoint) return toast(T('请填写 10Router 地址'), false);
|
|
2562
|
+
if (!f.key && !(tr.config && tr.config.keyMasked)) return toast(T('请填写虚拟 key'), false);
|
|
2100
2563
|
// 显示偏好只存在面板本地(localStorage),不上服务端
|
|
2101
2564
|
state.trHideEmpty = $('tr-hide-empty').checked;
|
|
2102
2565
|
try { localStorage.setItem('qd_tr_hideempty', state.trHideEmpty ? '1' : '0'); } catch (e) {}
|
|
2103
2566
|
try {
|
|
2104
2567
|
tr.config = await api('/api/tenrouter', { method: 'PUT', json: f });
|
|
2105
2568
|
closeModal('m-10r');
|
|
2106
|
-
toast('已保存 10Router 设置');
|
|
2569
|
+
toast(T('已保存 10Router 设置'));
|
|
2107
2570
|
tr.quotas = null; tr.error = null;
|
|
2108
2571
|
loadTrQuotas(true);
|
|
2109
2572
|
} catch (e) { toast(e.message, false); }
|
|
2110
2573
|
}
|
|
2111
2574
|
async function trDisconnect() {
|
|
2112
|
-
if (!(await uiConfirm('断开后会删除保存的 10Router 地址、key 与同步进度(10Router 里已同步的数据不受影响)。', '断开 10Router', '断开'))) return;
|
|
2575
|
+
if (!(await uiConfirm(T('断开后会删除保存的 10Router 地址、key 与同步进度(10Router 里已同步的数据不受影响)。'), T('断开 10Router'), T('断开')))) return;
|
|
2113
2576
|
try { tr.config = await api('/api/tenrouter', { method: 'DELETE' }); tr.quotas = null; tr.error = null; closeModal('m-10r'); render(); }
|
|
2114
2577
|
catch (e) { toast(e.message, false); }
|
|
2115
2578
|
}
|
|
2116
2579
|
async function trSyncAccounts() {
|
|
2117
|
-
if (!(tr.config && tr.config.configured)) return toast('请先保存 10Router 设置', false);
|
|
2580
|
+
if (!(tr.config && tr.config.configured)) return toast(T('请先保存 10Router 设置'), false);
|
|
2118
2581
|
const btn = $('tr-sync-accounts');
|
|
2119
2582
|
const pw = $('tr-admin-pw').value;
|
|
2120
2583
|
btn.disabled = true;
|
|
@@ -2176,6 +2639,12 @@ async function loadAccounts() {
|
|
|
2176
2639
|
renderStatus();
|
|
2177
2640
|
loadQuotas();
|
|
2178
2641
|
}
|
|
2642
|
+
// 轻量刷新账号列表(只重拉 /api/accounts + 重绘,不重查每账号额度):
|
|
2643
|
+
// 「缺网页会话」徽章的数据源 qoderWebCapturedAt 在账号 meta 里,网页会话是在登录窗
|
|
2644
|
+
// 关闭时才收割绑定的(晚于登录成功提示数秒),登录成功后与周期轮询里都要按这个口径补拉。
|
|
2645
|
+
async function refreshAccountList() {
|
|
2646
|
+
try { state.accounts = (await api('/api/accounts')).accounts; render(); } catch (e) { /* 下一轮再试 */ }
|
|
2647
|
+
}
|
|
2179
2648
|
let quotaRun = 0;
|
|
2180
2649
|
let quotaLoadedAt = 0;
|
|
2181
2650
|
async function loadQuotas(ids) {
|
|
@@ -2212,10 +2681,33 @@ function applyLogTabs() {
|
|
|
2212
2681
|
let show = true;
|
|
2213
2682
|
if (t === 'zcode') show = Boolean(state.zGateway);
|
|
2214
2683
|
if (t === 'minimax') show = Boolean(state.mGateway);
|
|
2684
|
+
if (t === 'trae') show = Boolean(state.tGateway);
|
|
2215
2685
|
b.style.display = show ? '' : 'none';
|
|
2216
2686
|
b.classList.toggle('active', t === state.logTab);
|
|
2217
2687
|
});
|
|
2688
|
+
document.querySelectorAll('#log-filter button').forEach((b) => {
|
|
2689
|
+
b.classList.toggle('active', b.dataset.logLevel === state.logLevel);
|
|
2690
|
+
});
|
|
2218
2691
|
}
|
|
2692
|
+
// 加载今日整段历史(结构化归档,按当前语言实时重渲染,突破环形缓冲 300 条上限)。
|
|
2693
|
+
// 常规 10 秒轮询会把视图刷回最近 200 条——历史视图点「切标签/级别」即回到常规轮询。
|
|
2694
|
+
$('btn-logs-history').addEventListener('click', async () => {
|
|
2695
|
+
const el = $('logs');
|
|
2696
|
+
if (!el) return;
|
|
2697
|
+
const qs = state.logTab === 'run' ? '' : ('&tag=' + encodeURIComponent(GW_LOG_TAG[state.logTab]));
|
|
2698
|
+
try {
|
|
2699
|
+
const data = await api('/api/logs?history=1' + qs);
|
|
2700
|
+
let logs = data.logs || [];
|
|
2701
|
+
if (state.logLevel !== 'all') logs = logs.filter((l) => l.level === 'warn' || l.level === 'error');
|
|
2702
|
+
const withTag = state.logTab === 'run';
|
|
2703
|
+
const ico = (lvl) => lvl === 'error' ? '✗' : lvl === 'warn' ? '⚠' : lvl === 'info' ? '·' : '';
|
|
2704
|
+
el.innerHTML = logs.length
|
|
2705
|
+
? logs.map((l) => '<div class="' + esc(l.level) + '">[' + esc(l.at) + '] <span class="lvl-ico">' + ico(l.level) + '</span>' + (withTag ? esc(l.tag) + ' · ' : '') + esc(l.msg) + '</div>').join('')
|
|
2706
|
+
: T('(暂无历史记录)');
|
|
2707
|
+
el.scrollTop = el.scrollHeight;
|
|
2708
|
+
toast(T('已加载今日历史 {n} 条', { n: logs.length }), true);
|
|
2709
|
+
} catch (e) { toast(e.message, false); }
|
|
2710
|
+
});
|
|
2219
2711
|
async function loadCurrentLogs() {
|
|
2220
2712
|
const el = $('logs');
|
|
2221
2713
|
if (!el) return;
|
|
@@ -2228,11 +2720,15 @@ async function loadCurrentLogs() {
|
|
|
2228
2720
|
const data = await api('/api/logs?tag=' + encodeURIComponent(GW_LOG_TAG[state.logTab]));
|
|
2229
2721
|
logs = data.logs || [];
|
|
2230
2722
|
}
|
|
2723
|
+
if (state.logLevel !== 'all') {
|
|
2724
|
+
logs = logs.filter((l) => l.level === 'warn' || l.level === 'error');
|
|
2725
|
+
}
|
|
2231
2726
|
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 8;
|
|
2232
2727
|
const withTag = state.logTab === 'run';
|
|
2728
|
+
const ico = (lvl) => lvl === 'error' ? '✗' : lvl === 'warn' ? '⚠' : lvl === 'info' ? '·' : '';
|
|
2233
2729
|
el.innerHTML = logs.length
|
|
2234
|
-
? logs.map((l) => '<div class="' + esc(l.level) + '">[' + esc(l.at) + '] ' + (withTag ? esc(l.tag) + ' · ' : '') + esc(l.msg) + '</div>').join('')
|
|
2235
|
-
: '(暂无)';
|
|
2730
|
+
? logs.map((l) => '<div class="' + esc(l.level) + '">[' + esc(l.at) + '] <span class="lvl-ico">' + ico(l.level) + '</span>' + (withTag ? esc(l.tag) + ' · ' : '') + esc(l.msg) + '</div>').join('')
|
|
2731
|
+
: (state.logLevel === 'all' ? T('(暂无)') : T('(暂无警告或错误)'));
|
|
2236
2732
|
if (atBottom) el.scrollTop = el.scrollHeight;
|
|
2237
2733
|
} catch (e) {}
|
|
2238
2734
|
}
|
|
@@ -2244,15 +2740,15 @@ async function checkinOne(id) {
|
|
|
2244
2740
|
const d = await api('/api/accounts/' + encodeURIComponent(id) + '/checkin', { method: 'POST' });
|
|
2245
2741
|
const r = d.results && d.results[0];
|
|
2246
2742
|
const accObj = state.accounts.find((x) => x.id === id);
|
|
2247
|
-
const accLabel = (r && r.account) || (accObj && (accObj.name || accObj.id)) || '该账号';
|
|
2248
|
-
toast(r ? (accLabel + ':' + (r.status === 'checked-in' ? '领取成功 +' + r.claimedAmount + ' Credits' : (r.error || r.message || r.status))) : d.summary, !(r && r.status === 'failed'));
|
|
2743
|
+
const accLabel = (r && r.account) || (accObj && (accObj.name || accObj.id)) || T('该账号');
|
|
2744
|
+
toast(r ? (accLabel + T(':') + (r.status === 'checked-in' ? T('领取成功 +') + r.claimedAmount + ' Credits' : (r.error || r.message || r.status))) : d.summary, !(r && r.status === 'failed'));
|
|
2249
2745
|
} catch (e) { toast(e.message, false); }
|
|
2250
2746
|
delete state.busy[id];
|
|
2251
2747
|
await loadStatus(); await loadAccounts(); loadLogs();
|
|
2252
2748
|
}
|
|
2253
2749
|
async function checkinAll(btn, product) {
|
|
2254
2750
|
const label = btn.innerHTML;
|
|
2255
|
-
btn.disabled = true; btn.innerHTML = I.refresh + '领取中…';
|
|
2751
|
+
btn.disabled = true; btn.innerHTML = I.refresh + T('领取中…');
|
|
2256
2752
|
try {
|
|
2257
2753
|
const d = await api('/api/checkin', { json: { skipIfCheckedToday: false, product } });
|
|
2258
2754
|
toast(d.summary);
|
|
@@ -2270,30 +2766,30 @@ async function onCardClick(e) {
|
|
|
2270
2766
|
if (act === 'claim') return openClaim(id);
|
|
2271
2767
|
if (act === 'switch') {
|
|
2272
2768
|
const a = state.accounts.find((x) => x.id === id);
|
|
2273
|
-
const client = CLIENT_LABEL[a && a.product] || (a && prodLabel(a.product)) || '客户端';
|
|
2769
|
+
const client = (CLIENT_LABEL[a && a.product] ? T(CLIENT_LABEL[a && a.product]) : '') || (a && prodLabel(a.product)) || T('客户端');
|
|
2274
2770
|
const tip = a && a.product === 'zcode'
|
|
2275
|
-
? '\n需先退出 ZCode;切换后重新打开 ZCode 即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。'
|
|
2771
|
+
? T('\n需先退出 ZCode;切换后重新打开 ZCode 即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。')
|
|
2276
2772
|
: a && a.product === 'mirasim'
|
|
2277
|
-
? '\n需先退出 Mirasim;切换后重新打开即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。'
|
|
2773
|
+
? T('\n需先退出 Mirasim;切换后重新打开即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。')
|
|
2278
2774
|
: a && a.product === 'catpaw'
|
|
2279
|
-
? '\n需先退出妙手;切换后重新打开妙手即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。'
|
|
2775
|
+
? T('\n需先退出妙手;切换后重新打开妙手即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。')
|
|
2280
2776
|
: a && a.product === 'trae'
|
|
2281
|
-
? '\n需先退出 Trae;切换后重新打开 Trae 即为新账号。当前登录会先自动快照保存到 CreditDaddy,不会丢号。'
|
|
2777
|
+
? T('\n需先退出 Trae;切换后重新打开 Trae 即为新账号。当前登录会先自动快照保存到 CreditDaddy,不会丢号。')
|
|
2282
2778
|
: a && a.product === 'qoder'
|
|
2283
|
-
? '\n需先退出 Qoder;切换后重新打开 Qoder 即为新账号。运行中的 Qoder 退出时会把旧登录写回去,可选「关闭并强制切换」代为关进程。'
|
|
2284
|
-
: '\n客户端会自动应用新会话;当前登录账号的最新 token 会先保存到 CreditDaddy。';
|
|
2285
|
-
if (!(await uiConfirm('把 '
|
|
2779
|
+
? T('\n需先退出 Qoder;切换后重新打开 Qoder 即为新账号。运行中的 Qoder 退出时会把旧登录写回去,可选「关闭并强制切换」代为关进程。')
|
|
2780
|
+
: T('\n客户端会自动应用新会话;当前登录账号的最新 token 会先保存到 CreditDaddy。');
|
|
2781
|
+
if (!(await uiConfirm(T('把 {client} 切换到「{name}」?', { client, name: (a && a.name || id) }) + tip, T('切换账号'), T('切换')))) return;
|
|
2286
2782
|
const doSwitch = (force) => api('/api/accounts/' + encodeURIComponent(id) + '/switch', { json: { force } });
|
|
2287
2783
|
try {
|
|
2288
2784
|
let r;
|
|
2289
2785
|
try { r = await doSwitch(false); }
|
|
2290
2786
|
catch (err) {
|
|
2291
2787
|
if (err.code !== 'ZCODE_RUNNING' && err.code !== 'MIRASIM_RUNNING' && err.code !== 'CATPAW_RUNNING' && err.code !== 'TRAE_RUNNING' && err.code !== 'QODER_RUNNING') throw err;
|
|
2292
|
-
if (!(await uiConfirm(err.message + '\n\n仍要强制切换吗?强制切换会先关闭客户端再写入登录,重新打开客户端即为新账号。', client + ' 正在运行', '关闭并强制切换'))) return;
|
|
2788
|
+
if (!(await uiConfirm(err.message + T('\n\n仍要强制切换吗?强制切换会先关闭客户端再写入登录,重新打开客户端即为新账号。'), client + T(' 正在运行'), T('关闭并强制切换')))) return;
|
|
2293
2789
|
r = await doSwitch(true);
|
|
2294
2790
|
}
|
|
2295
|
-
const msg = r && r.alreadyActive ? client + ' 当前已是该账号'
|
|
2296
|
-
: (r && r.closedClient ? '已关闭并切换 ' : '') + client + ' 已切换到 ' + (a && a.name || id);
|
|
2791
|
+
const msg = r && r.alreadyActive ? client + T(' 当前已是该账号')
|
|
2792
|
+
: (r && r.closedClient ? T('已关闭并切换 ') : '') + client + T(' 已切换到 ') + (a && a.name || id);
|
|
2297
2793
|
toast(msg);
|
|
2298
2794
|
await loadStatus(); await loadAccounts();
|
|
2299
2795
|
// 桌面版切号后自动唤醒打开客户端,免去手动查找客户端快捷方式
|
|
@@ -2301,7 +2797,7 @@ async function onCardClick(e) {
|
|
|
2301
2797
|
setTimeout(async () => {
|
|
2302
2798
|
try {
|
|
2303
2799
|
const re = await window.creditdaddy.openZcodeClient();
|
|
2304
|
-
if (re && re.ok) toast('已重新启动 ZCode 客户端');
|
|
2800
|
+
if (re && re.ok) toast(T('已重新启动 ZCode 客户端'));
|
|
2305
2801
|
} catch (e) {}
|
|
2306
2802
|
}, 800);
|
|
2307
2803
|
}
|
|
@@ -2309,7 +2805,7 @@ async function onCardClick(e) {
|
|
|
2309
2805
|
setTimeout(async () => {
|
|
2310
2806
|
try {
|
|
2311
2807
|
const re = await window.creditdaddy.openMirasimClient();
|
|
2312
|
-
if (re && re.ok) toast('已重新启动 Mirasim 客户端');
|
|
2808
|
+
if (re && re.ok) toast(T('已重新启动 Mirasim 客户端'));
|
|
2313
2809
|
} catch (e) {}
|
|
2314
2810
|
}, 800);
|
|
2315
2811
|
}
|
|
@@ -2317,7 +2813,7 @@ async function onCardClick(e) {
|
|
|
2317
2813
|
setTimeout(async () => {
|
|
2318
2814
|
try {
|
|
2319
2815
|
const re = await window.creditdaddy.openCatpawClient();
|
|
2320
|
-
if (re && re.ok) toast('已重新启动妙手客户端');
|
|
2816
|
+
if (re && re.ok) toast(T('已重新启动妙手客户端'));
|
|
2321
2817
|
} catch (e) {}
|
|
2322
2818
|
}, 800);
|
|
2323
2819
|
}
|
|
@@ -2325,8 +2821,8 @@ async function onCardClick(e) {
|
|
|
2325
2821
|
setTimeout(async () => {
|
|
2326
2822
|
try {
|
|
2327
2823
|
const re = await window.creditdaddy.openTraeClient();
|
|
2328
|
-
if (re && re.ok) toast('已重新启动 Trae 客户端');
|
|
2329
|
-
else toast((re && re.error) || '无法打开 Trae 客户端(桌面版才能自动拉起)', false);
|
|
2824
|
+
if (re && re.ok) toast(T('已重新启动 Trae 客户端'));
|
|
2825
|
+
else toast((re && re.error) || T('无法打开 Trae 客户端(桌面版才能自动拉起)'), false);
|
|
2330
2826
|
} catch (e) {}
|
|
2331
2827
|
}, 800);
|
|
2332
2828
|
}
|
|
@@ -2335,7 +2831,7 @@ async function onCardClick(e) {
|
|
|
2335
2831
|
}
|
|
2336
2832
|
if (act === 'rename') {
|
|
2337
2833
|
const a = state.accounts.find((x) => x.id === id);
|
|
2338
|
-
const name = await uiPrompt('修改备注名', a && a.name || '', { okText: '保存' });
|
|
2834
|
+
const name = await uiPrompt(T('修改备注名'), a && a.name || '', { okText: T('保存') });
|
|
2339
2835
|
if (name === null) return;
|
|
2340
2836
|
try { await api('/api/accounts/' + encodeURIComponent(id), { method: 'PATCH', json: { name } }); await loadAccounts(); }
|
|
2341
2837
|
catch (err) { toast(err.message, false); }
|
|
@@ -2345,11 +2841,11 @@ async function onCardClick(e) {
|
|
|
2345
2841
|
if (!armed[id]) {
|
|
2346
2842
|
armed[id] = setTimeout(() => { delete armed[id]; btn.classList.remove('armed'); }, 3000);
|
|
2347
2843
|
btn.classList.add('armed');
|
|
2348
|
-
toast('再点一次确认删除');
|
|
2844
|
+
toast(T('再点一次确认删除'));
|
|
2349
2845
|
return;
|
|
2350
2846
|
}
|
|
2351
2847
|
clearTimeout(armed[id]); delete armed[id];
|
|
2352
|
-
try { await api('/api/accounts/' + encodeURIComponent(id), { method: 'DELETE' }); toast('已删除'); delete state.quotas[id]; await loadAccounts(); }
|
|
2848
|
+
try { await api('/api/accounts/' + encodeURIComponent(id), { method: 'DELETE' }); toast(T('已删除')); delete state.quotas[id]; await loadAccounts(); }
|
|
2353
2849
|
catch (err) { toast(err.message, false); }
|
|
2354
2850
|
}
|
|
2355
2851
|
}
|
|
@@ -2366,7 +2862,7 @@ function loadCaptchaSdk() {
|
|
|
2366
2862
|
const s = document.createElement('script');
|
|
2367
2863
|
s.src = CAPTCHA_SDK_URL;
|
|
2368
2864
|
s.onload = () => resolve();
|
|
2369
|
-
s.onerror = () => reject(new Error('验证码组件加载失败(网络问题?)'));
|
|
2865
|
+
s.onerror = () => reject(new Error(T('验证码组件加载失败(网络问题?)')));
|
|
2370
2866
|
document.head.appendChild(s);
|
|
2371
2867
|
});
|
|
2372
2868
|
}
|
|
@@ -2379,7 +2875,7 @@ async function openClaim(id) {
|
|
|
2379
2875
|
claimState.plans = [];
|
|
2380
2876
|
claimState.captcha = null;
|
|
2381
2877
|
claimState.busy = false;
|
|
2382
|
-
$('claim-body').innerHTML = '<div class="dim">读取可领取的活动…</div>';
|
|
2878
|
+
$('claim-body').innerHTML = T('<div class="dim">读取可领取的活动…</div>');
|
|
2383
2879
|
openModal('m-claim');
|
|
2384
2880
|
try {
|
|
2385
2881
|
const [plansResp, cap] = await Promise.all([
|
|
@@ -2397,17 +2893,17 @@ async function openClaim(id) {
|
|
|
2397
2893
|
|
|
2398
2894
|
function renderClaimBody() {
|
|
2399
2895
|
if (!claimState.plans.length) {
|
|
2400
|
-
$('claim-body').innerHTML = '<div class="muted-empty">当前没有可领取的活动</div>';
|
|
2896
|
+
$('claim-body').innerHTML = T('<div class="muted-empty">当前没有可领取的活动</div>');
|
|
2401
2897
|
return;
|
|
2402
2898
|
}
|
|
2403
2899
|
$('claim-body').innerHTML = claimState.plans.map((pl, i) =>
|
|
2404
2900
|
'<div class="claim-plan"><div><b>' + esc(pl.name || pl.planId) + '</b>'
|
|
2405
2901
|
+ (pl.description ? '<div class="dim grants">' + esc(pl.description) + '</div>' : '')
|
|
2406
|
-
+ (pl.grants && pl.grants.length ? '<div class="dim grants">' + pl.grants.map(esc).join(';') + '</div>' : '')
|
|
2407
|
-
+ '</div><button class="btn primary" data-claim="' + i + '"' + (claimState.busy ? ' disabled' : '') + '>领取</button></div>'
|
|
2902
|
+
+ (pl.grants && pl.grants.length ? '<div class="dim grants">' + pl.grants.map(esc).join(T(';')) + '</div>' : '')
|
|
2903
|
+
+ '</div><button class="btn primary" data-claim="' + i + '"' + (claimState.busy ? ' disabled' : '') + T('>领取</button></div>')
|
|
2408
2904
|
).join('')
|
|
2409
2905
|
+ '<div id="claim-status" class="dim" style="margin-top:8px"></div>'
|
|
2410
|
-
+ '<div id="cap-holder"></div><button class="btn" id="cap-btn" hidden>打开验证码</button>';
|
|
2906
|
+
+ T('<div id="cap-holder"></div><button class="btn" id="cap-btn" hidden>打开验证码</button>');
|
|
2411
2907
|
}
|
|
2412
2908
|
|
|
2413
2909
|
// 阿里云验证码:先静默验证(8s 超时),不行就让用户点按钮弹验证码。移植自 zcode-switch captcha.js。
|
|
@@ -2421,13 +2917,13 @@ function runCaptcha(cfg) {
|
|
|
2421
2917
|
if (done || !param) return;
|
|
2422
2918
|
done = true;
|
|
2423
2919
|
clearTimeout(timer);
|
|
2424
|
-
st.textContent = '人机验证已通过';
|
|
2920
|
+
st.textContent = T('人机验证已通过');
|
|
2425
2921
|
resolve(param);
|
|
2426
2922
|
};
|
|
2427
2923
|
const interactive = (why) => {
|
|
2428
2924
|
if (done) return;
|
|
2429
2925
|
clearTimeout(timer);
|
|
2430
|
-
st.textContent = '需要完成人机验证:点下方按钮,按提示完成验证码';
|
|
2926
|
+
st.textContent = T('需要完成人机验证:点下方按钮,按提示完成验证码');
|
|
2431
2927
|
if (btn) { btn.hidden = false; btn.focus(); }
|
|
2432
2928
|
if (why && typeof why !== 'string') st.title = JSON.stringify(why).slice(0, 160);
|
|
2433
2929
|
};
|
|
@@ -2441,7 +2937,7 @@ function runCaptcha(cfg) {
|
|
|
2441
2937
|
button: '#cap-btn',
|
|
2442
2938
|
getInstance: (instance) => {
|
|
2443
2939
|
if (instance && typeof instance.startTracelessVerification === 'function') {
|
|
2444
|
-
st.textContent = '人机验证中…';
|
|
2940
|
+
st.textContent = T('人机验证中…');
|
|
2445
2941
|
instance.startTracelessVerification();
|
|
2446
2942
|
timer = setTimeout(() => interactive(), 8000);
|
|
2447
2943
|
} else interactive();
|
|
@@ -2460,7 +2956,7 @@ async function doClaim(i) {
|
|
|
2460
2956
|
const st = $('claim-status');
|
|
2461
2957
|
claimState.busy = true;
|
|
2462
2958
|
renderClaimBody();
|
|
2463
|
-
$('claim-status').textContent = '准备领取…';
|
|
2959
|
+
$('claim-status').textContent = T('准备领取…');
|
|
2464
2960
|
let captchaParam = '';
|
|
2465
2961
|
let region = '';
|
|
2466
2962
|
try {
|
|
@@ -2470,27 +2966,27 @@ async function doClaim(i) {
|
|
|
2470
2966
|
region = cfg.region || '';
|
|
2471
2967
|
captchaParam = await runCaptcha(cfg);
|
|
2472
2968
|
} else {
|
|
2473
|
-
$('claim-status').textContent = '验证码配置不可用,按无验证码直接尝试…';
|
|
2969
|
+
$('claim-status').textContent = T('验证码配置不可用,按无验证码直接尝试…');
|
|
2474
2970
|
}
|
|
2475
2971
|
} catch (e) {
|
|
2476
|
-
$('claim-status').innerHTML = '<span class="msg err">' + esc('验证码失败:' + e.message) + '</span>';
|
|
2972
|
+
$('claim-status').innerHTML = '<span class="msg err">' + esc(T('验证码失败:') + e.message) + '</span>';
|
|
2477
2973
|
claimState.busy = false;
|
|
2478
2974
|
renderClaimBody();
|
|
2479
2975
|
return;
|
|
2480
2976
|
}
|
|
2481
|
-
$('claim-status').textContent = '领取中…';
|
|
2977
|
+
$('claim-status').textContent = T('领取中…');
|
|
2482
2978
|
try {
|
|
2483
2979
|
const r = await api('/api/accounts/' + encodeURIComponent(claimState.accountId) + '/zcode/claim', {
|
|
2484
2980
|
json: { planId: pl.planId, captchaParam, region },
|
|
2485
2981
|
});
|
|
2486
|
-
toast('领取成功:' + (r.planName || pl.name || pl.planId));
|
|
2982
|
+
toast(T('领取成功:') + (r.planName || pl.name || pl.planId));
|
|
2487
2983
|
closeModal('m-claim');
|
|
2488
2984
|
loadQuotas([claimState.accountId]);
|
|
2489
2985
|
loadLogs();
|
|
2490
2986
|
} catch (e) {
|
|
2491
2987
|
const errBox = $('claim-status');
|
|
2492
2988
|
if (errBox) {
|
|
2493
|
-
errBox.innerHTML = '<span class="msg err">' + esc(e.message) + (e.nextAt ? '(' + fmtTime(e.nextAt).slice(5, 16) + ' 后可再试)' : '') + '</span>';
|
|
2989
|
+
errBox.innerHTML = '<span class="msg err">' + esc(e.message) + (e.nextAt ? T('(') + fmtTime(e.nextAt).slice(5, 16) + T(' 后可再试)') : '') + '</span>';
|
|
2494
2990
|
}
|
|
2495
2991
|
}
|
|
2496
2992
|
claimState.busy = false;
|
|
@@ -2503,7 +2999,11 @@ $('claim-body').addEventListener('click', (e) => {
|
|
|
2503
2999
|
});
|
|
2504
3000
|
|
|
2505
3001
|
// ── 弹窗 ──
|
|
2506
|
-
function openModal(id) {
|
|
3002
|
+
function openModal(id) {
|
|
3003
|
+
// 设置弹出层不是遮罩弹窗:开其它弹窗前先收掉,避免浮在遮罩弹窗之上(层级错乱来源)
|
|
3004
|
+
if (id !== 'm-settings') { const st = $('m-settings'); if (st) st.classList.remove('show'); }
|
|
3005
|
+
$(id).classList.add('show');
|
|
3006
|
+
}
|
|
2507
3007
|
function closeModal(id) { $(id).classList.remove('show'); if (id === 'm-add') deviceStop(); if (id === 'm-dialog') settleDialog(null); }
|
|
2508
3008
|
|
|
2509
3009
|
// 页面内对话框(替代原生 confirm / prompt:面板嵌在飞牛桌面窗口里是跨域 iframe,原生弹框可能被浏览器拦截)
|
|
@@ -2517,17 +3017,61 @@ function uiDialog({ title, message, input, okText }) {
|
|
|
2517
3017
|
$('dlg-msg').style.display = message ? '' : 'none';
|
|
2518
3018
|
$('dlg-field').style.display = input ? '' : 'none';
|
|
2519
3019
|
if (input) { $('dlg-input').type = input.type || 'text'; $('dlg-input').value = input.value || ''; $('dlg-input').placeholder = input.placeholder || ''; }
|
|
2520
|
-
$('dlg-ok').textContent = okText || '确定';
|
|
3020
|
+
$('dlg-ok').textContent = okText || T('确定');
|
|
2521
3021
|
openModal('m-dialog');
|
|
2522
3022
|
setTimeout(() => (input ? $('dlg-input') : $('dlg-ok')).focus(), 30);
|
|
2523
3023
|
return new Promise((resolve) => { dialogResolve = (v) => resolve(input ? v : v !== null); });
|
|
2524
3024
|
}
|
|
2525
|
-
const uiConfirm = (message, title, okText) => uiDialog({ title: title || '请确认', message, okText });
|
|
3025
|
+
const uiConfirm = (message, title, okText) => uiDialog({ title: title || T('请确认'), message, okText });
|
|
2526
3026
|
const uiPrompt = (title, value, opts = {}) => uiDialog({ title, message: opts.message, input: { value, type: opts.type, placeholder: opts.placeholder }, okText: opts.okText });
|
|
2527
3027
|
function okDialog() { const v = $('dlg-field').style.display === 'none' ? true : $('dlg-input').value; $('m-dialog').classList.remove('show'); settleDialog(v); }
|
|
2528
3028
|
$('dlg-ok').addEventListener('click', okDialog);
|
|
2529
3029
|
$('dlg-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') okDialog(); });
|
|
2530
3030
|
|
|
3031
|
+
// ── 重启服务(桌面客户端或 CLI) ──
|
|
3032
|
+
async function restartCreditDaddy() {
|
|
3033
|
+
const mask = document.createElement('div');
|
|
3034
|
+
mask.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;font-size:15px;gap:12px;backdrop-filter:blur(4px);user-select:none;';
|
|
3035
|
+
mask.innerHTML = T('<div style="font-size:28px">⏳</div><div style="font-weight:600">正在重启 CreditDaddy,请稍候…</div><div style="font-size:12px;color:rgba(255,255,255,.6)">服务恢复后将自动刷新页面</div>');
|
|
3036
|
+
document.body.appendChild(mask);
|
|
3037
|
+
|
|
3038
|
+
try {
|
|
3039
|
+
if (window.creditdaddy && typeof window.creditdaddy.restartApp === 'function') {
|
|
3040
|
+
await window.creditdaddy.restartApp();
|
|
3041
|
+
} else {
|
|
3042
|
+
await api('/api/system/restart', { method: 'POST' });
|
|
3043
|
+
}
|
|
3044
|
+
} catch {}
|
|
3045
|
+
|
|
3046
|
+
let attempts = 0;
|
|
3047
|
+
const poll = setInterval(async () => {
|
|
3048
|
+
attempts++;
|
|
3049
|
+
try {
|
|
3050
|
+
const res = await fetch('/api/status', { cache: 'no-store' });
|
|
3051
|
+
if (res.ok) {
|
|
3052
|
+
clearInterval(poll);
|
|
3053
|
+
location.reload();
|
|
3054
|
+
}
|
|
3055
|
+
} catch {}
|
|
3056
|
+
if (attempts > 30) {
|
|
3057
|
+
clearInterval(poll);
|
|
3058
|
+
mask.innerHTML = T('<div>重启超时或端口已改变</div><button class="btn primary" onclick="location.reload()" style="margin-top:10px">手动刷新</button>');
|
|
3059
|
+
}
|
|
3060
|
+
}, 1000);
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
async function maybePromptRestartForLan(oldLan, newLan) {
|
|
3064
|
+
if (oldLan !== newLan) {
|
|
3065
|
+
const ok = await uiConfirm(T('局域网开放绑定改动需重启服务生效,是否立即重启 CreditDaddy?'), T('配置已保存'), T('立即重启'));
|
|
3066
|
+
if (ok) {
|
|
3067
|
+
await restartCreditDaddy();
|
|
3068
|
+
return true;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
return false;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
|
|
2531
3075
|
// ── 检查更新(桌面版):主进程推状态(update:state),面板只管渲染 ──
|
|
2532
3076
|
const updBridge = window.creditdaddy && window.creditdaddy.update ? window.creditdaddy.update : null;
|
|
2533
3077
|
let updState = null;
|
|
@@ -2538,55 +3082,64 @@ function renderUpdateModal(s) {
|
|
|
2538
3082
|
updState = s;
|
|
2539
3083
|
const body = $('upd-body');
|
|
2540
3084
|
const acts = $('upd-actions');
|
|
2541
|
-
const cur = '当前 v' + (s.current || '?');
|
|
2542
|
-
const rel = '<button class="btn" id="upd-rel">打开 Releases</button>';
|
|
2543
|
-
const close = '<button class="btn" data-close-uu>稍后</button>';
|
|
3085
|
+
const cur = T('当前 v') + (s.current || '?');
|
|
3086
|
+
const rel = T('<button class="btn" id="upd-rel">打开 Releases</button>');
|
|
3087
|
+
const close = T('<button class="btn" data-close-uu>稍后</button>');
|
|
2544
3088
|
switch (s.phase) {
|
|
3089
|
+
case 'unsupported-browser':
|
|
3090
|
+
body.innerHTML = T('<div class="hint">在浏览器里打开的面板没有本机更新通道——请从 GitHub Releases 下载最新安装包安装。</div>');
|
|
3091
|
+
acts.innerHTML = rel + T('<button class="btn" data-close-uu>取消</button>');
|
|
3092
|
+
break;
|
|
2545
3093
|
case 'unsupported':
|
|
2546
|
-
body.innerHTML = '<div class="hint">当前环境不支持应用内更新(开发模式 / Portable / 未签名的 macOS 版),请从 GitHub Releases 手动下载。</div>';
|
|
2547
|
-
acts.innerHTML = rel + '<button class="btn" data-close-uu>取消</button>';
|
|
3094
|
+
body.innerHTML = T('<div class="hint">当前环境不支持应用内更新(开发模式 / Portable / 未签名的 macOS 版),请从 GitHub Releases 手动下载。</div>');
|
|
3095
|
+
acts.innerHTML = rel + T('<button class="btn" data-close-uu>取消</button>');
|
|
2548
3096
|
break;
|
|
2549
3097
|
case 'checking':
|
|
2550
|
-
body.innerHTML = '<div class="hint">正在检查更新…</div>';
|
|
3098
|
+
body.innerHTML = T('<div class="hint">正在检查更新…</div>');
|
|
2551
3099
|
acts.innerHTML = close;
|
|
2552
3100
|
break;
|
|
2553
3101
|
case 'latest':
|
|
2554
|
-
body.innerHTML = '<div class="hint"><span class="dot on"></span> 已是最新版本(' + cur + ')</div>';
|
|
2555
|
-
acts.innerHTML = '<button class="btn primary" data-close-uu>好的</button>';
|
|
3102
|
+
body.innerHTML = T('<div class="hint"><span class="dot on"></span> 已是最新版本(') + cur + ')</div>';
|
|
3103
|
+
acts.innerHTML = T('<button class="btn primary" data-close-uu>好的</button>');
|
|
2556
3104
|
break;
|
|
2557
3105
|
case 'available':
|
|
2558
3106
|
body.innerHTML = s.via === 'updater'
|
|
2559
|
-
? '<div class="hint">发现新版本 <b>v' + esc(s.version) + '</b>(' + cur + ')——正在后台下载,完成后退出 CreditDaddy 时自动安装。</div>'
|
|
2560
|
-
: '<div class="hint">发现新版本 <b>v' + esc(s.version) + '</b>(' + cur + (s.sizeMB ? ',约 ' + s.sizeMB + ' MB' : '') + '),可由本机直接下载安装。</div>';
|
|
2561
|
-
acts.innerHTML = (s.via === 'github' ? updActBtn('upd-go', '立即下载安装', 'primary') : '') + rel + close;
|
|
3107
|
+
? T('<div class="hint">发现新版本 <b>v') + esc(s.version) + T('</b>(') + cur + T(')——正在后台下载,完成后退出 CreditDaddy 时自动安装。</div>')
|
|
3108
|
+
: T('<div class="hint">发现新版本 <b>v') + esc(s.version) + T('</b>(') + cur + (s.sizeMB ? T(',约 ') + s.sizeMB + ' MB' : '') + T('),可由本机直接下载安装。</div>');
|
|
3109
|
+
acts.innerHTML = (s.via === 'github' ? updActBtn('upd-go', T('立即下载安装'), 'primary') : '') + rel + close;
|
|
2562
3110
|
break;
|
|
2563
3111
|
case 'downloading':
|
|
2564
|
-
body.innerHTML = '<div class="hint">正在下载 v' + esc(s.version) + '</div>'
|
|
3112
|
+
body.innerHTML = T('<div class="hint">正在下载 v') + esc(s.version) + '</div>'
|
|
2565
3113
|
+ '<div class="meter" style="margin:8px 0 4px"><span style="width:' + (s.percent || 0) + '%"></span></div>'
|
|
2566
|
-
+ '<div class="hint">' + (s.percent != null ? s.percent + '%' : '') + '(' + (s.mb || '…') + ' MB)' + (s.percent == null ? ' 获取大小中…' : '') + '</div>';
|
|
2567
|
-
acts.innerHTML = '<button class="btn" data-close-uu>后台继续</button>';
|
|
3114
|
+
+ '<div class="hint">' + (s.percent != null ? s.percent + '%' : '') + T('(') + (s.mb || '…') + T(' MB)') + (s.percent == null ? T(' 获取大小中…') : '') + '</div>';
|
|
3115
|
+
acts.innerHTML = T('<button class="btn" data-close-uu>后台继续</button>');
|
|
2568
3116
|
break;
|
|
2569
3117
|
case 'downloaded':
|
|
2570
3118
|
body.innerHTML = '<div class="hint"><span class="dot on pulse"></span> <b>v' + esc(s.version) + '</b> '
|
|
2571
|
-
+ (s.via === 'updater' ? '已下载完成,重启即完成更新(退出时也会自动安装)。' : '安装包已下载完成,安装时 CreditDaddy 会先退出。') + '</div>';
|
|
2572
|
-
acts.innerHTML = updActBtn('upd-go', s.via === 'updater' ? '立即重启更新' : '立即安装', 'primary') + close;
|
|
3119
|
+
+ (s.via === 'updater' ? T('已下载完成,重启即完成更新(退出时也会自动安装)。') : T('安装包已下载完成,安装时 CreditDaddy 会先退出。')) + '</div>';
|
|
3120
|
+
acts.innerHTML = updActBtn('upd-go', s.via === 'updater' ? T('立即重启更新') : T('立即安装'), 'primary') + close;
|
|
2573
3121
|
break;
|
|
2574
3122
|
case 'error':
|
|
2575
3123
|
default:
|
|
2576
|
-
body.innerHTML = '<div class="hint" style="color:var(--err)">检查更新失败:' + esc(s.error || '未知错误') + '</div>';
|
|
2577
|
-
acts.innerHTML = updActBtn('upd-retry', '重试', 'primary') + rel + '<button class="btn" data-close-uu>取消</button>';
|
|
3124
|
+
body.innerHTML = T('<div class="hint" style="color:var(--err)">检查更新失败:') + esc(T(s.error || '') || T('未知错误')) + '</div>';
|
|
3125
|
+
acts.innerHTML = updActBtn('upd-retry', T('重试'), 'primary') + rel + T('<button class="btn" data-close-uu>取消</button>');
|
|
2578
3126
|
break;
|
|
2579
3127
|
}
|
|
2580
3128
|
const relBtn = $('upd-rel');
|
|
2581
|
-
if (relBtn) relBtn.onclick = () => updBridge.openReleases();
|
|
3129
|
+
if (relBtn) relBtn.onclick = () => { if (updBridge) updBridge.openReleases(); else openFullChangelog(); };
|
|
2582
3130
|
const goBtn = $('upd-go');
|
|
2583
|
-
if (goBtn) { goBtn.disabled = true; goBtn.textContent = '处理中…'; updBridge.install().then(renderUpdateModal).catch((e) => { goBtn.disabled = false; goBtn.textContent = '重试'; }); }
|
|
3131
|
+
if (goBtn) { goBtn.disabled = true; goBtn.textContent = T('处理中…'); updBridge.install().then(renderUpdateModal).catch((e) => { goBtn.disabled = false; goBtn.textContent = T('重试'); }); }
|
|
2584
3132
|
const retryBtn = $('upd-retry');
|
|
2585
3133
|
if (retryBtn) retryBtn.onclick = () => updBridge.check().then(renderUpdateModal).catch(() => {});
|
|
2586
3134
|
acts.querySelectorAll('[data-close-uu]').forEach((b) => { b.onclick = () => closeModal('m-update'); });
|
|
2587
3135
|
}
|
|
2588
3136
|
async function openUpdateModal(autoCheck) {
|
|
2589
|
-
if (!updBridge)
|
|
3137
|
+
if (!updBridge) {
|
|
3138
|
+
// 纯浏览器:没有本机 updater 通道,保留入口并给出 Releases 下载承接
|
|
3139
|
+
openModal('m-update');
|
|
3140
|
+
renderUpdateModal({ phase: 'unsupported-browser', current: (state.status && state.status.version) || '' });
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
2590
3143
|
openModal('m-update');
|
|
2591
3144
|
try {
|
|
2592
3145
|
updState = await updBridge.state();
|
|
@@ -2597,10 +3150,36 @@ async function openUpdateModal(autoCheck) {
|
|
|
2597
3150
|
}
|
|
2598
3151
|
}
|
|
2599
3152
|
if (updBridge) {
|
|
2600
|
-
updBridge.onState((s) => { updState = s; $('upd-ver') && ($('upd-ver').textContent = '当前 v' + (s.current || '?')); if (document.getElementById('m-update').classList.contains('show')) renderUpdateModal(s); });
|
|
3153
|
+
updBridge.onState((s) => { updState = s; $('upd-ver') && ($('upd-ver').textContent = T('当前 v') + (s.current || '?')); if (document.getElementById('m-update').classList.contains('show')) renderUpdateModal(s); });
|
|
2601
3154
|
updBridge.onOpen(() => openUpdateModal(true));
|
|
2602
3155
|
}
|
|
2603
|
-
|
|
3156
|
+
// 更新日志小窗:桌面版读随包 CHANGELOG.md 的简要摘要;网页面板直接开 GitHub Releases
|
|
3157
|
+
function openFullChangelog() {
|
|
3158
|
+
if (updBridge && typeof updBridge.openReleases === 'function') updBridge.openReleases();
|
|
3159
|
+
else window.open('https://github.com/techysy/CreditDaddy/releases', '_blank', 'noopener');
|
|
3160
|
+
}
|
|
3161
|
+
function openChangelogModal() {
|
|
3162
|
+
const openReleases = openFullChangelog;
|
|
3163
|
+
if (!(window.creditdaddy && typeof window.creditdaddy.changelogBrief === 'function')) { openReleases(); return; }
|
|
3164
|
+
window.creditdaddy.changelogBrief().then((r) => {
|
|
3165
|
+
const box = $('changelog-body');
|
|
3166
|
+
const strip = (s) => esc(String(s || '').replace(/\*\*/g, ''));
|
|
3167
|
+
box.innerHTML = (r && r.ok && r.sections && r.sections.length)
|
|
3168
|
+
? r.sections.map((s) => '<div class="changelog-sec">'
|
|
3169
|
+
+ '<div style="font-weight:700;margin:0 0 4px">v' + esc(s.version)
|
|
3170
|
+
+ (s.date ? ' <span class="dim" style="font-weight:400;font-size:11px">' + esc(s.date) + '</span>' : '') + '</div>'
|
|
3171
|
+
+ (s.theme ? '<div class="hint">' + strip(s.theme) + '</div>' : '')
|
|
3172
|
+
+ (s.topics && s.topics.length ? '<div class="hint" style="margin-top:4px">' + s.topics.map(strip).join(' · ') + '</div>' : '')
|
|
3173
|
+
+ '</div>').join('')
|
|
3174
|
+
: T('<div class="hint">暂无本地更新日志,请到 GitHub Releases 查看。</div>');
|
|
3175
|
+
openModal('m-changelog');
|
|
3176
|
+
}).catch(openReleases);
|
|
3177
|
+
}
|
|
3178
|
+
$('btn-changelog-full').onclick = openFullChangelog;
|
|
3179
|
+
// 托盘菜单触发(IPC)
|
|
3180
|
+
if (updBridge && typeof updBridge.onChangelogOpen === 'function') {
|
|
3181
|
+
updBridge.onChangelogOpen(openChangelogModal);
|
|
3182
|
+
}
|
|
2604
3183
|
|
|
2605
3184
|
// ZCode 自动领取计划:保存(按新配置启用)/ 停用
|
|
2606
3185
|
$('zac-ok').addEventListener('click', async () => {
|
|
@@ -2611,7 +3190,7 @@ $('zac-ok').addEventListener('click', async () => {
|
|
|
2611
3190
|
state.zClaimWindowMin = r.claimWindowMin;
|
|
2612
3191
|
renderStatus(); render();
|
|
2613
3192
|
closeModal('m-zac');
|
|
2614
|
-
toast('自动领取已开启:每 ' + r.claimIntervalMin + ' 分钟检查' + (r.claimWindowMin > 0 ? ',运行 ' + r.claimWindowMin + ' 分钟后自动关闭' : ',一直运行直到手动关闭'));
|
|
3193
|
+
toast(T('自动领取已开启:每 ') + r.claimIntervalMin + T(' 分钟检查') + (r.claimWindowMin > 0 ? T(',运行 ') + r.claimWindowMin + T(' 分钟后自动关闭') : T(',一直运行直到手动关闭')));
|
|
2615
3194
|
} catch (err) { toast(err.message, false); }
|
|
2616
3195
|
});
|
|
2617
3196
|
$('zac-off').addEventListener('click', async () => {
|
|
@@ -2620,7 +3199,7 @@ $('zac-off').addEventListener('click', async () => {
|
|
|
2620
3199
|
state.zAutoClaim = r.autoClaim === true;
|
|
2621
3200
|
renderStatus(); render();
|
|
2622
3201
|
closeModal('m-zac');
|
|
2623
|
-
toast('ZCode 自动领取:已关闭');
|
|
3202
|
+
toast(T('ZCode 自动领取:已关闭'));
|
|
2624
3203
|
} catch (err) { toast(err.message, false); }
|
|
2625
3204
|
});
|
|
2626
3205
|
|
|
@@ -2633,6 +3212,7 @@ function syncZgwLanFields() {
|
|
|
2633
3212
|
$('zgw-on').addEventListener('change', syncZgwLanFields);
|
|
2634
3213
|
$('zgw-ok').addEventListener('click', async () => {
|
|
2635
3214
|
const allow = $('zgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
|
|
3215
|
+
const oldLan = state.zGatewayLan;
|
|
2636
3216
|
try {
|
|
2637
3217
|
const r = await api('/api/zcode-gateway', { method: 'PUT', json: { enabled: $('zgw-on').checked, lan: $('zgw-lan').checked, allow } });
|
|
2638
3218
|
state.zGateway = r.enabled === true;
|
|
@@ -2640,8 +3220,9 @@ $('zgw-ok').addEventListener('click', async () => {
|
|
|
2640
3220
|
state.zGatewayAllow = r.allow || [];
|
|
2641
3221
|
renderStatus(); render();
|
|
2642
3222
|
closeModal('m-zgw');
|
|
2643
|
-
|
|
2644
|
-
|
|
3223
|
+
if (await maybePromptRestartForLan(oldLan, state.zGatewayLan)) return;
|
|
3224
|
+
toast(T('体验包接口:') + (r.enabled ? T('已开启') : T('已关闭'))
|
|
3225
|
+
+ (r.enabled && r.lan ? T(',局域网开放') + (r.allow && r.allow.length ? T(',白名单 ') + r.allow.join(T('、')) : T(',白名单为空——白名单外需虚拟 key')) + T(';绑定改动重启后生效') : ''));
|
|
2645
3226
|
} catch (err) { toast(err.message, false); }
|
|
2646
3227
|
});
|
|
2647
3228
|
|
|
@@ -2654,6 +3235,7 @@ function syncMgwLanFields() {
|
|
|
2654
3235
|
$('mgw-on').addEventListener('change', syncMgwLanFields);
|
|
2655
3236
|
$('mgw-ok').addEventListener('click', async () => {
|
|
2656
3237
|
const allow = $('mgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
|
|
3238
|
+
const oldLan = state.mGatewayLan;
|
|
2657
3239
|
try {
|
|
2658
3240
|
const r = await api('/api/minimax-gateway', { method: 'PUT', json: { enabled: $('mgw-on').checked, lan: $('mgw-lan').checked, allow } });
|
|
2659
3241
|
state.mGateway = r.enabled === true;
|
|
@@ -2661,27 +3243,59 @@ $('mgw-ok').addEventListener('click', async () => {
|
|
|
2661
3243
|
state.mGatewayAllow = r.allow || [];
|
|
2662
3244
|
renderStatus(); render();
|
|
2663
3245
|
closeModal('m-mgw');
|
|
2664
|
-
|
|
2665
|
-
|
|
3246
|
+
if (await maybePromptRestartForLan(oldLan, state.mGatewayLan)) return;
|
|
3247
|
+
toast(T('MiniMax 接口:') + (r.enabled ? T('已开启') : T('已关闭'))
|
|
3248
|
+
+ (r.enabled && r.lan ? T(',局域网开放') + (r.allow && r.allow.length ? T(',白名单 ') + r.allow.join(T('、')) : '') + T(';绑定改动重启后生效') : ''));
|
|
3249
|
+
} catch (err) { toast(err.message, false); }
|
|
3250
|
+
});
|
|
3251
|
+
|
|
3252
|
+
// Trae 本地接口:开关 + 局域网开放 + 免密 IP 白名单一次保存
|
|
3253
|
+
function syncTgwLanFields() {
|
|
3254
|
+
const off = !$('tgw-on').checked;
|
|
3255
|
+
$('tgw-lan-fields').querySelectorAll('input, textarea').forEach((el) => { el.disabled = off; });
|
|
3256
|
+
$('tgw-lan-fields').style.opacity = off ? '.45' : '';
|
|
3257
|
+
}
|
|
3258
|
+
$('tgw-on').addEventListener('change', syncTgwLanFields);
|
|
3259
|
+
$('tgw-ok').addEventListener('click', async () => {
|
|
3260
|
+
const allow = $('tgw-allow').value.split('\n').map((x) => x.trim()).filter(Boolean);
|
|
3261
|
+
const oldLan = state.tGatewayLan;
|
|
3262
|
+
try {
|
|
3263
|
+
const r = await api('/api/trae-gateway', { method: 'PUT', json: { enabled: $('tgw-on').checked, lan: $('tgw-lan').checked, allow } });
|
|
3264
|
+
state.tGateway = r.enabled === true;
|
|
3265
|
+
state.tGatewayLan = r.lan === true;
|
|
3266
|
+
state.tGatewayAllow = r.allow || [];
|
|
3267
|
+
renderStatus(); render();
|
|
3268
|
+
closeModal('m-tgw');
|
|
3269
|
+
if (await maybePromptRestartForLan(oldLan, state.tGatewayLan)) return;
|
|
3270
|
+
toast(T('Trae 接口:') + (r.enabled ? T('已开启') : T('已关闭'))
|
|
3271
|
+
+ (r.enabled && r.lan ? T(',局域网开放') + (r.allow && r.allow.length ? T(',白名单 ') + r.allow.join(T('、')) : '') + T(';绑定改动重启后生效') : ''));
|
|
2666
3272
|
} catch (err) { toast(err.message, false); }
|
|
2667
3273
|
});
|
|
2668
3274
|
|
|
2669
|
-
//
|
|
3275
|
+
// 网络出口(全局,面板设置里):顺序 + 代理地址保存;代理只回填到 placeholder(脱敏),留空即清除
|
|
2670
3276
|
$('znet-ok').addEventListener('click', async () => {
|
|
2671
3277
|
try {
|
|
2672
3278
|
const body = { proxyFirst: $('znet-proxy').checked, proxyUrl: $('znet-url').value.trim() };
|
|
2673
3279
|
const r = await api('/api/zcode/net', { method: 'PUT', json: body });
|
|
3280
|
+
state.zProxyFirst = r.proxyFirst === true;
|
|
2674
3281
|
await loadZNet();
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
3282
|
+
$('znet-url').value = '';
|
|
3283
|
+
$('znet-url').placeholder = state.zProxyUrl || (state.zHasEnvProxy ? T('跟随环境变量 HTTPS_PROXY') : 'http://192.168.31.101:7890');
|
|
3284
|
+
$('znet-state').textContent = T('已保存:') + (state.zProxyFirst === true ? T('代理优先') : T('直连优先'))
|
|
3285
|
+
+ (state.zProxyUrl ? T(' · 代理 ') + state.zProxyUrl : '');
|
|
3286
|
+
toast(T('网络出口:') + (r.proxyFirst ? T('代理优先(直连兜底)') : T('直连优先(代理兜底)')) + (body.proxyUrl === '' ? T(',代理已清除') : T(',代理已保存')));
|
|
2678
3287
|
} catch (err) { toast(err.message, false); }
|
|
2679
3288
|
});
|
|
2680
|
-
document.querySelectorAll('.mask').forEach((m) => {
|
|
3289
|
+
document.querySelectorAll('.mask, .setpop').forEach((m) => {
|
|
2681
3290
|
m.addEventListener('mousedown', (e) => { if (e.target === m) closeModal(m.id); });
|
|
2682
3291
|
m.querySelectorAll('[data-close]').forEach((b) => b.addEventListener('click', () => closeModal(m.id)));
|
|
2683
3292
|
});
|
|
2684
|
-
document.addEventListener('keydown', (e) => {
|
|
3293
|
+
document.addEventListener('keydown', (e) => {
|
|
3294
|
+
if (e.key !== 'Escape') return;
|
|
3295
|
+
const pop = document.getElementById('hmenu-pop');
|
|
3296
|
+
if (pop && pop.classList.contains('show')) { pop.classList.remove('show'); return; } // ESC 先收下拉
|
|
3297
|
+
document.querySelectorAll('.mask.show, .setpop.show').forEach((m) => closeModal(m.id));
|
|
3298
|
+
});
|
|
2685
3299
|
document.querySelectorAll('#m-add .tab').forEach((t) => t.addEventListener('click', () => switchTab(t.dataset.tab)));
|
|
2686
3300
|
function switchTab(name) {
|
|
2687
3301
|
document.querySelectorAll('#m-add .tab').forEach((t) => t.classList.toggle('active', t.dataset.tab === name));
|
|
@@ -2704,7 +3318,7 @@ function openAdd(product) {
|
|
|
2704
3318
|
// 各产品支持的添加方式:MiniMax 三种都支持(浏览器登录 = 官方设备码授权,独立凭据链不与本机客户端互斥)
|
|
2705
3319
|
const tabShown = { local: true, device: product !== 'mirasim' && product !== 'catpaw' && product !== 'trae', token: product !== 'zcode' && product !== 'mirasim' && product !== 'catpaw' && product !== 'trae' };
|
|
2706
3320
|
document.querySelectorAll('#m-add .tab').forEach((t) => { t.style.display = tabShown[t.dataset.tab] ? '' : 'none'; });
|
|
2707
|
-
$('local-hint').textContent = LOCAL_HINT[state.addProduct];
|
|
3321
|
+
$('local-hint').textContent = T(LOCAL_HINT[state.addProduct]);
|
|
2708
3322
|
let first = null;
|
|
2709
3323
|
for (const o of $('f-provider').options) {
|
|
2710
3324
|
const ok = !product || provOf(o.value).prod === product;
|
|
@@ -2720,8 +3334,8 @@ function openAdd(product) {
|
|
|
2720
3334
|
if (ok && !firstDev) firstDev = o.value;
|
|
2721
3335
|
}
|
|
2722
3336
|
if (firstDev) $('dev-provider').value = firstDev;
|
|
2723
|
-
$('dev-hint').textContent = (hasIncognito() ? '在内置隐私窗口里登录(不带出系统浏览器已登录的账号),' : '在浏览器里登录并授权,')
|
|
2724
|
-
+ '完成后账号自动添加。' + (product === 'qoder' ? '' : 'WorkBuddy / ZCode 用浏览器登录的账号同样可以一键切换客户端。');
|
|
3337
|
+
$('dev-hint').textContent = (hasIncognito() ? T('在内置隐私窗口里登录(不带出系统浏览器已登录的账号),') : T('在浏览器里登录并授权,'))
|
|
3338
|
+
+ T('完成后账号自动添加。') + (product === 'qoder' ? '' : T('WorkBuddy / ZCode 用浏览器登录的账号同样可以一键切换客户端。'));
|
|
2725
3339
|
openModal('m-add');
|
|
2726
3340
|
switchTab('local');
|
|
2727
3341
|
localScan();
|
|
@@ -2730,46 +3344,46 @@ function openAdd(product) {
|
|
|
2730
3344
|
// 本机导入
|
|
2731
3345
|
async function localScan() {
|
|
2732
3346
|
const box = $('scan-result');
|
|
2733
|
-
$('scan-status').textContent = '扫描中(最多约 10 秒)…';
|
|
2734
|
-
box.innerHTML = '<div class="hint">正在读取本机客户端凭据(Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax)…</div>';
|
|
3347
|
+
$('scan-status').textContent = T('扫描中(最多约 10 秒)…');
|
|
3348
|
+
box.innerHTML = T('<div class="hint">正在读取本机客户端凭据(Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax)…</div>');
|
|
2735
3349
|
try {
|
|
2736
3350
|
const r = await api('/api/local/scan', { method: 'POST' });
|
|
2737
3351
|
$('scan-status').textContent = '';
|
|
2738
3352
|
let html = '';
|
|
2739
3353
|
const cands = r.candidates.filter((c) => !state.addProduct || (c.provider ? provOf(c.provider).prod : 'qoder') === state.addProduct);
|
|
2740
3354
|
for (const c of cands) {
|
|
2741
|
-
const title = c.kind === 'app' ? esc(c.name || c.email || c.phone || '(未命名)') : '<span style="font-family:monospace">' + esc(c.tokenMasked) + '</span>';
|
|
2742
|
-
const prov = (c.provider ? provBadges(c.provider) : '<span class="badge">' + esc(c.kind) + '</span>') + (c.current ? '<span class="pill cur">当前登录</span>' : '');
|
|
3355
|
+
const title = c.kind === 'app' ? esc(c.name || c.email || c.phone || T('(未命名)')) : '<span style="font-family:monospace">' + esc(c.tokenMasked) + '</span>';
|
|
3356
|
+
const prov = (c.provider ? provBadges(c.provider) : '<span class="badge">' + esc(c.kind) + '</span>') + (c.current ? T('<span class="pill cur">当前登录</span>') : '');
|
|
2743
3357
|
const sub = c.kind === 'app'
|
|
2744
|
-
? esc(c.source) + (c.email ? ' · ' + esc(mask(c.email)) : c.phone ? ' · ' + esc(c.phone) : '') + (c.expiresAt ? ' · 有效期至 ' + esc(fmtTime(c.expiresAt)) : '')
|
|
2745
|
-
: esc(c.source) + ' · ' + esc(String(c.file || '').split(/[\\/]/).pop()) + '(' + c.hits + ' 处)';
|
|
2746
|
-
const btns = c.imported ? '<span class="pill ok">已导入</span>' + (c.provider ? '<button class="btn" data-cid="' + esc(c.id) + '" title="用本机凭据更新 token / 有效期 / 邮箱">同步信息</button>' : '')
|
|
2747
|
-
: c.provider ? '<button class="btn primary" data-cid="' + esc(c.id) + '">导入</button>'
|
|
2748
|
-
: '<button class="btn" data-cid="' + esc(c.id) + '" data-prov="qoder">导入·国际</button><button class="btn" data-cid="' + esc(c.id) + '" data-prov="qoder-cn">导入·国内</button>';
|
|
3358
|
+
? esc(c.source) + (c.email ? ' · ' + esc(mask(c.email)) : c.phone ? ' · ' + esc(c.phone) : '') + (c.expiresAt ? T(' · 有效期至 ') + esc(fmtTime(c.expiresAt)) : '')
|
|
3359
|
+
: esc(c.source) + ' · ' + esc(String(c.file || '').split(/[\\/]/).pop()) + T('(') + c.hits + T(' 处)');
|
|
3360
|
+
const btns = c.imported ? T('<span class="pill ok">已导入</span>') + (c.provider ? '<button class="btn" data-cid="' + esc(c.id) + T('" title="用本机凭据更新 token / 有效期 / 邮箱">同步信息</button>') : '')
|
|
3361
|
+
: c.provider ? '<button class="btn primary" data-cid="' + esc(c.id) + T('">导入</button>')
|
|
3362
|
+
: '<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>');
|
|
2749
3363
|
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>';
|
|
2750
3364
|
}
|
|
2751
3365
|
for (const e of r.errors || []) {
|
|
2752
|
-
html += '<div class="hint" style="color:var(--err)">读取失败:' + esc(e.file) + ':' + esc(e.error) + '</div>';
|
|
3366
|
+
html += T('<div class="hint" style="color:var(--err)">读取失败:') + esc(e.file) + T(':') + esc(e.error) + '</div>';
|
|
2753
3367
|
}
|
|
2754
3368
|
if (!cands.length) {
|
|
2755
|
-
const where = state.addProduct ? prodLabel(state.addProduct) : 'Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax';
|
|
2756
|
-
const alt = state.addProduct === 'zcode' || state.addProduct === 'mirasim' || state.addProduct === 'catpaw' || state.addProduct === 'trae' ? '' : ',或改用「浏览器登录」「粘贴 Token」';
|
|
2757
|
-
html += '<div class="hint">未发现已登录的 ' + where + ' 账号。请先在客户端登录' + alt + '。<br>已检查:Qoder / Qoder CN 客户端数据目录、WorkBuddy 凭据目录、ZCode 数据目录、mirasim 数据目录、妙手凭据目录、Trae 数据目录、MiniMax 数据目录'
|
|
2758
|
-
+ (r.scannedDirs && r.scannedDirs.length ? '、' + r.scannedDirs.map(esc).join('、') : '') + '</div>';
|
|
3369
|
+
const where = state.addProduct ? prodLabel(state.addProduct) : T('Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax');
|
|
3370
|
+
const alt = state.addProduct === 'zcode' || state.addProduct === 'mirasim' || state.addProduct === 'catpaw' || state.addProduct === 'trae' ? '' : T(',或改用「浏览器登录」「粘贴 Token」');
|
|
3371
|
+
html += T('<div class="hint">未发现已登录的 ') + where + T(' 账号。请先在客户端登录') + alt + T('。<br>已检查:Qoder / Qoder CN 客户端数据目录、WorkBuddy 凭据目录、ZCode 数据目录、mirasim 数据目录、妙手凭据目录、Trae 数据目录、MiniMax 数据目录')
|
|
3372
|
+
+ (r.scannedDirs && r.scannedDirs.length ? T('、') + r.scannedDirs.map(esc).join(T('、')) : '') + '</div>';
|
|
2759
3373
|
}
|
|
2760
3374
|
box.innerHTML = html;
|
|
2761
3375
|
box.querySelectorAll('button[data-cid]').forEach((b) => b.addEventListener('click', async () => {
|
|
2762
3376
|
b.disabled = true;
|
|
2763
3377
|
try {
|
|
2764
3378
|
const d = await api('/api/local/import', { json: { candidateId: b.dataset.cid, provider: b.dataset.prov } });
|
|
2765
|
-
toast((d.updated ? '已更新:' : '已导入:') + (d.account.name || d.account.id));
|
|
3379
|
+
toast((d.updated ? T('已更新:') : T('已导入:')) + (d.account.name || d.account.id));
|
|
2766
3380
|
await loadAccounts();
|
|
2767
3381
|
localScan();
|
|
2768
3382
|
} catch (e) { toast(e.message, false); b.disabled = false; }
|
|
2769
3383
|
}));
|
|
2770
3384
|
} catch (e) {
|
|
2771
3385
|
$('scan-status').textContent = '';
|
|
2772
|
-
box.innerHTML = '<div class="hint" style="color:var(--err)">扫描失败:' + esc(e.message) + '</div>';
|
|
3386
|
+
box.innerHTML = T('<div class="hint" style="color:var(--err)">扫描失败:') + esc(e.message) + '</div>';
|
|
2773
3387
|
}
|
|
2774
3388
|
}
|
|
2775
3389
|
|
|
@@ -2793,13 +3407,13 @@ async function deviceLogin() {
|
|
|
2793
3407
|
const flow = await api('/api/auth/device/start', { json: { provider } });
|
|
2794
3408
|
$('device-box').style.display = '';
|
|
2795
3409
|
$('dev-cancel').style.display = '';
|
|
2796
|
-
const where = hasIncognito() ? '已在隐私窗口打开登录页' : '请在浏览器打开下方链接';
|
|
2797
|
-
$('dev-status').textContent = where + ',登录并授权(' + Math.floor(flow.expiresIn / 60) + ' 分钟内有效)';
|
|
3410
|
+
const where = hasIncognito() ? T('已在隐私窗口打开登录页') : T('请在浏览器打开下方链接');
|
|
3411
|
+
$('dev-status').textContent = where + T(',登录并授权(') + Math.floor(flow.expiresIn / 60) + T(' 分钟内有效)');
|
|
2798
3412
|
$('dev-link').href = flow.url;
|
|
2799
3413
|
$('dev-link').textContent = flow.url;
|
|
2800
3414
|
openAuth(flow.url);
|
|
2801
3415
|
pollDevice(flow.sessionId, 0, ++devGen);
|
|
2802
|
-
} catch (e) { toast('发起授权失败:' + e.message, false); }
|
|
3416
|
+
} catch (e) { toast(T('发起授权失败:') + e.message, false); }
|
|
2803
3417
|
}
|
|
2804
3418
|
async function pollDevice(sessionId, n, gen) {
|
|
2805
3419
|
if (gen !== devGen) return;
|
|
@@ -2807,20 +3421,24 @@ async function pollDevice(sessionId, n, gen) {
|
|
|
2807
3421
|
const r = await api('/api/auth/device/poll', { json: { sessionId } });
|
|
2808
3422
|
if (gen !== devGen) return;
|
|
2809
3423
|
if (r.status === 'pending') {
|
|
2810
|
-
if (n > 450) return deviceStop('授权超时,请重试');
|
|
2811
|
-
$('dev-status').textContent = '等待授权中…(' + n + ')';
|
|
3424
|
+
if (n > 450) return deviceStop(T('授权超时,请重试'));
|
|
3425
|
+
$('dev-status').textContent = T('等待授权中…(') + n + T(')');
|
|
2812
3426
|
devTimer = setTimeout(() => pollDevice(sessionId, n + 1, gen), 2000);
|
|
2813
3427
|
} else if (r.status === 'ok') {
|
|
2814
|
-
deviceStop('✓ 登录成功:' + (r.account.name || r.account.id) + (r.already ? '(账号已存在,已更新登录凭据)' : ''));
|
|
2815
|
-
toast(r.already ? '已更新「' + (r.account.name || r.account.id) + '」的登录凭据' : '已添加:' + (r.account.name || r.account.id));
|
|
3428
|
+
deviceStop(T('✓ 登录成功:') + (r.account.name || r.account.id) + (r.already ? T('(账号已存在,已更新登录凭据)') : ''));
|
|
3429
|
+
toast(r.already ? T('已更新「') + (r.account.name || r.account.id) + T('」的登录凭据') : T('已添加:') + (r.account.name || r.account.id));
|
|
2816
3430
|
if (r.account.id) delete state.quotas[r.account.id]; // 旧 token 的积分查询结果(可能是失败)作废,马上重查
|
|
2817
3431
|
await loadStatus(); await loadAccounts();
|
|
3432
|
+
// 网页会话 Cookie 是登录窗「关闭」时才收割绑定的(比这里的成功提示晚好几秒)——
|
|
3433
|
+
// 补两轮轻量刷新,「缺网页会话」徽章随绑定落地即时消失,不必等 30s 周期轮。
|
|
3434
|
+
setTimeout(refreshAccountList, 12000);
|
|
3435
|
+
setTimeout(refreshAccountList, 30000);
|
|
2818
3436
|
} else if (r.status === 'expired') {
|
|
2819
|
-
deviceStop('会话已过期,请重新发起');
|
|
3437
|
+
deviceStop(T('会话已过期,请重新发起'));
|
|
2820
3438
|
} else {
|
|
2821
|
-
deviceStop('授权失败:' + (r.error || r.status));
|
|
3439
|
+
deviceStop(T('授权失败:') + (r.error || r.status));
|
|
2822
3440
|
}
|
|
2823
|
-
} catch (e) { if (gen === devGen) deviceStop('轮询异常:' + e.message); }
|
|
3441
|
+
} catch (e) { if (gen === devGen) deviceStop(T('轮询异常:') + e.message); }
|
|
2824
3442
|
}
|
|
2825
3443
|
function deviceStop(msg) {
|
|
2826
3444
|
devGen++;
|
|
@@ -2832,11 +3450,11 @@ function deviceStop(msg) {
|
|
|
2832
3450
|
// 粘贴 token
|
|
2833
3451
|
async function addToken() {
|
|
2834
3452
|
const token = $('f-token').value.trim();
|
|
2835
|
-
if (!token) return toast('请粘贴 token', false);
|
|
3453
|
+
if (!token) return toast(T('请粘贴 token'), false);
|
|
2836
3454
|
const b = $('btn-add-token'); b.disabled = true;
|
|
2837
3455
|
try {
|
|
2838
3456
|
const d = await api('/api/accounts', { json: { provider: $('f-provider').value, name: $('f-name').value.trim(), token } });
|
|
2839
|
-
toast('已添加:' + (d.account.name || d.account.id) + (d.account.verified === false ? '(token 校验失败,请检查)' : ''), d.account.verified !== false);
|
|
3457
|
+
toast(T('已添加:') + (d.account.name || d.account.id) + (d.account.verified === false ? T('(token 校验失败,请检查)') : ''), d.account.verified !== false);
|
|
2840
3458
|
$('f-token').value = ''; $('f-name').value = '';
|
|
2841
3459
|
closeModal('m-add');
|
|
2842
3460
|
await loadAccounts();
|
|
@@ -2844,11 +3462,59 @@ async function addToken() {
|
|
|
2844
3462
|
b.disabled = false;
|
|
2845
3463
|
}
|
|
2846
3464
|
|
|
3465
|
+
// Qoder 卡片徽章:网页会话(Cookie)缺失或超 7 天时点名提醒——
|
|
3466
|
+
// 10Router 的「套餐内 Credits」逐资源包明细依赖它(同 qoderWebSessionNote 口径)。
|
|
3467
|
+
// 正常状态不出徽章,只在需要「浏览器登录」补 cookie 时出橙色警示,避免噪音。
|
|
3468
|
+
function qoderWebBadge(a) {
|
|
3469
|
+
if (!String(a.provider || '').startsWith('qoder')) return '';
|
|
3470
|
+
const at = a.qoderWebCapturedAt;
|
|
3471
|
+
if (at && Date.now() - new Date(at).getTime() <= 7 * 86400000) return '';
|
|
3472
|
+
const stale = Boolean(at);
|
|
3473
|
+
const proxyHint = a.provider === 'qoder' ? T('(国际版按 IP 识别地区:需先在「面板设置 → 网络出口」配置出口代理)') : '';
|
|
3474
|
+
const tip = stale
|
|
3475
|
+
? T('网页会话已超过 7 天(') + fmtTime(at).slice(0, 10) + T(' 抓取),可能已过期:导出到 10Router 后「套餐内 Credits」可能缺失,用「浏览器登录」重新登录一次 Qoder 即可补上') + proxyHint
|
|
3476
|
+
: T('缺少 Qoder 网页会话(Cookie):导出到 10Router 后「套餐内 Credits」可能缺失,用「浏览器登录」登录一次 Qoder 即可自动抓取') + proxyHint;
|
|
3477
|
+
return '<span class="badge web web-warn" title="' + esc(tip) + '">' + (stale ? T('网页会话过期') : T('缺网页会话')) + '</span>';
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
// 导出弹窗里的网页会话提示(10Router issue #44):
|
|
3481
|
+
// 10Router 的「套餐内 Credits」逐资源包明细依赖随账号导出的 Qoder 网页会话
|
|
3482
|
+
// (openapi 聚合值在部分账号上会归零)。会话缺失或明显过期 → 导出前点名提醒,
|
|
3483
|
+
// 否则用户在 10Router 端只会看到套餐积分凭空消失。
|
|
3484
|
+
function qoderWebSessionNote() {
|
|
3485
|
+
const sel = $('ex-provider').value;
|
|
3486
|
+
const list = state.accounts.filter((a) => {
|
|
3487
|
+
if (!String(a.provider || '').startsWith('qoder')) return false;
|
|
3488
|
+
if (!sel || sel === 'p:qoder') return true;
|
|
3489
|
+
return a.provider === sel;
|
|
3490
|
+
});
|
|
3491
|
+
if (!list.length) return '';
|
|
3492
|
+
const missing = [], stale = [];
|
|
3493
|
+
for (const a of list) {
|
|
3494
|
+
const at = a.qoderWebCapturedAt;
|
|
3495
|
+
if (!at) missing.push(a);
|
|
3496
|
+
else if (Date.now() - new Date(at).getTime() > 7 * 86400000) stale.push(a);
|
|
3497
|
+
}
|
|
3498
|
+
if (!missing.length && !stale.length) return '';
|
|
3499
|
+
const names = (arr) => arr.map((x) => x.name || x.email || x.uid || x.id).join(T('、'));
|
|
3500
|
+
const bits = [];
|
|
3501
|
+
if (missing.length) bits.push(T('没有网页会话:') + names(missing));
|
|
3502
|
+
if (stale.length) bits.push(T('会话已超过 7 天:') + names(stale));
|
|
3503
|
+
return T('10router 的「套餐内 Credits」逐资源包明细依赖 Qoder 网页会话(') + bits.join(T(';'))
|
|
3504
|
+
+ T(')。这些账号过去后套餐积分可能显示缺失——在「浏览器登录」重新登录一次 Qoder 再导出即可补上。');
|
|
3505
|
+
}
|
|
3506
|
+
function refreshExportNote() {
|
|
3507
|
+
const msg = qoderWebSessionNote();
|
|
3508
|
+
const el = $('ex-session-note');
|
|
3509
|
+
el.textContent = msg;
|
|
3510
|
+
el.style.display = msg ? '' : 'none';
|
|
3511
|
+
}
|
|
3512
|
+
|
|
2847
3513
|
// 导出
|
|
2848
3514
|
async function doExport() {
|
|
2849
3515
|
const password = $('ex-password').value;
|
|
2850
|
-
if (!password.trim()) return toast('导出必须设置加密口令', false);
|
|
2851
|
-
if (password.length < 4) return toast('口令至少 4 位', false);
|
|
3516
|
+
if (!password.trim()) return toast(T('导出必须设置加密口令'), false);
|
|
3517
|
+
if (password.length < 4) return toast(T('口令至少 4 位'), false);
|
|
2852
3518
|
try {
|
|
2853
3519
|
const sel = $('ex-provider').value;
|
|
2854
3520
|
const scope = sel.startsWith('p:') ? { product: sel.slice(2) } : { provider: sel || undefined };
|
|
@@ -2861,7 +3527,7 @@ async function doExport() {
|
|
|
2861
3527
|
URL.revokeObjectURL(a.href);
|
|
2862
3528
|
$('ex-password').value = '';
|
|
2863
3529
|
closeModal('m-export');
|
|
2864
|
-
toast('已导出加密文件(可在 10router 或本面板导入)');
|
|
3530
|
+
toast(T('已导出加密文件(可在 10router 或本面板导入)'));
|
|
2865
3531
|
} catch (e) { toast(e.message, false); }
|
|
2866
3532
|
}
|
|
2867
3533
|
|
|
@@ -2872,10 +3538,10 @@ $('file-input').addEventListener('change', async () => {
|
|
|
2872
3538
|
const f = $('file-input').files[0];
|
|
2873
3539
|
if (!f) return;
|
|
2874
3540
|
let data;
|
|
2875
|
-
try { data = JSON.parse(await f.text()); } catch (e) { return toast('文件不是合法 JSON', false); }
|
|
3541
|
+
try { data = JSON.parse(await f.text()); } catch (e) { return toast(T('文件不是合法 JSON'), false); }
|
|
2876
3542
|
pendingImport = data;
|
|
2877
3543
|
if (data && data.format === '10router-oauth-secure-v1') {
|
|
2878
|
-
$('pass-hint').textContent = '「' + f.name + '」已加密(CreditDaddy / 10router 迁移格式),请输入导出时设置的口令。';
|
|
3544
|
+
$('pass-hint').textContent = T('「') + f.name + T('」已加密(CreditDaddy / 10router 迁移格式),请输入导出时设置的口令。');
|
|
2879
3545
|
$('pass-input').value = '';
|
|
2880
3546
|
openModal('m-pass');
|
|
2881
3547
|
setTimeout(() => $('pass-input').focus(), 50);
|
|
@@ -2887,7 +3553,7 @@ async function submitImport(password) {
|
|
|
2887
3553
|
try {
|
|
2888
3554
|
const d = await api('/api/import', { json: { data: pendingImport, password } });
|
|
2889
3555
|
closeModal('m-pass');
|
|
2890
|
-
toast('导入完成(' + (d.source === '10router' ? '10router' : 'CreditDaddy') + '):新增 ' + d.added + ',续期 ' + d.updated + ',跳过 ' + d.skipped);
|
|
3556
|
+
toast(T('导入完成(') + (d.source === '10router' ? '10router' : 'CreditDaddy') + T('):新增 ') + d.added + T(',续期 ') + d.updated + T(',跳过 ') + d.skipped);
|
|
2891
3557
|
pendingImport = null;
|
|
2892
3558
|
await loadAccounts();
|
|
2893
3559
|
} catch (e) {
|
|
@@ -2898,7 +3564,7 @@ async function submitImport(password) {
|
|
|
2898
3564
|
|
|
2899
3565
|
// ── 绑定 ──
|
|
2900
3566
|
// 全局组件开关:收起 Qoder / WorkBuddy / ZCode / 10Router 的标签页与仪表盘对应卡片
|
|
2901
|
-
$('btn-comp').innerHTML = I.
|
|
3567
|
+
$('btn-comp').innerHTML = I.settings;
|
|
2902
3568
|
|
|
2903
3569
|
// ── 卡片拖拽排序(产品面板 + 10Router):手动顺序优先级最高,落库 localStorage ──
|
|
2904
3570
|
const dragState = { kind: null, id: null, vendor: null };
|
|
@@ -3015,13 +3681,13 @@ function applyComponents() {
|
|
|
3015
3681
|
// 右上角面板设置:访问密码 + 组件显隐与排序 + 每产品账号排序(默认积分多者靠前,可手动固定)
|
|
3016
3682
|
function secStatusHtml() {
|
|
3017
3683
|
if (!state.sec.loaded) return '<span class="sec-status">…</span>';
|
|
3018
|
-
return state.sec.enabled ? '<span class="sec-status on">已开启</span>' : '<span class="sec-status">未开启</span>';
|
|
3684
|
+
return state.sec.enabled ? T('<span class="sec-status on">已开启</span>') : T('<span class="sec-status">未开启</span>');
|
|
3019
3685
|
}
|
|
3020
3686
|
function renderSecActions() {
|
|
3021
3687
|
if (!state.sec.loaded) return '';
|
|
3022
3688
|
return state.sec.enabled
|
|
3023
|
-
? '<button class="btn" data-sec-action="change">修改密码</button><button class="btn" data-sec-action="disable">关闭</button>'
|
|
3024
|
-
: '<button class="btn primary" data-sec-action="enable">开启面板密码</button>';
|
|
3689
|
+
? T('<button class="btn" data-sec-action="change">修改密码</button><button class="btn" data-sec-action="disable">关闭</button>')
|
|
3690
|
+
: T('<button class="btn primary" data-sec-action="enable">开启面板密码</button>');
|
|
3025
3691
|
}
|
|
3026
3692
|
async function applyPanelKey(next) {
|
|
3027
3693
|
setKey(next);
|
|
@@ -3029,86 +3695,87 @@ async function applyPanelKey(next) {
|
|
|
3029
3695
|
}
|
|
3030
3696
|
async function doSecAction(action) {
|
|
3031
3697
|
if (action === 'enable' || action === 'change') {
|
|
3032
|
-
const pw = await uiPrompt(action === 'enable' ? '设置面板访问密码' : '输入新密码', '', {
|
|
3033
|
-
type: 'password', okText: action === 'enable' ? '开启' : '保存',
|
|
3034
|
-
placeholder: '至少 4 位,不能包含空格',
|
|
3035
|
-
message: '设置后每次打开面板都需要输入此密码。导出账号的加密口令是独立设置,始终必填。',
|
|
3698
|
+
const pw = await uiPrompt(action === 'enable' ? T('设置面板访问密码') : T('输入新密码'), '', {
|
|
3699
|
+
type: 'password', okText: action === 'enable' ? T('开启') : T('保存'),
|
|
3700
|
+
placeholder: T('至少 4 位,不能包含空格'),
|
|
3701
|
+
message: T('设置后每次打开面板都需要输入此密码。导出账号的加密口令是独立设置,始终必填。'),
|
|
3036
3702
|
});
|
|
3037
3703
|
if (!pw) return;
|
|
3038
3704
|
const val = String(pw).trim();
|
|
3039
|
-
if (val.length < 4) return toast('面板访问密码至少 4 位', false);
|
|
3040
|
-
if (/\s/.test(val)) return toast('面板访问密码不能包含空格', false);
|
|
3705
|
+
if (val.length < 4) return toast(T('面板访问密码至少 4 位'), false);
|
|
3706
|
+
if (/\s/.test(val)) return toast(T('面板访问密码不能包含空格'), false);
|
|
3041
3707
|
try {
|
|
3042
3708
|
const r = await api('/api/settings', { method: 'PUT', json: { panelKey: val } });
|
|
3043
3709
|
await applyPanelKey(val);
|
|
3044
3710
|
renderCompMenu();
|
|
3045
|
-
toast(r.panelKeyEnabled ? '已设置面板访问密码,下次打开面板需要输入' : '已保存', true);
|
|
3711
|
+
toast(r.panelKeyEnabled ? T('已设置面板访问密码,下次打开面板需要输入') : T('已保存'), true);
|
|
3046
3712
|
} catch (e) { toast(e.message, false); }
|
|
3047
3713
|
return;
|
|
3048
3714
|
}
|
|
3049
3715
|
if (action === 'disable') {
|
|
3050
3716
|
const ok = await uiConfirm(
|
|
3051
|
-
'关闭后打开面板不再需要密码(局域网 / 公网访问将无保护,仅建议本机使用)。账号导出仍必须设置加密口令。\n\n确定关闭面板访问密码吗?',
|
|
3052
|
-
'关闭面板访问密码', '关闭');
|
|
3717
|
+
T('关闭后打开面板不再需要密码(局域网 / 公网访问将无保护,仅建议本机使用)。账号导出仍必须设置加密口令。\n\n确定关闭面板访问密码吗?'),
|
|
3718
|
+
T('关闭面板访问密码'), T('关闭'));
|
|
3053
3719
|
if (!ok) return;
|
|
3054
3720
|
try {
|
|
3055
3721
|
const r = await api('/api/settings', { method: 'PUT', json: { disable: true } });
|
|
3056
3722
|
await loadSettings();
|
|
3057
3723
|
await applyPanelKey(r.panelKeyEnabled ? getKey() : '');
|
|
3058
3724
|
renderCompMenu();
|
|
3059
|
-
toast(r.panelKeyEnabled ? '该部署环境(fnOS / 环境变量)强制启用访问密码,无法在面板内关闭' : '已关闭面板访问密码', true);
|
|
3725
|
+
toast(r.panelKeyEnabled ? T('该部署环境(fnOS / 环境变量)强制启用访问密码,无法在面板内关闭') : T('已关闭面板访问密码'), true);
|
|
3060
3726
|
} catch (e) { toast(e.message, false); }
|
|
3061
3727
|
}
|
|
3062
3728
|
}
|
|
3063
3729
|
function renderCompMenu() {
|
|
3064
|
-
|
|
3730
|
+
// 组件顺序横向 chips:一行放下全部产品,勾选 = 显隐,拖动 = 前后移(顺序与标签栏同步)
|
|
3731
|
+
const compChips = state.compOrder.map((id) => {
|
|
3065
3732
|
const c = COMPONENTS.find((x) => x.id === id);
|
|
3066
3733
|
if (!c) return '';
|
|
3067
3734
|
const vis = compVisible(id);
|
|
3068
|
-
return '<
|
|
3069
|
-
+ '
|
|
3070
|
-
+ '<span class="
|
|
3071
|
-
+ '<
|
|
3072
|
-
+ '
|
|
3073
|
-
+ '</span></div>';
|
|
3735
|
+
return '<span class="comp-chip' + (vis ? '' : ' off') + '" draggable="true" data-drag-comp="' + id + '" data-comp-toggle="' + id + '"'
|
|
3736
|
+
+ ' title="' + T('点击切换显隐') + ' · ' + T('按住拖动调整顺序') + '">'
|
|
3737
|
+
+ T('<span class="drag-grip">') + I.grip + '</span>'
|
|
3738
|
+
+ '<span class="sort-name">' + esc(T(c.label)) + '</span>'
|
|
3739
|
+
+ '</span>';
|
|
3074
3740
|
}).join('');
|
|
3075
3741
|
const sortSections = PRODUCTS.filter((p) => compVisible(p.id) && ofProduct(p.id).length).map((p) => {
|
|
3076
3742
|
const manual = state.cardOrder[p.id];
|
|
3077
|
-
const rows = sortedAccounts(p.id, ofProduct(p.id)).map((a) => '<
|
|
3078
|
-
+ '<span class="
|
|
3079
|
-
+ '<span class="
|
|
3080
|
-
+ '
|
|
3081
|
-
|
|
3082
|
-
+ '
|
|
3083
|
-
return '<details class="sort-sec"' + (manual && manual.length ? ' open' : '') + '><summary>' + esc(p.label)
|
|
3084
|
-
+ (manual && manual.length ? ' <span class="badge">手动排序</span>' : '')
|
|
3743
|
+
const rows = sortedAccounts(p.id, ofProduct(p.id)).map((a) => '<span class="comp-chip" draggable="true" data-drag-acc="' + esc(a.id) + '" data-prod="' + p.id + '" title="' + esc(a.name || a.email || a.uid || a.id) + '">'
|
|
3744
|
+
+ T('<span class="drag-grip" title="按住拖动调整顺序">') + I.grip + '</span>'
|
|
3745
|
+
+ '<span class="sort-name">' + esc(a.name || a.email || ('#' + a.id.slice(0, 6))) + '</span>'
|
|
3746
|
+
+ '</span>').join('');
|
|
3747
|
+
return '<details class="sort-sec"' + (manual && manual.length ? ' open' : '') + '><summary>' + esc(T(p.label))
|
|
3748
|
+
+ (manual && manual.length ? T(' <span class="badge">手动排序</span>') : '')
|
|
3085
3749
|
+ '<span class="dim" style="font-size:11px">' + ofProduct(p.id).length + '</span></summary>'
|
|
3086
|
-
+ '<div class="sort-hint"
|
|
3087
|
-
+ rows
|
|
3088
|
-
+ (manual && manual.length ? '<button class="btn" data-acc-reset="' + p.id + '" style="margin-top:6px
|
|
3750
|
+
+ T('<div class="sort-hint">默认按剩余积分从多到少;直接按住拖动卡片调整顺序。</div>')
|
|
3751
|
+
+ '<div class="comp-chips">' + rows + '</div>'
|
|
3752
|
+
+ (manual && manual.length ? '<button class="btn" data-acc-reset="' + p.id + T('" style="margin-top:6px">恢复默认排序</button>') : '')
|
|
3089
3753
|
+ '</details>';
|
|
3090
3754
|
}).join('');
|
|
3755
|
+
$('sec-block').innerHTML =
|
|
3756
|
+
secStatusHtml()
|
|
3757
|
+
+ T('<div class="hint">开启后每次打开面板都需要输入密码;密码可随时在此修改或关闭。')
|
|
3758
|
+
+ (state.sec.enabled ? '' : T('账号导出不受影响(导出口令始终必填)。')) + '</div>'
|
|
3759
|
+
+ '<div class="row">' + renderSecActions() + '</div>';
|
|
3091
3760
|
$('comp-menu').innerHTML =
|
|
3092
|
-
'<div class="comp-menu-title"
|
|
3093
|
-
+ '<div class="hint">开启后每次打开面板都需要输入密码;密码可随时在此修改或关闭。'
|
|
3094
|
-
+ (state.sec.enabled ? '' : '账号导出不受影响(导出口令始终必填)。') + '</div>'
|
|
3095
|
-
+ '<div class="row">' + renderSecActions() + '</div>'
|
|
3096
|
-
+ '<div class="comp-menu-title">自动签到</div>'
|
|
3761
|
+
T('<div class="comp-menu-title">自动签到</div>')
|
|
3097
3762
|
+ '<div class="hint">' + autoCheckinHint() + '</div>'
|
|
3098
|
-
+ '<div class="comp-menu-title">组件顺序(仪表盘与标签栏同步)</div>'
|
|
3099
|
-
+ '<div class="
|
|
3100
|
-
+
|
|
3763
|
+
+ T('<div class="comp-menu-title">组件顺序(仪表盘与标签栏同步)</div>')
|
|
3764
|
+
+ T('<div class="sort-hint">点按切换显隐(置灰 = 隐藏);按住拖动调整顺序。</div>')
|
|
3765
|
+
+ '<div class="comp-chips">' + compChips + '</div>'
|
|
3766
|
+
+ T('<div class="comp-menu-title">账号卡片排序</div>')
|
|
3767
|
+
+ (sortSections || T('<div class="sort-hint">暂无账号</div>'));
|
|
3101
3768
|
}
|
|
3102
3769
|
|
|
3103
3770
|
/** 自动签到口径说明:调度周期 + 各产品业务日刷新时点 */
|
|
3104
3771
|
function autoCheckinHint() {
|
|
3105
3772
|
const sch = state.status && state.status.scheduler;
|
|
3106
|
-
const next = sch && sch.nextTickAt ? ',下次执行 ' + esc(String(sch.nextTickAt).slice(11, 16)) : '';
|
|
3773
|
+
const next = sch && sch.nextTickAt ? T(',下次执行 ') + esc(String(sch.nextTickAt).slice(11, 16)) : '';
|
|
3107
3774
|
const zc = sch && sch.zcode;
|
|
3108
3775
|
const zcText = zc && zc.enabled
|
|
3109
|
-
? 'ZCode 自动领取运行中(每 ' + (zc.intervalMin || 2) + ' 分钟' + (zc.autoOffAt ? ',约 ' + Math.max(1, Math.ceil((new Date(zc.autoOffAt).getTime() - Date.now()) / 60000)) + ' 分钟后自动关闭' : ',一直运行') + '),周期可在 ZCode 页「自动领取计划」里改'
|
|
3110
|
-
: 'ZCode 活动领取默认关闭,可在 ZCode 页「自动领取计划」里开启并配置轮询周期';
|
|
3111
|
-
return '守护进程每 ~2 小时自动扫描执行一轮全部账号' + next + '。各产品「业务日」刷新时点(UTC+8):<b>Qoder 每日 10:00</b>(每台设备每日限领一个国际版账号)、<b>WorkBuddy 每日 00:00</b>,其余产品按 00:00;已领成功的账号按对应业务日记忆跳过,跨过刷新点后当天照常重试。' + esc(zcText) + '。';
|
|
3776
|
+
? T('ZCode 自动领取运行中(每 ') + (zc.intervalMin || 2) + T(' 分钟') + (zc.autoOffAt ? T(',约 ') + Math.max(1, Math.ceil((new Date(zc.autoOffAt).getTime() - Date.now()) / 60000)) + T(' 分钟后自动关闭') : T(',一直运行')) + T('),周期可在 ZCode 页「自动领取计划」里改')
|
|
3777
|
+
: T('ZCode 活动领取默认关闭,可在 ZCode 页「自动领取计划」里开启并配置轮询周期');
|
|
3778
|
+
return T('守护进程每 ~2 小时自动扫描执行一轮全部账号') + next + T('。各产品「业务日」刷新时点(UTC+8):<b>Qoder 每日 10:00</b>(每台设备每日限领一个国际版账号)、<b>WorkBuddy 每日 00:00</b>,其余产品按 00:00;已领成功的账号按对应业务日记忆跳过,跨过刷新点后当天照常重试。') + esc(zcText) + T('。');
|
|
3112
3779
|
}
|
|
3113
3780
|
|
|
3114
3781
|
/** 在手动排序列表里移动一个 id;列表不存在时先按当前生效顺序物化一份 */
|
|
@@ -3124,49 +3791,45 @@ function moveAccountId(prod, aid, dir) {
|
|
|
3124
3791
|
saveCardOrder();
|
|
3125
3792
|
}
|
|
3126
3793
|
// 右上角齿轮 → 面板设置弹窗(访问密码 + 组件显隐/排序 + 账号卡片排序)
|
|
3794
|
+
// 面板设置分页签:通用 / 网络 / 组件与排序(内容越来越长的解法——分 pane 而不是一条流水账)
|
|
3795
|
+
$('set-tabs').addEventListener('click', (e) => {
|
|
3796
|
+
const b = e.target.closest('button[data-set-tab]');
|
|
3797
|
+
if (!b) return;
|
|
3798
|
+
document.querySelectorAll('#set-tabs .tab').forEach((x) => x.classList.toggle('active', x === b));
|
|
3799
|
+
document.querySelectorAll('#m-settings .pane[data-set-pane]').forEach((p) => p.classList.toggle('active', p.dataset.setPane === b.dataset.setTab));
|
|
3800
|
+
});
|
|
3127
3801
|
$('btn-comp').addEventListener('click', async () => {
|
|
3802
|
+
// 已打开再点齿轮 = 关闭(普通弹出层的开合直觉)
|
|
3803
|
+
if ($('m-settings').classList.contains('show')) { closeModal('m-settings'); return; }
|
|
3128
3804
|
renderCompMenu();
|
|
3129
3805
|
openModal('m-settings');
|
|
3806
|
+
// 每次打开都回到「通用」页签
|
|
3807
|
+
document.querySelectorAll('#set-tabs .tab').forEach((x) => x.classList.toggle('active', x.dataset.setTab === 'net'));
|
|
3808
|
+
document.querySelectorAll('#m-settings .pane[data-set-pane]').forEach((p) => p.classList.toggle('active', p.dataset.setPane === 'net'));
|
|
3130
3809
|
await loadSettings();
|
|
3131
3810
|
renderCompMenu();
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
$('
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
if (e.target.checked) state.hidden.delete(c); else state.hidden.add(c);
|
|
3142
|
-
saveHiddenComps();
|
|
3143
|
-
applyComponents();
|
|
3144
|
-
renderCompMenu();
|
|
3811
|
+
fillAutoCheckin();
|
|
3812
|
+
$('cfg-logs-display').value = logsDisplayDefault();
|
|
3813
|
+
$('cfg-wb-activity').checked = wbActOn();
|
|
3814
|
+
// 网络出口(全局):预填当前顺序与脱敏代理地址
|
|
3815
|
+
await loadZNet();
|
|
3816
|
+
(state.zProxyFirst === true ? $('znet-proxy') : $('znet-direct')).checked = true;
|
|
3817
|
+
$('znet-url').value = '';
|
|
3818
|
+
$('znet-url').placeholder = state.zProxyUrl || (state.zHasEnvProxy ? T('跟随环境变量 HTTPS_PROXY') : 'http://192.168.31.101:7890');
|
|
3819
|
+
$('znet-state').textContent = '';
|
|
3145
3820
|
});
|
|
3146
3821
|
$('comp-menu').addEventListener('click', (e) => {
|
|
3147
|
-
const
|
|
3148
|
-
if (
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
const id = move.dataset.comp;
|
|
3153
|
-
const i = state.compOrder.indexOf(id);
|
|
3154
|
-
const j = move.dataset.move === 'up' ? i - 1 : i + 1;
|
|
3155
|
-
if (i < 0 || j < 0 || j >= state.compOrder.length) return;
|
|
3156
|
-
[state.compOrder[i], state.compOrder[j]] = [state.compOrder[j], state.compOrder[i]];
|
|
3157
|
-
saveCompOrder();
|
|
3822
|
+
const toggle = e.target.closest && e.target.closest('[data-comp-toggle]');
|
|
3823
|
+
if (toggle && Date.now() >= suppressToggleUntil) {
|
|
3824
|
+
const c = toggle.dataset.compToggle;
|
|
3825
|
+
if (state.hidden.has(c)) state.hidden.delete(c); else state.hidden.add(c);
|
|
3826
|
+
saveHiddenComps();
|
|
3158
3827
|
applyComponents();
|
|
3159
3828
|
renderCompMenu();
|
|
3160
3829
|
return;
|
|
3161
3830
|
}
|
|
3162
|
-
const
|
|
3163
|
-
if (
|
|
3164
|
-
e.stopPropagation();
|
|
3165
|
-
moveAccountId(accMove.dataset.prod, accMove.dataset.aid, accMove.dataset.accMove);
|
|
3166
|
-
render();
|
|
3167
|
-
renderCompMenu();
|
|
3168
|
-
return;
|
|
3169
|
-
}
|
|
3831
|
+
const secBtn = e.target.closest && e.target.closest('[data-sec-action]');
|
|
3832
|
+
if (secBtn) { doSecAction(secBtn.dataset.secAction); return; }
|
|
3170
3833
|
const reset = e.target.closest && e.target.closest('[data-acc-reset]');
|
|
3171
3834
|
if (reset) {
|
|
3172
3835
|
e.stopPropagation();
|
|
@@ -3176,30 +3839,100 @@ $('comp-menu').addEventListener('click', (e) => {
|
|
|
3176
3839
|
renderCompMenu();
|
|
3177
3840
|
}
|
|
3178
3841
|
});
|
|
3179
|
-
|
|
3842
|
+
|
|
3843
|
+
// ── 拖拽重排:组件与账号卡片共用(HTML5 DnD,横向 chip 按中线决定插前/插后)──
|
|
3844
|
+
let dragCtx = null; // {type:'comp'|'acc', id, prod}
|
|
3845
|
+
let suppressToggleUntil = 0; // 拖放结束瞬-ms 内不触发显隐切换(防止拖着拖着被当成点击)
|
|
3846
|
+
const clearDropMarks = () => document.querySelectorAll('.comp-chip.drop-before, .comp-chip.drop-after, .comp-chip.dragging')
|
|
3847
|
+
.forEach((x) => x.classList.remove('drop-before', 'drop-after', 'dragging'));
|
|
3848
|
+
$('comp-menu').addEventListener('dragstart', (e) => {
|
|
3849
|
+
const compEl = e.target.closest('[data-drag-comp]');
|
|
3850
|
+
const accEl = e.target.closest('[data-drag-acc]');
|
|
3851
|
+
dragCtx = compEl ? { type: 'comp', id: compEl.dataset.dragComp }
|
|
3852
|
+
: accEl ? { type: 'acc', prod: accEl.dataset.prod, id: accEl.dataset.dragAcc } : null;
|
|
3853
|
+
if (!dragCtx) return;
|
|
3854
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
3855
|
+
try { e.dataTransfer.setData('text/plain', dragCtx.id); } catch {}
|
|
3856
|
+
(compEl || accEl).classList.add('dragging');
|
|
3857
|
+
});
|
|
3858
|
+
$('comp-menu').addEventListener('dragend', () => { dragCtx = null; suppressToggleUntil = Date.now() + 150; clearDropMarks(); });
|
|
3859
|
+
$('comp-menu').addEventListener('dragover', (e) => {
|
|
3860
|
+
if (!dragCtx) return;
|
|
3861
|
+
const sel = dragCtx.type === 'comp' ? '[data-drag-comp]' : '[data-drag-acc]';
|
|
3862
|
+
const t = e.target.closest(sel);
|
|
3863
|
+
if (!t) { e.dataTransfer.dropEffect = 'none'; return; }
|
|
3864
|
+
if (dragCtx.type === 'acc' && t.dataset.prod !== dragCtx.prod) { e.dataTransfer.dropEffect = 'none'; return; }
|
|
3865
|
+
e.preventDefault();
|
|
3866
|
+
e.dataTransfer.dropEffect = 'move';
|
|
3867
|
+
const r = t.getBoundingClientRect();
|
|
3868
|
+
const before = e.clientX < r.left + r.width / 2;
|
|
3869
|
+
t.classList.toggle('drop-before', before);
|
|
3870
|
+
t.classList.toggle('drop-after', !before);
|
|
3871
|
+
});
|
|
3872
|
+
$('comp-menu').addEventListener('dragleave', (e) => {
|
|
3873
|
+
const t = e.target.closest('.comp-chip');
|
|
3874
|
+
if (t) t.classList.remove('drop-before', 'drop-after');
|
|
3875
|
+
});
|
|
3876
|
+
$('comp-menu').addEventListener('drop', (e) => {
|
|
3877
|
+
if (!dragCtx) return;
|
|
3878
|
+
const sel = dragCtx.type === 'comp' ? '[data-drag-comp]' : '[data-drag-acc]';
|
|
3879
|
+
const t = e.target.closest(sel);
|
|
3880
|
+
clearDropMarks();
|
|
3881
|
+
if (!t) { dragCtx = null; return; }
|
|
3882
|
+
e.preventDefault();
|
|
3883
|
+
const r = t.getBoundingClientRect();
|
|
3884
|
+
const after = e.clientX >= r.left + r.width / 2;
|
|
3885
|
+
if (dragCtx.type === 'comp') {
|
|
3886
|
+
const arr = state.compOrder.filter((id) => id !== dragCtx.id);
|
|
3887
|
+
let idx = arr.indexOf(t.dataset.dragComp);
|
|
3888
|
+
if (idx < 0) { dragCtx = null; return; }
|
|
3889
|
+
if (after) idx += 1;
|
|
3890
|
+
arr.splice(idx, 0, dragCtx.id);
|
|
3891
|
+
state.compOrder = arr;
|
|
3892
|
+
saveCompOrder();
|
|
3893
|
+
applyComponents();
|
|
3894
|
+
} else {
|
|
3895
|
+
const baseIds = sortedAccounts(dragCtx.prod, ofProduct(dragCtx.prod)).map((a) => a.id);
|
|
3896
|
+
const arr = (state.cardOrder[dragCtx.prod] && state.cardOrder[dragCtx.prod].length ? state.cardOrder[dragCtx.prod].slice() : baseIds)
|
|
3897
|
+
.filter((id) => id !== dragCtx.id);
|
|
3898
|
+
let idx = arr.indexOf(t.dataset.dragAcc);
|
|
3899
|
+
if (idx < 0) { dragCtx = null; return; }
|
|
3900
|
+
if (after) idx += 1;
|
|
3901
|
+
arr.splice(idx, 0, dragCtx.id);
|
|
3902
|
+
state.cardOrder[dragCtx.prod] = arr;
|
|
3903
|
+
saveCardOrder();
|
|
3904
|
+
}
|
|
3905
|
+
suppressToggleUntil = Date.now() + 150;
|
|
3906
|
+
dragCtx = null;
|
|
3907
|
+
renderCompMenu();
|
|
3908
|
+
});
|
|
3909
|
+
|
|
3910
|
+
applyComponents();
|
|
3180
3911
|
renderCompMenu();
|
|
3181
3912
|
|
|
3182
|
-
$('btn-checkin-all').innerHTML = I.play + '全部领取';
|
|
3183
|
-
for (const id of ['btn-add', 'btn-add-dash']) $(id).innerHTML = I.plus + '添加账号';
|
|
3184
|
-
for (const id of ['btn-import', 'btn-import-dash']) { $(id).innerHTML = I.upload + '导入'; $(id).onclick = pickImport; }
|
|
3185
|
-
for (const id of ['btn-export', 'btn-export-dash']) $(id).innerHTML = I.download + '导出';
|
|
3913
|
+
$('btn-checkin-all').innerHTML = I.play + T('全部领取');
|
|
3914
|
+
for (const id of ['btn-add', 'btn-add-dash']) $(id).innerHTML = I.plus + T('添加账号');
|
|
3915
|
+
for (const id of ['btn-import', 'btn-import-dash']) { $(id).innerHTML = I.upload + T('导入'); $(id).onclick = pickImport; }
|
|
3916
|
+
for (const id of ['btn-export', 'btn-export-dash']) $(id).innerHTML = I.download + T('导出');
|
|
3186
3917
|
$('btn-checkin-all').onclick = () => checkinAll($('btn-checkin-all'));
|
|
3187
3918
|
$('btn-checkin').onclick = () => checkinAll($('btn-checkin'), state.view);
|
|
3188
3919
|
$('btn-add-dash').onclick = () => openAdd('');
|
|
3189
3920
|
$('btn-add').onclick = () => openAdd(state.view);
|
|
3190
|
-
$('btn-export-dash').onclick = () => { $('ex-provider').value = ''; openModal('m-export'); };
|
|
3921
|
+
$('btn-export-dash').onclick = () => { $('ex-provider').value = ''; refreshExportNote(); openModal('m-export'); };
|
|
3191
3922
|
$('btn-export').onclick = () => {
|
|
3192
3923
|
const v = state.view;
|
|
3193
3924
|
// ZCode 是单一 provider,直接按 provider 选;其余产品页按产品线(p:);仪表盘 / 10Router 导出全部
|
|
3194
3925
|
$('ex-provider').value = v === 'zcode' ? 'zcode' : (v === 'qoder' || v === 'workbuddy' || v === 'mirasim' || v === 'catpaw' || v === 'trae' || v === 'minimax') ? 'p:' + v : '';
|
|
3926
|
+
refreshExportNote();
|
|
3195
3927
|
openModal('m-export');
|
|
3196
3928
|
};
|
|
3929
|
+
$('ex-provider').addEventListener('change', refreshExportNote);
|
|
3197
3930
|
$('btn-do-export').onclick = doExport;
|
|
3198
3931
|
$('btn-pass-ok').onclick = () => submitImport($('pass-input').value);
|
|
3199
3932
|
$('pass-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') submitImport($('pass-input').value); });
|
|
3200
3933
|
$('btn-scan').onclick = localScan;
|
|
3201
3934
|
$('btn-device').onclick = deviceLogin;
|
|
3202
|
-
$('dev-cancel').onclick = () => deviceStop('已取消');
|
|
3935
|
+
$('dev-cancel').onclick = () => deviceStop(T('已取消'));
|
|
3203
3936
|
$('dev-link').addEventListener('click', (e) => { e.preventDefault(); openAuth($('dev-link').href); });
|
|
3204
3937
|
$('btn-add-token').onclick = addToken;
|
|
3205
3938
|
$('f-token').addEventListener('keydown', (e) => { if (e.key === 'Enter') addToken(); });
|
|
@@ -3220,6 +3953,7 @@ function applyView() {
|
|
|
3220
3953
|
$('view-10r').classList.toggle('active', state.view === 'tenrouter');
|
|
3221
3954
|
if (state.view === 'tenrouter') loadZGateway();
|
|
3222
3955
|
if (state.view === 'minimax') loadMGateway();
|
|
3956
|
+
if (state.view === 'trae') loadTGateway();
|
|
3223
3957
|
if (state.view === 'tenrouter' && tr.config && tr.config.configured && !tr.quotas && !tr.loading) loadTrQuotas();
|
|
3224
3958
|
followLogTab();
|
|
3225
3959
|
applyLogTabs();
|
|
@@ -3247,15 +3981,16 @@ $('region').addEventListener('click', (e) => {
|
|
|
3247
3981
|
renderStatus();
|
|
3248
3982
|
});
|
|
3249
3983
|
$('f-provider').addEventListener('change', () => {
|
|
3250
|
-
$('f-token').placeholder = $('f-provider').value
|
|
3251
|
-
? 'MiniMax OAuth Bearer Token(mmoat_…)'
|
|
3984
|
+
$('f-token').placeholder = $('f-provider').value.startsWith('minimax')
|
|
3985
|
+
? T('MiniMax OAuth Bearer Token(mmoat_…)')
|
|
3252
3986
|
: $('f-provider').value.startsWith('workbuddy')
|
|
3253
|
-
? 'WorkBuddy / CodeBuddy 登录 token(eyJ…,可在 CodeBuddyExtension 的 auth/*.info 中找到)'
|
|
3254
|
-
: 'dt-… / jt-… 或 PAT(pt-…,自动兑换)';
|
|
3987
|
+
? T('WorkBuddy / CodeBuddy 登录 token(eyJ…,可在 CodeBuddyExtension 的 auth/*.info 中找到)')
|
|
3988
|
+
: T('dt-… / jt-… 或 PAT(pt-…,自动兑换)');
|
|
3255
3989
|
});
|
|
3256
3990
|
$('btn-logs').onclick = () => {
|
|
3257
3991
|
const c = $('log-card').classList.toggle('collapsed');
|
|
3258
|
-
|
|
3992
|
+
try { localStorage.setItem('qd_logs_expanded', c ? '0' : '1'); } catch {}
|
|
3993
|
+
$('btn-logs').textContent = c ? T('展开') : T('收起');
|
|
3259
3994
|
if (!c) { loadCurrentLogs(); $('logs').scrollTop = $('logs').scrollHeight; }
|
|
3260
3995
|
};
|
|
3261
3996
|
$('log-tabs').addEventListener('click', (e) => {
|
|
@@ -3267,6 +4002,14 @@ $('log-tabs').addEventListener('click', (e) => {
|
|
|
3267
4002
|
loadCurrentLogs();
|
|
3268
4003
|
$('logs').scrollTop = $('logs').scrollHeight;
|
|
3269
4004
|
});
|
|
4005
|
+
$('log-filter').addEventListener('click', (e) => {
|
|
4006
|
+
const b = e.target.closest('button[data-log-level]');
|
|
4007
|
+
if (!b) return;
|
|
4008
|
+
state.logLevel = b.dataset.logLevel;
|
|
4009
|
+
applyLogTabs();
|
|
4010
|
+
loadCurrentLogs();
|
|
4011
|
+
$('logs').scrollTop = $('logs').scrollHeight;
|
|
4012
|
+
});
|
|
3270
4013
|
|
|
3271
4014
|
// ── 仪表盘底部两卡:默认收起,点标题展开,展开状态存本地 ──
|
|
3272
4015
|
const DASH_FOLD_KEY = 'qd_dash_fold';
|
|
@@ -3279,7 +4022,7 @@ function applyDashFold() {
|
|
|
3279
4022
|
});
|
|
3280
4023
|
}
|
|
3281
4024
|
document.querySelectorAll('.dash-cols .fold-head').forEach((h) => {
|
|
3282
|
-
h.title = '展开 / 收起';
|
|
4025
|
+
h.title = T('展开 / 收起');
|
|
3283
4026
|
h.addEventListener('click', () => {
|
|
3284
4027
|
const k = h.closest('.pcard').dataset.fold;
|
|
3285
4028
|
state.dashFold[k] = state.dashFold[k] !== true;
|
|
@@ -3291,15 +4034,20 @@ applyDashFold();
|
|
|
3291
4034
|
|
|
3292
4035
|
// ── 启动:先直接请求(本机模式无需密钥);只有服务端返回 401 时才询问密钥 ──
|
|
3293
4036
|
initTheme();
|
|
3294
|
-
$('btn-home').innerHTML = I.github;
|
|
3295
4037
|
applyView();
|
|
4038
|
+
function hideBootBusy() { const b = $('boot-busy'); if (!b) return; clearTimeout(hideBootBusy._t); b.classList.add('fade'); setTimeout(() => b.remove(), 300); }
|
|
3296
4039
|
(function boot() {
|
|
3297
4040
|
function start() {
|
|
3298
|
-
|
|
4041
|
+
// 兜底:接口长时间不返回也不能让加载态永久盖着
|
|
4042
|
+
hideBootBusy._t = setTimeout(hideBootBusy, 9000);
|
|
4043
|
+
applyDictFromServer().then(() => {
|
|
4044
|
+
window.__syncTheme?.(); // 头部主题按钮的图标/标题在字典到达后重渲染(initTheme 时字典还没到,T() 当时只能回落中文)
|
|
4045
|
+
loadStatus().then(loadAccounts).then(hideBootBusy, (e) => { toast(e.message, false); hideBootBusy(); });
|
|
3299
4046
|
loadSettings().then(renderCompMenu);
|
|
3300
4047
|
loadZNet();
|
|
3301
4048
|
loadZGateway();
|
|
3302
4049
|
loadMGateway();
|
|
4050
|
+
loadTGateway();
|
|
3303
4051
|
loadTrConfig().then(() => loadTrQuotas());
|
|
3304
4052
|
setInterval(() => loadTrQuotas(), 5 * 60 * 1000);
|
|
3305
4053
|
// 账号余额(ZCode 剩余 token 等)自动刷新:可见时每 2 分钟一轮;从托盘/后台切回时若超过 1 分钟没刷过立即补一轮
|
|
@@ -3309,12 +4057,15 @@ applyView();
|
|
|
3309
4057
|
});
|
|
3310
4058
|
loadLogs();
|
|
3311
4059
|
setInterval(loadLogs, 10000);
|
|
3312
|
-
setInterval(async () => { await loadStatus(); await loadZNet(); await loadZGateway(); await loadMGateway(); render(); }, 30000);
|
|
4060
|
+
setInterval(async () => { await loadStatus(); await loadZNet(); await loadZGateway(); await loadMGateway(); await loadTGateway(); await refreshAccountList(); render(); }, 30000);
|
|
4061
|
+
});
|
|
3313
4062
|
}
|
|
3314
4063
|
// 先直接请求(本机模式无需密码);需要密码时 api() 会弹框并重试
|
|
3315
|
-
api('/api/status').then(start).catch((e) => toast(e.code === 'PANEL_KEY'
|
|
3316
|
-
? '未输入面板访问密码,刷新页面可重试' : '无法连接守护进程:' + e.message, false));
|
|
4064
|
+
api('/api/status').then(start).catch((e) => { hideBootBusy(); toast(e.code === 'PANEL_KEY'
|
|
4065
|
+
? T('未输入面板访问密码,刷新页面可重试') : T('无法连接守护进程:') + e.message, false); });
|
|
3317
4066
|
})();
|
|
3318
4067
|
</script>
|
|
4068
|
+
<!-- 语言切换加载态 -->
|
|
4069
|
+
<div id="locale-busy" hidden><div class="lb-card"><span class="lb-spin"></span><span data-i18n="正在切换语言…">正在切换语言…</span></div></div>
|
|
3319
4070
|
</body>
|
|
3320
4071
|
</html>
|