jinx 2 days ago
parent
commit
f2c3739ead
3 changed files with 203 additions and 47 deletions
  1. 117 40
      src/views/Report/index.vue
  2. 75 0
      src/views/Report/reportInfoStream.js
  3. 11 7
      vue.config.js

+ 117 - 40
src/views/Report/index.vue

@@ -6,7 +6,10 @@
       <el-button
         v-for="item in tagList"
         :key="item.id"
-        :class="['report-btn', { 'is-active': activeReportItem && activeReportItem.id === item.id }]"
+        :class="[
+          'report-btn',
+          { 'is-active': activeReportItem && activeReportItem.id === item.id },
+        ]"
         type="primary"
         plain
         :loading="reportingId === item.id"
@@ -29,10 +32,13 @@
           <span class="btn-name">结束上报</span>
         </span>
       </el-button>
+
+      <!-- <div @click="getReportInfo">获取数据:{{ servicesData }}</div> -->
     </div>
 
     <p v-if="activeReportItem" class="status-text">
-      Reporting: {{ activeReportItem.name }} #{{ activeReportItem.id }} (every 1s)
+      Reporting: {{ activeReportItem.name }} #{{ activeReportItem.id }} (every
+      1s)
     </p>
 
     <div v-if="lastReportedItem" class="report-result">
@@ -43,12 +49,12 @@
 </template>
 
 <script>
-import { ElMessage } from 'element-plus'
-import { reportTagInfo } from '@/api.js'
-import tagCoordinateList from './tagCoordinateList.json'
-
+import { ElMessage } from "element-plus";
+import { reportTagInfo } from "@/api.js";
+import tagCoordinateList from "./tagCoordinateList.json";
+import reportInfoStream from "./reportInfoStream.js";
 export default {
-  name: 'ReportPage',
+  name: "ReportPage",
   data() {
     return {
       tagList: tagCoordinateList,
@@ -57,48 +63,117 @@ export default {
       lastReportedItem: null,
       reportTimer: null,
       isRequesting: false,
-    }
+      reportStream: null,
+      servicesData: null,
+    };
   },
   beforeUnmount() {
-    this.clearReportTimer()
+    this.clearReportTimer();
+    this.closeReportStream();
+
+    this.unsubData && this.unsubData();
+    this.unsubErr && this.unsubErr();
+  },
+  mounted() {
+    this.unsubData = reportInfoStream.subscribe((data) => {
+      console.log("stream:", data);
+      this.servicesData = data;
+    });
+
+    this.unsubErr = reportInfoStream.onError((err) => {
+      console.error(err);
+    });
   },
   methods: {
+    closeReportStream() {
+      if (this.reportStream) {
+        this.reportStream.close();
+        this.reportStream = null;
+      }
+    },
+    getReportInfo() {
+      reportInfoStream.start();
+
+      return;
+      this.closeReportStream();
+      this.reportStream = new EventSource("/location/stream");
+
+      this.reportStream.onopen = () => {
+        console.log("[report stream] connected");
+      };
+
+      //   this.reportStream.onmessage = (e) => {
+      //     console.log("[report stream] message:", e.data);
+      //     this.servicesData = e.data;
+      //   };
+
+      // Some backends push custom SSE event names instead of default "message".
+      const onCustomEvent = (e) => {
+        console.log("[report stream] custom event:", e.type, e.data);
+        this.servicesData = e.data;
+      };
+      //   this.reportStream.addEventListener("data", onCustomEvent);
+      //   this.reportStream.addEventListener("update", onCustomEvent);
+      this.reportStream.addEventListener("location", onCustomEvent);
+
+      this.reportStream.onerror = (e) => {
+        // EventSource will auto-reconnect when network/server is temporarily unavailable.
+        console.error(
+          "[report stream] error, readyState=",
+          this.reportStream?.readyState,
+          e,
+        );
+
+        // 2 means CLOSED, usually unrecoverable.
+        if (this.reportStream?.readyState === 2) {
+          ElMessage({
+            message: "getReportInfo stream closed",
+            type: "error",
+          });
+          this.closeReportStream();
+        }
+      };
+    },
     clearReportTimer() {
       if (this.reportTimer) {
-        clearInterval(this.reportTimer)
-        this.reportTimer = null
+        clearInterval(this.reportTimer);
+        this.reportTimer = null;
       }
     },
     sendReportOnce() {
       if (!this.activeReportItem || this.isRequesting) {
-        return
+        return;
       }
 
-      const item = this.activeReportItem
-      this.reportingId = item.id
-      this.isRequesting = true
+      const item = this.activeReportItem;
+      this.reportingId = item.id;
+      this.isRequesting = true;
 
-      reportTagInfo(item).then(() => {
-        this.lastReportedItem = item
-      }).catch((err) => {
-        ElMessage({
-          message: err?.message || err || 'report failed',
-          type: 'error',
+      reportTagInfo(item)
+        .then(() => {
+          this.lastReportedItem = item;
+        })
+        .catch((err) => {
+          ElMessage({
+            message: err?.message || err || "report failed",
+            type: "error",
+          });
         })
-      }).finally(() => {
-        this.isRequesting = false
-        this.reportingId = null
-      })
+        .finally(() => {
+          this.isRequesting = false;
+          this.reportingId = null;
+        });
     },
     onReport(item) {
-      const switched = !this.activeReportItem || this.activeReportItem.id !== item.id
-      this.activeReportItem = item
+      const switched =
+        !this.activeReportItem || this.activeReportItem.id !== item.id;
+      this.activeReportItem = item;
 
-      this.clearReportTimer()
-      this.sendReportOnce()
+      this.clearReportTimer();
+      this.sendReportOnce();
       this.reportTimer = setInterval(() => {
-        this.sendReportOnce()
-      }, 1000)
+        this.sendReportOnce();
+      }, 500);
 
       if (switched) {
         // ElMessage({
@@ -108,22 +183,23 @@ export default {
       }
     },
     onStopReport() {
-      this.clearReportTimer()
-      this.activeReportItem = null
-      this.reportingId = null
-      this.isRequesting = false
+      this.clearReportTimer();
+      this.activeReportItem = null;
+      this.reportingId = null;
+      this.isRequesting = false;
       ElMessage({
-        message: 'report stopped',
-        type: 'info',
-      })
+        message: "report stopped",
+        type: "info",
+      });
     },
   },
