餐饮油烟智能监测与监管一体化平台
feiyu02
6 天以前 ccc970e575ef3f3e5c67af8da210263f4ac549f9
src/views/monitor/DataDashboard.vue
@@ -1,11 +1,26 @@
<template>
  <div class="data-dashboard">
    <!-- 地图模式切换 -->
      <div class="map-mode-card">
        <div class="mode-tab-container">
          <div
            v-for="mode in mapModes"
            :key="mode.value"
            class="mode-tab"
            :class="{ active: activeMode === mode.value }"
            @click="handleModeChange(mode)"
          >
            {{ mode.label }}
          </div>
        </div>
      </div>
    <!-- 顶部指标卡片区 -->
    <div class="top-cards">
      <div class="cards-container">
        <!-- 时间周期选项卡片 -->
        <div class="time-period-card">
          <div class="card-title">时间周期</div>
      <!-- 时间周期选项卡片 -->
      <div class="time-period-card">
        <!-- <div class="card-title">时间选择</div> -->
        <div class="time-controls">
          <div class="time-tab-container">
            <div
              v-for="tab in timeTabs"
@@ -17,12 +32,138 @@
              {{ tab.label }}
            </div>
          </div>
          <div class="time-navigator">
            <button class="nav-btn" @click="navigateTime(-1)" title="上一个周期">
              <svg
                width="16"
                height="16"
                viewBox="0 0 24 24"
                fill="none"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M15 18L9 12L15 6"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
              </svg>
            </button>
            <div class="current-time">{{ currentTimeDisplay }}</div>
            <button class="nav-btn" @click="navigateTime(1)" title="下一个周期">
              <svg
                width="16"
                height="16"
                viewBox="0 0 24 24"
                fill="none"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M9 18L15 12L9 6"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
              </svg>
            </button>
          </div>
        </div>
      </div>
      <div class="cards-container">
        <!-- 设备在线率 -->
        <el-popover placement="right-start" title="设备监控" width="400" trigger="click">
          <div class="popover-content">
            <div class="overview-items-container">
              <div class="overview-item">
                <div class="overview-label">餐饮店铺总数</div>
                <div class="overview-value">{{ overview.totalShops }}</div>
              </div>
        <!-- 超标数 -->
              <div class="overview-item">
                <div class="overview-label">在线设备数</div>
                <div class="overview-value">{{ overview.onlineDevices }}</div>
              </div>
              <div class="overview-item">
                <div class="overview-label">离线设备数</div>
                <div class="overview-value">{{ overview.offlineDevices }}</div>
              </div>
            </div>
            <!-- 设备状态饼图 -->
            <div class="device-status-chart">
              <canvas id="deviceStatusChart"></canvas>
            </div>
          </div>
          <template #reference>
            <div class="metric-card">
              <div class="card-header">
                <div class="card-title">设备在线率</div>
                <div class="card-icon online-icon">
                  <svg
                    width="20"
                    height="20"
                    viewBox="0 0 24 24"
                    fill="none"
                    xmlns="http://www.w3.org/2000/svg"
                  >
                    <path
                      d="M9 12L11 14L15 10"
                      stroke="currentColor"
                      stroke-width="2"
                      stroke-linecap="round"
                      stroke-linejoin="round"
                    />
                    <path
                      d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
                      stroke="currentColor"
                      stroke-width="2"
                      stroke-linecap="round"
                      stroke-linejoin="round"
                    />
                  </svg>
                </div>
              </div>
              <div class="card-value">{{ metrics.onlineRate }}<el-text>%</el-text></div>
              <div class="card-trend">
                <span
                  class="trend-arrow"
                  :class="{ up: metrics.onlineRateTrend > 0, down: metrics.onlineRateTrend < 0 }"
                >
                  {{ metrics.onlineRateTrend > 0 ? '↑' : '↓' }}
                </span>
                <span class="trend-text">{{ Math.abs(metrics.onlineRateTrend) }}%</span>
                <span class="trend-label">{{ getCompareLabel() }}</span>
              </div>
              <div class="view-details">
                <span>详情</span>
                <svg
                  width="12"
                  height="12"
                  viewBox="0 0 24 24"
                  fill="none"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path
                    d="M9 18L15 12L9 6"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  />
                </svg>
              </div>
            </div>
          </template>
        </el-popover>
        <!-- 浓度预警 -->
        <div class="metric-card">
          <div class="card-header">
            <div class="card-title">{{ getPeriodLabel() }}超标数</div>
            <div class="card-title">{{ getPeriodLabel() }}浓度预警</div>
            <div class="card-icon warning-icon">
              <svg
                width="20"
@@ -48,7 +189,7 @@
              </svg>
            </div>
          </div>
          <div class="card-value">{{ metrics.overStandardCount }}</div>
          <div class="card-value">{{ metrics.overStandardCount }}<el-text>次</el-text></div>
          <div class="card-trend">
            <span
              class="trend-arrow"
