Align header title and ETH/BTC quotes with crypto_okx.
Match fullwidth title separator, page spacing, and move live tickers into the nav right column using the same spot-ticker UI/API. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,107 +0,0 @@
|
||||
/**
|
||||
* 顶栏右上角 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);
|
||||
@@ -3,13 +3,25 @@
|
||||
.order-entry-model-row select.order-entry-category{min-width:4.8em;max-width:6.5em}
|
||||
.order-entry-model-row select.order-entry-model-sub{min-width:7em;max-width:10rem}
|
||||
.order-leverage-hint{font-size:.78rem;color:#cfd3ef;white-space:nowrap;align-self:center}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px 20px}
|
||||
.container{width:100%;max-width:min(1440px,94vw);margin:0 auto;padding:0 clamp(8px,1.5vw,20px)}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px 12px}
|
||||
/* 与 crypto_okx 同款:普通屏铺满;embed 壳限宽 */
|
||||
.container{width:100%;max-width:none;margin:0 auto;padding:0 clamp(6px,1vw,14px)}
|
||||
body[data-embed-shell="1"] .container{
|
||||
width:100%;
|
||||
max-width:min(1800px, 100%);
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
padding:0 clamp(6px,1vw,14px);
|
||||
}
|
||||
.header{display:flex;flex-direction:column;align-items:center;gap:8px;margin-bottom:12px}
|
||||
.header h1{font-size:1.75rem;color:#dbe4ff;text-align:center;line-height:1.25}
|
||||
.header h1{font-size:1.75rem;color:#dbe4ff;text-align:center;line-height:1.25;font-weight:700}
|
||||
.exchange-tag{font-size:.82rem;font-weight:600;color:#b8f5d0;background:#14241e;border:1px solid #2d6a4f;padding:5px 14px;border-radius:999px;letter-spacing:.06em}
|
||||
.header-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center}
|
||||
.top-nav{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-bottom:12px}
|
||||
.top-nav{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:10px 12px;margin-bottom:12px}
|
||||
.top-nav-links{grid-column:2;display:flex;gap:8px;flex-wrap:wrap;justify-content:center;align-items:center;min-width:0}
|
||||
.top-nav-links > a{flex:0 0 auto;white-space:nowrap}
|
||||
.top-nav > a{justify-self:center;align-self:center;width:max-content;max-width:100%}
|
||||
.top-nav-spot-tickers{grid-column:3;justify-self:end;display:flex;flex-direction:column;gap:3px;min-width:0}
|
||||
.top-nav a{padding:6px 10px;border:1px solid #304164;border-radius:8px;background:#151a2a;color:#8fc8ff;text-decoration:none}
|
||||
.top-nav a.active{background:#2a3f6c;color:#dbe4ff}
|
||||
.stat-box{display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));gap:12px;margin-bottom:16px;align-items:stretch}
|
||||
|
||||
@@ -53,22 +53,43 @@ html[data-theme="light"] {
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
display: flex !important;
|
||||
flex-wrap: nowrap !important;
|
||||
justify-content: flex-start !important;
|
||||
align-items: stretch;
|
||||
overflow-x: auto !important;
|
||||
overflow-y: hidden;
|
||||
display: grid !important;
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 8px !important;
|
||||
margin-bottom: 12px !important;
|
||||
padding: 2px 2px 6px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.top-nav-links {
|
||||
grid-column: 1 !important;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto !important;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-x: contain;
|
||||
scrollbar-width: none;
|
||||
gap: 6px !important;
|
||||
margin-bottom: 12px !important;
|
||||
padding: 2px 2px 6px;
|
||||
gap: 6px;
|
||||
scroll-padding-inline: 10px;
|
||||
touch-action: pan-x;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.top-nav-links::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.top-nav-spot-tickers {
|
||||
grid-column: 1 !important;
|
||||
justify-self: stretch;
|
||||
align-self: stretch;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.top-nav::-webkit-scrollbar {
|
||||
@@ -561,126 +582,125 @@ html[data-theme="light"] .theme-toggle-btn.is-active {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* 顶栏品牌:加密货币 | OKX 对冲 + 右上角 ETH/BTC 报价 */
|
||||
.header.header--brand {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 52px;
|
||||
margin-bottom: 10px;
|
||||
padding: 0 168px 0 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.header.header--brand .brand-title {
|
||||
margin: 0;
|
||||
font-size: 1.7rem;
|
||||
font-weight: 700;
|
||||
color: #e8ecff;
|
||||
text-align: center;
|
||||
line-height: 1.25;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.header.header--brand .brand-prefix {
|
||||
color: #e8ecff;
|
||||
}
|
||||
.header.header--brand .brand-sep {
|
||||
display: inline-block;
|
||||
margin: 0 0.45em;
|
||||
color: #9aa3bf;
|
||||
font-weight: 500;
|
||||
}
|
||||
.header.header--brand .brand-product {
|
||||
color: #e8ecff;
|
||||
}
|
||||
.header-live-quotes {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 3px;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
.header-live-quotes .hlq-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.2;
|
||||
color: #c5cce0;
|
||||
/* 导航右侧 ETH/BTC 报价 — 与 crypto_okx 同款 */
|
||||
.top-nav-links > a {
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.top-nav-spot-tickers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.header-live-quotes .hlq-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
.nav-spot-row {
|
||||
display: grid;
|
||||
grid-template-columns: 3px 18px minmax(4.6em, auto) minmax(5em, auto) minmax(3.6em, auto) minmax(3.8em, auto);
|
||||
align-items: center;
|
||||
column-gap: 7px;
|
||||
padding: 3px 9px 3px 5px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
background: rgba(16, 22, 36, 0.72);
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.nav-spot-row:hover {
|
||||
border-color: #3a4568;
|
||||
}
|
||||
|
||||
.nav-spot-ribbon {
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
border-radius: 1px 2px 2px 1px;
|
||||
background: #e24b6a;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.nav-spot-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
flex: 0 0 auto;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
.header-live-quotes .hlq-icon--eth {
|
||||
background: radial-gradient(circle at 35% 30%, #8ad0ff, #3c7cff 55%, #1a3a8a);
|
||||
|
||||
.nav-spot-icon--eth {
|
||||
background: #627eea;
|
||||
}
|
||||
.header-live-quotes .hlq-icon--btc {
|
||||
background: radial-gradient(circle at 35% 30%, #ffd27a, #f0a028 55%, #a65a00);
|
||||
|
||||
.nav-spot-icon--btc {
|
||||
background: #f2a900;
|
||||
color: #1a1200;
|
||||
}
|
||||
.header-live-quotes .hlq-sym {
|
||||
color: #9aa3bf;
|
||||
min-width: 58px;
|
||||
}
|
||||
.header-live-quotes .hlq-last {
|
||||
color: #e8ecff;
|
||||
|
||||
.nav-spot-sym {
|
||||
color: var(--inst-text, #e8ecff);
|
||||
font-weight: 600;
|
||||
min-width: 64px;
|
||||
text-align: right;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.header-live-quotes .hlq-chg,
|
||||
.header-live-quotes .hlq-pct {
|
||||
min-width: 48px;
|
||||
|
||||
.nav-spot-last,
|
||||
.nav-spot-chg,
|
||||
.nav-spot-pct {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-spot--up .nav-spot-last,
|
||||
.nav-spot--up .nav-spot-chg,
|
||||
.nav-spot--up .nav-spot-pct {
|
||||
color: #2dd4a8;
|
||||
}
|
||||
|
||||
.nav-spot--down .nav-spot-last,
|
||||
.nav-spot--down .nav-spot-chg,
|
||||
.nav-spot--down .nav-spot-pct {
|
||||
color: #ff6b7a;
|
||||
}
|
||||
|
||||
.nav-spot--flat .nav-spot-last,
|
||||
.nav-spot--flat .nav-spot-chg,
|
||||
.nav-spot--flat .nav-spot-pct {
|
||||
color: #9aa3bf;
|
||||
}
|
||||
.header-live-quotes .hlq-row.is-up .hlq-chg,
|
||||
.header-live-quotes .hlq-row.is-up .hlq-pct {
|
||||
color: #3dd68c;
|
||||
|
||||
html[data-theme="light"] .nav-spot-row {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-color: #d5e0ec;
|
||||
}
|
||||
.header-live-quotes .hlq-row.is-down .hlq-chg,
|
||||
.header-live-quotes .hlq-row.is-down .hlq-pct {
|
||||
color: #ff8a8a;
|
||||
|
||||
html[data-theme="light"] .nav-spot-row:hover {
|
||||
border-color: #9bb4cc;
|
||||
}
|
||||
html[data-theme="light"] .header.header--brand .brand-title,
|
||||
html[data-theme="light"] .header.header--brand .brand-prefix,
|
||||
html[data-theme="light"] .header.header--brand .brand-product {
|
||||
color: #142232;
|
||||
|
||||
html[data-theme="light"] .nav-spot--up .nav-spot-last,
|
||||
html[data-theme="light"] .nav-spot--up .nav-spot-chg,
|
||||
html[data-theme="light"] .nav-spot--up .nav-spot-pct {
|
||||
color: #0a8f6c;
|
||||
}
|
||||
html[data-theme="light"] .header.header--brand .brand-sep,
|
||||
html[data-theme="light"] .header-live-quotes .hlq-sym {
|
||||
color: #5a6f85;
|
||||
|
||||
html[data-theme="light"] .nav-spot--down .nav-spot-last,
|
||||
html[data-theme="light"] .nav-spot--down .nav-spot-chg,
|
||||
html[data-theme="light"] .nav-spot--down .nav-spot-pct {
|
||||
color: #d12b3a;
|
||||
}
|
||||
html[data-theme="light"] .header-live-quotes .hlq-last {
|
||||
color: #142232;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.header.header--brand {
|
||||
padding: 0 8px;
|
||||
min-height: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.header.header--brand .brand-title {
|
||||
font-size: 1.05rem !important;
|
||||
text-align: left;
|
||||
}
|
||||
.header-live-quotes {
|
||||
position: static;
|
||||
transform: none;
|
||||
margin-top: 6px;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.nav-spot-row {
|
||||
grid-template-columns: 3px 16px minmax(4em, auto) minmax(4.2em, auto) minmax(3.2em, auto) minmax(3.4em, auto);
|
||||
column-gap: 5px;
|
||||
font-size: 0.8rem;
|
||||
padding: 2px 7px 2px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 导航栏 BTC/ETH 报价:相对北京 08:00 开盘涨跌(与 crypto_okx 同款).
|
||||
*/
|
||||
(function (global) {
|
||||
"use strict";
|
||||
|
||||
const DEFAULT_POLL_MS = 5000;
|
||||
let timer = null;
|
||||
let inflight = false;
|
||||
|
||||
function pollMs() {
|
||||
const raw =
|
||||
(document.body && document.body.getAttribute("data-price-refresh-ms")) || "";
|
||||
const n = Number(raw);
|
||||
return Number.isFinite(n) && n >= 2000 ? n : DEFAULT_POLL_MS;
|
||||
}
|
||||
|
||||
function fmtPrice(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 >= 100) digits = 2;
|
||||
else if (abs >= 1) digits = 3;
|
||||
else digits = 4;
|
||||
try {
|
||||
return v.toLocaleString("en-US", {
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits,
|
||||
});
|
||||
} catch (_) {
|
||||
return v.toFixed(digits);
|
||||
}
|
||||
}
|
||||
|
||||
function fmtSigned(n, isPct) {
|
||||
const v = Number(n);
|
||||
if (!Number.isFinite(v)) return "—";
|
||||
const abs = Math.abs(v);
|
||||
let digits = isPct ? 2 : abs >= 100 ? 1 : 2;
|
||||
if (!isPct && abs < 1) digits = 3;
|
||||
const body = abs.toLocaleString("en-US", {
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits,
|
||||
});
|
||||
const sign = v > 0 ? "" : v < 0 ? "-" : "";
|
||||
return sign + body + (isPct ? "%" : "");
|
||||
}
|
||||
|
||||
function dirClass(n) {
|
||||
const v = Number(n);
|
||||
if (!Number.isFinite(v) || v === 0) return "nav-spot--flat";
|
||||
return v > 0 ? "nav-spot--up" : "nav-spot--down";
|
||||
}
|
||||
|
||||
function paintRow(row, t) {
|
||||
if (!row || !t) return;
|
||||
const lastEl = row.querySelector('[data-field="last"]');
|
||||
const chgEl = row.querySelector('[data-field="change"]');
|
||||
const pctEl = row.querySelector('[data-field="change_pct"]');
|
||||
const dir = dirClass(t.change);
|
||||
row.classList.remove("nav-spot--up", "nav-spot--down", "nav-spot--flat");
|
||||
row.classList.add(dir);
|
||||
if (lastEl) lastEl.textContent = fmtPrice(t.last);
|
||||
if (chgEl) chgEl.textContent = fmtSigned(t.change, false);
|
||||
if (pctEl) pctEl.textContent = fmtSigned(t.change_pct, true);
|
||||
const openHint =
|
||||
t.open != null
|
||||
? "开盘 " + fmtPrice(t.open) + " · 北京 08:00 (sodUtc0)"
|
||||
: "北京 08:00 (sodUtc0)";
|
||||
row.title = (t.symbol || "") + " " + openHint;
|
||||
}
|
||||
|
||||
function apply(data) {
|
||||
const root = document.getElementById("nav-spot-tickers");
|
||||
if (!root) return;
|
||||
const list = (data && data.tickers) || [];
|
||||
list.forEach(function (t) {
|
||||
const base = (t.base || "").toUpperCase();
|
||||
if (!base) return;
|
||||
const row = root.querySelector('.nav-spot-row[data-base="' + base + '"]');
|
||||
paintRow(row, t);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
const root = document.getElementById("nav-spot-tickers");
|
||||
if (!root || inflight) return;
|
||||
inflight = true;
|
||||
fetch("/api/nav_spot_tickers")
|
||||
.then(function (r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
if (data && data.ok !== false) apply(data);
|
||||
})
|
||||
.catch(function () {})
|
||||
.finally(function () {
|
||||
inflight = false;
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (!document.getElementById("nav-spot-tickers")) return;
|
||||
refresh();
|
||||
if (timer) clearInterval(timer);
|
||||
timer = setInterval(refresh, pollMs());
|
||||
}
|
||||
|
||||
global.refreshNavSpotTickers = refresh;
|
||||
global.startNavSpotTickers = start;
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", start);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
})(window);
|
||||
Reference in New Issue
Block a user