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; 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 { .pos-action-group {
display: inline-flex; display: inline-flex;
flex-direction: row; flex-direction: row;
+67 -25
View File
@@ -1537,16 +1537,25 @@
.join(""); .join("");
} }
function renderPositionBlock(exchangeId, exchangeKey, x, monitorOrder, trendPlan, tickMap, opts) { function renderPositionTableRow(
exchangeId,
exchangeKey,
x,
monitorOrder,
trendPlan,
tickMap,
opts
) {
const options = opts || {}; const options = opts || {};
const compact = !!options.compact; const compact = !!options.compact;
const symAttr = esc(x.symbol || "").replace(/"/g, "&quot;"); 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 sideAttr = esc((x.side || "").toLowerCase()).replace(/"/g, "&quot;");
const side = sideAttr || "long"; 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 cond = condOrdersFromPosition(x);
const reg = Array.isArray(x.regular_orders) ? x.regular_orders : [];
const tpsl = resolvePositionTpsl(x, monitorOrder, trendPlan); const tpsl = resolvePositionTpsl(x, monitorOrder, trendPlan);
const beSecured = isBreakevenSecured(side, tpsl.entry, monitorOrder, cond, x); const beSecured = isBreakevenSecured(side, tpsl.entry, monitorOrder, cond, x);
const slAttr = esc(String(tpsl.sl)).replace(/"/g, "&quot;"); 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-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> <button type="button" class="btn-close-pos btn-sm danger" data-ex-id="${esc(exchangeId)}" data-symbol="${symAttr}" data-side="${sideAttr}">平仓</button>
</div>`; </div>`;
const ordersBlock = compact return `<tr>
? ""
: 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>
<td class="td-symbol"><button type="button" class="btn-open-market sym-link" ${mktAttrs} title="打开行情区(含入场/止盈止损)">${esc(x.symbol)}</button>${symBeBadge}</td> <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="${sideDirCls(x.side)}">${renderDirectionHtml(x.side)}</td>
<td class="td-entry">${fmtEntryPrice(x, tickMap)}</td> <td class="td-entry">${fmtEntryPrice(x, tickMap)}</td>
@@ -1573,13 +1576,57 @@
<td>${fmt(x.contracts, 4)}</td> <td>${fmt(x.contracts, 4)}</td>
<td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 2)}</td> <td class="${pnlCls(x.unrealized_pnl)}">${fmt(x.unrealized_pnl, 2)}</td>
<td class="td-actions">${actionCell}</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> </tbody></table>
</div> </div>
${ordersBlock} ${ordersBlock}
</div>`; </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) { function renderGridBody(row, ag, pos, hm, flaskOk, keys, orders, trends, rolls, kmap) {
const tickMap = buildPriceTickMap(row); const tickMap = buildPriceTickMap(row);
let inner = `<div class="stat-row"> let inner = `<div class="stat-row">
@@ -1588,19 +1635,14 @@
</div>`; </div>`;
inner += `<div class="section-title">交易所持仓 · ${pos.length} 仓</div>`; inner += `<div class="section-title">交易所持仓 · ${pos.length} 仓</div>`;
if (pos.length) { if (pos.length) {
inner += pos inner += renderGridPositionsTable(
.map((p) => row.id,
renderPositionBlock( row.key || row.id,
row.id, pos,
row.key || row.id, orders,
p, trends,
findMonitorOrder(orders, p.symbol, p.side), tickMap
findTrendPlan(trends, p.symbol, p.side), );
tickMap,
{ compact: true }
)
)
.join("");
} else { } else {
inner += '<div class="empty-hint">无持仓</div>'; inner += '<div class="empty-hint">无持仓</div>';
} }
+1 -1
View File
@@ -246,6 +246,6 @@
<div id="toast"></div> <div id="toast"></div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script> <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/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> </body>
</html> </html>