ui: 顶栏透明、设置两列、下单与持仓监控优化
导航栏与页面背景一致;系统设置双列布局;下单三行表单与开仓状态反馈;持仓卡片增加平仓与止盈止损挂单展示。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-2
@@ -56,8 +56,8 @@
|
||||
|
||||
.site-header{
|
||||
border-bottom:1px solid var(--border-header);
|
||||
background:var(--header-bg);
|
||||
backdrop-filter:blur(12px);
|
||||
background:transparent;
|
||||
backdrop-filter:none;
|
||||
}
|
||||
.site-header::after{
|
||||
content:"";display:block;height:1px;margin-top:-1px;
|
||||
|
||||
+19
-7
@@ -10,26 +10,38 @@
|
||||
.trade-order-status{display:grid;gap:.55rem;margin:.5rem 0 .75rem;padding:.65rem .85rem;background:var(--card-inner);border:1px solid var(--card-border);border-radius:8px;font-size:.82rem}
|
||||
.trade-order-status-compact{margin-top:0}
|
||||
.trade-order-status .status-row{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .65rem}
|
||||
.trade-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:.75rem .65rem;margin-bottom:.85rem}
|
||||
.trade-form-grid .span-2{grid-column:span 2}
|
||||
.trade-form-rows{display:flex;flex-direction:column;gap:.75rem;margin-bottom:.85rem}
|
||||
.trade-form-line{display:grid;gap:.65rem;align-items:end}
|
||||
.trade-form-line.line-3{grid-template-columns:1.4fr 0.8fr 0.8fr}
|
||||
.trade-field label{display:block;font-size:.72rem;margin-bottom:.28rem;color:var(--text-label)}
|
||||
.trade-field select,.trade-field input{width:100%;box-sizing:border-box}
|
||||
.trade-field .lots-auto{color:var(--accent);font-weight:600;background:var(--card-inner);cursor:default}
|
||||
.price-type-tabs{display:flex;gap:.35rem;margin-bottom:.35rem}
|
||||
.price-tab{border:1px solid var(--card-border);background:var(--card-inner);color:var(--text-muted);padding:.28rem .7rem;border-radius:6px;font-size:.75rem;cursor:pointer;flex:1;text-align:center}
|
||||
.price-tab{border:1px solid var(--card-border);background:var(--card-inner);color:var(--text-muted);padding:.28rem .7rem;border-radius:6px;font-size:.75rem;cursor:pointer;flex:1;text-align:center;width:auto}
|
||||
.price-tab.active{border-color:var(--accent);color:var(--accent);font-weight:600;background:rgba(56,189,248,.08)}
|
||||
.market-hint{font-size:.7rem;margin-top:.25rem}
|
||||
.trade-action-row{display:grid;grid-template-columns:1fr 1fr;gap:.65rem;margin:.85rem 0 .55rem}
|
||||
.trade-action-row .btn-open,.trade-action-row .btn-secondary{padding:.6rem .75rem;font-size:.88rem;width:100%}
|
||||
.trade-action-row{display:flex;flex-direction:column;gap:.45rem;margin:.85rem 0 .55rem}
|
||||
.trade-action-row .btn-open{padding:.65rem .75rem;font-size:.9rem;width:100%}
|
||||
.trade-action-row .btn-open:disabled{opacity:.65;cursor:wait}
|
||||
.trade-order-msg{font-size:.82rem;text-align:center;margin:0;padding:.35rem}
|
||||
.trade-order-msg.ok{color:var(--profit)}
|
||||
.trade-order-msg.err{color:var(--loss)}
|
||||
.trade-footer{background:var(--card-inner);border-radius:8px;padding:.65rem .85rem;font-size:.78rem;line-height:1.5;border:1px solid var(--card-border);margin-top:.5rem}
|
||||
.trade-footer strong{color:var(--accent)}
|
||||
.rec-blocked td{opacity:.55}
|
||||
.rec-ok td:first-child{font-weight:600}
|
||||
#positions .card-body{max-height:460px;overflow-y:auto}
|
||||
.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)}
|
||||
.pos-pending-item.sl{border-left:3px solid var(--loss)}
|
||||
.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}
|
||||
.pos-close-btn:disabled{opacity:.55;cursor:wait}
|
||||
|
||||
@media (max-width:900px){
|
||||
.trade-row-split{grid-template-columns:1fr}
|
||||
#positions .card-body{max-height:360px}
|
||||
.trade-form-grid{grid-template-columns:1fr}
|
||||
.trade-form-grid .span-2{grid-column:span 1}
|
||||
.trade-form-line.line-3{grid-template-columns:1fr}
|
||||
}
|
||||
|
||||
+92
-19
@@ -177,28 +177,46 @@
|
||||
});
|
||||
}
|
||||
|
||||
function showOrderMsg(text, ok) {
|
||||
var el = document.getElementById('order-msg');
|
||||
if (!el) return;
|
||||
if (!text) {
|
||||
el.hidden = true;
|
||||
el.textContent = '';
|
||||
el.className = 'trade-order-msg';
|
||||
return;
|
||||
}
|
||||
el.hidden = false;
|
||||
el.textContent = text;
|
||||
el.className = 'trade-order-msg ' + (ok ? 'ok' : 'err');
|
||||
}
|
||||
|
||||
function postOrder(offset) {
|
||||
var sym = selectedSymbol();
|
||||
if (!sym) { alert('请选择品种'); return; }
|
||||
if (!sym) { showOrderMsg('请选择品种', false); return; }
|
||||
var direction = dirSelect ? dirSelect.value : 'long';
|
||||
var price = entryPrice();
|
||||
if (!price || price <= 0) {
|
||||
alert('无法获取有效价格,请先填写或刷新行情');
|
||||
showOrderMsg('无法获取有效价格,请先填写或刷新行情', false);
|
||||
return;
|
||||
}
|
||||
var lots = effectiveLots();
|
||||
if (offset === 'open') {
|
||||
if (isRiskMode() && lots <= 0) {
|
||||
alert('请填写止损,系统将自动计算手数');
|
||||
showOrderMsg('请填写止损,系统将自动计算手数', false);
|
||||
return;
|
||||
}
|
||||
if (!isRiskMode() && lots <= 0) {
|
||||
alert('请填写手数');
|
||||
showOrderMsg('请填写手数', false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
lots = parseInt(lotsInput && lotsInput.value, 10) || 1;
|
||||
}
|
||||
var btnOpen = document.getElementById('btn-open');
|
||||
if (btnOpen) {
|
||||
btnOpen.disabled = true;
|
||||
btnOpen.textContent = '开仓中…';
|
||||
}
|
||||
showOrderMsg('开仓中…', true);
|
||||
var body = {
|
||||
symbol: sym,
|
||||
offset: offset,
|
||||
@@ -214,22 +232,48 @@
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
}).then(function (r) { return r.json(); }).then(function (data) {
|
||||
if (!data.ok) { alert(data.error || '下单失败'); return; }
|
||||
alert((offset === 'open' ? '开仓' : '平仓') + '已提交 ' + (data.lots || lots) + ' 手');
|
||||
if (!data.ok) {
|
||||
showOrderMsg(data.error || '下单失败', false);
|
||||
return;
|
||||
}
|
||||
showOrderMsg('开仓成功 · ' + (data.lots || lots) + ' 手', true);
|
||||
pollPositions();
|
||||
refreshQuote();
|
||||
setTimeout(function () { showOrderMsg(''); }, 4000);
|
||||
}).catch(function () {
|
||||
showOrderMsg('网络错误,请重试', false);
|
||||
}).finally(function () {
|
||||
if (btnOpen) {
|
||||
btnOpen.disabled = false;
|
||||
btnOpen.textContent = '开仓';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildPendingHtml(items) {
|
||||
if (!items || !items.length) return '';
|
||||
var rows = items.map(function (p) {
|
||||
var cls = p.order_kind === 'stop_loss' ? 'sl' : (p.order_kind === 'take_profit' ? 'tp' : 'ctp');
|
||||
return (
|
||||
'<div class="pos-pending-item ' + cls + '">' +
|
||||
'<span>' + (p.label || '挂单') + '</span>' +
|
||||
'<span><strong>' + fmtNum(p.price) + '</strong> · ' + (p.lots || 1) + ' 手</span>' +
|
||||
'</div>'
|
||||
);
|
||||
}).join('');
|
||||
return '<div class="pos-pending-orders"><div class="pending-title">止盈止损挂单</div>' + rows + '</div>';
|
||||
}
|
||||
|
||||
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 dirBadge = row.direction_label || (row.direction === 'long' ? '做多' : '做空');
|
||||
var closePayload = encodeURIComponent(JSON.stringify({
|
||||
source: row.source, symbol_code: row.symbol_code, direction: row.direction,
|
||||
lots: row.lots, mark_price: row.mark_price, monitor_id: row.monitor_id || null
|
||||
}));
|
||||
var closeBtn = row.can_close ?
|
||||
'<button type="button" class="btn-del pos-del" data-close=\'' + JSON.stringify({
|
||||
source: row.source, symbol_code: row.symbol_code, direction: row.direction,
|
||||
lots: row.lots, mark_price: row.mark_price, monitor_id: row.monitor_id || null
|
||||
}) + '\'>平仓</button>' : '';
|
||||
'<button type="button" class="pos-close-btn" data-close="' + closePayload + '">平仓</button>' : '';
|
||||
return (
|
||||
'<div class="pos-card">' +
|
||||
'<div class="pos-card-head"><div><div class="title">' + row.symbol + ' <span class="badge dir">' + dirBadge + '</span></div>' +
|
||||
@@ -240,21 +284,39 @@
|
||||
'<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 ' + pnlClass + '"><label>浮盈亏</label><div>' + pnlText + '</div></div>' +
|
||||
'</div><div class="pos-footer"><span>' + row.lots + ' 手</span></div></div>'
|
||||
'</div>' + buildPendingHtml(row.pending_orders) +
|
||||
'<div class="pos-footer"><span>' + row.lots + ' 手</span></div></div>'
|
||||
);
|
||||
}
|
||||
|
||||
function closePosition(payload) {
|
||||
function closePosition(payload, btn) {
|
||||
function doClose(price) {
|
||||
if (!price || price <= 0) { alert('无法获取现价'); return; }
|
||||
if (!confirm('确认平仓 ' + payload.lots + ' 手?')) return;
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = '平仓中…';
|
||||
}
|
||||
fetch('/api/trading/close', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(Object.assign({}, payload, { price: price }))
|
||||
}).then(function (r) { return r.json(); }).then(function (d) {
|
||||
if (!d.ok) { alert(d.error || '平仓失败'); return; }
|
||||
if (!d.ok) {
|
||||
alert(d.error || '平仓失败');
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '平仓';
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (btn) btn.textContent = '已平仓';
|
||||
pollPositions();
|
||||
}).catch(function () {
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '平仓';
|
||||
}
|
||||
});
|
||||
}
|
||||
if (payload.mark_price > 0) {
|
||||
@@ -285,13 +347,26 @@
|
||||
return;
|
||||
}
|
||||
if (!rows.length) {
|
||||
list.innerHTML = '<div class="empty-hint">柜台暂无持仓。</div>';
|
||||
var pendingOnly = data.pending_orders || [];
|
||||
if (pendingOnly.length) {
|
||||
list.innerHTML = '<div class="empty-hint" style="margin-bottom:.75rem">柜台暂无持仓</div>' +
|
||||
pendingOnly.map(function (p) {
|
||||
return (
|
||||
'<div class="pos-pending-item ' +
|
||||
(p.order_kind === 'stop_loss' ? 'sl' : (p.order_kind === 'take_profit' ? 'tp' : 'ctp')) +
|
||||
'"><span>' + (p.label || '挂单') + ' · ' + (p.symbol || p.symbol_code) + '</span>' +
|
||||
'<span><strong>' + fmtNum(p.price) + '</strong> · ' + (p.lots || 1) + ' 手</span></div>'
|
||||
);
|
||||
}).join('');
|
||||
} else {
|
||||
list.innerHTML = '<div class="empty-hint">柜台暂无持仓。</div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
list.innerHTML = rows.map(buildPosCard).join('');
|
||||
list.querySelectorAll('[data-close]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
closePosition(JSON.parse(btn.getAttribute('data-close')));
|
||||
closePosition(JSON.parse(decodeURIComponent(btn.getAttribute('data-close'))), btn);
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -357,9 +432,7 @@
|
||||
}
|
||||
|
||||
var btnOpen = document.getElementById('btn-open');
|
||||
var btnClose = document.getElementById('btn-close-pos');
|
||||
if (btnOpen) btnOpen.addEventListener('click', function () { postOrder('open'); });
|
||||
if (btnClose) btnClose.addEventListener('click', function () { postOrder('close'); });
|
||||
|
||||
var btnConnect = document.getElementById('btn-ctp-connect');
|
||||
if (btnConnect) {
|
||||
|
||||
Reference in New Issue
Block a user