@@ -61,11 +202,160 @@
          </div>
        </div>
        <!-- 在线率 -->
        <!-- 现场巡查 -->
        <el-popover placement="right-start" title="现场巡查统计" width="350" trigger="click">
          <div class="inspection-popover-content">
            <!-- 巡查量统计 -->
            <div class="inspection-metrics">
              <div class="inspection-metric-item">
                <div class="inspection-metric-label">店铺总计</div>
                <div class="inspection-metric-value">{{ inspectionStats.totalShops }}</div>
              </div>
              <div class="inspection-metric-item">
                <div class="inspection-metric-label">巡查店铺</div>
                <div class="inspection-metric-value">{{ inspectionStats.inspectedShops }}</div>
              </div>
              <div class="inspection-metric-item">
                <div class="inspection-metric-label">巡查点次</div>
                <div class="inspection-metric-value">{{ inspectionStats.inspectionPoints }}</div>
              </div>
              <div class="inspection-metric-item">
                <div class="inspection-metric-label">复查点次</div>
                <div class="inspection-metric-value">{{ inspectionStats.reviewPoints }}</div>
              </div>
            </div>
            <!-- 问题整改情况 -->
            <div class="inspection-chart-container">
              <div class="section-header"><h3>问题整改</h3></div>
              <canvas id="rectificationChart"></canvas>
            </div>
            <!-- 问题审核情况 -->
            <div class="inspection-table-container" style="display: none">
              <div class="section-header"><h3>审核汇总</h3></div>
              <div class="inspection-metric-label">问题审核</div>
              <div class="inspection-table">
                <div class="inspection-metric-item">
                  <div class="inspection-metric-label">无问题</div>
                  <div class="inspection-metric-value">{{ inspectionStats.noProblemShops }}</div>
                </div>
                <div class="inspection-metric-item">
                  <div class="inspection-metric-label">未审核</div>
                  <div class="inspection-metric-value">
                    {{ inspectionStats.unreviewedProblemShops }}
                  </div>
                </div>
                <div class="inspection-metric-item">
                  <div class="inspection-metric-label">部分审核</div>
                  <div class="inspection-metric-value">
                    {{ inspectionStats.partiallyReviewedProblemShops }}
                  </div>
                </div>
                <div class="inspection-metric-item">
                  <div class="inspection-metric-label">全部审核</div>
                  <div class="inspection-metric-value">
                    {{ inspectionStats.fullyReviewedProblemShops }}
                  </div>
                </div>
              </div>
              <div class="inspection-metric-label">整改审核</div>
              <div class="inspection-table">
                <div class="inspection-metric-item">
                  <div class="inspection-metric-label">未整改</div>
                  <div class="inspection-metric-value">{{ inspectionStats.unrectifiedShops }}</div>
                </div>
                <div class="inspection-metric-item">
                  <div class="inspection-metric-label">未审核</div>
                  <div class="inspection-metric-value">
                    {{ inspectionStats.unreviewedRectifiedShops }}
                  </div>
                </div>
                <div class="inspection-metric-item">
                  <div class="inspection-metric-label">部分审核</div>
                  <div class="inspection-metric-value">
                    {{ inspectionStats.partiallyReviewedRectifiedShops }}
                  </div>
                </div>
                <div class="inspection-metric-item">
                  <div class="inspection-metric-label">全部审核</div>
                  <div class="inspection-metric-value">
                    {{ inspectionStats.fullyReviewedRectifiedShops }}
                  </div>
                </div>
              </div>
            </div>
          </div>
          <template #reference>
            <div class="metric-card">
              <div class="card-header">
                <div class="card-title">现场巡查</div>
                <div class="card-icon task-icon">
                  <svg
                    width="20"
                    height="20"
                    viewBox="0 0 24 24"
                    fill="none"
                    xmlns="http://www.w3.org/2000/svg"
                  >
                    <path
                      d="M22 11.08V12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C15.7376 2 19.0503 4.16113 20.7748 7.33007"
                      stroke="currentColor"
                      stroke-width="2"
                      stroke-linecap="round"
                      stroke-linejoin="round"
                    />
                    <path
                      d="M22 4L12 14.01L9 11.01"
                      stroke="currentColor"
                      stroke-width="2"
                      stroke-linecap="round"
                      stroke-linejoin="round"
                    />
                  </svg>
                </div>
              </div>
              <div class="card-value">{{ metrics.inspectionPoints }}<el-text>点次</el-text></div>
              <div class="card-trend">
                <span
                  class="trend-arrow"
                  :class="{
                    up: metrics.inspectionPointsTrend > 0,
                    down: metrics.inspectionPointsTrend < 0,
                  }"
                >
                  {{ metrics.inspectionPointsTrend > 0 ? '↑' : '↓' }}
                </span>
                <span class="trend-text">{{ Math.abs(metrics.inspectionPointsTrend) }}</span>
                <span class="trend-label">{{ getCompareLabel() }}</span>
              </div>
              <div class="view-details">
                <span>详情</span>
                <svg
                  width="12"
                  height="12"
                  viewBox="0 0 24 24"
                  fill="none"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path
                    d="M9 18L15 12L9 6"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  />
                </svg>
              </div>
            </div>
          </template>
        </el-popover>
        <!-- 信访投诉 -->
        <div class="metric-card">
          <div class="card-header">
            <div class="card-title">在线率</div>
            <div class="card-icon online-icon">
            <div class="card-title">信访投诉</div>
            <div class="card-icon warning-icon">
              <svg
                width="20"
                height="20"
