|
@@ -270,6 +270,14 @@
|
|
|
<!-- 数据卡片已移除 -->
|
|
<!-- 数据卡片已移除 -->
|
|
|
|
|
|
|
|
<div id="chartsContainer" style="display: none;">
|
|
<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">
|
|
<div class="chart-container">
|
|
|
<h3>价格对比图</h3>
|
|
<h3>价格对比图</h3>
|
|
|
<div class="chart-wrapper">
|
|
<div class="chart-wrapper">
|
|
@@ -292,6 +300,8 @@
|
|
|
<script>
|
|
<script>
|
|
|
let priceChart = null;
|
|
let priceChart = null;
|
|
|
let diffChart = null;
|
|
let diffChart = null;
|
|
|
|
|
+ let thumbnailChart = null;
|
|
|
|
|
+ let thumbnailData = null;
|
|
|
let autoRefreshInterval = null;
|
|
let autoRefreshInterval = null;
|
|
|
// 动态设置API基础URL
|
|
// 动态设置API基础URL
|
|
|
let API_BASE = '';
|
|
let API_BASE = '';
|
|
@@ -363,16 +373,19 @@
|
|
|
showLoading();
|
|
showLoading();
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // 并行加载数据和统计信息
|
|
|
|
|
- const [dataResponse, statsResponse, latestResponse] = await Promise.all([
|
|
|
|
|
|
|
+ // 并行加载数据、统计信息和24小时缩略图
|
|
|
|
|
+ const [dataResponse, statsResponse, latestResponse, thumbnailResponse] = await Promise.all([
|
|
|
axios.get(`${API_BASE}/data/${symbol}?hours=${hours}&limit=1000`),
|
|
axios.get(`${API_BASE}/data/${symbol}?hours=${hours}&limit=1000`),
|
|
|
axios.get(`${API_BASE}/stats/${symbol}?hours=${hours}`),
|
|
axios.get(`${API_BASE}/stats/${symbol}?hours=${hours}`),
|
|
|
- axios.get(`${API_BASE}/latest/${symbol}`)
|
|
|
|
|
|
|
+ axios.get(`${API_BASE}/latest/${symbol}`),
|
|
|
|
|
+ axios.get(`${API_BASE}/thumbnail/${symbol}`)
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
const data = dataResponse.data.data;
|
|
const data = dataResponse.data.data;
|
|
|
|
|
+ thumbnailData = thumbnailResponse.data.data;
|
|
|
// 数据卡片相关代码已移除
|
|
// 数据卡片相关代码已移除
|
|
|
|
|
|
|
|
|
|
+ updateThumbnailChart(thumbnailData, symbol);
|
|
|
updateCharts(data, symbol);
|
|
updateCharts(data, symbol);
|
|
|
updateLastUpdateTime();
|
|
updateLastUpdateTime();
|
|
|
|
|
|
|
@@ -388,6 +401,95 @@
|
|
|
|
|
|
|
|
// 数据卡片相关函数已移除
|
|
// 数据卡片相关函数已移除
|
|
|
|
|
|
|
|
|
|
+ function updateThumbnailChart(data, symbol) {
|
|
|
|
|
+ if (!data || data.length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const ctx = document.getElementById('thumbnailChart').getContext('2d');
|
|
|
|
|
+
|
|
|
|
|
+ if (thumbnailChart) {
|
|
|
|
|
+ thumbnailChart.destroy();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const labels = data.map(item => {
|
|
|
|
|
+ const date = new Date(item.timestamp);
|
|
|
|
|
+ return date.toLocaleTimeString('zh-CN', {
|
|
|
|
|
+ timeZone: 'Asia/Shanghai',
|
|
|
|
|
+ hour: '2-digit',
|
|
|
|
|
+ minute: '2-digit'
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ thumbnailChart = new Chart(ctx, {
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ data: {
|
|
|
|
|
+ labels: labels,
|
|
|
|
|
+ datasets: [
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Binance 价格',
|
|
|
|
|
+ data: data.map(item => item.binance_mark_price),
|
|
|
|
|
+ borderColor: '#ff6384',
|
|
|
|
|
+ backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
|
|
|
|
+ tension: 0.1,
|
|
|
|
|
+ pointRadius: 0,
|
|
|
|
|
+ borderWidth: 1
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Lighter 价格',
|
|
|
|
|
+ data: data.map(item => item.lighter_mark_price),
|
|
|
|
|
+ borderColor: '#36a2eb',
|
|
|
|
|
+ backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
|
|
|
|
+ tension: 0.1,
|
|
|
|
|
+ pointRadius: 0,
|
|
|
|
|
+ borderWidth: 1
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ options: {
|
|
|
|
|
+ responsive: true,
|
|
|
|
|
+ maintainAspectRatio: false,
|
|
|
|
|
+ interaction: {
|
|
|
|
|
+ mode: 'index',
|
|
|
|
|
+ intersect: false
|
|
|
|
|
+ },
|
|
|
|
|
+ plugins: {
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ display: true,
|
|
|
|
|
+ position: 'top'
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ callbacks: {
|
|
|
|
|
+ title: function(context) {
|
|
|
|
|
+ const index = context[0].dataIndex;
|
|
|
|
|
+ const timestamp = data[index].timestamp;
|
|
|
|
|
+ return new Date(timestamp).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ scales: {
|
|
|
|
|
+ x: {
|
|
|
|
|
+ display: true,
|
|
|
|
|
+ ticks: {
|
|
|
|
|
+ maxTicksLimit: 12
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ y: {
|
|
|
|
|
+ display: true
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onClick: (event, elements) => {
|
|
|
|
|
+ if (elements.length > 0) {
|
|
|
|
|
+ const index = elements[0].index;
|
|
|
|
|
+ const clickedTimestamp = data[index].timestamp;
|
|
|
|
|
+ loadDetailData(symbol, clickedTimestamp);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function updateCharts(data, symbol) {
|
|
function updateCharts(data, symbol) {
|
|
|
if (!data || data.length === 0) {
|
|
if (!data || data.length === 0) {
|
|
|
showError('没有可用的数据');
|
|
showError('没有可用的数据');
|
|
@@ -618,6 +720,30 @@
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ async function loadDetailData(symbol, centerTimestamp) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ showLoading();
|
|
|
|
|
+ const response = await fetch(`${API_BASE}/api/range/${symbol}?timestamp=${centerTimestamp}`);
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (data.error) {
|
|
|
|
|
+ throw new Error(data.error);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ updateCharts(data.data, symbol);
|
|
|
|
|
+ updateLastUpdateTime();
|
|
|
|
|
+ document.getElementById('loading').style.display = 'none';
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error loading detail data:', error);
|
|
|
|
|
+ showError('加载详细数据失败: ' + error.message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
function setupAutoRefresh() {
|
|
function setupAutoRefresh() {
|
|
|
const interval = parseInt(document.getElementById('autoRefresh').value);
|
|
const interval = parseInt(document.getElementById('autoRefresh').value);
|
|
|
|
|
|