| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>价格与价差监控 MUBARAK/USDT</title>
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <!-- 引入 Chart.js -->
- <style>
- body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
- .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); margin-bottom: 20px; }
- h1, h2 { text-align: center; color: #333; }
- table { width: 100%; border-collapse: collapse; margin-top: 20px; margin-bottom: 20px; }
- th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
- th { background-color: #e9e9e9; }
- .price-up { color: green; }
- .price-down { color: red; }
- .error-message { color: #c00; font-style: italic; font-size: 0.9em; } /* Renamed from .error */
- .status-cell { /* For status messages, not just errors */ }
- .timestamp { font-size: 0.9em; color: #666; text-align: right; margin-top: 15px; }
- .chart-container {
- position: relative;
- height: 40vh; /* Responsive height */
- width: 80vw; /* Responsive width */
- margin: auto; /* Center the chart */
- margin-bottom: 30px;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>MUBARAK/USDT 价格监控</h1>
- <table>
- <thead>
- <tr>
- <th>平台</th>
- <th>价格 (USDT/MUBARAK)</th>
- <th>状态/错误</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>OpenOcean (BSC)</td>
- <td id="oo-price">加载中...</td>
- <td id="oo-status" class="status-cell"></td>
- </tr>
- <tr>
- <td>Gate.io (Spot)</td>
- <td id="gate-price">加载中...</td>
- <td id="gate-status" class="status-cell"></td>
- </tr>
- <tr>
- <td><b>价差百分比</b></td>
- <td id="diff-percentage" colspan="2">计算中...</td>
- </tr>
- </tbody>
- </table>
- <div class="timestamp">最后更新: <span id="last-updated">N/A</span></div>
- </div>
- <div class="container">
- <h2>价格历史曲线</h2>
- <div class="chart-container">
- <canvas id="priceHistoryChart"></canvas>
- </div>
- </div>
- <div class="container">
- <h2>价差百分比历史曲线</h2>
- <div class="chart-container">
- <canvas id="diffHistoryChart"></canvas>
- </div>
- </div>
- <script>
- let priceChartInstance = null;
- let diffChartInstance = null;
- const MAX_CHART_POINTS = 60; // 与后端 MAX_HISTORY_POINTS 对应或稍小
- function formatPrice(priceStr) {
- if (priceStr === null || priceStr === undefined || priceStr === "N/A") return "N/A";
- const price = parseFloat(priceStr);
- return isNaN(price) ? "N/A" : price.toFixed(6);
- }
- function initializeChart(ctx, chartType, datasetsConfig, initialLabels, titleText) {
- return new Chart(ctx, {
- type: 'line',
- data: {
- labels: initialLabels, // 时间戳
- datasets: datasetsConfig // e.g., [{ label: 'OpenOcean', data: [], borderColor: 'rgb(75, 192, 192)', tension: 0.1, fill: false }, ...]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- animation: {
- duration: 200 // 平滑过渡
- },
- scales: {
- x: {
- title: { display: true, text: '时间' }
- },
- y: {
- title: { display: true, text: chartType === 'price' ? '价格 (USDT)' : '价差 (%)' },
- beginAtZero: chartType === 'diff' ? false : undefined // 价差可能为负
- }
- },
- plugins: {
- legend: { position: 'top' },
- title: { display: true, text: titleText }
- },
- elements: {
- point:{
- radius: 2 // Smaller points
- }
- }
- }
- });
- }
- function updateChartData(chartInstance, newLabels, newDatasetsData) {
- chartInstance.data.labels = newLabels;
- newDatasetsData.forEach((datasetData, index) => {
- chartInstance.data.datasets[index].data = datasetData;
- });
- chartInstance.update('quiet'); // 'quiet' to prevent re-animation on every update
- }
- function updateDisplayAndCharts() {
- fetch('/data')
- .then(response => response.json())
- .then(data => {
- // --- 更新表格数据 ---
- const current = data.current;
- document.getElementById('oo-price').textContent = formatPrice(current.oo_price);
- document.getElementById('gate-price').textContent = formatPrice(current.gate_price);
- const diffEl = document.getElementById('diff-percentage');
- if (current.difference_percentage && current.difference_percentage !== "N/A") {
- diffEl.textContent = current.difference_percentage;
- const diffValue = parseFloat(current.difference_percentage.replace('%', ''));
- if (!isNaN(diffValue)) { // Ensure it's a number before comparing
- if (diffValue > 0) diffEl.className = 'price-up';
- else if (diffValue < 0) diffEl.className = 'price-down';
- else diffEl.className = '';
- } else {
- diffEl.className = '';
- }
- } else {
- diffEl.textContent = current.difference_percentage || "N/A";
- diffEl.className = '';
- }
- const ooStatusEl = document.getElementById('oo-status');
- ooStatusEl.textContent = current.oo_error ? `错误: ${current.oo_error}` : '正常';
- ooStatusEl.className = current.oo_error ? 'status-cell error-message' : 'status-cell ';
- const gateStatusEl = document.getElementById('gate-status');
- gateStatusEl.textContent = current.gate_error ? `错误: ${current.gate_error}` : '正常';
- gateStatusEl.className = current.gate_error ? 'status-cell error-message' : 'status-cell ';
- document.getElementById('last-updated').textContent = current.last_updated || "N/A";
- // --- 初始化或更新图表 ---
- const history = data.history;
- // 价格历史图表
- const priceCtx = document.getElementById('priceHistoryChart').getContext('2d');
- const priceDatasets = [
- {
- label: 'OpenOcean',
- data: history.prices.oo,
- borderColor: 'rgb(75, 192, 192)',
- tension: 0.1,
- fill: false
- },
- {
- label: 'Gate.io',
- data: history.prices.gate,
- borderColor: 'rgb(255, 99, 132)',
- tension: 0.1,
- fill: false
- }
- ];
- if (!priceChartInstance) {
- priceChartInstance = initializeChart(priceCtx, 'price', priceDatasets, history.prices.labels, '价格历史 (MUBARAK/USDT)');
- } else {
- updateChartData(priceChartInstance, history.prices.labels, [history.prices.oo, history.prices.gate]);
- }
- // 价差历史图表
- const diffCtx = document.getElementById('diffHistoryChart').getContext('2d');
- const diffDatasets = [
- {
- label: '价差百分比 (OO vs Gate)',
- data: history.difference.values,
- borderColor: 'rgb(54, 162, 235)',
- tension: 0.1,
- fill: false
- }
- ];
- if (!diffChartInstance) {
- diffChartInstance = initializeChart(diffCtx, 'diff', diffDatasets, history.difference.labels, '价差百分比历史');
- } else {
- updateChartData(diffChartInstance, history.difference.labels, [history.difference.values]);
- }
- })
- .catch(error => {
- console.error('Error fetching data:', error);
- // Handle display errors if fetch fails
- document.getElementById('oo-price').textContent = '错误';
- document.getElementById('gate-price').textContent = '错误';
- document.getElementById('diff-percentage').textContent = '无法获取数据';
- });
- }
- // 首次加载数据并初始化图表
- updateDisplayAndCharts();
- // 每5秒更新一次数据和图表 (与后端更新频率匹配)
- setInterval(updateDisplayAndCharts, 5000);
- </script>
- </body>
- </html>
|