From 9dbf6b1f1e7146f0bc5f0865da124ce819af77f6 Mon Sep 17 00:00:00 2001 From: dekun Date: Mon, 29 Jun 2026 23:47:59 +0800 Subject: [PATCH] Fix position cards, breakeven badge, and tablet equal-height layout. Stop clipping pos cards and match trailing-BE stop detection for the badge. On tablet, align order and live-trading panels to equal height with internal scroll; keep desktop positions scrollable after three cards. Co-authored-by: Cursor --- install_trading.py | 7 +++- static/css/responsive.css | 1 + static/css/tech.css | 2 +- static/css/trade.css | 82 ++++++++++++++++++++++++++++++++++++++- static/js/dashboard.js | 21 +++++++--- static/js/trade.js | 63 +++++++++++++++++++++--------- 6 files changed, 149 insertions(+), 27 deletions(-) diff --git a/install_trading.py b/install_trading.py index 16b65a4..4dc22ab 100644 --- a/install_trading.py +++ b/install_trading.py @@ -186,8 +186,13 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se if entry_f <= 0: return False tick = float(tick_size or 0) or max(abs(entry_f) * 1e-6, 0.01) - buf = tick * max(2, get_trailing_be_tick_buffer(get_setting)) + be_mult = max(1, get_trailing_be_tick_buffer(get_setting)) d = (direction or "long").strip().lower() + expected_be = entry_f + be_mult * tick if d == "long" else entry_f - be_mult * tick + tol = be_mult * tick + tick * 0.05 + if abs(sl_f - expected_be) <= tol: + return True + buf = tick * max(2, be_mult) near = abs(sl_f - entry_f) <= buf + tick if d == "long": return near and sl_f >= entry_f - tick * 0.05 diff --git a/static/css/responsive.css b/static/css/responsive.css index e046a7d..17de15e 100644 --- a/static/css/responsive.css +++ b/static/css/responsive.css @@ -777,6 +777,7 @@ html[data-layout="tablet"][data-orientation="landscape"] .trade-split { html[data-layout="tablet"][data-orientation="landscape"] .split-grid .card, html[data-layout="tablet"][data-orientation="landscape"] .trade-split .card { min-height: 380px; + height: 100%; } html[data-layout="tablet"][data-orientation="landscape"] .trade-form-line.line-3 { diff --git a/static/css/tech.css b/static/css/tech.css index 945902f..cb00ec1 100644 --- a/static/css/tech.css +++ b/static/css/tech.css @@ -166,7 +166,7 @@ table tbody tr:hover{background:var(--row-hover)} } .pos-card{ - position:relative;overflow:hidden; + position:relative;overflow:visible; transition:border-color .2s,box-shadow .2s; } .pos-card::before{ diff --git a/static/css/trade.css b/static/css/trade.css index e66504f..cfedadc 100644 --- a/static/css/trade.css +++ b/static/css/trade.css @@ -92,7 +92,87 @@ #recommend.card{height:auto} #recommend .card-body{display:flex;flex-direction:column} #recommend .trade-table-wrap{flex:0 0 auto} -#positions .card-body.card-scroll{flex:1;max-height:none;overflow-y:auto} +#trading-live.card, +.trade-split .trade-card#trading-live { + overflow: visible; +} +#position-live-list { + overflow: visible; +} +#trading-live .trading-live-body.card-scroll { + flex: 1; + max-height: none; + overflow-y: visible; +} + +/* 电脑端:右侧委托面板随内容增高;平板见下方等高规则 */ +@media (min-width: 768px) { + html:not([data-layout="phone"]):not(.layout-phone):not([data-layout="tablet"]):not(.layout-tablet) .trade-split .card-body { + overflow: visible; + } + html:not([data-layout="phone"]):not(.layout-phone):not([data-layout="tablet"]):not(.layout-tablet) .trade-split .trade-card#trading-live { + height: auto; + min-height: auto; + align-self: start; + } + html:not([data-layout="phone"]):not(.layout-phone):not([data-layout="tablet"]):not(.layout-tablet) .trade-split .trade-card#trading-live .trading-live-body { + overflow: visible; + flex: none; + } + html:not([data-layout="phone"]):not(.layout-phone):not([data-layout="tablet"]):not(.layout-tablet) #position-live-list.pos-list-many { + --pos-card-unit-h: 17.5rem; + max-height: calc(var(--pos-card-unit-h) * 3 + 1.5rem); + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding-right: .15rem; + } +} + +/* 平板:期货下单与委托持仓两列卡片等高 */ +html[data-layout="tablet"] .trade-split, +html.layout-tablet .trade-split { + align-items: stretch; +} +html[data-layout="tablet"] .trade-split .trade-card#order, +html[data-layout="tablet"] .trade-split .trade-card#trading-live, +html.layout-tablet .trade-split .trade-card#order, +html.layout-tablet .trade-split .trade-card#trading-live { + height: 100%; + min-height: 380px; + align-self: stretch; +} +html[data-layout="tablet"] .trade-split .trade-card#order .card-body, +html.layout-tablet .trade-split .trade-card#order .card-body { + flex: 1; + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +html[data-layout="tablet"] .trade-split .trade-card#trading-live .trading-live-body, +html.layout-tablet .trade-split .trade-card#trading-live .trading-live-body { + flex: 1; + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +html[data-layout="tablet"] #position-live-list, +html.layout-tablet #position-live-list { + overflow: visible; +} +html[data-layout="tablet"] #position-live-list.pos-list-many, +html.layout-tablet #position-live-list.pos-list-many { + --pos-card-unit-h: 17.5rem; + max-height: calc(var(--pos-card-unit-h) * 3 + 1.5rem); + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding-right: .15rem; +} + +.dash-be-badge, +.pos-be-badge { + font-size: .66rem; + vertical-align: middle; +} .pos-pending-orders{margin-top:.55rem;padding-top:.55rem;border-top:1px dashed var(--table-border)} .pos-pending-orders .pending-title{font-size:.68rem;color:var(--text-muted);margin-bottom:.35rem} .pos-pending-item{display:flex;justify-content:space-between;align-items:center;gap:.5rem;font-size:.75rem;padding:.35rem .5rem;border-radius:6px;margin-bottom:.25rem;background:var(--list-item-bg)} diff --git a/static/js/dashboard.js b/static/js/dashboard.js index 469a755..d8ae924 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -95,8 +95,12 @@ var sl = Number(row.stop_loss); if (isNaN(entry) || isNaN(sl) || entry <= 0) return false; var tick = Number(row.tick_size) || Math.max(Math.abs(entry) * 1e-6, 0.01); - var buf = tick * 2.5; + var beMult = 2; var dir = (row.direction || 'long').toString().toLowerCase(); + var expectedBe = dir === 'short' ? entry - beMult * tick : entry + beMult * tick; + var tol = beMult * tick + tick * 0.05; + if (Math.abs(sl - expectedBe) <= tol) return true; + var buf = tick * Math.max(2, beMult); if (Math.abs(sl - entry) > buf + tick) return false; return dir === 'short' ? sl <= entry + tick * 0.05 : sl >= entry - tick * 0.05; } @@ -234,15 +238,19 @@ titleInner += ' ' + escHtml(exchange) + ''; } titleInner += mainBadge; - titleInner += breakevenBadgeHtml(row); if (code && String(name).toLowerCase() !== String(code).toLowerCase()) { - titleInner += ' ' + escHtml(code) + ' ' + directionBadgeHtml(row); + titleInner += ' ' + escHtml(code) + ''; + titleInner += breakevenBadgeHtml(row); + titleInner += ' ' + directionBadgeHtml(row); } else if (!name && code) { titleInner = (exchange ? '' + escHtml(exchange) + ' ' - : '') + '' + escHtml(code) + ' ' + directionBadgeHtml(row); + : '') + '' + escHtml(code) + ''; + titleInner += breakevenBadgeHtml(row); + titleInner += ' ' + directionBadgeHtml(row); } else { titleInner += ' ' + directionBadgeHtml(row); + titleInner += breakevenBadgeHtml(row); } return titleInner; } @@ -270,13 +278,16 @@ titleInner += ' ' + escHtml(exchange) + ''; } titleInner += mainBadge; - titleInner += breakevenBadgeHtml(row); if (code && String(name).toLowerCase() !== String(code).toLowerCase()) { titleInner += ' ' + escHtml(code) + ''; + titleInner += breakevenBadgeHtml(row); } else if (!name && code) { titleInner = (exchange ? '' + escHtml(exchange) + ' ' : '') + '' + escHtml(code) + ''; + titleInner += breakevenBadgeHtml(row); + } else { + titleInner += breakevenBadgeHtml(row); } return ( '
' + diff --git a/static/js/trade.js b/static/js/trade.js index 8acbfc8..2078a78 100644 --- a/static/js/trade.js +++ b/static/js/trade.js @@ -322,12 +322,14 @@ return; } list.innerHTML = '
暂无持仓。
'; + syncPositionListScroll(0); return; } if (!connected && ctpAutoConnectEnabled) { tryAutoCtpReconnect(); } list.innerHTML = rows.map(buildPosCard).join(''); + syncPositionListScroll(rows.length); bindPendingDismiss(list); bindCancelOpenButtons(list); bindSlTpButtons(list); @@ -1019,32 +1021,55 @@ return '未开启'; } + function syncPositionListScroll(count) { + if (!list) return; + var n = count != null ? count : list.querySelectorAll('.pos-card[data-pos-key]').length; + var isDesktopTablet = !document.documentElement.classList.contains('layout-phone') + && document.documentElement.dataset.layout !== 'phone' + && document.documentElement.dataset.mobile !== '1'; + list.classList.toggle('pos-list-many', isDesktopTablet && n > 3); + } + + function breakevenBadgeHtml(row) { + if (row.breakeven_locked) { + return ' 已保本'; + } + if ((row.trailing_r_locked || 0) >= 1) { + return ' 已保本'; + } + if (row.stop_loss == null || row.entry_price == null) return ''; + var entry = Number(row.entry_price); + var sl = Number(row.stop_loss); + if (isNaN(entry) || isNaN(sl) || entry <= 0) return ''; + var tick = Number(row.tick_size) || Math.max(Math.abs(entry) * 1e-6, 0.01); + var beMult = 2; + var dir = (row.direction || 'long').toString().toLowerCase(); + var expectedBe = dir === 'short' ? entry - beMult * tick : entry + beMult * tick; + var tol = beMult * tick + tick * 0.05; + if (Math.abs(sl - expectedBe) <= tol) { + return ' 已保本'; + } + var buf = tick * Math.max(2, beMult); + if (Math.abs(sl - entry) > buf + tick) return ''; + if (dir === 'short' ? sl <= entry + tick * 0.05 : sl >= entry - tick * 0.05) { + return ' 已保本'; + } + return ''; + } + function posSymbolTitleHtml(row, extraBadges) { extraBadges = extraBadges || ''; var name = row.symbol_name || row.symbol || ''; var code = row.symbol_code || ''; var mainBadge = row.symbol_is_main ? ' 主力' : ''; - var beBadge = (function () { - if (row.breakeven_locked) return ' 已保本'; - if ((row.trailing_r_locked || 0) >= 1) return ' 已保本'; - if (row.stop_loss == null || row.entry_price == null) return ''; - var entry = Number(row.entry_price); - var sl = Number(row.stop_loss); - if (isNaN(entry) || isNaN(sl)) return ''; - var tick = Number(row.tick_size) || Math.max(Math.abs(entry) * 1e-6, 0.01); - var buf = tick * 2.5; - var dir = (row.direction || 'long').toString().toLowerCase(); - if (Math.abs(sl - entry) > buf + tick) return ''; - if (dir === 'short' ? sl <= entry + tick * 0.05 : sl >= entry - tick * 0.05) { - return ' 已保本'; - } - return ''; - }()); - var inner = name + mainBadge + beBadge; + var beBadge = breakevenBadgeHtml(row); + var inner = name + mainBadge; if (code && String(name).toLowerCase() !== String(code).toLowerCase()) { - inner += ' ' + code + ''; + inner += ' ' + code + '' + beBadge; } else if (!name && code) { - inner = '' + code + ''; + inner = '' + code + '' + beBadge; + } else { + inner += beBadge; } if (marketNavEnabled && code) { var href = '/market?symbol=' + encodeURIComponent(code) + '&period=15m';