22de1fd40c
Use 「加密货币 | OKX 对冲」 and show live top-right ETHUSDT/BTCUSDT quotes like the options instance. Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
3.2 KiB
JavaScript
108 lines
3.2 KiB
JavaScript
/**
|
|
* 顶栏右上角 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);
|