Add trailing BE to SL/TP dialog and speed up position refresh.
Use modal for monitor upsert with trailing checkbox, refresh CTP tick every second, and push full snapshot after orders. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -116,6 +116,13 @@
|
||||
.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)}
|
||||
.pos-order-btn:disabled:not(.pos-order-done){cursor:wait}
|
||||
|
||||
.sl-tp-modal{max-width:420px;width:100%}
|
||||
.sl-tp-modal-fields{display:flex;flex-direction:column;gap:.75rem;margin-bottom:1rem}
|
||||
.sl-tp-modal-fields .trade-field{margin:0}
|
||||
.sl-tp-modal-trailing{margin-top:.15rem}
|
||||
.sl-tp-modal-actions{display:flex;gap:.5rem;justify-content:flex-end}
|
||||
.sl-tp-modal-actions .btn-secondary,.sl-tp-modal-actions .btn-primary{width:auto;min-width:5rem;padding:.45rem 1rem;font-size:.85rem}
|
||||
|
||||
@media (min-width:768px) and (max-width:1100px){
|
||||
.trade-split .card{min-height:420px}
|
||||
.trade-form-line.line-3{grid-template-columns:1fr 1fr}
|
||||
|
||||
+93
-14
@@ -1061,16 +1061,18 @@
|
||||
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) ?
|
||||
var slTpBtn = (!row.stop_loss && !row.take_profit && !row.trailing_be && row.can_close) ?
|
||||
'<button type="button" class="pos-dismiss-btn pos-sl-btn" data-sl-tp="' +
|
||||
encodeURIComponent(JSON.stringify({
|
||||
symbol_code: row.symbol_code, direction: row.direction,
|
||||
lots: row.lots, entry_price: row.entry_price, monitor_id: row.monitor_id || null
|
||||
lots: row.lots, entry_price: row.entry_price, monitor_id: row.monitor_id || null,
|
||||
trailing_be: !!row.trailing_be
|
||||
})) + '">设置止盈止损</button>' : '';
|
||||
var editPayload = encodeURIComponent(JSON.stringify({
|
||||
symbol_code: row.symbol_code, direction: row.direction,
|
||||
lots: row.lots, entry_price: row.entry_price, monitor_id: row.monitor_id || null,
|
||||
stop_loss: row.stop_loss, take_profit: row.take_profit
|
||||
stop_loss: row.stop_loss, take_profit: row.take_profit,
|
||||
trailing_be: !!row.trailing_be
|
||||
}));
|
||||
var entrustBtn = row.can_close ?
|
||||
'<button type="button" class="pos-order-btn pos-entrust-btn" data-edit-sl-tp="' + editPayload + '">委托</button>' : '';
|
||||
@@ -1172,16 +1174,66 @@
|
||||
});
|
||||
}
|
||||
|
||||
function promptStopTakeProfit(payload, btn, btnLabel) {
|
||||
btnLabel = btnLabel || '设置止盈止损';
|
||||
var slDefault = payload.stop_loss != null && payload.stop_loss !== '' ? String(payload.stop_loss) : '';
|
||||
var tpDefault = payload.take_profit != null && payload.take_profit !== '' ? String(payload.take_profit) : '';
|
||||
var slRaw = prompt('止损价(可留空)', slDefault);
|
||||
if (slRaw === null) return;
|
||||
var tpRaw = prompt('止盈价(可留空)', tpDefault);
|
||||
if (tpRaw === null) return;
|
||||
var sl = slRaw.trim() ? parseFloat(slRaw) : null;
|
||||
var tp = tpRaw.trim() ? parseFloat(tpRaw) : null;
|
||||
var slTpModalState = { payload: null, btn: null, btnLabel: '设置止盈止损' };
|
||||
|
||||
function syncSlTpModalTrailingUi() {
|
||||
var trailingEl = document.getElementById('sl-tp-modal-trailing');
|
||||
var tpWrap = document.getElementById('sl-tp-modal-tp-wrap');
|
||||
var hint = document.getElementById('sl-tp-modal-trailing-hint');
|
||||
var on = !!(trailingEl && trailingEl.checked);
|
||||
if (tpWrap) tpWrap.hidden = on;
|
||||
if (hint) hint.hidden = !on;
|
||||
if (on) {
|
||||
var tpInput = document.getElementById('sl-tp-modal-tp');
|
||||
if (tpInput) tpInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function closeSlTpModal() {
|
||||
var mask = document.getElementById('sl-tp-modal');
|
||||
if (mask) mask.classList.remove('show');
|
||||
slTpModalState.payload = null;
|
||||
slTpModalState.btn = null;
|
||||
}
|
||||
|
||||
function openSlTpModal(payload, btn, btnLabel) {
|
||||
var mask = document.getElementById('sl-tp-modal');
|
||||
var title = document.getElementById('sl-tp-modal-title');
|
||||
var slInput = document.getElementById('sl-tp-modal-sl');
|
||||
var tpInput = document.getElementById('sl-tp-modal-tp');
|
||||
var trailingEl = document.getElementById('sl-tp-modal-trailing');
|
||||
if (!mask || !slInput) return;
|
||||
slTpModalState.payload = payload;
|
||||
slTpModalState.btn = btn || null;
|
||||
slTpModalState.btnLabel = btnLabel || '设置止盈止损';
|
||||
if (title) title.textContent = slTpModalState.btnLabel;
|
||||
slInput.value = payload.stop_loss != null && payload.stop_loss !== '' ? String(payload.stop_loss) : '';
|
||||
if (tpInput) {
|
||||
tpInput.value = payload.take_profit != null && payload.take_profit !== '' ? String(payload.take_profit) : '';
|
||||
}
|
||||
if (trailingEl) trailingEl.checked = !!payload.trailing_be;
|
||||
syncSlTpModalTrailingUi();
|
||||
mask.classList.add('show');
|
||||
slInput.focus();
|
||||
}
|
||||
|
||||
function saveSlTpModal() {
|
||||
var payload = slTpModalState.payload;
|
||||
if (!payload) return;
|
||||
var btn = slTpModalState.btn;
|
||||
var btnLabel = slTpModalState.btnLabel;
|
||||
var slInput = document.getElementById('sl-tp-modal-sl');
|
||||
var tpInput = document.getElementById('sl-tp-modal-tp');
|
||||
var trailingEl = document.getElementById('sl-tp-modal-trailing');
|
||||
var trailingOn = !!(trailingEl && trailingEl.checked);
|
||||
var slRaw = slInput && slInput.value ? slInput.value.trim() : '';
|
||||
var tpRaw = trailingOn ? '' : (tpInput && tpInput.value ? tpInput.value.trim() : '');
|
||||
var sl = slRaw ? parseFloat(slRaw) : null;
|
||||
var tp = tpRaw ? parseFloat(tpRaw) : null;
|
||||
if (trailingOn && (sl == null || isNaN(sl))) {
|
||||
alert('移动保本须填写止损价');
|
||||
return;
|
||||
}
|
||||
if (sl == null && tp == null) {
|
||||
alert('请至少填写止损或止盈');
|
||||
return;
|
||||
@@ -1190,6 +1242,8 @@
|
||||
btn.disabled = true;
|
||||
btn.textContent = '保存中…';
|
||||
}
|
||||
var saveBtn = document.getElementById('sl-tp-modal-save');
|
||||
if (saveBtn) saveBtn.disabled = true;
|
||||
fetch('/api/trading/monitor/upsert', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -1201,7 +1255,8 @@
|
||||
entry_price: payload.entry_price,
|
||||
monitor_id: payload.monitor_id || null,
|
||||
stop_loss: sl,
|
||||
take_profit: tp
|
||||
take_profit: tp,
|
||||
trailing_be: trailingOn
|
||||
})
|
||||
})
|
||||
.then(function (r) {
|
||||
@@ -1214,6 +1269,7 @@
|
||||
})
|
||||
.then(function (d) {
|
||||
if (!d.ok) throw new Error(d.error || '保存失败');
|
||||
closeSlTpModal();
|
||||
pollPositions();
|
||||
})
|
||||
.catch(function (e) {
|
||||
@@ -1224,9 +1280,31 @@
|
||||
btn.disabled = false;
|
||||
btn.textContent = btnLabel;
|
||||
}
|
||||
})
|
||||
.finally(function () {
|
||||
if (saveBtn) saveBtn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function bindSlTpModal() {
|
||||
var mask = document.getElementById('sl-tp-modal');
|
||||
var trailingEl = document.getElementById('sl-tp-modal-trailing');
|
||||
var cancelBtn = document.getElementById('sl-tp-modal-cancel');
|
||||
var saveBtn = document.getElementById('sl-tp-modal-save');
|
||||
if (trailingEl) trailingEl.addEventListener('change', syncSlTpModalTrailingUi);
|
||||
if (cancelBtn) cancelBtn.addEventListener('click', closeSlTpModal);
|
||||
if (saveBtn) saveBtn.addEventListener('click', saveSlTpModal);
|
||||
if (mask) {
|
||||
mask.addEventListener('click', function (e) {
|
||||
if (e.target === mask) closeSlTpModal();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function promptStopTakeProfit(payload, btn, btnLabel) {
|
||||
openSlTpModal(payload, btn, btnLabel || '设置止盈止损');
|
||||
}
|
||||
|
||||
function bindSlTpButtons(root) {
|
||||
if (!root) return;
|
||||
root.querySelectorAll('[data-sl-tp]').forEach(function (btn) {
|
||||
@@ -1754,6 +1832,7 @@
|
||||
}
|
||||
pollPositions();
|
||||
connectPositionStream();
|
||||
bindSlTpModal();
|
||||
initCtpOnLoad();
|
||||
connectRecommendStream();
|
||||
initRecommendSortControls();
|
||||
|
||||
Reference in New Issue
Block a user