-}
+};
 </script>
 
 <style scoped>
 .report-page {
-  padding: calc(12px + env(safe-area-inset-top)) 12px calc(16px + env(safe-area-inset-bottom));
+  padding: calc(12px + env(safe-area-inset-top)) 12px
+    calc(16px + env(safe-area-inset-bottom));
   max-width: 920px;
   margin: 0 auto;
 }
@@ -214,3 +290,4 @@ export default {
   }
 }
 </style>
+

+ 75 - 0
src/views/Report/reportInfoStream.js

@@ -0,0 +1,75 @@
+// src/services/reportInfoStream.js
+class ReportInfoStream {
+  constructor(url = "https://uat-laser.4dkankan.com/location/stream") {
+    this.url = url;
+    this.es = null;
+    this.latestData = null;
+    this.dataListeners = new Set();
+    this.errorListeners = new Set();
+  }
+
+  start() {
+    const onCustomEvent = (e) => {
+      let payload = e.data;
+      try {
+        payload = JSON.parse(e.data); // 如果后端是 JSON
+      } catch (_) {
+        // 非 JSON 就保持原字符串
+      }
+
+      this.latestData = payload;
+      this.dataListeners.forEach((fn) => fn(payload));
+    }
+    if (this.es) return; // already started
+
+    this.es = new EventSource(this.url);
+    this.es.addEventListener("location", onCustomEvent);
+
+    this.es.onmessage = (e) => {
+      let payload = e.data;
+      try {
+        payload = JSON.parse(e.data); // 如果后端是 JSON
+      } catch (_) {
+        // 非 JSON 就保持原字符串
+      }
+
+      this.latestData = payload;
+      this.dataListeners.forEach((fn) => fn(payload));
+    };
+
+    this.es.onerror = (err) => {
+      this.errorListeners.forEach((fn) => fn(err));
+    };
+  }
+
+  stop() {
+    if (!this.es) return;
+    this.es.close();
+    this.es = null;
+  }
+
+  subscribe(fn, { immediate = true } = {}) {
+    this.dataListeners.add(fn);
+    if (immediate && this.latestData !== null) fn(this.latestData);
+
+    // 返回取消订阅函数
+    return () => {
+      this.dataListeners.delete(fn);
+    };
+  }
+
+  onError(fn) {
+    this.errorListeners.add(fn);
+    return () => {
+      this.errorListeners.delete(fn);
+    };
+  }
+
+  getLatestData() {
+    return this.latestData;
+  }
+}
+
+// 单例导出:全项目都用同一个流
+export const reportInfoStream = new ReportInfoStream();
+export default reportInfoStream;

+ 11 - 7
vue.config.js

@@ -5,11 +5,15 @@ module.exports = {
       "Cache-Control": "no-store",
     },
     https: false,
-    proxy: {
-      "ingest": {
-        target: process.env.VUE_APP_API_URL_PREFIX,
-        changeOrigin: true,
-      },
-    },
-  },
+    proxy: {
+      // "ingest": {
+      //   target: process.env.VUE_APP_API_URL_PREFIX,
+      //   changeOrigin: true,
+      // },
+      "/location": {
+        target: process.env.VUE_APP_API_URL_PREFIX,
+        changeOrigin: true,
+      },
+    },
+  },
 };