@lynn123411/dsh-a6api 1.0.1 → 1.2.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/lib/client.js CHANGED
@@ -31,6 +31,13 @@ module.exports = __toCommonJS(index_exports);
31
31
  var import_react4 = require("react");
32
32
 
33
33
  // src/client/store.ts
34
+ function formatRelativeNow(tsSec) {
35
+ const diff = Math.floor(Date.now() / 1e3) - tsSec;
36
+ if (diff < 60) return "\u521A\u521A";
37
+ if (diff < 3600) return `${Math.floor(diff / 60)} \u5206\u949F\u524D`;
38
+ if (diff < 86400) return `${Math.floor(diff / 3600)} \u5C0F\u65F6\u524D`;
39
+ return `${Math.floor(diff / 86400)} \u5929\u524D`;
40
+ }
34
41
  var A6ApiStore = class {
35
42
  state = {
36
43
  loading: true,
@@ -45,10 +52,12 @@ var A6ApiStore = class {
45
52
  dshConfiguredModels: [],
46
53
  recentLogs: [],
47
54
  probingModelNames: /* @__PURE__ */ new Set(),
48
- error: null
55
+ error: null,
56
+ priceFluctuation: { pendingCount: 0, unseenCount: 0, totalCount: 0, updatedAt: null }
49
57
  };
50
58
  listeners = /* @__PURE__ */ new Set();
51
59
  autoRefreshTimer = null;
60
+ pricePollTimer = null;
52
61
  constructor() {
53
62
  this.startAutoRefresh();
54
63
  }
@@ -85,6 +94,13 @@ var A6ApiStore = class {
85
94
  this.state.recentLogs = data.recentLogs;
86
95
  }
87
96
  this.state.error = null;
97
+ if (this.state.config?.hasToken) {
98
+ const last = this.state.priceFluctuation?.updatedAt;
99
+ if (!last || Date.now() - last > 1e4) {
100
+ this.fetchPriceFluctuation().catch(() => {
101
+ });
102
+ }
103
+ }
88
104
  }
89
105
  }
90
106
  } catch (err) {
@@ -127,6 +143,38 @@ var A6ApiStore = class {
127
143
  } catch {
128
144
  }
129
145
  }
146
+ async fetchPriceFluctuation() {
147
+ try {
148
+ const res = await fetch("/api/dsh-a6api/price-fluctuation");
149
+ if (res.ok) {
150
+ const json = await res.json();
151
+ if (json?.data) {
152
+ const d = json.data;
153
+ const hasAuth = d.hasAuth !== false && !d.authError;
154
+ if (!hasAuth) {
155
+ const next2 = { pendingCount: 0, unseenCount: 0, totalCount: 0, updatedAt: Date.now(), hasAuth: false, authError: Boolean(d.authError) };
156
+ if (JSON.stringify(next2) !== JSON.stringify(this.state.priceFluctuation)) {
157
+ this.state.priceFluctuation = next2;
158
+ this.notify();
159
+ }
160
+ return;
161
+ }
162
+ const pending = Number(d.pendingCount ?? 0);
163
+ const unseen = Number(d.unseenCount ?? 0);
164
+ const total = Number(d.totalCount ?? 0);
165
+ const next = { pendingCount: pending, unseenCount: unseen, totalCount: total, updatedAt: Date.now(), hasAuth: true, authError: false };
166
+ if (pending !== this.state.priceFluctuation.pendingCount || unseen !== this.state.priceFluctuation.unseenCount || this.state.priceFluctuation.hasAuth === false || this.state.priceFluctuation.updatedAt === null) {
167
+ this.state.priceFluctuation = next;
168
+ this.notify();
169
+ } else if (this.state.priceFluctuation.updatedAt === null) {
170
+ this.state.priceFluctuation = next;
171
+ this.notify();
172
+ }
173
+ }
174
+ }
175
+ } catch {
176
+ }
177
+ }
130
178
  async probeModel(modelName) {
131
179
  this.state.probingModelNames.add(modelName);
132
180
  this.state.models = this.state.models.map(
@@ -149,7 +197,10 @@ var A6ApiStore = class {
149
197
  probeStatus: "success",
150
198
  probeLatencyMs: json.result.durationMs,
151
199
  probeError: void 0,
152
- lastProbedAt: Date.now()
200
+ lastProbedAt: Date.now(),
201
+ // 探测请求本身会写路由日志,乐观更新路由快照时效,下次 /state 以日志为准
202
+ lastRoutedAt: Math.floor(Date.now() / 1e3),
203
+ lastRoutedText: formatRelativeNow(Math.floor(Date.now() / 1e3))
153
204
  } : m
154
205
  );
155
206
  } else if (json?.result?.error) {
@@ -243,6 +294,31 @@ var A6ApiStore = class {
243
294
  this.refreshBalance().catch(() => {
244
295
  });
245
296
  }, 6e4);
