From 49e2b7ea866695957633855f71f9e2f943b11ec7 Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期三, 04 三月 2026 17:29:03 +0800
Subject: [PATCH] 2026.3.4

---
 src/components/head/FYPageHeader.vue                                               |   25 
 components.d.ts                                                                    |    2 
 src/views/analysis/evalution/components/CompReport.vue                             |  101 +
 src/components/monitor/RealTimeData.vue                                            |  543 ++++++++-
 src/constants/envCreditCode.js                                                     |   26 
 src/router/index.js                                                                |    2 
 src/views/analysis/evalution/components/precheck/components/CompCheckConfirm.vue   |  108 ++
 src/views/monitor/DataDashboard.vue                                                |  155 +
 src/components/table/FYTable.vue                                                   |   95 
 src/components/monitor/DistrictRanking.vue                                         |   67 
 src/views/analysis/evalution/components/CompDataResultEdit.vue                     |  556 ++++++++++
 src/views/analysis/evalution/EvalutationRecord.vue                                 |  451 ++++++++
 src/views/analysis/evalution/EvalutationTask.vue                                   |   34 
 src/views/analysis/evalution/components/precheck/components/CompCheckExemption.vue |   45 
 src/components/monitor/DeviceStatus.vue                                            |    9 
 src/views/analysis/evalution/components/precheck/components/CompCheckSource.vue    |  334 ++++++
 src/views/analysis/evalution/EvalutationEdit.vue                                   |  279 +++++
 src/views/analysis/evalution/components/precheck/components/CompCheckArea.vue      |  138 ++
 src/views/analysis/evalution/components/CompEvaTask.vue                            |  144 ++
 src/views/analysis/evalution/components/precheck/CompPreCheck.vue                  |   97 +
 20 files changed, 2,981 insertions(+), 230 deletions(-)

diff --git a/components.d.ts b/components.d.ts
index cbb0eab..dac87af 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -99,6 +99,7 @@
     FYOptionTime: typeof import('./src/components/search-option/FYOptionTime.vue')['default']
     FYOptionTopTask: typeof import('./src/components/search-option/FYOptionTopTask.vue')['default']
     FYOptionUserType: typeof import('./src/components/search-option/FYOptionUserType.vue')['default']
+    FYPageHeader: typeof import('./src/components/head/FYPageHeader.vue')['default']
     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']
@@ -213,6 +214,7 @@
   const FYOptionTime: typeof import('./src/components/search-option/FYOptionTime.vue')['default']
   const FYOptionTopTask: typeof import('./src/components/search-option/FYOptionTopTask.vue')['default']
   const FYOptionUserType: typeof import('./src/components/search-option/FYOptionUserType.vue')['default']
+  const FYPageHeader: typeof import('./src/components/head/FYPageHeader.vue')['default']
   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']
diff --git a/src/components/head/FYPageHeader.vue b/src/components/head/FYPageHeader.vue
new file mode 100644
index 0000000..f474776
--- /dev/null
+++ b/src/components/head/FYPageHeader.vue
@@ -0,0 +1,25 @@
+<template>
+  <el-page-header @back="onBack" class="page-header">
+    <template #content>
+      <span> {{ title }} </span>
+    </template>
+  </el-page-header>
+  <el-divider />
+</template>
+
+<script>
+export default {
+  props: {
+    title: {
+      type: String,
+      default: '褰撳墠涓洪粯璁ら〉闈㈡爣棰橈紝璇蜂紶鍏ユ爣棰樺悕瀛�'
+    }
+  },
+  methods: {
+    // 鍥為��椤甸潰
+    onBack() {
+      this.$router.back();
+    }
+  }
+};
+</script>
diff --git a/src/components/monitor/DeviceStatus.vue b/src/components/monitor/DeviceStatus.vue
index 20228aa..13c776e 100644
--- a/src/components/monitor/DeviceStatus.vue
+++ b/src/components/monitor/DeviceStatus.vue
@@ -6,9 +6,9 @@
         <span>璁惧鍦ㄧ嚎鎯呭喌</span>
       </div>
     </template>
-    <el-row :gutter="20">
+    <el-row :gutter="0">
       <el-col :span="12">
-        <el-row :gutter="20">
+        <el-row :gutter="10">
           <el-col :span="12">
             <el-card class="status-card online-card" shadow="hover">
               <div class="status-content">
@@ -126,6 +126,7 @@
           formatter: '{a} <br/>{b}: {c} ({d}%)',
         },
         legend: {
+          show: false,
           bottom: '0%',
           data: ['鍦ㄧ嚎璁惧', '绂荤嚎璁惧', '姝e父璁惧', '鏁呴殰璁惧'],
         },
