餐饮油烟智能监测与监管一体化平台
riku
2026-03-20 59af55dc3e72f8f2655ae06af9d1b6f766bac423
src/views/monitor/DataException.vue
@@ -2,264 +2,281 @@
  <div class="data-exception-container">
    <!-- 搜索区域 -->
    <div ref="h1" class="search-container">
      <!-- <div class="search-header">
        <h3>查询表格</h3>
      </div> -->
      <el-row>
        <div class="search-form">
          <div class="form-row">
            <div class="form-item">
              <span class="form-label">店铺设备:</span>
              <ShopNameAndID @submit-id="(n) => (deviceId[1] = n)"></ShopNameAndID>
            </div>
            <div class="form-item">
              <ExceptionType @submitExceptionType="(val) => (exceptionValue = val)">
              </ExceptionType>
            </div>
          </div>
          <div class="form-row">
            <div class="form-item full-width">
              <TimeSelect @submit-time="giveTime" :useNewStyle="false"></TimeSelect>
            </div>
          </div>
        </div>
        <div class="form-actions">
          <el-button type="primary" :loading="button.queryButton" @click="showTable"
            >查询</el-button
          >
          <el-tooltip
            class="box-item"
            effect="dark"
            content="点击可导出Excel文件"
            placement="top-start"
          >
            <el-icon class="iconExcel clickable" title="导出Excel文件" @click="exportDom">
              <i-ep-Download />
            </el-icon>
          </el-tooltip>
        </div>
      </el-row>
      <div class="summary-info">
        <span>{{ beginTime }} —— {{ endTime }} 油烟监测异常信息汇总</span>
      </div>
    </div>
    <!-- 异常分析 -->
    <div class="analysis-container">
      <el-collapse v-model="activeNames">
      <el-collapse v-model="activeSearchNames" @change="handleSearchCollapseChange">
        <el-collapse-item name="1">
          <template #title>
            <div class="collapse-title">
              <h4 class="collapse-header">异常分析</h4>
              <el-icon class="header-icon">
                <i-ep-info-filled />
              </el-icon>
            <div class="search-header">
              <h3>查询条件</h3>
              <span v-if="!isSearchExpanded" class="search-summary">
                {{ getSearchSummary() }}
              </span>
            </div>
          </template>
          <el-card class="analysis-card">
            <el-row :gutter="24">
              <el-col :span="8">
                <div class="analysis-item">
                  <div class="item-header">
                    <img src="@/assets/exceed.jpg" class="item-icon" />
                    <span class="item-title">油烟浓度超标</span>
                  </div>
                  <div class="item-content">
                    <div class="item-stats">
                      <span class="stats-label">异常店铺占比:</span>
                      <span class="stats-value">{{ exception0.length }} /{{ shopsTotal }}</span>
                      <span class="stats-percent"
                        >({{ ((exception0.length / shopsTotal) * 100).toFixed(1) }}%)</span
                      >
                    </div>
                    <div class="item-percent">
                      异常数占比:{{ ((exception0Num / exceptionAllNum) * 100).toFixed(1) }}%
                    </div>
                  </div>
                  <hr class="item-divider" />
                  <div class="item-shops">
                    <el-scrollbar max-height="80px">
                      <ExceptionText
                        v-for="(item, index) in exception0"
                        :key="item"
                        :devId="item.devId"
                        exception-value="0"
                        :begin-time="beginTime"
                        :end-time="endTime"
                        @submit-exception-data="getAbnormalDataByClick"
                      >
                        {{ item.diName }}
                        <span v-if="index < exception0.length - 1" class="text-blank">,</span>
                      </ExceptionText>
                    </el-scrollbar>
                  </div>
          <el-row>
            <div class="search-form">
              <div class="form-row">
                <div class="form-item">
                  <span class="form-label">店铺设备:</span>
                  <ShopNameAndID @submit-id="(n) => (deviceId[1] = n)"></ShopNameAndID>
                </div>
              </el-col>
              <el-col :span="8">
                <div class="analysis-item">
                  <div class="item-header">
                    <img src="@/assets/exception.jpg" class="item-icon" />
                    <span class="item-title">供电异常</span>
                  </div>
                  <div class="item-content">
                    <div class="item-stats">
                      <span class="stats-label">异常店铺占比:</span>
                      <span class="stats-value">{{ exception1.length }} /{{ shopsTotal }}</span>
                      <span class="stats-percent"
                        >({{ ((exception1.length / shopsTotal) * 100).toFixed(1) }}%)</span
                      >
                    </div>
                    <div class="item-percent">
                      异常数占比:{{ ((exception1Num / exceptionAllNum) * 100).toFixed(1) }}%
                    </div>
                  </div>
                  <hr class="item-divider" />
                  <div class="item-shops">
                    <el-scrollbar max-height="80px">
                      <ExceptionText
                        v-for="(item, index) in exception1"
                        :key="item"
                        :devId="item.devId"
                        exception-value="1"
                        :begin-time="beginTime"
                        :end-time="endTime"
                        @submit-exception-data="getAbnormalDataByClick"
                      >
                        {{ item.diName }}
                        <span v-if="index < exception1.length - 1" class="text-blank">,</span>
                      </ExceptionText>
                    </el-scrollbar>
                  </div>
                <div class="form-item">
                  <ExceptionType @submitExceptionType="(val) => (exceptionValue = val)">
                  </ExceptionType>
                </div>
              </el-col>
              <el-col :span="8">
                <div class="analysis-item">
                  <div class="item-header">
                    <img src="@/assets/offline.jpg" class="item-icon" />
                    <span class="item-title">设备或网络异常</span>
                  </div>
                  <div class="item-content">
                    <div class="item-stats">
                      <span class="stats-label">异常店铺占比:</span>
                      <span class="stats-value">{{ exception2.length }} /{{ shopsTotal }}</span>
                      <span class="stats-percent"
                        >({{ ((exception2.length / shopsTotal) * 100).toFixed(1) }}%)</span
                      >
                    </div>
                    <div class="item-percent">异常数占比:{{ connectException }}%</div>
                  </div>
                  <hr class="item-divider" />
                  <div class="item-shops">
                    <el-scrollbar max-height="80px">
                      <ExceptionText
                        v-for="(item, index) in exception2"
                        :key="item"
                        :devId="item.devId"
                        exception-value="2"
                        :begin-time="beginTime"
                        :end-time="endTime"
                        @submit-exception-data="getAbnormalDataByClick"
                      >
                        {{ item.diName }}
                        <span v-if="index < exception2.length - 1" class="text-blank">,</span>
                      </ExceptionText>
                    </el-scrollbar>
                  </div>
              </div>
              <div class="form-row">
                <div class="form-item full-width">
                  <TimeSelect @submit-time="giveTime" :useNewStyle="false"></TimeSelect>
                </div>
              </el-col>
            </el-row>
          </el-card>
              </div>
            </div>
            <div class="form-actions">
              <el-button type="primary" :loading="button.queryButton" @click="showTable"
                >查询</el-button
              >
              <el-tooltip
                class="box-item"
                effect="dark"
                content="点击可导出Excel文件"
                placement="top-start"
              >
                <el-icon class="iconExcel clickable" title="导出Excel文件" @click="exportDom">
                  <i-ep-Download />
                </el-icon>
              </el-tooltip>
            </div>
          </el-row>
          <div class="summary-info">
            <span>{{ beginTime }} —— {{ endTime }} 油烟监测异常信息汇总</span>
          </div>
        </el-collapse-item>
      </el-collapse>
    </div>
    <!-- 异常分析 -->
    <div class="analysis-container">
      <el-space>
        <h4 class="collapse-header">监测预警</h4>
        <el-icon class="header-icon">
          <i-ep-info-filled />
        </el-icon>
      </el-space>
      <el-card class="analysis-card">
        <el-row :gutter="24">
          <el-col :span="8">
            <div class="analysis-item">
              <div class="item-header">
                <img src="@/assets/exceed.jpg" class="item-icon" />
                <span class="item-title">油烟浓度超标</span>
              </div>
              <div class="item-content">
                <div class="item-stats">
                  <span class="stats-label">异常店铺占比:</span>
                  <span class="stats-value">{{ exception0.length }} /{{ shopsTotal }}</span>
                  <span class="stats-percent"
                    >({{ ((exception0.length / shopsTotal) * 100).toFixed(1) }}%)</span
                  >
                </div>
                <div class="item-percent">
                  异常数占比:{{ ((exception0Num / exceptionAllNum) * 100).toFixed(1) }}%
                </div>
              </div>
              <hr class="item-divider" />
              <div class="item-shops">
                <el-scrollbar :height="scrollbarHeight">
                  <el-space wrap>
                    <ExceptionText
                      v-for="(item, index) in exception0"
                      :key="item"
                      :devId="item.devId"
                      exception-value="0"
                      :begin-time="beginTime"
                      :end-time="endTime"
                      @submit-exception-data="getAbnormalDataByClick"
                    >
                      {{ item.diName }}
                      <span v-if="index < exception0.length - 1" class="text-blank"></span>
                    </ExceptionText>
                  </el-space>
                </el-scrollbar>
              </div>
            </div>
          </el-col>
          <el-col :span="8">
            <div class="analysis-item">
              <div class="item-header">
                <img src="@/assets/exception.jpg" class="item-icon" />
                <span class="item-title">供电异常</span>
              </div>
              <div class="item-content">
                <div class="item-stats">
                  <span class="stats-label">异常店铺占比:</span>
                  <span class="stats-value">{{ exception1.length }} /{{ shopsTotal }}</span>
                  <span class="stats-percent"
                    >({{ ((exception1.length / shopsTotal) * 100).toFixed(1) }}%)</span
                  >
                </div>
                <div class="item-percent">
                  异常数占比:{{ ((exception1Num / exceptionAllNum) * 100).toFixed(1) }}%
                </div>
              </div>
              <hr class="item-divider" />
              <div class="item-shops">
                <el-scrollbar :height="scrollbarHeight">
                  <el-space wrap>
                    <ExceptionText
                      v-for="(item, index) in exception1"
                      :key="item"
                      :devId="item.devId"
                      exception-value="1"
                      :begin-time="beginTime"
                      :end-time="endTime"
                      @submit-exception-data="getAbnormalDataByClick"
                    >
                      {{ item.diName }}
                      <span v-if="index < exception1.length - 1" class="text-blank"></span>
                    </ExceptionText>
                  </el-space>
                </el-scrollbar>
              </div>
            </div>
          </el-col>
          <el-col :span="8">
            <div class="analysis-item">
              <div class="item-header">
                <img src="@/assets/offline.jpg" class="item-icon" />
                <span class="item-title">设备或网络异常</span>
              </div>
              <div class="item-content">
                <div class="item-stats">
                  <span class="stats-label">异常店铺占比:</span>
                  <span class="stats-value">{{ exception2.length }} /{{ shopsTotal }}</span>
                  <span class="stats-percent"
                    >({{ ((exception2.length / shopsTotal) * 100).toFixed(1) }}%)</span
                  >
                </div>
                <div class="item-percent">异常数占比:{{ connectException }}%</div>
              </div>
              <hr class="item-divider" />
              <div class="item-shops">
                <el-scrollbar :height="scrollbarHeight">
                  <el-space wrap>
                    <ExceptionText
                      v-for="(item, index) in exception2"
                      :key="item"
                      :devId="item.devId"
                      exception-value="2"
                      :begin-time="beginTime"
                      :end-time="endTime"
                      @submit-exception-data="getAbnormalDataByClick"
                    >
                      {{ item.diName }}
                      <span v-if="index < exception2.length - 1" class="text-blank"></span>
                    </ExceptionText>
                  </el-space>
                </el-scrollbar>
              </div>
            </div>
          </el-col>
        </el-row>
      </el-card>
    </div>
    <!-- 异常数据表格 -->
    <div class="table-container">
      <h4 class="table-title">异常数据</h4>
      <el-card v-show="!isNoData">
        <el-table
          ref="tableH"
          v-loading="loading"
          :data="displayData"
          style="width: 100%"
          border
          :height="tableHeight"
          :cell-class-name="tableCellClassName"
        >
          <el-table-column prop="diName" label="店铺名称" align="center">
            <template #default="{ row }">
              <el-tooltip effect="dark" :content="row.diName">
                <div class="cell ellipsis">{{ row.diName }}</div>
              </el-tooltip>
            </template>
          </el-table-column>
          <el-table-column prop="devId" label="设备编号" align="center">
            <template #default="{ row }">
              <el-tooltip effect="dark" :content="row.devId">
                <div class="cell ellipsis">{{ row.devId }}</div>
              </el-tooltip>
            </template>
          </el-table-column>
          <el-table-column prop="diSupplier" label="供应商" align="center">
            <template #default="{ row }">
              <el-tooltip effect="dark" :content="row.diSupplier">
                <div class="cell ellipsis">{{ row.diSupplier }}</div>
              </el-tooltip>
            </template>
          </el-table-column>
          <el-table-column prop="exception" label="异常分类" align="center">
            <template #default="{ row }">
              <el-tooltip effect="dark" :content="row.exception">
                <div class="cell ellipsis">{{ row.exception }}</div>
              </el-tooltip>
            </template>
          </el-table-column>
          <el-table-column label="异常类型" align="center">
            <template #default="{ row }">
              <span v-if="row.exceptionType == '0'">油烟数据超标</span>
              <span v-else-if="row.exceptionType == '1'">疑似供电异常</span>
              <span v-else-if="row.exceptionType == '2'">掉线</span>
            </template>
          </el-table-column>
          <el-table-column prop="region" label="地区" align="center">
            <template #default="{ row }">
              <el-tooltip effect="dark" :content="row.region">
                <div class="cell ellipsis">{{ row.region }}</div>
              </el-tooltip>
            </template>
          </el-table-column>
          <el-table-column prop="beginTime" label="开始时间" align="center">
            <template #default="{ row }">
              <el-tooltip effect="dark" :content="row.beginTime">
                <div class="cell ellipsis">{{ row.beginTime }}</div>
              </el-tooltip>
            </template>
          </el-table-column>
          <el-table-column prop="endTime" label="结束时间" align="center">
            <template #default="{ row }">
              <el-tooltip effect="dark" :content="row.endTime">
                <div class="cell ellipsis">{{ row.endTime }}</div>
              </el-tooltip>
            </template>
          </el-table-column>
          <el-table-column label="操作" align="center" width="120">
            <template #default="{ row }">
              <el-button type="primary" size="small" @click="showDrawer(row)">查看</el-button>
            </template>
          </el-table-column>
        </el-table>
        <div class="pagination-container">
          <el-pagination
            ref="h4"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :total="total"
            :page-size="pageSize"
            layout="total, prev, pager, next, jumper"
          />
        </div>
      </el-card>
      <el-empty v-show="isNoData" :image-size="200" />
      <el-collapse v-model="activeNames">
        <el-collapse-item name="1">
          <template #title>
            <div class="collapse-title">
              <h4 class="table-title">异常数据</h4>
            </div>
          </template>
          <el-card v-show="!isNoData">
            <el-table
              ref="tableH"
              v-loading="loading"
              :data="displayData"
              style="width: 100%"
              border
              :cell-class-name="tableCellClassName"
              :show-overflow-tooltip="true"
            >
              <el-table-column prop="diName" label="店铺名称" align="center">
                <!-- <template #default="{ row }">
                  <el-tooltip effect="dark" :content="row.diName">
                    <div class="cell ellipsis">{{ row.diName }}</div>
                  </el-tooltip>
                </template> -->
              </el-table-column>
              <el-table-column prop="devId" label="设备编号" align="center">
                <!-- <template #default="{ row }">
                  <el-tooltip effect="dark" :content="row.devId">
                    <div class="cell ellipsis">{{ row.devId }}</div>
                  </el-tooltip>
                </template> -->
              </el-table-column>
              <el-table-column prop="diSupplier" label="供应商" align="center">
                <!-- <template #default="{ row }">
                  <el-tooltip effect="dark" :content="row.diSupplier">
                    <div class="cell ellipsis">{{ row.diSupplier }}</div>
                  </el-tooltip>
                </template> -->
              </el-table-column>
              <el-table-column prop="exception" label="异常分类" align="center" width="90">
                <!-- <template #default="{ row }">
                  <el-tooltip effect="dark" :content="row.exception">
                    <div class="cell ellipsis">{{ row.exception }}</div>
                  </el-tooltip>
                </template> -->
              </el-table-column>
              <el-table-column label="异常类型" align="center" width="120">
                <template #default="{ row }">
                  <span v-if="row.exceptionType == '0'">油烟数据超标</span>
                  <span v-else-if="row.exceptionType == '1'">疑似供电异常</span>
                  <span v-else-if="row.exceptionType == '2'">掉线</span>
                </template>
              </el-table-column>
              <el-table-column prop="region" label="地区" align="center" width="80">
                <!-- <template #default="{ row }">
                  <el-tooltip effect="dark" :content="row.region">
                    <div class="cell ellipsis">{{ row.region }}</div>
                  </el-tooltip>
                </template> -->
              </el-table-column>
              <el-table-column prop="beginTime" label="开始时间" align="center">
                <!-- <template #default="{ row }">
                  <el-tooltip effect="dark" :content="row.beginTime">
                    <div class="cell ellipsis">{{ row.beginTime }}</div>
                  </el-tooltip>
                </template> -->
              </el-table-column>
              <el-table-column prop="endTime" label="结束时间" align="center">
                <!-- <template #default="{ row }">
                  <el-tooltip effect="dark" :content="row.endTime">
                    <div class="cell ellipsis">{{ row.endTime }}</div>
                  </el-tooltip>
                </template> -->
              </el-table-column>
              <el-table-column label="操作" align="center" width="120">
                <template #default="{ row }">
                  <el-button type="primary" size="small" @click="showDrawer(row)">查看</el-button>
                </template>
              </el-table-column>
            </el-table>
            <div class="pagination-container">
              <el-pagination
                ref="h4"
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :total="total"
                :page-size="pageSize"
                layout="total, prev, pager, next, jumper"
              />
            </div>
          </el-card>
          <el-empty v-show="isNoData" :image-size="200" />
        </el-collapse-item>
      </el-collapse>
    </div>
    <!-- 对话框 -->
