Match options branding: title prefix and ETH/BTC header quotes.
Use 「加密货币 | OKX 对冲」 and show live top-right ETHUSDT/BTCUSDT quotes like the options instance. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 顶栏右上角 ETH/BTC 实时报价(公共 ticker).
|
||||
*/
|
||||
(function (global) {
|
||||
const BASES = ["ETH", "BTC"];
|
||||
|
||||
function $(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function refreshMs() {
|
||||
const raw = document.body && document.body.getAttribute("data-price-refresh-ms");
|
||||
const n = Number(raw);
|
||||
return n > 2000 ? n : 5000;
|
||||
}
|
||||
|
||||
function fmtLast(base, v) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "—";
|
||||
const n = Number(v);
|
||||
if (base === "BTC") {
|
||||
return n.toLocaleString("en-US", { minimumFractionDigits: 1, maximumFractionDigits: 1 });
|
||||
}
|
||||
return n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function fmtChg(base, v) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "—";
|
||||
const n = Number(v);
|
||||
const abs = Math.abs(n);
|
||||
const body =
|
||||
base === "BTC"
|
||||
? abs.toLocaleString("en-US", { minimumFractionDigits: 1, maximumFractionDigits: 1 })
|
||||
: abs.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
if (n > 0) return "+" + body;
|
||||
if (n < 0) return "-" + body;
|
||||
return body;
|
||||
}
|
||||
|
||||
function fmtPct(v) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "—";
|
||||
const n = Number(v);
|
||||
const body = Math.abs(n).toFixed(2) + "%";
|
||||
if (n > 0) return "+" + body;
|
||||
if (n < 0) return "-" + body;
|
||||
return body;
|
||||
}
|
||||
|
||||
function paintRow(row, q) {
|
||||
if (!row || !q) return;
|
||||
const last = row.querySelector('[data-hlq="last"]');
|
||||
const chg = row.querySelector('[data-hlq="chg"]');
|
||||
const pct = row.querySelector('[data-hlq="pct"]');
|
||||
const base = row.getAttribute("data-base") || "";
|
||||
if (last) last.textContent = fmtLast(base, q.last);
|
||||
if (chg) chg.textContent = fmtChg(base, q.change);
|
||||
if (pct) pct.textContent = fmtPct(q.pct);
|
||||
const up = Number(q.change) > 0 || Number(q.pct) > 0;
|
||||
const down = Number(q.change) < 0 || Number(q.pct) < 0;
|
||||
row.classList.toggle("is-up", up);
|
||||
row.classList.toggle("is-down", down);
|
||||
}
|
||||
|
||||
async function fetchQuotes() {
|
||||
const r = await fetch("/api/header-quotes", {
|
||||
credentials: "same-origin",
|
||||
cache: "no-store",
|
||||
});
|
||||
const d = await r.json();
|
||||
if (!d || !d.ok) throw new Error((d && d.msg) || "行情失败");
|
||||
return d.quotes || [];
|
||||
}
|
||||
|
||||
async function tick() {
|
||||
const host = $("header-live-quotes");
|
||||
if (!host) return;
|
||||
try {
|
||||
const quotes = await fetchQuotes();
|
||||
BASES.forEach(function (base) {
|
||||
const row = host.querySelector('.hlq-row[data-base="' + base + '"]');
|
||||
const q = quotes.find(function (x) {
|
||||
return String((x && x.base) || "").toUpperCase() === base;
|
||||
});
|
||||
if (row && q && !q.error) paintRow(row, q);
|
||||
});
|
||||
} catch (_) {
|
||||
/* 静默:保持上次有效行情 */
|
||||
}
|
||||
}
|
||||
|
||||
let timer = null;
|
||||
function boot() {
|
||||
if (!$("header-live-quotes")) return;
|
||||
void tick();
|
||||
if (timer) clearInterval(timer);
|
||||
timer = setInterval(function () {
|
||||
void tick();
|
||||
}, refreshMs());
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
|
||||
global.HeaderLiveQuotes = { refresh: tick, boot: boot };
|
||||
})(window);
|
||||
Reference in New Issue
Block a user