diff --git a/static/hub.css b/static/hub.css index 98b424c..e317c74 100644 --- a/static/hub.css +++ b/static/hub.css @@ -1666,3 +1666,291 @@ input.readonly { body.page-stats .main { max-width: 1800px; } + +/* —— 手机单独壳:电脑样式不动,仅 html.hub-phone-html 启用 —— */ +.hub-phone-chrome { + display: none; +} +html.hub-phone-html .hub-desk-chrome { + display: none !important; +} +html.hub-phone-html .hub-phone-bar { + display: flex; +} +html.hub-phone-html .hub-phone-tabbar { + display: flex; +} +html.hub-phone-html .hub-phone-more { + display: block; +} +html.hub-phone-html body { + padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px)); +} +html.hub-phone-html body.page-desk { + height: auto; + overflow: auto; +} +html.hub-phone-html body.page-desk .main { + height: auto; + max-height: none; + display: block; + padding: 10px 14px 20px; +} +html.hub-phone-html .main { + max-width: none; + padding: 10px 14px 20px; +} +html.hub-phone-html .topbar-logout { + display: none; +} +.hub-phone-bar { + display: none; + align-items: center; + justify-content: space-between; + gap: 10px; + margin: 8px 0 12px; + padding: 8px 4px 10px; + border-bottom: 1px solid rgba(56, 189, 248, 0.16); +} +.hub-phone-bar-title { + font-size: 1.05rem; + font-weight: 700; + color: #dbe4ff; + white-space: nowrap; +} +.hub-phone-quotes { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; + font-variant-numeric: tabular-nums; +} +.hub-phone-quotes .nav-spot-row { + grid-template-columns: 16px minmax(4.2em, auto) minmax(3.4em, auto); + padding: 1px 6px 1px 4px; + font-size: 0.72rem; + background: transparent; + border: none; +} +.hub-phone-tabbar { + display: none; + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 40; + justify-content: space-around; + align-items: stretch; + gap: 4px; + padding: 6px 8px calc(8px + env(safe-area-inset-bottom, 0px)); + background: rgba(10, 16, 28, 0.96); + border-top: 1px solid rgba(56, 189, 248, 0.22); + backdrop-filter: blur(10px); +} +.hub-phone-tab { + flex: 1; + appearance: none; + border: 0; + background: transparent; + color: #8aa0b8; + font: inherit; + font-size: 0.86rem; + font-weight: 600; + padding: 10px 4px; + text-align: center; + cursor: pointer; +} +.hub-phone-tab.is-active, +.hub-phone-tab[aria-expanded="true"] { + color: #e8f1ff; +} +.hub-phone-more { + display: none; +} +html.hub-phone-html .hub-phone-more { + pointer-events: none; +} +html.hub-phone-html.hub-phone-more-open .hub-phone-more, +html.hub-phone-html body.hub-phone-more-open .hub-phone-more { + pointer-events: auto; +} +.hub-phone-more-backdrop { + position: fixed; + inset: 0; + z-index: 45; + background: rgba(0, 0, 0, 0.45); + opacity: 0; + visibility: hidden; + transition: opacity 0.18s ease; +} +body.hub-phone-more-open .hub-phone-more-backdrop { + opacity: 1; + visibility: visible; +} +.hub-phone-more-sheet { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 46; + max-height: min(78vh, 560px); + overflow: auto; + padding: 10px 16px calc(18px + env(safe-area-inset-bottom, 0px)); + border-radius: 16px 16px 0 0; + background: #101828; + border: 1px solid rgba(56, 189, 248, 0.28); + transform: translateY(110%); + transition: transform 0.2s ease; +} +body.hub-phone-more-open .hub-phone-more-sheet { + transform: translateY(0); +} +.hub-phone-more-handle { + width: 42px; + height: 4px; + margin: 4px auto 10px; + border-radius: 99px; + background: #3a4568; +} +.hub-phone-more-title { + margin: 0 0 4px; + font-size: 1rem; +} +.hub-phone-more-hint { + margin: 0 0 12px; + font-size: 0.78rem; + color: var(--muted); +} +.hub-phone-more-nav { + display: flex; + flex-direction: column; + gap: 6px; +} +.hub-phone-more-nav a { + display: block; + padding: 12px 12px; + border-radius: 10px; + background: #151a2a; + border: 1px solid #304164; + color: #dbe4ff; +} +.hub-phone-more-nav a.is-active { + background: #2a3f6c; +} +.hub-phone-more-close { + display: block; + width: 100%; + margin-top: 14px; + padding: 10px; + border-radius: 10px; + border: 1px solid #304164; + background: transparent; + color: #8aa0b8; + font: inherit; +} +html.hub-phone-html .page-head { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} +html.hub-phone-html .page-head h1 { + font-size: 1.15rem; + margin: 0; + flex: 1; +} +html.hub-phone-html .overview-bj-clock { + display: none; +} +html.hub-phone-html .overview-summary { + display: none !important; +} +html.hub-phone-html #overview-status { + font-size: 0.78rem; + margin: 0 0 10px; +} +.hub-phone-cards { + display: flex; + flex-direction: column; + gap: 10px; +} +.hub-phone-card { + background: rgba(16, 24, 40, 0.92); + border: 1px solid rgba(56, 189, 248, 0.2); + border-radius: 12px; + padding: 12px; +} +.hub-phone-card-head { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px 8px; + margin-bottom: 8px; +} +.hub-phone-card-kpis { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + margin-bottom: 8px; +} +.hub-phone-card-kpis .muted { + display: block; + font-size: 0.68rem; + margin-bottom: 2px; +} +.hub-phone-card-pos .pos-brief { + font-size: 0.78rem; +} +.hub-phone-card-exit, +.hub-phone-card-liq { + font-size: 0.76rem; + margin-top: 4px; +} +.hub-phone-card-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 10px; +} +html.hub-phone-html .desk { + display: none !important; +} +.hub-phone-desk { + display: none; +} +html.hub-phone-html .hub-phone-desk { + display: block; +} +.hub-phone-desk-list { + list-style: none; + margin: 12px 0 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 8px; +} +.hub-phone-desk-open { + width: 100%; + text-align: left; +} +html.hub-phone-html .settings-row-2, +html.hub-phone-html .settings-grid { + display: block; +} +html.hub-phone-html .inst-table, +html.hub-phone-html .backup-table, +html.hub-phone-html .stats-table { + font-size: 0.78rem; +} +html.hub-phone-html .table-wrap { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} +html.hub-phone-html .login-wrap { + padding: 48px 16px 24px; +} +html.hub-phone-html .login-card { + width: 100%; + max-width: none; +} diff --git a/static/hub.js b/static/hub.js index b0ca8ed..fda84b5 100644 --- a/static/hub.js +++ b/static/hub.js @@ -298,6 +298,104 @@ ); } + function isHubPhone() { + return ( + document.documentElement.classList.contains("hub-phone-html") || + !!(document.body && document.body.classList.contains("hub-phone")) + ); + } + + function openClient(instanceId) { + if (!instanceId) return; + fetch("/api/instances/" + encodeURIComponent(instanceId) + "/open_url") + .then(function (r) { + return r.json(); + }) + .then(function (d) { + if (d && d.url) { + window.location.href = d.url; + return; + } + window.location.href = "/desk?id=" + encodeURIComponent(instanceId); + }) + .catch(function () { + window.location.href = "/desk?id=" + encodeURIComponent(instanceId); + }); + } + + function bindOverviewActions(root) { + if (!root) return; + root.querySelectorAll("[data-close-opt]").forEach(function (btn) { + btn.addEventListener("click", function () { + requestClose(btn); + }); + }); + root.querySelectorAll("[data-open-inst]").forEach(function (btn) { + btn.addEventListener("click", function () { + openClient(btn.getAttribute("data-open-inst")); + }); + }); + } + + function renderPhoneCard(row) { + const snap = row.snapshot || {}; + const inst = snap.instance || {}; + const pnl = + snap.pnl && snap.pnl.unrealized_usdt != null + ? snap.pnl.unrealized_usdt + : snap.upl_total_usdc; + const brief = snap.positions_brief || snap.positions || []; + const cond = activeCondOrder(snap); + const online = !!row.online; + const m = String(inst.mode || "").toLowerCase(); + const modeHtml = + m === "sim" || m === "live" + ? '' + escapeHtml(m) + "" + : '' + escapeHtml(inst.mode || "—") + ""; + const err = row.error + ? '
错误: ' + escapeHtml(row.error) + "
" + : ""; + return ( + '
' + + '
' + + "" + + escapeHtml(row.name || "") + + "" + + (online ? '在线' : '离线') + + modeHtml + + condTagHtml(cond) + + "
" + + err + + '
' + + '
浮盈' + + fmtPnlHtml(pnl) + + "
" + + '
权利金' + + premiumCell(snap) + + "
" + + '
占比' + + premiumRatioCell(snap) + + "
" + + "
" + + '
' + + positionSummary(brief, cond) + + "
" + + '
出场 ' + + (brief.length ? escapeHtml(formatExitBrief(brief[0])) : "—") + + "
" + + '
' + + bidLiquiditySummary(brief) + + "
" + + '
' + + '' + + closeActionButtons(brief, row.id, online) + + "
" + + "
" + ); + } + function renderRow(row) { const snap = row.snapshot || {}; const inst = snap.instance || {}; @@ -594,6 +692,10 @@ if (!list.length) { grid.innerHTML = '

暂无启用实例,请先到「实例」添加.

'; + } else if (isHubPhone()) { + grid.innerHTML = + '
' + list.map(renderPhoneCard).join("") + "
"; + bindOverviewActions(grid); } else { grid.innerHTML = '
' + @@ -602,11 +704,7 @@ "" + list.map(renderRow).join("") + "
"; - grid.querySelectorAll("[data-close-opt]").forEach(function (btn) { - btn.addEventListener("click", function () { - requestClose(btn); - }); - }); + bindOverviewActions(grid); } if (status) { const s = summarizeOverview(list); diff --git a/static/hub_phone.js b/static/hub_phone.js new file mode 100644 index 0000000..9eb42c4 --- /dev/null +++ b/static/hub_phone.js @@ -0,0 +1,62 @@ +(function (global) { + var MQ = "(max-width: 720px)"; + + function isMobile() { + try { + return window.matchMedia(MQ).matches; + } catch (e) { + return false; + } + } + + function sync() { + var on = isMobile(); + document.documentElement.classList.toggle("hub-phone-html", on); + if (document.body) document.body.classList.toggle("hub-phone", on); + if (!on) closeMore(); + } + + function closeMore() { + if (!document.body) return; + document.body.classList.remove("hub-phone-more-open"); + var more = document.getElementById("hub-phone-more"); + var btn = document.getElementById("hub-phone-more-btn"); + if (more) more.setAttribute("aria-hidden", "true"); + if (btn) btn.setAttribute("aria-expanded", "false"); + } + + function openMore() { + if (!isMobile()) return; + document.body.classList.add("hub-phone-more-open"); + var more = document.getElementById("hub-phone-more"); + var btn = document.getElementById("hub-phone-more-btn"); + if (more) more.setAttribute("aria-hidden", "false"); + if (btn) btn.setAttribute("aria-expanded", "true"); + } + + function boot() { + sync(); + var moreBtn = document.getElementById("hub-phone-more-btn"); + var backdrop = document.getElementById("hub-phone-more-backdrop"); + var closeBtn = document.getElementById("hub-phone-more-close"); + if (moreBtn) { + moreBtn.addEventListener("click", function (ev) { + ev.preventDefault(); + if (document.body.classList.contains("hub-phone-more-open")) closeMore(); + else openMore(); + }); + } + if (backdrop) backdrop.addEventListener("click", closeMore); + if (closeBtn) closeBtn.addEventListener("click", closeMore); + document.addEventListener("keydown", function (ev) { + if (ev.key === "Escape") closeMore(); + }); + var t = null; + window.addEventListener("resize", function () { + clearTimeout(t); + t = setTimeout(sync, 120); + }); + } + + global.HubPhone = { boot: boot, isMobile: isMobile, sync: sync }; +})(window); diff --git a/static/hub_quotes.js b/static/hub_quotes.js index 7c90c1a..7323cd6 100644 --- a/static/hub_quotes.js +++ b/static/hub_quotes.js @@ -77,11 +77,7 @@ return best; } - function paint(list) { - const root = document.getElementById("nav-spot-tickers"); - if (!root) return; - const src = pickQuoteSource(list); - const tickers = src ? src.tickers : []; + function paintRoot(root, tickers, src) { ["ETH", "BTC"].forEach(function (base) { const row = root.querySelector('.nav-spot-row[data-base="' + base + '"]'); if (!row) return; @@ -119,6 +115,14 @@ } } + function paint(list) { + const src = pickQuoteSource(list); + const tickers = src ? src.tickers : []; + document.querySelectorAll("[data-hub-quotes]").forEach(function (root) { + paintRoot(root, tickers, src); + }); + } + let timer = null; let inflight = false; @@ -137,7 +141,7 @@ } function boot() { - if (!document.getElementById("nav-spot-tickers")) return; + if (!document.querySelector("[data-hub-quotes]")) return; if (global.HubOverview) return; refresh(); if (timer) clearInterval(timer); diff --git a/templates/base.html b/templates/base.html index 852adcf..ec1b19e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -2,7 +2,7 @@ - + {% block title %}加密货币|OKX 期权中控{% endblock %} @@ -11,16 +11,40 @@ - + + {% if session.get('logged_in') %} - 退出 + 退出 {% endif %}
{% if session.get('logged_in') %} - 加密货币|OKX 期权中控 -
+
+
期权中控
+ +
+ 加密货币|OKX 期权中控 +
-
+ {% if session.get('logged_in') %} + + + {% endif %} {% if session.get('logged_in') and not request.path.startswith('/desk') %} - + {% endif %} {% block scripts %}{% endblock %} {% if session.get('logged_in') and not request.path.startswith('/desk') %} {% endif %} + + diff --git a/templates/dashboard.html b/templates/dashboard.html index b035552..2ffede8 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -15,6 +15,6 @@
{% endblock %} {% block scripts %} - + {% endblock %} diff --git a/templates/desk.html b/templates/desk.html index 6f839c8..82687be 100644 --- a/templates/desk.html +++ b/templates/desk.html @@ -37,9 +37,44 @@ {% endif %} +
+

点开后进入该客户端(手机页),不经过中控工作台嵌页。

+ +
{% endblock %} {% block scripts %} +