fix(hub): use single position table header on desktop monitor cards

Render all exchange positions in one table row group instead of repeating column headers per symbol.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-03 23:20:34 +08:00
parent 833c51e34a
commit 7957a62c65
3 changed files with 76 additions and 26 deletions
+8
View File
@@ -1272,6 +1272,14 @@ body.market-chart-fs-open {
margin-bottom: 0;
}
.pos-table-wrap {
margin-bottom: 8px;
}
.data-table-positions tbody tr:not(:last-child) td {
border-bottom: 1px dashed var(--border-soft);
}
.pos-action-group {
display: inline-flex;
flex-direction: row;
+67 -25
View File
@@ -1537,16 +1537,25 @@
.join("");
}
function renderPositionBlock(exchangeId, exchangeKey, x, monitorOrder, trendPlan, tickMap, opts) {
function renderPositionTableRow(
exchangeId,
exchangeKey,
x,
monitorOrder,
trendPlan,
tickMap,
opts
) {
const options = opts || {};
const compact = !!options.compact;
const symAttr = esc(x.symbol || "").replace(/"/g, "&quot;");
const exKeyAttr = esc(exchangeKey || exchangeId || "").replace(/"/g, "&quot;");
const sideAttr = esc((x.side || "").toLowerCase()).replace(/"/g, "&quot;");
const side = sideAttr || "long";
const contractsAttr = esc(String(x.contracts != null ? x.contracts : "")).replace(/"/g, "&quot;");
const contractsAttr = esc(String(x.contracts != null ? x.contracts : "")).replace(
/"/g,
"&quot;"
);
const cond = condOrdersFromPosition(x);
const reg = Array.isArray(x.regular_orders) ? x.regular_orders : [];
const tpsl = resolvePositionTpsl(x, monitorOrder, trendPlan);
const beSecured = isBreakevenSecured(side, tpsl.entry, monitorOrder, cond, x);
const slAttr = esc(String(tpsl.sl)).replace(/"/g, "&quot;");
@@ -1559,13 +1568,7 @@
<button type="button" class="btn-place-tpsl btn-sm ghost" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}" data-contracts="${contractsAttr}" data-sl="${slAttr}" data-tp="${tpAttr}">委托</button>
<button type="button" class="btn-close-pos btn-sm danger" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}">平仓</button>
</div>`;
const ordersBlock = compact
? ""
: renderOrdersCollapse(exchangeId, x.symbol, cond, reg, tickMap);
return `<div class="pos-block">
<div class="table-scroll">
<table class="data-table"><thead><tr><th>合约</th><th>方向</th><th>开仓价</th><th>标记价</th><th>张数</th><th>浮盈</th><th>操作</th></tr></thead><tbody>
<tr>
return `<tr>
<td class="td-symbol"><button type="button" class="btn-open-market sym-link" ${mktAttrs} title="打开行情区(含入场/止盈止损)">${esc(x.symbol)}</button>${symBeBadge}</td>
<td class="${sideDirCls(x.side)}">${renderDirectionHtml(x.side)}</td>
<td class="td-entry">${fmtEntryPrice(x, tickMap)}</td>
@@ -1573,13 +1576,57 @@
<td>${fmt(x.contracts, 4)}</td>
<td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 2)}</td>
<td class="td-actions">${actionCell}</td>
</tr>
</tr>`;
}
function renderPositionBlock(exchangeId, exchangeKey, x, monitorOrder, trendPlan, tickMap, opts) {
const options = opts || {};
const compact = !!options.compact;
const reg = Array.isArray(x.regular_orders) ? x.regular_orders : [];
const cond = condOrdersFromPosition(x);
const ordersBlock = compact
? ""
: renderOrdersCollapse(exchangeId, x.symbol, cond, reg, tickMap);
const rowHtml = renderPositionTableRow(
exchangeId,
exchangeKey,
x,
monitorOrder,
trendPlan,
tickMap,
opts
);
return `<div class="pos-block">
<div class="table-scroll">
<table class="data-table"><thead><tr><th>合约</th><th>方向</th><th>开仓价</th><th>标记价</th><th>张数</th><th>浮盈</th><th>操作</th></tr></thead><tbody>
${rowHtml}
</tbody></table>
</div>
${ordersBlock}
</div>`;
}
function renderGridPositionsTable(exchangeId, exchangeKey, positions, orders, trends, tickMap) {
const rows = positions
.map((p) =>
renderPositionTableRow(
exchangeId,
exchangeKey,
p,
findMonitorOrder(orders, p.symbol, p.side),
findTrendPlan(trends, p.symbol, p.side),
tickMap,
{ compact: true }
)
)
.join("");
return `<div class="pos-table-wrap table-scroll">
<table class="data-table data-table-positions"><thead><tr><th>合约</th><th>方向</th><th>开仓价</th><th>标记价</th><th>张数</th><th>浮盈</th><th>操作</th></tr></thead><tbody>
${rows}
</tbody></table>
</div>`;
}
function renderGridBody(row, ag, pos, hm, flaskOk, keys, orders, trends, rolls, kmap) {
const tickMap = buildPriceTickMap(row);
let inner = `<div class="stat-row">
@@ -1588,19 +1635,14 @@
</div>`;
inner += `<div class="section-title">交易所持仓 · ${pos.length} 仓</div>`;
if (pos.length) {
inner += pos
.map((p) =>
renderPositionBlock(
row.id,
row.key || row.id,
p,
findMonitorOrder(orders, p.symbol, p.side),
findTrendPlan(trends, p.symbol, p.side),
tickMap,
{ compact: true }
)
)
.join("");
inner += renderGridPositionsTable(
row.id,
row.key || row.id,
pos,
orders,
trends,
tickMap
);
} else {
inner += '<div class="empty-hint">无持仓</div>';
}
+1 -1
View File
@@ -246,6 +246,6 @@
<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=20260603-hub-binance-tick"></script>
<script src="/assets/app.js?v=20260604-hub-grid-compact2"></script>
<script src="/assets/app.js?v=20260604-hub-pos-table"></script>
</body>
</html>