From 3f615df738f2e1150fab099c00cfec4e4c43196b Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期一, 26 十月 2026 09:24:45 +0800
Subject: [PATCH] feat(环保门禁): 智能巡检回传详情(AI分析/无人机轨迹结构化展示)、异常报警15秒轮询+弹窗提醒、车辆手机采集加拍照上传与移动端适配

---
 src/views/environmentAccess/intelligentInspectionManagement/index.vue |  152 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 148 insertions(+), 4 deletions(-)

diff --git a/src/views/environmentAccess/intelligentInspectionManagement/index.vue b/src/views/environmentAccess/intelligentInspectionManagement/index.vue
index ada1f9a..214f7b0 100644
--- a/src/views/environmentAccess/intelligentInspectionManagement/index.vue
+++ b/src/views/environmentAccess/intelligentInspectionManagement/index.vue
@@ -61,8 +61,8 @@
           <el-col :span="24"><el-form-item label="宸℃缁撴灉/璁板綍锛�"><el-input v-model="form.inspectionResult" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ贰妫�缁撴灉" /></el-form-item></el-col>
         </el-row>
         <el-row :gutter="30">
-          <el-col :span="12"><el-form-item label="AI瑙嗛鍒嗘瀽缁撴灉锛�"><el-input v-model="form.aiAnalysis" type="textarea" :rows="2" placeholder="棰勭暀锛欰I瑙嗛鍒嗘瀽缁撴灉" /></el-form-item></el-col>
-          <el-col :span="12"><el-form-item label="鏃犱汉鏈�/鏈哄櫒浜烘暟鎹細"><el-input v-model="form.uavData" type="textarea" :rows="2" placeholder="棰勭暀锛氭棤浜烘満/鏈哄櫒浜哄洖浼犳暟鎹�" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="AI瑙嗛鍒嗘瀽缁撴灉锛�"><el-input v-model="form.aiAnalysis" type="textarea" :rows="2" placeholder="璁惧鍥炰紶鍚庤嚜鍔ㄥ啓鍏ワ紱涔熷彲鎵嬪伐褰曞叆" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="鏃犱汉鏈�/鏈哄櫒浜烘暟鎹細"><el-input v-model="form.uavData" type="textarea" :rows="2" placeholder="璁惧鍥炰紶鍚庤嚜鍔ㄥ啓鍏ワ紱涔熷彲鎵嬪伐褰曞叆" /></el-form-item></el-col>
         </el-row>
         <el-row :gutter="30">
           <el-col :span="24"><el-form-item label="宸℃鎶ュ憡锛�"><el-input v-model="form.reportPath" type="textarea" :rows="2" placeholder="鑷姩鐢熸垚/濉啓宸℃鎶ュ憡" /></el-form-item></el-col>
@@ -85,12 +85,78 @@
         <el-button @click="reportVisible = false">鍏抽棴</el-button>
       </template>
     </el-dialog>
