jinx 2 hari lalu
induk
melakukan
a647ccfc1d
4 mengubah file dengan 488 tambahan dan 18 penghapusan
  1. 15 8
      src/api.js
  2. 2 0
      src/router/index.js
  3. 457 0
      src/views/Review/index.vue
  4. 14 10
      vue.config.js

+ 15 - 8
src/api.js

@@ -61,14 +61,21 @@ export function startReport(data) {
     }
   });
 }
-export function stopReport(data) {
-  const url = `https://test.4dkankan.com/openDevice/end`;
-
-  return axios.post(url, data).then((res) => {
+export function stopReport(data) {
+  const url = `https://test.4dkankan.com/openDevice/end`;
+
+  return axios.post(url, data).then((res) => {
     if (res?.data?.code === 200 || res?.status === 200) {
       return res?.data || "stop report success";
     } else {
-      throw res?.data?.message || "stop report failed";
-    }
-  });
-}
+      throw res?.data?.message || "stop report failed";
+    }
+  });
+}
+
+export function getDebugMessages(deviceId = "sn123") {
+  const currentDeviceId = encodeURIComponent(deviceId || "sn123");
+  const url = `https://test.4dkankan.com/openDevice/mqtt/debug/messages/${currentDeviceId}`;
+
+  return axios.get(url).then((res) => res?.data);
+}

+ 2 - 0
src/router/index.js

@@ -1,10 +1,12 @@
 import { createRouter, createWebHistory ,createWebHashHistory } from "vue-router";
 import Home from "../views/Home/index.vue";
 import Report from "../views/Report/index.vue";