297
+ if (this.pricePollTimer) clearInterval(this.pricePollTimer);
298
+ this.pricePollTimer = setInterval(() => {
299
+ if (this.state.config?.hasToken) {
300
+ this.fetchPriceFluctuation().catch(() => {
301
+ });
302
+ }
303
+ }, 60 * 1e3);
304
+ }
305
+ stopAutoRefresh() {
306
+ if (this.autoRefreshTimer) {
307
+ clearInterval(this.autoRefreshTimer);
308
+ this.autoRefreshTimer = null;
309
+ }
310
+ if (this.pricePollTimer) {
311
+ clearInterval(this.pricePollTimer);
312
+ this.pricePollTimer = null;
313
+ }
314
+ }
315
+ initPricePolling() {
316
+ if (this.pricePollTimer) return;
317
+ this.startAutoRefresh();
318
+ if (this.state.config?.hasToken) {
319
+ this.fetchPriceFluctuation().catch(() => {
320
+ });
321
+ }
246
322
  }
247
323
  };
248
324
  var store = new A6ApiStore();
@@ -250,6 +326,14 @@ var store = new A6ApiStore();
250
326
  // src/client/components/MerchantCard.tsx
251
327
  var import_react = require("react");
252
328
  var import_jsx_runtime = require("react/jsx-runtime");
