From f690d3216d27d560c1d5d07a9a5cb76f5e31821d Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Wed, 08 Jul 2026 11:47:41 +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