From 8e8d00477b1f30183d0d09cd7ec744067595dc46 Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期三, 18 三月 2026 17:29:15 +0800
Subject: [PATCH] 2026.3.18

---
 src/views/monitor/DataDashboard.vue |  104 ++++++++++-
 src/assets/offline.png              |    0 
 components.d.ts                     |   24 +-
 src/debug/debugdata.js              |   16 +
 src/assets/exceed.png               |    0 
 src/constants/menu.js               |   13 
 src/views/system/SystemManage.vue   |   58 +++++
 src/components/core/AppAside.vue    |    4 
 src/assets/exception.png            |    0 
 src/utils/map/marks.js              |  279 ++++++++++++++++++++++++++----
 10 files changed, 421 insertions(+), 77 deletions(-)

diff --git a/components.d.ts b/components.d.ts
index 9882412..f866cd1 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -39,7 +39,6 @@
     ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
     ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDialog: typeof import('element-plus/es')['ElDialog']
-    ElDivider: typeof import('element-plus/es')['ElDivider']
     ElDrawer: typeof import('element-plus/es')['ElDrawer']
     ElEmpty: typeof import('element-plus/es')['ElEmpty']
     ElForm: typeof import('element-plus/es')['ElForm']
@@ -47,9 +46,8 @@
     ElHeader: typeof import('element-plus/es')['ElHeader']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
-    ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
     ElInput: typeof import('element-plus/es')['ElInput']
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
+    ElLink: typeof import('element-plus/es')['ElLink']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
@@ -57,12 +55,12 @@
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSpace: typeof import('element-plus/es')['ElSpace']
-    ElStep: typeof import('element-plus/es')['ElStep']
-    ElSteps: typeof import('element-plus/es')['ElSteps']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTable: typeof import('element-plus/es')['ElTable']
@@ -72,8 +70,6 @@
     ElTag: typeof import('element-plus/es')['ElTag']
     ElText: typeof import('element-plus/es')['ElText']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
-    ElTree: typeof import('element-plus/es')['ElTree']
-    ElUpload: typeof import('element-plus/es')['ElUpload']
     FormCol: typeof import('./src/components/layout/FormCol.vue')['default']
     FYBgTaskCard: typeof import('./src/components/bg-task/FYBgTaskCard.vue')['default']
     FYBgTaskDialog: typeof import('./src/components/bg-task/FYBgTaskDialog.vue')['default']
@@ -100,6 +96,8 @@
     FYReconfrimButton: typeof import('./src/components/button/FYReconfrimButton.vue')['default']
     FYSearchBar: typeof import('./src/components/search-option/FYSearchBar.vue')['default']
     FYTable: typeof import('./src/components/table/FYTable.vue')['default']
+    IEpDownload: typeof import('~icons/ep/download')['default']
+    IEpInfoFilled: typeof import('~icons/ep/info-filled')['default']
     ItemDevice: typeof import('./src/components/list-item/ItemDevice.vue')['default']
     ItemMonitorObj: typeof import('./src/components/list-item/ItemMonitorObj.vue')['default']
     ItemScene: typeof import('./src/components/list-item/ItemScene.vue')['default']
@@ -149,7 +147,6 @@
   const ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
   const ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
   const ElDialog: typeof import('element-plus/es')['ElDialog']
-  const ElDivider: typeof import('element-plus/es')['ElDivider']
   const ElDrawer: typeof import('element-plus/es')['ElDrawer']
   const ElEmpty: typeof import('element-plus/es')['ElEmpty']
   const ElForm: typeof import('element-plus/es')['ElForm']
@@ -157,9 +154,8 @@
   const ElHeader: typeof import('element-plus/es')['ElHeader']
   const ElIcon: typeof import('element-plus/es')['ElIcon']
   const ElImage: typeof import('element-plus/es')['ElImage']
-  const ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
   const ElInput: typeof import('element-plus/es')['ElInput']
-  const ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
+  const ElLink: typeof import('element-plus/es')['ElLink']
   const ElMain: typeof import('element-plus/es')['ElMain']
   const ElMenu: typeof import('element-plus/es')['ElMenu']
   const ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
@@ -167,12 +163,12 @@
   const ElOption: typeof import('element-plus/es')['ElOption']
   const ElPagination: typeof import('element-plus/es')['ElPagination']
   const ElPopover: typeof import('element-plus/es')['ElPopover']
+  const ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
+  const ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
   const ElRow: typeof import('element-plus/es')['ElRow']
   const ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
   const ElSelect: typeof import('element-plus/es')['ElSelect']
   const ElSpace: typeof import('element-plus/es')['ElSpace']
-  const ElStep: typeof import('element-plus/es')['ElStep']
-  const ElSteps: typeof import('element-plus/es')['ElSteps']
   const ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
   const ElSwitch: typeof import('element-plus/es')['ElSwitch']
   const ElTable: typeof import('element-plus/es')['ElTable']
@@ -182,8 +178,6 @@
   const ElTag: typeof import('element-plus/es')['ElTag']
   const ElText: typeof import('element-plus/es')['ElText']
   const ElTooltip: typeof import('element-plus/es')['ElTooltip']