@@ -74,14 +364,28 @@
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M9 12L11 14L15 10"
                  d="M21 6H3C2.46957 6 1.96086 6.21071 1.58579 6.58579C1.21071 6.96086 1 7.46957 1 8V18C1 19.1046 1.89543 20 3 20H21C22.1046 20 23 19.1046 23 18V8C23 7.46957 22.7893 6.96086 22.4142 6.58579C22.0391 6.21071 21.5304 6 21 6Z"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
                  d="M8 12H16"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  d="M8 16H16"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  d="M8 8H16"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
@@ -90,23 +394,23 @@
              </svg>
            </div>
          </div>
          <div class="card-value">{{ metrics.onlineRate }}%</div>
          <div class="card-value">{{ metrics.overStandardCount }}<el-text>件</el-text></div>
          <div class="card-trend">
            <span
              class="trend-arrow"
              :class="{ up: metrics.onlineRateTrend > 0, down: metrics.onlineRateTrend < 0 }"
              :class="{ up: metrics.overStandardTrend > 0, down: metrics.overStandardTrend < 0 }"
            >
              {{ metrics.onlineRateTrend > 0 ? '↑' : '↓' }}
              {{ metrics.overStandardTrend > 0 ? '↑' : '↓' }}
            </span>
            <span class="trend-text">{{ Math.abs(metrics.onlineRateTrend) }}%</span>
            <span class="trend-text">{{ Math.abs(metrics.overStandardTrend) }}%</span>
            <span class="trend-label">{{ getCompareLabel() }}</span>
          </div>
        </div>
        <!-- 净化器运行效率 -->
        <!-- 环信码 -->
        <div class="metric-card">
          <div class="card-header">
            <div class="card-title">净化器运行效率</div>
            <div class="card-title">环信码</div>
            <div class="card-icon efficiency-icon">
              <svg
                width="20"
@@ -116,14 +420,21 @@
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2Z"
                  d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  d="M12 6V12L16 14"
                  d="M12 8V16"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  d="M8 12H16"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
@@ -132,7 +443,14 @@
              </svg>
            </div>
          </div>
          <div class="card-value">{{ metrics.purifierEfficiency }}%</div>
          <div class="card-value" style="color: #52c41a">
            <div>{{ metrics.purifierEfficiency }}<el-text>%</el-text></div>
            <div class="card-subvalues">
              <span style="color: #faad14; font-size: 14px">黄码:3%</span>
              <span style="color: #f5222d; font-size: 14px; margin-left: 12px">红码:2%</span>
            </div>
          </div>
          <div class="card-trend">
            <span
              class="trend-arrow"
@@ -148,11 +466,11 @@
          </div>
        </div>
        <!-- 任务完成率 -->
        <!-- 行政处罚 -->
        <div class="metric-card">
          <div class="card-header">
            <div class="card-title">任务完成率</div>
            <div class="card-icon task-icon">
            <div class="card-title">行政处罚</div>
            <div class="card-icon warning-icon">
              <svg
                width="20"
                height="20"
@@ -161,14 +479,28 @@
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M22 11.08V12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C15.7376 2 19.0503 4.16113 20.7748 7.33007"
                  d="M12 15C15.866 15 19 11.866 19 8C19 4.13401 15.866 1 12 1C8.13401 1 5 4.13401 5 8C5 11.866 8.13401 15 12 15Z"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  d="M22 4L12 14.01L9 11.01"
                  d="M12 15C12 15 15 21 15 21H9C9 21 12 15 12 15Z"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  d="M11 8H13"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  d="M11 11H13"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
@@ -177,18 +509,15 @@
              </svg>
            </div>
          </div>
          <div class="card-value">{{ metrics.taskCompletionRate }}%</div>
          <div class="card-value">{{ metrics.overStandardCount }}<el-text>次</el-text></div>
          <div class="card-trend">
            <span
              class="trend-arrow"
              :class="{
                up: metrics.taskCompletionRateTrend > 0,
                down: metrics.taskCompletionRateTrend < 0,
              }"
              :class="{ up: metrics.overStandardTrend > 0, down: metrics.overStandardTrend < 0 }"
            >
              {{ metrics.taskCompletionRateTrend > 0 ? '↑' : '↓' }}
              {{ metrics.overStandardTrend > 0 ? '↑' : '↓' }}
            </span>
            <span class="trend-text">{{ Math.abs(metrics.taskCompletionRateTrend) }}%</span>
            <span class="trend-text">{{ Math.abs(metrics.overStandardTrend) }}%</span>
            <span class="trend-label">{{ getCompareLabel() }}</span>
          </div>
        </div>
