行情区: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
+77 -20
View File
@@ -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);
}
+2
View File
@@ -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();
}
}
+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 (
+13 -10
View File
@@ -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>