Hide empty dashboard sections and render data as tables.
Orders/keys/strategy/options/hedge only appear when they have rows, shown in table layout. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* 实例数据看板:拉 /api/instance/dashboard 渲染只读区块.
|
||||
* 实例数据看板:拉 /api/instance/dashboard 渲染只读表格.
|
||||
* 各区块无数据时不展示;有数据按表格展示.
|
||||
*/
|
||||
(function (global) {
|
||||
const SECTION_ORDER = ["orders", "keys", "strategy", "options", "hedge_plan"];
|
||||
@@ -28,9 +29,9 @@
|
||||
}
|
||||
|
||||
function fmtPnl(v) {
|
||||
if (v == null || v === "") return "";
|
||||
if (v == null || v === "") return "—";
|
||||
const n = Number(v);
|
||||
if (!Number.isFinite(n)) return "";
|
||||
if (!Number.isFinite(n)) return "—";
|
||||
const cls = n > 0 ? "pos-pnl-profit" : n < 0 ? "pos-pnl-loss" : "";
|
||||
const sign = n > 0 ? "+" : "";
|
||||
return '<span class="' + cls + '">' + sign + n.toFixed(2) + "U</span>";
|
||||
@@ -53,46 +54,197 @@
|
||||
location.href = path;
|
||||
}
|
||||
|
||||
function renderItems(items) {
|
||||
if (!items || !items.length) {
|
||||
return '<p class="inst-dash-empty muted">暂无</p>';
|
||||
}
|
||||
function tableWrap(headers, rowsHtml) {
|
||||
return (
|
||||
'<div class="inst-dash-list">' +
|
||||
items
|
||||
.map(function (it) {
|
||||
const meta = [];
|
||||
if (it.entry != null) meta.push("入场 " + fmtNum(it.entry));
|
||||
if (it.stop_loss != null) meta.push("止损 " + fmtNum(it.stop_loss));
|
||||
if (it.take_profit != null) meta.push("止盈 " + fmtNum(it.take_profit));
|
||||
if (it.upper != null || it.lower != null) {
|
||||
meta.push("上 " + fmtNum(it.upper) + " / 下 " + fmtNum(it.lower));
|
||||
}
|
||||
const pnlHtml = fmtPnl(it.pnl);
|
||||
if (pnlHtml) meta.push(pnlHtml);
|
||||
return (
|
||||
'<button type="button" class="inst-dash-item" data-dash-tab="' +
|
||||
escapeHtml(it.tab || "") +
|
||||
'">' +
|
||||
'<div class="inst-dash-item-title">' +
|
||||
escapeHtml(it.title || "-") +
|
||||
"</div>" +
|
||||
(it.subtitle
|
||||
? '<div class="inst-dash-item-sub muted">' + escapeHtml(it.subtitle) + "</div>"
|
||||
: "") +
|
||||
(meta.length ? '<div class="inst-dash-item-meta">' + meta.join(" · ") + "</div>" : "") +
|
||||
"</button>"
|
||||
);
|
||||
})
|
||||
.join("") +
|
||||
"</div>"
|
||||
'<div class="inst-dash-table-wrap">' +
|
||||
'<table class="inst-dash-table">' +
|
||||
"<thead><tr>" +
|
||||
headers.map(function (h) {
|
||||
return "<th>" + escapeHtml(h) + "</th>";
|
||||
}).join("") +
|
||||
"</tr></thead>" +
|
||||
"<tbody>" +
|
||||
rowsHtml +
|
||||
"</tbody></table></div>"
|
||||
);
|
||||
}
|
||||
|
||||
function rowClickAttrs(tab) {
|
||||
return ' class="inst-dash-row" data-dash-tab="' + escapeHtml(tab || "") + '" role="link" tabindex="0"';
|
||||
}
|
||||
|
||||
function renderOrdersTable(items) {
|
||||
const rows = items
|
||||
.map(function (it) {
|
||||
return (
|
||||
"<tr" +
|
||||
rowClickAttrs(it.tab || "trade") +
|
||||
">" +
|
||||
"<td>" +
|
||||
escapeHtml(it.symbol || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.direction_label || it.direction || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.subtitle || "—") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.entry) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.stop_loss) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.take_profit) +
|
||||
"</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return tableWrap(["合约", "方向", "类型", "入场", "止损", "止盈"], rows);
|
||||
}
|
||||
|
||||
function renderKeysTable(items) {
|
||||
const rows = items
|
||||
.map(function (it) {
|
||||
return (
|
||||
"<tr" +
|
||||
rowClickAttrs(it.tab || "key_monitor") +
|
||||
">" +
|
||||
"<td>" +
|
||||
escapeHtml(it.symbol || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.direction_label || it.direction || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.subtitle || "—") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.upper) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.lower) +
|
||||
"</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return tableWrap(["合约", "方向", "信号", "上沿", "下沿"], rows);
|
||||
}
|
||||
|
||||
function renderStrategyTable(items) {
|
||||
const rows = items
|
||||
.map(function (it) {
|
||||
const kindLabel = it.kind === "roll" ? "顺势加仓" : it.kind === "trend" ? "趋势回调" : "策略";
|
||||
return (
|
||||
"<tr" +
|
||||
rowClickAttrs(it.tab || "strategy") +
|
||||
">" +
|
||||
"<td>" +
|
||||
escapeHtml(kindLabel) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.symbol || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.direction_label || it.direction || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.status || "—") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.entry) +
|
||||
"</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return tableWrap(["类型", "合约", "方向", "状态", "入场"], rows);
|
||||
}
|
||||
|
||||
function renderOptionsTable(items) {
|
||||
const rows = items
|
||||
.map(function (it) {
|
||||
const opt =
|
||||
String(it.opt_type || "").toUpperCase() === "C"
|
||||
? "Call"
|
||||
: String(it.opt_type || "").toUpperCase() === "P"
|
||||
? "Put"
|
||||
: it.opt_type || "—";
|
||||
return (
|
||||
"<tr" +
|
||||
rowClickAttrs(it.tab || "options") +
|
||||
">" +
|
||||
"<td>" +
|
||||
escapeHtml(it.inst_id || it.title || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(opt) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtNum(it.pos) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtPnl(it.pnl) +
|
||||
"</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return tableWrap(["合约", "类型", "张数", "盈亏"], rows);
|
||||
}
|
||||
|
||||
function renderHedgeTable(items) {
|
||||
const rows = items
|
||||
.map(function (it) {
|
||||
return (
|
||||
"<tr" +
|
||||
rowClickAttrs(it.tab || "hedge_plan") +
|
||||
">" +
|
||||
"<td>#" +
|
||||
escapeHtml(it.id != null ? it.id : "—") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.underlying || "-") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.plan_type || "—") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.status || "—") +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
escapeHtml(it.subtitle || "—") +
|
||||
"</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return tableWrap(["ID", "标的", "计划类型", "状态", "说明"], rows);
|
||||
}
|
||||
|
||||
function renderTable(key, items) {
|
||||
if (key === "orders") return renderOrdersTable(items);
|
||||
if (key === "keys") return renderKeysTable(items);
|
||||
if (key === "strategy") return renderStrategyTable(items);
|
||||
if (key === "options") return renderOptionsTable(items);
|
||||
if (key === "hedge_plan") return renderHedgeTable(items);
|
||||
return "";
|
||||
}
|
||||
|
||||
function sectionHasData(sec) {
|
||||
if (!sec) return false;
|
||||
const count = Number(sec.count);
|
||||
if (Number.isFinite(count) && count > 0) return true;
|
||||
return Array.isArray(sec.items) && sec.items.length > 0;
|
||||
}
|
||||
|
||||
function renderSection(key, sec) {
|
||||
if (!sec) return "";
|
||||
if ((key === "options" || key === "hedge_plan") && !sec.visible) return "";
|
||||
const count = Number(sec.count) || 0;
|
||||
if (!sectionHasData(sec)) return "";
|
||||
const items = sec.items || [];
|
||||
const count = Number(sec.count) || items.length;
|
||||
return (
|
||||
'<section class="inst-dash-section" data-dash-section="' +
|
||||
escapeHtml(key) +
|
||||
@@ -107,16 +259,23 @@
|
||||
escapeHtml(sec.tab || "") +
|
||||
'">打开</button>' +
|
||||
"</div>" +
|
||||
renderItems(sec.items || []) +
|
||||
renderTable(key, items) +
|
||||
"</section>"
|
||||
);
|
||||
}
|
||||
|
||||
function bindClicks(el) {
|
||||
if (!el) return;
|
||||
el.querySelectorAll("[data-dash-tab]").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
goTab(btn.getAttribute("data-dash-tab"));
|
||||
el.querySelectorAll("[data-dash-tab]").forEach(function (node) {
|
||||
const handler = function () {
|
||||
goTab(node.getAttribute("data-dash-tab"));
|
||||
};
|
||||
node.addEventListener("click", handler);
|
||||
node.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
ev.preventDefault();
|
||||
handler();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -140,9 +299,11 @@
|
||||
}
|
||||
if (updated) updated.textContent = "更新 " + (data.updated_at || "—");
|
||||
if (sections) {
|
||||
sections.innerHTML = SECTION_ORDER.map(function (k) {
|
||||
const html = SECTION_ORDER.map(function (k) {
|
||||
return renderSection(k, data[k]);
|
||||
}).join("");
|
||||
sections.innerHTML =
|
||||
html || '<p class="inst-dash-empty muted">当前无活跃监控与持仓</p>';
|
||||
bindClicks(sections);
|
||||
}
|
||||
if (status) status.textContent = "";
|
||||
|
||||
@@ -279,10 +279,11 @@
|
||||
.inst-dash-section-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
|
||||
.inst-dash-section-head h3{margin:0;font-size:.95rem;color:#dbe4ff}
|
||||
.inst-dash-count{display:inline-block;min-width:1.4em;padding:1px 7px;margin-left:4px;border-radius:999px;background:#1f3a5a;color:#8fc8ff;font-size:.75rem;font-weight:600}
|
||||
.inst-dash-empty{margin:0;padding:12px;text-align:center;border:1px dashed #2a3348;border-radius:8px;font-size:.84rem}
|
||||
.inst-dash-list{display:flex;flex-direction:column;gap:8px}
|
||||
.inst-dash-item{display:block;width:100%;text-align:left;padding:10px 12px;border-radius:8px;border:1px solid #2a3150;background:#1a2034;color:#eaeaea;cursor:pointer}
|
||||
.inst-dash-item:hover{border-color:#3d4f78;background:#1e2740}
|
||||
.inst-dash-item-title{font-weight:600;font-size:.9rem;color:#dbe4ff}
|
||||
.inst-dash-item-sub{font-size:.78rem;margin-top:3px}
|
||||
.inst-dash-item-meta{font-size:.78rem;margin-top:6px;color:#a8b0d8;font-variant-numeric:tabular-nums}
|
||||
.inst-dash-empty{margin:0;padding:14px;text-align:center;border:1px dashed #2a3348;border-radius:8px;font-size:.84rem}
|
||||
.inst-dash-table-wrap{overflow:auto;border:1px solid #2a3150;border-radius:8px}
|
||||
.inst-dash-table{width:100%;border-collapse:collapse;font-size:.84rem}
|
||||
.inst-dash-table th,.inst-dash-table td{padding:8px 10px;text-align:left;border-bottom:1px solid #25253b;white-space:nowrap}
|
||||
.inst-dash-table th{color:#a9a9ff;background:#151a2a;font-weight:600}
|
||||
.inst-dash-table tbody tr:last-child td{border-bottom:none}
|
||||
.inst-dash-table tbody tr.inst-dash-row{cursor:pointer}
|
||||
.inst-dash-table tbody tr.inst-dash-row:hover{background:#1e2740}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="inst-dash-head">
|
||||
<div>
|
||||
<h2 style="margin-bottom:4px">数据看板</h2>
|
||||
<p class="muted inst-dash-desc">本户活跃监控总览 · 只读 · 期权/对冲有数据才显示</p>
|
||||
<p class="muted inst-dash-desc">本户活跃监控总览 · 只读 · 无数据的区块不显示 · 有数据按表格展示</p>
|
||||
</div>
|
||||
<div class="inst-dash-head-actions">
|
||||
<span class="muted inst-dash-updated" id="inst-dash-updated">—</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script src="/static/instance_theme.js?v=50"></script>
|
||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=7">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=8">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=95">
|
||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
@@ -110,7 +110,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
|
||||
<script src="/static/instance_stats.js?v=4"></script>
|
||||
{% include 'embed_boot_scripts.html' %}
|
||||
<script src="/static/records_review_page.js?v=2"></script>
|
||||
<script src="/static/instance_dashboard.js?v=1"></script>
|
||||
<script src="/static/instance_dashboard.js?v=2"></script>
|
||||
<script>
|
||||
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
||||
</script>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||
<title>{{ pwa_app_name }}</title>
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=7">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=8">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=95">
|
||||
|
||||
</head>
|
||||
@@ -1992,7 +1992,7 @@ tickOrderHoldDurations();
|
||||
setInterval(refreshPriceSnapshotConditional, {{ price_refresh_seconds * 1000 }});
|
||||
</script>
|
||||
<script src="/static/records_review_page.js?v=2"></script>
|
||||
<script src="/static/instance_dashboard.js?v=1"></script>
|
||||
<script src="/static/instance_dashboard.js?v=2"></script>
|
||||
<script>
|
||||
window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
||||
{% if page == 'dashboard' %}
|
||||
|
||||
Reference in New Issue
Block a user