|
@@ -1,5 +1,14 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="app-container">
|
|
<div class="app-container">
|
|
|
|
|
+ <!-- 等级统计 -->
|
|
|
|
|
+ <div class="stat-row">
|
|
|
|
|
+ <div v-for="card in levelStatCards" :key="card.label" class="stat-card">
|
|
|
|
|
+ <span class="stat-bar" :style="{ background: card.color }" />
|
|
|
|
|
+ <div class="stat-value" :style="{ color: card.color }">{{ card.value }}</div>
|
|
|
|
|
+ <div class="stat-label">{{ card.label }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<el-form ref="queryRef" :inline="true" :model="searchForm" v-show="showSearch" label-width="80px">
|
|
<el-form ref="queryRef" :inline="true" :model="searchForm" v-show="showSearch" label-width="80px">
|
|
|
<el-form-item label="隐患类型" prop="hazardType">
|
|
<el-form-item label="隐患类型" prop="hazardType">
|
|
|
<el-select v-model="searchForm.hazardType" placeholder="请选择隐患类型" clearable style="width: 160px">
|
|
<el-select v-model="searchForm.hazardType" placeholder="请选择隐患类型" clearable style="width: 160px">
|
|
@@ -11,22 +20,21 @@
|
|
|
<el-option v-for="d in hazardLevelDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
|
|
<el-option v-for="d in hazardLevelDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="状态" prop="hazardStatus">
|
|
|
|
|
- <el-select v-model="searchForm.hazardStatus" placeholder="请选择状态" clearable style="width: 160px">
|
|
|
|
|
- <el-option v-for="d in hazardStatusDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="报送状态" prop="isSubmitted">
|
|
|
|
|
- <el-select v-model="searchForm.isSubmitted" placeholder="请选择报送状态" clearable style="width: 160px">
|
|
|
|
|
- <el-option label="未报送" :value="0" />
|
|
|
|
|
- <el-option label="已报送" :value="1" />
|
|
|
|
|
|
|
+ <el-form-item label="所属片区" prop="district">
|
|
|
|
|
+ <el-select v-model="searchForm.district" placeholder="请选择片区" clearable style="width: 160px">
|
|
|
|
|
+ <el-option v-for="d in districtDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="关联管网" prop="networkId">
|
|
|
|
|
- <el-select-v2 v-model="searchForm.networkId" placeholder="请选择关联管网" clearable filterable style="width: 200px" :options="networkSelectOptions" @focus="loadNetworkOptions" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="关联设备" prop="equipmentId">
|
|
|
|
|
- <el-select-v2 v-model="searchForm.equipmentId" placeholder="请选择关联设备" clearable filterable style="width: 200px" :options="equipmentSelectOptions" @focus="loadEquipmentOptions" />
|
|
|
|
|
|
|
+ <el-form-item label="上报时间" prop="dateRange">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="searchForm.dateRange"
|
|
|
|
|
+ type="daterange"
|
|
|
|
|
+ range-separator="至"
|
|
|
|
|
+ start-placeholder="开始日期"
|
|
|
|
|
+ end-placeholder="结束日期"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ style="width: 240px"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
<el-form-item>
|
|
|
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
|
|
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
|
|
@@ -55,10 +63,8 @@
|
|
|
<el-table-column prop="hazardSource" label="隐患来源" width="110" align="center">
|
|
<el-table-column prop="hazardSource" label="隐患来源" width="110" align="center">
|
|
|
<template #default="{ row }">{{ getDictLabel(hazardSourceDict, row.hazardSource) }}</template>
|
|
<template #default="{ row }">{{ getDictLabel(hazardSourceDict, row.hazardSource) }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column prop="hazardStatus" label="状态" width="100" align="center">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-tag :type="statusTag(row.hazardStatus)">{{ getDictLabel(hazardStatusDict, row.hazardStatus) }}</el-tag>
|
|
|
|
|
- </template>
|
|
|
|
|
|
|
+ <el-table-column prop="district" label="所属片区" width="100" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ getDictLabel(districtDict, row.district) || '-' }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="隐患图片" width="90" align="center">
|
|
<el-table-column label="隐患图片" width="90" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
@@ -76,19 +82,21 @@
|
|
|
<el-tag v-else type="info" size="small">无</el-tag>
|
|
<el-tag v-else type="info" size="small">无</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="关联管网" width="140" show-overflow-tooltip>
|
|
|
|
|
- <template #default="{ row }">{{ getNetworkName(row.networkId) }}</template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
<el-table-column label="关联设备" width="140" show-overflow-tooltip>
|
|
<el-table-column label="关联设备" width="140" show-overflow-tooltip>
|
|
|
<template #default="{ row }">{{ getEquipmentName(row.equipmentId) }}</template>
|
|
<template #default="{ row }">{{ getEquipmentName(row.equipmentId) }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column prop="reportUnit" label="上报单位" width="130" show-overflow-tooltip />
|
|
|
|
|
<el-table-column prop="reportTime" label="上报时间" width="170" align="center" />
|
|
<el-table-column prop="reportTime" label="上报时间" width="170" align="center" />
|
|
|
<el-table-column label="操作" width="200" fixed="right" align="center" class-name="small-padding fixed-width">
|
|
<el-table-column label="操作" width="200" fixed="right" align="center" class-name="small-padding fixed-width">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
- <el-button link type="primary" icon="Edit" @click="handleEdit(row)">修改</el-button>
|
|
|
|
|
- <el-button link type="primary" icon="View" @click="handleDetail(row)">详情</el-button>
|
|
|
|
|
- <el-button link type="danger" icon="Delete" @click="handleDelete(row)">删除</el-button>
|
|
|
|
|
|
|
+ <!-- 权限控制:仅未报送数据支持修改、删除 -->
|
|
|
|
|
+ <template v-if="row.isSubmitted === 0">
|
|
|
|
|
+ <el-button link type="primary" icon="Edit" @click="handleEdit(row)">修改</el-button>
|
|
|
|
|
+ <el-button link type="primary" icon="View" @click="handleDetail(row)">详情</el-button>
|
|
|
|
|
+ <el-button link type="danger" icon="Delete" @click="handleDelete(row)">删除</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <el-button link type="primary" icon="View" @click="handleDetail(row)">详情</el-button>
|
|
|
|
|
+ </template>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
@@ -106,11 +114,6 @@
|
|
|
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="750px" append-to-body destroy-on-close>
|
|
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="750px" append-to-body destroy-on-close>
|
|
|
<el-form :model="formData" ref="formRef" label-width="100px" :rules="rules">
|
|
<el-form :model="formData" ref="formRef" label-width="100px" :rules="rules">
|
|
|
<el-row :gutter="16">
|
|
<el-row :gutter="16">
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="隐患编号" prop="hazardNo">
|
|
|
|
|
- <el-input v-model="formData.hazardNo" placeholder="保存时自动生成" readonly />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
<el-form-item label="隐患类型" prop="hazardType">
|
|
<el-form-item label="隐患类型" prop="hazardType">
|
|
|
<el-select v-model="formData.hazardType" style="width: 100%">
|
|
<el-select v-model="formData.hazardType" style="width: 100%">
|
|
@@ -133,18 +136,15 @@
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="关联管网">
|
|
|
|
|
- <el-select-v2 v-model="formData.networkId" placeholder="请选择" clearable filterable style="width: 100%" :options="networkSelectOptions" />
|
|
|
|
|
|
|
+ <el-form-item label="所属片区" prop="district">
|
|
|
|
|
+ <el-select v-model="formData.district" style="width: 100%" placeholder="请选择所属片区" clearable>
|
|
|
|
|
+ <el-option v-for="d in districtDict" :key="d.dictValue" :label="d.dictLabel" :value="d.dictValue" />
|
|
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
<el-form-item label="关联设备" prop="equipmentId">
|
|
<el-form-item label="关联设备" prop="equipmentId">
|
|
|
- <el-select-v2 v-model="formData.equipmentId" placeholder="请选择" clearable filterable style="width: 100%" :options="equipmentSelectOptions" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="上报单位">
|
|
|
|
|
- <el-input v-model="formData.reportUnit" />
|
|
|
|
|
|
|
+ <el-select-v2 v-model="formData.equipmentId" placeholder="请选择" clearable filterable style="width: 100%" :options="equipmentSelectOptions" @change="handleEquipmentChange" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
@@ -157,31 +157,11 @@
|
|
|
<el-input v-model="formData.hazardDesc" type="textarea" :rows="3" placeholder="请输入隐患描述" />
|
|
<el-input v-model="formData.hazardDesc" type="textarea" :rows="3" placeholder="请输入隐患描述" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="整改方案">
|
|
|
|
|
- <el-input v-model="formData.rectifyPlan" type="textarea" :rows="3" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
<el-form-item label="整改期限">
|
|
<el-form-item label="整改期限">
|
|
|
<el-date-picker v-model="formData.rectifyDeadline" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%" />
|
|
<el-date-picker v-model="formData.rectifyDeadline" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="整改责任人">
|
|
|
|
|
- <el-input v-model="formData.rectifyPerson" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="经度">
|
|
|
|
|
- <el-input-number v-model="formData.longitude" :precision="10" style="width: 100%" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="12">
|
|
|
|
|
- <el-form-item label="纬度">
|
|
|
|
|
- <el-input-number v-model="formData.latitude" :precision="10" style="width: 100%" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
<el-col :span="24">
|
|
<el-col :span="24">
|
|
|
<el-form-item label="隐患图片" class="image-form-item">
|
|
<el-form-item label="隐患图片" class="image-form-item">
|
|
|
<div class="image-upload-content">
|
|
<div class="image-upload-content">
|
|
@@ -220,7 +200,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <!-- 详情对话框 -->
|
|
|
|
|
|
|
+ <!-- 详情抽屉 -->
|
|
|
<el-drawer title="隐患详情" v-model="detailVisible" size="900px" class="detail-drawer" direction="rtl">
|
|
<el-drawer title="隐患详情" v-model="detailVisible" size="900px" class="detail-drawer" direction="rtl">
|
|
|
<el-descriptions v-if="detailData" :column="2" border>
|
|
<el-descriptions v-if="detailData" :column="2" border>
|
|
|
<el-descriptions-item label="隐患编号">{{ detailData.hazardNo }}</el-descriptions-item>
|
|
<el-descriptions-item label="隐患编号">{{ detailData.hazardNo }}</el-descriptions-item>
|
|
@@ -229,15 +209,11 @@
|
|
|
<el-tag :type="levelTag(detailData.hazardLevel)">{{ getDictLabel(hazardLevelDict, detailData.hazardLevel) }}</el-tag>
|
|
<el-tag :type="levelTag(detailData.hazardLevel)">{{ getDictLabel(hazardLevelDict, detailData.hazardLevel) }}</el-tag>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="隐患来源">{{ getDictLabel(hazardSourceDict, detailData.hazardSource) }}</el-descriptions-item>
|
|
<el-descriptions-item label="隐患来源">{{ getDictLabel(hazardSourceDict, detailData.hazardSource) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="状态">
|
|
|
|
|
- <el-tag :type="statusTag(detailData.hazardStatus)">{{ getDictLabel(hazardStatusDict, detailData.hazardStatus) }}</el-tag>
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
|
|
+ <el-descriptions-item label="所属片区">{{ getDictLabel(districtDict, detailData.district) || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="报送状态">
|
|
<el-descriptions-item label="报送状态">
|
|
|
<el-tag :type="detailData.isSubmitted === 1 ? 'success' : 'info'">{{ detailData.isSubmitted === 1 ? '已报送' : '未报送' }}</el-tag>
|
|
<el-tag :type="detailData.isSubmitted === 1 ? 'success' : 'info'">{{ detailData.isSubmitted === 1 ? '已报送' : '未报送' }}</el-tag>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="关联管网">{{ getNetworkName(detailData.networkId) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="关联设备">{{ getEquipmentName(detailData.equipmentId) }}</el-descriptions-item>
|
|
<el-descriptions-item label="关联设备">{{ getEquipmentName(detailData.equipmentId) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="上报单位">{{ detailData.reportUnit || '-' }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="上报时间">{{ detailData.reportTime || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="上报时间">{{ detailData.reportTime || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="隐患描述" :span="2">{{ detailData.hazardDesc || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="隐患描述" :span="2">{{ detailData.hazardDesc || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="整改方案" :span="2">{{ detailData.rectifyPlan || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="整改方案" :span="2">{{ detailData.rectifyPlan || '-' }}</el-descriptions-item>
|
|
@@ -281,12 +257,11 @@
|
|
|
import { computed, ref, reactive, shallowRef, onMounted } from 'vue'
|
|
import { computed, ref, reactive, shallowRef, onMounted } from 'vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import { Close } from '@element-plus/icons-vue'
|
|
import { Close } from '@element-plus/icons-vue'
|
|
|
-import request from '@/utils/request'
|
|
|
|
|
import { createMinioBatchUploader } from '@/utils/minioBatchUpload'
|
|
import { createMinioBatchUploader } from '@/utils/minioBatchUpload'
|
|
|
-import { getHazardPage, getHazardById, addHazard, updateHazard, deleteHazard } from '@/api/pipeNetwork/hazard'
|
|
|
|
|
|
|
+import { getHazardPage, getHazardById, getHazardStatisticsByLevel, addHazard, updateHazard, deleteHazard } from '@/api/pipeNetwork/hazard'
|
|
|
import { getDicts } from '@/api/system/dict/data'
|
|
import { getDicts } from '@/api/system/dict/data'
|
|
|
-import { buildMinioAttachmentUrl } from '@/utils/minioAttachment'
|
|
|
|
|
-import { getPipeNetworkOptions, getEquipmentByTopLevelType } from '@/api/pipeNetwork/basic'
|
|
|
|
|
|
|
+import { buildMinioAttachmentUrl, minioAttachmentPath } from '@/utils/minioAttachment'
|
|
|
|
|
+import { getEquipmentByTopLevelType } from '@/api/pipeNetwork/basic'
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
const showSearch = ref(true)
|
|
const showSearch = ref(true)
|
|
@@ -295,7 +270,7 @@ const tableData = ref([])
|
|
|
const pageNum = ref(1)
|
|
const pageNum = ref(1)
|
|
|
const pageSize = ref(10)
|
|
const pageSize = ref(10)
|
|
|
const total = ref(0)
|
|
const total = ref(0)
|
|
|
-const searchForm = reactive({ hazardType: '', hazardLevel: '', hazardStatus: '', isSubmitted: null, networkId: '', equipmentId: '' })
|
|
|
|
|
|
|
+const searchForm = reactive({ hazardType: '', hazardLevel: '', dateRange: [] })
|
|
|
const dialogVisible = ref(false)
|
|
const dialogVisible = ref(false)
|
|
|
const dialogTitle = ref('新增')
|
|
const dialogTitle = ref('新增')
|
|
|
const formRef = ref(null)
|
|
const formRef = ref(null)
|
|
@@ -312,25 +287,36 @@ const rules = {
|
|
|
|
|
|
|
|
const formData = reactive({
|
|
const formData = reactive({
|
|
|
hazardId: '', hazardNo: '', hazardType: '', hazardLevel: '', hazardDesc: '',
|
|
hazardId: '', hazardNo: '', hazardType: '', hazardLevel: '', hazardDesc: '',
|
|
|
- hazardSource: '', hazardStatus: '待核查', reportUnit: '', reportTime: '',
|
|
|
|
|
|
|
+ hazardSource: '', district: '', hazardStatus: '待核查', reportTime: '',
|
|
|
rectifyPlan: '', rectifyDeadline: '', rectifyPerson: '',
|
|
rectifyPlan: '', rectifyDeadline: '', rectifyPerson: '',
|
|
|
- networkId: '', equipmentId: '', longitude: null, latitude: null,
|
|
|
|
|
|
|
+ equipmentId: '', longitude: null, latitude: null,
|
|
|
hazardImages: ''
|
|
hazardImages: ''
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+// 等级统计(顶部卡片):接口返回 [{name: 等级编码, value: 数量}]
|
|
|
|
|
+const levelCountMap = shallowRef({})
|
|
|
|
|
+const levelStatCards = computed(() => {
|
|
|
|
|
+ const counts = levelCountMap.value
|
|
|
|
|
+ // 等级配色与报警等级一致:1 红 / 2 橙 / 3 蓝 / 4 绿
|
|
|
|
|
+ const colorMap = { '1': '#f5222d', '2': '#fa8c16', '3': '#1890ff', '4': '#52c41a' }
|
|
|
|
|
+ const bgMap = { '1': '#fef0f0', '2': '#fdf6ec', '3': '#ecf5ff', '4': '#f0f9eb' }
|
|
|
|
|
+ const cards = [{ label: '隐患总数', value: Object.values(counts).reduce((sum, n) => sum + Number(n || 0), 0), color: '#409eff', bg: '#ecf5ff' }]
|
|
|
|
|
+ hazardLevelDict.value.forEach(d => {
|
|
|
|
|
+ cards.push({ label: d.dictLabel, value: Number(counts[d.dictValue] || 0), color: colorMap[d.dictValue] || '#909399', bg: bgMap[d.dictValue] || '#f4f4f5' })
|
|
|
|
|
+ })
|
|
|
|
|
+ return cards
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
const hazardTypeDict = ref([])
|
|
const hazardTypeDict = ref([])
|
|
|
const hazardLevelDict = ref([])
|
|
const hazardLevelDict = ref([])
|
|
|
-const hazardStatusDict = ref([])
|
|
|
|
|
const hazardSourceDict = ref([])
|
|
const hazardSourceDict = ref([])
|
|
|
-// 选项列表可能较大,使用浅层响应式并缓存请求,避免每次编辑都触发整棵数组深度更新。
|
|
|
|
|
-const networkOptions = shallowRef([])
|
|
|
|
|
|
|
+// 所属片区,与字典 drainage_district、DistrictEnum、equipment_base.district 同口径(0城南/1城北/2太常)
|
|
|
|
|
+const districtDict = ref([])
|
|
|
|
|
+// 选项列表可能较大,使用浅层响应式并缓存请求,避免重复深度更新。
|
|
|
const equipmentOptions = shallowRef([])
|
|
const equipmentOptions = shallowRef([])
|
|
|
-const networkNameCache = shallowRef(new Map())
|
|
|
|
|
const equipmentNameCache = shallowRef(new Map())
|
|
const equipmentNameCache = shallowRef(new Map())
|
|
|
const rowImageCache = new WeakMap()
|
|
const rowImageCache = new WeakMap()
|
|
|
-const networkSelectOptions = computed(() => networkOptions.value.map(item => ({ value: item.networkId, label: item.networkName })))
|
|
|
|
|
const equipmentSelectOptions = computed(() => equipmentOptions.value.map(item => ({ value: item.equipmentId, label: item.equipmentName })))
|
|
const equipmentSelectOptions = computed(() => equipmentOptions.value.map(item => ({ value: item.equipmentId, label: item.equipmentName })))
|
|
|
-let networkOptionsRequest = null
|
|
|
|
|
let equipmentOptionsRequest = null
|
|
let equipmentOptionsRequest = null
|
|
|
|
|
|
|
|
// 详情相关
|
|
// 详情相关
|
|
@@ -352,11 +338,8 @@ function getDictLabel(dict, value) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function levelTag(value) {
|
|
function levelTag(value) {
|
|
|
- const map = { '0': 'danger', '1': 'warning', '2': 'info', '3': '' }
|
|
|
|
|
- return map[value] || 'info'
|
|
|
|
|
-}
|
|
|
|
|
-function statusTag(value) {
|
|
|
|
|
- const map = { '待核查': 'info', '整改中': 'warning', '已整改': 'success', '已闭环': '' }
|
|
|
|
|
|
|
+ // 与报警等级一致:1 danger / 2 warning / 3 primary(空串) / 4 success
|
|
|
|
|
+ const map = { '1': 'danger', '2': 'warning', '3': '', '4': 'success' }
|
|
|
return map[value] || 'info'
|
|
return map[value] || 'info'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -406,6 +389,42 @@ function normalizeImageUrl(value) {
|
|
|
return url.startsWith('/') ? `${baseApi}${url}` : `${baseApi}/${url}`
|
|
return url.startsWith('/') ? `${baseApi}${url}` : `${baseApi}/${url}`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+async function loadDicts() {
|
|
|
|
|
+ // 隐患等级统一使用 hazard_level 字典(已对齐报警等级口径:1~4,1 最严重,红/橙/蓝/绿)
|
|
|
|
|
+ const [typeRes, levelRes, sourceRes, districtRes] = await Promise.all([
|
|
|
|
|
+ getDicts('hazard_type'),
|
|
|
|
|
+ getDicts('hazard_level'),
|
|
|
|
|
+ getDicts('hazard_source'),
|
|
|
|
|
+ getDicts('drainage_district')
|
|
|
|
|
+ ])
|
|
|
|
|
+ hazardTypeDict.value = typeRes.data
|
|
|
|
|
+ hazardLevelDict.value = levelRes.data
|
|
|
|
|
+ hazardSourceDict.value = sourceRes.data
|
|
|
|
|
+ districtDict.value = districtRes.data
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 等级统计(全量,不按时间过滤) */
|
|
|
|
|
+async function loadLevelStats() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getHazardStatisticsByLevel()
|
|
|
|
|
+ const list = res?.data || []
|
|
|
|
|
+ const map = {}
|
|
|
|
|
+ list.forEach(item => { map[item.name] = Number(item.value || 0) })
|
|
|
|
|
+ levelCountMap.value = map
|
|
|
|
|
+ } catch (e) { console.error(e) }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 选择关联设备后自动带出经纬度写入 formData(表单已不展示,仅供隐患一张图取点;
|
|
|
|
|
+ * equipment_base 存 WGS84,与隐患表同口径,直接拷贝) */
|
|
|
|
|
+function handleEquipmentChange(equipmentId) {
|
|
|
|
|
+ if (!equipmentId) { formData.longitude = null; formData.latitude = null; return }
|
|
|
|
|
+ const eq = equipmentOptions.value.find(item => String(item.equipmentId) === String(equipmentId))
|
|
|
|
|
+ if (!eq) return
|
|
|
|
|
+ const lng = Number(eq.longitude), lat = Number(eq.latitude)
|
|
|
|
|
+ formData.longitude = Number.isFinite(lng) ? lng : null
|
|
|
|
|
+ formData.latitude = Number.isFinite(lat) ? lat : null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** 图片选择后立即上传到 MinIO(同一批次多张合并为一次 /minio/uploads 请求) */
|
|
/** 图片选择后立即上传到 MinIO(同一批次多张合并为一次 /minio/uploads 请求) */
|
|
|
const hazardImageUploader = createMinioBatchUploader('hazard')
|
|
const hazardImageUploader = createMinioBatchUploader('hazard')
|
|
|
hazardImageUploader.onBatchSettled(() => { imageUploading.value = false })
|
|
hazardImageUploader.onBatchSettled(() => { imageUploading.value = false })
|
|
@@ -418,10 +437,12 @@ async function handleImageChange(file) {
|
|
|
imageUploading.value = true
|
|
imageUploading.value = true
|
|
|
try {
|
|
try {
|
|
|
const info = await hazardImageUploader(file)
|
|
const info = await hazardImageUploader(file)
|
|
|
- const url = normalizeImageUrl(info.relativePath || info.url || info.fileName)
|
|
|
|
|
- if (!url) throw new Error('上传接口未返回图片地址')
|
|
|
|
|
- imageUrls.value.push(url)
|
|
|
|
|
- imageList.value.push(url)
|
|
|
|
|
|
|
+ const displayUrl = normalizeImageUrl(info.relativePath || info.url || info.fileName)
|
|
|
|
|
+ if (!displayUrl) throw new Error('上传接口未返回图片地址')
|
|
|
|
|
+ // imageUrls 存 MinIO 相对路径(入库口径,工单校验只认相对路径);imageList 存展示用完整地址
|
|
|
|
|
+ const relativePath = minioAttachmentPath(info.relativePath || info.url || info.fileName)
|
|
|
|
|
+ imageUrls.value.push(relativePath)
|
|
|
|
|
+ imageList.value.push(displayUrl)
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
ElMessage.error('图片上传失败')
|
|
ElMessage.error('图片上传失败')
|
|
|
}
|
|
}
|
|
@@ -440,23 +461,21 @@ function previewImage(index) {
|
|
|
previewVisible.value = true
|
|
previewVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function loadDicts() {
|
|
|
|
|
- const [typeRes, levelRes, statusRes, sourceRes] = await Promise.all([
|
|
|
|
|
- getDicts('hazard_type'),
|
|
|
|
|
- getDicts('hazard_level'),
|
|
|
|
|
- getDicts('hazard_status'),
|
|
|
|
|
- getDicts('hazard_source')
|
|
|
|
|
- ])
|
|
|
|
|
- hazardTypeDict.value = typeRes.data
|
|
|
|
|
- hazardLevelDict.value = levelRes.data
|
|
|
|
|
- hazardStatusDict.value = statusRes.data
|
|
|
|
|
- hazardSourceDict.value = sourceRes.data
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
async function loadData() {
|
|
async function loadData() {
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
try {
|
|
try {
|
|
|
- const res = await getHazardPage(pageNum.value, pageSize.value, searchForm)
|
|
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ hazardType: searchForm.hazardType,
|
|
|
|
|
+ hazardLevel: searchForm.hazardLevel,
|
|
|
|
|
+ district: searchForm.district,
|
|
|
|
|
+ // 隐患排查只看未报送数据;已报送的走隐患报送/台账页
|
|
|
|
|
+ isSubmitted: 0
|
|
|
|
|
+ }
|
|
|
|
|
+ if (Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) {
|
|
|
|
|
+ params.startDate = searchForm.dateRange[0]
|
|
|
|
|
+ params.endDate = searchForm.dateRange[1]
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await getHazardPage(pageNum.value, pageSize.value, params)
|
|
|
tableData.value = res.data.records || []
|
|
tableData.value = res.data.records || []
|
|
|
total.value = res.data.total || 0
|
|
total.value = res.data.total || 0
|
|
|
} catch (e) { console.error(e) } finally { loading.value = false }
|
|
} catch (e) { console.error(e) } finally { loading.value = false }
|
|
@@ -464,22 +483,9 @@ async function loadData() {
|
|
|
function handleSearch() { pageNum.value = 1; loadData() }
|
|
function handleSearch() { pageNum.value = 1; loadData() }
|
|
|
function handleReset() {
|
|
function handleReset() {
|
|
|
queryRef.value?.resetFields()
|
|
queryRef.value?.resetFields()
|
|
|
- Object.assign(searchForm, { hazardType: '', hazardLevel: '', hazardStatus: '', isSubmitted: null, networkId: '', equipmentId: '' })
|
|
|
|
|
|
|
+ Object.assign(searchForm, { hazardType: '', hazardLevel: '', district: '', dateRange: [] })
|
|
|
handleSearch()
|
|
handleSearch()
|
|
|
}
|
|
}
|
|
|
-async function loadNetworkOptions() {
|
|
|
|
|
- if (Array.isArray(networkOptions.value) && networkOptions.value.length > 0) return
|
|
|
|
|
- if (networkOptionsRequest) return networkOptionsRequest
|
|
|
|
|
- networkOptionsRequest = getPipeNetworkOptions()
|
|
|
|
|
- .then(res => {
|
|
|
|
|
- networkOptions.value = Array.isArray(res.data) ? res.data : []
|
|
|
|
|
- networkNameCache.value = new Map(networkOptions.value.map(item => [String(item.networkId), item.networkName]))
|
|
|
|
|
- return networkOptions.value
|
|
|
|
|
- })
|
|
|
|
|
- .catch(e => { console.error(e); return [] })
|
|
|
|
|
- .finally(() => { networkOptionsRequest = null })
|
|
|
|
|
- return networkOptionsRequest
|
|
|
|
|
-}
|
|
|
|
|
async function loadEquipmentOptions() {
|
|
async function loadEquipmentOptions() {
|
|
|
if (Array.isArray(equipmentOptions.value) && equipmentOptions.value.length > 0) return
|
|
if (Array.isArray(equipmentOptions.value) && equipmentOptions.value.length > 0) return
|
|
|
if (equipmentOptionsRequest) return equipmentOptionsRequest
|
|
if (equipmentOptionsRequest) return equipmentOptionsRequest
|
|
@@ -493,9 +499,6 @@ async function loadEquipmentOptions() {
|
|
|
.finally(() => { equipmentOptionsRequest = null })
|
|
.finally(() => { equipmentOptionsRequest = null })
|
|
|
return equipmentOptionsRequest
|
|
return equipmentOptionsRequest
|
|
|
}
|
|
}
|
|
|
-function getNetworkName(id) {
|
|
|
|
|
- return networkNameCache.value.get(String(id)) || id || '-'
|
|
|
|
|
-}
|
|
|
|
|
function getEquipmentName(id) {
|
|
function getEquipmentName(id) {
|
|
|
return equipmentNameCache.value.get(String(id)) || id || '-'
|
|
return equipmentNameCache.value.get(String(id)) || id || '-'
|
|
|
}
|
|
}
|
|
@@ -509,20 +512,20 @@ function generateHazardNo() {
|
|
|
}
|
|
}
|
|
|
function handleAdd() {
|
|
function handleAdd() {
|
|
|
dialogTitle.value = '新增'
|
|
dialogTitle.value = '新增'
|
|
|
- Object.assign(formData, { hazardId: '', hazardNo: generateHazardNo(), hazardType: '', hazardLevel: '', hazardDesc: '', hazardSource: '', hazardStatus: '待核查', reportUnit: '', reportTime: '', rectifyPlan: '', rectifyDeadline: '', rectifyPerson: '', networkId: '', equipmentId: '', longitude: null, latitude: null, hazardImages: '', isSubmitted: null, submitTime: null, verifyPerson: null, verifyTime: null })
|
|
|
|
|
|
|
+ // 隐患编号不在表单展示,新增时自动生成(后端 saveWithCheck 会校验唯一)
|
|
|
|
|
+ Object.assign(formData, { hazardId: '', hazardNo: generateHazardNo(), hazardType: '', hazardLevel: '', hazardDesc: '', hazardSource: '', district: '', hazardStatus: '待核查', reportTime: '', rectifyPlan: '', rectifyDeadline: '', rectifyPerson: '', equipmentId: '', longitude: null, latitude: null, hazardImages: '', isSubmitted: null, submitTime: null, verifyPerson: null, verifyTime: null })
|
|
|
imageList.value = []
|
|
imageList.value = []
|
|
|
imageUrls.value = []
|
|
imageUrls.value = []
|
|
|
dialogVisible.value = true
|
|
dialogVisible.value = true
|
|
|
- loadNetworkOptions(); loadEquipmentOptions()
|
|
|
|
|
|
|
+ loadEquipmentOptions()
|
|
|
}
|
|
}
|
|
|
function handleEdit(row) {
|
|
function handleEdit(row) {
|
|
|
dialogTitle.value = '编辑'
|
|
dialogTitle.value = '编辑'
|
|
|
// 只回填表单字段,避免把接口返回的分页/审计字段直接写入响应式表单。
|
|
// 只回填表单字段,避免把接口返回的分页/审计字段直接写入响应式表单。
|
|
|
- // 直接 Object.assign(row) 在部分数据返回结构下会导致弹窗渲染持续触发更新。
|
|
|
|
|
const editableFields = [
|
|
const editableFields = [
|
|
|
'hazardId', 'hazardNo', 'hazardType', 'hazardLevel', 'hazardDesc',
|
|
'hazardId', 'hazardNo', 'hazardType', 'hazardLevel', 'hazardDesc',
|
|
|
- 'hazardSource', 'hazardStatus', 'reportUnit', 'reportTime',
|
|
|
|
|
- 'rectifyPlan', 'rectifyDeadline', 'rectifyPerson', 'networkId',
|
|
|
|
|
|
|
+ 'hazardSource', 'district', 'hazardStatus', 'reportTime',
|
|
|
|
|
+ 'rectifyPlan', 'rectifyDeadline', 'rectifyPerson',
|
|
|
'equipmentId', 'longitude', 'latitude', 'hazardImages'
|
|
'equipmentId', 'longitude', 'latitude', 'hazardImages'
|
|
|
]
|
|
]
|
|
|
editableFields.forEach(key => {
|
|
editableFields.forEach(key => {
|
|
@@ -535,13 +538,13 @@ function handleEdit(row) {
|
|
|
formData[key] = value ?? ''
|
|
formData[key] = value ?? ''
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
- // 回显已有图片
|
|
|
|
|
|
|
+ // 回显已有图片:imageList 存展示地址,imageUrls 规整为相对路径(保存入库口径,兼容历史绝对地址)
|
|
|
const urls = getRowImageUrls(row)
|
|
const urls = getRowImageUrls(row)
|
|
|
- imageUrls.value = [...urls]
|
|
|
|
|
|
|
+ imageUrls.value = urls.map(item => minioAttachmentPath(item))
|
|
|
imageList.value = [...urls]
|
|
imageList.value = [...urls]
|
|
|
dialogVisible.value = true
|
|
dialogVisible.value = true
|
|
|
// 选项加载不阻塞弹窗打开,且函数内部会避免重复请求。
|
|
// 选项加载不阻塞弹窗打开,且函数内部会避免重复请求。
|
|
|
- void Promise.allSettled([loadNetworkOptions(), loadEquipmentOptions()])
|
|
|
|
|
|
|
+ void Promise.allSettled([loadEquipmentOptions()])
|
|
|
}
|
|
}
|
|
|
async function handleSave() {
|
|
async function handleSave() {
|
|
|
if (!formRef.value) return
|
|
if (!formRef.value) return
|
|
@@ -552,7 +555,7 @@ async function handleSave() {
|
|
|
formData.hazardImages = imageUrls.value.join(',')
|
|
formData.hazardImages = imageUrls.value.join(',')
|
|
|
if (formData.hazardId) { await updateHazard(formData); ElMessage.success('修改成功') }
|
|
if (formData.hazardId) { await updateHazard(formData); ElMessage.success('修改成功') }
|
|
|
else { await addHazard(formData); ElMessage.success('新增成功') }
|
|
else { await addHazard(formData); ElMessage.success('新增成功') }
|
|
|
- dialogVisible.value = false; loadData()
|
|
|
|
|
|
|
+ dialogVisible.value = false; loadData(); loadLevelStats()
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
const msg = e?.response?.data?.msg || e?.message || '操作失败'
|
|
const msg = e?.response?.data?.msg || e?.message || '操作失败'
|
|
|
ElMessage.error(msg)
|
|
ElMessage.error(msg)
|
|
@@ -560,7 +563,7 @@ async function handleSave() {
|
|
|
}
|
|
}
|
|
|
function handleDelete(row) {
|
|
function handleDelete(row) {
|
|
|
ElMessageBox.confirm('确认删除该隐患记录?', '提示', { type: 'warning' }).then(async () => {
|
|
ElMessageBox.confirm('确认删除该隐患记录?', '提示', { type: 'warning' }).then(async () => {
|
|
|
- await deleteHazard(row.hazardId); ElMessage.success('删除成功'); loadData()
|
|
|
|
|
|
|
+ await deleteHazard(row.hazardId); ElMessage.success('删除成功'); loadData(); loadLevelStats()
|
|
|
}).catch(() => {})
|
|
}).catch(() => {})
|
|
|
}
|
|
}
|
|
|
async function handleDetail(row) {
|
|
async function handleDetail(row) {
|
|
@@ -578,10 +581,47 @@ async function handleDetail(row) {
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
await loadDicts()
|
|
await loadDicts()
|
|
|
void loadData()
|
|
void loadData()
|
|
|
|
|
+ void loadLevelStats()
|
|
|
|
|
+ void loadEquipmentOptions()
|
|
|
})
|
|
})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
|
|
+/* 等级统计卡片:白底 + 左侧色条,flex 等分一行排开 */
|
|
|
|
|
+.stat-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.stat-card {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ padding: 14px 16px 14px 22px;
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 21, 41, 0.06);
|
|
|
|
|
+}
|
|
|
|
|
+.stat-bar {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ width: 4px;
|
|
|
|
|
+ border-radius: 6px 0 0 6px;
|
|
|
|
|
+}
|
|
|
|
|
+.stat-value {
|
|
|
|
|
+ font-size: 26px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+}
|
|
|
|
|
+.stat-label {
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/* 表格图片样式 */
|
|
/* 表格图片样式 */
|
|
|
.table-image {
|
|
.table-image {
|
|
|
width: 40px;
|
|
width: 40px;
|