-  const ElTree: typeof import('element-plus/es')['ElTree']
-  const ElUpload: typeof import('element-plus/es')['ElUpload']
   const FormCol: typeof import('./src/components/layout/FormCol.vue')['default']
   const FYBgTaskCard: typeof import('./src/components/bg-task/FYBgTaskCard.vue')['default']
   const FYBgTaskDialog: typeof import('./src/components/bg-task/FYBgTaskDialog.vue')['default']
@@ -210,6 +204,8 @@
   const FYReconfrimButton: typeof import('./src/components/button/FYReconfrimButton.vue')['default']
   const FYSearchBar: typeof import('./src/components/search-option/FYSearchBar.vue')['default']
   const FYTable: typeof import('./src/components/table/FYTable.vue')['default']
+  const IEpDownload: typeof import('~icons/ep/download')['default']
+  const IEpInfoFilled: typeof import('~icons/ep/info-filled')['default']
   const ItemDevice: typeof import('./src/components/list-item/ItemDevice.vue')['default']
   const ItemMonitorObj: typeof import('./src/components/list-item/ItemMonitorObj.vue')['default']
   const ItemScene: typeof import('./src/components/list-item/ItemScene.vue')['default']
diff --git a/src/assets/exceed.png b/src/assets/exceed.png
new file mode 100644
index 0000000..a8c2b5f
--- /dev/null
+++ b/src/assets/exceed.png
Binary files differ
diff --git a/src/assets/exception.png b/src/assets/exception.png
new file mode 100644
index 0000000..f5418a1
--- /dev/null
+++ b/src/assets/exception.png
Binary files differ
diff --git a/src/assets/offline.png b/src/assets/offline.png
new file mode 100644
index 0000000..28675e2
--- /dev/null
+++ b/src/assets/offline.png
Binary files differ
diff --git a/src/components/core/AppAside.vue b/src/components/core/AppAside.vue
index e7a1501..b700b9a 100644
--- a/src/components/core/AppAside.vue
+++ b/src/components/core/AppAside.vue
@@ -33,7 +33,7 @@
       <!-- </el-scrollbar> -->
       <!-- 鍟嗘爣 -->
       <el-row ref="subTitleRef" class="sub-title" justify="center">
-        <el-space>{{ collapse ? '' : subTitle }}</el-space>
+        <!-- <el-space>{{ collapse ? '' : subTitle }}</el-space> -->
       </el-row>
     </el-scrollbar>
   </el-aside>
@@ -55,7 +55,7 @@
     return {
       // collapse: false,
       menuHeight: '80vh',
-      title: '娌圭儫鏅鸿兘鐩戞祴涓庣洃绠�',
+      title: '椁愰ギ娌圭儫鏅鸿兘鐩戞祴鐩戠',
       subTitle: '漏涓婃捣椋炵窘鐜繚绉戞妧鏈夐檺鍏徃',
       appIcon: AppIcon,
     }