+
+    <!-- 宸℃鍥炰紶璇︽儏锛欰I 瑙嗛鍒嗘瀽 + 鏃犱汉鏈�/鏈哄櫒浜哄洖浼犳暟鎹� -->
+    <el-dialog v-model="traceVisible" :title="traceTitle" width="75%" top="6vh">
+      <div v-if="!traceHasData" class="trace-empty">
+        璇ュ贰妫�浠诲姟鏆傛棤璁惧鍥炰紶鏁版嵁銆傛棤浜烘満 / 鏈哄櫒浜� / AI 鎽勫儚澶撮�氳繃鍥炰紶鎺ュ彛涓婃姤鍚庯紝杩欓噷浼氳嚜鍔ㄥ睍绀鸿瘑鍒粨鏋溿�佺幇鍦虹収鐗囦笌杞ㄨ抗銆�
+      </div>
+      <template v-else>
+        <div class="trace-block">
+          <div class="trace-title">鏁版嵁鏉ユ簮</div>
+          <el-descriptions :column="4" border size="small">
+            <el-descriptions-item label="鏉ユ簮绫诲瀷">{{ deviceTypeText(trace.device.deviceType) }}</el-descriptions-item>
+            <el-descriptions-item label="璁惧缂栧彿">{{ trace.device.deviceCode || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="宸℃鍖哄煙">{{ trace.device.area || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="璁惧鐢甸噺">{{ trace.device.battery != null ? trace.device.battery + "%" : "-" }}</el-descriptions-item>
+            <el-descriptions-item label="閲囬泦鏃堕棿">{{ trace.device.collectedAt || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="绯荤粺鎺ユ敹鏃堕棿">{{ trace.device.receivedAt || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="閲囬泦鍧愭爣">{{ coordText }}</el-descriptions-item>
+            <el-descriptions-item label="浣滀笟楂樺害">{{ trace.device.altitude != null ? trace.device.altitude + " m" : "-" }}</el-descriptions-item>
+          </el-descriptions>
+        </div>
+
+        <div class="trace-block">
+          <div class="trace-title">AI 瑙嗛鍒嗘瀽缁撴灉</div>
+          <div class="trace-summary">{{ trace.ai.summary || "鏈鍥炰紶鏈檮甯� AI 缁撹" }}</div>
+          <el-table v-if="aiObjects.length" :data="aiObjects" border size="small">
+            <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+            <el-table-column prop="object" label="璇嗗埆瀵硅薄" min-width="140" show-overflow-tooltip />
+            <el-table-column prop="conclusion" label="璇嗗埆缁撹" min-width="160" show-overflow-tooltip />
+            <el-table-column label="缃俊搴�" width="110" align="center">
+              <template #default="{ row }">{{ row.confidence != null ? (Number(row.confidence) * 100).toFixed(1) + "%" : "-" }}</template>
+            </el-table-column>
+            <el-table-column prop="frameTime" label="鎴抚鏃堕棿" width="170" />
+          </el-table>
+          <div v-else class="trace-sub-empty">鏆傛棤缁撴瀯鍖栬瘑鍒潯鐩�</div>
+        </div>
+
+        <div v-if="tracePhotos.length" class="trace-block">
+          <div class="trace-title">鐜板満閲囬泦鐓х墖</div>
+          <div class="trace-photos">
+            <el-image
+              v-for="(p, i) in tracePhotos"
+              :key="i"
+              :src="p"
+              :preview-src-list="tracePhotos"
+              :initial-index="i"
+              fit="cover"
+              class="trace-photo"
+            />
+          </div>
+        </div>
+
+        <div v-if="traceTrack.length" class="trace-block">
+          <div class="trace-title">璁惧宸℃杞ㄨ抗</div>
+          <el-table :data="traceTrack" border size="small" max-height="200">
+            <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+            <el-table-column prop="t" label="鏃堕棿" width="140" />
+            <el-table-column prop="lng" label="缁忓害" width="140" />
+            <el-table-column prop="lat" label="绾害" width="140" />
+            <el-table-column prop="alt" label="楂樺害(m)" width="110" />
+          </el-table>
+        </div>
+      </template>
+      <template #footer>
+        <el-button @click="traceVisible = false">鍏抽棴</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { Search } from "@element-plus/icons-vue";
-import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue";
+import { ref, reactive, toRefs, onMounted, computed, getCurrentInstance } from "vue";
 import { ElMessageBox } from "element-plus";
 import { listInspection, addInspection, updateInspection, deleteInspection, generateInspectionReport } from "@/api/environmentAccess/inspection.js";
 
@@ -112,8 +178,9 @@
   { label: "鐘舵��", prop: "status", width: 90, dataType: "tag", formatData: (p) => statusText(p), formatType: (p) => statusType(p) },
   { label: "宸℃缁撴灉", prop: "inspectionResult", showOverflowTooltip: true },
   {
-    dataType: "action", label: "鎿嶄綔", align: "center", fixed: "right", width: 100,
+    dataType: "action", label: "鎿嶄綔", align: "center", fixed: "right", width: 200,
     operation: [
+      { name: "鍥炰紶璇︽儏", type: "text", clickFun: (row) => openTrace(row) },
       { name: "缂栬緫", type: "text", clickFun: (row) => openForm("edit", row) },
       { name: "鐢熸垚鎶ュ憡", type: "text", clickFun: (row) => openReport(row) },
     ],
@@ -149,6 +216,47 @@
   a.download = reportTitle.value + ".doc";
   a.click();
   URL.revokeObjectURL(a.href);
+};
+
+/* ---------- 宸℃鍥炰紶璇︽儏锛圓I 瑙嗛鍒嗘瀽 + 鏃犱汉鏈�/鏈哄櫒浜哄洖浼狅級 ---------- */
+const traceVisible = ref(false);
+const traceTitle = ref("宸℃鍥炰紶璇︽儏");
+const trace = ref({ device: {}, ai: {}, photos: [], track: [] });
+const deviceTypeText = (t) => ({ UAV: "鏃犱汉鏈�", ROBOT: "宸℃鏈哄櫒浜�", CAMERA: "AI 鎽勫儚澶�" }[t] || t || "-");
+const safeJson = (txt) => {
+  if (!txt) return null;
+  try {
+    const v = JSON.parse(txt);
+    return v && typeof v === "object" ? v : null;
+  } catch (e) {
+    return null;
+  }
+};
+const traceHasData = computed(() => {
+  const d = trace.value.device || {};
+  const ai = trace.value.ai || {};
+  return !!(d.deviceCode || d.deviceType || ai.summary || (ai.objects && ai.objects.length));
+});
+const aiObjects = computed(() => (trace.value.ai && trace.value.ai.objects) || []);
+const tracePhotos = computed(() => (trace.value.device && trace.value.device.photos) || []);
+const traceTrack = computed(() => (trace.value.device && trace.value.device.track) || []);
+const coordText = computed(() => {
+  const d = trace.value.device || {};
+  if (d.longitude == null || d.latitude == null) return "-";
+  return d.longitude + ", " + d.latitude;
+});
+const openTrace = (row) => {
+  const uav = safeJson(row.uavData);
+  const aiParsed = safeJson(row.aiAnalysis);
+  const ai = aiParsed || { summary: row.aiAnalysis || "", objects: [] };
+  trace.value = {
+    device: uav || {},
+    ai: ai,
+    photos: (uav && uav.photos) || [],
+    track: (uav && uav.track) || [],
+  };
+  traceTitle.value = (row.inspectionName || "鏅鸿兘宸℃") + " - 鍥炰紶璇︽儏";
+  traceVisible.value = true;
 };
 
 const handleQuery = () => { page.current = 1; getList(); };
@@ -192,4 +300,40 @@
 <style scoped lang="scss">
 .ml10 { margin-left: 10px; }
 .table_list { margin-top: unset; }
+.trace-block { margin-bottom: 18px; }
+.trace-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  border-left: 3px solid #409eff;
+  padding-left: 8px;
+  margin-bottom: 10px;
+}
+.trace-summary {
+  font-size: 13px;
+  color: #606266;
+  background: #f5f7fa;
+  border-radius: 4px;
+  padding: 8px 12px;
+  margin-bottom: 10px;
+  line-height: 1.6;
+}
+.trace-sub-empty,
+.trace-empty {
+  color: #909399;
+  font-size: 13px;
+  line-height: 1.8;
+}
+.trace-empty {
+  padding: 24px 8px;
+  text-align: center;
+}
+.trace-photos { display: flex; flex-wrap: wrap; gap: 10px; }
+.trace-photo {
+  width: 132px;
+  height: 96px;
+  border-radius: 4px;
+  border: 1px solid #ebeef5;
+  cursor: pointer;
+}
 </style>

--
Gitblit v1.9.3