|
@@ -27,6 +27,9 @@
|
|
|
border-radius: 0;
|
|
border-radius: 0;
|
|
|
box-shadow: none;
|
|
box-shadow: none;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ height: 100vh;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.header {
|
|
.header {
|
|
@@ -47,10 +50,75 @@
|
|
|
opacity: 0.9;
|
|
opacity: 0.9;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .main-content {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .sidebar {
|
|
|
|
|
+ width: 250px;
|
|
|
|
|
+ background: #f8f9fa;
|
|
|
|
|
+ border-right: 1px solid #e9ecef;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .sidebar-header {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ background: #e9ecef;
|
|
|
|
|
+ border-bottom: 1px solid #dee2e6;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .sidebar-header h3 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 1.1em;
|
|
|
|
|
+ color: #495057;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .symbol-list {
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ list-style: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .symbol-item {
|
|
|
|
|
+ padding: 12px 20px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ border-bottom: 1px solid #e9ecef;
|
|
|
|
|
+ transition: all 0.2s ease;
|
|
|
|
|
+ color: #495057;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .symbol-item:hover {
|
|
|
|
|
+ background: #e9ecef;
|
|
|
|
|
+ color: #2c3e50;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .symbol-item.active {
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .symbol-item.active:hover {
|
|
|
|
|
+ background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .content-area {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.controls {
|
|
.controls {
|
|
|
- padding: 30px;
|
|
|
|
|
|
|
+ padding: 20px 30px;
|
|
|
background: #f8f9fa;
|
|
background: #f8f9fa;
|
|
|
border-bottom: 1px solid #e9ecef;
|
|
border-bottom: 1px solid #e9ecef;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.control-group {
|
|
.control-group {
|
|
@@ -58,6 +126,7 @@
|
|
|
gap: 20px;
|
|
gap: 20px;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.control-item {
|
|
.control-item {
|
|
@@ -113,6 +182,8 @@
|
|
|
|
|
|
|
|
.content {
|
|
.content {
|
|
|
padding: 30px;
|
|
padding: 30px;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.stats-grid {
|
|
.stats-grid {
|
|
@@ -209,9 +280,21 @@
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
@media (max-width: 768px) {
|
|
|
|
|
+ .main-content {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .sidebar {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 200px;
|
|
|
|
|
+ border-right: none;
|
|
|
|
|
+ border-bottom: 1px solid #e9ecef;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.control-group {
|
|
.control-group {
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
align-items: stretch;
|
|
align-items: stretch;
|
|
|
|
|
+ justify-content: center;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.stats-grid {
|
|
.stats-grid {
|
|
@@ -231,49 +314,56 @@
|
|
|
<p>Lighter vs Binance 价格差异分析</p>
|
|
<p>Lighter vs Binance 价格差异分析</p>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="controls">
|
|
|
|
|
- <div class="control-group">
|
|
|
|
|
- <div class="control-item">
|
|
|
|
|
- <label for="symbolSelect">选择币对:</label>
|
|
|
|
|
- <select id="symbolSelect">
|
|
|
|
|
- <option value="">请选择币对...</option>
|
|
|
|
|
- </select>
|
|
|
|
|
|
|
+ <div class="main-content">
|
|
|
|
|
+ <div class="sidebar">
|
|
|
|
|
+ <div class="sidebar-header">
|
|
|
|
|
+ <h3>选择币对</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <ul id="symbolList" class="symbol-list">
|
|
|
|
|
+ <li class="symbol-item loading-item">正在加载币对...</li>
|
|
|
|
|
+ </ul>
|
|
|
</div>
|
|
</div>
|
|
|
- <button class="btn" onclick="loadData()">刷新数据</button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
|
|
|
- <div class="content">
|
|
|
|
|
- <div id="loading" class="loading">请选择币对以查看数据</div>
|
|
|
|
|
- <div id="error" class="error" style="display: none;"></div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 数据卡片已移除 -->
|
|
|
|
|
-
|
|
|
|
|
- <div id="chartsContainer" style="display: none;">
|
|
|
|
|
- <!-- 24小时缩略图 -->
|
|
|
|
|
- <div class="chart-container">
|
|
|
|
|
- <h3>24小时价格缩略图 (点击查看详情)</h3>
|
|
|
|
|
- <div class="chart-wrapper" style="height: 200px;">
|
|
|
|
|
- <canvas id="thumbnailChart"></canvas>
|
|
|
|
|
|
|
+ <div class="content-area">
|
|
|
|
|
+ <div class="controls">
|
|
|
|
|
+ <div class="control-group">
|
|
|
|
|
+ <button class="btn" onclick="loadData()">刷新数据</button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="chart-container">
|
|
|
|
|
- <h3>价格对比图</h3>
|
|
|
|
|
- <div class="chart-wrapper">
|
|
|
|
|
- <canvas id="priceChart"></canvas>
|
|
|
|
|
|
|
+ <div class="content">
|
|
|
|
|
+ <div id="loading" class="loading">请选择币对以查看数据</div>
|
|
|
|
|
+ <div id="error" class="error" style="display: none;"></div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 数据卡片已移除 -->
|
|
|
|
|
+
|
|
|
|
|
+ <div id="chartsContainer" style="display: none;">
|
|
|
|
|
+ <!-- 24小时缩略图 -->
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <h3>24小时价格缩略图 (点击查看详情)</h3>
|
|
|
|
|
+ <div class="chart-wrapper" style="height: 200px;">
|
|
|
|
|
+ <canvas id="thumbnailChart"></canvas>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <h3>价格对比图</h3>
|
|
|
|
|
+ <div class="chart-wrapper">
|
|
|
|
|
+ <canvas id="priceChart"></canvas>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <h3>价格差异图</h3>
|
|
|
|
|
+ <div class="chart-wrapper">
|
|
|
|
|
+ <canvas id="diffChart"></canvas>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
|
|
|
|
- <div class="chart-container">
|
|
|
|
|
- <h3>价格差异图</h3>
|
|
|
|
|
- <div class="chart-wrapper">
|
|
|
|
|
- <canvas id="diffChart"></canvas>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div id="lastUpdate" class="last-update" style="display: none;"></div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <div id="lastUpdate" class="last-update" style="display: none;"></div>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -283,6 +373,8 @@
|
|
|
let thumbnailChart = null;
|
|
let thumbnailChart = null;
|
|
|
let thumbnailData = null;
|
|
let thumbnailData = null;
|
|
|
let autoRefreshInterval = null;
|
|
let autoRefreshInterval = null;
|
|
|
|
|
+ let selectedSymbol = null;
|
|
|
|
|
+
|
|
|
// 动态设置API基础URL
|
|
// 动态设置API基础URL
|
|
|
let API_BASE = '';
|
|
let API_BASE = '';
|
|
|
|
|
|
|
@@ -300,15 +392,25 @@
|
|
|
// 初始化
|
|
// 初始化
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
loadSymbols();
|
|
loadSymbols();
|
|
|
- setupEventListeners();
|
|
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- function setupEventListeners() {
|
|
|
|
|
- document.getElementById('symbolSelect').addEventListener('change', function() {
|
|
|
|
|
- if (this.value) {
|
|
|
|
|
- loadData();
|
|
|
|
|
|
|
+ function selectSymbol(symbol) {
|
|
|
|
|
+ // 移除之前选中的样式
|
|
|
|
|
+ const previousActive = document.querySelector('.symbol-item.active');
|
|
|
|
|
+ if (previousActive) {
|
|
|
|
|
+ previousActive.classList.remove('active');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加新的选中样式
|
|
|
|
|
+ const symbolItems = document.querySelectorAll('.symbol-item');
|
|
|
|
|
+ symbolItems.forEach(item => {
|
|
|
|
|
+ if (item.textContent === symbol) {
|
|
|
|
|
+ item.classList.add('active');
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ selectedSymbol = symbol;
|
|
|
|
|
+ loadData();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function loadSymbols() {
|
|
async function loadSymbols() {
|
|
@@ -316,24 +418,25 @@
|
|
|
const response = await axios.get(`${API_BASE}/symbols`);
|
|
const response = await axios.get(`${API_BASE}/symbols`);
|
|
|
const symbols = response.data.symbols;
|
|
const symbols = response.data.symbols;
|
|
|
|
|
|
|
|
- const select = document.getElementById('symbolSelect');
|
|
|
|
|
- select.innerHTML = '<option value="">请选择币对...</option>';
|
|
|
|
|
|
|
+ const symbolList = document.getElementById('symbolList');
|
|
|
|
|
+ symbolList.innerHTML = '';
|
|
|
|
|
|
|
|
symbols.forEach(symbol => {
|
|
symbols.forEach(symbol => {
|
|
|
- const option = document.createElement('option');
|
|
|
|
|
- option.value = symbol;
|
|
|
|
|
- option.textContent = symbol;
|
|
|
|
|
- select.appendChild(option);
|
|
|
|
|
|
|
+ const listItem = document.createElement('li');
|
|
|
|
|
+ listItem.className = 'symbol-item';
|
|
|
|
|
+ listItem.textContent = symbol;
|
|
|
|
|
+ listItem.onclick = () => selectSymbol(symbol);
|
|
|
|
|
+ symbolList.appendChild(listItem);
|
|
|
});
|
|
});
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
|
|
+ const symbolList = document.getElementById('symbolList');
|
|
|
|
|
+ symbolList.innerHTML = '<li class="symbol-item error-item">加载币对列表失败</li>';
|
|
|
showError('加载币对列表失败: ' + error.message);
|
|
showError('加载币对列表失败: ' + error.message);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function loadData() {
|
|
async function loadData() {
|
|
|
- const symbol = document.getElementById('symbolSelect').value;
|
|
|
|
|
-
|
|
|
|
|
- if (!symbol) {
|
|
|
|
|
|
|
+ if (!selectedSymbol) {
|
|
|
showError('请先选择币对');
|
|
showError('请先选择币对');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -343,18 +446,18 @@
|
|
|
try {
|
|
try {
|
|
|
// 并行加载数据、统计信息和24小时缩略图
|
|
// 并行加载数据、统计信息和24小时缩略图
|
|
|
const [dataResponse, statsResponse, latestResponse, thumbnailResponse] = await Promise.all([
|
|
const [dataResponse, statsResponse, latestResponse, thumbnailResponse] = await Promise.all([
|
|
|
- axios.get(`${API_BASE}/data/${symbol}?hours=24&limit=1000`),
|
|
|
|
|
- axios.get(`${API_BASE}/stats/${symbol}?hours=24`),
|
|
|
|
|
- axios.get(`${API_BASE}/latest/${symbol}`),
|
|
|
|
|
- axios.get(`${API_BASE}/thumbnail/${symbol}`)
|
|
|
|
|
|
|
+ axios.get(`${API_BASE}/data/${selectedSymbol}?hours=24&limit=1000`),
|
|
|
|
|
+ axios.get(`${API_BASE}/stats/${selectedSymbol}?hours=24`),
|
|
|
|
|
+ axios.get(`${API_BASE}/latest/${selectedSymbol}`),
|
|
|
|
|
+ axios.get(`${API_BASE}/thumbnail/${selectedSymbol}`)
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
const data = dataResponse.data.data;
|
|
const data = dataResponse.data.data;
|
|
|
thumbnailData = thumbnailResponse.data.data;
|
|
thumbnailData = thumbnailResponse.data.data;
|
|
|
// 数据卡片相关代码已移除
|
|
// 数据卡片相关代码已移除
|
|
|
|
|
|
|
|
- updateThumbnailChart(thumbnailData, symbol);
|
|
|
|
|
- updateCharts(data, symbol);
|
|
|
|
|
|
|
+ updateThumbnailChart(thumbnailData, selectedSymbol);
|
|
|
|
|
+ updateCharts(data, selectedSymbol);
|
|
|
updateLastUpdateTime();
|
|
updateLastUpdateTime();
|
|
|
|
|
|
|
|
document.getElementById('chartsContainer').style.display = 'block';
|
|
document.getElementById('chartsContainer').style.display = 'block';
|