行情区:60s 刷新、横向 OHLCV、交易所标识、价格自动按钮
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1976,47 +1976,104 @@ body.login-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.market-exchange-badge {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 52px;
|
||||
z-index: 3;
|
||||
transform: translateY(-50%) rotate(-90deg);
|
||||
transform-origin: center center;
|
||||
font-family: var(--font-display, var(--font));
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
color: rgba(184, 212, 232, 0.22);
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.market-exchange-badge:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.market-ohlcv-overlay {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
padding: 10px 12px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
background: rgba(8, 14, 24, 0.88);
|
||||
border: 1px solid var(--border-soft);
|
||||
font-size: 0.78rem;
|
||||
min-width: 200px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.12s ease;
|
||||
}
|
||||
|
||||
.market-ohlcv-overlay.is-active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
max-width: calc(100% - 80px);
|
||||
}
|
||||
|
||||
.market-ohlcv-title {
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 10px;
|
||||
}
|
||||
|
||||
.market-ohlcv-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
.mkt-exchange-tag {
|
||||
padding: 1px 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 255, 157, 0.12);
|
||||
border: 1px solid rgba(0, 255, 157, 0.35);
|
||||
color: #00ff9d;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mkt-exchange-tag:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.market-ohlcv-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px 14px;
|
||||
}
|
||||
|
||||
.market-ohlcv-grid .k {
|
||||
color: var(--muted);
|
||||
margin-right: 6px;
|
||||
.market-ohlcv-row .ohlcv-item {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.market-ohlcv-grid .market-vol {
|
||||
grid-column: 1 / -1;
|
||||
.market-ohlcv-row .k {
|
||||
color: var(--muted);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.market-price-auto {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 42%;
|
||||
z-index: 5;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.68rem;
|
||||
font-family: var(--font);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-soft);
|
||||
background: rgba(8, 14, 24, 0.92);
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.market-price-auto:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.market-price-auto.is-on {
|
||||
color: #00ff9d;
|
||||
border-color: rgba(0, 255, 157, 0.45);
|
||||
background: rgba(0, 255, 157, 0.1);
|
||||
}
|
||||
|
||||
@@ -262,6 +262,8 @@
|
||||
if (page === "settings") loadSettingsUI();
|
||||
if (page === "market" && window.hubMarketChart) {
|
||||
window.hubMarketChart.init();
|
||||
} else if (window.hubMarketChart && window.hubMarketChart.stopAutoRefresh) {
|
||||
window.hubMarketChart.stopAutoRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* 中控行情区:K 线 + 底部成交量;十字线时显示 OHLCV;可视区间高低点。
|
||||
* 中控行情区:K 线 + 成交量;默认最新 OHLCV;60s 自动刷新;价格轴「自动」。
|
||||
*/
|
||||
(function () {
|
||||
const AUTO_REFRESH_MS = 60000;
|
||||
const chartHost = document.getElementById("market-chart");
|
||||
if (!chartHost) return;
|
||||
|
||||
@@ -11,25 +12,29 @@
|
||||
const elRefresh = document.getElementById("market-refresh");
|
||||
const elStatus = document.getElementById("market-status");
|
||||
const elUpdated = document.getElementById("market-updated");
|
||||
const elOverlay = document.querySelector(".market-ohlcv-overlay");
|
||||
const elO = document.getElementById("mkt-o");
|
||||
const elH = document.getElementById("mkt-h");
|
||||
const elL = document.getElementById("mkt-l");
|
||||
const elC = document.getElementById("mkt-c");
|
||||
const elV = document.getElementById("mkt-v");
|
||||
const elExLabel = document.getElementById("mkt-exchange-label");
|
||||
const elExBadge = document.getElementById("market-exchange-badge");
|
||||
const elSymLabel = document.getElementById("mkt-symbol-label");
|
||||
const elTfLabel = document.getElementById("mkt-tf-label");
|
||||
const elPriceAuto = document.getElementById("market-price-auto");
|
||||
|
||||
let chart = null;
|
||||
let candleSeries = null;
|
||||
let volumeSeries = null;
|
||||
let priceTick = null;
|
||||
let priceAutoScale = true;
|
||||
let rangeMarkers = [];
|
||||
let lastCandles = [];
|
||||
let candleByTime = {};
|
||||
let chartMeta = null;
|
||||
let loadToken = 0;
|
||||
let marketInited = false;
|
||||
let refreshTimer = null;
|
||||
|
||||
function fmtVol(v) {
|
||||
if (v == null || Number.isNaN(Number(v))) return "-";
|
||||
@@ -62,9 +67,26 @@
|
||||
return text;
|
||||
}
|
||||
|
||||
function setOverlayVisible(on) {
|
||||
if (!elOverlay) return;
|
||||
elOverlay.classList.toggle("is-active", !!on);
|
||||
function exchangeLabel() {
|
||||
if (!elExchange) return "";
|
||||
const opt = elExchange.options[elExchange.selectedIndex];
|
||||
if (opt && opt.textContent) return opt.textContent.trim();
|
||||
return (elExchange.value || "").trim().toUpperCase();
|
||||
}
|
||||
|
||||
function updateExchangeDisplay() {
|
||||
const label = exchangeLabel();
|
||||
if (elExLabel) elExLabel.textContent = label;
|
||||
if (elExBadge) {
|
||||
elExBadge.textContent = label;
|
||||
elExBadge.setAttribute("aria-hidden", label ? "false" : "true");
|
||||
}
|
||||
}
|
||||
|
||||
function updateHeaderLabels(sym, tf) {
|
||||
if (elSymLabel) elSymLabel.textContent = sym || "—";
|
||||
if (elTfLabel) elTfLabel.textContent = tf || "—";
|
||||
updateExchangeDisplay();
|
||||
}
|
||||
|
||||
function paintOhlcv(bar) {
|
||||
@@ -82,9 +104,18 @@
|
||||
if (elV) elV.textContent = fmtVol(bar.volume);
|
||||
}
|
||||
|
||||
function hideOhlcvOverlay() {
|
||||
setOverlayVisible(false);
|
||||
paintOhlcv(null);
|
||||
function latestCandle() {
|
||||
return lastCandles.length ? lastCandles[lastCandles.length - 1] : null;
|
||||
}
|
||||
|
||||
function showLatestOhlcv() {
|
||||
paintOhlcv(latestCandle());
|
||||
}
|
||||
|
||||
function applyPriceAutoScale() {
|
||||
if (!chart) return;
|
||||
chart.priceScale("right").applyOptions({ autoScale: priceAutoScale });
|
||||
if (elPriceAuto) elPriceAuto.classList.toggle("is-on", priceAutoScale);
|
||||
}
|
||||
|
||||
function indexCandles(candles) {
|
||||
@@ -125,7 +156,7 @@
|
||||
vertLines: { visible: false },
|
||||
horzLines: { visible: false },
|
||||
},
|
||||
rightPriceScale: { borderColor: "#2a4058" },
|
||||
rightPriceScale: { borderColor: "#2a4058", autoScale: true },
|
||||
timeScale: { borderColor: "#2a4058", timeVisible: true, secondsVisible: false },
|
||||
crosshair: {
|
||||
mode: LightweightCharts.CrosshairMode
|
||||
@@ -175,18 +206,18 @@
|
||||
chart.priceScale("volume").applyOptions({
|
||||
scaleMargins: { top: 0.78, bottom: 0 },
|
||||
});
|
||||
applyPriceAutoScale();
|
||||
|
||||
chart.subscribeCrosshairMove(function (param) {
|
||||
if (!param || param.time == null) {
|
||||
hideOhlcvOverlay();
|
||||
showLatestOhlcv();
|
||||
return;
|
||||
}
|
||||
const bar = candleAtTime(param.time);
|
||||
if (!bar) {
|
||||
hideOhlcvOverlay();
|
||||
showLatestOhlcv();
|
||||
return;
|
||||
}
|
||||
setOverlayVisible(true);
|
||||
paintOhlcv(bar);
|
||||
});
|
||||
|
||||
@@ -199,7 +230,6 @@
|
||||
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
|
||||
});
|
||||
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
|
||||
hideOhlcvOverlay();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -240,7 +270,7 @@
|
||||
lineWidth: 1,
|
||||
lineStyle: 2,
|
||||
axisLabelVisible: true,
|
||||
title: "可视高",
|
||||
title: "高点",
|
||||
})
|
||||
);
|
||||
rangeMarkers.push(
|
||||
@@ -250,7 +280,7 @@
|
||||
lineWidth: 1,
|
||||
lineStyle: 2,
|
||||
axisLabelVisible: true,
|
||||
title: "可视低",
|
||||
title: "低点",
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -270,6 +300,20 @@
|
||||
if (elTf && !elTf.value) elTf.value = "1d";
|
||||
}
|
||||
|
||||
function startAutoRefresh() {
|
||||
stopAutoRefresh();
|
||||
refreshTimer = setInterval(function () {
|
||||
const page = document.getElementById("page-market");
|
||||
if (!page || page.classList.contains("hidden")) return;
|
||||
loadChart(false);
|
||||
}, AUTO_REFRESH_MS);
|
||||
}
|
||||
|
||||
function stopAutoRefresh() {
|
||||
if (refreshTimer) clearInterval(refreshTimer);
|
||||
refreshTimer = null;
|
||||
}
|
||||
|
||||
async function loadMeta() {
|
||||
const r = await fetch("/api/chart/meta", { credentials: "same-origin" });
|
||||
chartMeta = await r.json();
|
||||
@@ -283,6 +327,7 @@
|
||||
});
|
||||
readQuery();
|
||||
applyDefaults();
|
||||
updateExchangeDisplay();
|
||||
}
|
||||
|
||||
async function loadChart(force) {
|
||||
@@ -302,9 +347,7 @@
|
||||
elStatus.className = "market-status";
|
||||
elStatus.textContent = "加载中…";
|
||||
}
|
||||
hideOhlcvOverlay();
|
||||
if (elSymLabel) elSymLabel.textContent = sym;
|
||||
if (elTfLabel) elTfLabel.textContent = tf;
|
||||
updateHeaderLabels(sym, tf);
|
||||
|
||||
const qs = new URLSearchParams({
|
||||
exchange_key: exKey,
|
||||
@@ -330,7 +373,9 @@
|
||||
candleSeries.setData(lastCandles);
|
||||
volumeSeries.setData(buildVolumeData(lastCandles));
|
||||
chart.timeScale().fitContent();
|
||||
applyPriceAutoScale();
|
||||
updateVisibleRangeMarkers();
|
||||
showLatestOhlcv();
|
||||
|
||||
const limit = data.limit || lastCandles.length;
|
||||
let hint =
|
||||
@@ -342,9 +387,7 @@
|
||||
(data.from_cache || 0) +
|
||||
" / 新拉 " +
|
||||
(data.fetched || 0) +
|
||||
")· 保留 " +
|
||||
(data.retention_days || 15) +
|
||||
" 天";
|
||||
")· 每 60s 刷新";
|
||||
if (data.stale && data.stale_message) {
|
||||
hint += " · 缓存:" + data.stale_message;
|
||||
}
|
||||
@@ -375,6 +418,7 @@
|
||||
}
|
||||
if (elExchange) {
|
||||
elExchange.addEventListener("change", function () {
|
||||
updateExchangeDisplay();
|
||||
loadChart(false);
|
||||
});
|
||||
}
|
||||
@@ -389,6 +433,12 @@
|
||||
loadChart(false);
|
||||
});
|
||||
}
|
||||
if (elPriceAuto) {
|
||||
elPriceAuto.addEventListener("click", function () {
|
||||
priceAutoScale = !priceAutoScale;
|
||||
applyPriceAutoScale();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.hubMarketChart = {
|
||||
@@ -398,11 +448,14 @@
|
||||
await loadMeta();
|
||||
bind();
|
||||
}
|
||||
startAutoRefresh();
|
||||
await loadChart(false);
|
||||
},
|
||||
reload: function (force) {
|
||||
loadChart(!!force);
|
||||
},
|
||||
startAutoRefresh: startAutoRefresh,
|
||||
stopAutoRefresh: stopAutoRefresh,
|
||||
};
|
||||
|
||||
if (
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260528-hub-market2" />
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260528-hub-market3" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-bg" aria-hidden="true"></div>
|
||||
@@ -66,7 +66,7 @@
|
||||
<summary>数据说明</summary>
|
||||
<div class="hint-body">
|
||||
优先读中控 <code>data/hub_kline.db</code>,不足时向所选交易所实例请求并写入库。<br />
|
||||
日内周期最多 1000 根,日线/周线最多 500 根。仅在本页操作或点「刷新」时拉取交易所。
|
||||
日内周期最多 1000 根,日线/周线最多 500 根。打开本页时每 60 秒自动刷新一次 K 线。
|
||||
</div>
|
||||
</details>
|
||||
<div class="market-toolbar toolbar">
|
||||
@@ -97,19 +97,22 @@
|
||||
</div>
|
||||
<p id="market-status" class="market-status"></p>
|
||||
<div class="market-chart-wrap">
|
||||
<div id="market-exchange-badge" class="market-exchange-badge" aria-hidden="true"></div>
|
||||
<div class="market-ohlcv-overlay" aria-label="K线详情">
|
||||
<div class="market-ohlcv-title">
|
||||
<span id="mkt-exchange-label" class="mkt-exchange-tag"></span>
|
||||
<span id="mkt-symbol-label">—</span>
|
||||
<span id="mkt-tf-label">1d</span>
|
||||
</div>
|
||||
<div class="market-ohlcv-grid">
|
||||
<div><span class="k">开</span><span id="mkt-o">—</span></div>
|
||||
<div><span class="k">高</span><span id="mkt-h">—</span></div>
|
||||
<div><span class="k">低</span><span id="mkt-l">—</span></div>
|
||||
<div><span class="k">收</span><span id="mkt-c">—</span></div>
|
||||
<div class="market-vol"><span class="k">量</span><span id="mkt-v">—</span></div>
|
||||
<div class="market-ohlcv-row">
|
||||
<span class="ohlcv-item"><span class="k">开</span><span id="mkt-o">—</span></span>
|
||||
<span class="ohlcv-item"><span class="k">高</span><span id="mkt-h">—</span></span>
|
||||
<span class="ohlcv-item"><span class="k">低</span><span id="mkt-l">—</span></span>
|
||||
<span class="ohlcv-item"><span class="k">收</span><span id="mkt-c">—</span></span>
|
||||
<span class="ohlcv-item market-vol"><span class="k">量</span><span id="mkt-v">—</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="market-price-auto" class="market-price-auto is-on" title="价格轴自动缩放">自动</button>
|
||||
<div id="market-chart" class="market-chart-host"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,7 +182,7 @@
|
||||
|
||||
<div id="toast"></div>
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<script src="/assets/chart.js?v=20260528-hub-market2"></script>
|
||||
<script src="/assets/app.js?v=20260528-hub-market2"></script>
|
||||
<script src="/assets/chart.js?v=20260528-hub-market3"></script>
|
||||
<script src="/assets/app.js?v=20260528-hub-market3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user