+import Review from "../views/Review/index.vue";
 
 const routes = [
   { path: "/", component: Home },
   { path: "/report", component: Report },
+  { path: "/review", component: Review },
 ];
 
 const router = createRouter({

+ 457 - 0
src/views/Review/index.vue

@@ -0,0 +1,457 @@
+<template>
+  <div class="review-page">
+    <section class="hero-card">
+      <div class="hero-copy">
+        <h2 class="title">
+          轮询设备消息
+        </h2>
+      </div>
+
+      <div class="toolbar">
+        <el-tag
+          :type="isPolling ? 'success' : 'info'"
+          effect="dark"
+          class="status-tag"
+        >
+          {{ isPolling ? "轮询中" : "已停止" }}
+        </el-tag>
+        <el-button
+          class="action-btn"
+          type="primary"
+          plain
+          :disabled="isPolling"
+          @click="handleStartPolling"
+        >
+          开启轮询
+        </el-button>
+        <el-button
+          class="action-btn"
+          type="danger"
+          plain
+          :disabled="!isPolling"
+          @click="handleStopPolling"
+        >
+          结束轮询
+        </el-button>
+      </div>
+    </section>
+
+    <section class="controls-card">
+      <div class="field-block">
+        <span class="field-label">deviceId</span>
+        <el-input
+          v-model.trim="deviceId"
+          placeholder="请输入 deviceId"
+          clearable
+        />
+      </div>
+    </section>
+
+    <el-alert
+      v-if="errorText"
+      class="status-alert"
+      type="error"
+      :closable="false"
+      :title="errorText"
+      show-icon
+    />
+
+    <section class="records-section">
+      <div class="section-head">
+        <h3>消息列表</h3>
+      </div>
+
+      <div
+        v-if="messages.length"
+        class="record-list"
+      >
+        <article
+          v-for="(message, index) in messages"
+          :key="message.id"
+          class="record-card"
+        >
+          <p class="record-order">
+            第 {{ index + 1 }} 条
+          </p>
+          <pre class="record-payload">{{ message.payloadText }}</pre>
+        </article>
+      </div>
+
+      <el-empty
+        v-else
+        description="点击开启轮询后显示消息"
+      />
+    </section>
+  </div>
+</template>
+
+<script>
+import { ElMessage } from "element-plus";
+import { getDebugMessages } from "@/api.js";
+
+const POLLING_INTERVAL = 500;
+
+export default {
+  name: "ReviewPage",
+  data() {
+    return {
+      deviceId: "sn123",
+      messages: [],
+      isPolling: false,
+      isRequesting: false,
+      pollTimer: null,
+      errorText: "",
+      sequence: 0,
+    };
+  },
+  beforeUnmount() {
+    this.stopPolling();
+  },
+  methods: {
+    async fetchMessages() {
+      if (this.isRequesting) {
+        return;
+      }
+
+      const currentDeviceId = this.deviceId || "sn123";
+      if (!currentDeviceId) {
+        this.errorText = "deviceId 不能为空";
+        return;
+      }
+
+      this.isRequesting = true;
+
+      try {
+        const response = await getDebugMessages(currentDeviceId);
+        const normalizedMessages = this.normalizeMessages(response);
+        this.messages = normalizedMessages;
+        this.errorText = "";
+      } catch (err) {
+        this.errorText = err?.message || "获取调试消息失败";
+      } finally {
+        this.isRequesting = false;
+      }
+    },
+    startPolling() {
+      if (this.isPolling) {
+        return;
+      }
+
+      this.errorText = "";
+      this.isPolling = true;
+      this.fetchMessages();
+      this.pollTimer = setInterval(() => {
+        this.fetchMessages();
+      }, POLLING_INTERVAL);
+    },
+    stopPolling() {
+      if (this.pollTimer) {
+        clearInterval(this.pollTimer);
+        this.pollTimer = null;
+      }
+
+      this.isPolling = false;
+      this.isRequesting = false;
+    },
+    handleStartPolling() {
+      if (!this.deviceId) {
+        ElMessage({
+          message: "请输入 deviceId",
+          type: "warning",
+        });
+        return;
+      }
+
+      this.startPolling();
+      ElMessage({
+        message: "已开启轮询",
+        type: "success",
+      });
+    },
+    handleStopPolling() {
+      this.stopPolling();
+      ElMessage({
+        message: "已结束轮询",
+        type: "info",
+      });
+    },
+    normalizeMessages(response) {
+      const list = this.pickMessageList(response);
+
+      return list.map((item) => {
+        this.sequence += 1;
+        const payload = item?.payload;
+
+        return {
+          id: `${this.sequence}-${Date.now()}`,
+          payloadText: this.formatPayload(payload),
+        };
+      });
+    },
+    pickMessageList(response) {
+      if (Array.isArray(response?.ataMessages)) {
+        return response.ataMessages;
+      }
+
+      if (Array.isArray(response?.dataMessages)) {
+        return response.dataMessages;
+      }
+
+      if (response?.data && Array.isArray(response.data.ataMessages)) {
+        return response.data.ataMessages;
+      }
+
+      if (response?.data && Array.isArray(response.data.dataMessages)) {
+        return response.data.dataMessages;
+      }
+
+      if (
+        response?.data?.data &&
+        Array.isArray(response.data.data.ataMessages)
+      ) {
+        return response.data.data.ataMessages;
+      }
+
+      if (
+        response?.data?.data &&
+        Array.isArray(response.data.data.dataMessages)
+      ) {
+        return response.data.data.dataMessages;
+      }
+
+      if (Array.isArray(response)) {
+        return response;
+      }
+
+      return [];
+    },
+    formatPayload(payload) {
+      if (typeof payload === "string") {
+        return payload;
+      }
+
+      if (payload === undefined) {
+        return "";
+      }
+
+      return JSON.stringify(payload, null, 2);
+    },
+  },
+};
+</script>
+
+<style scoped>
+.review-page {
+  min-height: 100vh;
+  padding: 18px 14px calc(24px + env(safe-area-inset-bottom));
+  background: radial-gradient(
+      circle at top left,
+      rgba(38, 142, 255, 0.18),
+      transparent 34%
+    ),
+    linear-gradient(180deg, #f6fbff 0%, #eef3f9 100%);
+  color: #10233b;
+}
+
+.hero-card,
+.controls-card,
+.record-card {
+  border: 1px solid rgba(16, 35, 59, 0.08);
+  border-radius: 20px;
+  background: rgba(255, 255, 255, 0.9);
+  box-shadow: 0 18px 48px rgba(16, 35, 59, 0.08);
+  backdrop-filter: blur(12px);
+}
+
+.hero-card {
+  display: flex;
+  justify-content: space-between;
+  gap: 16px;
+  padding: 20px;
+  margin-bottom: 14px;
+}
+
+.hero-copy {
+  max-width: 560px;
+}
+
+.eyebrow {
+  margin: 0 0 8px;
+  font-size: 12px;
+  letter-spacing: 0.18em;
+  text-transform: uppercase;
+  color: #2f6fed;
+}
+
+.title {
+  margin: 0;
+  font-size: 28px;
+  line-height: 1.15;
+}
+
+.subtitle {
+  margin: 10px 0 0;
+  color: #53657c;
+  line-height: 1.6;
+}
+
+.toolbar {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 10px;
+  min-width: 220px;
+}
+
+.status-tag {
+  flex-shrink: 0;
+}
+
+.action-btn {
+  min-width: 112px;
+  min-height: 40px;
+}
+
+.controls-card {
+  display: block;
+  padding: 16px;
+  margin-bottom: 14px;
+}
+
+.field-block {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.field-label {
+  font-size: 12px;
+  color: #6a7b90;
+}
+
+.field-block :deep(.el-input__wrapper) {
+  min-height: 42px;
+}
+
+.status-alert {
+  margin-bottom: 14px;
+}
+
+.records-section {
+  padding-bottom: 12px;
+}
+
+.section-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 12px;
+}
+
+.section-head h3 {
+  margin: 0;
+  font-size: 18px;
+}
+
+.section-head span {
+  color: #5f7188;
+  font-size: 13px;
+}
+
+.record-list {
+  display: grid;
+  gap: 12px;
+}
+
+.record-card {
+  padding: 16px;
+}
+
+.record-order {
+  margin: 0 0 10px;
+  font-size: 12px;
+  color: #2f6fed;
+}
+
+.record-payload {
+  margin: 12px 0 0;
+  padding: 14px;
+  border-radius: 14px;
+  background: #0f1d2c;
+  color: #ecf3ff;
+  overflow-x: auto;
+  white-space: pre-wrap;
+  word-break: break-word;
+  overflow-wrap: anywhere;
+  font-size: 12px;
+  line-height: 1.6;
+}
+
+@media (max-width: 767px) {
+  .review-page {
+    padding: 12px 10px calc(18px + env(safe-area-inset-bottom));
+  }
+
+  .hero-card {
+    flex-direction: column;
+    padding: 16px;
+    border-radius: 16px;
+  }
+
+  .toolbar {
+    width: 100%;
+    justify-content: flex-start;
+    display: grid;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
+  .status-tag {
+    grid-column: 1 / -1;
+    justify-self: flex-start;
+  }
+
+  .action-btn {
+    width: 100%;
+    margin-left: 0 !important;
+  }
+
+  .controls-card,
+  .record-card {
+    padding: 14px;
+    border-radius: 16px;
+  }
+
+  .title {
+    font-size: 22px;
+  }
+
+  .section-head h3 {
+    font-size: 16px;
+  }
+
+  .record-order {
+    margin-bottom: 8px;
+  }
+
+  .record-payload {
+    margin-top: 8px;
+    padding: 12px;
+    font-size: 11px;
+    line-height: 1.5;
+    max-height: 52vh;
+  }
+
+  .section-head {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>
+<style>
+* {
+  margin: 0;
+  padding: 0;
+}
+</style>

+ 14 - 10
vue.config.js

@@ -1,3 +1,16 @@
+const proxy = process.env.VUE_APP_API_URL_PREFIX
+  ? {
+      "/ingest": {
+        target: process.env.VUE_APP_API_URL_PREFIX,
+        changeOrigin: true,
+      },
+      "/location": {
+        target: process.env.VUE_APP_API_URL_PREFIX,
+        changeOrigin: true,
+      },
+    }
+  : undefined;
+
 module.exports = {
   publicPath: "./",
   devServer: {
@@ -5,15 +18,6 @@ module.exports = {
       "Cache-Control": "no-store",
     },
     https: false,
-    proxy: {
-      // "ingest": {
-      //   target: process.env.VUE_APP_API_URL_PREFIX,
-      //   changeOrigin: true,
-      // },
-      // "/location": {
-      //   target: process.env.VUE_APP_API_URL_PREFIX,
-      //   changeOrigin: true,
-      // },
-    },
+    ...(proxy ? { proxy } : {}),
   },
 };