index.html 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>价格与价差监控 MUBARAK/USDT</title>
  7. <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <!-- 引入 Chart.js -->
  8. <style>
  9. body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
  10. .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); margin-bottom: 20px; }
  11. h1, h2 { text-align: center; color: #333; }
  12. table { width: 100%; border-collapse: collapse; margin-top: 20px; margin-bottom: 20px; }
  13. th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
  14. th { background-color: #e9e9e9; }
  15. .price-up { color: green; }
  16. .price-down { color: red; }
  17. .error-message { color: #c00; font-style: italic; font-size: 0.9em; } /* Renamed from .error */
  18. .status-cell { /* For status messages, not just errors */ }
  19. .timestamp { font-size: 0.9em; color: #666; text-align: right; margin-top: 15px; }
  20. .chart-container {
  21. position: relative;
  22. height: 40vh; /* Responsive height */
  23. width: 80vw; /* Responsive width */
  24. margin: auto; /* Center the chart */
  25. margin-bottom: 30px;
  26. }
  27. </style>
  28. </head>
  29. <body>
  30. <div class="container">
  31. <h1>MUBARAK/USDT 价格监控</h1>
  32. <table>
  33. <thead>
  34. <tr>
  35. <th>平台</th>
  36. <th>价格 (USDT/MUBARAK)</th>
  37. <th>状态/错误</th>
  38. </tr>
  39. </thead>
  40. <tbody>
  41. <tr>
  42. <td>OpenOcean (BSC)</td>
  43. <td id="oo-price">加载中...</td>
  44. <td id="oo-status" class="status-cell"></td>
  45. </tr>
  46. <tr>
  47. <td>Gate.io (Spot)</td>
  48. <td id="gate-price">加载中...</td>
  49. <td id="gate-status" class="status-cell"></td>
  50. </tr>
  51. <tr>
  52. <td><b>价差百分比</b></td>
  53. <td id="diff-percentage" colspan="2">计算中...</td>
  54. </tr>
  55. </tbody>
  56. </table>
  57. <div class="timestamp">最后更新: <span id="last-updated">N/A</span></div>
  58. </div>
  59. <div class="container">
  60. <h2>价格历史曲线</h2>
  61. <div class="chart-container">
  62. <canvas id="priceHistoryChart"></canvas>
  63. </div>
  64. </div>
  65. <div class="container">
  66. <h2>价差百分比历史曲线</h2>
  67. <div class="chart-container">
  68. <canvas id="diffHistoryChart"></canvas>
  69. </div>
  70. </div>
  71. <script>
  72. let priceChartInstance = null;
  73. let diffChartInstance = null;
  74. const MAX_CHART_POINTS = 60; // 与后端 MAX_HISTORY_POINTS 对应或稍小
  75. function formatPrice(priceStr) {
  76. if (priceStr === null || priceStr === undefined || priceStr === "N/A") return "N/A";
  77. const price = parseFloat(priceStr);
  78. return isNaN(price) ? "N/A" : price.toFixed(6);
  79. }
  80. function initializeChart(ctx, chartType, datasetsConfig, initialLabels, titleText) {
  81. return new Chart(ctx, {
  82. type: 'line',
  83. data: {
  84. labels: initialLabels, // 时间戳
  85. datasets: datasetsConfig // e.g., [{ label: 'OpenOcean', data: [], borderColor: 'rgb(75, 192, 192)', tension: 0.1, fill: false }, ...]
  86. },
  87. options: {
  88. responsive: true,
  89. maintainAspectRatio: false,
  90. animation: {
  91. duration: 200 // 平滑过渡
  92. },
  93. scales: {
  94. x: {
  95. title: { display: true, text: '时间' }
  96. },
  97. y: {
  98. title: { display: true, text: chartType === 'price' ? '价格 (USDT)' : '价差 (%)' },
  99. beginAtZero: chartType === 'diff' ? false : undefined // 价差可能为负
  100. }
  101. },
  102. plugins: {
  103. legend: { position: 'top' },
  104. title: { display: true, text: titleText }
  105. },
  106. elements: {
  107. point:{
  108. radius: 2 // Smaller points
  109. }
  110. }
  111. }
  112. });
  113. }
  114. function updateChartData(chartInstance, newLabels, newDatasetsData) {
  115. chartInstance.data.labels = newLabels;
  116. newDatasetsData.forEach((datasetData, index) => {
  117. chartInstance.data.datasets[index].data = datasetData;
  118. });
  119. chartInstance.update('quiet'); // 'quiet' to prevent re-animation on every update
  120. }
  121. function updateDisplayAndCharts() {
  122. fetch('/data')
  123. .then(response => response.json())
  124. .then(data => {
  125. // --- 更新表格数据 ---
  126. const current = data.current;
  127. document.getElementById('oo-price').textContent = formatPrice(current.oo_price);
  128. document.getElementById('gate-price').textContent = formatPrice(current.gate_price);
  129. const diffEl = document.getElementById('diff-percentage');
  130. if (current.difference_percentage && current.difference_percentage !== "N/A") {
  131. diffEl.textContent = current.difference_percentage;
  132. const diffValue = parseFloat(current.difference_percentage.replace('%', ''));
  133. if (!isNaN(diffValue)) { // Ensure it's a number before comparing
  134. if (diffValue > 0) diffEl.className = 'price-up';
  135. else if (diffValue < 0) diffEl.className = 'price-down';
  136. else diffEl.className = '';
  137. } else {
  138. diffEl.className = '';
  139. }
  140. } else {
  141. diffEl.textContent = current.difference_percentage || "N/A";
  142. diffEl.className = '';
  143. }
  144. const ooStatusEl = document.getElementById('oo-status');
  145. ooStatusEl.textContent = current.oo_error ? `错误: ${current.oo_error}` : '正常';
  146. ooStatusEl.className = current.oo_error ? 'status-cell error-message' : 'status-cell ';
  147. const gateStatusEl = document.getElementById('gate-status');
  148. gateStatusEl.textContent = current.gate_error ? `错误: ${current.gate_error}` : '正常';
  149. gateStatusEl.className = current.gate_error ? 'status-cell error-message' : 'status-cell ';
  150. document.getElementById('last-updated').textContent = current.last_updated || "N/A";
  151. // --- 初始化或更新图表 ---
  152. const history = data.history;
  153. // 价格历史图表
  154. const priceCtx = document.getElementById('priceHistoryChart').getContext('2d');
  155. const priceDatasets = [
  156. {
  157. label: 'OpenOcean',
  158. data: history.prices.oo,
  159. borderColor: 'rgb(75, 192, 192)',
  160. tension: 0.1,
  161. fill: false
  162. },
  163. {
  164. label: 'Gate.io',
  165. data: history.prices.gate,
  166. borderColor: 'rgb(255, 99, 132)',
  167. tension: 0.1,
  168. fill: false
  169. }
  170. ];
  171. if (!priceChartInstance) {
  172. priceChartInstance = initializeChart(priceCtx, 'price', priceDatasets, history.prices.labels, '价格历史 (MUBARAK/USDT)');
  173. } else {
  174. updateChartData(priceChartInstance, history.prices.labels, [history.prices.oo, history.prices.gate]);
  175. }
  176. // 价差历史图表
  177. const diffCtx = document.getElementById('diffHistoryChart').getContext('2d');
  178. const diffDatasets = [
  179. {
  180. label: '价差百分比 (OO vs Gate)',
  181. data: history.difference.values,
  182. borderColor: 'rgb(54, 162, 235)',
  183. tension: 0.1,
  184. fill: false
  185. }
  186. ];
  187. if (!diffChartInstance) {
  188. diffChartInstance = initializeChart(diffCtx, 'diff', diffDatasets, history.difference.labels, '价差百分比历史');
  189. } else {
  190. updateChartData(diffChartInstance, history.difference.labels, [history.difference.values]);
  191. }
  192. })
  193. .catch(error => {
  194. console.error('Error fetching data:', error);
  195. // Handle display errors if fetch fails
  196. document.getElementById('oo-price').textContent = '错误';
  197. document.getElementById('gate-price').textContent = '错误';
  198. document.getElementById('diff-percentage').textContent = '无法获取数据';
  199. });
  200. }
  201. // 首次加载数据并初始化图表
  202. updateDisplayAndCharts();
  203. // 每5秒更新一次数据和图表 (与后端更新频率匹配)
  204. setInterval(updateDisplayAndCharts, 5000);
  205. </script>
  206. </body>
  207. </html>