dashboard.html 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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">
  6. <title>大屏展示</title>
  7. <style>
  8. body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, PingFang SC, Microsoft YaHei, sans-serif; background: #0b1020; color: #e8ecf1; }
  9. .header { display:flex; justify-content: space-between; align-items:center; padding: 14px 20px; background: #10182b; border-bottom: 1px solid #1e2a44; }
  10. .title { font-size: 18px; font-weight: 600; }
  11. .btn { height: 34px; padding: 0 12px; border-radius: 8px; border: 1px solid #2a3b5f; background: #0e1424; color: #e8ecf1; cursor:pointer; }
  12. .grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; padding: 16px; }
  13. .card { background: #121a2a; border: 1px solid #1e2a44; border-radius: 12px; padding: 16px; }
  14. .metric { display:flex; gap: 16px; margin-top: 8px; }
  15. .kv { background: #0e1424; border: 1px solid #2a3b5f; border-radius: 10px; padding: 12px; min-width: 140px; text-align:center; }
  16. .k { font-size: 12px; color: #9fb0ca; }
  17. .v { font-size: 22px; font-weight: 700; margin-top: 6px; }
  18. .error { color: #ff6b6b; font-size: 13px; min-height: 18px; margin-top: 6px; }
  19. </style>
  20. <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
  21. <script>
  22. let personChart;
  23. let vehicleChart;
  24. function ensureToken() {
  25. const token = localStorage.getItem('access_token');
  26. if (!token) {
  27. window.location.href = '/login';
  28. return null;
  29. }
  30. return token;
  31. }
  32. async function fetchCount(algorithmId) {
  33. const token = ensureToken();
  34. if (!token) return null;
  35. const resp = await fetch(`/api/v1/CrossCount/getCrossCountByAlgorithmsId/${algorithmId}`, {
  36. headers: { 'Authorization': `Bearer ${token}` }
  37. });
  38. if (!resp.ok) throw new Error(await resp.text());
  39. return resp.json();
  40. }
  41. function initChartsIfNeeded() {
  42. if (!personChart) {
  43. const ctx = document.getElementById('person-chart').getContext('2d');
  44. personChart = new Chart(ctx, {
  45. type: 'line',
  46. data: { labels: [], datasets: [
  47. { label: '进线', data: [], borderColor: '#4cafef', backgroundColor: 'rgba(76,175,239,.2)', tension: .3 },
  48. { label: '出线', data: [], borderColor: '#ff9f40', backgroundColor: 'rgba(255,159,64,.2)', tension: .3 },
  49. { label: '在场', data: [], borderColor: '#62d26f', backgroundColor: 'rgba(98,210,111,.2)', tension: .3 }
  50. ]},
  51. options: { responsive: true, plugins: { legend: { labels: { color: '#9fb0ca' } } }, scales: { x: { ticks: { color: '#9fb0ca' }, grid: { color: '#1e2a44' } }, y: { ticks: { color: '#9fb0ca' }, grid: { color: '#1e2a44' } } } }
  52. });
  53. }
  54. if (!vehicleChart) {
  55. const ctx = document.getElementById('vehicle-chart').getContext('2d');
  56. vehicleChart = new Chart(ctx, {
  57. type: 'line',
  58. data: { labels: [], datasets: [
  59. { label: '进线', data: [], borderColor: '#4cafef', backgroundColor: 'rgba(76,175,239,.2)', tension: .3 },
  60. { label: '出线', data: [], borderColor: '#ff9f40', backgroundColor: 'rgba(255,159,64,.2)', tension: .3 },
  61. { label: '在场', data: [], borderColor: '#62d26f', backgroundColor: 'rgba(98,210,111,.2)', tension: .3 }
  62. ]},
  63. options: { responsive: true, plugins: { legend: { labels: { color: '#9fb0ca' } } }, scales: { x: { ticks: { color: '#9fb0ca' }, grid: { color: '#1e2a44' } }, y: { ticks: { color: '#9fb0ca' }, grid: { color: '#1e2a44' } } } }
  64. });
  65. }
  66. }
  67. function pushChartPoint(chart, label, up, down, total) {
  68. const maxPoints = 60; // 保留最近5分钟的 5s 间隔点
  69. chart.data.labels.push(label);
  70. chart.data.datasets[0].data.push(up);
  71. chart.data.datasets[1].data.push(down);
  72. chart.data.datasets[2].data.push(total);
  73. if (chart.data.labels.length > maxPoints) {
  74. chart.data.labels.shift();
  75. chart.data.datasets.forEach(d => d.data.shift());
  76. }
  77. chart.update('none');
  78. }
  79. async function loadData() {
  80. const err = document.getElementById('error');
  81. err.textContent = '';
  82. try {
  83. initChartsIfNeeded();
  84. const [person, vehicle] = await Promise.all([
  85. fetchCount(2),
  86. fetchCount(3)
  87. ]);
  88. renderCard('person', person);
  89. renderCard('vehicle', vehicle);
  90. const ts = new Date();
  91. const label = ts.toLocaleTimeString();
  92. pushChartPoint(personChart, label, person.crossTotalUpCount ?? 0, person.crossTotalDownCount ?? 0, person.totalCount ?? 0);
  93. pushChartPoint(vehicleChart, label, vehicle.crossTotalUpCount ?? 0, vehicle.crossTotalDownCount ?? 0, vehicle.totalCount ?? 0);
  94. } catch (e) {
  95. err.textContent = e.message || '加载失败';
  96. }
  97. }
  98. function renderCard(prefix, data) {
  99. document.getElementById(`${prefix}-up`).textContent = data.crossTotalUpCount ?? 0;
  100. document.getElementById(`${prefix}-down`).textContent = data.crossTotalDownCount ?? 0;
  101. document.getElementById(`${prefix}-total`).textContent = data.totalCount ?? 0;
  102. }
  103. function logout() {
  104. localStorage.removeItem('access_token');
  105. window.location.href = '/login';
  106. }
  107. document.addEventListener('DOMContentLoaded', loadData);
  108. // 每60秒自动刷新一次数据
  109. setInterval(loadData, 60000);
  110. </script>
  111. </head>
  112. <body>
  113. <div class="header">
  114. <div class="title">大屏展示</div>
  115. <div>
  116. <button class="btn" onclick="loadData()">刷新</button>
  117. <button class="btn" onclick="logout()">退出</button>
  118. </div>
  119. </div>
  120. <div class="grid">
  121. <div class="card">
  122. <div class="title">办公室人员进出入统计</div>
  123. <div class="metric">
  124. <div class="kv"><div class="k">进线总数</div><div id="person-up" class="v">0</div></div>
  125. <div class="kv"><div class="k">出线总数</div><div id="person-down" class="v">0</div></div>
  126. <div class="kv"><div class="k">当前在场</div><div id="person-total" class="v">0</div></div>
  127. </div>
  128. <div style="margin-top: 12px;">
  129. <canvas id="person-chart" height="120"></canvas>
  130. </div>
  131. </div>
  132. <div class="card">
  133. <div class="title">园区车辆进出入统计</div>
  134. <div class="metric">
  135. <div class="kv"><div class="k">进线总数</div><div id="vehicle-up" class="v">0</div></div>
  136. <div class="kv"><div class="k">出线总数</div><div id="vehicle-down" class="v">0</div></div>
  137. <div class="kv"><div class="k">当前在场</div><div id="vehicle-total" class="v">0</div></div>
  138. </div>
  139. <div style="margin-top: 12px;">
  140. <canvas id="vehicle-chart" height="120"></canvas>
  141. </div>
  142. </div>
  143. </div>
  144. <div id="error" class="error" style="padding: 0 16px;"></div>
  145. </body>
  146. </html>