@@ -133,7 +134,7 @@
           {
             name: '鍦ㄧ嚎鐘舵��',
             type: 'pie',
-            radius: ['40%', '70%'],
+            radius: ['20%', '40%'],
             center: ['48%', '50%'],
             startAngle: 270,
             endAngle: 90,
@@ -152,7 +153,7 @@
           {
             name: '杩愯鐘舵��',
             type: 'pie',
-            radius: ['40%', '70%'],
+            radius: ['30%', '50%'],
             center: ['52%', '50%'],
             startAngle: 90,
             endAngle: 270,
diff --git a/src/components/monitor/DistrictRanking.vue b/src/components/monitor/DistrictRanking.vue
index f471c32..ea1c4e2 100644
--- a/src/components/monitor/DistrictRanking.vue
+++ b/src/components/monitor/DistrictRanking.vue
@@ -24,25 +24,31 @@
         </el-radio-group>
       </el-col>
     </el-row>
-    <div ref="rankingChart" class="chart-container"></div>
-    <el-table :data="sortedRankingData" style="width: 100%" stripe class="ranking-table">
-      <el-table-column label="鎺掑悕" type="index" width="80" />
-      <el-table-column prop="name" label="鍖哄幙鍚嶇О" />
-      <el-table-column prop="value" label="娴撳害鍧囧�� (mg/m鲁)" />
-      <el-table-column label="鎺掑悕鍙樺寲" width="120">
-        <template #default="scope">
-          <div v-if="scope.row.rankChange > 0" class="rank-up">
-            <el-icon><ArrowUp /></el-icon> {{ scope.row.rankChange }}
-          </div>
-          <div v-else-if="scope.row.rankChange < 0" class="rank-down">
-            <el-icon><ArrowDown /></el-icon> {{ Math.abs(scope.row.rankChange) }}
-          </div>
-          <div v-else class="rank-no-change">
-            <el-icon><Minus /></el-icon> 0
-          </div>
-        </template>
-      </el-table-column>
-    </el-table>
+    <el-row :gutter="20">
+      <el-col :span="10">
+        <div ref="rankingChart" class="chart-container"></div>
+      </el-col>
+      <el-col :span="14">
+        <el-table :data="sortedRankingData" size="small" stripe class="ranking-table">
+          <el-table-column label="鎺掑悕" type="index" width="80" />
+          <el-table-column prop="name" label="鍖哄幙鍚嶇О" />
+          <el-table-column prop="value" label="娴撳害鍧囧�� (mg/m鲁)" />
+          <el-table-column label="鎺掑悕鍙樺寲" width="120">
+            <template #default="scope">
+              <div v-if="scope.row.rankChange > 0" class="rank-up">
+                <el-icon><ArrowUp /></el-icon> {{ scope.row.rankChange }}
+              </div>
+              <div v-else-if="scope.row.rankChange < 0" class="rank-down">
+                <el-icon><ArrowDown /></el-icon> {{ Math.abs(scope.row.rankChange) }}
+              </div>
+              <div v-else class="rank-no-change">
+                <el-icon><Minus /></el-icon> 0
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-col>
+    </el-row>
   </el-card>
 </template>
 
@@ -112,26 +118,29 @@
           },
         },
         grid: {
-          left: '3%',
+          left: '0%',
           right: '4%',
-          bottom: '3%',
+          bottom: '6%',
+          top: '4%',
           containLabel: true,
         },
         xAxis: {
-          type: 'category',
-          data: this.sortedRankingData.map((item) => item.name),
+          type: 'value',
+          nameLocation: 'middle',
+          nameGap: 30,
+          name: this.rankingType === 'hourly' ? '灏忔椂鍧囧�� (mg/m鲁)' : '鏈堝潎鍊� (mg/m鲁)',
         },
         yAxis: {
-          type: 'value',
-          name: this.rankingType === 'hourly' ? '灏忔椂鍧囧�� (mg/m鲁)' : '鏈堝潎鍊� (mg/m鲁)',
+          type: 'category',
+          data: this.sortedRankingData.map((item) => item.name).reverse(),
         },
         series: [
           {
             name: '娴撳害鍧囧��',
             type: 'bar',
-            data: this.sortedRankingData.map((item) => parseFloat(item.value)),
+            data: this.sortedRankingData.map((item) => parseFloat(item.value)).reverse(),
             itemStyle: {
-              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
                 { offset: 0, color: '#83bff6' },
                 { offset: 0.5, color: '#188df0' },
                 { offset: 1, color: '#188df0' },
@@ -153,7 +162,7 @@
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .section {
   margin-bottom: 20px;
 }
@@ -165,7 +174,7 @@
 }
 
 .chart-container {
-  height: 300px;
+  height: 400px;
   width: 100%;
 }
 
diff --git a/src/components/monitor/RealTimeData.vue b/src/components/monitor/RealTimeData.vue
index f8aba0a..8e3e361 100644
--- a/src/components/monitor/RealTimeData.vue
+++ b/src/components/monitor/RealTimeData.vue
@@ -6,153 +6,313 @@
         <span>璁惧瀹炴椂鏁版嵁</span>
       </div>
     </template>
-    <el-row :gutter="20">
-      <el-col :span="8">
-        <el-card class="realtime-data" shadow="hover">
-          <template #header>
-            <span>瀹炴椂鍒嗛挓鏁版嵁</span>
-          </template>
-          <el-descriptions :column="1" v-if="currentDevice">
-            <el-descriptions-item label="璁惧缂栧彿">{{
-              currentDevice.deviceId
-            }}</el-descriptions-item>
-            <el-descriptions-item label="璁惧渚涘簲鍟�">{{
-              currentDevice.supplier
-            }}</el-descriptions-item>
-            <el-descriptions-item label="娌圭儫娴撳害"
-              >{{ currentDevice.娌圭儫娴撳害 }} mg/m鲁</el-descriptions-item
+    <el-space v-if="devices && devices.length > 0" justify="center" wrap>
+      <el-card
+        v-for="(device, index) in devices"
+        :key="device.deviceId"
+        class="device-card"
+        shadow="hover"
+        :class="{ 'abnormal-device': isDeviceAbnormal(device) }"
+      >
+        <template #header>
+          <div class="device-header">
+            <span class="device-id">{{ device.deviceId }}</span>
+            <div class="device-status">
+              <el-icon v-if="device.status === '姝e父'" class="status-icon normal-icon"
+                ><iconify-icon icon="mdi:check-circle"
+              /></el-icon>
+              <el-icon v-else class="status-icon abnormal-icon"
+                ><iconify-icon icon="mdi:alert-circle"
+              /></el-icon>
+              <span>{{ device.status }}</span>
+            </div>
+          </div>
+        </template>
+
+        <!-- 瀹炴椂鍒嗛挓鏁版嵁 -->
+        <div class="realtime-data">
+          <div class="basic-info">
+            <span class="supplier">{{ device.supplier }}</span>
+            <span class="monitor-time">{{
+              device.monitorTime || new Date().toLocaleString()
+            }}</span>
+          </div>
+          <div class="monitor-values">
+            <div
+              class="value-item"
+              :class="{ 'abnormal-value': isAbnormal('smokeDensity', device.smokeDensity) }"
             >
-            <el-descriptions-item label="椋庢満鐢垫祦"
-              >{{ currentDevice.椋庢満鐢垫祦 }} A</el-descriptions-item
+              <span class="value-label">娌圭儫娴撳害</span>
+              <span class="value">{{ device.smokeDensity }} <span class="unit">mg/m鲁</span></span>
+            </div>
+            <div
+              class="value-item"
+              :class="{ 'abnormal-value': isAbnormal('fanCurrent', device.fanCurrent) }"
             >
-            <el-descriptions-item label="鍑�鍖栧櫒鐢垫祦"
-              >{{ currentDevice.鍑�鍖栧櫒鐢垫祦 }} A</el-descriptions-item
+              <span class="value-label">椋庢満鐢垫祦</span>
+              <span class="value">{{ device.fanCurrent }} <span class="unit">A</span></span>
+            </div>
+            <div
+              class="value-item"
+              :class="{ 'abnormal-value': isAbnormal('purifierCurrent', device.purifierCurrent) }"
             >
-          </el-descriptions>
-        </el-card>
-      </el-col>
-      <el-col :span="16">
-        <el-card class="realtime-chart" shadow="hover">
-          <template #header>
-            <span>杩戜竴灏忔椂鏁版嵁</span>
-          </template>
-          <div ref="hourlyChart" class="chart-container"></div>
-        </el-card>
-      </el-col>
-    </el-row>
+              <span class="value-label">鍑�鍖栧櫒鐢垫祦</span>
+              <span class="value">{{ device.purifierCurrent }} <span class="unit">A</span></span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 杩戜竴灏忔椂鏁版嵁 -->
+        <div class="hourly-charts">
+          <el-popover
+            placement="left"
+            :width="600"
+            trigger="click"
+            @show="() => initDeviceCharts(device)"
+          >
+            <template #reference>
+              <div class="chart-header">
+                <span class="date">{{ new Date().toLocaleDateString() }}</span>
+                <el-button size="small" type="primary" link> 鏌ョ湅杩戜竴灏忔椂鏁版嵁 </el-button>
+              </div>
+            </template>
+            <div class="popover-content">
+              <div class="popover-header">
+                <h3>{{ device.deviceId }} 杩戜竴灏忔椂鏁版嵁</h3>
+              </div>
+              <div ref="charts" :key="device.deviceId" class="charts-container">
+                <div class="chart-item">
+                  <div class="chart-title">娌圭儫娴撳害(mg/m鲁)</div>
+                  <div ref="smokeChart" :data-device-id="device.deviceId" class="small-chart"></div>
+                </div>
+                <div class="chart-item">
+                  <div class="chart-title">椋庢満鐢垫祦(A)</div>
+                  <div ref="fanChart" :data-device-id="device.deviceId" class="small-chart"></div>
+                </div>
+                <div class="chart-item">
+                  <div class="chart-title">鍑�鍖栧櫒鐢垫祦(A)</div>
+                  <div
+                    ref="purifierChart"
+                    :data-device-id="device.deviceId"
+                    class="small-chart"
+                  ></div>
+                </div>
+              </div>
+            </div>
+          </el-popover>
+        </div>
+      </el-card>
+    </el-space>
+    <div v-else class="no-data">
+      <el-empty description="鏆傛棤璁惧鏁版嵁" />
+    </div>
   </el-card>
 </template>
 
 <script>
 import * as echarts from 'echarts'
+import { Icon } from '@iconify/vue'
 
 export default {
   name: 'RealTimeData',
+  components: {
+    IconifyIcon: Icon,
+  },
   props: {
-    currentDevice: {
-      type: Object,
-      default: null,
-    },
-    hourlyData: {
+    devices: {
       type: Array,
       default: () => [],
     },
   },
   data() {
     return {
-      hourlyChart: null,
+      charts: {},
     }
   },
   mounted() {
-    this.initChart()
+    // 涓嶉渶瑕佽嚜鍔ㄥ垵濮嬪寲鍥捐〃锛屽彧鍦ㄥ脊鍑烘鏄剧ず鏃跺垵濮嬪寲
   },
   beforeUnmount() {
-    if (this.hourlyChart) {
-      this.hourlyChart.dispose()
-    }
+    Object.values(this.charts).forEach((chart) => {
+      if (chart) {
+        chart.dispose()
+      }
+    })
   },
   watch: {
-    hourlyData() {
-      this.updateChart()
+    devices: {
+      handler(newDevices) {
+        // 娓呴櫎鏃у浘琛�
+        Object.values(this.charts).forEach((chart) => {
+          if (chart) {
+            chart.dispose()
+          }
+        })
+        this.charts = {}
+      },
+      deep: true,
     },
   },
   methods: {
-    initChart() {
-      this.hourlyChart = echarts.init(this.$refs.hourlyChart)
-      this.updateChart()
+    initDeviceCharts(device) {
+      const deviceId = device.deviceId
 
-      window.addEventListener('resize', () => {
-        this.hourlyChart.resize()
+      // 娓呴櫎璇ヨ澶囩殑鏃у浘琛�
+      Object.keys(this.charts).forEach((key) => {
+        if (key.startsWith(deviceId)) {
+          this.charts[key].dispose()
+          delete this.charts[key]
+        }
+      })
+
+      this.$nextTick(() => {
+        // 娌圭儫娴撳害鍥捐〃
+        const smokeChartEl = document.querySelector(`[data-device-id="${deviceId}"]`)
+        if (smokeChartEl) {
+          this.charts[`${deviceId}_smoke`] = echarts.init(smokeChartEl)
+        }
+
+        // 椋庢満鐢垫祦鍥捐〃
+        const fanChartEl = document.querySelectorAll(`[data-device-id="${deviceId}"]`)[1]
+        if (fanChartEl) {
+          this.charts[`${deviceId}_fan`] = echarts.init(fanChartEl)
+        }
+
+        // 鍑�鍖栧櫒鐢垫祦鍥捐〃
+        const purifierChartEl = document.querySelectorAll(`[data-device-id="${deviceId}"]`)[2]
+        if (purifierChartEl) {
+          this.charts[`${deviceId}_purifier`] = echarts.init(purifierChartEl)
+        }
+
+        this.updateDeviceCharts(device)
       })
     },
-    updateChart() {
-      if (!this.hourlyChart) return
+    updateCharts() {
+      this.devices.forEach((device) => {
+        this.updateDeviceCharts(device)
+      })
+    },
+    updateDeviceCharts(device) {
+      const deviceId = device.deviceId
+      const deviceHourlyData = device.hourlyData || []
 
-      const option = {
+      const _baseOption = {
         tooltip: {
           trigger: 'axis',
-          axisPointer: {
-            type: 'cross',
-            label: {
-              backgroundColor: '#6a7985',
-            },
-          },
-        },
-        legend: {
-          data: ['娌圭儫娴撳害', '椋庢満鐢垫祦', '鍑�鍖栧櫒鐢垫祦'],
         },
         grid: {
-          left: '3%',
-          right: '4%',
-          bottom: '3%',
+          left: '0%',
+          right: '0%',
+          top: '10%',
+          bottom: '0%',
           containLabel: true,
         },
         xAxis: {
           type: 'category',
           boundaryGap: false,
-          data: this.hourlyData.map((item) => item.time),
+          data: deviceHourlyData.map((item) => item.time),
+          axisLabel: {
+            fontSize: 10,
+          },
         },
-        yAxis: [
-          {
-            type: 'value',
-            name: '娌圭儫娴撳害 (mg/m鲁)',
-            position: 'left',
-          },
-          {
-            type: 'value',
-            name: '鐢垫祦 (A)',
-            position: 'right',
-          },
-        ],
-        series: [
-          {
-            name: '娌圭儫娴撳害',
-            type: 'line',
-            data: this.hourlyData.map((item) => parseFloat(item.娌圭儫娴撳害)),
-            yAxisIndex: 0,
-          },
-          {
-            name: '椋庢満鐢垫祦',
-            type: 'line',
-            data: this.hourlyData.map((item) => parseFloat(item.椋庢満鐢垫祦)),
-            yAxisIndex: 1,
-          },
-          {
-            name: '鍑�鍖栧櫒鐢垫祦',
-            type: 'line',
-            data: this.hourlyData.map((item) => parseFloat(item.鍑�鍖栧櫒鐢垫祦)),
-            yAxisIndex: 1,
-          },
-        ],
       }
 
-      this.hourlyChart.setOption(option)
+      // 鏇存柊娌圭儫娴撳害鍥捐〃
+      if (this.charts[`${deviceId}_smoke`]) {
+        const smokeOption = {
+          ..._baseOption,
+          yAxis: {
+            type: 'value',
+            name: 'mg/m鲁',
+            // nameLocation: 'middle',
+            // nameGap: 30,
+            axisLabel: {
+              fontSize: 10,
+            },
+          },
+          series: [
+            {
+              name: '娌圭儫娴撳害',
+              type: 'line',
+              data: deviceHourlyData.map((item) => parseFloat(item.smokeDensity)),
+              smooth: true,
+            },
+          ],
+        }
+        this.charts[`${deviceId}_smoke`].setOption(smokeOption)
+      }
+
+      // 鏇存柊椋庢満鐢垫祦鍥捐〃
+      if (this.charts[`${deviceId}_fan`]) {
+        const fanOption = {
+          ..._baseOption,
+          yAxis: {
+            type: 'value',
+            name: 'A',
+            // nameLocation: 'middle',
+            // nameGap: 30,
+            axisLabel: {
+              fontSize: 10,
+            },
+          },
+          series: [
+            {
+              name: '椋庢満鐢垫祦',
+              type: 'line',
+              data: deviceHourlyData.map((item) => parseFloat(item.fanCurrent)),
+              smooth: true,
+            },
+          ],
+        }
+        this.charts[`${deviceId}_fan`].setOption(fanOption)
+      }
+
+      // 鏇存柊鍑�鍖栧櫒鐢垫祦鍥捐〃
+      if (this.charts[`${deviceId}_purifier`]) {
+        const purifierOption = {
+          ..._baseOption,
+          yAxis: {
+            type: 'value',
+            name: 'A',
+            // nameLocation: 'middle',
+            // nameGap: 30,
+            axisLabel: {
+              fontSize: 10,
+            },
+          },
+          series: [
+            {
+              name: '鍑�鍖栧櫒鐢垫祦',
+              type: 'line',
+              data: deviceHourlyData.map((item) => parseFloat(item.purifierCurrent)),
+              smooth: true,
+            },
+          ],
+        }
+        this.charts[`${deviceId}_purifier`].setOption(purifierOption)
+      }
+    },
+    isDeviceAbnormal(device) {
+      return (
+        this.isAbnormal('smokeDensity', device.smokeDensity) ||
+        this.isAbnormal('fanCurrent', device.fanCurrent) ||
+        this.isAbnormal('purifierCurrent', device.purifierCurrent)
+      )
+    },
+    isAbnormal(type, value) {
+      // 杩欓噷鍙互鏍规嵁瀹為檯鎯呭喌瀹氫箟寮傚父鍊煎垽鏂�昏緫
+      const thresholds = {
+        smokeDensity: 10,
+        fanCurrent: 5,
+        purifierCurrent: 3,
+      }
+      return parseFloat(value) > (thresholds[type] || 0)
     },
   },
 }
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .section {
   margin-bottom: 20px;
 }
@@ -163,8 +323,191 @@
   align-items: center;
 }
 
-.chart-container {
-  height: 300px;
+.device-card {
+  // width: 100%;
+  margin-bottom: 20px;
+  transition: all 0.3s ease;
+}
+
+.device-card:hover {
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.abnormal-device {
+  border-bottom: 4px solid #f56c6c;
+}
+
+.device-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.device-id {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.device-status {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+.status-icon {
+  font-size: 16px;
+}
+
+.normal-icon {
+  color: #67c23a;
+}
+
+.abnormal-icon {
+  color: #f56c6c;
+}
+
+.realtime-data {
+  margin-bottom: 20px;
+}
+
+.basic-info {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 15px;
+  font-size: 12px;
+  color: #909399;
+}
+
+.supplier {
+  font-style: italic;
+}
+
+.monitor-time {
+  font-family: monospace;
+}
+
+.monitor-values {
+  display: flex;
+  justify-content: space-around;
+  gap: 10px;
+}
+
+.value-item {
+  flex: 1;
+  width: 60px;
+  text-align: center;
+  padding: 10px;
+  border-radius: 4px;
+  background-color: #f9f9f9;
+  border: 1px solid transparent;
+}
+
+.value-label {
+  display: block;
+  font-size: 12px;
+  color: #606266;
+  margin-bottom: 5px;
+}
+
+.value {
+  display: block;
+  font-size: 20px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.unit {
+  font-size: 12px;
+  font-weight: normal;
+  color: #909399;
+}
+
+.abnormal-value {
+  background-color: #fef0f0;
+  border: 1px solid #fbc4c4;
+}
+
+.abnormal-value .value {
+  color: #f56c6c;
+}
+
+.hourly-charts {
+  margin-top: 20px;
+}
+
+.chart-header {
+  margin-top: 10px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 5px 0;
+}
+
+.popover-content {
+  padding: 10px;
+}
+
+.popover-header {
+  margin-bottom: 15px;
+  border-bottom: 1px solid #f0f0f0;
+  padding-bottom: 10px;
+}
+
+.popover-header h3 {
+  margin: 0;
+  font-size: 14px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.date {
+  font-size: 12px;
+  color: #909399;
+  font-family: monospace;
+}
+
+.charts-container {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.chart-item {
+  background-color: #f9f9f9;
+  border-radius: 4px;
+  padding: 10px;
+}
+
+.chart-title {
+  font-size: 12px;
+  color: #606266;
+  margin-bottom: 5px;
+  text-align: center;
+}
+
+.small-chart {
+  height: 120px;
   width: 100%;
 }
+
+.no-data {
+  padding: 40px 0;
+  text-align: center;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+  .monitor-values {
+    flex-direction: column;
+  }
+
+  .value-item {
+    width: 100%;
+  }
+
+  .small-chart {
+    height: 100px;
+  }
+}
 </style>
diff --git a/src/components/table/FYTable.vue b/src/components/table/FYTable.vue
index 25023f6..8cd1348 100644
--- a/src/components/table/FYTable.vue
+++ b/src/components/table/FYTable.vue
@@ -1,4 +1,5 @@
 <template>
+  <div>
   <el-row ref="searchRef">
     <FYSearchBar @search="onSearch">
       <template #options v-if="$slots.options">
@@ -75,30 +76,30 @@
     cellClassName: Function || String,
     pagination: {
       type: Boolean,
-      default: true
+      default: true,
     },
     // '' | 'small' | 'default' | 'large'
     size: {
       type: String,
-      default: 'default'
+      default: 'default',
     },
     data: {
       type: Array,
-      default: () => []
+      default: () => [],
     },
     totalCount: {
       type: Number,
-      default: 0
+      default: 0,
     },
     defaultPageSize: {
       type: Number,
-      default: 20
+      default: 20,
     },
     // 棰濆鐨勯珮搴︼紝鐢ㄤ簬璁$畻琛ㄦ牸楂樺害
     extraHeight: {
       type: Number,
-      default: 0
-    }
+      default: 0,
+    },
   },
   data() {
     return {
@@ -108,46 +109,46 @@
       currentPage: 1,
       pageSize: this.defaultPageSize,
       loading: false,
-      fontSize: 'default'
-    };
+      fontSize: 'default',
+    }
   },
   emits: ['search', 'cellClick', 'tablePaste', 'sortChange'],
   watch: {
     currentPage(nValue, oValue) {
       if (nValue != oValue) {
-        this.onSearch();
+        this.onSearch()
       }
     },
     pageSize(nValue, oValue) {
       if (nValue != oValue) {
-        this.onSearch();
+        this.onSearch()
       }
     },
     size: {
       handler(nValue, oValue) {
         if (nValue != oValue) {
-          this.fontSize = nValue;
+          this.fontSize = nValue
         }
       },
-      immediate: true
+      immediate: true,
     },
     data(nValue, oValue) {
       if (nValue != oValue) {
-        this.tableData = nValue;
+        this.tableData = nValue
       }
     },
     totalCount(nValue, oValue) {
       if (nValue != oValue) {
-        this.total = nValue;
+        this.total = nValue
       }
     },
     extraHeight: {
       handler(nValue, oValue) {
         if (nValue != oValue) {
-          this.tableHeight = this.calcTableHeight();
+          this.tableHeight = this.calcTableHeight()
         }
-      }
-    }
+      },
+    },
   },
   computed: {},
   methods: {
@@ -157,73 +158,71 @@
      * 鍥炶皟鍑芥暟鎺ユ敹涓�涓璞★紝鍖呮嫭琛ㄦ牸鏁版嵁鏁扮粍data鍜屾暟鎹�绘暟total
      */
     onSearch() {
-      this.loading = true;
+      this.loading = true
       this.$emit(
         'search',
         {
           currentPage: this.currentPage,
-          pageSize: this.pageSize
+          pageSize: this.pageSize,
         },
         (res) => {
           if (res) {
             if (res.data) {
-              this.tableData = res.data;
+              this.tableData = res.data
             }
             if (res.total) {
-              this.total = res.total;
+              this.total = res.total
             }
           }
-          this.loading = false;
-          this.doLayout();
-        }
-      );
+          this.loading = false
+          this.doLayout()
+        },
+      )
     },
     calcTableHeight() {
-      const h1 = this.$refs.searchRef.$el.offsetHeight;
-      const h2 = this.$refs.paginationRef
-        ? this.$refs.paginationRef.$el.offsetHeight
-        : 0;
-      const h3 = this.$refs.expandRef.$el.offsetHeight;
-      const h4 = this.$refs.expand2Ref.offsetHeight;
+      const h1 = this.$refs.searchRef.$el.offsetHeight
+      const h2 = this.$refs.paginationRef ? this.$refs.paginationRef.$el.offsetHeight : 0
+      const h3 = this.$refs.expandRef.$el.offsetHeight
+      const h4 = this.$refs.expand2Ref.offsetHeight
 
-      const h = h1 + h2 + h3 + h4 + this.extraHeight;
-      return this.contentMaxHeight.value - h + 'px';
+      const h = h1 + h2 + h3 + h4 + this.extraHeight
+      return this.contentMaxHeight - h + 'px'
       // return `calc(100vh - ${h}px - 60px - var(--el-main-padding) * 2)`;
     },
     tableRowClassName({ row }) {
       if (this.rowClassName) {
         if (typeof this.rowClassName == 'string') {
-          return this.rowClassName;
+          return this.rowClassName
         } else if (typeof this.rowClassName == 'function') {
-          return this.rowClassName({ row });
+          return this.rowClassName({ row })
         }
       } else {
-        return row.extension1 != '0' ? 'online-row' : 'offline-row';
+        return row.extension1 != '0' ? 'online-row' : 'offline-row'
       }
     },
     cellClick(row, column, cell, event) {
-      this.$emit('cellClick', row, column, cell, event);
+      this.$emit('cellClick', row, column, cell, event)
     },
     handlePaste(event) {
-      this.$emit('tablePaste', event);
+      this.$emit('tablePaste', event)
     },
     doLayout() {
-      this.$refs.tableRef.doLayout();
+      this.$refs.tableRef.doLayout()
     },
     handleSortChange({ column, prop, order }) {
-      this.$emit('sortChange', { column, prop, order });
+      this.$emit('sortChange', { column, prop, order })
     },
     clearSort() {
-      this.$refs.tableRef.clearSort();
-    }
+      this.$refs.tableRef.clearSort()
+    },
   },
   mounted() {
     setTimeout(() => {
-      this.tableHeight = this.calcTableHeight();
-    }, 100);
-    this.onSearch();
-  }
-};
+      this.tableHeight = this.calcTableHeight()
+    }, 100)
+    this.onSearch()
+  },
+}
 </script>
 
 <style>
diff --git a/src/constants/envCreditCode.js b/src/constants/envCreditCode.js
new file mode 100644
index 0000000..184d7af
--- /dev/null
+++ b/src/constants/envCreditCode.js
@@ -0,0 +1,26 @@
+// 鐜俊鐮佽浆鎹�
+
+function envCreditCode(score) {
+  const s = parseInt(score)
+  if (s <= 59) {
+    return {
+      value: 2,
+      name: '绾㈢爜',
+      color: '#db2828'
+    };
+  } else if (s <= 89) {
+    return {
+      value: 1,
+      name: '榛勭爜',
+      color: '#f7a62c'
+    };
+  } else {
+    return {
+      value: 0,
+      name: '缁跨爜',
+      color: '#21ba45'
+    };
+  }
+}
+
+export { envCreditCode };
diff --git a/src/router/index.js b/src/router/index.js
index 6713a23..4f042be 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -98,7 +98,7 @@
             {
               name: 'auto-evalution',
               path: 'auto-evalution',
-              component: () => import('@/views/analysis/evalution/AutoEvalution.vue'),
+              component: () => import('@/views/analysis/evalution/EvalutationRecord.vue'),
             },
             {
               name: 'huanxincode-manage',
diff --git a/src/views/analysis/evalution/EvalutationEdit.vue b/src/views/analysis/evalution/EvalutationEdit.vue
new file mode 100644
index 0000000..f45dabf
--- /dev/null
+++ b/src/views/analysis/evalution/EvalutationEdit.vue
@@ -0,0 +1,279 @@
+<template>
+  <FYPageHeader title="璇勪及缁撴灉璇︽儏"></FYPageHeader>
+  <el-row v-for="item in evaluation" :key="item.id"> </el-row>
+  <div class="btns">
+    <el-button type="primary" @click="submit" :disabled="!isUpdated">鎻愪氦</el-button>
+  </div>
+  <el-table
+    class="table-style"
+    :data="tableData"
+    ref="tableRef"
+    :span-method="objectSpanMethod"
+    table-layout="fixed"
+    :cell-style="cellClassName"
+    border
+    stripe
+  >
+    <el-table-column v-slot="scope" prop="one_title" label="涓�绾ф寚鏍�" width="200">
+      <!-- <el-checkbox v-model="scope.row.one_select" @change="checked => oneSelectChange(checked, scope.row)">{{ scope.row.one_title }}</el-checkbox> -->
+    </el-table-column>
+    <el-table-column prop="one_score" label="鍒嗗��" width="55" />
+    <el-table-column prop="one_maxScore" label="鏈�澶у垎鍊�" width="90" />
+    <el-table-column v-slot="scope" prop="two_title" label="浜岀骇鎸囨爣" width="200">
+      <!-- <el-checkbox v-model="scope.row.two_select" @change="checked => twoSelectChange(checked, scope.row)">{{ scope.row.two_title }}</el-checkbox> -->
+    </el-table-column>
+    <el-table-column prop="two_score" label="鍒嗗��" width="55" />
+    <el-table-column prop="two_maxScore" label="鏈�澶у垎鍊�" width="90" />
+    <el-table-column v-slot="scope" prop="three_title" label="鍏蜂綋闂">
+      <el-checkbox
+        v-model="scope.row.three_select"
+        @change="(checked) => threeSelectChange(checked, scope.row)"
+        >{{ scope.row.three_title }}</el-checkbox
+      >
+    </el-table-column>
+    <el-table-column prop="three_score" label="鍗曢」鎵e垎" width="90" />
+  </el-table>
+</template>
+
+<script>
+import evaluateApi from '@/api/fysp/evaluateApi';
+import { useFetchData } from '@/composables/fetchData';
+import { ElMessage } from 'element-plus';
+export default {
+  setup() {
+    const { loading, fetchData } = useFetchData();
+    return { loading, fetchData };
+  },
+  data() {
+    return {
+      tableData: [],
+      evaluation: [],
+      subTaskId: '',
+      isUpdated: false
+    };
+  },
+  created() {
+    // // watch 璺敱鐨勫弬鏁帮紝浠ヤ究鍐嶆鑾峰彇鏁版嵁
+    // this.$watch(
+    //   () => this.$route.params,
+    //   () => {
+    //     this.getScore();
+    //   },
+    //   // 缁勪欢鍒涘缓瀹屽悗鑾峰彇鏁版嵁锛�
+    //   // 姝ゆ椂 data 宸茬粡琚� observed 浜�
+    //   { immediate: true }
+    // );
+  },
+  computed: {
+    // 宸茶鍕鹃�夌殑item
+    checkedUpdatedList() {
+      var list = [];
+      for (let index = 0; index < this.tableData.length; index++) {
+        const element = this.tableData[index];
+        if (element.three_select) {
+          list.push(element.three_id);
+        }
+      }
+      return list;
+    }
+  },
+  mounted() {
+    this.getList();
+  },
+  methods: {
+    // 姣忎竴涓崟鍏冩牸鐨刢lass
+    cellClassName({ row, column, rowIndex, columnIndex }) {
+      if (column.property === 'one_score') {
+        if (row.one_score < 0) {
+          return { color: 'red' };
+        }
+      } else if (column.property === 'two_score') {
+        if (row.two_score < 0) {
+          return { color: 'red' };
+        }
+      } else if (column.property === 'three_score') {
+        if (row.three_score < 0) {
+          return { color: 'red' };
+        }
+      }
+      return { color: 'black' };
+    },
+    /** 鎻愪环 */
+    submit() {
+      evaluateApi
+        .updateScore({
+          subTaskId: this.subTaskId,
+          itemList: this.checkedUpdatedList
+        })
+        .then((res) => {
+          if (res.success) {
+            ElMessage({
+              message: res.message,
+              type: 'success'
+            });
+          }else {
+            ElMessage({
+              message: res.message,
+              type: 'error'
+            });
+          }
+        });
+      setTimeout(() => {
+        this.getList();
+      }, 1000);
+    },
+    /** 閫氳繃绗笁绾х殑id鑾峰彇涓婄骇浠ュ強椤剁骇 */
+    getSuperObjByThreeId(threeId, list, path = []) {
+      for (let index = 0; index < list.length; index++) {
+        const item = list[index];
+        // 灏嗗綋鍓嶉」娣诲姞鍒拌矾寰勪腑
+        const currentPath = path.concat(item);
+        if (item.id === threeId) {
+          // 濡傛灉鎵惧埌鍖归厤鐨� id锛岃繑鍥炶矾寰勬暟缁�
+          return currentPath;
+        }
+        const subList = item.subList;
+        if (subList) {
+          // 閫掑綊鏌ユ壘瀛愬垪琛�
+          const result = this.getSuperObjByThreeId(threeId, subList, currentPath);
+          if (result) {
+            return result; // 濡傛灉鎵惧埌鍖归厤鐨� id锛岃繑鍥炵粨鏋�
+          }
+        }
+      }
+      return null; // 濡傛灉娌℃湁鎵惧埌鍖归厤鐨� id锛岃繑鍥� null
+    },
+    /** 闂閫夋嫨妗� */
+    threeSelectChange(isSelect, row) {
+      this.isUpdated = true;
+    },
+    /** 鍒楀悎骞� */
+    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
+      if (columnIndex === 0 || columnIndex === 1 || columnIndex === 2) {
+        // 瀵� 涓�绾ф寚鏍� 鍒楄繘琛屽悎骞�
+        let rowSpan = 1;
+        for (let i = rowIndex + 1; i < this.tableData.length; i++) {
+          if (this.tableData[i].one_id === row.one_id) {
+            rowSpan++;
+          } else {
+            break;
+          }
+        }
+        if (rowIndex > 0) {
+          if (this.tableData[rowIndex - 1].one_id === row.one_id) {
+            return { rowspan: 0, colspan: 0 };
+          }
+        }
+        return { rowspan: rowSpan, colspan: 1 };
+      } else if (columnIndex === 3 || columnIndex === 4 || columnIndex === 5) {
+        // 瀵� 浜岀骇鎸囨爣 鍒楄繘琛屽悎骞讹紝纭繚 涓�绾ф寚鏍� 涓�鏍�
+        let rowSpan = 1;
+        for (let i = rowIndex + 1; i < this.tableData.length; i++) {
+          if (this.tableData[i].one_id === row.one_id && this.tableData[i].two_id === row.two_id) {
+            rowSpan++;
+          } else {
+            break;
+          }
+        }
+        if (rowIndex > 0) {
+          if (
+            this.tableData[rowIndex - 1].one_id === row.one_id &&
+            this.tableData[rowIndex - 1].two_id === row.two_id
+          ) {
+            return { rowspan: 0, colspan: 0 };
+          }
+        }
+        return { rowspan: rowSpan, colspan: 1 };
+      }
+    },
+    /** 瀵硅薄灞炴�ф嫹璐� */
+    deepCopyWithPrefix(obj, target, prefix) {
+      // 纭繚 target 鏄竴涓璞�
+      if (typeof target !== 'object' || target === null) {
+        target = {};
+      }
+
+      // 閬嶅巻瀵硅薄鐨勬墍鏈夊睘鎬�
+      for (const key in obj) {
+        if (obj.hasOwnProperty(key)) {
+          // 涓哄睘鎬у悕鍔犱笂鍓嶇紑
+          const newKey = prefix + key;
+          // 濡傛灉灞炴�у�兼槸瀵硅薄锛屽垯閫掑綊澶嶅埗
+          if (typeof obj[key] === 'object' && obj[key] !== null) {
+            this.deepCopyWithPrefix(obj[key], (target[newKey] = {}), prefix);
+          } else {
+            // 鍚﹀垯鐩存帴澶嶅埗灞炴��
+            target[newKey] = obj[key];
+          }
+        }
+      }
+
+      return target;
+    },
+    /** @param data 鍒楄〃鏁版嵁 */
+    genTableData(data) {
+      var result = [];
+      if (data) {
+        for (let i = 0; i < data.length; i++) {
+          const firstLevelItem = data[i];
+          var secondLevel = firstLevelItem.subList;
+          if (secondLevel) {
+            for (let j = 0; j < secondLevel.length; j++) {
+              const secondLevelItem = secondLevel[j];
+              var thirdLevel = secondLevelItem.subList;
+              if (thirdLevel) {
+                for (let q = 0; q < thirdLevel.length; q++) {
+                  const thirdLevelItem = thirdLevel[q];
+                  var item = {};
+                  this.deepCopyWithPrefix(firstLevelItem, item, 'one_');
+                  this.deepCopyWithPrefix(secondLevelItem, item, 'two_');
+                  this.deepCopyWithPrefix(thirdLevelItem, item, 'three_');
+                  result.push(item);
+                }
+              }
+            }
+          }
+        }
+      }
+      return result;
+    },
+    getList() {
+      this.subTaskId = this.$route.params.subTaskId;
+      evaluateApi.fetchItemEvaluation(this.subTaskId).then((res) => {
+        this.isUpdated = false;
+        this.tableData = this.genTableData(res.data.details);
+      });
+    },
+    onSearch(page, func) {
+      evaluateApi.fetchItemEvaluation(this.$route.params.subTaskId).then((res) => {
+        if (typeof func === 'function') {
+          // 澶勭悊鏁版嵁
+          var data = this.genTableData(res.data);
+
+          func({ data: data });
+        }
+        this.tableData = this.genTableData(res.data);
+      });
+    }
+  }
+};
+</script>
+<style scoped>
+.table-style {
+  width: 100%;
+  padding-bottom: 30px;
+}
+.btns {
+  padding-bottom: 10px;
+  padding-right: 30px;
+  display: flex;
+  flex-direction: row-reverse;
+}
+/* 鏀瑰彉琛ㄦ牸鍐呭崟鍏冩牸杈规棰滆壊 */
+.el-table {
+  --el-table-border-color: #000000;
+}
+.red-cell {
+  background-color: red;
+}
+</style>
diff --git a/src/views/analysis/evalution/EvalutationRecord.vue b/src/views/analysis/evalution/EvalutationRecord.vue
new file mode 100644
index 0000000..40735b6
--- /dev/null
+++ b/src/views/analysis/evalution/EvalutationRecord.vue
@@ -0,0 +1,451 @@
+<template>
+  <FYTable
+    @search="onSearch"
+    :pagination="false"
+    ref="tableRef"
+    @cell-click="cellClick"
+    :cell-class-name="cellClassName"
+    @table-paste="handlePaste"
+    @sort-change="handleSortChange"
+  >
+    <template #options>
+      <!-- 鍖哄幙 -->
+      <FYOptionLocation
+        :allOption="false"
+        :level="3"
+        :checkStrictly="false"
+        v-model:value="formSearch.locations"
+      ></FYOptionLocation>
+      <!-- 鍦烘櫙绫诲瀷 -->
+      <FYOptionScene
+        :allOption="false"
+        :type="2"
+        v-model:value="formSearch.scenetype"
+      ></FYOptionScene>
+      <!-- 鏃堕棿 -->
+      <FYOptionTime
+        :initValue="false"
+        type="month"
+        v-model:value="formSearch.time"
+      ></FYOptionTime>
+    </template>
+    <template #buttons>
+      <!-- <el-button icon="Download" size="default" type="success" @click="download"
+        >瑙勮寖鎬ц瘎浼颁笌鍒嗘瀽鎶ュ憡</el-button
+      > -->
+      <CompReport
+        :locations="formSearch.locations"
+        :scenetype="formSearch.scenetype"
+        :time="formSearch.time"
+      ></CompReport>
+    </template>
+
+    <template #options-expand>
+      <el-form :inline="true">
+        <CompQuickSet @quick-set="setOptions"></CompQuickSet>
+      </el-form>
+    </template>
+
+    <template #table-column="{ size }">
+      <el-table-column
+        fixed="left"
+        sortable="custom"
+        prop="sceneIndex"
+        label="缂栧彿"
+        width="80"
+      >
+      </el-table-column>
+      <el-table-column
+        prop="sceneName"
+        :show-overflow-tooltip="true"
+        label="鍚嶇О"
+        width="300"
+      >
+      </el-table-column>
+      <el-table-column
+        prop="subTaskTime"
+        label="宸℃煡鏃ユ湡"
+        width="110"
+        sortable="custom"
+        :formatter="timeFormat"
+      />
+      <el-table-column
+        prop="evaluation.resultscorebef"
+        label="寰楀垎"
+        width="90"
+        sortable="custom"
+      />
+      <el-table-column
+        prop="evaluation.resultscorebef"
+        label="鐜俊鐮�"
+        width="100"
+      >
+        <template #default="{ row }">
+          <span :style="`color: ${toCode(row).color};`">{{
+            toCode(row).name
+          }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column prop="dname" label="鍖哄幙" width="90" />
+      <el-table-column
+        prop="tname"
+        label="琛楅亾"
+        width="110"
+        :filters="townFilters"
+        :filter-method="filterHandler"
+      />
+      <el-table-column prop="evaluation.scenseaddress" label="鍦板潃" />
+      <!-- <el-table-column prop="biArea" label="闆嗕腑鍖�" width="110" />
+      <el-table-column prop="biManagementCompany" label="鐗╀笟" min-width="110"/> -->
+      <el-table-column
+        v-if="scoreShow"
+        fixed="right"
+        align="right"
+        label="鐩戞祴鏁版嵁瓒呮爣"
+        width="160"
+      >
+        <template #default="{ row }">
+          <el-input :size="size" v-model="row.score1" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        v-if="scoreShow"
+        fixed="right"
+        align="right"
+        label="鐩戞祴鏁版嵁瓒呭尯鏈堝潎鍊�"
+        width="160"
+      >
+        <template #default="{ row }">
+          <el-input :size="size" v-model="row.score2" />
+        </template>
+      </el-table-column>
+      <el-table-column fixed="right" align="right" label="鎿嶄綔" width="160">
+        <template #header>
+          <el-button
+            v-show="!scoreShow"
+            icon="ArrowLeft"
+            size="small"
+            type="success"
+            @click="scoreShow = true"
+            >鐩戞祴鏁版嵁寰楀垎</el-button
+          >
+          <el-button
+            v-show="scoreShow"
+            size="small"
+            type="primary"
+            @click="updateMultipleScore"
+            :loading="updateLoading"
+            >涓婁紶</el-button
+          >
+          <el-button
+            v-show="scoreShow"
+            size="small"
+            type="error"
+            @click="scoreShow = false"
+            >鍙栨秷</el-button
+          >
+        </template>
+        <template #default="{ row }">
+          <el-button type="primary" size="small" @click="editRow(row)"
+            >鏌ョ湅</el-button
+          >
+        </template>
+      </el-table-column>
+    </template>
+  </FYTable>
+</template>
+
+<script>
+import dayjs from 'dayjs';
+import evaluateApi from '@/api/fysp/evaluateApi';
+import { envCreditCode } from '@/constants/index';
+import CompReport from './components/CompReport.vue';
+import { useTablePaste } from '@/composables/tablePaste';
+import { useCloned } from '@vueuse/core';
+import { useMessageBoxTip } from '@/composables/messageBox';
+
+export default {
+  setup() {
+    const {
+      cellClick,
+      cellClassName,
+      handlePaste,
+      setTableData,
+      addRefreshEvent,
+      tableData
+    } = useTablePaste({
+      score1: 8,
+      score2: 9
+    });
+    return {
+      cellClick,
+      cellClassName,
+      handlePaste,
+      setTableData,
+      addRefreshEvent,
+      tableData
+    };
+  },
+  components: { CompReport },
+  data() {
+    return {
+      formSearch: {
+        locations: {},
+        scenetype: {},
+        time: dayjs().add(-1, 'M').date(1).toDate()
+      },
+      townFilters: [],
+      // 鍘熷鏁版嵁锛岀敤浜庢帓搴忓彇娑堝悗
+      orginData: [],
+      evaluationRule: undefined,
+      evaluationSubRule: undefined,
+      //鐩戞祴鏁版嵁瑙勫垯鍚嶇О
+      ruleName: {
+        score1: {
+          id: undefined,
+          name: '鐩戞祴鏁版嵁鍑虹幇鍗曟棩鍙婁互涓婃湁鏁堣秴鏍�'
+        },
+        score2: {
+          id: undefined,
+          name: '鐩戞祴鏁版嵁鏈堝潎鍊艰秴鍖哄煙鏈堝潎鍊�20%浠ヤ笂鎴栨暟鎹槑鏄惧紓甯�'
+        }
+      },
+      scoreShow: false,
+      updateLoading: false
+    };
+  },
+  computed: {
+    area() {
+      const { locations, scenetype, time } = this.formSearch;
+      return {
+        provincecode: locations.pCode,
+        provincename: locations.pName,
+        citycode: locations.cCode,
+        cityname: locations.cName,
+        districtcode: locations.dCode,
+        districtname: locations.dName,
+        starttime: dayjs(time).format('YYYY-MM-DD HH:mm:ss'),
+        scensetypeid: scenetype.value
+      };
+    }
+  },
+  methods: {
+    // _getParam() {
+    //   const { locations, scenetype, time } = this.formSearch;
+    //   return {
+    //     provincecode: locations.pCode,
+    //     provincename: locations.pName,
+    //     citycode: locations.cCode,
+    //     cityname: locations.cName,
+    //     districtcode: locations.dCode,
+    //     districtname: locations.dName,
+    //     starttime: dayjs(time).format('YYYY-MM-DD HH:mm:ss'),
+    //     scensetypeid: scenetype.value
+    //   };
+    // },
+    editRow(row) {
+      this.$router.push(`evalutationEdit/${row.subTaskId}`);
+    },
+    setOptions(param) {
+      this.formSearch.locations = param.locations;
+      this.formSearch.scenetype = param.scenetype;
+      this.formSearch.sourceType = param.sourceType;
+      this.$refs.tableRef.onSearch();
+    },
+    onSearch(page, func) {
+      this.$refs.tableRef.clearSort();
+      this.fetchEvaluationRule(this.area).then(() => {
+        evaluateApi.fetchAutoEvaluation(this.area).then((res) => {
+          if (res.data) {
+            this.tableData = res.data;
+            this.tableData.forEach((v) => {
+              // 灏嗗師濮嬬殑寰楀垎灞曠ず鍒拌〃鏍间腑
+              if (v.itemEvaluations) {
+                for (const key in this.ruleName) {
+                  const value = this.ruleName[key];
+                  const itemEva = v.itemEvaluations.find((ie) => {
+                    return ie.esrguid == value.id && ie.extension1 == 'true';
+                  });
+                  if (itemEva) {
+                    v[key] = itemEva.value;
+                  }
+                }
+              }
+            });
+            this.orginData = useCloned(this.tableData).cloned;
+            this.getFilters(res.data);
+            if (typeof func === 'function') {
+              func({ data: this.tableData });
+            }
+          } else {
+            this.tableData = [];
+            this.orginData = [];
+            if (typeof func === 'function') {
+              func({ data: this.tableData });
+            }
+          }
+        });
+      });
+    },
+    fetchEvaluationRule() {
+      const param = {
+        // 鑷姩璇勪及绫诲瀷
+        taskTypeId: 99,
+        ...this.area
+      };
+      // 鑾峰彇璇勪及鎬昏鍒�
+      return evaluateApi.fetchEvaluationRule(param).then((res) => {
+        if (res.data.length > 0) {
+          this.evaluationRule = res.data[0];
+          // 鑾峰彇鍏蜂綋瀛愯鍒�
+          return evaluateApi
+            .getSubRules(this.evaluationRule.guid)
+            .then((res) => {
+              this.evaluationSubRule = res.data;
+              // 鏌ユ壘鍙鍏ュ緱鍒嗙殑瑙勫垯id
+              for (const key in this.ruleName) {
+                const value = this.ruleName[key];
+                const subrule = this.evaluationSubRule.find((v) => {
+                  return v.itemname == value.name;
+                });
+                if (subrule) {
+                  value.id = subrule.guid;
+                }
+              }
+            });
+        }
+      });
+    },
+    // 鎵归噺鏇存柊鐩戞祴鏁版嵁寰楀垎
+    updateMultipleScore() {
+      this.updateLoading = true;
+      useMessageBoxTip({
+        confirmMsg: '鏄惁涓婁紶鐩戞祴鏁版嵁寰楀垎',
+        confirmTitle: '涓婁紶鐩戞祴鏁版嵁寰楀垎',
+        onConfirm: async () => {
+          if (this.evaluationRule) {
+            const subTaskEvaList = this.tableData.map((v) => {
+              const subRule = [];
+              for (const key in this.ruleName) {
+                const value = this.ruleName[key];
+                subRule.push({
+                  id: value.id,
+                  selected: v[key] && (v[key] + '').trim() != ''
+                });
+              }
+              return {
+                subTaskId: v.subTaskId,
+                ruleId: this.evaluationRule.guid,
+                subRule
+              };
+            });
+            const param = {
+              ...this.area,
+              subTaskEvaList
+            };
+
+            return await evaluateApi
+              .updateMultipleScore(param)
+              .finally(() => (this.updateLoading = false));
+          }
+        }
+      });
+    },
+    // 瑙勮寖鎬ц瘎浼颁笌鍒嗘瀽鎶ュ憡鍚庡彴鐢熸垚浠诲姟
+    download() {
+      evaluateApi.downloadAutoEvaluation(this.area).then((res) => {
+        if (res == false) {
+          // 鏈笅杞芥枃妗o紝鑰屾槸寮�鍚簡鏂囨。鐢熸垚鍚庡彴浠诲姟
+          this.$parent;
+        }
+      });
+    },
+    // 瀵煎嚭琛ㄦ牸涓篹xcel鏍煎紡
+    exportExcel() {},
+    getFilters(data) {
+      const townList = [];
+      data.forEach((e) => {
+        if (townList.indexOf(e.tname) == -1) {
+          townList.push(e.tname);
+        }
+      });
+      this.townFilters = townList.map((v) => {
+        return { text: v, value: v };
+      });
+    },
+    toCode(row, column) {
+      if (row.evaluation) {
+        return envCreditCode(row.evaluation.resultscorebef);
+      } else {
+        return '';
+      }
+    },
+    timeFormat(row, column) {
+      const time = row.subTaskTime;
+      if (time) {
+        return dayjs(time).format('MM-DD');
+      } else {
+        return '';
+      }
+    },
+    filterHandler(value, row, column) {
+      const property = column['property'];
+      return row[property] === value;
+    },
+    sortScore(a, b) {
+      const s1 = a.evaluation ? parseInt(a.evaluation.resultscorebef) : 0;
+      const s2 = b.evaluation ? parseInt(b.evaluation.resultscorebef) : 0;
+      return s1 - s2;
+    },
+    handleSortChange({ column, prop, order }) {
+      console.log(column, prop, order);
+      if (order == null) {
+        this.orginData.forEach((e, i) => {
+          this.tableData[i] = e;
+        });
+      } else if (prop == 'evaluation.resultscorebef') {
+        this.tableData.sort((a, b) => {
+          const s1 = a.evaluation ? parseInt(a.evaluation.resultscorebef) : 0;
+          const s2 = b.evaluation ? parseInt(b.evaluation.resultscorebef) : 0;
+          if (order == 'ascending') {
+            return s1 - s2;
+          } else if (order == 'descending') {
+            return s2 - s1;
+          }
+        });
+      } else if (prop == 'sceneIndex') {
+        this.tableData.sort((a, b) => {
+          if (order == 'ascending') {
+            if (a.sceneIndex === b.sceneIndex) {
+              return a.subTaskTime > b.subTaskTime ? 1 : -1;
+            } else {
+              return a.sceneIndex - b.sceneIndex;
+            }
+          } else if (order == 'descending') {
+            if (a.sceneIndex === b.sceneIndex) {
+              return b.subTaskTime > a.subTaskTime ? 1 : -1;
+            } else {
+              return b.sceneIndex - a.sceneIndex;
+            }
+          }
+        });
+      } else if (prop == 'subTaskTime') {
+        this.tableData.sort((a, b) => {
+          if (order == 'ascending') {
+            return a[prop] > b[prop] ? 1 : -1;
+            // return dayjs(a).isAfter(dayjs(b)) ? 1 : -1;
+          } else if (order == 'descending') {
+            return b[prop] > a[prop] ? 1 : -1;
+            // return dayjs(b).isAfter(dayjs(a)) ? 1 : -1;
+          }
+        });
+      }
+    }
+  },
+  mounted() {
+    this.addRefreshEvent(this.$refs.tableRef.doLayout);
+  }
+};
+</script>
+<style scoped></style>
diff --git a/src/views/analysis/evalution/EvalutationTask.vue b/src/views/analysis/evalution/EvalutationTask.vue
new file mode 100644
index 0000000..239df8c
--- /dev/null
+++ b/src/views/analysis/evalution/EvalutationTask.vue
@@ -0,0 +1,34 @@
+<template>
+  <el-row :gutter="16">
+    <el-col :span="16">
+      <CompPreCheck @start-task="refreshTask"></CompPreCheck>
+    </el-col>
+    <el-col :span="8">
+      <CompEvaTask ref="refEvaTask"></CompEvaTask>
+    </el-col>
+  </el-row>
+</template>
+
+<script>
+import CompEvaTask from './components/CompEvaTask.vue';
+import CompPreCheck from './components/precheck/CompPreCheck.vue';
+export default {
+  name: 'EvalutationTask',
+  components: { CompPreCheck, CompEvaTask },
+  data() {
+    return {};
+  },
+  methods:{
+    refreshTask(){
+      this.$refs.refEvaTask.fetchTask()
+    }
+  }
+};
+</script>
+<style scoped>
+.radius {
+  height: 80vh;
+  /* border: 1px solid var(--el-border-color); */
+  /* border-radius: var(--el-border-radius-base); */
+}
+</style>
diff --git a/src/views/analysis/evalution/components/CompDataResultEdit.vue b/src/views/analysis/evalution/components/CompDataResultEdit.vue
new file mode 100644
index 0000000..8cb5a18
--- /dev/null
+++ b/src/views/analysis/evalution/components/CompDataResultEdit.vue
@@ -0,0 +1,556 @@
+<template>
+  <el-row align="top" justify="space-between">
+    <el-row align="top">
+      <el-upload
+        ref="upload"
+        class="upload-file"
+        :limit="1"
+        accept=".xls,.xlsx"
+        :on-change="handleChange"
+        :on-exceed="handleExceed"
+        :auto-upload="false"
+      >
+        <template #trigger>
+          <el-button type="success" :loading="tableLoading">瀵煎叆鏂囦欢</el-button>
+        </template>
+        <template #tip>
+          <div>
+            <el-text type="danger">{{ tips }}</el-text>
+          </div>
+        </template>
+      </el-upload>
+      <div v-if="tableLoading">
+        <el-icon class="is-loading"><Loading /></el-icon>
+        <el-text>{{ loadTxt }}</el-text>
+      </div>
+    </el-row>
+    <el-button type="default" icon="download" @click="downloadTemplate"
+      >涓嬭浇瀵煎叆妯℃澘</el-button
+    >
+  </el-row>
+  <el-table
+    ref="tableRef"
+    :data="data"
+    v-loading="tableLoading"
+    table-layout="fixed"
+    row-key="id"
+    :expand-row-keys="expandRowKeys"
+    :row-class-name="tableRowClassName"
+    size="small"
+    height="60vh"
+    border
+  >
+    <!-- <el-table-column type="expand">
+      <template #default="{ row }">
+        {{ row.drSceneName }}
+      </template>
+    </el-table-column> -->
+    <el-table-column
+      v-if="isUploadNewFile"
+      prop="isFound"
+      label="鍚堣"
+      width="30"
+    >
+      <template #default="{ row }">
+        <el-icon class="is-loading" v-if="row.loading">
+          <Loading color="#409eff" />
+        </el-icon>
+        <el-icon v-else>
+          <Check v-if="row.isFound" />
+          <Close v-else />
+        </el-icon>
+      </template>
+    </el-table-column>
+    <el-table-column
+      v-if="isUploadNewFile"
+      :show-overflow-tooltip="true"
+      prop="sceneIndex"
+      label="鍞竴缂栧彿"
+      width="70"
+    >
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.sceneIndex"
+          @change="(e) => handleSceneNameChange(e, row)"
+        />
+        <span v-else>{{ row.sceneIndex }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column
+      :show-overflow-tooltip="true"
+      prop="drSceneName"
+      label="鍦烘櫙鍚嶇О"
+    >
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drSceneName"
+        />
+        <span v-else>{{ row.drSceneName }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column
+      v-if="isUploadNewFile"
+      :show-overflow-tooltip="true"
+      prop="drDeviceCode"
+      label="璁惧鍚嶇О"
+    >
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.deviceName"
+        />
+        <span v-else>{{ row.deviceName }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drDeviceCode" label="璁惧鍙�" width="130">
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drDeviceCode"
+        />
+        <span v-else>{{ row.drDeviceCode }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drTime" label="鏃堕棿" width="70">
+      <template #default="{ row }">
+        <span>{{ $fm.formatYM(row.drTime) }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drExceedTimes" label="瓒呮爣娆℃暟" width="50">
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drExceedTimes"
+        />
+        <span v-else>{{ row.drExceedTimes }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drAvg" label="骞冲潎鍊�" width="65">
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drAvg"
+        />
+        <span v-else>{{ row.drAvg }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drMax" label="鏈�澶у��" width="65">
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drMax"
+        />
+        <span v-else>{{ row.drMax }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drMin" label="鏈�灏忓��" width="65">
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drMin"
+        />
+        <span v-else>{{ row.drMin }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drOverAvgPer" label="瓒呭尯鍧囧�肩櫨鍒嗘瘮" width="70">
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drOverAvgPer"
+        />
+        <span v-else>{{ row.drOverAvgPer }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drDataNum" label="鏁版嵁閲�" width="65">
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drDataNum"
+        />
+        <span v-else>{{ row.drDataNum }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column prop="drEffectiveRate" label="鏈夋晥鐜�" width="65">
+      <template #default="{ row }">
+        <el-input
+          v-if="isUploadNewFile && !row.isFound"
+          size="small"
+          v-model="row.drEffectiveRate"
+          placeholder="鍦烘櫙鍚嶇О"
+        />
+        <span v-else>{{ row.drEffectiveRate }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column v-if="isUploadNewFile" type="expand">
+      <template #default="{ row }">
+        <div class="p-v-4">
+          <div v-if="!row.isFound" class="p-h-16">
+            <div v-if="row.notSure">
+              <el-text type="warning" size="small"
+                >鏍规嵁鍞竴缂栧彿鍙婅鏀垮尯鍒掓壘鍒颁簡鐩稿叧鍦烘櫙锛屼絾涓庡凡鏈夊満鏅悕绉颁笉鍖归厤锛岃纭畾鏄摢涓満鏅�</el-text
+              >
+              <div class="m-t-8">
+                <el-button
+                  v-for="(v, i) in row.sourceScene"
+                  :key="v.guid"
+                  type="primary"
+                  text
+                  bg
+                  size="small"
+                  class="m-b-2"
+                  @click="handleRadioChange(v, row)"
+                >
+                  {{ v.name }}
+                </el-button>
+                <!-- <el-radio-group v-model="row.radioValue">
+                <el-radio
+                  v-for="(v, i) in row.sourceScene"
+                  :key="v.guid"
+                  :value="i"
+                  size="small"
+                  border
+                  @change="handleRadioChange(v, row)"
+                  >{{ v.name }}</el-radio
+                >
+              </el-radio-group> -->
+              </div>
+            </div>
+            <div v-else>
+              <el-text type="danger" size="small"
+                >鏍规嵁鍞竴缂栧彿鍙婅鏀垮尯鍒掓湭鎵惧埌鐩稿叧鍦烘櫙锛岃淇敼鍞竴缂栧彿</el-text
+              >
+            </div>
+          </div>
+          <div v-else class="p-h-16">
+            <el-text type="success" size="small"> 宸叉纭尮閰嶅埌璇ュ満鏅� </el-text>
+            <el-text v-if="row.remark" type="success" size="small">
+              {{ '锛�' + row.remark }}
+            </el-text>
+          </div>
+        </div>
+      </template>
+    </el-table-column>
+  </el-table>
+  <el-button
+    class="m-t-8"
+    type="primary"
+    :loading="uploadLoading"
+    :disabled="!isUploadNewFile"
+    icon="upload"
+    @click="uploadFile"
+    >涓婁紶缁熻缁撴灉</el-button
+  >
+</template>
+<script setup>
+import { ref, reactive, watch, onMounted, getCurrentInstance } from 'vue';
+import { useMessageBoxTip, useMessageBox } from '@/composables/messageBox';
+import { genFileId } from 'element-plus';
+import monitordataApi from '@/api/fysp/monitordataApi';
+import sceneApi from '@/api/fysp/sceneApi';
+import * as XLSX from 'xlsx';
+import { exportDocx } from '@/utils/doc';
+
+const cns = getCurrentInstance();
+const $fm = cns.appContext.config.globalProperties.$fm;
+
+const props = defineProps({
+  areaInfo: { type: Object }
+});
+
+let workbook;
+const isUploadNewFile = ref(false);
+const data = ref([]);
+const expandRowKeys = ref([]);
+const upload = ref();
+const tableLoading = ref(false);
+const loadTxt = ref('');
+const tips = ref('');
+const uploadLoading = ref(false);
+
+const tableRowClassName = ({ row, rowIndex }) => {
+  if (row.loading) {
+    return 'loading-row';
+  } else if (row.isFound == undefined) {
+    return '';
+  } else {
+    return row.isFound
+      ? 'success-row'
+      : row.notSure
+        ? 'warning-row'
+        : 'danger-row';
+  }
+};
+
+// 鑾峰彇鍘嗗彶缁熻缁撴灉
+function fetchDustDataResult() {
+  monitordataApi.fetchDustDataResult(props.areaInfo).then((res) => {
+    data.value = res.data;
+  });
+}
+
+function handleExceed(files, uploadFiles) {
+  upload.value.clearFiles();
+  const file = files[0];
+  file.uid = genFileId();
+  upload.value.handleStart(file);
+}
+
+/**
+ * 澶勭悊涓婁紶鏂囦欢瑙f瀽
+ * @param uploadFile
+ * @param uploadFiles
+ */
+function handleChange(uploadFile, uploadFiles) {
+  expandRowKeys.value = [];
+  tableLoading.value = true;
+  loadTxt.value = '鏂囦欢瑙f瀽涓�...';
+  // console.log(uploadFile, uploadFiles);
+  const fileReader = new FileReader();
+  fileReader.onload = (file) => {
+    const fileData = file.target.result;
+    workbook = XLSX.read(fileData, { type: 'array' });
+    console.log(workbook.SheetNames);
+    if (workbook.SheetNames.length == 0) {
+      tips.value = 'excel鏂囦欢閿欒锛屾病鏈塻heet琛ㄥ崟';
+      return;
+    }
+    const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+    const tableData = XLSX.utils.sheet_to_json(worksheet);
+    const _data = tableData.map((v, i) => {
+      return {
+        id: i,
+        sceneIndex: v['鍞竴缂栧彿'],
+        drSceneName: v['鍦烘櫙鍚嶇О'],
+        deviceName: v['璁惧鍚嶇О'],
+        drDeviceCode: v['璁惧鍙�'],
+        drTime: $fm.formatDateFromExcel(v['鏃堕棿'], '-'),
+        drExceedTimes: v['瓒呮爣娆℃暟'],
+        drAvg: v['骞冲潎鍊�'],
+        drMax: v['鏈�澶у��'],
+        drMin: v['鏈�灏忓��'],
+        drOverAvgPer: v['瓒呭尯鍧囧�肩櫨鍒嗘瘮'],
+        drDataNum: v['鏁版嵁閲�'],
+        drEffectiveRate: v['鏈夋晥鐜�']
+      };
+    });
+    data.value = combineSameScene(_data);
+    // console.log(tableData);
+    setTimeout(() => {
+      tableLoading.value = false;
+      isUploadNewFile.value = true;
+      data.value.forEach((d) => {
+        searchScene(d);
+      });
+    }, 1000);
+  };
+  fileReader.readAsArrayBuffer(uploadFile.raw);
+}
+
+/**
+ * 鍚堝苟鐩稿悓鍦烘櫙鐨勫鍙扮洃娴嬭澶囷紝榛樿鍙栧尯鍧囧�兼渶楂樼殑涓�鍙拌澶�
+ */
+function combineSameScene(dataList) {
+  // 鏍规嵁鍦烘櫙鍞竴缂栧彿杩涜鐩稿悓璁惧褰掔被
+  const tempMap = new Map();
+  dataList.forEach((d) => {
+    if (!tempMap.has(d.sceneIndex)) {
+      tempMap.set(d.sceneIndex, []);
+    }
+    tempMap.get(d.sceneIndex).push(d);
+  });
+  const res = [];
+  // 鐩稿悓鍦烘櫙涓嬶紝鍙栧尯鍧囧�兼渶楂樼殑涓�鍙拌澶囦綔涓虹粨鏋�
+  for (const [k, v] of tempMap) {
+    v.sort((a, b) => b.drAvg - a.drAvg);
+    if (v.length > 1) {
+      v[0].remark = `鏈満鏅叡鏈�${v.length}鍙拌澶囷紝宸茶嚜鍔ㄩ�夋嫨鍖哄潎鍊兼渶楂樼殑涓�鍙颁负缁熻缁撴灉`;      
+    }
+    res.push(reactive(v[0]));
+  }
+  return res;
+}
+
+// 鏌ヨ浠庢枃浠朵笂浼犵殑姣忎釜鍦烘櫙鏄惁鑳藉湪绯荤粺涓壘鍒板搴旂殑鍦烘櫙淇℃伅
+function searchScene(d) {
+  d.loading = true;
+  // 鏍规嵁鍦烘櫙鐨勫敮涓�缂栧彿銆佽鏀垮尯鍒掑拰鍦烘櫙绫诲瀷杩涜鏌ユ壘
+  if (!d.sceneIndex) {
+    d.isFound = false;
+    d.loading = false;
+    return Promise;
+  } else {
+    sceneApi
+      .findScene({
+        // name: d.drSceneName,
+        typeid: props.areaInfo.scensetypeid,
+        provincecode: props.areaInfo.provincecode,
+        citycode: props.areaInfo.citycode,
+        districtcode: props.areaInfo.districtcode,
+        towncode: props.areaInfo.towncode,
+        index: d.sceneIndex,
+        // 绛涢�夋煡璇㈠湪绾跨殑鍦烘櫙
+        extension1: '1'
+      })
+      .then((res) => {
+        setTimeout(() => {
+          if (res.length > 0) {
+            // 1. 鏍规嵁鍦烘櫙鍞竴缂栧彿杩涜鏌ヨ鏃讹紝涓�鑸儏鍐典笅搴旇鍙湁涓�涓敮涓�缁撴灉
+            // 2-1. 褰撲笂浼犳枃浠朵腑鍦烘櫙鍚嶇О涓虹┖鐧芥椂锛岃嚜鍔ㄥ尮閰嶆煡璇㈡墍寰楀満鏅�
+            // 2-2. 褰撲笂浼犳枃浠朵腑鍦烘櫙鍚嶇О涓嶄负绌虹櫧鏃讹紝姣斿涓よ�呯殑鍦烘櫙鍚嶇О锛岃嫢涓嶅悓鍒欒繘琛岃鍛婃彁绀�
+            if (res.length == 1) {
+              const findRes = res[0];
+              if (!d.drSceneName || d.drSceneName == '') {
+                d.drSceneId = findRes.guid;
+                d.drSceneName = findRes.name;
+                d.isFound = true;
+                d.notSure = false;
+                if (d.remark) expandRowKeys.value.push(d.id);
+              } else {
+                if (d.drSceneName == findRes.name) {
+                  d.drSceneId = findRes.guid;
+                  d.isFound = true;
+                  d.notSure = false;
+                } else {
+                  d.isFound = false;
+                  d.notSure = true;
+                  expandRowKeys.value.push(d.id);
+                }
+              }
+            } else {
+              d.isFound = false;
+              d.notSure = true;
+              expandRowKeys.value.push(d.id);
+            }
+            d.sourceScene = res;
+
+            // const findRes = res.find((v) => v.name == d.drSceneName);
+            // if (findRes) {
+            //   d.drSceneId = findRes.guid;
+            //   d.isFound = true;
+            // } else {
+            //   d.isFound = false;
+            //   d.notSure = true;
+            //   expandRowKeys.value.push(d.id);
+            // }
+            // d.sourceScene = res;
+          } else {
+            d.isFound = false;
+            d.notSure = false;
+            expandRowKeys.value.push(d.id);
+          }
+          d.loading = false;
+        }, 1000);
+      })
+      .finally(() => {
+        setTimeout(() => {
+          d.loading = false;
+        }, 1000);
+      });
+  }
+}
+
+function handleSceneNameChange(newName, row) {
+  searchScene(row);
+}
+
+function handleRadioChange(value, row) {
+  const scene = value;
+  row.sceneIndex = scene.index;
+  row.drSceneId = scene.guid;
+  row.drSceneName = scene.name;
+  searchScene(row);
+}
+
+// 涓婁紶缁熻缁撴灉鏂囨。
+function uploadFile() {
+  useMessageBoxTip({
+    confirmMsg: `鏄惁纭涓婁紶锛焋,
+    confirmTitle: '涓婁紶',
+    onConfirm: () => {
+      uploadLoading.value = true;
+      return monitordataApi
+        .uploadDustDataResult(data.value)
+        .finally(() => (uploadLoading.value = false));
+    }
+  });
+}
+
+/**
+ * 涓嬭浇妯℃澘鏂囦欢
+ */
+function downloadTemplate() {
+  const fName = '鎵皹鐩戞祴鏁版嵁鏈堝害缁熻妯℃澘.xlsx';
+  const path = `/${fName}`;
+  const link = document.createElement('a');
+  link.href = path;
+  link.download = fName;
+  document.body.appendChild(link);
+  link.click();
+  document.body.removeChild(link);
+}
+
+onMounted(() => {
+  fetchDustDataResult();
+});
+</script>
+<style scoped>
+.upload-file {
+  /* background-color: aliceblue; */
+  width: 300px;
+  min-height: 60px;
+}
+
+:deep(.el-text) {
+  align-self: auto;
+}
+
+:deep(.el-table__expanded-cell) {
+  padding: 0;
+  /* background-color: var(--el-bg-color-page); */
+}
+
+/* :deep(.el-table__body tr>td.hover-cell) {
+  background-color: red !important;
+} */
+/* .el-table--enable-row-hover
+  .el-table__body
+  tr:hover
+  > td
+  :deep(.el-table__cell) {
+  background-color: red !important;
+} */
+</style>
+<style>
+.el-table .warning-row {
+  --el-table-tr-bg-color: var(--el-color-warning-light-5);
+}
+.el-table .success-row {
+  --el-table-tr-bg-color: var(--el-color-success-light-7);
+}
+.el-table .danger-row {
+  --el-table-tr-bg-color: var(--el-color-danger-light-5);
+}
+.el-table .loading-row {
+  color: var(--el-text-color-disabled);
+  /* --el-table-tr-bg-color: var(--el-text-color-placeholder); */
+  --el-table-tr-bg-color: var(--el-bg-color);
+}
+/* .el-table__body tr>td.hover-cell {
+  background-color: red !important;
+} */
+/* .el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell {
+  background-color: unset;
+} */
+</style>
diff --git a/src/views/analysis/evalution/components/CompEvaTask.vue b/src/views/analysis/evalution/components/CompEvaTask.vue
new file mode 100644
index 0000000..dd1dd53
--- /dev/null
+++ b/src/views/analysis/evalution/components/CompEvaTask.vue
@@ -0,0 +1,144 @@
+<template>
+  <el-card shadow="never" :body-style="{ padding: 0 }">
+    <template #header>
+      <el-row justify="space-between">
+        <div>
+          <div><el-text tag="b" size="large">鑷姩璇勪及浠诲姟</el-text></div>
+          <el-text size="small" type="info">鏄剧ず褰撳墠姝e湪杩涜鐨勮嚜鍔ㄨ瘎浼颁换鍔$姸鎬�</el-text>
+        </div>
+        <el-button
+          icon="Refresh"
+          type="primary"
+          size="default"
+          :loading="loading"
+          @click="fetchTask"
+          >鍒锋柊浠诲姟</el-button
+        >
+      </el-row>
+      <!-- <el-row>
+        <el-button type="default" size="default" @click="newTestTask">鏂板娴嬭瘯浠诲姟</el-button>
+        <el-button type="default" size="default" @click="startNewTestTask"
+          >鏂板缓骞惰繍琛屼竴涓祴璇曚换鍔�</el-button
+        >
+        <el-button type="default" size="default" @click="shutDownTask"
+          >寮哄埗鍏抽棴鎵�鏈夋祴璇曚换鍔�</el-button
+        >
+      </el-row> -->
+    </template>
+    <el-scrollbar height="70vh" class="scrollbar">
+      <template v-for="(v, i) in taskList" :key="i">
+        <FYBgTaskItem
+          :model="v"
+          :index="i"
+          @start="startTask"
+          @shutDown="shutDownTask"
+          @remove="removeTask"
+          @gotoResult="gotoResult"
+        ></FYBgTaskItem>
+      </template>
+    </el-scrollbar>
+  </el-card>
+</template>
+<script>
+/**
+ * 鑷姩璇勪及浠诲姟绠$悊
+ */
+import { useFetchData } from '@/composables/fetchData';
+import bgtaskApi from '@/api/fysp/bgtaskApi';
+import { enumBgTask, BG_TASK_TYPE } from '@/enum/bgTask';
+
+export default {
+  setup() {
+    const { loading, fetchData } = useFetchData();
+    return { loading, fetchData };
+  },
+  data() {
+    return {
+      taskList: [],
+      taskIndex: 0
+    };
+  },
+  methods: {
+    addTask(){
+
+    },
+    newTestTask() {
+      this.fetchData((page, pageSize) => {
+        return bgtaskApi.newTestTask(`Test-Task-${++this.taskIndex}`).then((res) => {
+          this.taskList.push(res.data);
+        });
+      });
+    },
+    startNewTestTask() {
+      this.fetchData((page, pageSize) => {
+        return bgtaskApi.startNewTestTask(`Test-Task-${++this.taskIndex}`).then((res) => {
+          this.taskList.push(res.data);
+        });
+      });
+    },
+
+    _getParam(taskStatus) {
+      return {
+        type: taskStatus.type,
+        id: taskStatus.id
+      };
+    },
+    fetchTask() {
+      this.fetchData((page, pageSize) => {
+        return bgtaskApi
+          .fetchTaskStatus({
+            type: BG_TASK_TYPE.AUTO_SCORE.name
+          })
+          .then((res) => {
+            this.taskList = res.data;
+          });
+      });
+    },
+    startTask(index, callback) {
+      this.fetchData((page, pageSize) => {
+        const param = this._getParam(this.taskList[index]);
+        return bgtaskApi.startTask(param).then((res) => {
+          this.taskList[index] = res.data;
+          callback(true);
+        });
+      });
+    },
+    shutDownTask(index, callback) {
+      this.fetchData((page, pageSize) => {
+        const param = this._getParam(this.taskList[index]);
+        return bgtaskApi.shutDownTask(param).then((res) => {
+          if (index && res.data && res.data.length == 1) {
+            this.taskList[index] = res.data[0];
+          } else {
+            res.data.forEach((e) => {
+              let v = this.taskList.find((value) => {
+                return value.id == e.id;
+              });
+              const i = this.taskList.indexOf(v);
+              this.taskList[i] = e;
+            });
+          }
+          callback(true);
+        });
+      });
+    },
+    removeTask(index, callback) {
+      this.fetchData((page, pageSize) => {
+        const param = this._getParam(this.taskList[index]);
+        return bgtaskApi.removeTask(param).then((res) => {
+          if (res.data) {
+            this.taskList.splice(index, 1);
+            callback(true);
+          }
+        });
+      });
+    },
+    gotoResult(index) {}
+  }
+};
+</script>
+<style scoped>
+.scrollbar {
+  padding: 8px;
+}
+</style>
diff --git a/src/views/analysis/evalution/components/CompReport.vue b/src/views/analysis/evalution/components/CompReport.vue
new file mode 100644
index 0000000..7d1a7b0
--- /dev/null
+++ b/src/views/analysis/evalution/components/CompReport.vue
@@ -0,0 +1,101 @@
+<template>
+  <el-button icon="Download" type="success" @click="dialogVisible = true"
+    >瑙勮寖鎬ц瘎浼颁笌鍒嗘瀽鎶ュ憡</el-button
+  >
+  <el-dialog
+    v-model="dialogVisible"
+    title="瑙勮寖鎬ц瘎浼颁笌鍒嗘瀽鎶ュ憡鐢熸垚"
+    width="500"
+  >
+    <el-text tag="b" size="large">鏁版嵁鑼冨洿纭</el-text>
+    <el-text tag="div">鍖哄煙锛歿{ locationText }}</el-text>
+    <el-text tag="div">绫诲瀷锛歿{ scenetype.label }}</el-text>
+    <el-text tag="div">鏃堕棿锛歿{ timeText }}</el-text>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-row align="middle">
+          <el-checkbox v-model="forceUpdate" label="寮哄埗鐢熸垚鏂版姤鍛�" />
+          <el-tooltip placement="bottom-start" effect="light">
+            <template #content>
+              <el-text tag="b" size="small">涓嶅嬀閫夛細</el-text><br />
+              <el-text size="small"
+                >涓嶅嬀閫夋椂锛屽鏋滃凡鐢熸垚杩囩浉鍚屽尯鍩熺殑鎶ュ憡锛屽垯鐩存帴鑾峰彇璇ヤ唤鎶ュ憡璁板綍</el-text
+              ><br />
+              <el-text tag="b" size="small">鍕鹃�夛細</el-text><br />
+              <el-text size="small"
+                >鍕鹃�夋椂锛屾棤璁烘槸鍚︽湁鍘嗗彶璁板綍锛岄兘浼氬惎鍔ㄦ姤鍛婄敓鎴愪换鍔¤鐩栨棫璁板綍锛屽彲鍦ㄥ悗鍙颁换鍔$晫闈㈡煡鐪嬩换鍔¤繘搴�</el-text
+              ><br />
+            </template>
+            <el-icon class="m-l-8 cursor-p" :size="16" color="var(--el-color-warning)"
+              ><QuestionFilled
+            /></el-icon>
+          </el-tooltip>
+        </el-row>
+        <div>
+          <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+          <el-button type="primary" @click="download">纭畾</el-button>
+        </div>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+import dayjs from 'dayjs';
+import evaluateApi from '@/api/fysp/evaluateApi';
+
+export default {
+  props: ['locations', 'time', 'scenetype'],
+  data() {
+    return {
+      dialogVisible: false,
+      forceUpdate: false
+    };
+  },
+  computed: {
+    locationText() {
+      const loc = this.locations;
+      let text = '';
+      text = loc.pName == loc.cName ? loc.pName : loc.pName + loc.cName;
+      text += loc.dName;
+      return text;
+    },
+    timeText() {
+      return dayjs(this.time).format('YYYY骞碝M鏈�');
+    }
+  },
+  methods: {
+    // 瑙勮寖鎬ц瘎浼颁笌鍒嗘瀽鎶ュ憡鍚庡彴鐢熸垚浠诲姟
+    download() {
+      const locations = this.locations;
+      const time = this.time;
+      const scenetype = this.scenetype;
+      const area = {
+        provincecode: locations.pCode,
+        provincename: locations.pName,
+        citycode: locations.cCode,
+        cityname: locations.cName,
+        districtcode: locations.dCode,
+        districtname: locations.dName,
+        starttime: dayjs(this.time).format('YYYY-MM-DD HH:mm:ss'),
+        scensetypeid: scenetype.value
+      };
+      evaluateApi.downloadAutoEvaluation(area, this.forceUpdate).then((res) => {
+        if (res == false) {
+          // 鏈笅杞芥枃妗o紝鑰屾槸寮�鍚簡鏂囨。鐢熸垚鍚庡彴浠诲姟
+          this.$parent;
+        }
+        this.dialogVisible = false;
+      });
+    }
+  }
+};
+</script>
+<style scoped>
+.dialog-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 2px;
+}
+</style>
diff --git a/src/views/analysis/evalution/components/precheck/CompPreCheck.vue b/src/views/analysis/evalution/components/precheck/CompPreCheck.vue
new file mode 100644
index 0000000..3dba2b8
--- /dev/null
+++ b/src/views/analysis/evalution/components/precheck/CompPreCheck.vue
@@ -0,0 +1,97 @@
+<template>
+  <el-steps :active="stepIndex" finish-status="success" style="" align-center>
+    <el-step title="璇勪及鑼冨洿" />
+    <el-step title="鏁版嵁婧愭鏌�" />
+    <el-step title="鏉$洰璞佸厤" />
+    <el-step title="鑷姩璇勪及" />
+  </el-steps>
+  <CompCheckArea v-show="stepIndex == 0" v-model="stepIndex" @change="onAreaChange"></CompCheckArea>
+  <CompCheckSource
+    v-show="stepIndex == 1"
+    v-model="stepIndex"
+    ref="refSource"
+    @change="onDataSourceChange"
+  ></CompCheckSource>
+  <CompCheckExemption
+    v-show="stepIndex == 2"
+    v-model="stepIndex"
+    @change="onExemptionChange"
+  ></CompCheckExemption>
+  <CompCheckConfirm
+    v-show="stepIndex == 3"
+    v-model="stepIndex"
+    :area-info="area"
+    :data-source="dataSource"
+    :exemption-items="exemptionItems"
+    @start="onNewTask"
+  ></CompCheckConfirm>
+</template>
+
+<script>
+import dayjs from 'dayjs';
+import CompCheckArea from './components/CompCheckArea.vue';
+import CompCheckSource from './components/CompCheckSource.vue';
+import CompCheckExemption from './components/CompCheckExemption.vue';
+import CompCheckConfirm from './components/CompCheckConfirm.vue';
+
+/**
+ * 鑷姩璇勪及鏉′欢鍚堣鎬ф鏌�
+ */
+export default {
+  name: 'CompPreCheck',
+  components: { CompCheckArea, CompCheckSource, CompCheckExemption, CompCheckConfirm },
+  props: {},
+  emits: ['startTask'],
+  data() {
+    return {
+      // 鎿嶄綔姝ラ涓嬫爣
+      stepIndex: 0,
+      area: {
+        _locations: {},
+        _scenetype: {}
+      },
+      dataSource: {},
+      // 璞佸厤鏉$洰
+      exemptionItems: {}
+    };
+  },
+  methods: {
+    /**
+     * 鐩戝惉璇勪及鑼冨洿鍙樻洿
+     */
+    onAreaChange(val) {
+      const v = val.value;
+      this.area = v;
+      const a = {
+        provincecode: v._locations.pCode,
+        provincename: v._locations.pName,
+        citycode: v._locations.cCode,
+        cityname: v._locations.cName,
+        districtcode: v._locations.dCode,
+        districtname: v._locations.dName,
+        towncode: v._locations.tCode,
+        townname: v._locations.tName,
+        starttime: this.$fm.formatYMDHMS(v.time),
+        endtime: this.$fm.formatYMDHMS(v.time),
+        scensetypeid: v._scenetype.value,
+        online: true,
+        sourceType: v.sourceType
+      };
+      this.$refs.refSource.startCheck(a);
+    },
+    onDataSourceChange(val) {
+      this.dataSource = val;
+    },
+    onExemptionChange(val) {
+      this.exemptionItems = val;
+    },
+    /**
+     * 鑷姩璇勪及鍓嶇疆鍚堣鎬ф鏌�
+     * 妫�鏌ユ墍閫夎寖鍥村唴鍚勯」璇勪及鏁版嵁婧愭槸鍚﹀畬鏁�
+     */
+    onNewTask() {
+      this.$emit('startTask');
+    }
+  }
+};
+</script>
diff --git a/src/views/analysis/evalution/components/precheck/components/CompCheckArea.vue b/src/views/analysis/evalution/components/precheck/components/CompCheckArea.vue
new file mode 100644
index 0000000..b2ce350
--- /dev/null
+++ b/src/views/analysis/evalution/components/precheck/components/CompCheckArea.vue
@@ -0,0 +1,138 @@
+<template>
+  <el-card shadow="never">
+    <template #header>
+      <div><el-text tag="b" size="large">閫夋嫨璇勪及鑼冨洿</el-text></div>
+      <el-text size="small" type="info">鍖呮嫭鍖哄幙銆佸満鏅被鍨嬩互鍙婃湀浠�</el-text>
+    </template>
+    <FormCol>
+      <FYForm ref="formRef" :rules="evaConditionRules" :showButtons="false" @submit="nextStep">
+        <template #form-item="{ formObj }">
+          <CompQuickSet @quick-set="setOptions"></CompQuickSet>
+          <el-form-item label="涓绘暟鎹簮" prop="sourceType">
+            <el-radio-group
+              v-model="formObj.sourceType"
+              size="small"
+              @change="sceneOptionSourceInit = true"
+            >
+              <el-radio-button :value="1">瀹堟硶鏈嶅姟璁板綍</el-radio-button>
+              <el-radio-button :value="2">鐜板満宸℃煡璁板綍</el-radio-button>
+            </el-radio-group>
+            <el-tooltip placement="bottom-start" effect="light">
+              <template #content>
+                <el-text tag="i" size="default" type="warning"
+                  >璇ラ�夐」鏄敤浜庡喅瀹氳瘎浼颁富浣撳璞$殑鑾峰彇鏂瑰紡</el-text
+                ><br />
+                <el-text tag="b" size="small">瀹堟硶鏈嶅姟璁板綍锛�</el-text><br />
+                <el-text size="small"
+                  >琛ㄧず鍦ㄨ瘎浼版椂锛岃瘎浼板璞℃槸浠庡畧娉曟湇鍔″皬绋嬪簭绯荤粺涓幏鍙栫殑褰撳墠鍙敤鐨勭敤鎴凤紱<br />
+                  涓�鑸儏鍐典笅锛屽綋璇勪及瀵硅薄娌℃湁杩涜鐜板満宸℃煡锛屽彧鏈夊畧娉曟湇鍔$浉鍏宠褰曟椂锛岄噰鐢ㄦ閫夐」锛�</el-text
+                ><br />
+                <el-text tag="b" size="small">鐜板満宸℃煡璁板綍锛�</el-text><br />
+                <el-text size="small"
+                  >琛ㄧず鍦ㄨ瘎浼版椂锛岃瘎浼板璞℃槸浠庣幇鍦哄贰鏌ョ洃绠$郴缁熶腑鑾峰彇鐨勬�讳换鍔′笅鐨勬墍鏈夌洃绠″満鏅紱<br />
+                  涓�鑸儏鍐典笅锛屽綋璇勪及瀵硅薄鏈夎繘琛岀幇鍦哄贰鏌ワ紝閲囩敤姝ら�夐」锛� </el-text
+                ><br />
+              </template>
+              <el-icon class="m-l-8 cursor-p" :size="16" color="var(--el-color-warning)"
+                ><QuestionFilled
+              /></el-icon>
+            </el-tooltip>
+          </el-form-item>
+          <!-- 鍖哄幙 -->
+          <FYOptionLocation
+            :allOption="false"
+            :level="3"
+            :initValue="false"
+            :checkStrictly="false"
+            v-model:value="formObj._locations"
+          ></FYOptionLocation>
+          <!-- 鍦烘櫙绫诲瀷 -->
+          <FYOptionScene
+            :allOption="false"
+            :initValue="false"
+            :sourceInit="sceneOptionSourceInit"
+            :type="formObj.sourceType"
+            v-model:value="formObj._scenetype"
+          ></FYOptionScene>
+          <!-- 鏃堕棿 -->
+          <FYOptionTime
+            prop="time"
+            :initValue="true"
+            type="month"
+            v-model:value="formObj.time"
+          ></FYOptionTime>
+        </template>
+      </FYForm>
+    </FormCol>
+    <template #footer>
+      <el-row justify="space-around">
+        <el-button type="primary" size="default" :loading="loading" @click="submit"
+          >涓嬩竴姝�</el-button
+        >
+      </el-row>
+    </template>
+  </el-card>
+</template>
+
+<script>
+
+/**
+ * 璇勪及鑼冨洿鍚堣鎬ф鏌�
+ */
+export default {
+  props: {
+    // 姝ラ涓嬫爣
+    modelValue: Number
+  },
+  emits: ['update:modelValue', 'change'],
+  data() {
+    return {
+      loading: false,
+      evaConditionRules: {
+        time: [
+          {
+            required: true,
+            message: '鏃堕棿涓嶈兘涓虹┖',
+            trigger: 'change'
+          }
+        ],
+        sourceType: [
+          {
+            required: true,
+            message: '涓绘暟鎹簮蹇呴』閫夋嫨',
+            trigger: 'change'
+          }
+        ]
+      },
+      // 褰撳満鏅�夐」鍒囨崲鏁版嵁婧愭椂锛屾槸鍚︽竻绌哄綋鍓嶉�夐」鍊�
+      sceneOptionSourceInit: true
+    };
+  },
+  methods: {
+    setOptions(param) {
+      this.sceneOptionSourceInit = false;
+      this.$refs.formRef.formObj._locations = param.locations;
+      this.$refs.formRef.formObj._scenetype = param.scenetype;
+      this.$refs.formRef.formObj.sourceType = param.sourceType;
+    },
+    submit() {
+      this.$refs.formRef.onSubmit(false);
+    },
+    // 璺宠浆涓嬩竴姝�
+    nextStep(formObj, success, fail) {
+      // todo: 妫�鏌ユ槸鍚﹀凡鏈夎瘎浼拌褰曪紝鎻愮ず鐢ㄦ埛鍙洿鎺ヨ烦杞煡鐪嬫垨缁х画涓嬩竴姝�
+
+      this.loading = true;
+      return new Promise((reslove, reject) => {
+        setTimeout(() => {
+          this.$emit('change', formObj);
+          this.$emit('update:modelValue', this.modelValue + 1);
+          this.loading = false;
+          success();
+          reslove();
+        }, 1000);
+      });
+    }
+  }
+};
+</script>
diff --git a/src/views/analysis/evalution/components/precheck/components/CompCheckConfirm.vue b/src/views/analysis/evalution/components/precheck/components/CompCheckConfirm.vue
new file mode 100644
index 0000000..7c6deb2
--- /dev/null
+++ b/src/views/analysis/evalution/components/precheck/components/CompCheckConfirm.vue
@@ -0,0 +1,108 @@
+<template>
+  <el-card shadow="never">
+    <template #header>
+      <div><el-text tag="b" size="large">鑷姩璇勪及纭</el-text></div>
+      <el-text size="small" type="info">鏈�缁堢‘璁よ嚜鍔ㄨ瘎浼板悇閰嶇疆椤癸紝骞跺紑濮嬭嚜鍔ㄨ瘎浼�</el-text>
+    </template>
+    <el-form label-width="160px" label-position="left">
+      <div class="m-b-16"><el-text tag="b" size="large">璇勪及鑼冨洿</el-text></div>
+      <el-form-item label="鍖哄煙">
+        <el-text size="default">{{ areaInfo._locations.pName }}</el-text>
+        <el-text size="default">{{ areaInfo._locations.cName }}</el-text>
+        <el-text size="default">{{ areaInfo._locations.dName }}</el-text>
+        <el-text size="default">{{ areaInfo.starttime }}</el-text>
+      </el-form-item>
+      <el-form-item label="鏃堕棿">
+        <el-text size="default">{{ $fm.formatYM(areaInfo.time) }}</el-text>
+      </el-form-item>
+      <el-form-item label="鍦烘櫙">
+        <el-text size="default">{{ areaInfo._scenetype.label }}</el-text>
+      </el-form-item>
+      <el-divider />
+      <div class="m-b-16"><el-text tag="b" size="large">璇勪及鏁版嵁婧愬畬鏁村害</el-text></div>
+      <el-form-item :label="v.name" v-for="v in dataSource" :key="v.name">
+        <template v-if="v.pass == true">
+          <el-icon color="var(--el-color-success)"><Check /></el-icon>
+          <el-text size="default" type="success">閫氳繃</el-text>
+        </template>
+        <template v-else-if="v.pass == false">
+          <el-icon color="var(--el-color-danger)"><Close /></el-icon>
+          <el-text size="default" type="danger">缂哄け</el-text>
+        </template>
+        <template v-else>
+          <el-icon color="var(--el-color-warning)"><Warning /></el-icon>
+          <el-text size="default" type="warning">鏆傜暐杩�</el-text>
+        </template>
+      </el-form-item>
+      <el-divider />
+      <div class="m-b-16"><el-text tag="b" size="large">璞佸厤鏉$洰</el-text></div>
+      <div class="m-b-16"><el-text size="default">鏃犺眮鍏嶆潯鐩�</el-text></div>
+    </el-form>
+
+    <template #footer>
+      <el-row justify="space-around">
+        <el-button type="primary" size="default" @click="lastStep">涓婁竴姝�</el-button>
+        <el-button type="primary" size="default" @click="startEvaluate">寮�濮嬭瘎浼�</el-button>
+      </el-row>
+    </template>
+  </el-card>
+</template>
+
+<script>
+import evaluateApi from '@/api/fysp/evaluateApi';
+
+/**
+ * 璇勪及浠诲姟鏈�缁堝紑鍚‘璁�
+ */
+export default {
+  props: {
+    // 姝ラ涓嬫爣
+    modelValue: Number,
+    // 鍖哄煙淇℃伅
+    areaInfo: {
+      type: Object,
+      default: () => {
+        return {
+          _locations: {},
+          _scenetype: {}
+        };
+      }
+    },
+    // 鏁版嵁婧愬畬鏁村害鎯呭喌
+    dataSource: Array,
+    // 鏉$洰璞佸厤鎯呭喌
+    exemptionItems: Array
+  },
+  emits: ['update:modelValue', 'start'],
+  data() {
+    return {};
+  },
+  methods: {
+    // 寮�鍚嚜鍔ㄨ瘎浼颁换鍔�
+    startEvaluate() {
+      const v = this.areaInfo
+      const a = {
+        provincecode: v._locations.pCode,
+        provincename: v._locations.pName,
+        citycode: v._locations.cCode,
+        cityname: v._locations.cName,
+        districtcode: v._locations.dCode,
+        districtname: v._locations.dName,
+        towncode: v._locations.tCode,
+        townname: v._locations.tName,
+        starttime: this.$fm.formatYMDHMS(v.time),
+        scensetypeid: v._scenetype.value,
+        online: true,
+        sourceType: v.sourceType
+      };
+      evaluateApi.autoEvaluate(a).then((res) => {
+        this.$emit('start', res.data);
+      });
+    },
+    // 璺宠浆涓婁竴姝�
+    lastStep() {
+      this.$emit('update:modelValue', this.modelValue - 1);
+    }
+  }
+};
+</script>
diff --git a/src/views/analysis/evalution/components/precheck/components/CompCheckExemption.vue b/src/views/analysis/evalution/components/precheck/components/CompCheckExemption.vue
new file mode 100644
index 0000000..5578e54
--- /dev/null
+++ b/src/views/analysis/evalution/components/precheck/components/CompCheckExemption.vue
@@ -0,0 +1,45 @@
+<template>
+  <el-card shadow="never">
+    <template #header>
+      <div><el-text tag="b" size="large">鏉$洰璞佸厤</el-text></div>
+      <el-text size="small" type="info">鑷畾涔夎缃娆¤瘎浼颁笉鍙備笌璁$畻鐨勬潯鐩�</el-text>
+    </template>
+    璞佸厤鏉$洰鍔熻兘鏆傛湭瀹屾垚锛岃鐩存帴鐐瑰嚮涓嬩竴姝�
+    <template #footer>
+      <el-row justify="space-around">
+        <el-button type="primary" size="default" @click="lastStep">涓婁竴姝�</el-button>
+        <el-button type="primary" size="default" @click="nextStep">涓嬩竴姝�</el-button>
+      </el-row>
+    </template>
+  </el-card>
+</template>
+
+<script>
+/**
+ * 璇勪及鏉$洰璞佸厤璁惧畾
+ */
+export default {
+  props: {
+    // 姝ラ涓嬫爣
+    modelValue: Number
+  },
+  emits: ['update:modelValue', 'change'],
+  data() {
+    return {
+      // 璞佸厤鏉$洰
+      exemptionItems: []
+    };
+  },
+  methods: {
+    // 璺宠浆涓嬩竴姝�
+    nextStep() {
+      this.$emit('change', this.exemptionItems);
+      this.$emit('update:modelValue', this.modelValue + 1);
+    },
+    // 璺宠浆涓嬩竴姝�
+    lastStep() {
+      this.$emit('update:modelValue', this.modelValue - 1);
+    }
+  }
+};
+</script>
diff --git a/src/views/analysis/evalution/components/precheck/components/CompCheckSource.vue b/src/views/analysis/evalution/components/precheck/components/CompCheckSource.vue
new file mode 100644
index 0000000..2103658
--- /dev/null
+++ b/src/views/analysis/evalution/components/precheck/components/CompCheckSource.vue
@@ -0,0 +1,334 @@
+<template>
+  <div>
+    <el-card shadow="never">
+      <template #header>
+        <div><el-text tag="b" size="large">鏁版嵁婧愭鏌�</el-text></div>
+        <el-text size="small" type="info">妫�鏌ヨ瘎浼版墍闇�鏁版嵁婧愭槸鍚﹀畬鏁�</el-text>
+      </template>
+      <FormCol>
+        <template v-for="(v, i) in checkResults" :key="i">
+          <el-row class="h-small" align="middle">
+            <el-col :span="14">
+              <el-row align="middle">
+                <el-text
+                  size="default"
+                  :class="v.required ? 'required' : 'not-required'"
+                  >*</el-text
+                >
+                <el-text size="default" class="m-l-4">{{ v.name }}</el-text>
+              </el-row>
+            </el-col>
+            <el-col :span="5">
+              <el-row align="middle">
+                <el-space>
+                  <template v-if="v.loading">
+                    <el-icon class="is-loading"><Loading /></el-icon>
+                    <el-text size="default" type="default">妫�鏌ヤ腑...</el-text>
+                  </template>
+                  <template v-else-if="v.pass == true">
+                    <el-icon color="var(--el-color-success)"><Check /></el-icon>
+                    <el-text size="default" type="success">閫氳繃</el-text>
+                  </template>
+                  <template v-else-if="v.pass == false">
+                    <el-icon color="var(--el-color-danger)"><Close /></el-icon>
+                    <el-text size="default" type="danger">缂哄け</el-text>
+                  </template>
+                  <template v-else>
+                    <el-icon color="var(--el-color-warning)"
+                      ><Warning
+                    /></el-icon>
+                    <el-text size="default" type="warning">鏆傜暐杩�</el-text>
+                  </template>
+                </el-space>
+              </el-row>
+            </el-col>
+            <el-col :span="5">
+              <el-button
+                v-show="!v.loading"
+                :type="v.pass ? '' : 'danger'"
+                size="small"
+                @click="goto(v.path)"
+                :disabled="v.path == ''"
+              >
+                {{ v.pass ? '鍘讳慨鏀�' : '鍘诲畬鍠�' }}
+                <el-icon class="m-l-4"><Right /></el-icon>
+              </el-button>
+            </el-col>
+          </el-row>
+          <el-row align="middle" class="m-b-16">
+            <el-text size="small" class="not-required">*</el-text>
+            <el-text size="small" class="m-l-4 color-i">{{ v.des }}</el-text>
+          </el-row>
+        </template>
+      </FormCol>
+      <template #footer>
+        <el-row justify="space-around">
+          <el-button type="primary" size="default" @click="lastStep"
+            >涓婁竴姝�</el-button
+          >
+          <el-button
+            :disabled="!checkPass"
+            type="primary"
+            size="default"
+            @click="nextStep"
+            >涓嬩竴姝�</el-button
+          >
+        </el-row>
+      </template>
+    </el-card>
+    <el-dialog
+      title="鎵皹鐩戞祴鏁版嵁鏈堝害缁熻绠$悊"
+      v-model="dialog1"
+      destroy-on-close
+      width="90%"
+    >
+      <CompDataResultEdit :areaInfo="areaInfo"></CompDataResultEdit>
+      <template #footer> </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import evaluateApi from '@/api/fysp/evaluateApi';
+import taskApi from '@/api/fysp/taskApi';
+import userMapApi from '@/api/fysp/userMapApi';
+import problemApi from '@/api/fysp/problemApi';
+import monitordataApi from '@/api/fysp/monitordataApi';
+import complaintApi from '@/api/fytz/complaintApi';
+import CompDataResultEdit from '../../CompDataResultEdit.vue';
+
+/**
+ * 鐢熸垚涓�椤规暟鎹簮妫�鏌ユ潯鐩�
+ * @param {*} _name 鏉$洰鍚嶇О
+ * @param {*} _path 璺宠浆椤甸潰URL
+ * @param {*} _fetch 鏉$洰鐨勭綉缁滆姹傚嚱鏁�
+ * @param {*} _required 鏄惁蹇呴��
+ */
+function baseCheckItem(_name, _path, _fetch, _required) {
+  return {
+    required: _required,
+    name: _name,
+    loading: true,
+    pass: false,
+    path: _path,
+    des: '',
+    async fetch() {
+      this.loading = true;
+      setTimeout(async () => {
+        if (typeof _fetch === 'function') {
+          _fetch()
+            .then((res) => {
+              this.pass = res ? res.pass : undefined;
+              this.des = res ? res.des : undefined;
+            })
+            .catch(() => {
+              this.pass = false;
+              this.des = '缃戠粶閾炬帴閿欒';
+            })
+            .finally(() => {
+              this.loading = false;
+            });
+        } else {
+          this.pass = undefined;
+          this.des = undefined;
+          this.loading = false;
+        }
+      }, 1000);
+    }
+  };
+}
+
+/**
+ * 璇勪及鏁版嵁婧愬畬鏁存�ф鏌�
+ */
+export default {
+  components: {
+    CompDataResultEdit
+  },
+  props: {
+    // 姝ラ涓嬫爣
+    modelValue: Number
+  },
+  emits: ['update:modelValue', 'change'],
+  data() {
+    return {
+      areaInfo: {},
+      // 鏁版嵁婧愭鏌ヨ褰�
+      checkResults: [
+        // 鍖哄煙鑼冨洿鍐呯殑鑷姩璇勪及瑙勫垯琛ㄦ槸鍚﹀瓨鍦�
+        baseCheckItem(
+          '鑷姩璇勪及瑙勫垯琛�',
+          '',
+          () => {
+            const param = {
+              taskTypeId: 99,
+              ...this.areaInfo
+            };
+            return evaluateApi.fetchEvaluationRule(param).then((res) => {
+              const pass = res.data.length > 0;
+              let des = '';
+              if (pass) {
+                res.data.forEach((e) => {
+                  if (des != '') {
+                    des += '銆�';
+                  }
+                  des += `銆�${e.rulename}銆媊;
+                });
+              } else {
+                des = '鏈壘鍒扮浉鍏宠瘎浼拌鍒欒〃';
+              }
+              return { pass, des };
+            });
+          },
+          true
+        ),
+        // 鍖哄煙鑼冨洿鍐呯殑鐩戠浠诲姟鏄惁瀛樺湪
+        baseCheckItem('鐜板満鐩戠宸℃煡鎬讳换鍔�', '', () => {
+          return taskApi.fetchTopTasks(this.areaInfo).then((res) => {
+            const pass = res.data.length > 0;
+            let des = '';
+            if (pass) {
+              res.data.forEach((e) => {
+                if (des != '') {
+                  des += '銆�';
+                }
+                des += e.name;
+              });
+            } else {
+              des = '鏈壘鍒扮浉鍏冲贰鏌ユ�讳换鍔�';
+            }
+            return { pass, des };
+          });
+        }),
+        // 鍖哄煙鑼冨洿鍐呯殑鐩戞祴鏁版嵁鏄惁瀛樺湪銆佹暟鎹椂闂磋法搴︽槸鍚﹀畬鏁淬�佹暟鎹殑鍒濇鍒嗘瀽鏄惁瀹屾垚
+        baseCheckItem(
+          '鐜板満鐩戞祴鏁版嵁',
+          () => {
+            this.dialog1 = true;
+          },
+          () => {
+            return monitordataApi
+              .fetchDustDataResult(this.areaInfo)
+              .then((res) => {
+                const pass = res.data.length > 0;
+                let des = '';
+                if (pass) {
+                  des = `鎵惧埌鏈堝害缁熻鍏�${res.data.length}鏉;
+                } else {
+                  des = '鏈壘鍒扮浉鍏宠褰�';
+                }
+                return { pass, des };
+              });
+          }
+        ),
+        // 鍖哄煙鑼冨洿鍐呯殑姣忎釜鐩戠鐐逛綅涓庣洃娴嬩华鍣ㄧ殑鍖归厤璁板綍鏄惁瀛樺湪锛岀己澶辨儏鍐电瓑
+        baseCheckItem('鐩戠鐐逛綅涓庣洃娴嬬偣鍖归厤', '', () => {
+          return userMapApi.fetchDeviceMap(this.areaInfo).then((res) => {
+            const pass = res.data.length > 0;
+            let des = '';
+            if (pass) {
+              des = `鎵惧埌鍖归厤璁板綍鍏�${res.data.length}鏉;
+            } else {
+              des = '鏈壘鍒扮浉鍏冲尮閰嶈褰�';
+            }
+            return { pass, des };
+          });
+        }),
+        // 鍖哄煙鑼冨洿鍐呯殑鐩戠闂閰嶇疆琛ㄦ槸鍚﹀瓨鍦�
+        baseCheckItem('鐜板満鐩戠闂绫诲瀷', '', () => {
+          const param = {
+            cityCode: this.areaInfo.citycode,
+            districtCode: this.areaInfo.districtcode,
+            sceneTypeId: this.areaInfo.scensetypeid
+          };
+          return problemApi.fetchProblemType(param).then((res) => {
+            const pass = res.length > 0;
+            let des = '';
+            if (pass) {
+              des = `鎵惧埌闂绫诲瀷鍏�${res.length}鏉;
+            } else {
+              des = '鏈壘鍒扮浉鍏抽棶棰樼被鍨�';
+            }
+            return { pass, des };
+          });
+        }),
+        // complaintApi.fetchComplaints();
+        // 鍖哄煙鑼冨洿鍐呯殑淇¤鎶曡瘔璁板綍鏄惁瀛樺湪锛屽彲闅忔椂琛ュ厖
+        baseCheckItem('淇¤鎶曡瘔', ''),
+        // complaintApi.fetchPunishment();
+        // 鍖哄煙鑼冨洿鍐呯殑琛屾斂澶勭綒璁板綍鏄惁瀛樺湪锛屽彲闅忔椂琛ュ厖
+        baseCheckItem('琛屾斂澶勭綒', '')
+      ],
+      dialog1: false
+    };
+  },
+  computed: {
+    /**
+     * 鍒ゆ柇鏁版嵁婧愭鏌ユ槸鍚﹂�氳繃
+     * 鍏ㄩ儴鍔犺浇瀹屾垚鍚庯紝蹇呰椤瑰繀椤婚�氳繃锛屽彲閫夐」闈炲繀椤婚�氳繃
+     */
+    checkPass() {
+      let res = true;
+      this.checkResults.forEach((e) => {
+        if (e.loading) {
+          res = res && false;
+        } else if (e.required) {
+          res = res && e.pass;
+        }
+      });
+      return res;
+    }
+  },
+  methods: {
+    // 璺宠浆涓嬩竴姝�
+    nextStep() {
+      this.$emit('change', this.checkResults);
+      this.$emit('update:modelValue', this.modelValue + 1);
+    },
+    // 璺宠浆涓婁竴姝�
+    lastStep() {
+      this.$emit('update:modelValue', this.modelValue - 1);
+    },
+    // 璺宠浆妫�鏌ラ」鐨勯摼鎺�
+    goto(path) {
+      if (typeof path === 'string' && path != '') {
+        this.$router.push(path);
+      } else if (typeof path === 'function') {
+        path();
+      }
+    },
+    // 寮�濮嬫鏌ヤ换鍔�
+    startCheck(value) {
+      // const v = value.value;
+      // this.areaInfo = {
+      //   provincecode: v._locations.pCode,
+      //   provincename: v._locations.pName,
+      //   citycode: v._locations.cCode,
+      //   cityname: v._locations.cName,
+      //   districtcode: v._locations.dCode,
+      //   districtname: v._locations.dName,
+      //   towncode: v._locations.tCode,
+      //   townname: v._locations.tName,
+      //   starttime: this.$fm.formatYMDHMS(v.time),
+      //   scensetypeid: v._scenetype.value,
+      //   online: true,
+      //   sourceType: v.sourceType
+      // };
+      this.areaInfo = value;
+
+      this.checkResults.forEach((e) => {
+        e.fetch();
+      });
+    }
+  }
+};
+</script>
+<style scoped>
+.required {
+  color: var(--el-color-danger);
+}
+
+.not-required {
+  color: transparent;
+}
+</style>
diff --git a/src/views/monitor/DataDashboard.vue b/src/views/monitor/DataDashboard.vue
index 4e266ec..3292858 100644
--- a/src/views/monitor/DataDashboard.vue
+++ b/src/views/monitor/DataDashboard.vue
@@ -1,27 +1,31 @@
-<!-- e:\VSprojects\fume-supervision-vue\src\views\monitor\DataDashboard.vue -->
 <template>
   <el-container class="data-dashboard">
     <el-main>
-      <!-- 璁惧鍦ㄧ嚎鎯呭喌鍖哄煙 -->
-      <DeviceStatus
-        :online-count="onlineCount"
-        :offline-count="offlineCount"
-        :normal-count="normalCount"
-        :fault-count="faultCount"
-      />
-
-      <!-- 璁惧瀹炴椂鏁版嵁鍖哄煙 -->
-      <RealTimeData :current-device="currentDevice" :hourly-data="hourlyData" />
-
-      <!-- 鍒嗗尯鏁版嵁鎺掑悕鍖哄煙 -->
-      <DistrictRanking
-        :selected-month="selectedMonth"
-        :ranking-type="rankingType"
-        :ranking-data="rankingData"
-        :sorted-ranking-data="sortedRankingData"
-        @month-change="handleMonthChange"
-        @type-change="handleTypeChange"
-      />
+      <div class="grid-container">
+        <div class="left-section">
+          <!-- 璁惧鍦ㄧ嚎鎯呭喌鍖哄煙 -->
+          <DeviceStatus
+            :online-count="onlineCount"
+            :offline-count="offlineCount"
+            :normal-count="normalCount"
+            :fault-count="faultCount"
+          />
+          <!-- 鍒嗗尯鏁版嵁鎺掑悕鍖哄煙 -->
+          <DistrictRanking
+            style="flex: 1"
+            :selected-month="selectedMonth"
+            :ranking-type="rankingType"
+            :ranking-data="rankingData"
+            :sorted-ranking-data="sortedRankingData"
+            @month-change="handleMonthChange"
+            @type-change="handleTypeChange"
+          />
+        </div>
+        <div class="right-section">
+          <!-- 璁惧瀹炴椂鏁版嵁鍖哄煙 -->
+          <RealTimeData style="flex: 1" :devices="devices" />
+        </div>
+      </div>
 
       <!-- 鍦ㄧ嚎璁惧鍜屽簵閾烘竻鍗曞尯鍩� -->
       <ShopList
@@ -58,8 +62,7 @@
       faultCount: 0,
 
       // 璁惧瀹炴椂鏁版嵁
-      currentDevice: null,
-      hourlyData: [],
+      devices: [],
 
       // 鍒嗗尯鏁版嵁鎺掑悕
       selectedMonth: '2023-12',
@@ -147,45 +150,80 @@
 
     updateRealTimeData() {
       // 妯℃嫙鏁版嵁 - 瀹為檯搴斾粠API鑾峰彇
-      const devices = [
+      this.devices = [
         {
           deviceId: 'DEV-001',
           supplier: '渚涘簲鍟咥',
-          娌圭儫娴撳害: (Math.random() * 2).toFixed(2),
-          椋庢満鐢垫祦: (Math.random() * 5 + 1).toFixed(2),
-          鍑�鍖栧櫒鐢垫祦: (Math.random() * 3 + 0.5).toFixed(2),
+          status: '姝e父',
+          monitorTime: new Date().toLocaleString(),
+          smokeDensity: (Math.random() * 2).toFixed(2),
+          fanCurrent: (Math.random() * 5 + 1).toFixed(2),
+          purifierCurrent: (Math.random() * 3 + 0.5).toFixed(2),
+          hourlyData: this.generateHourlyData(),
+        },
+        {
+          deviceId: 'DEV-002',
+          supplier: '渚涘簲鍟咮',
+          status: '姝e父',
+          monitorTime: new Date().toLocaleString(),
+          smokeDensity: (Math.random() * 2).toFixed(2),
+          fanCurrent: (Math.random() * 5 + 1).toFixed(2),
+          purifierCurrent: (Math.random() * 3 + 0.5).toFixed(2),
+          hourlyData: this.generateHourlyData(),
+        },
+        {
+          deviceId: 'DEV-003',
+          supplier: '渚涘簲鍟咰',
+          status: '寮傚父',
+          monitorTime: new Date().toLocaleString(),
+          smokeDensity: (Math.random() * 15 + 5).toFixed(2),
+          fanCurrent: (Math.random() * 3 + 4).toFixed(2),
+          purifierCurrent: (Math.random() * 2 + 2).toFixed(2),
+          hourlyData: this.generateHourlyData(),
+        },
+        {
+          deviceId: 'DEV-004',
+          supplier: '渚涘簲鍟咲',
+          status: '寮傚父',
+          monitorTime: new Date().toLocaleString(),
+          smokeDensity: (Math.random() * 15 + 5).toFixed(2),
+          fanCurrent: (Math.random() * 3 + 4).toFixed(2),
+          purifierCurrent: (Math.random() * 2 + 2).toFixed(2),
+          hourlyData: this.generateHourlyData(),
         },
       ]
+    },
 
-      this.currentDevice = devices[0]
-
+    generateHourlyData() {
       // 鐢熸垚妯℃嫙鐨勮繎涓�灏忔椂鏁版嵁
-      this.hourlyData = []
+      const hourlyData = []
       for (let i = 59; i >= 0; i--) {
         const time = new Date()
         time.setMinutes(time.getMinutes() - i)
-        this.hourlyData.push({
+        hourlyData.push({
           time: time.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
-          娌圭儫娴撳害: (Math.random() * 2).toFixed(2),
-          椋庢満鐢垫祦: (Math.random() * 5 + 1).toFixed(2),
-          鍑�鍖栧櫒鐢垫祦: (Math.random() * 3 + 0.5).toFixed(2),
+          smokeDensity: (Math.random() * 2).toFixed(2),
+          fanCurrent: (Math.random() * 5 + 1).toFixed(2),
+          purifierCurrent: (Math.random() * 3 + 0.5).toFixed(2),
         })
       }
+      return hourlyData
     },
 
     updateRankingData() {
       // 妯℃嫙鏁版嵁 - 瀹為檯搴斾粠API鑾峰彇
       this.rankingData = [
-        { name: '涓滃煄鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 0 },
-        { name: '瑗垮煄鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: -1 },
-        { name: '鏈濋槼鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 1 },
-        { name: '娴锋穩鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 0 },
-        { name: '涓板彴鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: -2 },
-        { name: '鐭虫櫙灞卞尯', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 2 },
-        { name: '閫氬窞鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 0 },
-        { name: '椤轰箟鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 1 },
-        { name: '鏄屽钩鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: -1 },
-        { name: '澶у叴鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 0 },
+        { name: '鐜勬鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 0 },
+        { name: '绉︽樊鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: -1 },
+        { name: '寤洪偤鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 1 },
+        { name: '榧撴ゼ鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 0 },
+        { name: '娴﹀彛鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: -2 },
+        { name: '鏍栭湠鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 2 },
+        { name: '闆ㄨ姳鍙板尯', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 0 },
+        { name: '姹熷畞鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 1 },
+        { name: '鍏悎鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: -1 },
+        { name: '婧ф按鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 0 },
+        { name: '楂樻烦鍖�', value: (Math.random() * 1.5 + 0.5).toFixed(2), rankChange: 1 },
       ]
 
       // 鎺掑簭
@@ -240,14 +278,35 @@
   padding: 20px;
 }
 
+.grid-container {
+  display: flex;
+  /* display: grid;
+  grid-template-columns: 1fr 1fr;
+  grid-template-rows: min-content; */
+  gap: 20px;
+}
+
+.left-section {
+  flex: 2;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.right-section {
+  width: 670px;
+  display: flex;
+  flex-direction: column;
+}
+
 /* 鍝嶅簲寮忚璁� */
 @media (max-width: 768px) {
-  .el-row {
-    flex-direction: column;
+  .grid-container {
+    grid-template-columns: 1fr;
   }
 
-  .el-col {
-    width: 100% !important;
+  .left-section,
+  .right-section {
     margin-bottom: 10px;
   }
 }

--
Gitblit v1.9.3