| | |
| | | .btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); } |
| | | .btn:disabled { opacity: .4; cursor: not-allowed; } |
| | | |
| | | /* ===== 手动控制面板 ===== */ |
| | | .manual-actions { display: flex; flex-wrap: wrap; gap: 8px; } |
| | | .manual-actions .btn { padding: 7px 14px; } |
| | | |
| | | /* ===== 内容区(Tabs) ===== */ |
| | | .content { flex: 1; overflow-y: auto; padding: 16px 24px; } |
| | | .tabs { |
| | |
| | | <h2 style="color:var(--dim)">← 选择一个实例</h2> |
| | | </div> |
| | | <div class="btn-group"> |
| | | <button class="btn btn-success" id="btnStart" disabled onclick="showConfirm('start')">▶ 启动</button> |
| | | <button class="btn btn-danger" id="btnStop" disabled onclick="showConfirm('stop')">⏹ 停止</button> |
| | | <button class="btn btn-success" id="btnStart" disabled onclick="showConfirm('start','启动')">▶ 启动</button> |
| | | <button class="btn btn-danger" id="btnStop" disabled onclick="showConfirm('stop','停止')">⏹ 停止</button> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | <!-- 概览 Tab --> |
| | | <div class="tab-content active" data-tab="overview"> |
| | | <div class="stats-grid" id="statsGrid"></div> |
| | | <div class="chart-container"> |
| | | <h3>⚙️ 手动控制 <span style="font-size:11px;color:var(--dim);font-weight:400">(实时下发 MQ 指令,策略运行中可用)</span></h3> |
| | | <div class="manual-actions"> |
| | | <button class="btn btn-outline" id="btnCancelLongTpSl" disabled onclick="showConfirm('cancel-long-tp-sl','取消多仓止盈止损')">取消多仓止盈止损</button> |
| | | <button class="btn btn-outline" id="btnCancelShortTpSl" disabled onclick="showConfirm('cancel-short-tp-sl','取消空仓止盈止损')">取消空仓止盈止损</button> |
| | | <button class="btn btn-outline" id="btnRebuildLongTpSl" disabled onclick="showConfirm('rebuild-long-tp-sl','生成多仓止盈止损')">生成多仓止盈止损</button> |
| | | <button class="btn btn-outline" id="btnRebuildShortTpSl" disabled onclick="showConfirm('rebuild-short-tp-sl','生成空仓止盈止损')">生成空仓止盈止损</button> |
| | | <button class="btn btn-danger" id="btnCloseLong" disabled onclick="showConfirm('close-long','多单平仓')">多单平仓</button> |
| | | <button class="btn btn-danger" id="btnCloseShort" disabled onclick="showConfirm('close-short','空单平仓')">空单平仓</button> |
| | | </div> |
| | | </div> |
| | | <div class="chart-container"> |
| | | <h3>📈 盈亏趋势(最近 PNL_SNAPSHOT 事件)</h3> |
| | | <div class="pnl-summary" id="pnlSummary"></div> |
| | |
| | | function updateActionButtons(inst) { |
| | | const startBtn = $('btnStart'); |
| | | const stopBtn = $('btnStop'); |
| | | // 手动控制按钮(止盈止损/平仓):需策略运行中才有持仓/挂单可操作 |
| | | const manualIds = ['btnCancelLongTpSl', 'btnCancelShortTpSl', 'btnRebuildLongTpSl', |
| | | 'btnRebuildShortTpSl', 'btnCloseLong', 'btnCloseShort']; |
| | | const setManual = (disabled) => { |
| | | manualIds.forEach(id => { const b = $(id); if (b) b.disabled = disabled; }); |
| | | }; |
| | | if (!inst) { |
| | | startBtn.disabled = true; |
| | | stopBtn.disabled = true; |
| | | setManual(true); |
| | | return; |
| | | } |
| | | const running = inst.state === 'ACTIVE' || inst.state === 'OPENING' || inst.state === 'WAITING_KLINE'; |
| | | startBtn.disabled = running; // 运行中不可重复启动 |
| | | stopBtn.disabled = !running; // 未运行不可停止 |
| | | setManual(!running); // 仅运行中可下发手动指令 |
| | | } |
| | | |
| | | function showConfirm(action) { |
| | | function showConfirm(action, label) { |
| | | if (!selectedMd5) return; |
| | | pendingAction = action; |
| | | const verb = action === 'start' ? '启动' : '停止'; |
| | | pendingAction = { path: action, label: label || action }; |
| | | const verb = pendingAction.label; |
| | | const isDanger = action === 'stop' || action === 'close-long' || action === 'close-short'; |
| | | $('modalTitle').textContent = `确认${verb}`; |
| | | $('modalMsg').textContent = `确定要${verb}实例 ${selectedMd5.substring(0, 12)}... 的策略吗?`; |
| | | $('modalMsg').textContent = `确定要对实例 ${selectedMd5.substring(0, 12)}... 执行「${verb}」吗?`; |
| | | $('modalConfirmBtn').textContent = verb; |
| | | $('modalConfirmBtn').className = 'btn ' + (action === 'start' ? 'btn-success' : 'btn-danger'); |
| | | $('modalConfirmBtn').className = 'btn ' + (isDanger ? 'btn-danger' : 'btn-success'); |
| | | $('modalOverlay').classList.add('show'); |
| | | } |
| | | |
| | |
| | | closeModal(); |
| | | |
| | | try { |
| | | const msg = await api(API + '/' + action + '?apiKeyMd5=' + encodeURIComponent(selectedMd5), { method: 'POST' }); |
| | | const msg = await api(API + '/' + action.path + '?apiKeyMd5=' + encodeURIComponent(selectedMd5), { method: 'POST' }); |
| | | toast(msg || '指令已发送', 'success'); |
| | | // 延迟刷新等待 ACK |
| | | setTimeout(refreshInstanceList, 2000); |