From 10e217d5652988d0f6cfe380beb4c5ab39a6b3cd Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期二, 27 十月 2026 15:45:58 +0800
Subject: [PATCH] feat(环保门禁): 智能巡检 AI 视频分析可视化——顶部统计卡片(任务数/AI识别目标/异常数/平均置信度)、列表新增AI分析列(可点击开回传详情)、详情弹窗新增AI截帧图列;异常判定排除否定句避免误判
---
src/views/environmentAccess/intelligentInspectionManagement/index.vue | 215 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 211 insertions(+), 4 deletions(-)
diff --git a/src/views/environmentAccess/intelligentInspectionManagement/index.vue b/src/views/environmentAccess/intelligentInspectionManagement/index.vue
index 214f7b0..560d15b 100644
--- a/src/views/environmentAccess/intelligentInspectionManagement/index.vue
+++ b/src/views/environmentAccess/intelligentInspectionManagement/index.vue
@@ -20,9 +20,45 @@
<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
</div>
</div>
+ <!-- AI 瑙嗛鍒嗘瀽缁熻姒傝 -->
+ <div class="ai-stat-row">
+ <div class="ai-stat-card">
+ <div class="ai-stat-label">宸℃浠诲姟</div>
+ <div class="ai-stat-value">{{ aiStat.total }}</div>
+ </div>
+ <div class="ai-stat-card">
+ <div class="ai-stat-label">宸插畬鎴� AI 鍒嗘瀽</div>
+ <div class="ai-stat-value">{{ aiStat.analyzed }}</div>
+ </div>
+ <div class="ai-stat-card">
+ <div class="ai-stat-label">AI 璇嗗埆鐩爣</div>
+ <div class="ai-stat-value accent">{{ aiStat.objectCount }}<span class="unit">椤�</span></div>
+ </div>
+ <div class="ai-stat-card" :class="{ danger: aiStat.abnormalCount > 0 }">
+ <div class="ai-stat-label">鍙戠幇寮傚父</div>
+ <div class="ai-stat-value">{{ aiStat.abnormalCount }}<span class="unit">澶�</span></div>
+ </div>
+ <div class="ai-stat-card">
+ <div class="ai-stat-label">骞冲潎缃俊搴�</div>
+ <div class="ai-stat-value">{{ aiStat.avgConfidence }}<span class="unit">%</span></div>
+ </div>
+ </div>
+
<div class="table_list">
<PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true"
- @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination"></PIMTable>
+ @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination">
+ <template #aiAnalysis="{ row }">
+ <span v-if="row._ai && row._ai.has" class="ai-cell" title="鐐瑰嚮鏌ョ湅鍥炰紶璇︽儏" @click="openTrace(row)">
+ <el-tag v-if="row._ai.abnormal > 0" type="danger" size="small" effect="dark">
+ {{ row._ai.total }} 椤� 路 {{ row._ai.abnormal }} 寮傚父
+ </el-tag>
+ <el-tag v-else type="success" size="small">
+ {{ row._ai.total }} 椤�
+ </el-tag>
+ </span>
+ <span v-else class="ai-none">鈥� 鏃犲洖浼�</span>
+ </template>
+ </PIMTable>
</div>
<el-dialog v-model="dialogVisible" :title="operationType === 'add' ? '鏂板宸℃' : '缂栬緫宸℃'" width="70%" @close="closeDia">
@@ -117,6 +153,18 @@
<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-column label="AI 鎴抚" width="110" align="center">
+ <template #default="{ row }">
+ <el-image
+ v-if="row.frameImage"
+ :src="row.frameImage"
+ :preview-src-list="[row.frameImage]"
+ fit="cover"
+ class="trace-frame"
+ />
+ <span v-else class="trace-frame-none">寰呭洖浼�</span>
+ </template>
+ </el-table-column>
</el-table>
<div v-else class="trace-sub-empty">鏆傛棤缁撴瀯鍖栬瘑鍒潯鐩�</div>
</div>
@@ -176,6 +224,7 @@
{ label: "璁″垝寮�濮�", prop: "planStartTime", width: 160 },
{ label: "宸℃浜哄憳", prop: "inspector", width: 100 },
{ label: "鐘舵��", prop: "status", width: 90, dataType: "tag", formatData: (p) => statusText(p), formatType: (p) => statusType(p) },
+ { label: "AI 鍒嗘瀽", prop: "aiAnalysis", width: 170, align: "center", dataType: "slot", slot: "aiAnalysis" },
{ label: "宸℃缁撴灉", prop: "inspectionResult", showOverflowTooltip: true },
{
dataType: "action", label: "鎿嶄綔", align: "center", fixed: "right", width: 200,
@@ -259,6 +308,90 @@
traceVisible.value = true;
};
+/* ---------- AI 瑙嗛鍒嗘瀽锛氳В鏋愪笌缁熻 ---------- */
+// 鍒ゅ畾"寮傚父"鐨勫叧閿瘝锛堝悗绔湭鏄惧紡缁� abnormal 鏍囪鏃剁殑鍏滃簳锛�
+const ABNORMAL_WORDS = ["鏈僵鎴�", "鏈寜", "寮傚父", "瓒呮爣", "杩濊", "妯$硦", "瑜壊", "闅句互杈ㄨ", "澶辨晥", "鍋忕Щ", "绉讳綅", "鍗犵敤", "閬楁拻", "娉㈠姩", "婕傜Щ", "涓柇", "鏉惧姩", "娓楁紡", "瓒呮椂", "瓒呴檺", "缂哄け", "鐮存崯"];
+// 鍚﹀畾璇嶏細鍑虹幇杩欎簺璇存槑鏄�"姝e父"鐨勬弿杩帮紝涓嶈兘绠楀紓甯革紙濡�"杩愯骞崇ǔ鏃犲紓甯稿櫔闊�""鏈彂鐜版笚婕�"锛�
+const NEGATIVE_WORDS = ["鏃犲紓甯�", "鏃犲紓鍝�", "鏈彂鐜�", "鏈嚭鐜�", "姝e父", "瀹屽ソ", "绗﹀悎", "鑹ソ", "榻愬叏", "娓呮櫚", "绋冲畾", "鏃犲崱婊�", "杈炬爣", "鏃犳笚婕�"];
+const EMPTY_AI = { has: false, total: 0, abnormal: 0, avgConf: 0, summary: "" };
+
+/**
+ * 瑙f瀽 aiAnalysis 瀛楁锛堝悗绔瓨鐨勬槸 JSON 瀛楃涓诧級
+ * 鍏煎涓夌鎯呭喌锛氣憼 鏍囧噯 JSON 鈶� 绾枃鏈� 鈶� 绌�
+ */
+const parseAi = (raw) => {
+ if (!raw) return { ...EMPTY_AI };
+ let obj = null;
+ try {
+ const v = JSON.parse(raw);
+ obj = v && typeof v === "object" ? v : null;
+ } catch (e) {
+ obj = null;
+ }
+ // 涓嶆槸 JSON 鈫� 褰撶函鏂囨湰澶勭悊锛屾爣璁板凡鍒嗘瀽浣嗘棤缁撴瀯鍖栨潯鐩�
+ if (!obj) {
+ return { has: true, total: 0, abnormal: 0, avgConf: 0, summary: String(raw) };
+ }
+ const list = Array.isArray(obj.objects) ? obj.objects : [];
+ let abnormal = 0;
+ let confSum = 0;
+ let confCnt = 0;
+ list.forEach((it) => {
+ const concl = String(it.conclusion || "");
+ // 鈶� 鍚庣鏄惧紡鏍囪鏈�鍑嗭紝浼樺厛鐢�
+ let isBad;
+ if (it.abnormal === true || it.abnormal === 1 || it.abnormal === "1" || it.abnormal === "true") {
+ isBad = true;
+ } else if (it.abnormal === false || it.abnormal === 0 || it.abnormal === "0" || it.abnormal === "false") {
+ isBad = false;
+ } else {
+ // 鈶� 娌℃湁鏄惧紡鏍囪鏃舵墠璧板叧閿瘝鍏滃簳锛氬懡涓紓甯歌瘝 涓� 涓嶅惈鍚﹀畾璇�
+ const hasNeg = NEGATIVE_WORDS.some((w) => concl.includes(w));
+ isBad = !hasNeg && ABNORMAL_WORDS.some((w) => concl.includes(w));
+ }
+ if (isBad) abnormal++;
+ const c = Number(it.confidence);
+ if (!Number.isNaN(c) && it.confidence !== null && it.confidence !== "") {
+ confSum += c > 1 ? c / 100 : c; // 鍏煎 0.93 鍜� 93 涓ょ鍐欐硶
+ confCnt++;
+ }
+ });
+ return {
+ has: true,
+ total: list.length,
+ abnormal,
+ avgConf: confCnt ? Math.round((confSum / confCnt) * 100) : 0,
+ summary: obj.summary || "",
+ };
+};
+
+const aiStat = ref({ total: 0, analyzed: 0, objectCount: 0, abnormalCount: 0, avgConfidence: 0 });
+const computeAiStat = (rows) => {
+ let analyzed = 0;
+ let objectCount = 0;
+ let abnormalCount = 0;
+ let confSum = 0; // 鎸夋潯鐩姞鏉�
+ let confCnt = 0;
+ rows.forEach((r) => {
+ const a = r._ai;
+ if (!a || !a.has) return;
+ analyzed++;
+ objectCount += a.total;
+ abnormalCount += a.abnormal;
+ if (a.total > 0 && a.avgConf > 0) {
+ confSum += a.avgConf * a.total;
+ confCnt += a.total;
+ }
+ });
+ aiStat.value = {
+ total: rows.length,
+ analyzed,
+ objectCount,
+ abnormalCount,
+ avgConfidence: confCnt ? Math.round(confSum / confCnt) : 0,
+ };
+};
+
const handleQuery = () => { page.current = 1; getList(); };
const pagination = (obj) => { page.current = obj.page; page.size = obj.limit; getList(); };
const handleSelectionChange = (s) => { selectedRows.value = s; };
@@ -266,8 +399,14 @@
tableLoading.value = true;
listInspection({ ...searchForm.value, ...page }).then((res) => {
tableLoading.value = false;
- tableData.value = res.data.records;
- page.total = res.data.total;
+ const rows = (res.data && res.data.records) || [];
+ // 棰勮В鏋� AI 鍒嗘瀽缁撴灉锛岄伩鍏嶆ā鏉块噷閲嶅 JSON.parse
+ rows.forEach((r) => {
+ r._ai = parseAi(r.aiAnalysis);
+ });
+ tableData.value = rows;
+ page.total = (res.data && res.data.total) || 0;
+ computeAiStat(rows);
}).catch(() => { tableLoading.value = false; });
};
const openForm = (type, row) => {
@@ -299,7 +438,64 @@
<style scoped lang="scss">
.ml10 { margin-left: 10px; }
-.table_list { margin-top: unset; }
+.table_list { margin-top: 12px; }
+
+/* ============ AI 瑙嗛鍒嗘瀽缁熻鍗$墖 ============ */
+.ai-stat-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin: 14px 0 4px;
+}
+.ai-stat-card {
+ flex: 1;
+ min-width: 130px;
+ padding: 12px 16px;
+ background: #f7f9fc;
+ border: 1px solid #e4e8ee;
+ border-radius: 6px;
+ box-sizing: border-box;
+
+ &.danger {
+ background: #fef0f0;
+ border-color: #fbc4c4;
+ }
+}
+.ai-stat-label {
+ font-size: 12px;
+ color: #909399;
+ margin-bottom: 6px;
+}
+.ai-stat-value {
+ font-size: 22px;
+ font-weight: 600;
+ color: #303133;
+ line-height: 1.2;
+
+ &.accent {
+ color: #409eff;
+ }
+ .ai-stat-card.danger & {
+ color: #f56c6c;
+ }
+ .unit {
+ font-size: 13px;
+ font-weight: 400;
+ margin-left: 2px;
+ color: #909399;
+ }
+}
+
+/* 鍒楄〃閲岀殑 AI 鍒嗘瀽鍒� */
+.ai-cell {
+ display: inline-block;
+ cursor: pointer;
+}
+.ai-none {
+ font-size: 13px;
+ color: #c0c4cc;
+}
+
.trace-block { margin-bottom: 18px; }
.trace-title {
font-size: 14px;
@@ -336,4 +532,15 @@
border: 1px solid #ebeef5;
cursor: pointer;
}
+.trace-frame {
+ width: 80px;
+ height: 60px;
+ border-radius: 4px;
+ border: 1px solid #ebeef5;
+ cursor: pointer;
+}
+.trace-frame-none {
+ font-size: 12px;
+ color: #c0c4cc;
+}
</style>
--
Gitblit v1.9.3