329
+ var formatAbsolute = (tsSec) => {
330
+ const d = new Date(tsSec * 1e3);
331
+ const p = (n) => String(n).padStart(2, "0");
332
+ const nowY = (/* @__PURE__ */ new Date()).getFullYear();
333
+ const y = d.getFullYear();
334
+ const md = `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
335
+ return y !== nowY ? `${y}-${md}` : md;
336
+ };
253
337
  var MerchantCard = ({ model }) => {
254
338
  const [expanded, setExpanded] = (0, import_react.useState)(false);
255
339
  const isProbing = model.probeStatus === "probing";
@@ -384,46 +468,64 @@ var MerchantCard = ({ model }) => {
384
468
  ] }) }),
385
469
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-a6-bar-tags", children: (merchant?.labels || ["\u7A33\u5B9A", "\u4F4E\u4EF7", "\u9AD8\u901F", "\u9AD8\u8D28"]).map((lbl, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `dsh-a6-smart-pill ${getTagClass(lbl)}`, children: lbl }, idx)) }),
386
470
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-a6-bar-actions", onClick: (e) => e.stopPropagation(), children: [
387
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
388
- "span",
389
- {
390
- className: "dsh-a6-time-ago",
391
- "data-tooltip": "\u8BE5\u5546\u6237\u8DEF\u7EBF\u5168\u7F51\u6700\u8FD1\u4E00\u6B21\u6210\u529F\u54CD\u5E94\u65F6\u95F4",
392
- children: merchant?.last_success_text || "\u521A\u521A"
393
- }
394
- ),
395
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
396
- "button",
397
- {
398
- type: "button",
399
- className: "dsh-a6-btn dsh-a6-btn-secondary dsh-a6-btn-sm",
400
- onClick: handleProbe,
401
- disabled: isProbing,
402
- "data-tooltip": "\u5411\u8BE5\u6A21\u578B\u53D1\u9001\u4E00\u6B21\u8BF7\u6C42\u4EE5\u63A2\u6D4B\u5E76\u6355\u83B7\u5176\u5B9E\u9645\u547D\u4E2D\u7684\u5546\u6237 ID\u3001\u4EF7\u683C\u53CA\u5065\u5EB7\u5EA6\u6307\u6807\uFF08\u6D88\u8017\u5C11\u91CFToken\uFF09",
403
- children: isProbing ? "\u63A2\u6D4B\u4E2D..." : "\u63A2\u6D4B\u5546\u5BB6"
404
- }
405
- ),
406
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
407
- "button",
408
- {
409
- type: "button",
410
- className: `dsh-a6-btn dsh-a6-btn-sm ${model.inDsh ? "dsh-a6-btn-in-dsh" : "dsh-a6-btn-primary"}`,
411
- onClick: handleToggleDsh,
412
- "data-tooltip": model.inDsh ? "\u5DF2\u52A0\u5165 DSH \u6A21\u578B\u9009\u62E9\u5668 (\u70B9\u51FB\u79FB\u9664)" : "\u6DFB\u52A0\u81F3 DSH \u6A21\u578B\u9009\u62E9\u5668",
413
- children: model.inDsh ? "\u79FB\u9664\u6A21\u578B" : "\u6DFB\u52A0\u6A21\u578B"
414
- }
415
- ),
416
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
417
- "button",
418
- {
419
- type: "button",
420
- className: `dsh-a6-expand-toggle-btn ${expanded ? "open" : ""}`,
421
- onClick: () => setExpanded(!expanded),
422
- "data-tooltip": expanded ? "\u6536\u8D77\u4EF7\u683C\u8BE6\u60C5" : "\u5C55\u5F00\u5B98\u65B9\u57FA\u51C6\u4EF7\u4E0E\u5546\u6237\u5B9E\u65F6\u4EF7\u5BF9\u6BD4\u8868",
423
- "data-tooltip-pos": "left",
424
- children: expanded ? "\u6536\u8D77" : "\u8BE6\u60C5"
425
- }
426
- )
471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-a6-time-stack", children: [
472
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
473
+ "span",
474
+ {
475
+ className: "dsh-a6-time-ago",
476
+ "data-tooltip": "\u8BE5\u5546\u6237\u8DEF\u7EBF\u5168\u7F51\u6700\u8FD1\u4E00\u6B21\u6210\u529F\u54CD\u5E94\u65F6\u95F4",
477
+ children: [
478
+ "\u5168\u7F51\u6700\u8FD1\uFF1A",
479
+ merchant?.last_success_text || "\u521A\u521A"
480
+ ]
481
+ }
482
+ ),
483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
484
+ "span",
485
+ {
486
+ className: `dsh-a6-time-ago dsh-a6-route-snapshot${model.lastRoutedAt ? "" : " never"}`,
487
+ "data-tooltip": model.lastRoutedAt ? `\u4E2A\u4EBA\u6700\u540E\u4E00\u6B21\u8BF7\u6C42\u8BE5\u6A21\u578B\u7684\u65F6\u95F4\uFF08\u5546\u5BB6\u8DEF\u7531\u60C5\u51B5\u7684\u622A\u6B62\u65F6\u6548\uFF09${formatAbsolute(model.lastRoutedAt)}` : "\u65E5\u5FD7\u4E2D\u6682\u65E0\u8BE5\u6A21\u578B\u7684\u8DEF\u7531\u8BB0\u5F55",
488
+ children: [
489
+ "\u4E2A\u4EBA\u6700\u8FD1\uFF1A",
490
+ model.lastRoutedText || "\u4ECE\u672A\u8DEF\u7531"
491
+ ]
492
+ }
493
+ )
494
+ ] }),
495
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-a6-bar-actions-btns", children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
497
+ "button",
498
+ {
499
+ type: "button",
500
+ className: "dsh-a6-btn dsh-a6-btn-secondary dsh-a6-btn-sm",
501
+ onClick: handleProbe,
502
+ disabled: isProbing,
503
+ "data-tooltip": "\u5411\u8BE5\u6A21\u578B\u53D1\u9001\u4E00\u6B21\u8BF7\u6C42\u4EE5\u63A2\u6D4B\u5E76\u6355\u83B7\u5176\u5B9E\u9645\u547D\u4E2D\u7684\u5546\u6237 ID\u3001\u4EF7\u683C\u53CA\u5065\u5EB7\u5EA6\u6307\u6807\uFF08\u6D88\u8017\u5C11\u91CFToken\uFF09",
504
+ children: isProbing ? "\u63A2\u6D4B\u4E2D..." : "\u63A2\u6D4B\u5546\u5BB6"
505
+ }
506
+ ),
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
508
+ "button",
509
+ {
510
+ type: "button",
511
+ className: `dsh-a6-btn dsh-a6-btn-sm ${model.inDsh ? "dsh-a6-btn-in-dsh" : "dsh-a6-btn-primary"}`,
512
+ onClick: handleToggleDsh,
513
+ "data-tooltip": model.inDsh ? "\u5DF2\u52A0\u5165 DSH \u6A21\u578B\u9009\u62E9\u5668 (\u70B9\u51FB\u79FB\u9664)" : "\u6DFB\u52A0\u81F3 DSH \u6A21\u578B\u9009\u62E9\u5668",
514
+ children: model.inDsh ? "\u79FB\u9664\u6A21\u578B" : "\u6DFB\u52A0\u6A21\u578B"
515
+ }
516
+ ),
517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
518
+ "button",
519
+ {
520
+ type: "button",
521
+ className: `dsh-a6-expand-toggle-btn ${expanded ? "open" : ""}`,
522
+ onClick: () => setExpanded(!expanded),
523
+ "data-tooltip": expanded ? "\u6536\u8D77\u4EF7\u683C\u8BE6\u60C5" : "\u5C55\u5F00\u5B98\u65B9\u57FA\u51C6\u4EF7\u4E0E\u5546\u6237\u5B9E\u65F6\u4EF7\u5BF9\u6BD4\u8868",
524
+ "data-tooltip-pos": "left",
525
+ children: expanded ? "\u6536\u8D77" : "\u8BE6\u60C5"
526
+ }
527
+ )
528
+ ] })
427
529
  ] })
428
530
  ] }),
429
531
  expanded && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-a6-detail-container", children: [
@@ -920,21 +1022,61 @@ var A6ApiSettingsPanel = () => {
920
1022
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-a6-container", children: [
921
1023
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-a6-main-header", children: [
922
1024
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-a6-header-text", children: [
923
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "dsh-a6-main-title", children: "A6API \u805A\u5408\u7AD9" }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "dsh-a6-main-title", children: "A6api" }),
924
1026
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dsh-a6-main-subtitle", children: "\u805A\u5408\u5168\u7403\u4E3B\u6D41\u4E0E\u9AD8\u6027\u4EF7\u6BD4\u6A21\u578B\uFF0C\u5B9E\u65F6\u76D1\u63A7\u5546\u6237\u6307\u6807\u3001\u4EF7\u683C\u500D\u7387\u4E0E\u8D26\u6237\u8D44\u4EA7\u3002" })
925
1027
  ] }),
926
- state.balance?.hasAccountAuth && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
927
- "div",
928
- {
929
- className: "dsh-a6-header-balance-badge",
930
- onClick: () => setActiveTab("account"),
931
- title: "\u70B9\u51FB\u5207\u6362\u81F3\u300C\u8D26\u6237\u8D44\u4EA7\u300D\u9875\u9762",
932
- children: [
933
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-a6-hb-label", children: "\u8D26\u6237\u4F59\u989D:" }),
934
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-a6-hb-amount", children: state.balance.accountBalanceFormatted })
935
- ]
936
- }
937
- )
1028
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-a6-header-badges", children: [
1029
+ state.balance?.hasAccountAuth && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1030
+ "div",
1031
+ {
1032
+ className: "dsh-a6-header-balance-badge",
1033
+ onClick: () => setActiveTab("account"),
1034
+ title: "\u70B9\u51FB\u5207\u6362\u81F3\u300C\u8D26\u6237\u8D44\u4EA7\u300D\u9875\u9762",
1035
+ children: [
1036
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-a6-hb-label", children: "\u8D26\u6237\u4F59\u989D:" }),
1037
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-a6-hb-amount", children: state.balance.accountBalanceFormatted })
1038
+ ]
1039
+ }
1040
+ ),
1041
+ (() => {
1042
+ const n = state.priceFluctuation?.pendingCount ?? 0;
1043
+ const pf = state.priceFluctuation;
1044
+ const hasAuth = pf?.hasAuth !== false && !pf?.authError && Boolean(state.config?.hasToken);
1045
+ const isAuthError = Boolean(pf?.authError);
1046
+ const isZero = n === 0;
1047
+ const isDisabled = !hasAuth || isZero;
1048
+ const cls = isDisabled ? !hasAuth ? "dsh-a6-price-pill disabled" : "dsh-a6-price-pill is-zero is-disabled-zero" : "dsh-a6-price-pill has-change";
1049
+ const title = !hasAuth ? isAuthError ? "\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C\u5DF2\u5931\u6548\uFF0C\u8BF7\u524D\u5F80\u57FA\u7840\u914D\u7F6E\u66F4\u65B0" : "\u672A\u914D\u7F6E\u7CFB\u7EDF\u8BBF\u95EE\u4EE4\u724C\uFF0C\u65E0\u6CD5\u83B7\u53D6\u4EF7\u683C\u53D8\u52A8" : isZero ? "\u6682\u65E0\u4EF7\u683C\u53D8\u52A8" : `\u6709 ${n} \u6761\u4EF7\u683C\u53D8\u52A8\u5F85\u5904\u7406\uFF0C\u70B9\u51FB\u524D\u5F80\u5B98\u7F51\u5904\u7406`;
1050
+ const onClick = () => {
1051
+ if (isDisabled) return;
1052
+ window.open("https://a6api.com/console/token", "_blank", "noopener");
1053
+ };
1054
+ const onKeyDown = (e) => {
1055
+ if (isDisabled) return;
1056
+ if (e.key === "Enter" || e.key === " ") {
1057
+ e.preventDefault();
1058
+ onClick();
1059
+ }
1060
+ };
1061
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1062
+ "div",
1063
+ {
1064
+ className: cls,
1065
+ onClick: isDisabled ? void 0 : onClick,
1066
+ onKeyDown,
1067
+ tabIndex: isDisabled ? -1 : 0,
1068
+ title,
1069
+ role: "button",
1070
+ "aria-disabled": isDisabled,
1071
+ style: isDisabled ? { cursor: "not-allowed" } : void 0,
1072
+ children: [
1073
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-a6-price-pill-label", children: "\u4EF7\u683C\u6CE2\u52A8\uFF1A" }),
1074
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-a6-price-pill-count", children: !hasAuth ? "--" : n })
1075
+ ]
1076
+ }
1077
+ );
1078
+ })()
1079
+ ] })
938
1080
  ] }),
939
1081
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-a6-nav-tabs", children: [
940
1082
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -1148,6 +1290,13 @@ var main_default = `/* A6API Plugin Styles - Clean Professional DSH Native Theme
1148
1290
  transition: all 0.15s;
1149
1291
  }
1150
1292
 
1293
+ .dsh-a6-header-badges {
1294
+ display: inline-flex;
1295
+ align-items: center;
1296
+ gap: 8px;
1297
+ flex-wrap: wrap;
1298
+ }
1299
+
1151
1300
  .dsh-a6-header-balance-badge:hover {
1152
1301
  border-color: #10b981;
1153
1302
  background: rgba(16, 185, 129, 0.04);
@@ -1158,6 +1307,91 @@ var main_default = `/* A6API Plugin Styles - Clean Professional DSH Native Theme
1158
1307
  color: var(--dsw-alias-label-secondary, #64748b);
1159
1308
  }
1160
1309
 
1310
+ .dsh-a6-price-pill {
1311
+ display: inline-flex;
1312
+ align-items: center;
1313
+ gap: 6px;
1314
+ padding: 5px 12px;
1315
+ border-radius: 16px;
1316
+ border: 1px solid var(--dsw-alias-border-l2, #e2e8f0);
1317
+ cursor: pointer;
1318
+ font-size: 12px;
1319
+ font-weight: 500;
1320
+ line-height: 1;
1321
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
1322
+ transition: all 0.15s;
1323
+ user-select: none;
1324
+ /* \u4E0E\u8D26\u6237\u4F59\u989D\u80F6\u56CA\u7B49\u9AD8\uFF1A\u7EE7\u627F\u540C\u6837\u5185\u8FB9\u8DDD\u4E0E\u884C\u9AD8\uFF0C\u907F\u514D\u89C6\u89C9\u5927\u5C0F\u5DEE\u5F02 */
1325
+ min-height: 28px;
1326
+ box-sizing: border-box;
1327
+ }
1328
+
1329
+ .dsh-a6-price-pill.is-zero {
1330
+ background: var(--dsw-alias-bg-layer-2, #ffffff);
1331
+ border-color: var(--dsw-alias-border-l2, #e2e8f0);
1332
+ color: var(--dsw-alias-label-secondary, #64748b);
1333
+ }
1334
+
1335
+ .dsh-a6-price-pill.is-zero:hover {
1336
+ border-color: #10b981;
1337
+ background: rgba(16, 185, 129, 0.04);
1338
+ }
1339
+
1340
+ .dsh-a6-price-pill.is-zero.is-disabled-zero:hover {
1341
+ border-color: var(--dsw-alias-border-l2, #e2e8f0);
1342
+ background: var(--dsw-alias-bg-layer-2, #ffffff);
1343
+ }
1344
+
1345
+ .dsh-a6-price-pill.has-change {
1346
+ background: var(--dsw-alias-bg-layer-2, #ffffff);
1347
+ border-color: var(--dsw-alias-border-l2, #e2e8f0);
1348
+ color: var(--dsw-alias-label-secondary, #64748b);
1349
+ }
1350
+
1351
+ .dsh-a6-price-pill.has-change:hover {
1352
+ border-color: #ef4444;
1353
+ background: rgba(239, 68, 68, 0.03);
1354
+ }
1355
+
1356
+ .dsh-a6-price-pill.is-disabled-zero {
1357
+ cursor: not-allowed;
1358
+ opacity: 0.9;
1359
+ }
1360
+
1361
+ .dsh-a6-price-pill.disabled {
1362
+ background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.04));
1363
+ border-color: var(--dsw-alias-border-l3, #cbd5e1);
1364
+ color: var(--dsw-alias-label-tertiary, #94a3b8);
1365
+ cursor: not-allowed;
1366
+ opacity: 0.85;
1367
+ }
1368
+
1369
+ .dsh-a6-price-pill-label {
1370
+ font-size: 12px;
1371
+ font-weight: 500;
1372
+ color: var(--dsw-alias-label-secondary, #64748b);
1373
+ }
1374
+
1375
+ .dsh-a6-price-pill-count {
1376
+ font-size: 13px;
1377
+ font-weight: 700;
1378
+ min-width: 8px;
1379
+ text-align: center;
1380
+ line-height: 1;
1381
+ }
1382
+
1383
+ .dsh-a6-price-pill.is-zero .dsh-a6-price-pill-count {
1384
+ color: #10b981;
1385
+ }
1386
+
1387
+ .dsh-a6-price-pill.has-change .dsh-a6-price-pill-count {
1388
+ color: #ef4444;
1389
+ }
1390
+
1391
+ .dsh-a6-price-pill.disabled .dsh-a6-price-pill-count {
1392
+ color: var(--dsw-alias-label-tertiary, #94a3b8);
1393
+ }
1394
+
1161
1395
  .dsh-a6-hb-amount {
1162
1396
  font-size: 13px;
1163
1397
  font-weight: 700;
@@ -1596,12 +1830,22 @@ var main_default = `/* A6API Plugin Styles - Clean Professional DSH Native Theme
1596
1830
  color: #059669;
1597
1831
  }
1598
1832
 
1599
- /* Right Actions Group */
1833
+ /* Right Actions Group \u2014 \u9ED8\u8BA4\u9760\u53F3\uFF0C\u7A84\u5C4F\u65F6\u5360\u6EE1\u884C\u5BBD\u4E24\u7AEF\u5BF9\u9F50 */
1600
1834
  .dsh-a6-bar-actions {
1601
1835
  display: flex;
1602
1836
  align-items: center;
1603
- gap: 8px;
1604
- flex-shrink: 0;
1837
+ gap: 12px;
1838
+ flex: 0 0 auto;
1839
+ margin-left: auto;
1840
+ }
1841
+
1842
+ @media (max-width: 900px) {
1843
+ .dsh-a6-bar-actions {
1844
+ flex: 1 1 100%;
1845
+ width: 100%;
1846
+ margin-left: 0;
1847
+ justify-content: space-between;
1848
+ }
1605
1849
  }
1606
1850
 
1607
1851
  .dsh-a6-time-ago {
@@ -1609,6 +1853,28 @@ var main_default = `/* A6API Plugin Styles - Clean Professional DSH Native Theme
1609
1853
  color: var(--dsw-alias-label-tertiary, #94a3b8);
1610
1854
  }
1611
1855
 
1856
+ /* \u4E24\u884C\u65F6\u95F4\u6233\u5782\u76F4\u53E0\u653E (\u5168\u7F51\u6700\u8FD1 / \u4E2A\u4EBA\u6700\u8FD1) \u2014 \u5DE6\u5BF9\u9F50 */
1857
+ .dsh-a6-time-stack {
1858
+ display: flex;
1859
+ flex-direction: column;
1860
+ align-items: flex-start;
1861
+ gap: 2px;
1862
+ flex-shrink: 0;
1863
+ text-align: left;
1864
+ }
1865
+
1866
+ /* \u53F3\u4FA7\u4E09\u6309\u94AE\u7EC4 */
1867
+ .dsh-a6-bar-actions-btns {
1868
+ display: flex;
1869
+ align-items: center;
1870
+ gap: 8px;
1871
+ flex-shrink: 0;
1872
+ }
1873
+
1874
+ .dsh-a6-route-snapshot.never {
1875
+ color: var(--dsw-alias-label-quaternary, #cbd5e1);
1876
+ }
1877
+
1612
1878
  .dsh-a6-btn-in-dsh {
1613
1879
  background: rgba(16, 185, 129, 0.12);
1614
1880
  border: 1px solid rgba(16, 185, 129, 0.35);
@@ -2366,19 +2632,24 @@ var main_default = `/* A6API Plugin Styles - Clean Professional DSH Native Theme
2366
2632
  bottom: calc(100% + 7px);
2367
2633
  left: 50%;
2368
2634
  transform: translateX(-50%);
2369
- padding: 5px 9px;
2370
- background: rgba(15, 23, 42, 0.94);
2635
+ padding: 6px 10px;
2636
+ background: rgba(15, 23, 42, 0.96);
2371
2637
  color: #f8fafc;
2372
2638
  font-size: 11px;
2373
2639
  font-weight: 400;
2374
- line-height: 1.35;
2375
- white-space: nowrap;
2376
- border-radius: 5px;
2377
- box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
2640
+ line-height: 1.4;
2641
+ white-space: normal;
2642
+ max-width: min(320px, 85vw);
2643
+ width: max-content;
2644
+ text-align: left;
2645
+ word-break: break-word;
2646
+ overflow-wrap: anywhere;
2647
+ border-radius: 6px;
2648
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
2378
2649
  pointer-events: none;
2379
2650
  opacity: 0;
2380
2651
  visibility: hidden;
2381
- transition: opacity 0.04s ease-out, visibility 0.04s ease-out;
2652
+ transition: opacity 0.08s ease-out, visibility 0.08s ease-out;
2382
2653
  z-index: 99999;
2383
2654
  border: 1px solid rgba(255, 255, 255, 0.12);
2384
2655
  }
@@ -2484,15 +2755,24 @@ var main_default = `/* A6API Plugin Styles - Clean Professional DSH Native Theme
2484
2755
  border-color: transparent transparent rgba(15, 23, 42, 0.94) transparent;
2485
2756
  }
2486
2757
 
2487
- /* Card actions tooltip default alignment */
2758
+ /* Card actions tooltip: keep centered (avoid left:0 right-edge overflow) */
2488
2759
  .dsh-a6-bar-actions [data-tooltip]:not([data-tooltip-pos])::after {
2489
- left: 0;
2490
- transform: none;
2760
+ left: 50%;
2761
+ transform: translateX(-50%);
2762
+ right: auto;
2491
2763
  }
2492
2764
 
2493
2765
  .dsh-a6-bar-actions [data-tooltip]:not([data-tooltip-pos])::before {
2494
- left: 16px;
2495
- transform: none;
2766
+ left: 50%;
2767
+ transform: translateX(-50%);
2768
+ right: auto;
2769
+ }
2770
+
2771
+ /* When JS portal tooltip is active, hide pseudo to avoid double rendering */
2772
+ body.dsh-a6api-tooltip-active [data-tooltip]::after,
2773
+ body.dsh-a6api-tooltip-active [data-tooltip]::before {
2774
+ opacity: 0 !important;
2775
+ visibility: hidden !important;
2496
2776
  }
2497
2777
  `;
2498
2778
 
@@ -2509,9 +2789,204 @@ function injectStyles() {
2509
2789
  document.head.appendChild(style);
2510
2790
  }
2511
2791
  }
2792
+ function setupGlobalTooltip() {
2793
+ if (typeof document === "undefined" || typeof window === "undefined") return;
2794
+ if (window.__dsh_a6api_tooltip_setup) return () => {
2795
+ };
2796
+ window.__dsh_a6api_tooltip_setup = true;
2797
+ const portalId = "dsh-a6api-tooltip";
2798
+ const arrowId = "dsh-a6api-tooltip-arrow";
2799
+ let portal = document.getElementById(portalId);
2800
+ let arrow = document.getElementById(arrowId);
2801
+ if (!portal) {
2802
+ portal = document.createElement("div");
2803
+ portal.id = portalId;
2804
+ portal.setAttribute("role", "tooltip");
2805
+ portal.style.cssText = "position:fixed;left:0;top:0;transform:translate(-9999px,-9999px);padding:6px 10px;background:rgba(15,23,42,0.96);color:#f8fafc;font-size:11px;line-height:1.4;border-radius:6px;box-shadow:0 6px 18px rgba(0,0,0,0.28);border:1px solid rgba(255,255,255,0.12);max-width:min(320px,85vw);width:max-content;white-space:normal;word-break:break-word;overflow-wrap:anywhere;text-align:left;pointer-events:none;opacity:0;visibility:hidden;transition:opacity 0.08s;z-index:2147483647;";
2806
+ document.body.appendChild(portal);
2807
+ }
2808
+ if (!arrow) {
2809
+ arrow = document.createElement("div");
2810
+ arrow.id = arrowId;
2811
+ arrow.style.cssText = "position:fixed;left:0;top:0;width:8px;height:8px;background:rgba(15,23,42,0.96);transform:translate(-9999px,-9999px) rotate(45deg);border-left:1px solid rgba(255,255,255,0.12);border-top:1px solid rgba(255,255,255,0.12);pointer-events:none;opacity:0;visibility:hidden;z-index:2147483646;";
2812
+ document.body.appendChild(arrow);
2813
+ }
2814
+ let currentTarget = null;
2815
+ function hide() {
2816
+ if (!portal || !arrow) return;
2817
+ portal.style.opacity = "0";
2818
+ portal.style.visibility = "hidden";
2819
+ arrow.style.opacity = "0";
2820
+ arrow.style.visibility = "hidden";
2821
+ document.body.classList.remove("dsh-a6api-tooltip-active");
2822
+ currentTarget = null;
2823
+ }
2824
+ function position(target) {
2825
+ if (!portal || !arrow || !target) return;
2826
+ const text = target.getAttribute("data-tooltip");
2827
+ if (!text) return;
2828
+ portal.textContent = text;
2829
+ portal.style.visibility = "hidden";
2830
+ portal.style.opacity = "0";
2831
+ portal.style.transform = "translate(-9999px,-9999px)";
2832
+ portal.style.visibility = "visible";
2833
+ const ttRect = portal.getBoundingClientRect();
2834
+ portal.style.visibility = "hidden";
2835
+ const rect = target.getBoundingClientRect();
2836
+ const gap = 8;
2837
+ const vw = window.innerWidth;
2838
+ const vh = window.innerHeight;
2839
+ const margin = 8;
2840
+ let pos = target.getAttribute("data-tooltip-pos") || "";
2841
+ if (!pos) {
2842
+ const spaceAbove = rect.top;
2843
+ const spaceBelow = vh - rect.bottom;
2844
+ if (spaceAbove < ttRect.height + gap + 12 && spaceBelow > spaceAbove) pos = "down";
2845
+ else pos = "top";
2846
+ }
2847
+ let top = 0;
2848
+ let left = 0;
2849
+ let arrowTop = 0;
2850
+ let arrowLeft = 0;
2851
+ const isDown = pos.startsWith("down");
2852
+ if (isDown) {
2853
+ top = rect.bottom + gap;
2854
+ arrowTop = rect.bottom + gap - 4;
2855
+ } else {
2856
+ top = rect.top - ttRect.height - gap;
2857
+ arrowTop = rect.top - gap - 4;
2858
+ }
2859
+ if (pos === "left") {
2860
+ left = rect.right - ttRect.width;
2861
+ arrowLeft = rect.right - 14;
2862
+ } else if (pos === "right") {
2863
+ left = rect.left;
2864
+ arrowLeft = rect.left + 14;
2865
+ } else if (pos === "down-left") {
2866
+ left = rect.right - ttRect.width;
2867
+ arrowLeft = rect.right - 14;
2868
+ } else if (pos === "down-right") {
2869
+ left = rect.left;
2870
+ arrowLeft = rect.left + 14;
2871
+ } else {
2872
+ left = rect.left + rect.width / 2 - ttRect.width / 2;
2873
+ arrowLeft = rect.left + rect.width / 2 - 4;
2874
+ }
2875
+ left = Math.max(margin, Math.min(left, vw - ttRect.width - margin));
2876
+ const minArrow = left + 8;
2877
+ const maxArrow = left + ttRect.width - 12;
2878
+ arrowLeft = Math.max(minArrow, Math.min(arrowLeft, maxArrow));
2879
+ if (top < margin) top = margin;
2880
+ if (top + ttRect.height > vh - margin) top = vh - ttRect.height - margin;
2881
+ portal.style.transform = "translate(" + left + "px," + top + "px)";
2882
+ portal.style.visibility = "visible";
2883
+ portal.style.opacity = "1";
2884
+ arrow.style.transform = "translate(" + arrowLeft + "px," + arrowTop + "px) rotate(45deg)";
2885
+ arrow.style.visibility = "visible";
2886
+ arrow.style.opacity = "1";
2887
+ document.body.classList.add("dsh-a6api-tooltip-active");
2888
+ }
2889
+ let hoverTimer = null;
2890
+ const onMouseOver = (e) => {
2891
+ const target = e.target && e.target.closest ? e.target.closest("[data-tooltip]") : null;
2892
+ if (!target) return;
2893
+ currentTarget = target;
2894
+ if (hoverTimer) clearTimeout(hoverTimer);
2895
+ hoverTimer = setTimeout(() => {
2896
+ if (currentTarget === target) position(target);
2897
+ }, 30);
2898
+ };
2899
+ const onMouseOut = (e) => {
2900
+ const target = e.target && e.target.closest ? e.target.closest("[data-tooltip]") : null;
2901
+ if (!target) return;
2902
+ const related = e.relatedTarget;
2903
+ if (related && target.contains(related)) return;
2904
+ if (currentTarget === target) {
2905
+ if (hoverTimer) clearTimeout(hoverTimer);
2906
+ hide();
2907
+ }
2908
+ };
2909
+ const onFocusIn = (e) => {
2910
+ const target = e.target && e.target.closest ? e.target.closest("[data-tooltip]") : null;
2911
+ if (!target) return;
2912
+ currentTarget = target;
2913
+ position(target);
2914
+ };
2915
+ const onFocusOut = (e) => {
2916
+ const target = e.target && e.target.closest ? e.target.closest("[data-tooltip]") : null;
2917
+ if (!target) return;
2918
+ if (currentTarget === target) hide();
2919
+ };
2920
+ const onScrollOrResize = () => {
2921
+ if (currentTarget) {
2922
+ if (document.body.contains(currentTarget) && currentTarget.matches && currentTarget.matches(":hover")) {
2923
+ position(currentTarget);
2924
+ } else if (document.activeElement === currentTarget) {
2925
+ position(currentTarget);
2926
+ } else {
2927
+ hide();
2928
+ }
2929
+ }
2930
+ };
2931
+ document.addEventListener("mouseover", onMouseOver, true);
2932
+ document.addEventListener("mouseout", onMouseOut, true);
2933
+ document.addEventListener("focusin", onFocusIn, true);
2934
+ document.addEventListener("focusout", onFocusOut, true);
2935
+ window.addEventListener("scroll", onScrollOrResize, true);
2936
+ window.addEventListener("resize", onScrollOrResize);
2937
+ document.addEventListener("scroll", onScrollOrResize, true);
2938
+ return () => {
2939
+ document.removeEventListener("mouseover", onMouseOver, true);
2940
+ document.removeEventListener("mouseout", onMouseOut, true);
2941
+ document.removeEventListener("focusin", onFocusIn, true);
2942
+ document.removeEventListener("focusout", onFocusOut, true);
2943
+ window.removeEventListener("scroll", onScrollOrResize, true);
2944
+ window.removeEventListener("resize", onScrollOrResize);
2945
+ document.removeEventListener("scroll", onScrollOrResize, true);
2946
+ if (hoverTimer) clearTimeout(hoverTimer);
2947
+ try {
2948
+ portal?.remove();
2949
+ } catch {
2950
+ }
2951
+ try {
2952
+ arrow?.remove();
2953
+ } catch {
2954
+ }
2955
+ document.body.classList.remove("dsh-a6api-tooltip-active");
2956
+ try {
2957
+ delete window.__dsh_a6api_tooltip_setup;
2958
+ } catch {
2959
+ window.__dsh_a6api_tooltip_setup = void 0;
2960
+ }
2961
+ currentTarget = null;
2962
+ };
2963
+ }
2512
2964
  function apply(ctx) {
2513
2965
  injectStyles();
2966
+ if (typeof window !== "undefined") {
2967
+ try {
2968
+ ctx.effect(() => {
2969
+ const dispose = setupGlobalTooltip();
2970
+ return () => {
2971
+ try {
2972
+ if (typeof dispose === "function") dispose();
2973
+ } catch {
2974
+ }
2975
+ };
2976
+ }, "dsh-a6api: tooltip portal");
2977
+ } catch {
2978
+ }
2979
+ }
2514
2980
  if (typeof window === "undefined") return;
2981
+ try {
2982
+ setTimeout(() => {
2983
+ try {
2984
+ store.initPricePolling();
2985
+ } catch {
2986
+ }
2987
+ }, 1500);
2988
+ } catch {
2989
+ }
2515
2990
  try {
2516
2991
  const slots = ctx?.slots || (ctx?.get ? ctx.get("slots") : null);
2517
2992
  if (!slots || typeof slots.inject !== "function") return;
@@ -2520,8 +2995,8 @@ function apply(ctx) {
2520
2995
  {
2521
2996
  name: "settings.section",
2522
2997
  id: "dsh-a6api",
2523
- order: 4,
2524
- label: () => "A6API \u805A\u5408\u7AD9"
2998
+ order: 11,
2999
+ label: () => "A6api"
2525
3000
  },
2526
3001
  A6ApiSettingsPanel
2527
3002
  );