index.html 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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>实时数据大屏</title>
  7. <script src="vue.global.min.js"></script>
  8. <link rel="stylesheet" href="index.css">
  9. </head>
  10. <body>
  11. <div id="app">
  12. <div v-if="profiles.length > 0" class="cards-grid">
  13. <div v-for="(profile, index) in profiles" :key="index" class="card">
  14. <div class="avatar-container">
  15. <img
  16. :src="profile.webUrl"
  17. class="avatar-image"
  18. @error="handleAvatarError"
  19. >
  20. </div>
  21. <div class="card-section">
  22. <div class="card-label">学院</div>
  23. <div class="card-school-value">{{ profile.className}}</div>
  24. </div>
  25. <div class="card-section">
  26. <div class="card-label">姓名</div>
  27. <div class="card-name-value">{{ profile.studentName}}</div>
  28. </div>
  29. <div class="card-divider"></div>
  30. <div class="card-section">
  31. <div class="card-label">成绩</div>
  32. <div class="card-score">{{ profile.achievement ? profile.achievement : '0' }}</div>
  33. </div>
  34. <div class="card-section">
  35. <div class="card-label">违规</div>
  36. <div class="card-violation">{{ profile.violationTimes ? profile.violationTimes : '0' }}</div>
  37. </div>
  38. </div>
  39. </div>
  40. </div>
  41. <script>
  42. const { createApp } = Vue;
  43. createApp({
  44. data() {
  45. return {
  46. profiles: [],
  47. connected: false,
  48. wsUrl: (() => {
  49. // const hostname = "192.168.1.32" // 测试地址
  50. const hostname = window.location.hostname; // 正式地址从浏览器获取
  51. let url = `ws://${hostname}:8090/api/screen/websocket`;
  52. const urlParams = new URLSearchParams(window.location.search);
  53. if (urlParams.toString()) {
  54. url += `?${urlParams.toString()}`;
  55. console.log("连接地址:", url)
  56. }
  57. return url;
  58. })(),
  59. ws: null,
  60. maxProfiles: 5,
  61. noDataTimeout: null,
  62. noDataDelay: 180000 // 3分钟
  63. };
  64. },
  65. mounted() {
  66. this.connect();
  67. },
  68. methods: {
  69. connect() {
  70. try {
  71. if (this.ws) {
  72. this.ws.close();
  73. }
  74. this.ws = new WebSocket(this.wsUrl);
  75. this.ws.onopen = () => {
  76. this.connected = true;
  77. console.log('WebSocket已连接');
  78. this.resetNoDataTimer();
  79. };
  80. this.ws.onmessage = (event) => {
  81. try {
  82. const data = JSON.parse(event.data);
  83. if (Array.isArray(data)) {
  84. data.forEach(item => {
  85. this.addNewProfile(item);
  86. });
  87. } else if (data && typeof data === 'object') {
  88. this.addNewProfile(data);
  89. }
  90. this.resetNoDataTimer();
  91. } catch (e) {
  92. console.error('数据解析错误:', e);
  93. }
  94. };
  95. this.ws.onerror = (error) => {
  96. console.error('WebSocket错误:', error);
  97. };
  98. this.ws.onclose = () => {
  99. this.connected = false;
  100. console.log('WebSocket已断开');
  101. setTimeout(() => this.connect(), 1000);
  102. };
  103. } catch (e) {
  104. console.error('连接失败:', e.message);
  105. setTimeout(() => this.connect(), 3000);
  106. }
  107. },
  108. addNewProfile(newProfile) {
  109. this.profiles.unshift(newProfile);
  110. // 如果超过最大数量,移除最早的数据
  111. if (this.profiles.length > this.maxProfiles) {
  112. this.profiles.pop();
  113. }
  114. },
  115. resetNoDataTimer() {
  116. if (this.noDataTimeout) {
  117. clearTimeout(this.noDataTimeout);
  118. }
  119. this.noDataTimeout = setTimeout(() => {
  120. this.profiles = [];
  121. }, this.noDataDelay);
  122. },
  123. // getFullAvatarUrl(avatar) {
  124. // const hostname = window.location.hostname;
  125. // return `http://${hostname}${avatar}`
  126. // },
  127. handleAvatarError(event) {
  128. event.target.src = '/avatar.png'; // 图片加载失败时采用默认头像
  129. },
  130. beforeUnmount() {
  131. if (this.ws) {
  132. this.ws.close();
  133. }
  134. if (this.noDataTimeout) {
  135. clearTimeout(this.noDataTimeout);
  136. }
  137. }
  138. }
  139. }).mount('#app');
  140. </script>
  141. </body>
  142. </html>