balance-dsh 2.0.6 → 2.0.7
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/lib/client.js +303 -80
- package/lib/index.js +10 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -9,6 +9,13 @@ window.__ModuleLoader__.load({
|
|
|
9
9
|
let jsx = require("react/jsx-runtime");
|
|
10
10
|
let primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
11
11
|
|
|
12
|
+
// 余额滚轮要"这一帧就挂上滚动结构",不能等 useEffect(它在 paint 之后跑),
|
|
13
|
+
// 否则余额一变会先渲染出一帧纯文本新值、下一帧才换成滚动格 —— 看起来就是
|
|
14
|
+
// "数字比动画先出来"(2026-10-30 用户反馈)。优先用 useLayoutEffect(paint 前同步),
|
|
15
|
+
// 老 React 没有就退回 useEffect(纯静态 stub 环境也不至于炸)。
|
|
16
|
+
const usePrePaintEffect =
|
|
17
|
+
react && typeof react.useLayoutEffect === "function" ? react.useLayoutEffect : (react && react.useEffect);
|
|
18
|
+
|
|
12
19
|
// ─── 样式 ────────────────────────────────────────────────────────────────
|
|
13
20
|
const css = `
|
|
14
21
|
/* 兜底定义主题过渡变量:上游 deepsuite theme/global.css 未加载时,
|
|
@@ -39,6 +46,14 @@ window.__ModuleLoader__.load({
|
|
|
39
46
|
.balance-footer__roll{display:inline-flex;align-items:baseline;white-space:pre;flex:none}
|
|
40
47
|
.balance-footer__dcell{display:inline-block;vertical-align:bottom;white-space:pre}
|
|
41
48
|
.balance-footer__dcell--roll{position:relative;overflow:hidden}
|
|
49
|
+
/* 位数减少时(11.04 → 8.04),最左边那一格的「新值」是空位。它得先把旧数字滚出去,
|
|
50
|
+
再把自己收成 0 宽 —— 否则滚动结束后那一格残留的宽度把胶囊撑宽 10px,
|
|
51
|
+
要等 setRoll(null) 才缩回,看起来就是数字左右跳一下。
|
|
52
|
+
收窄过程中 dout/din 是 absolute,会被 dcell 的 overflow:hidden 裁掉,不会和邻格重叠。
|
|
53
|
+
影子格必须保持 NBSP 而不是 "0":这一格要在动画期间维持原来的宽度,
|
|
54
|
+
真正让它消失的是这条 max-width 动画,不是字符宽度。 */
|
|
55
|
+
.balance-footer__dcell--gone{animation:balance-cell-gone .42s cubic-bezier(.33,.66,.24,1) forwards}
|
|
56
|
+
@keyframes balance-cell-gone{from{max-width:2ch}to{max-width:0}}
|
|
42
57
|
.balance-footer__dghost{display:block;visibility:hidden;white-space:pre}
|
|
43
58
|
.balance-footer__dout,.balance-footer__din{position:absolute;left:0;top:0;white-space:pre;will-change:transform,opacity}
|
|
44
59
|
.balance-footer__dout{animation:balance-roll-out .42s cubic-bezier(.33,.66,.24,1) forwards}
|
|
@@ -51,7 +66,7 @@ window.__ModuleLoader__.load({
|
|
|
51
66
|
只扫一层白光,**不覆盖扫过区域**——曾经有一个 ::after 暗罩把白条右侧整体压暗,
|
|
52
67
|
视觉上等于给扫过的部分蒙了层色,已删除(2026-10-30 反馈「不要为扫过区域的颜色覆盖了」)。
|
|
53
68
|
现在动效 = 一条白光从左走到右,走完即止,行内颜色全程不变。 */
|
|
54
|
-
.balance-footer__wave::before{content:"";position:absolute;top:1px;bottom:1px;left:0;width:
|
|
69
|
+
.balance-footer__wave::before{content:"";position:absolute;top:1px;bottom:1px;left:0;width:6px;margin-left:-3px;border-radius:999px;corner-shape:round;background:#fff;box-shadow:0 0 3px 1px rgba(255,255,255,.95),0 0 8px 3px rgba(255,255,255,.42),0 0 14px 5px rgba(255,255,255,.16);animation:balance-wipe-head .58s cubic-bezier(.34,.7,.26,1) forwards}
|
|
55
70
|
@keyframes balance-wipe-head{0%{left:0;opacity:0}5%{opacity:1}82%{opacity:1}100%{left:100%;opacity:0}}
|
|
56
71
|
/* 尊重系统的「减少动态效果」:不播,只留 --stale 的静态弱化 */
|
|
57
72
|
/* 浪潮白条不受 prefers-reduced-motion 影响:它是右键刷新的签名动效,故意保留 */
|
|
@@ -83,6 +98,36 @@ window.__ModuleLoader__.load({
|
|
|
83
98
|
.balance-footer__value--healthy{color:var(--dsw-alias-state-success-primary);background:rgba(34,197,94,.15)}
|
|
84
99
|
/* 正在刷新:金额栏压成半透明旧值,和左侧的时段占位呼应,避免旧数字看起来像刚更新的 */
|
|
85
100
|
.balance-footer__value--stale{opacity:.9}
|
|
101
|
+
/* 手动刷新(右键):金额被白光从左擦出来 —— 与输入区「本会话消耗」同一套手法。
|
|
102
|
+
**必须是双层**(2026-10-30 用户反馈「没被光条扫到的区域接着显示 不要直接消失了」):
|
|
103
|
+
底层 __reveal-base 常显(上一帧的值),上层 __reveal-sweep 才是被 clip-path 裁切的那层。
|
|
104
|
+
裁切挂在 sweep 自己身上而不是外层容器,所以光条没扫到的地方照旧显示旧值,不会先变空白。 */
|
|
105
|
+
.balance-footer__reveal{display:inline-grid;position:relative;height:20px;height:1lh}
|
|
106
|
+
.balance-footer__reveal>*{grid-area:1/1;white-space:nowrap}
|
|
107
|
+
/* 底层同样要走裁剪,不能靠 sweep 盖上来:文字是笔画不是色块,两层的字形会叠成一团糊字
|
|
108
|
+
(2026-10-30 用户截图「5.96 CNY」中间叠着别的数字,就是这么来的)。
|
|
109
|
+
所以 base 从左边被裁掉、sweep 从左边展开 —— 交界处永远只有一层内容。
|
|
110
|
+
收尾时 base 已被裁光,右侧不会留旧值的尾巴,也不需要额外淡出。 */
|
|
111
|
+
/* 底层**必须绝对定位**:clip-path 只作用于绘制,不影响布局 —— 留在 grid 流里时,
|
|
112
|
+
旧值的宽度照样会算进容器(实测 8.04 稳态 73.66px,带上旧值 11.04 的底层就变 83.53px)。
|
|
113
|
+
后果是自动刷新时胶囊先宽 10px 再缩回,看着像数字在左右跳。
|
|
114
|
+
拉出流之后宽度只由 sweep 决定,稳态与滚动过程中完全一致。 */
|
|
115
|
+
.balance-footer__reveal-base{position:absolute;inset:0 auto 0 0;display:flex;align-items:center;white-space:nowrap;animation:balance-base-in .34s cubic-bezier(.34,.7,.26,1) both}
|
|
116
|
+
@keyframes balance-base-in{from{clip-path:inset(0 0 0 0)}to{clip-path:inset(0 0 0 100%)}}
|
|
117
|
+
.balance-footer__reveal-sweep{position:relative;overflow:hidden;animation:balance-calc-reveal .34s cubic-bezier(.34,.7,.26,1) both}
|
|
118
|
+
/* 胶囊内部**不放光条**(2026-10-30 用户要求「余额内的光条撤了」):
|
|
119
|
+
外面那层 .balance-footer__wave 已经在整块扫白光了,胶囊里再来一道就是两道白光打架。
|
|
120
|
+
这里只保留 clip 揭示 —— 数字从左被「擦」出来,边界交给外面那道光。 */
|
|
121
|
+
/* 设置里关掉「右键刷新浪潮」:不载入动画,金额直接显示。
|
|
122
|
+
⚠️ 底层必须一起 display:none —— 去掉裁剪后 sweep 会全显,两层文字重叠成一团糊字
|
|
123
|
+
(2026-10-30 用户截图报的正是这个:5.96 CNY 中间叠了个别的数字)。 */
|
|
124
|
+
.balance-footer__reveal--off .balance-footer__reveal-sweep{animation:none;clip-path:none}
|
|
125
|
+
.balance-footer__reveal--off .balance-footer__reveal-base{display:none}
|
|
126
|
+
/* 同上:reduce 时没有动画,只留新值那一层,否则同样是两层重叠 */
|
|
127
|
+
@media (prefers-reduced-motion:reduce){
|
|
128
|
+
.balance-footer__reveal-sweep{animation:none;clip-path:none}
|
|
129
|
+
.balance-footer__reveal-base{display:none}
|
|
130
|
+
}
|
|
86
131
|
.balance-footer__value--low{color:#d97706;background:rgba(217,119,6,.15)}
|
|
87
132
|
.balance-footer__value--empty{color:var(--dsw-alias-state-error-primary,#e5484d);background:rgba(229,72,77,.15)}
|
|
88
133
|
.balance-footer__topup{margin-left:auto;flex:none;box-sizing:border-box;padding:2px 10px;border-radius:999px;font-weight:500;white-space:nowrap;color:var(--dsw-alias-state-error-primary,#e5484d);text-decoration:none;background:rgba(229,72,77,.15);transition:transform .15s var(--ds-ease-in-out,.2s)}
|
|
@@ -211,9 +256,62 @@ window.__ModuleLoader__.load({
|
|
|
211
256
|
.balance-usage-line svg{flex:none;width:14px;height:14px}
|
|
212
257
|
.balance-usage-line:hover,.balance-usage-line[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-secondary)}
|
|
213
258
|
.balance-usage-label{text-overflow:ellipsis;min-width:0;overflow:hidden}
|
|
214
|
-
/*
|
|
215
|
-
|
|
216
|
-
.
|
|
259
|
+
/* 金额那一段单独成类。**宽度必须恒定**:这段一变宽变窄,composer dock 那一行就重排,
|
|
260
|
+
输入框会被顶得上下跳(2026-10-30 用户实测反馈)。
|
|
261
|
+
真正的元凶不是「计算中」三个字,而是位数本身:toFixed(4) 下 0.4218 是 6 个数字位(57.58px)、
|
|
262
|
+
69.2184 是 7 位(64.63px)、123.4567 是 8 位(71.66px),金额一涨就变宽。
|
|
263
|
+
所以用 min-width 按最宽的 8 位兜住(1ch = 一个数字宽,随字号自动缩放),
|
|
264
|
+
短金额右侧留白也不补前导零;pending 那侧另用不可见锚占同样的位。 */
|
|
265
|
+
/* 高度也必须锁死:inline-block 一旦带上 overflow:hidden,行盒会被撑高,
|
|
266
|
+
数字就比「本会话消耗」高出两三个像素(2026-10-30 用户反馈「这个数字还变高了」)。
|
|
267
|
+
所以宽高一起固定 —— inline-flex + align-items:center + height:1lh(= 继承的行高,
|
|
268
|
+
跟随 DSH 字号设置走),待算与算完两态的高度完全一致。 */
|
|
269
|
+
.balance-usage-cost{white-space:nowrap;font-variant-numeric:tabular-nums;display:inline-flex;align-items:center;min-width:10.2ch;height:20px;height:1lh}
|
|
270
|
+
.balance-usage-cost--pending{opacity:1}
|
|
271
|
+
/* 待算时**什么都不画**:既没有「计算中」,也没有循环白条。
|
|
272
|
+
用户 2026-10-30 的原话是「为什么会扫两遍,如果没扫到就让他空着就行了」——
|
|
273
|
+
循环那道和擦出那道叠在一起就是两遍。现在只留擦出这一遍。 */
|
|
274
|
+
/* 数字出场=被光条擦出来(2026-10-30 用户要「光条滑过的地方开始显示数字 就跟窗户一样」):
|
|
275
|
+
外层 clip-path 从左往右展开,边界正好压住光条,所以看上去是光条把数字一格一格擦亮;
|
|
276
|
+
::after 那颗光条与 boundary 用同一条 balance-wipe-head 曲线,二者永远同步,
|
|
277
|
+
拉到右端时外层已全开、光条也淡出,收尾干净。只播一次(both),数字变了也不重播。 */
|
|
278
|
+
/* 时长从 .58s 收到 .34s:用户 2026-10-30 反馈「速度太慢了,点进会话会停顿 0.5 秒左右」。
|
|
279
|
+
0.34s 刚好够看清"光条擦过去",又不至于让人等。白条那份 balance-wipe-head 不动 ——
|
|
280
|
+
它和指针入场同源,selftest 会核对二者时长一致。 */
|
|
281
|
+
/* 「计算中…」模式下数据到手用淡入,不放光条(两种加载语言不混用)。 */
|
|
282
|
+
.balance-usage-fade{position:relative;display:inline-flex;align-items:center;height:20px;height:1lh;animation:balance-usage-fade-in .2s ease-out both}
|
|
283
|
+
@keyframes balance-usage-fade-in{from{opacity:0}to{opacity:1}}
|
|
284
|
+
@media (prefers-reduced-motion:reduce){
|
|
285
|
+
.balance-usage-fade{animation:none}
|
|
286
|
+
}
|
|
287
|
+
/* 「计算中…」的三个点逐颗跳动(设置里 usageLoadingStyle 选 "text" 时才用)。
|
|
288
|
+
文字本身不动,只有省略号在闪,这是最不打扰的一种「还在算」的信号。
|
|
289
|
+
⚠️ 点必须拆成三个独立 span,单个 "…"(U+2026)是一个字,没法分段做动画。 */
|
|
290
|
+
.balance-usage-calc{display:inline-flex;align-items:center;height:18px;padding:0 8px;border-radius:999px;corner-shape:round;background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.06))}
|
|
291
|
+
.balance-usage-calc__dots{display:inline-flex;margin-left:1px}
|
|
292
|
+
.balance-usage-calc__dot{animation:balance-dot-blink 1.2s ease-in-out infinite}
|
|
293
|
+
.balance-usage-calc__dot:nth-child(2){animation-delay:.15s}
|
|
294
|
+
.balance-usage-calc__dot:nth-child(3){animation-delay:.3s}
|
|
295
|
+
@keyframes balance-dot-blink{0%,70%,100%{opacity:.22}35%{opacity:1}}
|
|
296
|
+
@media (prefers-reduced-motion:reduce){
|
|
297
|
+
.balance-usage-calc__dot{animation:none;opacity:.75}
|
|
298
|
+
}
|
|
299
|
+
/* 数字跟着白光一起出来:clip 把这一层从左往右擦开,白光正好压在擦开的边缘上,
|
|
300
|
+
看起来就是「光扫到哪里,数字就亮到哪里」。
|
|
301
|
+
⚠️ 只在**数据到手那一刻**挂载(key 绑 sessionId),所以只擦一次。
|
|
302
|
+
数值之后的变动不走这里(外层不重挂),而是内层逐位滚动 —— 两套动效分开。
|
|
303
|
+
时长 .34s:0.58s 在这个窄 pill 里偏慢(2026-10-30「速度太慢」)。 */
|
|
304
|
+
.balance-usage-reveal{position:relative;display:inline-flex;align-items:center;overflow:hidden;height:20px;height:1lh;padding-right:7px;margin-right:-7px;animation:balance-calc-reveal .34s cubic-bezier(.34,.7,.26,1) both}
|
|
305
|
+
.balance-usage-reveal::after{content:"";position:absolute;top:50%;left:0;width:6px;height:18px;margin-top:-9px;margin-left:-3px;border-radius:999px;corner-shape:round;background:#fff;box-shadow:0 0 3px 1px rgba(255,255,255,.95),0 0 8px 3px rgba(255,255,255,.42),0 0 14px 5px rgba(255,255,255,.16);animation:balance-wipe-head .34s cubic-bezier(.34,.7,.26,1) forwards;pointer-events:none}
|
|
306
|
+
@media (prefers-reduced-motion:reduce){
|
|
307
|
+
.balance-usage-reveal{animation:none;clip-path:none}
|
|
308
|
+
.balance-usage-reveal::after{animation:none;content:none}
|
|
309
|
+
}
|
|
310
|
+
.balance-usage-cost-inner{display:inline-flex;align-items:center;white-space:nowrap;height:20px;height:1lh}
|
|
311
|
+
/* ≈ 的视觉修正:字形本身偏低,用 em 单位上提(跟随字号缩放),右侧留一点呼吸缝。
|
|
312
|
+
不要改成 vertical-align —— inline-flex 子项走的是 align-items:center,两者会打架。 */
|
|
313
|
+
.balance-usage-approx{position:relative;top:-.12em;margin-right:.28em}
|
|
314
|
+
@keyframes balance-calc-reveal{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 -4px 0 0)}}
|
|
217
315
|
/* ── 消耗明细浮层:规格照抄 DSH 原生「会话统计」面板(.HS2XBG_panel/.title/.titleRule/.details)── */
|
|
218
316
|
.balance-usage-panel{box-sizing:border-box;position:fixed;z-index:1100;border:0;border-radius:var(--dsw-radius-lg,12px);background:var(--dsw-specific-menu,var(--dsw-alias-bg-module-platform,#242526));width:max-content;min-width:min(300px,100vw - 24px);max-width:min(440px,100vw - 24px);backdrop-filter:var(--dsw-menu-backdrop-filter,none);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent,0 8px 24px rgba(0,0,0,.32));color:var(--dsw-alias-label-secondary);cursor:default;padding:16px;font-size:12px;line-height:18px;animation:balance-stats-panel-in .12s var(--ds-ease-in-out,ease-out) both}
|
|
219
317
|
.balance-usage-panel__title{display:flex;justify-content:space-between;gap:16px;margin-bottom:8px;color:var(--dsw-alias-label-primary);font-weight:500}
|
|
@@ -688,6 +786,65 @@ window.__ModuleLoader__.load({
|
|
|
688
786
|
}
|
|
689
787
|
}
|
|
690
788
|
|
|
789
|
+
// ─── 逐位滚动(数字每帧上下翻) ──────────────────────────────────────
|
|
790
|
+
// 把两段金额文本按「小数点对齐」拆成逐位滚动的格子:整数右对齐、小数点一位、
|
|
791
|
+
// 小数左对齐,只有真正变了的数位才滚,其余(小数点、货币后缀、没变的高位)静止。
|
|
792
|
+
// 空位用 \u0000 标记,渲染时换成 NBSP 或退回旧字符撑宽。
|
|
793
|
+
// 余额胶囊(自动刷新)与「本会话消耗」共用这一套 —— 两处滚动观感必须一致
|
|
794
|
+
// (2026-10-30 用户要求「对话消耗的数字变动动画跟余额的自动刷新一样」)。
|
|
795
|
+
const ROLL_EMPTY = "\u0000";
|
|
796
|
+
function buildRollCells(fromText, toText) {
|
|
797
|
+
const split = (text) => {
|
|
798
|
+
const m = /^([0-9]*)(\.?)([0-9]*)([\s\S]*)$/.exec(text);
|
|
799
|
+
return m
|
|
800
|
+
? { int: m[1], dot: m[2], frac: m[3], tail: m[4] }
|
|
801
|
+
: { int: "", dot: "", frac: "", tail: text };
|
|
802
|
+
};
|
|
803
|
+
const a = split(fromText);
|
|
804
|
+
const b = split(toText);
|
|
805
|
+
const padLeft = (s, n) => s.padStart(n, ROLL_EMPTY);
|
|
806
|
+
const padRight = (s, n) => s.padEnd(n, ROLL_EMPTY);
|
|
807
|
+
const intLen = Math.max(a.int.length, b.int.length);
|
|
808
|
+
const fracLen = Math.max(a.frac.length, b.frac.length);
|
|
809
|
+
const tailLen = Math.max(a.tail.length, b.tail.length);
|
|
810
|
+
const rows = [
|
|
811
|
+
[padLeft(a.int, intLen), padLeft(b.int, intLen)],
|
|
812
|
+
[a.dot || b.dot, b.dot || a.dot],
|
|
813
|
+
[padRight(a.frac, fracLen), padRight(b.frac, fracLen)],
|
|
814
|
+
[padRight(a.tail, tailLen), padRight(b.tail, tailLen)],
|
|
815
|
+
];
|
|
816
|
+
const cells = [];
|
|
817
|
+
for (const [left, right] of rows) {
|
|
818
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
819
|
+
cells.push({ from: left[i], to: right[i], changed: left[i] !== right[i] });
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return cells;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/** 渲染一组滚动格。prefix 只用于 React key(两处调用各用一个前缀,互不干扰)。 */
|
|
826
|
+
function renderRollCells(cells, prefix) {
|
|
827
|
+
return jsx.jsx("span", {
|
|
828
|
+
className: "balance-footer__roll",
|
|
829
|
+
children: cells.map((cell, i) => (
|
|
830
|
+
cell.changed
|
|
831
|
+
? jsx.jsxs("span", {
|
|
832
|
+
// 新值是空位(位数减少)时加 --gone:滚完自己收成 0 宽,否则尾巴多一格,
|
|
833
|
+
// 胶囊会被撑宽再缩回(2026-10-30 实测 73.66 ↔ 83.53)。
|
|
834
|
+
className: "balance-footer__dcell balance-footer__dcell--roll" + (cell.to === ROLL_EMPTY ? " balance-footer__dcell--gone" : ""),
|
|
835
|
+
children: [
|
|
836
|
+
// 影子格用「新字符」撑宽;新值是空位时退回旧字符 —— 滚动开始那一刻的宽度
|
|
837
|
+
// 正好等于滚动前,之后由 --gone 的 max-width 动画把它平滑收到 0。
|
|
838
|
+
jsx.jsx("span", { className: "balance-footer__dghost", children: cell.to === ROLL_EMPTY ? cell.from : cell.to }),
|
|
839
|
+
jsx.jsx("span", { className: "balance-footer__dout", children: cell.from === ROLL_EMPTY ? "\u00a0" : cell.from }),
|
|
840
|
+
jsx.jsx("span", { className: "balance-footer__din", children: cell.to === ROLL_EMPTY ? "\u00a0" : cell.to }),
|
|
841
|
+
],
|
|
842
|
+
}, prefix + i)
|
|
843
|
+
: jsx.jsx("span", { className: "balance-footer__dcell", children: cell.to === ROLL_EMPTY ? "\u00a0" : cell.to }, prefix + i)
|
|
844
|
+
)),
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
|
|
691
848
|
// ─── 高峰/空闲时段(纯客户端判断,无需服务端) ────────────────────────
|
|
692
849
|
// V4 峰谷定价(2026-08-17 生效):高峰价 = 空闲价 × 2。
|
|
693
850
|
// 官方高峰窗口(北京时间)为 工作日 09:00-12:00、14:00-18:00;
|
|
@@ -1011,6 +1168,8 @@ const CN_CALENDAR_SOURCES = [
|
|
|
1011
1168
|
footerStyle: "timeline",
|
|
1012
1169
|
showSidebarEntry: true,
|
|
1013
1170
|
refreshWave: true,
|
|
1171
|
+
// 与服务端 DEFAULT_SETTINGS 逐字段保持一致(selftest 会逐键比对求值后的对象)
|
|
1172
|
+
usageLoadingStyle: "wipe",
|
|
1014
1173
|
entrySeen: false,
|
|
1015
1174
|
newTagVersion: "",
|
|
1016
1175
|
newTagSince: 0,
|
|
@@ -1215,7 +1374,7 @@ const CN_CALENDAR_SOURCES = [
|
|
|
1215
1374
|
try {
|
|
1216
1375
|
const info = await fetchUpdateInfo();
|
|
1217
1376
|
if (!info || info.ok !== true || info.newer !== true) return;
|
|
1218
|
-
// latest 变了才重置 seen —— 用户已经无视过 2.
|
|
1377
|
+
// latest 变了才重置 seen —— 用户已经无视过 2.0.7,再出 2.0.8 仍然要提示
|
|
1219
1378
|
if (updateNotice.latest !== info.latest) {
|
|
1220
1379
|
updateNotice = { latest: info.latest, seen: false };
|
|
1221
1380
|
emitUpdateNotice();
|
|
@@ -2031,7 +2190,16 @@ function TimelineBand(props) {
|
|
|
2031
2190
|
: null;
|
|
2032
2191
|
const [roll, setRoll] = react.useState(null);
|
|
2033
2192
|
const lastRollTextRef = react.useRef(null);
|
|
2193
|
+
// 手动擦出时的底层内容=「上一帧显示的值」。ref 天然滞后一帧,正好就是它。
|
|
2194
|
+
// 用户 2026-10-30 的原话:「没被光条扫到的区域接着显示 不要直接消失了」——
|
|
2195
|
+
// 所以扫过的那层(sweep)才是新内容,没扫到的部分由 base 撑着,仍是旧值。
|
|
2196
|
+
const revealBaseRef = react.useRef(null);
|
|
2034
2197
|
react.useEffect(() => {
|
|
2198
|
+
if (rollText !== null) revealBaseRef.current = rollText;
|
|
2199
|
+
}, [rollText]);
|
|
2200
|
+
// 用 usePrePaintEffect(优先 useLayoutEffect):必须在浏览器 paint 之前就把 roll 挂上,
|
|
2201
|
+
// 否则这一帧画的是纯文本新值、下一帧才变成滚动格 —— 用户看到的就是"数字比动画先播"。
|
|
2202
|
+
usePrePaintEffect(() => {
|
|
2035
2203
|
const prev = lastRollTextRef.current;
|
|
2036
2204
|
lastRollTextRef.current = rollText;
|
|
2037
2205
|
if (rollText === null || prev === null || prev === rollText) return;
|
|
@@ -2041,43 +2209,9 @@ function TimelineBand(props) {
|
|
|
2041
2209
|
setRoll({ from: prev, to: rollText, at: Date.now() });
|
|
2042
2210
|
}, [rollText]);
|
|
2043
2211
|
|
|
2044
|
-
//
|
|
2045
|
-
//
|
|
2046
|
-
|
|
2047
|
-
//
|
|
2048
|
-
// 关键在于"按小数点对齐"而不是整串左对齐:
|
|
2049
|
-
// 11.04 CNY → 8.04 CNY,左对齐会变成 1↔8 / 1↔. / .↔0 …整串错位,
|
|
2050
|
-
// 按小数点分三段(整数右对齐、小数点一位、小数左对齐)之后,
|
|
2051
|
-
// 整数位各自对位、小数 .04 原样不动,只有该变的位在翻。
|
|
2052
|
-
const rollCells = roll === null ? null : (() => {
|
|
2053
|
-
const split = (text) => {
|
|
2054
|
-
const m = /^([0-9]*)(\.?)([0-9]*)([\s\S]*)$/.exec(text);
|
|
2055
|
-
return m
|
|
2056
|
-
? { int: m[1], dot: m[2], frac: m[3], tail: m[4] }
|
|
2057
|
-
: { int: "", dot: "", frac: "", tail: text };
|
|
2058
|
-
};
|
|
2059
|
-
const a = split(roll.from);
|
|
2060
|
-
const b = split(roll.to);
|
|
2061
|
-
const EMPTY = "\u0000";
|
|
2062
|
-
const padLeft = (s, n) => s.padStart(n, EMPTY);
|
|
2063
|
-
const padRight = (s, n) => s.padEnd(n, EMPTY);
|
|
2064
|
-
const intLen = Math.max(a.int.length, b.int.length);
|
|
2065
|
-
const fracLen = Math.max(a.frac.length, b.frac.length);
|
|
2066
|
-
const tailLen = Math.max(a.tail.length, b.tail.length);
|
|
2067
|
-
const rows = [
|
|
2068
|
-
[padLeft(a.int, intLen), padLeft(b.int, intLen)],
|
|
2069
|
-
[a.dot || b.dot, b.dot || a.dot],
|
|
2070
|
-
[padRight(a.frac, fracLen), padRight(b.frac, fracLen)],
|
|
2071
|
-
[padRight(a.tail, tailLen), padRight(b.tail, tailLen)],
|
|
2072
|
-
];
|
|
2073
|
-
const cells = [];
|
|
2074
|
-
for (const [left, right] of rows) {
|
|
2075
|
-
for (let i = 0; i < left.length; i++) {
|
|
2076
|
-
cells.push({ from: left[i], to: right[i], changed: left[i] !== right[i] });
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
return cells;
|
|
2080
|
-
})();
|
|
2212
|
+
// 逐位拆开:只在真正变了的数位上做上下滚动。实现见模块级的 buildRollCells ——
|
|
2213
|
+
// 「本会话消耗」用的是同一个函数,两处滚动因此逐格一致(2026-10-30 用户要求)。
|
|
2214
|
+
const rollCells = roll === null ? null : buildRollCells(roll.from, roll.to);
|
|
2081
2215
|
react.useEffect(() => {
|
|
2082
2216
|
if (roll === null) return;
|
|
2083
2217
|
const id = setTimeout(() => setRoll(null), 480);
|
|
@@ -2323,8 +2457,10 @@ function TimelineBand(props) {
|
|
|
2323
2457
|
// 展开态余额行:设置里关掉就不渲染(收起态圆钮由上面的 showFab 单独控制)
|
|
2324
2458
|
if (settings.showBalanceRow === false) return null;
|
|
2325
2459
|
|
|
2326
|
-
//
|
|
2327
|
-
//
|
|
2460
|
+
// 右键刷新的浪潮层:整块从左往右扫一道泛光。用户 2026-10-30 明确
|
|
2461
|
+
// 「别给我整个光条删了」——所以这层保留;金额胶囊内部另有 __reveal 的擦除揭示,
|
|
2462
|
+
// 两层叠加:泛光负责「你点了」,擦出负责「新数字出现」。
|
|
2463
|
+
// key 绑定 waveSeq,右键自增 → 重挂 → 动画重播。
|
|
2328
2464
|
const waveNode = settings.refreshWave === false
|
|
2329
2465
|
? null
|
|
2330
2466
|
: jsx.jsx("span", { className: "balance-footer__wave", key: "wave-" + waveSeq });
|
|
@@ -2474,29 +2610,35 @@ function TimelineBand(props) {
|
|
|
2474
2610
|
return withPanel(jsx.jsxs("div", {
|
|
2475
2611
|
className: "balance-footer balance-footer--stacked",
|
|
2476
2612
|
children: [
|
|
2477
|
-
// 抹除层覆盖整块显示区(today 行 + meta 行 +
|
|
2613
|
+
// 抹除层覆盖整块显示区(today 行 + meta 行 + 时段条):白条经过哪里,哪里才显影
|
|
2478
2614
|
waveNode,
|
|
2479
2615
|
periodNode === null
|
|
2480
2616
|
? jsx.jsx("span", { className: "balance-footer__spacer" })
|
|
2481
2617
|
: periodNode,
|
|
2482
2618
|
jsx.jsx("span", {
|
|
2483
2619
|
className: "balance-footer__value" + (low ? " balance-footer__value--low" : " balance-footer__value--healthy") + (state.refreshing ? " balance-footer__value--stale" : "") + valueDensity,
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2620
|
+
// key 绑定 waveSeq:只有右键手动刷新才 +1 → 重挂 → 白光把金额重新擦一遍。
|
|
2621
|
+
// 自动刷新(60s 轮询 / 对话完成触发的 fetch)不动 waveSeq,所以不会擦,
|
|
2622
|
+
// 数字继续走下面那套逐位滚动 —— 两套动效完全分开(2026-10-30 用户要求)。
|
|
2623
|
+
children: jsx.jsx("span", {
|
|
2624
|
+
className: "balance-footer__reveal" + (settings.refreshWave === false ? " balance-footer__reveal--off" : ""),
|
|
2625
|
+
key: "reveal-" + waveSeq,
|
|
2626
|
+
children: [
|
|
2627
|
+
// 底层(常显):光条没扫到的区域就是它,所以内容不会先消失再长出来。
|
|
2628
|
+
jsx.jsx("span", {
|
|
2629
|
+
className: "balance-footer__reveal-base",
|
|
2630
|
+
children: revealBaseRef.current !== null ? revealBaseRef.current : totalText,
|
|
2631
|
+
}, "base"),
|
|
2632
|
+
// 上层:被 clip-path 从左边擦开的那一层,光条走到哪,哪才盖住底层。
|
|
2633
|
+
jsx.jsx("span", {
|
|
2634
|
+
className: "balance-footer__reveal-sweep",
|
|
2635
|
+
// 滚动格与「本会话消耗」共用 renderRollCells —— 两处必须逐格一致
|
|
2636
|
+
children: rollCells === null
|
|
2637
|
+
? totalText
|
|
2638
|
+
: jsx.jsx("span", { key: "roll-" + roll.at, children: renderRollCells(rollCells, "f") }),
|
|
2639
|
+
}, "sweep"),
|
|
2640
|
+
],
|
|
2641
|
+
}),
|
|
2500
2642
|
}),
|
|
2501
2643
|
],
|
|
2502
2644
|
}), undefined, undefined, rowInteractions);
|
|
@@ -2554,6 +2696,17 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2554
2696
|
? (usage.uncachedInputTokens ?? 0) + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0) + (usage.outputTokens ?? 0)
|
|
2555
2697
|
: 0;
|
|
2556
2698
|
|
|
2699
|
+
// 切会话的第一帧:projection 里的 usage 可能还是上一个会话的,这一帧算出来的金额
|
|
2700
|
+
// 会属于**旧对话**(用户 2026-10-30 反馈「点对话之前会显示余额,而且这个余额不是正确余额」)。
|
|
2701
|
+
// 判据:sessionId 变了、usageKey 没跟着变 → usage 还没换过来 → 这一帧不显示数字。
|
|
2702
|
+
// 只影响 1 帧(下一帧 effect 就更新 stamp),不会造成可见停顿。
|
|
2703
|
+
const usageStampRef = react.useRef({ sessionId, usageKey });
|
|
2704
|
+
const staleUsage =
|
|
2705
|
+
usageStampRef.current.sessionId !== sessionId && usageStampRef.current.usageKey === usageKey;
|
|
2706
|
+
react.useEffect(() => {
|
|
2707
|
+
usageStampRef.current = { sessionId, usageKey };
|
|
2708
|
+
});
|
|
2709
|
+
|
|
2557
2710
|
// 解析本会话使用的模型,并按模型刷新定价(/balance-api?model=...)
|
|
2558
2711
|
// 依赖 [sessionId, usageKey]:切对话或会话内容推进(新消息)都会触发;若还没解析到模型则总是重试。
|
|
2559
2712
|
react.useEffect(() => {
|
|
@@ -2589,20 +2742,10 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2589
2742
|
};
|
|
2590
2743
|
}, [sessionId, usageKey]);
|
|
2591
2744
|
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
const cacheWrite = usage.cacheWriteTokens ?? 0;
|
|
2597
|
-
const output = usage.outputTokens ?? 0;
|
|
2598
|
-
// 新增内容 = 未缓存输入 + 输出(不含缓存重读)
|
|
2599
|
-
const newTokens = uncachedInput + output;
|
|
2600
|
-
// 计费总量 = 未缓存输入 + 缓存重读/写入 + 输出,与 DeepSeek 账单口径一致
|
|
2601
|
-
const billedTotal = uncachedInput + cacheRead + cacheWrite + output;
|
|
2602
|
-
if (newTokens === 0 && billedTotal === 0) return null;
|
|
2603
|
-
|
|
2604
|
-
// 金额:优先用服务端分时段 usage(高峰桶×2+空闲桶);未就绪时按当前时段价估算。
|
|
2605
|
-
// 只认本会话的分桶 —— 切到没缓存的会话时立刻退回本地估算,绝不用上一个会话的数据。
|
|
2745
|
+
// ── 金额与逐位滚动 ────────────────────────────────────────────────────
|
|
2746
|
+
// ⚠️ 这段是**计算 + hooks**,必须写在下面两处早退之前:hook 不能跟在条件 return 后面,
|
|
2747
|
+
// 否则「usage 为 null 的帧」与「有 usage 的帧」hook 数量不同,React 会直接抛错。
|
|
2748
|
+
// 所以 splitFor / split / splitUsable / cost 在这里算,早退之后不再重复定义。
|
|
2606
2749
|
const splitFor = sessionId ? splitMap[sessionId] || null : null;
|
|
2607
2750
|
const split = splitFor ? splitFor.usage : null;
|
|
2608
2751
|
const subagentUsage = splitFor ? splitFor.subagentUsage : null;
|
|
@@ -2613,11 +2756,48 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2613
2756
|
// 本地估算此时只是临时值:它把**整段历史**按当前时段价一次性乘(高峰窗口里就是 ×2),
|
|
2614
2757
|
// 而服务端是按每条消息实际发生的时段分桶。所以高峰时段点进旧会话必然先高后低
|
|
2615
2758
|
// (2026-10-30 实测:先显 ¥107、随即跳到 ¥69,比值 1.55 正好是「一半历史在空闲时段」)。
|
|
2616
|
-
//
|
|
2759
|
+
// 与其先闪一个会变的数字,不如先留空,拿到结果后一次到位。
|
|
2617
2760
|
const splitAsked =
|
|
2618
2761
|
sessionId != null && sessionId !== "" && Object.prototype.hasOwnProperty.call(splitMap, sessionId);
|
|
2619
2762
|
const costPending = !splitAsked && usageKey > 0;
|
|
2620
|
-
const cost = splitUsable
|
|
2763
|
+
const cost = splitUsable
|
|
2764
|
+
? computeSplitCost(split, p, peakMultiplier)
|
|
2765
|
+
: computeCost(usage ?? {}, periodAdjustedPricing(p, peakMultiplier));
|
|
2766
|
+
const costText = cost.toFixed(4);
|
|
2767
|
+
|
|
2768
|
+
// 逐位滚动:数值一变就翻(和余额胶囊自动刷新共用 buildRollCells/renderRollCells)。
|
|
2769
|
+
// usePrePaintEffect 是必须的 —— 等 useEffect(paint 之后)才挂滚动结构的话,
|
|
2770
|
+
// 会先渲染一帧纯文本新值,用户看到的就是「数字比动画先出来」(2026-10-30 反馈)。
|
|
2771
|
+
// skipCostRoll:切会话/待算/无 usage 时把基准清空,这样下次出现是「首次显示」而不是「从某个值滚过来」。
|
|
2772
|
+
// 加载动效风格:决定等待时显示什么、数据到手怎么进场(两种语言不混用)
|
|
2773
|
+
const loadingIsText = settings.usageLoadingStyle === "text";
|
|
2774
|
+
const skipCostRoll = staleUsage || costPending || !usage;
|
|
2775
|
+
const [costRoll, setCostRoll] = react.useState(null);
|
|
2776
|
+
const lastCostTextRef = react.useRef(null);
|
|
2777
|
+
usePrePaintEffect(() => {
|
|
2778
|
+
const prev = lastCostTextRef.current;
|
|
2779
|
+
lastCostTextRef.current = skipCostRoll ? null : costText;
|
|
2780
|
+
if (skipCostRoll || prev === null || prev === costText) return;
|
|
2781
|
+
setCostRoll({ from: prev, to: costText, at: Date.now() });
|
|
2782
|
+
}, [costText, skipCostRoll]);
|
|
2783
|
+
usePrePaintEffect(() => {
|
|
2784
|
+
if (costRoll === null) return;
|
|
2785
|
+
const id = setTimeout(() => setCostRoll(null), 480);
|
|
2786
|
+
return () => clearTimeout(id);
|
|
2787
|
+
}, [costRoll]);
|
|
2788
|
+
|
|
2789
|
+
if (!usage) return null;
|
|
2790
|
+
|
|
2791
|
+
const uncachedInput = usage.uncachedInputTokens ?? 0;
|
|
2792
|
+
const cacheRead = usage.cacheReadTokens ?? 0;
|
|
2793
|
+
const cacheWrite = usage.cacheWriteTokens ?? 0;
|
|
2794
|
+
const output = usage.outputTokens ?? 0;
|
|
2795
|
+
// 新增内容 = 未缓存输入 + 输出(不含缓存重读)
|
|
2796
|
+
const newTokens = uncachedInput + output;
|
|
2797
|
+
// 计费总量 = 未缓存输入 + 缓存重读/写入 + 输出,与 DeepSeek 账单口径一致
|
|
2798
|
+
const billedTotal = uncachedInput + cacheRead + cacheWrite + output;
|
|
2799
|
+
if (newTokens === 0 && billedTotal === 0) return null;
|
|
2800
|
+
|
|
2621
2801
|
const peakTokens = splitUsable ? split.peak.inputTokens + split.peak.cacheReadTokens + split.peak.cacheWriteTokens + split.peak.outputTokens : 0;
|
|
2622
2802
|
const offpeakTokens = splitUsable ? split.offpeak.inputTokens + split.offpeak.cacheReadTokens + split.offpeak.cacheWriteTokens + split.offpeak.outputTokens : 0;
|
|
2623
2803
|
|
|
@@ -2646,7 +2826,44 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2646
2826
|
jsx.jsx("span", {
|
|
2647
2827
|
key: "cost",
|
|
2648
2828
|
className: "balance-usage-cost" + (costPending ? " balance-usage-cost--pending" : ""),
|
|
2649
|
-
|
|
2829
|
+
// 两种加载动效二选一(usageLoadingStyle),**两套语言不混用**:
|
|
2830
|
+
// "wipe"(默认):等待时留空 → 数据到手挂 .balance-usage-reveal,白光擦过、数字跟着亮。
|
|
2831
|
+
// "text":等待时显示带呼吸的「计算中…」胶囊 → 数据到手**直接渐入**,不放光条
|
|
2832
|
+
// (2026-10-30 用户:「用的如果是计算中就不要用光条动画了 计算完直接渐入就行了」)。
|
|
2833
|
+
// 两种都只在等待期间出现,都不亮错误值。
|
|
2834
|
+
children: staleUsage || costPending
|
|
2835
|
+
? (loadingIsText
|
|
2836
|
+
? jsx.jsxs("span", {
|
|
2837
|
+
className: "balance-usage-calc",
|
|
2838
|
+
key: "calc-" + (sessionId || ""),
|
|
2839
|
+
children: [
|
|
2840
|
+
"计算中",
|
|
2841
|
+
// 三个点拆开逐个闪:单个 "…" 是一个字,没法分段动
|
|
2842
|
+
jsx.jsx("span", {
|
|
2843
|
+
className: "balance-usage-calc__dots",
|
|
2844
|
+
"aria-hidden": "true",
|
|
2845
|
+
children: [0, 1, 2].map((i) => jsx.jsx("span", { className: "balance-usage-calc__dot", children: "." }, i)),
|
|
2846
|
+
}, "dots"),
|
|
2847
|
+
],
|
|
2848
|
+
})
|
|
2849
|
+
: null)
|
|
2850
|
+
: jsx.jsx("span", {
|
|
2851
|
+
className: loadingIsText ? "balance-usage-fade" : "balance-usage-reveal",
|
|
2852
|
+
key: (loadingIsText ? "fade-" : "reveal-") + (sessionId || ""),
|
|
2853
|
+
children: jsx.jsxs("span", {
|
|
2854
|
+
className: "balance-usage-cost-inner",
|
|
2855
|
+
children: [
|
|
2856
|
+
// ≈ 单独成节点:U+2248 在中文字体里落在数学符号的中线,比数字基线低一截,
|
|
2857
|
+
// 直接跟「¥7.4619」串在一起会显得整个符号往下掉(2026-10-30 用户指出「有点靠下」)。
|
|
2858
|
+
jsx.jsx("span", { className: "balance-usage-approx", children: "≈" }, "approx"),
|
|
2859
|
+
jsx.jsx("span", { children: "¥" }, "cur"),
|
|
2860
|
+
// 只有数字参与逐位滚动:¥ 与 ≈ 是定值,跟着滚会把整个符号一起翻。
|
|
2861
|
+
costRoll === null
|
|
2862
|
+
? jsx.jsx("span", { children: costText }, "txt")
|
|
2863
|
+
: jsx.jsx("span", { key: "roll-" + costRoll.at, children: renderRollCells(buildRollCells(costRoll.from, costRoll.to), "u") }),
|
|
2864
|
+
],
|
|
2865
|
+
}),
|
|
2866
|
+
}),
|
|
2650
2867
|
}),
|
|
2651
2868
|
];
|
|
2652
2869
|
|
|
@@ -2678,7 +2895,7 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
2678
2895
|
// 金额口径要写明白:算完 = 服务端逐条按发生时段分桶;算完前 / 拿不到 = 本地按当前时段估算。
|
|
2679
2896
|
// 估算会把整段历史按「此刻」的峰谷价乘一次,高峰窗口里自然偏高 —— 说清楚,别让它冒充精确值。
|
|
2680
2897
|
if (costPending) {
|
|
2681
|
-
costDetails.push(["金额", "
|
|
2898
|
+
costDetails.push(["金额", "正在取服务端分时段用量"]);
|
|
2682
2899
|
} else if (!splitUsable) {
|
|
2683
2900
|
costDetails.push(["金额", "按当前时段估算(服务端未返回分桶)"]);
|
|
2684
2901
|
}
|
|
@@ -3161,6 +3378,7 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
3161
3378
|
["footerStyle", "2.0.0"], // 余额行样式(新版 timeline / 旧版 classic)
|
|
3162
3379
|
["showSidebarEntry", "2.0.0"], // 左侧导航栏入口
|
|
3163
3380
|
["refreshWave", "2.0.0"], // 右键刷新涟漪
|
|
3381
|
+
["usageLoadingStyle", "2.0.7"], // 消耗行加载动效:白光 / 计算中
|
|
3164
3382
|
]);
|
|
3165
3383
|
const newTagSince = Number(value.newTagSince);
|
|
3166
3384
|
// 升级前的版本(空串 = 全新安装)
|
|
@@ -3544,9 +3762,14 @@ function UsageCostLine({ useProjection, sessionId }) {
|
|
|
3544
3762
|
// 浪潮 = 新版独有(旧版刷新走「刷新中…」文字,没有白条可关)
|
|
3545
3763
|
// 时段标签 = 新版不可关闭(刻度条是新版的结构主体),只有旧版才允许收成纯金额
|
|
3546
3764
|
value.footerStyle === "timeline"
|
|
3547
|
-
? toggleRow("refreshWave", "右键刷新浪潮", "
|
|
3765
|
+
? toggleRow("refreshWave", "右键刷新浪潮", "右键余额时,金额会被一道白光从左往右擦出来(和输入区「本会话消耗」同一个动效)。")
|
|
3548
3766
|
: null,
|
|
3549
3767
|
toggleRow("showUsagePill", "会话消耗行", "输入框下方的「本会话消耗」胶囊(含点开的明细浮层)。"),
|
|
3768
|
+
// 两种加载动效互斥,用分段按钮选(和「余额行外观」同一个控件,视觉一致)
|
|
3769
|
+
choiceRow("usageLoadingStyle", "消耗行加载动效", "等分时段用量时显示什么。", [
|
|
3770
|
+
{ value: "wipe", label: "白光" },
|
|
3771
|
+
{ value: "text", label: "计算中" },
|
|
3772
|
+
]),
|
|
3550
3773
|
value.footerStyle === "classic"
|
|
3551
3774
|
? toggleRow("showPeriodLabel", "高峰 / 空闲标签", "旧版余额行里「金额 • 时段」的时段部分;关掉后只留金额。")
|
|
3552
3775
|
: null,
|
package/lib/index.js
CHANGED
|
@@ -527,7 +527,7 @@ function formatBalance(balance) {
|
|
|
527
527
|
// | { status:"failed" } (网络失败;官方明确“失败绝不变成 0 余额”)
|
|
528
528
|
// client 是 AccountClientMetadata(version / locale / timezoneOffsetSeconds),
|
|
529
529
|
// 缺字段会在平台侧组装请求头时抛错,所以三个字段都要给。
|
|
530
|
-
const PLUGIN_VERSION = "2.0.
|
|
530
|
+
const PLUGIN_VERSION = "2.0.7";
|
|
531
531
|
|
|
532
532
|
// ─── 检查更新 ───────────────────────────────────────────────────────────────
|
|
533
533
|
// 只查 npm registry 的 latest 标签(一个很小的 JSON),不下载任何东西。
|
|
@@ -1178,6 +1178,11 @@ const DEFAULT_SETTINGS = {
|
|
|
1178
1178
|
showSidebarEntry: true,
|
|
1179
1179
|
// 2.0.0:右键刷新时,余额行从左往右扫过一道泛光亮带
|
|
1180
1180
|
refreshWave: true,
|
|
1181
|
+
// 2.0.7:「本会话消耗」在等分时段用量时用哪种加载动效。
|
|
1182
|
+
// "wipe"(默认)= 白光擦出:数据到手那一刻一道白光从左扫过,数字跟着光显现。
|
|
1183
|
+
// "text" = 显示带呼吸动画的「计算中…」文字,数据到手后直接显示数字。
|
|
1184
|
+
// 两种都只在等待期间出现,不会先亮一个错误值再换成正确值。
|
|
1185
|
+
usageLoadingStyle: "wipe",
|
|
1181
1186
|
// 侧栏「余额与用量」入口是否已被点开过。false = 显示红点。
|
|
1182
1187
|
// 与版本号无关:只有用户真正点开过这个面板才会被写成 true,之后永不回退。
|
|
1183
1188
|
entrySeen: false,
|
|
@@ -1305,6 +1310,10 @@ function normalizeSettings(raw) {
|
|
|
1305
1310
|
footerStyle: src.footerStyle === "classic" ? "classic" : d.footerStyle,
|
|
1306
1311
|
showSidebarEntry: bool(src.showSidebarEntry, d.showSidebarEntry),
|
|
1307
1312
|
refreshWave: bool(src.refreshWave, d.refreshWave),
|
|
1313
|
+
// 枚举值只认已知项,其余(含旧版的布尔写入)一律回落默认
|
|
1314
|
+
usageLoadingStyle: src.usageLoadingStyle === "text" || src.usageLoadingStyle === "wipe"
|
|
1315
|
+
? src.usageLoadingStyle
|
|
1316
|
+
: d.usageLoadingStyle,
|
|
1308
1317
|
entrySeen: bool(src.entrySeen, d.entrySeen),
|
|
1309
1318
|
newTagVersion: typeof src.newTagVersion === "string" ? src.newTagVersion : d.newTagVersion,
|
|
1310
1319
|
newTagFrom: typeof src.newTagFrom === "string" ? src.newTagFrom : d.newTagFrom,
|