修复 OKX K 线无 since 时只拉 300 根的问题,并加入行情快捷键
- fetch_ohlcv_for_hub:无 since 时按目标根数分页拉取(OKX/Gate 单次约 300) - hub_kline_store 全量补拉传 fetch_start_ms - 行情区:数字键切换周期、Ctrl+空格全屏、Esc 退出全屏 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1970,6 +1970,10 @@ body.login-page {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.market-countdown.market-tf-key-hint {
|
||||
color: #ffb84d;
|
||||
}
|
||||
|
||||
.market-chart-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -22,6 +22,28 @@
|
||||
"1d": 24 * 60 * 60_000,
|
||||
"1w": 7 * 24 * 60 * 60_000,
|
||||
};
|
||||
const TF_BY_MINUTES = {
|
||||
"1": "1m",
|
||||
"5": "5m",
|
||||
"15": "15m",
|
||||
"60": "1h",
|
||||
"240": "4h",
|
||||
"1440": "1d",
|
||||
"10080": "1w",
|
||||
};
|
||||
const TF_MINUTE_KEYS = Object.keys(TF_BY_MINUTES).sort(function (a, b) {
|
||||
return b.length - a.length;
|
||||
});
|
||||
const TF_CN_LABEL = {
|
||||
"1m": "1分钟",
|
||||
"5m": "5分钟",
|
||||
"15m": "15分钟",
|
||||
"1h": "1小时",
|
||||
"4h": "4小时",
|
||||
"1d": "日线",
|
||||
"1w": "周线",
|
||||
};
|
||||
const TF_DIGIT_TIMEOUT_MS = 650;
|
||||
const chartHost = document.getElementById("market-chart");
|
||||
if (!chartHost) return;
|
||||
|
||||
@@ -103,6 +125,9 @@
|
||||
let lastViewKey = "";
|
||||
let currentTf = "1d";
|
||||
let priceTagTimer = null;
|
||||
let tfDigitBuf = "";
|
||||
let tfDigitTimer = null;
|
||||
let tfHintTimer = null;
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s || "")
|
||||
@@ -654,6 +679,155 @@
|
||||
updateHeaderLabels(elSymbol && elSymbol.value, elTf && elTf.value);
|
||||
}
|
||||
|
||||
function isMarketPageActive() {
|
||||
const page = document.getElementById("page-market");
|
||||
return !!(page && !page.classList.contains("hidden"));
|
||||
}
|
||||
|
||||
function isTypingInField(target) {
|
||||
if (!target) return false;
|
||||
const tag = (target.tagName || "").toLowerCase();
|
||||
if (tag === "input" || tag === "textarea" || tag === "select") return true;
|
||||
return !!target.isContentEditable;
|
||||
}
|
||||
|
||||
function canUseTfKeyboard(e) {
|
||||
if (!isMarketPageActive()) return false;
|
||||
if (e.altKey || e.ctrlKey || e.metaKey) return false;
|
||||
if (isTypingInField(e.target)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function canExtendTfDigitBuffer(buf) {
|
||||
if (!buf) return false;
|
||||
return TF_MINUTE_KEYS.some(function (k) {
|
||||
return k.indexOf(buf) === 0;
|
||||
});
|
||||
}
|
||||
|
||||
function resolveTfFromDigitBuffer(buf) {
|
||||
if (!buf) return null;
|
||||
return TF_BY_MINUTES[buf] || null;
|
||||
}
|
||||
|
||||
function flashTfSwitchHint(tf) {
|
||||
const label = TF_CN_LABEL[tf] || tf;
|
||||
const text = "周期 → " + label + "(" + tf + ")";
|
||||
if (elTfLabel) elTfLabel.textContent = tf;
|
||||
if (elBarCountdown) {
|
||||
if (tfHintTimer) clearTimeout(tfHintTimer);
|
||||
elBarCountdown.textContent = text;
|
||||
elBarCountdown.classList.add("market-tf-key-hint");
|
||||
tfHintTimer = setTimeout(function () {
|
||||
tfHintTimer = null;
|
||||
elBarCountdown.classList.remove("market-tf-key-hint");
|
||||
tickLiveClock();
|
||||
}, 1200);
|
||||
return;
|
||||
}
|
||||
if (elStatus) {
|
||||
if (tfHintTimer) clearTimeout(tfHintTimer);
|
||||
const prevClass = elStatus.className;
|
||||
const prevText = elStatus.textContent;
|
||||
elStatus.className = "market-status";
|
||||
elStatus.textContent = text;
|
||||
tfHintTimer = setTimeout(function () {
|
||||
tfHintTimer = null;
|
||||
elStatus.className = prevClass;
|
||||
elStatus.textContent = prevText;
|
||||
}, 1200);
|
||||
}
|
||||
}
|
||||
|
||||
function applyTimeframe(tf, fromKeyboard) {
|
||||
if (!tf || !TF_MS[tf]) return false;
|
||||
const cur = (elTf && elTf.value) || currentTf;
|
||||
if (cur === tf) return false;
|
||||
if (elTf) elTf.value = tf;
|
||||
if (elFsTf) elFsTf.value = tf;
|
||||
currentTf = tf;
|
||||
tickLiveClock();
|
||||
updateHeaderLabels(
|
||||
elSymbol && elSymbol.value.trim().toUpperCase(),
|
||||
tf
|
||||
);
|
||||
syncFsToolbarFromMain();
|
||||
if (fromKeyboard) flashTfSwitchHint(tf);
|
||||
loadChart(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
function commitTfDigitBuffer() {
|
||||
const buf = tfDigitBuf;
|
||||
tfDigitBuf = "";
|
||||
if (tfDigitTimer) {
|
||||
clearTimeout(tfDigitTimer);
|
||||
tfDigitTimer = null;
|
||||
}
|
||||
const tf = resolveTfFromDigitBuffer(buf);
|
||||
if (tf) applyTimeframe(tf, true);
|
||||
}
|
||||
|
||||
function handleTfDigitKey(digit) {
|
||||
if (!digit) return;
|
||||
if (tfDigitBuf && !canExtendTfDigitBuffer(tfDigitBuf)) {
|
||||
tfDigitBuf = "";
|
||||
}
|
||||
tfDigitBuf += digit;
|
||||
const immediate = resolveTfFromDigitBuffer(tfDigitBuf);
|
||||
if (immediate) {
|
||||
commitTfDigitBuffer();
|
||||
return;
|
||||
}
|
||||
if (!canExtendTfDigitBuffer(tfDigitBuf)) {
|
||||
tfDigitBuf = digit;
|
||||
const again = resolveTfFromDigitBuffer(tfDigitBuf);
|
||||
if (again) {
|
||||
commitTfDigitBuffer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (tfDigitTimer) clearTimeout(tfDigitTimer);
|
||||
tfDigitTimer = setTimeout(commitTfDigitBuffer, TF_DIGIT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function isFullscreenShortcut(e) {
|
||||
return (
|
||||
e.ctrlKey &&
|
||||
!e.altKey &&
|
||||
!e.metaKey &&
|
||||
!e.shiftKey &&
|
||||
(e.code === "Space" || e.key === " " || e.key === "Spacebar")
|
||||
);
|
||||
}
|
||||
|
||||
function onMarketKeydown(e) {
|
||||
if (!isMarketPageActive()) return;
|
||||
|
||||
if (e.key === "Escape" && chartFullscreen) {
|
||||
e.preventDefault();
|
||||
setChartFullscreen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFullscreenShortcut(e)) {
|
||||
e.preventDefault();
|
||||
toggleChartFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canUseTfKeyboard(e)) return;
|
||||
if (e.key >= "0" && e.key <= "9") {
|
||||
e.preventDefault();
|
||||
handleTfDigitKey(e.key);
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter" && tfDigitBuf) {
|
||||
e.preventDefault();
|
||||
commitTfDigitBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
function populateFsExchangeOptions() {
|
||||
if (!elFsExchange || !elExchange) return;
|
||||
elFsExchange.innerHTML = elExchange.innerHTML;
|
||||
@@ -1367,6 +1541,11 @@
|
||||
}
|
||||
if (elTf) {
|
||||
elTf.addEventListener("change", function () {
|
||||
tfDigitBuf = "";
|
||||
if (tfDigitTimer) {
|
||||
clearTimeout(tfDigitTimer);
|
||||
tfDigitTimer = null;
|
||||
}
|
||||
currentTf = (elTf && elTf.value) || "1d";
|
||||
tickLiveClock();
|
||||
syncFsToolbarFromMain();
|
||||
@@ -1418,9 +1597,7 @@
|
||||
updateIndicators();
|
||||
});
|
||||
});
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape" && chartFullscreen) setChartFullscreen(false);
|
||||
});
|
||||
document.addEventListener("keydown", onMarketKeydown);
|
||||
if (elFsExchange) {
|
||||
elFsExchange.addEventListener("change", function () {
|
||||
syncMainFromFsToolbar();
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<div id="page-market" class="page hidden">
|
||||
<div class="page-head">
|
||||
<h1><span class="head-tag">MKT</span> 行情区</h1>
|
||||
<p class="page-desc">按需拉取 K 线,本地库保留 15 天(无后台自动更新)</p>
|
||||
<p class="page-desc">按需拉取 K 线,本地库保留 15 天(无后台自动更新)。快捷键:<kbd>Ctrl</kbd>+<kbd>空格</kbd> 全屏/退出全屏(全屏时 <kbd>Esc</kbd> 退出);数字键切换周期(分钟):1/5/15/60/240/1440/10080。</p>
|
||||
</div>
|
||||
<details class="hint-box">
|
||||
<summary>数据说明</summary>
|
||||
@@ -112,7 +112,7 @@
|
||||
<label class="market-ind-opt"><input type="checkbox" id="market-ind-rsi" value="rsi" /> RSI</label>
|
||||
</div>
|
||||
</details>
|
||||
<button type="button" id="market-chart-fullscreen" class="ghost market-fs-btn" title="K线全屏">全屏</button>
|
||||
<button type="button" id="market-chart-fullscreen" class="ghost market-fs-btn" title="K线全屏(Ctrl+空格)">全屏</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="market-ohlcv-row">
|
||||
@@ -240,7 +240,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-ind-pane-fix"></script>
|
||||
<script src="/assets/chart.js?v=20260528-hub-fs-keys"></script>
|
||||
<script src="/assets/app.js?v=20260528-hub-tpsl-fix"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user