|
|
@@ -1,142 +1,226 @@
|
|
|
<template>
|
|
|
<div class="app-container lsyjgl-page">
|
|
|
|
|
|
- <!-- ==================== 搜索区域 ==================== -->
|
|
|
- <div class="search-panel">
|
|
|
- <div class="search-form">
|
|
|
- <div class="form-item">
|
|
|
- <span class="form-label">预警名称:</span>
|
|
|
- <el-input v-model="filters.equipmentName" placeholder="请输入" clearable style="width:200px" />
|
|
|
- </div>
|
|
|
- <div class="form-item">
|
|
|
- <span class="form-label">预警级别:</span>
|
|
|
- <el-select v-model="filters.warningLevel" placeholder="请选择" clearable style="width:160px">
|
|
|
- <el-option label="严重" :value="1" />
|
|
|
- <el-option label="重要" :value="2" />
|
|
|
- <el-option label="一般" :value="3" />
|
|
|
- </el-select>
|
|
|
+ <!-- ==================== 分类统计 ==================== -->
|
|
|
+ <div class="stats-panel">
|
|
|
+ <div class="stats-left">
|
|
|
+ <div class="stat-item" v-for="item in statItems" :key="item.label">
|
|
|
+ <div class="stat-value" :class="item.cls">{{ item.value }}</div>
|
|
|
+ <div class="stat-label">{{ item.label }}</div>
|
|
|
</div>
|
|
|
- <div class="form-item">
|
|
|
- <span class="form-label">处理状态:</span>
|
|
|
- <el-select v-model="filters.status" placeholder="请选择" clearable style="width:160px">
|
|
|
- <el-option label="已处置" value="HANDLED" />
|
|
|
- <el-option label="已解除" value="CLOSED" />
|
|
|
- </el-select>
|
|
|
+ </div>
|
|
|
+ <div class="stats-right">
|
|
|
+ <div class="ring-chart-box" v-for="chart in ringCharts" :key="chart.id">
|
|
|
+ <div :id="chart.id" class="ring-chart"></div>
|
|
|
+ <div class="ring-legend">
|
|
|
+ <div class="legend-row" v-for="(leg, i) in chart.legends.value" :key="i">
|
|
|
+ <span class="legend-dot" :style="{ background: leg.color }"></span>
|
|
|
+ <span class="legend-text">{{ leg.name }}</span>
|
|
|
+ <span class="legend-num">{{ leg.value }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
|
- <el-button @click="handleClear">清除</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- ==================== 搜索区域 ==================== -->
|
|
|
+ <el-form ref="queryRef" :model="filters" :inline="true" v-show="showSearch" label-width="80px">
|
|
|
+ <el-form-item label="预警名称">
|
|
|
+ <el-input v-model="filters.equipmentName" placeholder="请输入预警名称" clearable style="width: 200px"
|
|
|
+ @keyup.enter="handleSearch" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="预警类型">
|
|
|
+ <el-select v-model="filters.warningType" placeholder="请选择预警类型" clearable filterable style="width: 180px">
|
|
|
+ <el-option v-for="item in warningTypeOptions" :key="item.dictValue" :label="item.dictLabel"
|
|
|
+ :value="item.dictValue" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="预警级别">
|
|
|
+ <el-select v-model="filters.warningLevel" placeholder="请选择预警级别" clearable style="width: 180px">
|
|
|
+ <el-option label="严重" :value="1" />
|
|
|
+ <el-option label="重要" :value="2" />
|
|
|
+ <el-option label="一般" :value="3" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="处理状态">
|
|
|
+ <el-select v-model="filters.status" placeholder="请选择处理状态" clearable style="width: 180px">
|
|
|
+ <el-option label="已处置" value="HANDLED" />
|
|
|
+ <el-option label="已解除" value="CLOSED" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="预警时间" style="width: 308px">
|
|
|
+ <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="-" start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 240px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
|
|
|
+ <el-button icon="Refresh" @click="handleClear">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <!-- ==================== 操作栏 ==================== -->
|
|
|
+ <el-row :gutter="10" class="mb8">
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="warning" plain icon="Download" :loading="exporting" @click="handleExport">导出</el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="primary" plain icon="Refresh" :loading="loading" @click="loadData">刷新</el-button>
|
|
|
+ </el-col>
|
|
|
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="loadData"></right-toolbar>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
<!-- ==================== 数据表格 ==================== -->
|
|
|
- <div class="table-panel">
|
|
|
- <el-table :data="tableData" border style="width:100%" v-loading="loading">
|
|
|
- <el-table-column prop="warningNo" label="预警编号" min-width="140" />
|
|
|
- <el-table-column prop="equipmentName" label="预警名称" min-width="140" show-overflow-tooltip />
|
|
|
+ <div>
|
|
|
+ <el-table :data="tableData" style="width:100%" v-loading="loading"
|
|
|
+ empty-text="暂无已处置 / 已解除的预警记录">
|
|
|
+ <el-table-column label="归档编号" width="150" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">{{ archiveNoOf(row) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="warningNo" label="预警编号" width="175" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="equipmentName" label="预警名称" min-width="130" show-overflow-tooltip />
|
|
|
+ <el-table-column label="预警类型" width="130" align="center" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">{{ warningTypeText(row) }}</template>
|
|
|
+ </el-table-column>
|
|
|
<el-table-column prop="warningLevel" label="预警级别" width="90" align="center">
|
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="levelTagType(row.warningLevel)" size="small">{{ levelText(row.warningLevel) }}</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="deviceCode" label="预警类型" width="110" align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- {{ equipmentTypeMap.get(String(row.deviceCode)) || row.deviceCode || row.warningType || '-' }}
|
|
|
- </template>
|
|
|
+ <el-table-column label="预警内容" min-width="190" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">{{ row.content || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="ownershipUnit" label="权属单位" width="145" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">{{ row.ownershipUnit || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="location" label="位置" min-width="140" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">{{ row.location || '-' }}</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="warningSpecial" label="预警专项" width="110" align="center" show-overflow-tooltip />
|
|
|
- <el-table-column prop="location" label="位置" min-width="180" show-overflow-tooltip />
|
|
|
- <el-table-column prop="createTime" label="预警时间" width="160" />
|
|
|
- <el-table-column prop="status" label="处理状态" width="90" align="center">
|
|
|
+ <el-table-column label="预警时间" width="168">
|
|
|
+ <template #default="{ row }">{{ formatDateTime(row.createTime) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="publisher" label="发布人" width="110" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">{{ row.publisher || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="processor" label="处置人" width="110" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">{{ row.processor || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="归档时间" width="168">
|
|
|
+ <template #default="{ row }">{{ formatDateTime(archiveTimeOf(row)) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="status" label="处理状态" width="100" align="center">
|
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="statusTagType(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="操作" width="140" fixed="right">
|
|
|
+ <el-table-column label="操作" width="130" fixed="right">
|
|
|
<template #default="{ row }">
|
|
|
- <el-button link type="primary" @click="handleView(row)">查看</el-button>
|
|
|
- <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
|
|
+ <el-button link type="primary" size="small" @click="handleView(row)">查看</el-button>
|
|
|
+ <el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
- <el-pagination
|
|
|
- v-model:current-page="pageNum"
|
|
|
- v-model:page-size="pageSize"
|
|
|
- :page-sizes="[10, 20, 50, 100]"
|
|
|
- :total="total"
|
|
|
- layout="total, sizes, prev, pager, next, jumper"
|
|
|
- style="margin-top: 16px; justify-content: flex-end"
|
|
|
- @size-change="loadData"
|
|
|
- @current-change="loadData"
|
|
|
- />
|
|
|
+ <pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize"
|
|
|
+ @pagination="loadData" />
|
|
|
</div>
|
|
|
|
|
|
<!-- ==================== 查看详情弹窗 ==================== -->
|
|
|
- <el-drawer
|
|
|
- v-model="dialogVisible"
|
|
|
- title="历史预警详情"
|
|
|
- size="680px"
|
|
|
- :close-on-click-modal="false"
|
|
|
- class="detail-drawer" direction="rtl">
|
|
|
- <div class="detail-body" v-if="currentDetail">
|
|
|
- <!-- 右侧信息 -->
|
|
|
- <div class="detail-info">
|
|
|
- <div class="info-code">预警编号:{{ currentDetail.warningNo }}</div>
|
|
|
- <div class="info-grid">
|
|
|
- <div class="info-row">
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">预警名称:</span>
|
|
|
- <span class="info-value">{{ currentDetail.equipmentName }}</span>
|
|
|
- </div>
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">预警级别:</span>
|
|
|
- <span class="info-value">
|
|
|
- <el-tag :type="levelTagType(currentDetail.warningLevel)" size="small">{{ levelText(currentDetail.warningLevel) }}</el-tag>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="info-row">
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">预警类型:</span>
|
|
|
- <span class="info-value">{{ equipmentTypeMap.get(String(currentDetail.deviceCode)) || currentDetail.deviceCode || currentDetail.warningType || '-' }}</span>
|
|
|
- </div>
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">预警专项:</span>
|
|
|
- <span class="info-value">{{ currentDetail.warningSpecial || '-' }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="info-row">
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">预警时间:</span>
|
|
|
- <span class="info-value">{{ currentDetail.createTime }}</span>
|
|
|
- </div>
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">发布人:</span>
|
|
|
- <span class="info-value">{{ currentDetail.publisher || '-' }}</span>
|
|
|
+ <el-drawer v-model="dialogVisible" title="历史预警详情" size="720px" :close-on-click-modal="false"
|
|
|
+ class="detail-drawer" direction="rtl">
|
|
|
+ <div class="detail-content" v-if="currentDetail" v-loading="detailLoading">
|
|
|
+ <div class="section-box">
|
|
|
+ <div class="section-title">预警信息</div>
|
|
|
+ <el-descriptions :column="2" border size="small">
|
|
|
+ <el-descriptions-item label="预警编号">{{ currentDetail.warningNo || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="预警名称">{{ currentDetail.equipmentName || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="预警类型">{{ warningTypeText(currentDetail) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="预警级别">
|
|
|
+ <el-tag :type="levelTagType(currentDetail.warningLevel)" size="small">{{
|
|
|
+ levelText(currentDetail.warningLevel) }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="预警专项">{{ currentDetail.warningSpecial || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="权属单位">{{ currentDetail.ownershipUnit || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="预警时间">{{ formatDateTime(currentDetail.createTime) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="处理状态">
|
|
|
+ <el-tag :type="statusTagType(currentDetail.status)" size="small">{{ statusText(currentDetail.status)
|
|
|
+ }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="发布人">{{ currentDetail.publisher || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="创建人">{{ currentDetail.createBy || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="位置" :span="2">{{ currentDetail.location || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="预警内容" :span="2">
|
|
|
+ <div class="desc-text">{{ currentDetail.content || '-' }}</div>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item v-if="currentDetail.remark" label="备注" :span="2">
|
|
|
+ {{ currentDetail.remark }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section-box">
|
|
|
+ <div class="section-title">归档信息</div>
|
|
|
+ <el-descriptions :column="2" border size="small">
|
|
|
+ <el-descriptions-item label="归档编号">{{ archiveNoOf(currentDetail) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="归档时间">{{ formatDateTime(archiveTimeOf(currentDetail)) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="处置人">{{ currentDetail.processor || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="归档状态">{{ statusText(currentDetail.status) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="发布时间" :span="2">
|
|
|
+ {{ formatDateTime(currentDetail.publishTime) }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section-box">
|
|
|
+ <div class="section-title">关联设备与预警指标</div>
|
|
|
+ <el-descriptions v-if="detailDevice" :column="2" border size="small">
|
|
|
+ <el-descriptions-item label="设备名称">{{ detailDevice.equipmentName || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="设备编码">{{ detailDevice.equipmentCode || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="设备类型">{{ equipmentTypeMap.get(String(detailDevice.equipmentTypeId)) || '-'
|
|
|
+ }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="在线状态">
|
|
|
+ <el-tag :type="detailDevice.onlineStatus === 1 ? 'success' : 'info'" size="small">
|
|
|
+ {{ detailDevice.onlineStatus === 1 ? '在线' : '离线' }}
|
|
|
+ </el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="安装位置" :span="2">{{ detailDevice.equipmentLocation ||
|
|
|
+ currentDetail.location || '-' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ <el-alert v-else type="info" :closable="false" show-icon class="device-missing"
|
|
|
+ title="未在设备台账中找到该预警关联的设备,指标数据无法展示" />
|
|
|
+
|
|
|
+ <div class="metric-grid">
|
|
|
+ <div class="metric-card">
|
|
|
+ <div class="metric-label">实时液位</div>
|
|
|
+ <div class="metric-value">
|
|
|
+ <template v-if="detailMetric.level !== null">{{ detailMetric.level
|
|
|
+ }}<span class="metric-unit">m</span></template>
|
|
|
+ <span v-else class="metric-empty">—</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="info-row">
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">处理状态:</span>
|
|
|
- <span class="info-value">
|
|
|
- <el-tag :type="statusTagType(currentDetail.status)" size="small">{{ statusText(currentDetail.status) }}</el-tag>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">发布时间:</span>
|
|
|
- <span class="info-value">{{ currentDetail.publishTime || '-' }}</span>
|
|
|
- </div>
|
|
|
+ <div class="metric-card">
|
|
|
+ <div class="metric-label">报警阈值</div>
|
|
|
+ <div class="metric-value metric-value-text">{{ detailMetric.thresholdText || '未配置' }}</div>
|
|
|
</div>
|
|
|
- <div class="info-row single">
|
|
|
- <div class="info-item">
|
|
|
- <span class="info-label">位置:</span>
|
|
|
- <span class="info-value desc-text">{{ currentDetail.location }}</span>
|
|
|
- </div>
|
|
|
+ <div class="metric-card">
|
|
|
+ <div class="metric-label">采集时间</div>
|
|
|
+ <div class="metric-value metric-value-text">{{ detailMetric.collectTime || '—' }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <el-table v-if="detailThresholds.length" :data="detailThresholds" border size="small"
|
|
|
+ empty-text="该设备尚未配置报警阈值">
|
|
|
+ <el-table-column label="监测指标" prop="warningName" min-width="120" />
|
|
|
+ <el-table-column label="单位" width="90" align="center">
|
|
|
+ <template #default="{ row }">{{ row.warningUnit || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="阈值范围" min-width="150" align="center">
|
|
|
+ <template #default="{ row }">{{ formatThreshold(row) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
<template #footer>
|
|
|
- <el-button @click="dialogVisible = false">关闭</el-button>
|
|
|
+ <el-button @click="dialogVisible = false">关 闭</el-button>
|
|
|
</template>
|
|
|
</el-drawer>
|
|
|
|
|
|
@@ -144,12 +228,21 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup name="Lsyjgl">
|
|
|
-import { ref, reactive, onMounted } from 'vue'
|
|
|
+import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
-import { getWarningHistoryPage, deleteWarning, getEquipmentTypeList } from '@/api/drainage'
|
|
|
-
|
|
|
-// ==================== 设备类型映射 ====================
|
|
|
+import * as XLSX from 'xlsx'
|
|
|
+import { saveAs } from 'file-saver'
|
|
|
+import * as echarts from 'echarts'
|
|
|
+import { getWarningHistoryPage, deleteWarning, getEquipmentTypeList, getLatestWaterLevelByDevice } from '@/api/drainage'
|
|
|
+import { listEquipment } from '@/api/pipeNetwork/pdeviceInform'
|
|
|
+import { pageWarningThresholds } from '@/api/warningThreshold'
|
|
|
+import { listWarningTypes } from '@/api/warning/list'
|
|
|
+
|
|
|
+// ==================== 设备类型 / 预警类型映射 ====================
|
|
|
const equipmentTypeMap = ref(new Map())
|
|
|
+/** warningType(类型编码) -> 类型名称,来自 warning_type 表 */
|
|
|
+const warningTypeMap = ref(new Map())
|
|
|
+const warningTypeOptions = ref([])
|
|
|
|
|
|
async function loadEquipmentTypes() {
|
|
|
try {
|
|
|
@@ -167,6 +260,33 @@ async function loadEquipmentTypes() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+async function loadWarningTypes() {
|
|
|
+ try {
|
|
|
+ const res = await listWarningTypes()
|
|
|
+ const list = res?.data || res?.rows || []
|
|
|
+ const map = new Map()
|
|
|
+ if (Array.isArray(list)) {
|
|
|
+ list.forEach(item => {
|
|
|
+ if (item.dictValue) map.set(String(item.dictValue), item.dictLabel || item.dictValue)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ warningTypeOptions.value = Array.isArray(list) ? list : []
|
|
|
+ warningTypeMap.value = map
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('加载预警类型配置失败', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 预警类型展示:新数据看 warningType,历史数据回退到 deviceCode 的设备类型名 */
|
|
|
+function warningTypeText(row) {
|
|
|
+ if (!row) return '-'
|
|
|
+ const byType = row.warningType ? warningTypeMap.value.get(String(row.warningType)) : null
|
|
|
+ if (byType) return byType
|
|
|
+ const byDeviceType = row.deviceCode ? equipmentTypeMap.value.get(String(row.deviceCode)) : null
|
|
|
+ if (byDeviceType) return byDeviceType
|
|
|
+ return row.warningType || row.deviceCode || '-'
|
|
|
+}
|
|
|
+
|
|
|
// ==================== 等级/状态工具方法 ====================
|
|
|
function levelText(level) {
|
|
|
const map = { 1: '严重', 2: '重要', 3: '一般' }
|
|
|
@@ -185,6 +305,75 @@ function statusTagType(status) {
|
|
|
return map[status] || 'info'
|
|
|
}
|
|
|
|
|
|
+function formatDateTime(value) {
|
|
|
+ if (!value) return '-'
|
|
|
+ return String(value).replace('T', ' ').slice(0, 19)
|
|
|
+}
|
|
|
+
|
|
|
+/** 归档编号:预警解除即归档,编号由预警编号派生,稳定可追溯 */
|
|
|
+function archiveNoOf(row) {
|
|
|
+ if (!row?.warningNo) return '-'
|
|
|
+ return `ARC${String(row.warningNo).replace(/^WRN/, '').slice(-12)}`
|
|
|
+}
|
|
|
+
|
|
|
+/** 归档时间:解除/处置动作发生在 update_time,缺失时退回发布时间 */
|
|
|
+function archiveTimeOf(row) {
|
|
|
+ return row?.updateTime || row?.publishTime || null
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 分类统计 ====================
|
|
|
+const statItems = computed(() => {
|
|
|
+ const list = tableData.value
|
|
|
+ const devices = new Set(list.map(o => o.equipmentName).filter(Boolean))
|
|
|
+ return [
|
|
|
+ { label: '归档预警', value: total.value, cls: '' },
|
|
|
+ { label: '已处置', value: list.filter(o => o.status === 'HANDLED').length, cls: 'green' },
|
|
|
+ { label: '已解除', value: list.filter(o => o.status === 'CLOSED').length, cls: 'blue' },
|
|
|
+ { label: '涉及设备', value: devices.size, cls: '' }
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+const statusLegends = computed(() => [
|
|
|
+ { name: '已处置', value: tableData.value.filter(o => o.status === 'HANDLED').length, color: '#67C23A' },
|
|
|
+ { name: '已解除', value: tableData.value.filter(o => o.status === 'CLOSED').length, color: '#409EFF' }
|
|
|
+])
|
|
|
+
|
|
|
+const levelLegends = computed(() => [
|
|
|
+ { name: '严重', value: tableData.value.filter(o => o.warningLevel === 1).length, color: '#F56C6C' },
|
|
|
+ { name: '重要', value: tableData.value.filter(o => o.warningLevel === 2).length, color: '#E6A23C' },
|
|
|
+ { name: '一般', value: tableData.value.filter(o => o.warningLevel === 3).length, color: '#909399' }
|
|
|
+])
|
|
|
+
|
|
|
+const ringCharts = [
|
|
|
+ { id: 'historyStatusChart', legends: statusLegends },
|
|
|
+ { id: 'historyLevelChart', legends: levelLegends }
|
|
|
+]
|
|
|
+
|
|
|
+function initCharts() {
|
|
|
+ ringCharts.forEach(chart => {
|
|
|
+ const dom = document.getElementById(chart.id)
|
|
|
+ if (!dom) return
|
|
|
+ const legends = chart.legends.value
|
|
|
+ const chartInstance = echarts.getInstanceByDom(dom) || echarts.init(dom)
|
|
|
+ chartInstance.setOption({
|
|
|
+ tooltip: { trigger: 'item', formatter: '{b}:{c}({d}%)' },
|
|
|
+ series: [{
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['58%', '82%'],
|
|
|
+ center: ['50%', '50%'],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ label: { show: false },
|
|
|
+ labelLine: { show: false },
|
|
|
+ data: legends.map(item => ({
|
|
|
+ name: item.name,
|
|
|
+ value: item.value,
|
|
|
+ itemStyle: { color: item.color }
|
|
|
+ }))
|
|
|
+ }]
|
|
|
+ }, true)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
// ==================== 表格数据 ====================
|
|
|
const tableData = ref([])
|
|
|
const total = ref(0)
|
|
|
@@ -193,26 +382,38 @@ const pageNum = ref(1)
|
|
|
const pageSize = ref(10)
|
|
|
|
|
|
// ==================== 搜索筛选 ====================
|
|
|
+const showSearch = ref(true)
|
|
|
+const queryRef = ref()
|
|
|
const filters = reactive({
|
|
|
equipmentName: '',
|
|
|
+ warningType: '',
|
|
|
warningLevel: '',
|
|
|
- status: ''
|
|
|
+ status: '',
|
|
|
+ dateRange: null
|
|
|
})
|
|
|
|
|
|
+function buildParams() {
|
|
|
+ const params = {
|
|
|
+ historyOnly: true,
|
|
|
+ equipmentType: 'drainage'
|
|
|
+ }
|
|
|
+ if (filters.equipmentName) params.equipmentName = filters.equipmentName
|
|
|
+ if (filters.warningType) params.warningType = filters.warningType
|
|
|
+ if (filters.warningLevel) params.warningLevel = filters.warningLevel
|
|
|
+ if (filters.status !== '' && filters.status !== null) params.status = filters.status
|
|
|
+ if (filters.dateRange && filters.dateRange.length === 2) {
|
|
|
+ params.startTime = `${filters.dateRange[0]} 00:00:00`
|
|
|
+ params.endTime = `${filters.dateRange[1]} 23:59:59`
|
|
|
+ }
|
|
|
+ return params
|
|
|
+}
|
|
|
+
|
|
|
// ==================== 数据加载 ====================
|
|
|
async function loadData() {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
- // 历史页由后端按历史状态(HANDLED/CLOSED)限定;不再写死
|
|
|
- // 旧的数字类型和 HANDLED 状态,避免过滤掉实际历史记录。
|
|
|
- const params = {
|
|
|
- historyOnly: true,
|
|
|
- equipmentType: 'drainage'
|
|
|
- }
|
|
|
- if (filters.equipmentName) params.equipmentName = filters.equipmentName
|
|
|
- if (filters.warningLevel) params.warningLevel = filters.warningLevel
|
|
|
- if (filters.status !== '' && filters.status !== null) params.status = filters.status
|
|
|
- const res = await getWarningHistoryPage(pageNum.value, pageSize.value, params)
|
|
|
+ // 历史页由后端按历史状态(HANDLED/CLOSED)限定
|
|
|
+ const res = await getWarningHistoryPage(pageNum.value, pageSize.value, buildParams())
|
|
|
const pageData = res.code !== undefined ? res.data : res
|
|
|
tableData.value = pageData.records || []
|
|
|
total.value = pageData.total || 0
|
|
|
@@ -221,6 +422,7 @@ async function loadData() {
|
|
|
ElMessage.error('加载历史预警列表失败')
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
+ nextTick(() => initCharts())
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -231,12 +433,84 @@ function handleSearch() {
|
|
|
|
|
|
function handleClear() {
|
|
|
filters.equipmentName = ''
|
|
|
+ filters.warningType = ''
|
|
|
filters.warningLevel = ''
|
|
|
filters.status = ''
|
|
|
+ filters.dateRange = null
|
|
|
pageNum.value = 1
|
|
|
loadData()
|
|
|
}
|
|
|
|
|
|
+// ==================== 归档导出 ====================
|
|
|
+const exporting = ref(false)
|
|
|
+const EXPORT_PAGE_SIZE = 100
|
|
|
+
|
|
|
+const EXPORT_HEADER = [
|
|
|
+ '归档编号', '预警编号', '预警名称', '预警类型', '预警级别', '预警内容',
|
|
|
+ '权属单位', '位置', '预警时间', '发布人', '处置人', '归档时间', '处理状态'
|
|
|
+]
|
|
|
+
|
|
|
+function exportValues(row) {
|
|
|
+ return [
|
|
|
+ archiveNoOf(row),
|
|
|
+ row.warningNo || '',
|
|
|
+ row.equipmentName || '',
|
|
|
+ warningTypeText(row),
|
|
|
+ levelText(row.warningLevel),
|
|
|
+ row.content || '',
|
|
|
+ row.ownershipUnit || '',
|
|
|
+ row.location || '',
|
|
|
+ formatDateTime(row.createTime),
|
|
|
+ row.publisher || '',
|
|
|
+ row.processor || '',
|
|
|
+ formatDateTime(archiveTimeOf(row)),
|
|
|
+ statusText(row.status)
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+/** 按当前筛选条件翻页拉全量,单页给大值容易被后端分页上限截断 */
|
|
|
+async function fetchAllRows() {
|
|
|
+ const collected = []
|
|
|
+ let page = 1
|
|
|
+ while (page <= 50) {
|
|
|
+ const res = await getWarningHistoryPage(page, EXPORT_PAGE_SIZE, buildParams())
|
|
|
+ const pageData = res.code !== undefined ? res.data : res
|
|
|
+ const rows = pageData.records || pageData.rows || []
|
|
|
+ if (rows.length === 0) break
|
|
|
+ collected.push(...rows)
|
|
|
+ const totalCount = Number(pageData.total) || 0
|
|
|
+ if (rows.length < EXPORT_PAGE_SIZE) break
|
|
|
+ if (totalCount > 0 && collected.length >= totalCount) break
|
|
|
+ page += 1
|
|
|
+ }
|
|
|
+ return collected
|
|
|
+}
|
|
|
+
|
|
|
+async function handleExport() {
|
|
|
+ exporting.value = true
|
|
|
+ try {
|
|
|
+ const rows = await fetchAllRows()
|
|
|
+ if (rows.length === 0) {
|
|
|
+ ElMessage.warning('当前条件下没有可导出的归档记录')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const sheet = XLSX.utils.aoa_to_sheet([EXPORT_HEADER, ...rows.map(exportValues)])
|
|
|
+ const book = XLSX.utils.book_new()
|
|
|
+ XLSX.utils.book_append_sheet(book, sheet, '历史预警归档')
|
|
|
+ const d = new Date()
|
|
|
+ const pad = (n) => String(n).padStart(2, '0')
|
|
|
+ const stamp = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}`
|
|
|
+ saveAs(new Blob([XLSX.write(book, { bookType: 'xlsx', type: 'array' })]),
|
|
|
+ `历史预警归档清单_${stamp}.xlsx`)
|
|
|
+ ElMessage.success(`已导出 ${rows.length} 条归档记录`)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('导出归档清单失败', error)
|
|
|
+ ElMessage.error('导出失败,请稍后重试')
|
|
|
+ } finally {
|
|
|
+ exporting.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// ==================== 操作:删除 ====================
|
|
|
async function handleDelete(row) {
|
|
|
try {
|
|
|
@@ -252,14 +526,108 @@ async function handleDelete(row) {
|
|
|
// ==================== 查看详情弹窗 ====================
|
|
|
const dialogVisible = ref(false)
|
|
|
const currentDetail = ref(null)
|
|
|
+const detailLoading = ref(false)
|
|
|
+const detailDevice = ref(null)
|
|
|
+const detailThresholds = ref([])
|
|
|
+const waterLevelMap = ref({})
|
|
|
+const thresholdMap = ref({})
|
|
|
+/** equipmentName -> 设备档案:预警 device_code 存的是类型 id,只能按名称反查设备 */
|
|
|
+const deviceByName = ref(null)
|
|
|
+
|
|
|
+const detailMetric = computed(() => {
|
|
|
+ const code = detailDevice.value?.equipmentCode
|
|
|
+ const levelItem = code ? waterLevelMap.value[code] : null
|
|
|
+ const thresholds = code ? (thresholdMap.value[code] || []) : []
|
|
|
+ return {
|
|
|
+ level: levelItem && levelItem.value !== undefined && levelItem.value !== null ? levelItem.value : null,
|
|
|
+ collectTime: levelItem?.time ? formatDateTime(levelItem.time) : '',
|
|
|
+ thresholdText: thresholds.length > 0 ? thresholds.map(formatThreshold).join(' / ') : ''
|
|
|
+ }
|
|
|
+})
|
|
|
|
|
|
-function handleView(row) {
|
|
|
+async function handleView(row) {
|
|
|
currentDetail.value = { ...row }
|
|
|
+ detailDevice.value = null
|
|
|
+ detailThresholds.value = []
|
|
|
dialogVisible.value = true
|
|
|
+ detailLoading.value = true
|
|
|
+ try {
|
|
|
+ const [device] = await Promise.all([resolveDevice(row.equipmentName), loadLiveMetrics()])
|
|
|
+ detailDevice.value = device || null
|
|
|
+ const code = device?.equipmentCode
|
|
|
+ detailThresholds.value = code ? (thresholdMap.value[code] || []) : []
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('预警详情数据加载失败', error)
|
|
|
+ } finally {
|
|
|
+ detailLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function resolveDevice(equipmentName) {
|
|
|
+ if (!equipmentName) return null
|
|
|
+ if (!deviceByName.value) {
|
|
|
+ const map = new Map()
|
|
|
+ let page = 1
|
|
|
+ while (page <= 10) {
|
|
|
+ const res = await listEquipment({ rootTypeId: 2, pageNum: page, pageSize: 100 })
|
|
|
+ const pageData = res?.data && Array.isArray(res.data.records) ? res.data : res
|
|
|
+ const rows = pageData?.records || pageData?.rows || []
|
|
|
+ if (rows.length === 0) break
|
|
|
+ rows.forEach((item) => {
|
|
|
+ if (item?.equipmentName && !map.has(item.equipmentName)) map.set(item.equipmentName, item)
|
|
|
+ })
|
|
|
+ if (rows.length < 100) break
|
|
|
+ page += 1
|
|
|
+ }
|
|
|
+ deviceByName.value = map
|
|
|
+ }
|
|
|
+ return deviceByName.value.get(equipmentName) || null
|
|
|
+}
|
|
|
+
|
|
|
+async function loadLiveMetrics() {
|
|
|
+ const [levelResult, thresholdResult] = await Promise.allSettled([
|
|
|
+ getLatestWaterLevelByDevice(),
|
|
|
+ pageWarningThresholds({ rootTypeId: 2, pageNum: 1, pageSize: 500 })
|
|
|
+ ])
|
|
|
+
|
|
|
+ if (levelResult.status === 'fulfilled') {
|
|
|
+ const list = Array.isArray(levelResult.value) ? levelResult.value : (levelResult.value?.data || [])
|
|
|
+ const map = {}
|
|
|
+ list.forEach((item) => {
|
|
|
+ if (!item?.deviceCode) return
|
|
|
+ map[item.deviceCode] = { value: item.waterLevel, time: item.createTime }
|
|
|
+ })
|
|
|
+ waterLevelMap.value = map
|
|
|
+ }
|
|
|
+
|
|
|
+ if (thresholdResult.status === 'fulfilled') {
|
|
|
+ const res = thresholdResult.value
|
|
|
+ const pageData = res?.data && Array.isArray(res.data.records) ? res.data : res
|
|
|
+ const rows = pageData?.records || pageData?.rows || []
|
|
|
+ const map = {}
|
|
|
+ rows.forEach((row) => {
|
|
|
+ if (!row?.deviceCode) return
|
|
|
+ if (!map[row.deviceCode]) map[row.deviceCode] = []
|
|
|
+ map[row.deviceCode].push(row)
|
|
|
+ })
|
|
|
+ thresholdMap.value = map
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 阈值展示:dataType 0 区间 / 1 下限 / 2 上限 */
|
|
|
+function formatThreshold(item) {
|
|
|
+ if (!item) return '-'
|
|
|
+ const unit = item.warningUnit || ''
|
|
|
+ const min = item.minValue
|
|
|
+ const max = item.maxValue
|
|
|
+ if (item.dataType === 1) return min === null || min === undefined ? '-' : `≥ ${min}${unit}`
|
|
|
+ if (item.dataType === 2) return max === null || max === undefined ? '-' : `≤ ${max}${unit}`
|
|
|
+ if ((min === null || min === undefined) && (max === null || max === undefined)) return '未设置'
|
|
|
+ return `${min ?? '-'} ~ ${max ?? '-'}${unit}`
|
|
|
}
|
|
|
|
|
|
onMounted(async () => {
|
|
|
- await loadEquipmentTypes()
|
|
|
+ await Promise.all([loadEquipmentTypes(), loadWarningTypes()])
|
|
|
loadData()
|
|
|
})
|
|
|
</script>
|
|
|
@@ -271,6 +639,98 @@ onMounted(async () => {
|
|
|
padding: 16px;
|
|
|
}
|
|
|
|
|
|
+/* 分类统计 */
|
|
|
+.stats-panel {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ align-items: stretch;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding: 16px 20px;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.stats-left {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 12px;
|
|
|
+ width: 300px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-item {
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 10px 14px;
|
|
|
+ background: #fafbfc;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value.green {
|
|
|
+ color: #67c23a;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value.blue {
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-label {
|
|
|
+ margin-top: 2px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.stats-right {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ gap: 32px;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.ring-chart-box {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.ring-chart {
|
|
|
+ width: 110px;
|
|
|
+ height: 110px;
|
|
|
+}
|
|
|
+
|
|
|
+.ring-legend {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #606266;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-dot {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ border-radius: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-num {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
/* 搜索区 */
|
|
|
.search-panel {
|
|
|
background: #fff;
|
|
|
@@ -305,101 +765,82 @@ onMounted(async () => {
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
-/* 所属类型标签 */
|
|
|
-.type-tag {
|
|
|
- font-weight: 500;
|
|
|
- font-size: 13px;
|
|
|
+/* 详情弹窗 */
|
|
|
+.detail-content {
|
|
|
+ padding: 0 8px;
|
|
|
}
|
|
|
|
|
|
-.type-pipe {
|
|
|
- color: #409eff;
|
|
|
+.section-box {
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
-.type-point {
|
|
|
- color: #67c23a;
|
|
|
+.section-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 12px;
|
|
|
}
|
|
|
|
|
|
-.type-well {
|
|
|
- color: #e6a23c;
|
|
|
+.desc-text {
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ line-height: 1.6;
|
|
|
+ max-height: 120px;
|
|
|
+ overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
-/* 详情弹窗 */
|
|
|
-.detail-body {
|
|
|
- display: flex;
|
|
|
- gap: 24px;
|
|
|
- padding: 8px 4px;
|
|
|
+/* 预警指标卡片 */
|
|
|
+.metric-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 12px;
|
|
|
+ margin: 12px 0;
|
|
|
}
|
|
|
|
|
|
-/* 左侧图片占位 */
|
|
|
-.detail-img {
|
|
|
- flex-shrink: 0;
|
|
|
- width: 150px;
|
|
|
- height: 150px;
|
|
|
- background: #d9ecff;
|
|
|
- border: 2px solid #b3d8ff;
|
|
|
+.metric-card {
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
border-radius: 4px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.img-placeholder-icon {
|
|
|
- font-size: 64px;
|
|
|
- color: #409eff;
|
|
|
- opacity: 0.7;
|
|
|
+ padding: 12px 14px;
|
|
|
+ background: #fafbfc;
|
|
|
}
|
|
|
|
|
|
-/* 右侧详情信息 */
|
|
|
-.detail-info {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 10px;
|
|
|
+.metric-label {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ margin-bottom: 6px;
|
|
|
}
|
|
|
|
|
|
-.info-code {
|
|
|
- font-size: 15px;
|
|
|
+.metric-value {
|
|
|
+ font-size: 22px;
|
|
|
font-weight: 600;
|
|
|
color: #303133;
|
|
|
- padding-bottom: 6px;
|
|
|
- border-bottom: 1px solid #f0f0f0;
|
|
|
-}
|
|
|
-
|
|
|
-.info-grid {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 8px;
|
|
|
-}
|
|
|
-
|
|
|
-.info-row {
|
|
|
- display: flex;
|
|
|
- gap: 16px;
|
|
|
+ line-height: 1.2;
|
|
|
}
|
|
|
|
|
|
-.info-row.single {
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-.info-item {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- align-items: baseline;
|
|
|
+.metric-value-text {
|
|
|
font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ word-break: break-all;
|
|
|
}
|
|
|
|
|
|
-.info-label {
|
|
|
+.metric-unit {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 400;
|
|
|
color: #909399;
|
|
|
- white-space: nowrap;
|
|
|
- min-width: 64px;
|
|
|
+ margin-left: 2px;
|
|
|
}
|
|
|
|
|
|
-.info-value {
|
|
|
- color: #303133;
|
|
|
+.metric-empty {
|
|
|
+ color: #c0c4cc;
|
|
|
}
|
|
|
|
|
|
-.desc-text {
|
|
|
- color: #606266;
|
|
|
- line-height: 1.6;
|
|
|
- word-break: break-all;
|
|
|
+.device-missing {
|
|
|
+ margin-bottom: 12px;
|
|
|
}
|
|
|
</style>
|