diff --git a/src/constants/menu.js b/src/constants/menu.js
index 550f076..d13ac9d 100644
--- a/src/constants/menu.js
+++ b/src/constants/menu.js
@@ -96,6 +96,11 @@
     name: '鐜俊鐮�',
     children: [
       {
+        path: '/index/inspection/scene-info',
+        icon: 'solar:shop-2-line-duotone',
+        name: '搴楅摵绠$悊',
+      },
+      {
         path: '/index/analysis/auto-evalution',
         icon: 'solar:pie-chart-3-line-duotone',
         name: '鑷姩璇勪及',
@@ -103,18 +108,14 @@
       {
         path: '/index/analysis/huanxincode-manage',
         icon: 'solar:archive-down-minimlistic-line-duotone',
-        name: '鐜俊鐮�',
+        name: '璇勪及鎺掑悕',
       },
       {
         path: '/index/inspection/report-manage',
         icon: 'solar:folder-favourite-bookmark-line-duotone',
         name: '璇勪及鎶ュ憡',
       },
-      {
-        path: '/index/inspection/scene-info',
-        icon: 'solar:shop-2-line-duotone',
-        name: '搴楅摵绠$悊',
-      },
+
       // {
       //   path: '/index/analysis/data-product',
       //   icon: 'solar:document-add-line-duotone',
diff --git a/src/debug/debugdata.js b/src/debug/debugdata.js
index 6964a64..c500697 100644
--- a/src/debug/debugdata.js
+++ b/src/debug/debugdata.js
@@ -68,6 +68,12 @@
 
   // 鍏堟坊鍔犳寚瀹氱殑搴楅摵
   specifiedShops.forEach((name, index) => {
+    // 闅忔満鐢熸垚鍦ㄧ嚎鐘舵�侊紙80%姒傜巼鍦ㄧ嚎锛�
+    const isOnline = Math.random() < 0.8
+    // 闅忔満鐢熸垚寮傚父鐘舵�侊紙鍙湁鍦ㄧ嚎鏃舵墠鍙兘鏈夊紓甯革級
+    // 0: 娌圭儫娴撳害瓒呮爣, 1: 渚涚數寮傚父, 2: 璁惧鎴栫綉缁滃紓甯�, 3: 鏃犲紓甯�
+    const exceptionStatus = isOnline ? Math.floor(Math.random() * 4) : 2 // 绂荤嚎鏃堕粯璁や负璁惧鎴栫綉缁滃紓甯�
+
     shops.push({
       shop: {
         name: name,
@@ -76,6 +82,8 @@
         longitude: 121.45 + Math.random() * 0.1,
         ringCodeLevel: ringCodeLevels[Math.floor(Math.random() * ringCodeLevels.length)],
         ringCodePublishTime: '2023-03-16 10:00:00',
+        isOnline: isOnline,
+        exceptionStatus: exceptionStatus,
       },
       recentData: generateRecentData(),
     })
@@ -88,6 +96,12 @@
     const suffix = nameSuffixes[Math.floor(Math.random() * nameSuffixes.length)]
     const randomName = `${prefix}${cuisine}${suffix}${i}`
 
+    // 闅忔満鐢熸垚鍦ㄧ嚎鐘舵�侊紙80%姒傜巼鍦ㄧ嚎锛�
+    const isOnline = Math.random() < 0.8
+    // 闅忔満鐢熸垚寮傚父鐘舵�侊紙鍙湁鍦ㄧ嚎鏃舵墠鍙兘鏈夊紓甯革級
+    // 0: 娌圭儫娴撳害瓒呮爣, 1: 渚涚數寮傚父, 2: 璁惧鎴栫綉缁滃紓甯�, 3: 鏃犲紓甯�
+    const exceptionStatus = isOnline ? Math.floor(Math.random() * 4) : 2 // 绂荤嚎鏃堕粯璁や负璁惧鎴栫綉缁滃紓甯�
+
     shops.push({
       shop: {
         name: randomName,
@@ -96,6 +110,8 @@
         longitude: 121.41 + Math.random() * 0.1,
         ringCodeLevel: ringCodeLevels[Math.floor(Math.random() * ringCodeLevels.length)],
         ringCodePublishTime: '2023-03-16 10:00:00',
+        isOnline: isOnline,
+        exceptionStatus: exceptionStatus,
       },
       recentData: generateRecentData(),
     })
diff --git a/src/utils/map/marks.js b/src/utils/map/marks.js
index a8911b6..e5f0327 100644
--- a/src/utils/map/marks.js
+++ b/src/utils/map/marks.js
@@ -6,17 +6,22 @@
 import { useToolboxStore } from '@/stores/toolbox'
 import util from './util'
 import * as echarts from 'echarts'
+import exceedIcon from '@/assets/exceed.png'
+import exceptionIcon from '@/assets/exception.png'
+import offlineIcon from '@/assets/offline.png'
 
 const toolboxStore = useToolboxStore()
 
 var _massMarks = undefined
 
-// 鐜俊鐮佺瓑绾у拰瀵瑰簲棰滆壊
-const ringCodeLevelColors = [
-  '#52c41a', // 缁胯壊
-  '#faad14', // 榛勮壊
-  '#f5222d', // 绾㈣壊
-]
+// 鐘舵�佸浘鏍囬厤缃�
+const statusIcons = {
+  exceed: exceedIcon, // 娌圭儫娴撳害瓒呮爣
+  exception: exceptionIcon, // 渚涚數寮傚父
+  offline: offlineIcon, // 璁惧鎴栫綉缁滃紓甯�
+  online: createCustomMarkerOnline(), // 鍦ㄧ嚎鐘舵��
+  offlineStatus: createCustomMarkerOffline(), // 绂荤嚎鐘舵��
+}
 
 /**
  * 缁樺埗娴烽噺鐐规爣璁�
@@ -28,6 +33,8 @@
  * @param {number} shops[].shop.longitude 缁忓害
  * @param {string} shops[].shop.ringCodeLevel 鏈�鏂扮幆淇$爜绛夌骇
  * @param {string} shops[].shop.ringCodePublishTime 鏈�鏂扮幆淇$爜鍙戝竷鏃堕棿
+ * @param {boolean} shops[].shop.isOnline 鍦ㄧ嚎鐘舵��
+ * @param {number} shops[].shop.exceptionStatus 寮傚父鐘舵�侊紙0: 娌圭儫娴撳害瓒呮爣, 1: 渚涚數寮傚父, 2: 璁惧鎴栫綉缁滃紓甯�, 3: 鏃犲紓甯革級
  * @param {Array} shops[].recentData 杩�1灏忔椂鐨勭洃娴嬫暟鎹�
  * @param {string} shops[].recentData[].sampleTime 鏁版嵁閲囨牱鏃堕棿
  * @param {number} shops[].recentData[].oilSmokeConcentration 娌圭儫娴撳害
@@ -36,21 +43,65 @@
  */
 function drawMassMarks(shops) {
   // 閰嶇疆鏍峰紡
-  const massMarksStyle = ringCodeLevelColors.map((color, index) => ({
-    url: createCustomMarker(color),
-    size: new AMap.Size(20, 20),
-    anchor: new AMap.Pixel(10, 10),
-  }))
+  const massMarksStyle = [
+    {
+      url: statusIcons.exceed,
+      size: new AMap.Size(20, 20),
+      anchor: new AMap.Pixel(10, 10),
+    },
+    {
+      url: statusIcons.exception,
+      size: new AMap.Size(20, 20),
+      anchor: new AMap.Pixel(10, 10),
+    },
+    {
+      url: statusIcons.offline,
+      size: new AMap.Size(20, 20),
+      anchor: new AMap.Pixel(10, 10),
+    },
+    {
+      url: statusIcons.online,
+      size: new AMap.Size(32, 32),
+      anchor: new AMap.Pixel(10, 10),
+    },
+    {
+      url: statusIcons.offlineStatus,
+      size: new AMap.Size(32, 32),
+      anchor: new AMap.Pixel(10, 10),
+    },
+  ]
   // 鍑嗗娴烽噺鐐规暟鎹�
   const massMarksData = shops.map((shop, index) => {
-    // 鏍规嵁鐜俊鐮佺瓑绾ц幏鍙栭鑹�
-    const color = getColorByRingCodeLevel(shop.shop.ringCodeLevel)
+    // 鏍规嵁寮傚父鐘舵�佸拰鍦ㄧ嚎鐘舵�佽幏鍙栨牱寮忕储寮�
+    let styleIndex = 3 // 榛樿鍦ㄧ嚎鐘舵��
+
+    if (shop.shop.exceptionStatus !== undefined) {
+      switch (shop.shop.exceptionStatus) {
+        case 0: // 娌圭儫娴撳害瓒呮爣
+          styleIndex = 0
+          break
+        case 1: // 渚涚數寮傚父
+          styleIndex = 1
+          break
+        case 2: // 璁惧鎴栫綉缁滃紓甯�
+          styleIndex = 2
+          break
+        case 3: // 鏃犲紓甯革紝鏍规嵁鍦ㄧ嚎鐘舵�佸喅瀹�
+          styleIndex = shop.shop.isOnline ? 3 : 4
+          break
+        default:
+          styleIndex = shop.shop.isOnline ? 3 : 4
+      }
+    } else {
+      // 娌℃湁寮傚父鐘舵�佹椂锛屾牴鎹湪绾跨姸鎬佸喅瀹�
+      styleIndex = shop.shop.isOnline ? 3 : 4
+    }
 
     return {
       id: index,
       name: shop.shop.name,
       lnglat: [shop.shop.longitude, shop.shop.latitude],
-      style: shop.shop.ringCodeLevel, // 鏍峰紡绱㈠紩锛屽搴� massMarksStyle
+      style: styleIndex, // 鏍峰紡绱㈠紩锛屽搴� massMarksStyle
       extData: shop, // 瀛樺偍瀹屾暣鐨勫簵閾轰俊鎭�
     }
   })
@@ -134,6 +185,46 @@
 }
 
 /**
+ * 鏍规嵁寮傚父鐘舵�佽幏鍙栦腑鏂�
+ * @param {number} status 寮傚父鐘舵��
+ * @returns {string} 涓枃琛ㄧず
+ */
+function getExceptionStatusText(status) {
+  switch (status + '') {
+    case '0':
+      return '娌圭儫娴撳害瓒呮爣'
+    case '1':
+      return '渚涚數寮傚父'
+    case '2':
+      return '璁惧鎴栫綉缁滃紓甯�'
+    case '3':
+      return '鏃犲紓甯�'
+    default:
+      return '鏈煡鐘舵��'
+  }
+}
+
+/**
+ * 鏍规嵁寮傚父鐘舵�佽幏鍙栭鑹�
+ * @param {number} status 寮傚父鐘舵��
+ * @returns {string} 棰滆壊鍊�
+ */
+function getColorByExceptionStatus(status) {
+  switch (status + '') {
+    case '0':
+      return '#f5222d' // 娌圭儫娴撳害瓒呮爣 - 绾㈣壊
+    case '1':
+      return '#faad14' // 渚涚數寮傚父 - 榛勮壊
+    case '2':
+      return '#8c8c8c' // 璁惧鎴栫綉缁滃紓甯� - 鐏拌壊
+    case '3':
+      return '#52c41a' // 鏃犲紓甯� - 缁胯壊
+    default:
+      return '#8c8c8c' // 鐏拌壊
+  }
+}
+
+/**
  * 鍒涘缓鑷畾涔夋爣璁�
  * @param {string} color 鏍囪棰滆壊
  * @returns {string} 鏍囪鐨凷VG URL
@@ -149,41 +240,151 @@
 }
 
 /**
+ * 鍒涘缓鍦ㄧ嚎鐘舵�佺殑SVG鏍囪锛堟补鐑熺洃娴嬭澶囧舰璞★級
+ * @returns {string} 鏍囪鐨凷VG URL
+ */
+function createCustomMarkerOnline() {
+  const svg = `
+    <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+      <!-- 璁惧涓讳綋 - 鍦嗚鐭╁舰 -->
+      <rect x="5" y="8" width="22" height="16" rx="3" fill="#52c41a" stroke="white" stroke-width="2"/>
+
+      <!-- 璁惧椤堕儴 - 寮у舰 -->
+      <path d="M5 8 Q16 3 27 8" stroke="white" stroke-width="2" fill="#389e0d"/>
+
+      <!-- 璁惧搴曢儴 - 寮у舰 -->
+      <path d="M5 24 Q16 29 27 24" stroke="white" stroke-width="2" fill="#389e0d"/>
+
+      <!-- 璁惧鏄剧ず灞� -->
+      <rect x="8" y="11" width="16" height="10" rx="2" fill="white"/>
+
+      <!-- 鏄剧ず灞忔暟鎹� -->
+      <path d="M11 14 L21 14" stroke="#52c41a" stroke-width="1.5"/>
+      <path d="M11 17 L18 17" stroke="#52c41a" stroke-width="1.5"/>
+      <path d="M11 20 L15 20" stroke="#52c41a" stroke-width="1.5"/>
+
+      <!-- 璁惧澶╃嚎 -->
+      <line x1="16" y1="8" x2="16" y2="3" stroke="white" stroke-width="1.5"/>
+      <circle cx="16" cy="3" r="1.5" fill="white"/>
+
+      <!-- 璁惧鎸囩ず鐏� -->
+      <circle cx="27" cy="16" r="3" fill="#ffffff"/>
+      <circle cx="27" cy="16" r="1.5" fill="#52c41a"/>
+
+      <!-- 瑁呴グ绾挎潯 -->
+      <line x1="5" y1="13" x2="6" y2="13" stroke="white" stroke-width="1.5"/>
+      <line x1="5" y1="19" x2="6" y2="19" stroke="white" stroke-width="1.5"/>
+    </svg>
+  `
+  return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)))
+}
+
+/**
+ * 鍒涘缓绂荤嚎鐘舵�佺殑SVG鏍囪锛堟补鐑熺洃娴嬭澶囧舰璞★級
+ * @returns {string} 鏍囪鐨凷VG URL
+ */
+function createCustomMarkerOffline() {
+  const svg = `
+    <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+      <!-- 璁惧涓讳綋 - 鍦嗚鐭╁舰 -->
+      <rect x="5" y="8" width="22" height="16" rx="3" fill="#8c8c8c" stroke="white" stroke-width="2"/>
+
+      <!-- 璁惧椤堕儴 - 寮у舰 -->
+      <path d="M5 8 Q16 3 27 8" stroke="white" stroke-width="2" fill="#666666"/>
+
+      <!-- 璁惧搴曢儴 - 寮у舰 -->
+      <path d="M5 24 Q16 29 27 24" stroke="white" stroke-width="2" fill="#666666"/>
+
+      <!-- 璁惧鏄剧ず灞� -->
+      <rect x="8" y="11" width="16" height="10" rx="2" fill="white"/>
+
+      <!-- 鏄剧ず灞忔棤鏁版嵁 - 浜ゅ弶绾� -->
+      <line x1="11" y1="12" x2="21" y2="22" stroke="#8c8c8c" stroke-width="2"/>
+      <line x1="11" y1="22" x2="21" y2="12" stroke="#8c8c8c" stroke-width="2"/>
+
+      <!-- 璁惧澶╃嚎 -->
+      <line x1="16" y1="8" x2="16" y2="3" stroke="white" stroke-width="1.5"/>
+      <circle cx="16" cy="3" r="1.5" fill="white"/>
+
+      <!-- 璁惧鎸囩ず鐏� -->
+      <circle cx="27" cy="16" r="3" fill="#ffffff"/>
+      <circle cx="27" cy="16" r="1.5" fill="#8c8c8c"/>
+
+      <!-- 瑁呴グ绾挎潯 -->
+      <line x1="5" y1="13" x2="6" y2="13" stroke="white" stroke-width="1.5"/>
+      <line x1="5" y1="19" x2="6" y2="19" stroke="white" stroke-width="1.5"/>
+    </svg>
+  `
+  return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)))
+}
+
+/**
  * 鏄剧ず搴楅摵淇℃伅绐楀彛
  * @param {Object} shop 搴楅摵瀵硅薄
  */
 function showShopInfoWindow(shop) {
   // 鍑嗗淇℃伅绐楀彛鍐呭
-  // const content = `
-  //   <div style="padding: 10px; max-width: 300px;">
-  //     <h3 style="margin: 0 0 10px 0; color: #333;">${shop.shop.name}</h3>
-  //     <div style="font-size: 14px; line-height: 1.5;">
-  //       <p><strong>鍦板潃锛�</strong>${shop.shop.address}</p>
-  //       <p><strong>鐜俊鐮佺瓑绾э細</strong><span style="color: ${getColorByRingCodeLevel(shop.shop.ringCodeLevel)}">${shop.shop.ringCodeLevel}</span></p>
-  //       <p><strong>鐜俊鐮佸彂甯冩椂闂达細</strong>${shop.shop.ringCodePublishTime}</p>
-  //       <h4 style="margin: 10px 0 5px 0; color: #666;">杩�1灏忔椂鐩戞祴鏁版嵁</h4>
-  //       <div style="max-height: 150px; overflow-y: auto;">
-  //         ${shop.recentData
-  //           .map(
-  //             (item) => `
-  //           <div style="padding: 5px; border-bottom: 1px solid #f0f0f0;">
-  //             <p><strong>閲囨牱鏃堕棿锛�</strong>${item.sampleTime}</p>
-  //             <p><strong>娌圭儫娴撳害锛�</strong>${item.oilSmokeConcentration} mg/m鲁</p>
-  //             <p><strong>鍑�鍖栧櫒鐢垫祦锛�</strong>${item.purifierCurrent} A</p>
-  //             <p><strong>椋庢満鐢垫祦锛�</strong>${item.fanCurrent} A</p>
-  //           </div>
-  //         `,
-  //           )
-  //           .join('')}
-  //       </div>
-  //     </div>
-  //   </div>
-  // `
+  // 鑾峰彇鍦ㄧ嚎鐘舵�佹枃鏈�
+  const onlineStatusText = shop.shop.isOnline ? '鍦ㄧ嚎' : '绂荤嚎'
+  // 鑾峰彇寮傚父鐘舵�佹枃鏈�
+  const exceptionStatusText = getExceptionStatusText(shop.shop.exceptionStatus)
+  // 鑾峰彇寮傚父鐘舵�侀鑹�
+  const exceptionStatusColor = getColorByExceptionStatus(shop.shop.exceptionStatus)
+
+  // 鏍规嵁鐘舵�佽幏鍙栧搴旂殑鍥炬爣
+  let statusIcon = statusIcons.online // 榛樿鍦ㄧ嚎鍥炬爣
+  if (shop.shop.exceptionStatus !== undefined) {
+    switch (shop.shop.exceptionStatus) {
+      case 0:
+        statusIcon = statusIcons.exceed
+        break
+      case 1:
+        statusIcon = statusIcons.exception
+        break
+      case 2:
+        statusIcon = statusIcons.offline
+        break
+      case 3:
+        statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus
+        break
+      default:
+        statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus
+    }
+  } else {
+    statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus
+  }
+
+  // 鏍规嵁鍦ㄧ嚎鐘舵�佽幏鍙栧浘鏍�
+  const onlineIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus
+
+  // 鏍规嵁寮傚父鐘舵�佽幏鍙栧浘鏍�
+  let exceptionIcon = statusIcons.online // 榛樿鍦ㄧ嚎鍥炬爣
+  if (shop.shop.exceptionStatus !== undefined) {
+    switch (shop.shop.exceptionStatus) {
+      case 0:
+        exceptionIcon = statusIcons.exceed
+        break
+      case 1:
+        exceptionIcon = statusIcons.exception
+        break
+      case 2:
+        exceptionIcon = statusIcons.offline
+        break
+      case 3:
+        exceptionIcon = statusIcons.online
+        break
+      default:
+        exceptionIcon = statusIcons.online
+    }
+  }
+
   const content = `
     <div style="padding: 10px; width: 400px;">
       <h3 style="margin: 0 0 10px 0; color: #333;">${shop.shop.name}</h3>
       <div style="font-size: 14px; line-height: 1.5;">
         <p><strong>鍦板潃锛�</strong>${shop.shop.address}</p>
+        <p><strong>鍦ㄧ嚎鐘舵�侊細</strong><span style="color: ${shop.shop.isOnline ? '#52c41a' : '#8c8c8c'}">${onlineStatusText}</span> </p>
+        <p><strong>寮傚父鐘舵�侊細</strong><span style="color: ${exceptionStatusColor}">${exceptionStatusText}</span></p>
         <p><strong>鐜俊鐮佺瓑绾э細</strong><span style="color: ${getColorByRingCodeLevel(shop.shop.ringCodeLevel)}">${getRingCodeLevelText(shop.shop.ringCodeLevel)}</span></p>
         <p><strong>鐜俊鐮佸彂甯冩椂闂达細</strong>${shop.shop.ringCodePublishTime}</p>
         <h4 style="margin: 10px 0 5px 0; color: #666;">杩�1灏忔椂鐩戞祴鏁版嵁</h4>
diff --git a/src/views/monitor/DataDashboard.vue b/src/views/monitor/DataDashboard.vue
index 65cd655..45fa087 100644
--- a/src/views/monitor/DataDashboard.vue
+++ b/src/views/monitor/DataDashboard.vue
@@ -186,10 +186,10 @@
           </div>
         </div>
 
-        <!-- 浠诲姟瀹屾垚鐜� -->
+        <!-- 宸℃煡鐐规 -->
         <div class="metric-card">
           <div class="card-header">
-            <div class="card-title">浠诲姟瀹屾垚鐜�</div>
+            <div class="card-title">宸℃煡鐐规</div>
             <div class="card-icon task-icon">
               <svg
                 width="20"
@@ -215,18 +215,18 @@
               </svg>
             </div>
           </div>
-          <div class="card-value">{{ metrics.taskCompletionRate }}%</div>
+          <div class="card-value">{{ metrics.inspectionPoints }}</div>
           <div class="card-trend">
             <span
               class="trend-arrow"
               :class="{
-                up: metrics.taskCompletionRateTrend > 0,
-                down: metrics.taskCompletionRateTrend < 0,
+                up: metrics.inspectionPointsTrend > 0,
+                down: metrics.inspectionPointsTrend < 0,
               }"
             >
-              {{ metrics.taskCompletionRateTrend > 0 ? '鈫�' : '鈫�' }}
+              {{ metrics.inspectionPointsTrend > 0 ? '鈫�' : '鈫�' }}
             </span>
-            <span class="trend-text">{{ Math.abs(metrics.taskCompletionRateTrend) }}%</span>
+            <span class="trend-text">{{ Math.abs(metrics.inspectionPointsTrend) }}</span>
             <span class="trend-label">{{ getCompareLabel() }}</span>
           </div>
         </div>
@@ -306,6 +306,43 @@
       <!-- 璁惧鐘舵�侀ゼ鍥� -->
       <div class="device-status-chart">
         <canvas id="deviceStatusChart"></canvas>
+      </div>
+    </div>
+
+    <!-- 鍦板浘鍥句緥 -->
+    <div class="map-legend">
+      <div class="legend-header">
+        <h4>鍥句緥</h4>
+      </div>
+      <div class="legend-items">
+        <div class="legend-item">
+          <img src="@/assets/exceed.png" alt="娌圭儫娴撳害瓒呮爣" class="legend-icon" />
+          <span class="legend-text">娌圭儫娴撳害瓒呮爣</span>
+        </div>
+        <div class="legend-item">
+          <img src="@/assets/exception.png" alt="渚涚數寮傚父" class="legend-icon" />
+          <span class="legend-text">渚涚數寮傚父</span>
+        </div>
+        <div class="legend-item">
+          <img src="@/assets/offline.png" alt="璁惧鎴栫綉缁滃紓甯�" class="legend-icon" />
+          <span class="legend-text">璁惧鎴栫綉缁滃紓甯�</span>
+        </div>
+        <div class="legend-item">
+          <img
+            src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSI1IiB5PSI4IiB3aWR0aD0iMjIiIGhlaWdodD0iMTYiIHJ4PSIzIiBmaWxsPSIjNTJjNDFhIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiLz48cGF0aCBkPSJNNSA4IFEgMTYgMyAyNyA4IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiMzODllMGQiLz48cGF0aCBkPSJNNSAyNCBRIDE2IDI5IDI3IDI0IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiM2NjY2NjYiLz48cmVjdCB4PSI4IiB5PSIxMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjEwIiByeD0iMiIgZmlsbD0id2hpdGUiLz48cGF0aCBkPSJNMTIgMTQgTCAyMSAxNCIgc3Ryb2tlPSIjNTJjNDFhIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxwYXRoIGQ9Ik0xMiAxNyBMIDE4IDE3IiBzdHJva2U9IiM1MmM0MWEiIHN0cm9rZS13aWR0aD0iMS41Ii8+PHBhdGggZD0iTTEyIDIwIEwgMTUgMjAiIHN0cm9rZT0iIzUyYzQxYSIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iMTYiIHkxPSI4IiB4Mj0iMTYiIHkyPSIzIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMyIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIvPjxjaXJjbGUgY3g9IjI3IiBjeT0iMTYiIHI9IjMiIGZpbGw9IiNmZmZmZmYiLz48Y2lyY2xlIGN4PSIyNyIgY3k9IjE2IiByPSIxLjUiIGZpbGw9IiM1MmM0MWEiLz48bGluZSB4MT0iNSIgeTE9IjEzIiB4Mj0iNiIgeTI9IjEzIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxsaW5lIHgxPSI1IiB5MT0iMTkiIHgyPSI2IiB5Mj0iMTkiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PC9zdmc+"
+            alt="鍦ㄧ嚎鐘舵��"
+            class="legend-icon"
+          />
+          <span class="legend-text">鍦ㄧ嚎鐘舵��</span>
+        </div>
+        <div class="legend-item">
+          <img
+            src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSI1IiB5PSI4IiB3aWR0aD0iMjIiIGhlaWdodD0iMTYiIHJ4PSIzIiBmaWxsPSIjOGM4YzhjIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiLz48cGF0aCBkPSJNNSA4IFEgMTYgMyAyNyA4IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiM2NjY2NjYiLz48cGF0aCBkPSJNNSAyNCBRIDE2IDI5IDI3IDI0IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiM2NjY2NjYiLz48cmVjdCB4PSI4IiB5PSIxMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjEwIiByeD0iMiIgZmlsbD0id2hpdGUiLz48bGluZSB4MT0iMTEiIHkxPSIxMiIgeDI9IjIxIiB5Mj0iMjIiIHN0cm9rZT0iIzhjOGM4YyIgc3Ryb2tlLXdpZHRoPSIyIi8+PGxpbmUgeDE9IjExIiB5MT0iMjIiIHgyPSIyMSIgeTI9IjEyIiBzdHJva2U9IiM4YzhjOGMiIHN0cm9rZS13aWR0aD0iMiIvPjxsaW5lIHgxPSIxNiIgeTE9IjgiIHgyPSIxNiIgeTI9IjMiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PGNpcmNsZSBjeD0iMTYiIGN5PSIzIiByPSIxLjUiIGZpbGw9IndoaXRlIi8+PGNpcmNsZSBjeD0iMjciIGN5PSIxNiIgcj0iMyIgZmlsbD0iI2ZmZmZmZiIvPjxjaXJjbGUgY3g9IjI3IiBjeT0iMTYiIHI9IjEuNSIgZmlsbD0iIzhjOGM4YyIvPjxsaW5lIHgxPSI1IiB5MT0iMTMiIHgyPSI2IiB5Mj0iMTMiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PGxpbmUgeDE9IjUiIHkxPSIxOSIgeDI9IjYiIHkyPSIxOSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48L3N2Zz4="
+            alt="绂荤嚎鐘舵��"
+            class="legend-icon"
+          />
+          <span class="legend-text">绂荤嚎鐘舵��</span>
+        </div>
       </div>
     </div>
     <!-- 宸℃煡鎯呭喌缁熻鍗$墖 -->
@@ -469,8 +506,8 @@
         onlineRateTrend: 2,
         purifierEfficiency: 85,
         purifierEfficiencyTrend: -3,
-        taskCompletionRate: 78,
-        taskCompletionRateTrend: 10,
+        inspectionPoints: 350,
+        inspectionPointsTrend: 50,
       },
       overview: {
         totalShops: 245,
@@ -601,8 +638,8 @@
           onlineRateTrend: Math.floor(Math.random() * 10) - 5,
           purifierEfficiency: Math.floor(Math.random() * 30) + 70,
           purifierEfficiencyTrend: Math.floor(Math.random() * 10) - 5,
-          taskCompletionRate: Math.floor(Math.random() * 40) + 60,
-          taskCompletionRateTrend: Math.floor(Math.random() * 15) - 7,
+          inspectionPoints: Math.floor(Math.random() * 100) + 300,
+          inspectionPointsTrend: Math.floor(Math.random() * 100) - 50,
         }
 
         // 鏇存柊宸℃煡缁熻鏁版嵁
@@ -1263,6 +1300,51 @@
   animation: blink 1s infinite;
 }
 
+/* 鍦板浘鍥句緥鏍峰紡 */
+.map-legend {
+  position: absolute;
+  bottom: 4px;
+  right: 4px;
+  width: 200px;
+  background-color: rgba(255, 255, 255, 0.9);
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  padding: 16px;
+  z-index: 10;
+}
+
+.legend-header {
+  margin-bottom: 12px;
+}
+
+.legend-header h4 {
+  font-size: 14px;
+  font-weight: 600;
+  color: #262626;
+  margin: 0;
+  text-align: center;
+}
+
+.legend-items {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.legend-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  color: #4e5969;
+}
+
+.legend-icon {
+  width: 24px;
+  height: 24px;
+  object-fit: contain;
+}
+
 /* 鍝嶅簲寮忚璁� */
 /* @media (max-width: 1200px) {
   .top-cards {
diff --git a/src/views/system/SystemManage.vue b/src/views/system/SystemManage.vue
index 188cfc4..c345b68 100644
--- a/src/views/system/SystemManage.vue
+++ b/src/views/system/SystemManage.vue
@@ -202,12 +202,60 @@
 
 // 鐢ㄦ埛绠$悊鐩稿叧
 const users = ref([
-  { id: 1, username: 'admin', name: '绠$悊鍛�', role: 'admin', status: 'active' },
-  { id: 2, username: 'user1', name: '鐢ㄦ埛1', role: 'user', status: 'active' },
   {
-    id: 3,
-    username: 'restaurant1',
-    name: '搴楅摵绠$悊鍛�1',
+    id: 1,
+    username: 'fuxiaojie',
+    name: '浠樺皬濮愬湪鎴愰兘',
+    role: 'restaurant_admin',
+    status: 'active',
+  },
+  { id: 2, username: 'jike', name: '鍚夊埢鑱旂洘', role: 'restaurant_admin', status: 'active' },
+  { id: 3, username: 'jiazaitala', name: '瀹跺湪濉斿暒', role: 'restaurant_admin', status: 'active' },
+  { id: 4, username: 'langlailiao', name: '鐙兼潵浜�', role: 'restaurant_admin', status: 'active' },
+  { id: 5, username: 'lekaisai', name: '涔愬嚡鎾掓槦娓稿簵', role: 'restaurant_admin', status: 'active' },
+  {
+    id: 6,
+    username: 'xinyuan',
+    name: '棣ㄨ繙缇庨灏忛晣锛堝搱灏肩編椋熷箍鍦猴級',
+    role: 'restaurant_admin',
+    status: 'active',
+  },
+  { id: 7, username: 'bangyuehan', name: '妫掔害缈�', role: 'restaurant_admin', status: 'active' },
+  { id: 8, username: 'nangtang', name: '寮勫爞鍜亾', role: 'restaurant_admin', status: 'active' },
+  {
+    id: 9,
+    username: 'yangji',
+    name: '鏉ㄨ榻愰綈鍝堝皵鐑よ倝',
+    role: 'restaurant_admin',
+    status: 'active',
+  },
+  {
+    id: 10,
+    username: 'rensheng',
+    name: '涓婃捣绋斾紶椁愰ギ绠$悊鏈夐檺鍏徃锛堜汉鐢熶竴涓诧級',
+    role: 'restaurant_admin',
+    status: 'active',
+  },
+  { id: 11, username: 'yuanjia', name: '缂樺', role: 'restaurant_admin', status: 'active' },
+  {
+    id: 12,
+    username: 'quansheng',
+    name: '娉夌洓椁愰ギ锛堜笂娴凤級鏈夐檺鍏徃锛堥鍏跺锛�',
+    role: 'restaurant_admin',
+    status: 'active',
+  },
+  { id: 13, username: 'fengmao', name: '涓拌寕鐑や覆', role: 'restaurant_admin', status: 'active' },
+  {
+    id: 14,
+    username: 'taihuang',
+    name: '涓婃捣娉扮厡椁愰ギ绠$悊鏈夐檺鍏徃锛堟嘲鐓岄浮锛�',
+    role: 'restaurant_admin',
+    status: 'active',
+  },
+  {
+    id: 15,
+    username: 'chenxi',
+    name: '寰愭眹鍖鸿景鐔欓棣�(灏忛搧鍚涗覆鐑у眳閰掑眿)',
     role: 'restaurant_admin',
     status: 'active',
   },

--
Gitblit v1.9.3