行情区:60s 刷新、横向 OHLCV、交易所标识、价格自动按钮

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-02 11:19:20 +08:00
parent 113d8c1669
commit d942e5b088
4 changed files with 166 additions and 51 deletions
+74 -21
View File
@@ -1,7 +1,8 @@
/**
* 中控行情区:K 线 + 底部成交量;十字线时显示 OHLCV可视区间高低点
* 中控行情区:K 线 + 成交量;默认最新 OHLCV60s 自动刷新;价格轴「自动」
*/
(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 (