总览从在线客户端快照展示 ETH/BTC 报价,优先 live。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+38
-2
@@ -168,8 +168,39 @@ body.page-login .main {
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.overview-bj-clock {
|
||||
.overview-quotes {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.overview-quote-row {
|
||||
display: grid;
|
||||
grid-template-columns: 2.4em minmax(4.6em, auto) minmax(3.4em, auto) minmax(3.6em, auto);
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.overview-quote-sym { color: var(--muted); }
|
||||
.overview-quote-last,
|
||||
.overview-quote-chg,
|
||||
.overview-quote-pct { text-align: right; }
|
||||
.overview-quote-row.is-up .overview-quote-last,
|
||||
.overview-quote-row.is-up .overview-quote-chg,
|
||||
.overview-quote-row.is-up .overview-quote-pct { color: var(--ok); }
|
||||
.overview-quote-row.is-down .overview-quote-last,
|
||||
.overview-quote-row.is-down .overview-quote-chg,
|
||||
.overview-quote-row.is-down .overview-quote-pct { color: var(--bad); }
|
||||
.overview-quote-row.is-flat .overview-quote-last,
|
||||
.overview-quote-row.is-flat .overview-quote-chg,
|
||||
.overview-quote-row.is-flat .overview-quote-pct { color: var(--text); }
|
||||
.overview-bj-clock {
|
||||
margin-left: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
@@ -236,10 +267,15 @@ body.page-login .main {
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.page-head { flex-wrap: wrap; }
|
||||
.overview-bj-clock {
|
||||
.overview-quotes {
|
||||
margin-left: 0;
|
||||
order: 3;
|
||||
width: 100%;
|
||||
}
|
||||
.overview-bj-clock {
|
||||
margin-left: 0;
|
||||
order: 4;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
+123
@@ -566,6 +566,127 @@
|
||||
});
|
||||
}
|
||||
|
||||
function fmtQuotePx(n) {
|
||||
const v = Number(n);
|
||||
if (!Number.isFinite(v)) return "—";
|
||||
const abs = Math.abs(v);
|
||||
let digits = 2;
|
||||
if (abs >= 10000) digits = 1;
|
||||
else if (abs >= 1000) digits = 2;
|
||||
else if (abs >= 1) digits = 2;
|
||||
else digits = 4;
|
||||
try {
|
||||
return v.toLocaleString("en-US", {
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits,
|
||||
});
|
||||
} catch (e) {
|
||||
return v.toFixed(digits);
|
||||
}
|
||||
}
|
||||
|
||||
function fmtQuoteSigned(n, isPct) {
|
||||
const v = Number(n);
|
||||
if (!Number.isFinite(v)) return "—";
|
||||
const abs = Math.abs(v);
|
||||
const digits = isPct ? 2 : abs >= 100 ? 1 : 2;
|
||||
let body;
|
||||
try {
|
||||
body = abs.toLocaleString("en-US", {
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits,
|
||||
});
|
||||
} catch (e) {
|
||||
body = abs.toFixed(digits);
|
||||
}
|
||||
const sign = v > 0 ? "+" : v < 0 ? "-" : "";
|
||||
return sign + body + (isPct ? "%" : "");
|
||||
}
|
||||
|
||||
function quoteDirClass(n) {
|
||||
const v = Number(n);
|
||||
if (!Number.isFinite(v) || v === 0) return "is-flat";
|
||||
return v > 0 ? "is-up" : "is-down";
|
||||
}
|
||||
|
||||
function snapshotTickers(row) {
|
||||
const snap = (row && row.snapshot) || {};
|
||||
const quotes = snap.quotes && typeof snap.quotes === "object" ? snap.quotes : {};
|
||||
const list = quotes.tickers;
|
||||
return Array.isArray(list) ? list : [];
|
||||
}
|
||||
|
||||
function tickersHaveLast(tickers) {
|
||||
return (tickers || []).some(function (t) {
|
||||
return t && t.last != null && t.last !== "" && Number.isFinite(Number(t.last));
|
||||
});
|
||||
}
|
||||
|
||||
function pickQuoteSource(list) {
|
||||
let best = null;
|
||||
(list || []).forEach(function (row) {
|
||||
if (!row || !row.online) return;
|
||||
const tickers = snapshotTickers(row);
|
||||
if (!tickersHaveLast(tickers)) return;
|
||||
const mode = String((((row.snapshot || {}).instance || {}).mode) || "").toLowerCase();
|
||||
const live = mode === "live" ? 1 : 0;
|
||||
const pulled = String(row.pulled_at || ((row.snapshot || {}).quotes || {}).updated_at || "");
|
||||
const cand = { tickers: tickers, name: row.name || "", live: live, pulled: pulled };
|
||||
if (!best) {
|
||||
best = cand;
|
||||
return;
|
||||
}
|
||||
if (cand.live !== best.live) {
|
||||
if (cand.live > best.live) best = cand;
|
||||
return;
|
||||
}
|
||||
if (cand.pulled > best.pulled) best = cand;
|
||||
});
|
||||
return best;
|
||||
}
|
||||
|
||||
function paintOverviewQuotes(list) {
|
||||
const root = document.getElementById("overview-quotes");
|
||||
if (!root) return;
|
||||
const src = pickQuoteSource(list);
|
||||
const tickers = src ? src.tickers : [];
|
||||
["ETH", "BTC"].forEach(function (base) {
|
||||
const row = root.querySelector('.overview-quote-row[data-base="' + base + '"]');
|
||||
if (!row) return;
|
||||
let t = null;
|
||||
for (let i = 0; i < tickers.length; i++) {
|
||||
if (String((tickers[i] && tickers[i].base) || "").toUpperCase() === base) {
|
||||
t = tickers[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
row.classList.remove("is-up", "is-down", "is-flat");
|
||||
const lastEl = row.querySelector('[data-field="last"]');
|
||||
const chgEl = row.querySelector('[data-field="change"]');
|
||||
const pctEl = row.querySelector('[data-field="change_pct"]');
|
||||
if (!t || t.last == null) {
|
||||
row.classList.add("is-flat");
|
||||
if (lastEl) lastEl.textContent = "—";
|
||||
if (chgEl) chgEl.textContent = "—";
|
||||
if (pctEl) pctEl.textContent = "—";
|
||||
return;
|
||||
}
|
||||
row.classList.add(quoteDirClass(t.change));
|
||||
if (lastEl) lastEl.textContent = fmtQuotePx(t.last);
|
||||
if (chgEl) chgEl.textContent = fmtQuoteSigned(t.change, false);
|
||||
if (pctEl) pctEl.textContent = fmtQuoteSigned(t.change_pct, true);
|
||||
});
|
||||
if (src && src.name) {
|
||||
root.title =
|
||||
"来自 " +
|
||||
src.name +
|
||||
(src.live ? " · live" : " · sim") +
|
||||
" · 相对北京 08:00";
|
||||
} else {
|
||||
root.title = "暂无在线客户端报价 · 相对北京 08:00";
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const status = document.getElementById("overview-status");
|
||||
const grid = document.getElementById("overview-grid");
|
||||
@@ -577,6 +698,7 @@
|
||||
if (!d.ok) throw new Error(d.msg || "失败");
|
||||
const list = sortInstancesByMode(d.instances || []);
|
||||
renderOverviewSummary(list);
|
||||
paintOverviewQuotes(list);
|
||||
if (!list.length) {
|
||||
grid.innerHTML =
|
||||
'<p class="muted">暂无启用实例,请先到「实例」添加.</p>';
|
||||
@@ -611,6 +733,7 @@
|
||||
box.hidden = true;
|
||||
box.innerHTML = "";
|
||||
}
|
||||
paintOverviewQuotes([]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user