/** * 表单币种输入:防抖 + 定时刷新,展示交易所最新价(/api/order_defaults)。 */ (function (global) { "use strict"; const DEFAULT_DEBOUNCE_MS = 350; const DEFAULT_POLL_MS = 5000; const bound = new WeakSet(); function $(id) { return id ? document.getElementById(id) : null; } function symbolValue(el) { if (!el) return ""; return (el.value || "").trim(); } function directionValue(dirId) { const el = dirId ? $(dirId) : null; const v = (el && el.value ? el.value : "long").trim().toLowerCase(); return v === "short" ? "short" : "long"; } function formatPrice(px, sym) { const n = Number(px); if (!Number.isFinite(n)) return "—"; const u = (sym || "").trim().toUpperCase(); let digits = 4; if (u.startsWith("BTC") || u.startsWith("ETH") || n >= 1000) digits = 2; else if (n >= 10) digits = 3; else if (n >= 1) digits = 4; else if (n >= 0.01) digits = 5; else digits = 6; return n.toFixed(digits); } 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 paint(el, sym, px, err) { if (!el) return; if (err) { el.textContent = "现价:—"; el.classList.add("symbol-live-price--err"); el.classList.remove("symbol-live-price--ok"); el.title = err; return; } if (px === null || typeof px === "undefined") { el.textContent = "现价:—"; el.classList.remove("symbol-live-price--ok", "symbol-live-price--err"); el.title = sym ? "无法读取交易所价格" : ""; return; } const label = sym ? sym.toUpperCase().replace(/\/USDT.*/, "") : ""; el.textContent = label ? label + " 现价 " + formatPrice(px, sym) : "现价 " + formatPrice(px, sym); el.classList.add("symbol-live-price--ok"); el.classList.remove("symbol-live-price--err"); el.title = "交易所最新价(约 " + pollMs() / 1000 + "s 刷新)"; } function bindOne(el) { if (!el || bound.has(el)) return; bound.add(el); const symId = el.getAttribute("data-symbol-input"); const dirId = el.getAttribute("data-direction-input") || ""; let debounceTimer = null; let pollTimer = null; let fetchSeq = 0; function clearPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } function startPoll() { clearPoll(); pollTimer = setInterval(refresh, pollMs()); } function refresh() { const symEl = $(symId); const sym = symbolValue(symEl); if (!sym) { paint(el, "", null, ""); clearPoll(); return; } const dir = directionValue(dirId); const seq = ++fetchSeq; el.classList.add("symbol-live-price--loading"); fetch( "/api/order_defaults?symbol=" + encodeURIComponent(sym) + "&direction=" + encodeURIComponent(dir) ) .then(function (r) { return r.json().then(function (d) { return { status: r.status, data: d }; }); }) .then(function (res) { if (seq !== fetchSeq) return; el.classList.remove("symbol-live-price--loading"); const data = res.data || {}; if (res.status >= 400 || !data.ok) { paint(el, sym, null, (data && data.msg) || "读取失败"); return; } const px = data.last_price != null ? data.last_price : data.price; paint(el, data.symbol || sym, px, ""); if (!pollTimer) startPoll(); }) .catch(function () { if (seq !== fetchSeq) return; el.classList.remove("symbol-live-price--loading"); paint(el, sym, null, "网络错误"); }); } function schedule() { clearTimeout(debounceTimer); debounceTimer = setTimeout(refresh, DEFAULT_DEBOUNCE_MS); } const symEl = $(symId); if (symEl) { symEl.addEventListener("input", schedule); symEl.addEventListener("change", schedule); } const dirEl = dirId ? $(dirId) : null; if (dirEl) { dirEl.addEventListener("change", schedule); } schedule(); } function init(root) { const scope = root || document; scope.querySelectorAll(".symbol-live-price").forEach(bindOne); } global.SymbolLivePrice = { init: init, bind: bindOne }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", function () { init(document); }); } else { init(document); } })(typeof window !== "undefined" ? window : globalThis);