|
@@ -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>
|