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 <cursoragent@cursor.com>
This commit is contained in:
+6
-1
@@ -186,8 +186,13 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
|
|||||||
if entry_f <= 0:
|
if entry_f <= 0:
|
||||||
return False
|
return False
|
||||||
tick = float(tick_size or 0) or max(abs(entry_f) * 1e-6, 0.01)
|
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()
|
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
|
near = abs(sl_f - entry_f) <= buf + tick
|
||||||
if d == "long":
|
if d == "long":
|
||||||
return near and sl_f >= entry_f - tick * 0.05
|
return near and sl_f >= entry_f - tick * 0.05
|
||||||
|
|||||||
@@ -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"] .split-grid .card,
|
||||||
html[data-layout="tablet"][data-orientation="landscape"] .trade-split .card {
|
html[data-layout="tablet"][data-orientation="landscape"] .trade-split .card {
|
||||||
min-height: 380px;
|
min-height: 380px;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-layout="tablet"][data-orientation="landscape"] .trade-form-line.line-3 {
|
html[data-layout="tablet"][data-orientation="landscape"] .trade-form-line.line-3 {
|
||||||
|
|||||||
+1
-1
@@ -166,7 +166,7 @@ table tbody tr:hover{background:var(--row-hover)}
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pos-card{
|
.pos-card{
|
||||||
position:relative;overflow:hidden;
|
position:relative;overflow:visible;
|
||||||
transition:border-color .2s,box-shadow .2s;
|
transition:border-color .2s,box-shadow .2s;
|
||||||
}
|
}
|
||||||
.pos-card::before{
|
.pos-card::before{
|
||||||
|
|||||||
+81
-1
@@ -92,7 +92,87 @@
|
|||||||
#recommend.card{height:auto}
|
#recommend.card{height:auto}
|
||||||
#recommend .card-body{display:flex;flex-direction:column}
|
#recommend .card-body{display:flex;flex-direction:column}
|
||||||
#recommend .trade-table-wrap{flex:0 0 auto}
|
#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{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-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)}
|
.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)}
|
||||||
|
|||||||
+16
-5
@@ -95,8 +95,12 @@
|
|||||||
var sl = Number(row.stop_loss);
|
var sl = Number(row.stop_loss);
|
||||||
if (isNaN(entry) || isNaN(sl) || entry <= 0) return false;
|
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 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 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;
|
if (Math.abs(sl - entry) > buf + tick) return false;
|
||||||
return dir === 'short' ? sl <= entry + tick * 0.05 : sl >= entry - tick * 0.05;
|
return dir === 'short' ? sl <= entry + tick * 0.05 : sl >= entry - tick * 0.05;
|
||||||
}
|
}
|
||||||
@@ -234,15 +238,19 @@
|
|||||||
titleInner += ' <span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span>';
|
titleInner += ' <span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span>';
|
||||||
}
|
}
|
||||||
titleInner += mainBadge;
|
titleInner += mainBadge;
|
||||||
titleInner += breakevenBadgeHtml(row);
|
|
||||||
if (code && String(name).toLowerCase() !== String(code).toLowerCase()) {
|
if (code && String(name).toLowerCase() !== String(code).toLowerCase()) {
|
||||||
titleInner += ' <span class="text-accent">' + escHtml(code) + '</span> ' + directionBadgeHtml(row);
|
titleInner += ' <span class="text-accent">' + escHtml(code) + '</span>';
|
||||||
|
titleInner += breakevenBadgeHtml(row);
|
||||||
|
titleInner += ' ' + directionBadgeHtml(row);
|
||||||
} else if (!name && code) {
|
} else if (!name && code) {
|
||||||
titleInner = (exchange
|
titleInner = (exchange
|
||||||
? '<span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span> '
|
? '<span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span> '
|
||||||
: '') + '<span class="text-accent">' + escHtml(code) + '</span> ' + directionBadgeHtml(row);
|
: '') + '<span class="text-accent">' + escHtml(code) + '</span>';
|
||||||
|
titleInner += breakevenBadgeHtml(row);
|
||||||
|
titleInner += ' ' + directionBadgeHtml(row);
|
||||||
} else {
|
} else {
|
||||||
titleInner += ' ' + directionBadgeHtml(row);
|
titleInner += ' ' + directionBadgeHtml(row);
|
||||||
|
titleInner += breakevenBadgeHtml(row);
|
||||||
}
|
}
|
||||||
return titleInner;
|
return titleInner;
|
||||||
}
|
}
|
||||||
@@ -270,13 +278,16 @@
|
|||||||
titleInner += ' <span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span>';
|
titleInner += ' <span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span>';
|
||||||
}
|
}
|
||||||
titleInner += mainBadge;
|
titleInner += mainBadge;
|
||||||
titleInner += breakevenBadgeHtml(row);
|
|
||||||
if (code && String(name).toLowerCase() !== String(code).toLowerCase()) {
|
if (code && String(name).toLowerCase() !== String(code).toLowerCase()) {
|
||||||
titleInner += ' <span class="text-accent">' + escHtml(code) + '</span>';
|
titleInner += ' <span class="text-accent">' + escHtml(code) + '</span>';
|
||||||
|
titleInner += breakevenBadgeHtml(row);
|
||||||
} else if (!name && code) {
|
} else if (!name && code) {
|
||||||
titleInner = (exchange
|
titleInner = (exchange
|
||||||
? '<span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span> '
|
? '<span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span> '
|
||||||
: '') + '<span class="text-accent">' + escHtml(code) + '</span>';
|
: '') + '<span class="text-accent">' + escHtml(code) + '</span>';
|
||||||
|
titleInner += breakevenBadgeHtml(row);
|
||||||
|
} else {
|
||||||
|
titleInner += breakevenBadgeHtml(row);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
'<div class="dash-symbol-cell">' +
|
'<div class="dash-symbol-cell">' +
|
||||||
|
|||||||
+44
-19
@@ -322,12 +322,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = '<div class="empty-hint">暂无持仓。</div>';
|
list.innerHTML = '<div class="empty-hint">暂无持仓。</div>';
|
||||||
|
syncPositionListScroll(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!connected && ctpAutoConnectEnabled) {
|
if (!connected && ctpAutoConnectEnabled) {
|
||||||
tryAutoCtpReconnect();
|
tryAutoCtpReconnect();
|
||||||
}
|
}
|
||||||
list.innerHTML = rows.map(buildPosCard).join('');
|
list.innerHTML = rows.map(buildPosCard).join('');
|
||||||
|
syncPositionListScroll(rows.length);
|
||||||
bindPendingDismiss(list);
|
bindPendingDismiss(list);
|
||||||
bindCancelOpenButtons(list);
|
bindCancelOpenButtons(list);
|
||||||
bindSlTpButtons(list);
|
bindSlTpButtons(list);
|
||||||
@@ -1019,32 +1021,55 @@
|
|||||||
return '<span class="text-muted">未开启</span>';
|
return '<span class="text-muted">未开启</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ' <span class="badge profit pos-be-badge">已保本</span>';
|
||||||
|
}
|
||||||
|
if ((row.trailing_r_locked || 0) >= 1) {
|
||||||
|
return ' <span class="badge profit pos-be-badge">已保本</span>';
|
||||||
|
}
|
||||||
|
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 ' <span class="badge profit pos-be-badge">已保本</span>';
|
||||||
|
}
|
||||||
|
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 ' <span class="badge profit pos-be-badge">已保本</span>';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function posSymbolTitleHtml(row, extraBadges) {
|
function posSymbolTitleHtml(row, extraBadges) {
|
||||||
extraBadges = extraBadges || '';
|
extraBadges = extraBadges || '';
|
||||||
var name = row.symbol_name || row.symbol || '';
|
var name = row.symbol_name || row.symbol || '';
|
||||||
var code = row.symbol_code || '';
|
var code = row.symbol_code || '';
|
||||||
var mainBadge = row.symbol_is_main ? ' <span class="badge planned pos-main-badge">主力</span>' : '';
|
var mainBadge = row.symbol_is_main ? ' <span class="badge planned pos-main-badge">主力</span>' : '';
|
||||||
var beBadge = (function () {
|
var beBadge = breakevenBadgeHtml(row);
|
||||||
if (row.breakeven_locked) return ' <span class="badge profit dash-be-badge">已保本</span>';
|
var inner = name + mainBadge;
|
||||||
if ((row.trailing_r_locked || 0) >= 1) return ' <span class="badge profit dash-be-badge">已保本</span>';
|
|
||||||
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 ' <span class="badge profit dash-be-badge">已保本</span>';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}());
|
|
||||||
var inner = name + mainBadge + beBadge;
|
|
||||||
if (code && String(name).toLowerCase() !== String(code).toLowerCase()) {
|
if (code && String(name).toLowerCase() !== String(code).toLowerCase()) {
|
||||||
inner += ' <span class="text-accent">' + code + '</span>';
|
inner += ' <span class="text-accent">' + code + '</span>' + beBadge;
|
||||||
} else if (!name && code) {
|
} else if (!name && code) {
|
||||||
inner = '<span class="text-accent">' + code + '</span>';
|
inner = '<span class="text-accent">' + code + '</span>' + beBadge;
|
||||||
|
} else {
|
||||||
|
inner += beBadge;
|
||||||
}
|
}
|
||||||
if (marketNavEnabled && code) {
|
if (marketNavEnabled && code) {
|
||||||
var href = '/market?symbol=' + encodeURIComponent(code) + '&period=15m';
|
var href = '/market?symbol=' + encodeURIComponent(code) + '&period=15m';
|
||||||
|
|||||||
Reference in New Issue
Block a user