@@ -200,74 +529,67 @@
      <!-- 中部GIS地图区 -->
      <div class="map-section">
        <div id="map" class="map-container">
          <BaseMap></BaseMap>
          <BaseMap :showSatellite="false"></BaseMap>
        </div>
        <!-- 地图点位弹窗 -->
        <el-dialog v-model="dialogVisible" title="企业实时数据" width="400px">
          <div class="dialog-content">
            <el-descriptions :column="1" border>
              <el-descriptions-item label="企业名称">{{
                selectedPoint.enterpriseName
              }}</el-descriptions-item>
              <el-descriptions-item label="设备编号">{{
                selectedPoint.deviceId
              }}</el-descriptions-item>
              <el-descriptions-item label="油烟浓度"
                >{{ selectedPoint.oilSmokeConcentration }} mg/m³</el-descriptions-item
              >
              <el-descriptions-item label="颗粒物"
                >{{ selectedPoint.particulateMatter }} mg/m³</el-descriptions-item
              >
              <el-descriptions-item label="非甲烷总烃"
                >{{ selectedPoint.nonMethaneHydrocarbon }} mg/m³</el-descriptions-item
              >
              <el-descriptions-item label="监测时间">{{
                selectedPoint.monitoringTime
              }}</el-descriptions-item>
              <el-descriptions-item label="超标情况">
                <el-tag :type="selectedPoint.isOverStandard ? 'danger' : 'success'">
                  {{ selectedPoint.isOverStandard ? '超标' : '正常' }}
                </el-tag>
              </el-descriptions-item>
            </el-descriptions>
          </div>
          <template #footer>
            <span class="dialog-footer">
              <el-button @click="dialogVisible = false">关闭</el-button>
              <el-button type="primary" @click="viewDetails">查看详情</el-button>
            </span>
          </template>
        </el-dialog>
      </div>
    </div>
      <!-- 右侧实时监测总览区 -->
      <div class="overview-section">
        <div class="section-header">
          <h3>实时监测总览</h3>
          <span class="view-more">查看更多</span>
    <!--  -->
    <div class="monitor-control-container">
      <el-button size="large" @click="toggleMonitorControl" class="push-btn">
        <div style="display: flex; flex-direction: column">
          <el-icon>
            <ArrowRight v-if="isMonitorControlExpanded" />
            <ArrowLeft v-else />
          </el-icon>
          <!-- <div>现</div>
          <div>场</div>
          <div>巡</div>
          <div>查</div> -->
        </div>
      </el-button>
      <MonitorControl
        v-if="isMonitorControlExpanded"
        :class="{ 'monitor-control': true, collapsed: !isMonitorControlExpanded }"
        style="height: calc(90vh - 40px)"
      />
    </div>
        <div class="overview-items-container">
          <div class="overview-item">
            <div class="overview-label">餐饮店铺总数</div>
            <div class="overview-value">{{ overview.totalShops }}</div>
          </div>
          <div class="overview-item">
            <div class="overview-label">在线设备数</div>
            <div class="overview-value">{{ overview.onlineDevices }}</div>
          </div>
          <div class="overview-item">
            <div class="overview-label">离线设备数</div>
            <div class="overview-value">{{ overview.offlineDevices }}</div>
          </div>
    <!-- 地图图例 -->
    <div class="map-legend">
      <div class="legend-header">
        <h4>图例</h4>
      </div>
      <div class="legend-items">
        <!-- 污染态势模式下显示的图例 -->
        <div v-if="activeMode === 'pollution'" class="legend-item">
          <img src="@/assets/exceed.png" alt="油烟浓度超标" class="legend-icon" />
          <span class="legend-text">油烟浓度超标</span>
        </div>
        <!-- 设备状态饼图 -->
        <div class="device-status-chart">
          <canvas id="deviceStatusChart"></canvas>
        <div v-if="activeMode === 'pollution'" class="legend-item">
          <img src="@/assets/exception.png" alt="供电异常" class="legend-icon" />
          <span class="legend-text">供电异常</span>
        </div>
        <div v-if="activeMode === 'pollution'" class="legend-item">
          <img src="@/assets/offline.png" alt="设备或网络异常" class="legend-icon" />
          <span class="legend-text">设备或网络异常</span>
        </div>
        <!-- 设备状态模式下也显示在线状态 -->
        <div v-if="activeMode === 'device'" class="legend-item">
          <img
            src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSI1IiB5PSI4IiB3aWR0aD0iMjIiIGhlaWdodD0iMTYiIHJ4PSIzIiBmaWxsPSIjNTJjNDFhIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiLz48cGF0aCBkPSJNNSA4IFEgMTYgMyAyNyA4IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiMzODllMGQiLz48cGF0aCBkPSJNNSAyNCBRIDE2IDI5IDI3IDI0IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiM2NjY2NjYiLz48cmVjdCB4PSI4IiB5PSIxMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjEwIiByeD0iMiIgZmlsbD0id2hpdGUiLz48cGF0aCBkPSJNMTIgMTQgTCAyMSAxNCIgc3Ryb2tlPSIjNTJjNDFhIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxwYXRoIGQ9Ik0xMiAxNyBMIDE4IDE3IiBzdHJva2U9IiM1MmM0MWEiIHN0cm9rZS13aWR0aD0iMS41Ii8+PHBhdGggZD0iTTEyIDIwIEwgMTUgMjAiIHN0cm9rZT0iIzUyYzQxYSIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iMTYiIHkxPSI4IiB4Mj0iMTYiIHkyPSIzIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMyIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIvPjxjaXJjbGUgY3g9IjI3IiBjeT0iMTYiIHI9IjMiIGZpbGw9IiNmZmZmZmYiLz48Y2lyY2xlIGN4PSIyNyIgY3k9IjE2IiByPSIxLjUiIGZpbGw9IiM1MmM0MWEiLz48bGluZSB4MT0iNSIgeTE9IjEzIiB4Mj0iNiIgeTI9IjEzIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxsaW5lIHgxPSI1IiB5MT0iMTkiIHgyPSI2IiB5Mj0iMTkiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PC9zdmc+"
            alt="在线状态"
            class="legend-icon"
          />
          <span class="legend-text">在线状态</span>
        </div>
        <div v-if="activeMode === 'device'" 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>
