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 期权中控
+
-
+
Ξ
@@ -54,14 +78,38 @@
{% endwith %}
{% block body %}{% endblock %}
+ {% 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 %}
+
+ 点开后进入该客户端(手机页),不经过中控工作台嵌页。
+
+ {% for inst in instances %}
+ -
+
+
+ {% else %}
+ - 无启用实例
+ {% endfor %}
+
+
{% endblock %}
{% block scripts %}
+