@@ -448,7 +465,11 @@
      selectedRowIndex: -1,
      // 默认选择的折叠面板编号
      activeNames: ['1'],
      activeNames: [],
      // 搜索区域折叠状态
      activeSearchNames: [],
      // 搜索区域是否展开
      isSearchExpanded: false,
      // 异常时的表格
      abnormalTb: [],
      // 异常的起止时间
@@ -462,6 +483,8 @@
      exception1: [],
      // 保存着异常类型2对应的店铺名称和设备编号
      exception2: [],
      // 异常店铺滚动区域高度
      scrollbarHeight: 250,
      // 加载动画
      loading: false,
      // 抽屉加载动画
@@ -534,7 +557,6 @@
        this.isNextCantouch = false
      }
    },
    // 当选择的时间发生变化时,异常分析部分的异常店铺数量同步变化
    beginTime() {
      this.getShopNames()
@@ -573,6 +595,37 @@
    window.addEventListener('resize', this.updateChart)
  },
  methods: {
    // 处理搜索区域折叠变化
    handleSearchCollapseChange(val) {
      this.isSearchExpanded = val.length > 0
    },
    // 获取搜索条件摘要
    getSearchSummary() {
      let summary =
        '时间: ' +
        (this.beginTime ? this.beginTime.substring(0, 10) : '全部') +
        ' 至 ' +
        (this.endTime ? this.endTime.substring(0, 10) : '全部')
      summary +=
        ' | 店铺: ' +
        (this.deviceId[1]
          ? this.deviceInfo.find((item) => item.diCode === this.deviceId[1])?.diName || '已选择'
          : '全部')
      if (this.exceptionValue && this.exceptionValue.length > 0) {
        const exceptionTypes = {
          0: '油烟浓度超标',
          1: '供电异常',
          2: '设备或网络异常',
        }
        const selectedTypes = this.exceptionValue
          .map((val) => exceptionTypes[val] || val)
          .join(', ')
        summary += ' | 异常类型: ' + selectedTypes
      } else {
        summary += ' | 异常类型: 全部'
      }
      return summary
    },
    // 功能:对话框表格序号递增
    // 时间:2023-8-17
    indexMethod(index) {
@@ -886,6 +939,9 @@
        // 移除空数据状态
        this.isNoData = false
        this.handleCurrentChange(1)
        // 点击查询后折叠搜索区域
        this.activeSearchNames = []
        this.isSearchExpanded = false
      })
    },
    handleSizeChange(val) {
@@ -1391,6 +1447,15 @@
      this.total = this.abnormalData.length
      // 默认显示第一页
      this.handleCurrentChange(1)
      this.activeNames = ['1']
      // 滚动到异常数据表格位置
      this.$nextTick(() => {
        if (this.$refs.tableH) {
          setTimeout(() => {
            this.$refs.tableH.$el.scrollIntoView({ behavior: 'smooth' })
          }, 200)
        }
      })
    },
    // 根据异常类型返回店铺名称和设备编号
@@ -1509,7 +1574,10 @@
}
.search-header {
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
}
.search-header h3 {
@@ -1517,6 +1585,16 @@
  font-size: 16px;
  font-weight: 600;
  color: #333;
}
.search-summary {
  font-size: 14px;
  color: #666;
  flex: 1;
  margin-left: 20px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.search-form {
@@ -1596,7 +1674,6 @@
}
.analysis-item {
  height: 180px;
  display: flex;
  flex-direction: column;
}