@@ -276,20 +598,31 @@
<script>
import * as echarts from 'echarts'
import { onMapMounted, map, AMap } from '@/utils/map/index'
import { onMapMounted, satellite } from '@/utils/map/index'
import districtSearch from '@/utils/map/districtsearch.js'
import marks from '@/utils/map/marks.js'
import { generateTestShops } from '@/debug/debugdata'
import MonitorControl from '@/views/inspection/MonitorControl.vue'
export default {
  name: 'DataDashboard',
  components: {
    MonitorControl,
  },
  data() {
    return {
      activeTime: 'day',
      activeMode: 'pollution', // 默认污染态势模式
      currentDate: new Date('2025-08-01'),
      timeTabs: [
        { label: '日', value: 'day' },
        { label: '周', value: 'week' },
        { label: '月', value: 'month' },
      ],
      dialogVisible: false,
      mapModes: [
        { label: '污染态势', value: 'pollution' },
        { label: '设备状态', value: 'device' },
      ],
      selectedPoint: {
        enterpriseName: '',
        deviceId: '',
@@ -304,24 +637,66 @@
        overStandardTrend: 5,
        onlineRate: 92,
        onlineRateTrend: 2,
        purifierEfficiency: 85,
        purifierEfficiencyTrend: -3,
        taskCompletionRate: 78,
        taskCompletionRateTrend: 10,
        purifierEfficiency: 95,
        purifierEfficiencyTrend: 2,
        inspectionPoints: 350,
        inspectionPointsTrend: 50,
      },
      overview: {
        totalShops: 245,
        onlineDevices: 220,
        offlineDevices: 25,
      },
      inspectionStats: {
        // 巡查量
        totalShops: 245,
        inspectedShops: 180,
        inspectionPoints: 350,
        reviewPoints: 80,
        // 问题整改情况
        problemCount: 45,
        rectifiedCount: 38,
        rectificationRate: 84.4,
        // 问题审核情况
        noProblemShops: 135,
        unreviewedProblemShops: 8,
        partiallyReviewedProblemShops: 5,
        fullyReviewedProblemShops: 32,
        unreviewedRectifiedShops: 3,
        partiallyReviewedRectifiedShops: 2,
        fullyReviewedRectifiedShops: 33,
        unrectifiedShops: 7,
      },
      map: null,
      refreshTimer: null,
      isMonitorControlExpanded: true,
    }
  },
  computed: {
    currentTimeDisplay() {
      const date = this.currentDate
      let weekStart = new Date(date)
      let weekEnd = new Date(date)
      switch (this.activeTime) {
        case 'day':
          return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
        case 'week':
          // 简单计算周显示,实际项目中可能需要更复杂的周计算逻辑
          weekStart.setDate(date.getDate() - date.getDay() + 1)
          weekEnd.setDate(date.getDate() + (7 - date.getDay()))
          return `${weekStart.getFullYear()}-${String(weekStart.getMonth() + 1).padStart(2, '0')}-${String(weekStart.getDate()).padStart(2, '0')} ~ ${weekEnd.getFullYear()}-${String(weekEnd.getMonth() + 1).padStart(2, '0')}-${String(weekEnd.getDate()).padStart(2, '0')}`
        case 'month':
          return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
        default:
          return ''
      }
    },
  },
  mounted() {
    this.initMap()
    this.initDeviceStatusChart()
    this.startAutoRefresh()
    this.initRectificationChart()
    // this.startAutoRefresh()
  },
  beforeUnmount() {
    if (this.refreshTimer) {
@@ -329,21 +704,55 @@
    }
  },
  methods: {
    toggleMonitorControl() {
      this.isMonitorControlExpanded = !this.isMonitorControlExpanded
    },
    handleTimeChange(tab) {
      this.activeTime = tab.value
      // 模拟切换时间周期后的数据更新
      this.updateMetrics()
    },
    getPeriodLabel() {
    handleModeChange(mode) {
      this.activeMode = mode.value
    },
    navigateTime(direction) {
      const newDate = new Date(this.currentDate)
      switch (this.activeTime) {
        case 'day':
          return '今日'
          newDate.setDate(newDate.getDate() + direction)
          break
        case 'week':
          return '本周'
          newDate.setDate(newDate.getDate() + direction * 7)
          break
        case 'month':
          return '本月'
          newDate.setMonth(newDate.getMonth() + direction)
          break
      }
      this.currentDate = newDate
      // 模拟切换时间后的数据更新
      this.updateMetrics()
    },
    getPeriodLabel() {
      const today = new Date()
      const isToday =
        this.activeTime === 'day' &&
        this.currentDate.getDate() === today.getDate() &&
        this.currentDate.getMonth() === today.getMonth() &&
        this.currentDate.getFullYear() === today.getFullYear()
      if (isToday) {
        return '今日'
      }
      switch (this.activeTime) {
        case 'day':
          return '当日'
        case 'week':
          return '当周'
        case 'month':
          return '当月'
        default:
          return '今日'
          return '当日'
      }
    },
    getCompareLabel() {
@@ -362,24 +771,63 @@
      // 这里应该根据选择的时间周期从接口获取数据
      // 模拟数据更新
      setTimeout(() => {
        const m = Math.floor(Math.random() * 50) + 150
        this.overview = {
          totalShops: 245,
          onlineDevices: m,
          offlineDevices: 245 - m,
        }
        this.metrics = {
          overStandardCount: Math.floor(Math.random() * 30),
          overStandardTrend: Math.floor(Math.random() * 20) - 10,
          onlineRate: Math.floor(Math.random() * 20) + 80,
          onlineRate: ((this.overview.onlineDevices / this.overview.totalShops) * 100).toFixed(0),
          onlineRateTrend: Math.floor(Math.random() * 10) - 5,
          purifierEfficiency: Math.floor(Math.random() * 30) + 70,
          purifierEfficiency: Math.floor(Math.random() * 20) + 80,
          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,
        }
        // 更新巡查统计数据
        this.inspectionStats = {
          totalShops: 245,
          inspectedShops: Math.floor(Math.random() * 50) + 150,
          inspectionPoints: Math.floor(Math.random() * 100) + 300,
          reviewPoints: Math.floor(Math.random() * 50) + 50,
          problemCount: Math.floor(Math.random() * 30) + 20,
          rectifiedCount: Math.floor(Math.random() * 25) + 15,
          rectificationRate: Math.round((Math.random() * 30 + 70) * 10) / 10,
          noProblemShops: Math.floor(Math.random() * 50) + 100,
          unreviewedProblemShops: Math.floor(Math.random() * 10),
          partiallyReviewedProblemShops: Math.floor(Math.random() * 8),
          fullyReviewedProblemShops: Math.floor(Math.random() * 20) + 15,
          unreviewedRectifiedShops: Math.floor(Math.random() * 5),
          partiallyReviewedRectifiedShops: Math.floor(Math.random() * 5),
          fullyReviewedRectifiedShops: Math.floor(Math.random() * 20) + 15,
          unrectifiedShops: Math.floor(Math.random() * 10),
        }
        // 更新图表
        this.initDeviceStatusChart()
        this.initRectificationChart()
      }, 300)
    },
    initMap() {
      // setTimeout(() => {
      districtSearch.removeDistrict()
      districtSearch.drawDistrict('上海市')
      districtSearch.drawDistrictMask('上海市')
      // districtSearch.districtLayer('310106')
      // }, 2000)
      onMapMounted(() => {
        setTimeout(() => {
          marks.clearMassMarks()
          const shops = generateTestShops()
          console.log(shops)
          marks.drawMassMarks(shops)
        }, 2000)
      })
    },
    initDeviceStatusChart() {
      const chartDom = document.getElementById('deviceStatusChart')
@@ -452,9 +900,84 @@
        chart.setOption(option)
        // 响应式调整
        window.addEventListener('resize', () => {
          chart.resize()
        })
        // window.addEventListener('resize', () => {
        //   chart.resize()
        // })
      }
    },
    initRectificationChart() {
      const chartDom = document.getElementById('rectificationChart')
      if (chartDom) {
        const chart = echarts.init(chartDom)
        const option = {
          tooltip: {
            trigger: 'axis',
            axisPointer: {
              type: 'shadow',
            },
            backgroundColor: 'rgba(255, 255, 255, 0.95)',
            borderColor: '#e8e8e8',
            borderWidth: 1,
            textStyle: {
              color: '#333',
            },
          },
          grid: {
            left: '3%',
            right: '4%',
            bottom: '3%',
            top: '5%',
            containLabel: true,
          },
          xAxis: {
            type: 'category',
            data: ['问题数', '整改数'],
            axisLabel: {
              color: '#86909c',
              fontSize: 12,
            },
          },
          yAxis: {
            type: 'value',
            axisLabel: {
              color: '#86909c',
              fontSize: 12,
            },
          },
          series: [
            {
              name: '数量',
              type: 'bar',
              data: [
                {
                  value: this.inspectionStats.problemCount,
                  itemStyle: {
                    color: '#fa8c16',
                  },
                },
                {
                  value: this.inspectionStats.rectifiedCount,
                  itemStyle: {
                    color: '#52c41a',
                  },
                },
              ],
              barWidth: '60%',
              label: {
                show: true,
                position: 'top',
                color: '#262626',
                fontSize: 12,
              },
            },
          ],
        }
        chart.setOption(option)
        // 响应式调整
        // window.addEventListener('resize', () => {
        //   chart.resize()
        // })
      }
    },
    startAutoRefresh() {
@@ -476,7 +999,7 @@
/* 全局样式 */
.data-dashboard {
  width: 100%;
  height: calc(100vh - 60px);
  height: calc(100vh - 70px);
  background-color: #f5f7fa;
  color: #333;
  box-sizing: border-box;
@@ -488,32 +1011,115 @@
/* 顶部指标卡片区 */
.top-cards {
  position: absolute;
  top: 24px;
  left: 24px;
  top: 4px;
  left: 4px;
  z-index: 10;
  margin-bottom: 24px;
}
.cards-container {
  display: grid;
  grid-template-columns: 280px;
  grid-template-rows: auto repeat(4, auto);
  gap: 16px;
  background-color: rgba(255, 255, 255, 0.9);
  padding: 16px;
  grid-template-columns: repeat(2, 180px);
  grid-template-rows: auto repeat(2, auto);
  gap: 8px;
  /* background-color: rgba(255, 255, 255, 0.9); */
  /* padding: 16px; */
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
}
/* 监控控制卡片 */
.monitor-control {
  /* position: absolute; */
  width: 400px;
  transition: all 0.3s ease;
  /* top: 0px; */
  /* right: 0px; */
  /* z-index: 10; */
}
.push-btn {
  z-index: 1;
  width: 2.5rem;
  height: 40px;
  margin: initial;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* background-color: white; */
  /* border-color: white; */
  /* border-top: 1px solid;
  border-left: 1px solid;
  border-bottom: 1px solid; */
  border-top-right-radius: 0px;
  border-bottom-right-radius: 0px;
  /* box-shadow: var(--el-box-shadow-light); */
}
/* 时间周期卡片 */
.time-period-card {
  background-color: #ffffff;
  border-radius: 8px;
  padding: 20px;
  padding: 4px 16px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-bottom: 8px;
  min-width: 300px;
}
/* 地图模式卡片 */
.map-mode-card {
  position: absolute;
  top: 4px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  background-color: #ffffff;
  border-radius: 8px;
  padding: 4px 16px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-bottom: 8px;
  min-width: 300px;
}
.mode-tab-container {
  display: flex;
  flex-direction: row;
  gap: 8px;
  width: 100%;
  justify-content: center;
}
.mode-tab {
  padding: 4px 8px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.3s ease;
  color: #4e5969;
  text-align: center;
  border: 1px solid #e8e8e8;
  background-color: #fafafa;
  flex: 1;
}
.mode-tab.active {
  background-color: #1890ff;
  color: #ffffff;
  border-color: #1890ff;
  box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
}
.mode-tab:hover:not(.active) {
  color: #1890ff;
  border-color: #e6f7ff;
  background-color: #e6f7ff;
}
.time-period-card .card-title {
@@ -522,6 +1128,12 @@
  font-weight: 500;
  margin-bottom: 16px;
  text-align: center;
}
.time-controls {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.time-tab-container {
@@ -533,7 +1145,7 @@
}
.time-tab {
  padding: 2px 4px;
  padding: 4px 8px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
@@ -543,6 +1155,7 @@
  text-align: center;
  border: 1px solid #e8e8e8;
  background-color: #fafafa;
  flex: 1;
}
.time-tab.active {
@@ -556,6 +1169,43 @@
  color: #1890ff;
  border-color: #e6f7ff;
  background-color: #e6f7ff;
}
.time-navigator {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 8px 0;
}
.nav-btn {
  width: 32px;
  height: 32px;
  border: 1px solid #e8e8e8;
  background-color: #fafafa;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.3s ease;
  color: #4e5969;
}
.nav-btn:hover {
  border-color: #1890ff;
  color: #1890ff;
  background-color: #e6f7ff;
}
.current-time {
  font-size: 14px;
  font-weight: 500;
  color: #262626;
  min-width: 180px;
  text-align: center;
  padding: 0 12px;
}
/* 指标卡片 */
@@ -572,6 +1222,7 @@
.metric-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  cursor: pointer;
}
.card-header {
@@ -603,7 +1254,7 @@
}
.efficiency-icon {
  color: #722ed1;
  color: #52c41a;
}
.task-icon {
@@ -616,6 +1267,16 @@
  margin: 12px 0;
  color: #262626;
  line-height: 1.2;
  /* display: flex;
  justify-content: space-between;
  align-items: flex-end; */
}
.card-subvalues {
  display: flex;
  align-items: center;
  margin: 8px 0;
  font-weight: 500;
}
.card-trend {
@@ -712,28 +1373,17 @@
}
/* 右侧实时监测总览区 */
.overview-section {
  position: absolute;
  top: 200px;
  right: 24px;
  width: 320px;
  background-color: rgba(255, 255, 255, 0.9);
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 20px;
  display: flex;
  flex-direction: column;
  z-index: 10;
  max-height: calc(100vh - 220px);
.popover-content {
  padding: 10px;
}
.overview-items-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
  padding-bottom: 16px;
  border-bottom: 1px solid #f0f0f0;
  margin-bottom: 20px;
}
.overview-item {
@@ -759,11 +1409,176 @@
.device-status-chart {
  flex: 1;
  margin-top: 16px;
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 16px;
}
.view-details {
  position: absolute;
  bottom: 12px;
  right: 16px;
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: #1890ff;
  cursor: pointer;
}
.view-details:hover {
  text-decoration: underline;
}
.overview-items-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 16px;
  border-bottom: 1px solid #f0f0f0;
}
.overview-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
  text-align: center;
}
.overview-label {
  font-size: 12px;
  color: #86909c;
  font-weight: 500;
  margin-bottom: 8px;
}
.overview-value {
  font-size: 24px;
  font-weight: bold;
  color: #262626;
}
/* 巡查情况统计 */
.inspection-popover-content {
  padding: 10px;
  max-height: 400px;
  overflow-y: auto;
}
.monitor-control-container {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 10;
  transition: all 0.3s ease;
  /* background-color: rgba(255, 255, 255, 0.9); */
  display: flex;
  border-radius: 8px;
  /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
  overflow: hidden;
}
.monitor-control-container.collapsed {
  width: 60px;
}
.monitor-control-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 15px;
  border-bottom: 1px solid #e8e8e8;
  height: 40px;
  position: relative;
}
.monitor-control-header span {
  font-weight: 600;
  color: #333;
  writing-mode: vertical-rl;
  text-orientation: mixed;
  letter-spacing: 2px;
  white-space: nowrap;
}
.collapse-btn {
  /* transform: translateY(-50%); */
}
.inspection-metrics {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 20px;
}
.inspection-metric-item {
  background-color: #fafafa;
  border-radius: 6px;
  padding: 12px;
  text-align: center;
}
.inspection-metric-label {
  font-size: 12px;
  color: #86909c;
  margin-bottom: 4px;
}
.inspection-metric-value {
  font-size: 18px;
  font-weight: bold;
  color: #262626;
}
.inspection-chart-container {
  margin-bottom: 20px;
}
.chart-title {
  font-size: 14px;
  font-weight: 500;
  color: #262626;
  margin-bottom: 12px;
}
.inspection-table-container {
  /* max-height: 200px; */
  /* overflow-y: auto; */
}
.inspection-table {
  /* width: 100%;
  border-collapse: collapse; */
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
  margin-bottom: 20px;
}
.table-row {
  display: flex;
  border-bottom: 1px solid #f0f0f0;
  padding: 8px 0;
}
.table-row:last-child {
  border-bottom: none;
}
.table-cell {
  flex: 1;
  font-size: 12px;
  color: #4e5969;
}
.table-cell.value {
  font-weight: 500;
  color: #262626;
  text-align: right;
}
/* 弹窗样式 */
@@ -790,8 +1605,56 @@
  animation: blink 1s infinite;
}
/* 地图图例样式 */
.map-legend {
  position: absolute;
  display: flex;
  align-items: center;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  /* 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: 4px;
  z-index: 10;
}
.legend-header {
  margin-right: 12px;
}
.legend-header h4 {
  font-size: 14px;
  font-weight: 600;
  color: #262626;
  margin: 0;
  text-align: center;
}
.legend-items {
  display: flex;
  flex-direction: row;
  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) {
/* @media (max-width: 1200px) {
  .top-cards {
    position: relative;
    margin-bottom: 24px;
@@ -817,9 +1680,9 @@
    margin-top: 16px;
    background-color: #ffffff;
  }
}
} */
@media (max-width: 768px) {
/* @media (max-width: 768px) {
  .data-dashboard {
    padding: 16px;
  }
@@ -861,5 +1724,5 @@
    width: 100%;
    text-align: left;
  }
}
</style>
} */
</style>