From 13514082d1d22df2c3bd8b17b54ee354b0432f23 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Wed, 08 Jul 2026 11:48:18 +0800
Subject: [PATCH] fix(gateApi): 统一返回成功状态的数据格式
---
src/main/resources/static/gate-config.html | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 263 insertions(+), 0 deletions(-)
diff --git a/src/main/resources/static/gate-config.html b/src/main/resources/static/gate-config.html
new file mode 100644
index 0000000..42c6f5d
--- /dev/null
+++ b/src/main/resources/static/gate-config.html
@@ -0,0 +1,263 @@
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Gate 策略控制面板</title>
+ <style>
+ :root {
+ --bg: #0f1117; --card: #1a1d27; --border: #2a2d3a;
+ --text: #e1e4e8; --dim: #8b949e; --accent: #58a6ff;
+ --danger: #f85149; --green: #3fb950; --warn: #d29922;
+ --input-bg: #161b22;
+ }
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ background: var(--bg); color: var(--text); min-height: 100vh; padding: 20px;
+ }
+ .container { max-width: 960px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }
+ .card {
+ background: var(--card); border: 1px solid var(--border);
+ border-radius: 8px; padding: 20px;
+ }
+ .card-header {
+ display: flex; justify-content: space-between; align-items: center;
+ margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid var(--border);
+ }
+ h2 { font-size: 18px; display: flex; align-items: center; gap: 10px; }
+ .status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
+ .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; }
+ .form-group { display: flex; flex-direction: column; gap: 3px; }
+ .form-group.full { grid-column: span 2; }
+ .form-group label { font-size: 11px; color: var(--dim); font-weight: 500; }
+ .form-group input, .form-group select {
+ background: var(--input-bg); border: 1px solid var(--border);
+ border-radius: 5px; color: var(--text); padding: 7px 10px;
+ font-size: 13px; outline: none;
+ }
+ .form-group input:focus, .form-group select:focus { border-color: var(--accent); }
+ .btn {
+ padding: 7px 16px; border-radius: 5px; border: none;
+ font-size: 12px; cursor: pointer; font-weight: 500;
+ }
+ .btn:hover { opacity: .85; }
+ .btn-primary { background: var(--accent); color: #fff; }
+ .btn-success { background: var(--green); color: #fff; }
+ .btn-danger { background: var(--danger); color: #fff; }
+ .btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); }
+ .btn-group { display: flex; gap: 6px; flex-wrap: wrap; }
+ .api-row { display: flex; gap: 10px; align-items: flex-end; }
+ .api-row .form-group { flex: 1; }
+
+ /* 日志窗口 */
+ .log-panel {
+ height: 280px; overflow-y: auto; background: var(--bg);
+ border: 1px solid var(--border); border-radius: 6px; padding: 10px;
+ font-family: "SF Mono", "Consolas", monospace; font-size: 12px;
+ line-height: 1.6; white-space: pre-wrap; word-break: break-all;
+ }
+ .log-panel .INFO { color: var(--text); }
+ .log-panel .WARN { color: var(--warn); }
+ .log-panel .ERROR { color: var(--danger); }
+ .log-panel .empty { color: var(--dim); text-align: center; padding-top: 60px; }
+
+ .toast {
+ position: fixed; top: 20px; right: 20px; padding: 10px 18px;
+ border-radius: 6px; font-size: 13px; z-index: 999;
+ animation: slideIn .3s ease;
+ }
+ .toast.success { background: #1b3d2b; border:1px solid var(--green); color:var(--green); }
+ .toast.error { background: #3d1b1b; border:1px solid var(--danger); color:var(--danger); }
+ @keyframes slideIn { from{transform:translateX(100px);opacity:0} to{transform:translateX(0);opacity:1} }
+
+ .section-title { font-size: 13px; font-weight: 600; margin-bottom: 8px; color: var(--dim); }
+ .hidden { display: none !important; }
+ </style>
+</head>
+<body>
+<div class="container">
+ <!-- Step 1: apiKey 输入 -->
+ <div class="card" id="step1">
+ <div class="card-header">
+ <h2>🔑 输入 Gate API Key</h2>
+ </div>
+ <div class="api-row">
+ <div class="form-group">
+ <label>API Key</label>
+ <input id="inputApiKey" type="password" placeholder="粘贴你的 Gate API Key">
+ </div>
+ <button class="btn btn-primary" onclick="loadByApiKey()" style="margin-bottom:1px">加载配置</button>
+ </div>
+ </div>
+
+ <!-- Step 2: 配置面板(初始隐藏) -->
+ <div class="card hidden" id="step2">
+ <div class="card-header">
+ <h2>
+ <span class="status-dot" id="statusDot" style="background:var(--dim)"></span>
+ Gate 策略配置
+ <span style="font-size:11px;color:var(--dim);font-weight:400" id="currentApiKey"></span>
+ </h2>
+ <div class="btn-group">
+ <button class="btn btn-outline" onclick="refreshStatus()">刷新状态</button>
+ <button class="btn btn-success" onclick="saveConfig()">保存配置</button>
+ <button class="btn btn-primary" onclick="startStrategy()">启动策略</button>
+ <button class="btn btn-danger" onclick="stopStrategy()">停止策略</button>
+ </div>
+ </div>
+ <form id="configForm" class="form-grid">
+ <div class="form-group"><label>网格间距 (gridRate)</label><input id="gridRate" placeholder="0.005"></div>
+ <div class="form-group"><label>预期收益 USDT</label><input id="expectedProfit" placeholder="0.15"></div>
+ <div class="form-group"><label>最大亏损 USDT</label><input id="maxLoss" placeholder="1.5"></div>
+ <div class="form-group"><label>基底开仓张数</label><input id="baseQuantity" placeholder="2"></div>
+ <div class="form-group"><label>每次下单张数</label><input id="quantity" placeholder="2"></div>
+ <div class="form-group"><label>最大持仓张数</label><input id="maxPositionSize" placeholder="4"></div>
+ <div class="form-group"><label>止损阶梯次数</label><input id="stopLossCount" placeholder="0"></div>
+ </form>
+ </div>
+
+ <!-- 日志面板 -->
+ <div class="card">
+ <div class="card-header">
+ <h2>📋 实时日志</h2>
+ <button class="btn btn-outline btn-sm" onclick="clearLogs()">清屏</button>
+ </div>
+ <div class="log-panel" id="logPanel">
+ <div class="empty">等待日志...</div>
+ </div>
+ </div>
+</div>
+<div id="toast"></div>
+
+<script>
+const API = '/api/gate';
+let currentApiKey = '';
+let logLines = [];
+const logPanel = document.getElementById('logPanel');
+
+function byId(id) { return document.getElementById(id); }
+
+function toast(msg, type) {
+ const t = document.getElementById('toast');
+ t.innerHTML = msg; t.className = 'toast ' + type;
+ setTimeout(() => t.innerHTML = '', 3000);
+}
+
+function maskKey(k) { return k && k.length > 8 ? k.slice(0, 4) + '****' + k.slice(-4) : '***'; }
+
+// ========== 日志 ==========
+function appendLog(line) {
+ logLines.push(line);
+ if (logLines.length > 500) logLines.shift();
+ if (logPanel.querySelector('.empty')) logPanel.innerHTML = '';
+ const div = document.createElement('div');
+ const cls = line.includes('WARN') ? 'WARN' : line.includes('ERROR') ? 'ERROR' : 'INFO';
+ div.className = cls;
+ div.textContent = line;
+ logPanel.appendChild(div);
+ logPanel.scrollTop = logPanel.scrollHeight;
+}
+function clearLogs() {
+ logLines = [];
+ logPanel.innerHTML = '<div class="empty">已清屏</div>';
+}
+
+// SSE 连接(浏览器自动重连,无需手动 reconnect)
+(function initSSE() {
+ const es = new EventSource(API + '/logs');
+ es.addEventListener('log', e => appendLog(e.data));
+ es.onerror = () => { appendLog('[SYSTEM] SSE 连接断开,浏览器将自动重连...'); };
+})();
+
+// ========== API ==========
+async function fetchApi(url, opts) {
+ const res = await fetch(url, opts);
+ const data = await res.json();
+ if (data.code !== 0 && data.code !== 200) throw new Error(data.msg || '请求失败');
+ return data.data !== undefined ? data.data : data.msg;
+}
+
+// Step 1: 输入 apiKey 加载配置
+async function loadByApiKey() {
+ const key = document.getElementById('inputApiKey').value.trim();
+ if (!key) return toast('请输入 API Key', 'error');
+ try {
+ const d = await fetchApi(API + '/config/load?apiKey=' + encodeURIComponent(key), { method: 'POST' });
+ fillForm(d);
+ toast('配置已加载', 'success');
+ } catch(e) {
+ // 文件不存在 → 用默认值填充,用户可编辑后保存
+ fillForm({ gridRate:0.005, expectedProfit:0.15, maxLoss:1.5,
+ baseQuantity:'2', quantity:'2', maxPositionSize:4, stopLossCount:0 });
+ toast('未找到配置,已加载默认值,编辑后请保存', 'success');
+ }
+ currentApiKey = key;
+ document.getElementById('currentApiKey').textContent = 'apiKey: ' + maskKey(key);
+ document.getElementById('step2').classList.remove('hidden');
+ refreshStatus();
+}
+
+function fillForm(d) {
+ byId('gridRate').value = d.gridRate || '';
+ byId('expectedProfit').value = d.expectedProfit || '';
+ byId('maxLoss').value = d.maxLoss || '';
+ byId('baseQuantity').value = d.baseQuantity || '';
+ byId('quantity').value = d.quantity || '';
+ byId('maxPositionSize').value = d.maxPositionSize ?? '';
+ byId('stopLossCount').value = d.stopLossCount ?? '';
+}
+
+function collectForm() {
+ return {
+ apiKey: currentApiKey,
+ gridRate: parseFloat(byId('gridRate').value) || 0,
+ expectedProfit: parseFloat(byId('expectedProfit').value) || 0,
+ maxLoss: parseFloat(byId('maxLoss').value) || 0,
+ baseQuantity: byId('baseQuantity').value,
+ quantity: byId('quantity').value,
+ maxPositionSize: parseInt(byId('maxPositionSize').value) || 0,
+ stopLossCount: parseInt(byId('stopLossCount').value) || 0
+ };
+}
+
+async function saveConfig() {
+ if (!currentApiKey) return toast('请先输入 API Key', 'error');
+ try {
+ const body = collectForm();
+ const msg = await fetchApi(API + '/config', {
+ method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body)
+ });
+ toast(msg || '保存成功', 'success');
+ } catch(e) { toast(e.message, 'error'); }
+}
+
+async function startStrategy() {
+ if (!currentApiKey) return toast('请先输入 API Key', 'error');
+ if (!confirm('确定要启动策略吗?')) return;
+ try {
+ const msg = await fetchApi(API + '/restart?apiKey=' + encodeURIComponent(currentApiKey), { method: 'POST' });
+ toast(msg, 'success');
+ refreshStatus();
+ } catch(e) { toast(e.message, 'error'); }
+}
+
+async function stopStrategy() {
+ if (!confirm('确定要停止策略吗?')) return;
+ try {
+ const msg = await fetchApi(API + '/stop', { method: 'POST' });
+ toast(msg, 'success');
+ refreshStatus();
+ } catch(e) { toast(e.message, 'error'); }
+}
+
+async function refreshStatus() {
+ try {
+ const s = await fetchApi(API + '/status');
+ const dot = document.getElementById('statusDot');
+ dot.style.background = s === 'ACTIVE' ? 'var(--green)' : 'var(--danger)';
+ } catch(e) {}
+}
+</script>
+</body>
+</html>
--
Gitblit v1.9.1