fix: 持仓卡片布局优化、闭盘禁用平仓、固定金额step修复

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 15:41:35 +08:00
parent 9772f3d986
commit de74ffe5b9
4 changed files with 56 additions and 36 deletions
+2
View File
@@ -644,6 +644,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
"est_fee_open": fee_info["open_fee"],
"est_fee_close": fee_info["close_fee"],
"est_fee_close_type": fee_info["close_type"],
"fee_source": fee_info.get("fee_source") or "local",
"est_pnl_net": est_net,
"sl_order_active": order_st.get("sl_monitoring"),
"tp_order_active": order_st.get("tp_monitoring"),
@@ -654,6 +655,7 @@ def install_trading(app, *, login_required, require_nav, get_db, get_setting, se
"price_precision": tick.get("price_precision"),
"tick_size": tick.get("tick_size"),
"can_close": True,
"close_allowed": is_trading_session(),
"pending_orders": pending_for_row,
"trailing_be": bool(mon.get("trailing_be")) if mon else False,
"trailing_r_locked": int(mon.get("trailing_r_locked") or 0) if mon else 0,
+3 -1
View File
@@ -57,7 +57,9 @@
.pos-pending-item.tp{border-left:3px solid var(--profit)}
.pos-pending-item.ctp{border-left:3px solid var(--accent)}
.pos-close-btn{padding:.4rem .85rem;font-size:.78rem;border-radius:8px;border:1px solid var(--loss);background:var(--loss-bg);color:var(--loss);cursor:pointer;white-space:nowrap;width:auto;flex-shrink:0;min-height:36px}
.pos-close-btn:disabled{opacity:.55;cursor:wait}
.pos-close-btn:disabled,.pos-close-btn.is-session-off{opacity:.45;cursor:not-allowed;border-color:var(--text-muted);background:var(--card-inner);color:var(--text-muted)}
.pos-card-meta-line{font-size:.78rem;line-height:1.65;color:var(--text-muted);margin-bottom:.55rem}
.pos-card-meta-line strong{color:var(--text)}
.pos-card-actions{display:flex;gap:.35rem;flex-shrink:0;align-items:center}
.pos-order-btn{padding:.4rem .85rem;font-size:.78rem;border-radius:8px;border:1px solid var(--accent);background:rgba(56,189,248,.1);color:var(--accent);cursor:pointer;white-space:nowrap;width:auto;flex-shrink:0;min-height:36px}
.pos-order-btn:disabled,.pos-order-btn.pos-order-done{opacity:.55;cursor:default;border-color:var(--table-border);background:var(--card-inner);color:var(--text-muted)}
+50 -34
View File
@@ -623,12 +623,36 @@
});
}
function slTpStatusHtml(row) {
var parts = [];
if (row.sl_order_active || row.sl_monitoring) {
parts.push('<span class="text-profit">止损监控中</span>');
} else if (row.stop_loss != null) {
parts.push('<span class="text-muted">止损已设</span>');
}
if (row.tp_order_active || row.tp_monitoring) {
parts.push('<span class="text-profit">止盈监控中</span>');
} else if (row.take_profit != null) {
parts.push('<span class="text-muted">止盈已设</span>');
}
if (!parts.length) return '<span class="text-muted">未设置</span>';
return parts.join(' · ');
}
function trailingStatusHtml(row) {
if (row.trailing_be) {
return '<span class="text-accent">已开启' +
(row.trailing_r_locked ? '(锁' + row.trailing_r_locked + 'R' : '') + '</span>';
}
return '<span class="text-muted">未开启</span>';
}
function buildPosCard(row) {
var pnlClass = row.float_pnl > 0 ? 'pnl-pos' : (row.float_pnl < 0 ? 'pnl-neg' : '');
var pnlText = row.float_pnl != null ? ((row.float_pnl >= 0 ? '+' : '') + fmtNum(row.float_pnl) + ' 元') : '--';
var netClass = row.est_pnl_net > 0 ? 'pnl-pos' : (row.est_pnl_net < 0 ? 'pnl-neg' : '');
var dirBadge = row.direction_label || (row.direction === 'long' ? '做多' : '做空');
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
var closeAllowed = row.close_allowed !== false && isTradingSession;
var slTpBtn = (!row.stop_loss && !row.take_profit && row.can_close) ?
'<button type="button" class="pos-dismiss-btn pos-sl-btn" data-sl-tp="' +
encodeURIComponent(JSON.stringify({
@@ -651,51 +675,39 @@
lots: row.lots, mark_price: row.mark_price, monitor_id: row.monitor_id || null
}));
var closeBtn = row.can_close ?
'<button type="button" class="pos-close-btn" data-close="' + closePayload + '">平仓</button>' : '';
'<button type="button" class="pos-close-btn' + (closeAllowed ? '' : ' is-session-off') + '" ' +
(closeAllowed ? '' : 'disabled title="不在交易时间段"') +
' data-close="' + closePayload + '">平仓</button>' : '';
var actionBtns = (entrustBtn || orderBtn || closeBtn) ?
'<div class="pos-card-actions">' + entrustBtn + orderBtn + closeBtn + '</div>' : '';
var riskMeta = '';
if (row.rr_ratio != null) {
riskMeta += ' · 盈亏比 <strong>' + row.rr_ratio + ':1</strong>';
}
if (row.risk_amount != null) {
riskMeta += ' · 亏损额度 <strong>' + fmtNum(row.risk_amount) + ' 元</strong>';
}
var metaLine =
'来源 <strong>' + (row.source_label || 'CTP') + '</strong>' +
(row.rr_ratio != null ? ' · 盈亏比 <strong>' + row.rr_ratio + ':1</strong>' : '') +
' · 止损 <strong>' + (row.stop_loss != null ? fmtNum(row.stop_loss) : '--') + '</strong>' +
' · 止盈 <strong>' + (row.take_profit != null ? fmtNum(row.take_profit) : '--') + '</strong>' +
' · ' + slTpStatusHtml(row) +
' · 移动保本 ' + trailingStatusHtml(row) +
(slTpBtn ? ' · ' + slTpBtn : '') +
(row.sync_pending ? ' · <span class="text-muted">同步柜台中…</span>' : '');
var feeLabel = row.fee_source === 'ctp' ? '手续费(柜台)' : '手续费';
var marginLabel = row.margin_source === 'ctp' ? '占用保证金(柜台)' : '占用保证金';
return (
'<div class="pos-card">' +
'<div class="pos-card-head"><div><div class="title">' + row.symbol + ' <span class="badge dir">' + dirBadge + '</span></div>' +
'<div class="text-muted" style="font-size:.72rem">' + (row.symbol_code || '') + '</div></div>' +
actionBtns + '</div>' +
'<div class="pos-card-meta">来源 <strong>' + (row.source_label || 'CTP') + '</strong>' + riskMeta +
(row.sync_pending ? ' · <span class="text-muted">同步柜台中…</span>' : '') +
' · 浮盈' +
(slTpBtn ? ' · ' + slTpBtn : '') +
(row.sl_order_active ? ' · <span class="text-profit">止损监控中</span>' : '') +
(row.tp_order_active ? ' · <span class="text-profit">止盈监控中</span>' : '') + '</div>' +
'<div class="pos-card-meta pos-card-meta-line">' + metaLine + '</div>' +
'<div class="pos-metrics">' +
'<div class="cell"><label>移动保本</label><div>' +
(row.trailing_be ?
'<span class="text-accent">已开启' + (row.trailing_r_locked ? '(锁' + row.trailing_r_locked + 'R' : '') + '</span>' :
'<span class="text-muted">未开启</span>') +
'</div></div>' +
'<div class="cell"><label>持仓均价</label><div>' + fmtNum(row.entry_price) + '</div></div>' +
'<div class="cell"><label>手数 / 均价</label><div><strong>' + row.lots + ' 手</strong> @ ' + fmtNum(row.entry_price) + '</div></div>' +
'<div class="cell"><label>当前价格</label><div>' + (row.current_price != null ? fmtNum(row.current_price) : '--') + '</div></div>' +
'<div class="cell"><label>止损</label><div>' + (row.stop_loss != null ? fmtNum(row.stop_loss) : '--') + '</div></div>' +
'<div class="cell"><label>止盈</label><div>' + (row.take_profit != null ? fmtNum(row.take_profit) : '--') + '</div></div>' +
'<div class="cell"><label>' + (row.margin_source === 'ctp' ? '占用保证金(柜台)' : '占用保证金') + '</label><div>' + (row.margin != null ? fmtNum(row.margin) + ' 元' : '--') + '</div></div>' +
'<div class="cell"><label>' + marginLabel + '</label><div>' + (row.margin != null ? fmtNum(row.margin) + ' 元' : '--') + '</div></div>' +
'<div class="cell"><label>仓位占比</label><div>' + (row.position_pct != null ? fmtNum(row.position_pct) + '%' : '--') + '</div></div>' +
'<div class="cell ' + pnlClass + '"><label>浮盈亏</label><div>' + pnlText + '</div></div>' +
'<div class="cell"><label>预估手续费</label><div>' + (row.est_fee != null ? fmtNum(row.est_fee) + ' 元' : '--') + '</div></div>' +
'<div class="cell ' + netClass + '"><label>扣费后</label><div>' +
(row.est_pnl_net != null ? fmtNum(row.est_pnl_net) + ' 元' : '--') + '</div></div>' +
'<div class="cell"><label>' + feeLabel + '</label><div>' + (row.est_fee != null ? fmtNum(row.est_fee) + ' 元' : '--') + '</div></div>' +
'<div class="cell"><label>开仓</label><div>' + (openT || '--') + '</div></div>' +
'<div class="cell"><label>持仓</label><div>' + (row.holding_duration || '--') + '</div></div>' +
'</div>' + buildPendingHtml(row.pending_orders) +
'<div class="pos-footer">' +
'<span>' + row.lots + ' 手</span>' +
'<span>开仓 ' + (openT || '--') + '</span>' +
'<span>持仓 ' + (row.holding_duration || '--') + '</span>' +
'<span>保证金 ' + (row.margin != null ? fmtNum(row.margin) + ' 元' : '--') + '</span>' +
'<span>仓位 ' + (row.position_pct != null ? fmtNum(row.position_pct) + '%' : '--') + '</span>' +
'</div></div>'
'</div>'
);
}
@@ -811,6 +823,10 @@
}
function closePosition(payload, btn) {
if (!isTradingSession) {
alert('不在交易时间段,无法平仓');
return;
}
function doClose(price) {
if (!price || price <= 0) { alert('无法获取现价'); return; }
if (!confirm('确认平仓 ' + payload.lots + ' 手?')) return;
+1 -1
View File
@@ -65,7 +65,7 @@
</div>
<div class="field" id="field-fixed-amount" {% if position_sizing_mode not in ('amount', 'risk') %}hidden{% endif %}>
<label>固定金额(元)</label>
<input name="fixed_amount" type="number" step="100" min="1" value="{{ fixed_amount }}">
<input name="fixed_amount" type="number" step="1" min="1" value="{{ fixed_amount }}">
</div>
<div class="field">
<label>保证金占用上限(%</label>