| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- <template>
- <div class="app-container bjsh-page">
- <!-- ==================== 上半部分:GIS地图 ==================== -->
- <div class="map-section">
- <div id="bjshMap" class="map-container"></div>
- </div>
- <!-- ==================== 下半部分:搜索 + 报警审核表格 ==================== -->
- <div class="table-section">
- <!-- 搜索区 -->
- <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
- <el-form-item label="设备名称">
- <el-input v-model="queryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 200px" />
- </el-form-item>
- <el-form-item label="报警等级">
- <el-select v-model="queryParams.warningLevel" placeholder="请选择" clearable style="width: 200px">
- <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="queryParams.status" placeholder="请选择" clearable style="width: 200px">
- <el-option label="待审核" :value="1" />
- <el-option label="已审核" :value="2" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery">搜索</el-button>
- <el-button @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- <!-- 报警审核表格 -->
- <el-table :data="tableData" v-loading="loading" border stripe style="width: 100%">
- <el-table-column prop="id" label="报警编号" width="80" align="center" />
- <el-table-column prop="equipmentName" label="设备名称" min-width="140" />
- <el-table-column label="报警等级" width="100">
- <template #default="{ row }">
- <el-tag :type="levelTagType(row.warningLevel)" size="small">{{ levelText(row.warningLevel) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="warningTime" label="报警时间" width="160" />
- <el-table-column label="监测值" width="100">
- <template #default="{ row }">
- <span :style="{ color: parseFloat(row.currentValue) > parseFloat(row.thresholdValue) ? '#f56c6c' : '#303133' }">{{ row.currentValue }}</span>
- </template>
- </el-table-column>
- <el-table-column label="审核状态" width="100">
- <template #default="{ row }">
- <el-tag :type="auditStatusTagType(row.status)" size="small">{{ auditStatusText(row.status) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="160" align="center">
- <template #default="{ row }">
- <el-button v-if="row.status === 1" link type="primary" @click="openAudit(row)">审核</el-button>
- <el-button link type="primary" @click="openView(row)">查看</el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <!-- ==================== 审核弹窗 ==================== -->
- <el-dialog v-model="auditVisible" title="报警审核" width="700px" :before-close="closeAudit">
- <!-- 报警信息展示 -->
- <el-descriptions :column="2" border size="small" style="margin-bottom: 16px">
- <el-descriptions-item label="设备名称">{{ currentAlarm.equipmentName }}</el-descriptions-item>
- <el-descriptions-item label="报警时间">{{ currentAlarm.warningTime }}</el-descriptions-item>
- <el-descriptions-item label="报警等级">
- <el-tag :type="levelTagType(currentAlarm.warningLevel)" size="small">{{ levelText(currentAlarm.warningLevel) }}</el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="指标值">{{ currentAlarm.warningType || '-' }}:{{ currentAlarm.currentValue }}(阈值 {{ currentAlarm.thresholdValue }})</el-descriptions-item>
- </el-descriptions>
- <!-- 审核操作 -->
- <div class="audit-form-section">
- <div class="section-title">审核操作</div>
- <el-form :model="auditForm" label-width="90px" style="margin-top: 12px">
- <el-form-item label="报警类型">
- <el-tag type="info" size="small">{{ currentAlarm.warningType || '设备报警' }}</el-tag>
- </el-form-item>
- <el-form-item label="审核结果" required>
- <el-radio-group v-model="auditForm.result">
- <el-radio v-for="opt in auditResultOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="审核/解除意见" required>
- <el-input v-model="auditForm.opinion" type="textarea" :rows="3" :placeholder="auditForm.result === '解除报警' ? '请输入解除原因及说明' : '请输入审核意见'" />
- </el-form-item>
- <el-form-item label="附件上传">
- <el-upload
- action="#"
- :auto-upload="false"
- :file-list="auditFiles"
- :limit="5"
- :on-change="handleFileChange"
- :on-remove="handleFileRemove"
- accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"
- >
- <el-button type="primary" size="small">选择文件</el-button>
- <template #tip>
- <div class="upload-tip">支持jpg/png/pdf/doc格式,最多5个文件,单文件不超过10MB</div>
- </template>
- </el-upload>
- </el-form-item>
- </el-form>
- </div>
- <!-- 处置流程时间线 -->
- <div class="audit-timeline-section">
- <div class="section-title">处置全流程跟踪</div>
- <el-timeline style="margin-top: 12px; padding-left: 2px">
- <el-timeline-item :timestamp="currentAlarm.warningTime || ''" placement="top" color="#f56c6c">
- <div class="timeline-node">报警触发</div>
- <div class="timeline-desc">{{ currentAlarm.equipmentName }} {{ currentAlarm.warningType || '' }}超标,当前值:{{ currentAlarm.currentValue || '-' }},阈值:{{ currentAlarm.thresholdValue || '-' }}</div>
- </el-timeline-item>
- <el-timeline-item placement="top" color="#e6a23c">
- <div class="timeline-node">系统通知</div>
- <div class="timeline-desc">已自动推送至值守人员及相关负责人</div>
- </el-timeline-item>
- <el-timeline-item v-if="currentAlarm.firstApproverName" placement="top" color="#409eff">
- <div class="timeline-node">下派处理</div>
- <div class="timeline-desc">已下派至:{{ currentAlarm.firstApproverName }}</div>
- </el-timeline-item>
- <el-timeline-item timestamp="当前" placement="top" color="#409eff">
- <div class="timeline-node">人工审核</div>
- <div class="timeline-desc">审核进行中,等待审核结果</div>
- </el-timeline-item>
- <el-timeline-item timestamp="待完成" placement="top" color="#909399">
- <div class="timeline-node">处置完成</div>
- <div class="timeline-desc">审核通过后完成闭环</div>
- </el-timeline-item>
- </el-timeline>
- </div>
- <template #footer>
- <el-button @click="closeAudit">取消</el-button>
- <el-button type="primary" :loading="submitting" @click="submitAudit">提交审核</el-button>
- </template>
- </el-dialog>
- <!-- ==================== 查看详情弹窗 ==================== -->
- <el-dialog v-model="viewVisible" title="报警处置全流程" width="650px">
- <el-descriptions :column="2" border size="small">
- <el-descriptions-item label="报警编号">{{ viewRow.id }}</el-descriptions-item>
- <el-descriptions-item label="设备名称">{{ viewRow.equipmentName }}</el-descriptions-item>
- <el-descriptions-item label="报警等级">
- <el-tag :type="levelTagType(viewRow.warningLevel)" size="small">{{ levelText(viewRow.warningLevel) }}</el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="报警时间">{{ viewRow.warningTime }}</el-descriptions-item>
- <el-descriptions-item label="监测指标">{{ viewRow.warningType || '-' }}</el-descriptions-item>
- <el-descriptions-item label="实时值">{{ viewRow.currentValue }}</el-descriptions-item>
- <el-descriptions-item label="阈值">{{ viewRow.thresholdValue }}</el-descriptions-item>
- <el-descriptions-item label="审核状态">
- <el-tag :type="auditStatusTagType(viewRow.status)" size="small">{{ auditStatusText(viewRow.status) }}</el-tag>
- </el-descriptions-item>
- <el-descriptions-item v-if="viewRow.auditResult" label="审核结果">{{ viewRow.auditResult }}</el-descriptions-item>
- <el-descriptions-item v-if="viewRow.auditOpinion" label="审核意见">{{ viewRow.auditOpinion }}</el-descriptions-item>
- <el-descriptions-item label="位置" :span="2">{{ viewRow.location || '-' }}</el-descriptions-item>
- <el-descriptions-item label="描述" :span="2">{{ viewRow.content || '-' }}</el-descriptions-item>
- </el-descriptions>
- <!-- 处置全流程跟踪 -->
- <div style="margin-top: 16px">
- <div class="section-title">处置全流程跟踪</div>
- <el-timeline style="margin-top: 12px; padding-left: 2px">
- <el-timeline-item :timestamp="viewRow.warningTime || ''" placement="top" color="#f56c6c">
- <div class="timeline-node">报警触发</div>
- <div class="timeline-desc">{{ viewRow.equipmentName }} {{ viewRow.warningType || '' }}超标,当前值:{{ viewRow.currentValue || '-' }}</div>
- </el-timeline-item>
- <el-timeline-item placement="top" color="#e6a23c">
- <div class="timeline-node">系统通知</div>
- <div class="timeline-desc">已自动推送至值守人员及相关负责人</div>
- </el-timeline-item>
- <el-timeline-item v-if="viewRow.firstApproverName" placement="top" color="#409eff">
- <div class="timeline-node">下派处理</div>
- <div class="timeline-desc">已下派至:{{ viewRow.firstApproverName }}</div>
- </el-timeline-item>
- <el-timeline-item v-if="viewRow.status === 2" placement="top" color="#67c23a">
- <div class="timeline-node">审核完成</div>
- <div class="timeline-desc">审核结果:{{ viewRow.auditResult || '-' }};意见:{{ viewRow.auditOpinion || '-' }}</div>
- </el-timeline-item>
- <el-timeline-item v-if="viewRow.status === 2" placement="top" color="#67c23a">
- <div class="timeline-node">处置完成</div>
- <div class="timeline-desc">报警处置流程已闭环</div>
- </el-timeline-item>
- <el-timeline-item v-if="viewRow.status !== 2" timestamp="进行中" placement="top" color="#909399">
- <div class="timeline-node">待审核</div>
- <div class="timeline-desc">等待人工审核处理</div>
- </el-timeline-item>
- </el-timeline>
- </div>
- </el-dialog>
- </div>
- </template>
- <script setup name="Bjsh">
- import { ref, computed, onMounted, nextTick } from 'vue'
- import { ElMessage } from 'element-plus'
- import locationIcon from '@/assets/images/location.png'
- import { getAlarmDataPage, handleAlarm } from '@/api/drainage'
- import useUserStore from '@/store/modules/user'
- // ==================== 用户信息 ====================
- const userStore = useUserStore()
- const isAdmin = computed(() => userStore.roles && userStore.roles.includes('admin'))
- // ==================== 地图变量 ====================
- let mapInstance = null
- let mapInitialized = false
- // ==================== 等级/状态映射 ====================
- const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
- const levelColorMap = { 1: '#f56c6c', 2: '#e6a23c', 3: '#409eff' }
- const auditStatusMap = { 1: '待审核', 2: '已审核' }
- const auditStatusColorMap = { 1: '#e6a23c', 2: '#67c23a' }
- function levelText(level) {
- return levelMap[level] || '-'
- }
- function levelTagType(level) {
- if (level === 1) return 'danger'
- if (level === 2) return 'warning'
- return ''
- }
- function auditStatusText(status) {
- return auditStatusMap[status] || '-'
- }
- function auditStatusTagType(status) {
- if (status === 2) return 'success'
- return 'warning'
- }
- // ==================== 搜索参数 ====================
- const queryParams = ref({
- equipmentName: '',
- warningLevel: '',
- status: ''
- })
- const queryRef = ref(null)
- function handleQuery() {
- loadData()
- }
- function resetQuery() {
- queryParams.value = { equipmentName: '', warningLevel: '', status: '' }
- loadData()
- }
- // ==================== 表格数据 ====================
- const tableData = ref([])
- const loading = ref(false)
- async function loadData() {
- loading.value = true
- try {
- // 调用 alarm_data 真实接口,只取未处理报警(alarmStatus=0)
- const res = await getAlarmDataPage(1, 200, { equipmentType: 'drainage', alarmStatus: 0 })
- const pageData = res.code !== undefined ? res.data : res
- const records = pageData.records || pageData.rows || []
- let tableData_local = records.map(row => ({
- id: row.id,
- equipmentId: row.deviceCode,
- equipmentName: row.deviceName || '-',
- warningTime: row.alarmTime,
- warningLevel: row.alarmLevel,
- content: row.warningType || '-',
- location: '-',
- longitude: row.longitude ? parseFloat(row.longitude) : null,
- latitude: row.latitude ? parseFloat(row.latitude) : null,
- currentValue: row.actualValue,
- thresholdValue: row.maxValue,
- warningType: row.warningType,
- status: 1, // 未处理(alarmStatus=0)在审核页面显示为"待审核"
- source: 'alarm_data'
- }))
- // 前端筛选(equipmentName/warningLevel/status)
- let filtered = tableData_local
- if (queryParams.value.equipmentName) {
- filtered = filtered.filter(item => item.equipmentName.includes(queryParams.value.equipmentName))
- }
- if (queryParams.value.warningLevel) {
- filtered = filtered.filter(item => item.warningLevel === queryParams.value.warningLevel)
- }
- if (queryParams.value.status !== '' && queryParams.value.status !== null) {
- filtered = filtered.filter(item => item.status === queryParams.value.status)
- }
- tableData.value = filtered
- // 如果地图已初始化,刷新标记
- if (mapInitialized) {
- refreshMapMarkers()
- }
- } catch (error) {
- console.error('加载报警数据失败', error)
- ElMessage.error('加载报警数据失败')
- } finally {
- loading.value = false
- }
- }
- // ==================== 审核弹窗 ====================
- const auditVisible = ref(false)
- const currentAlarm = ref({})
- const auditForm = ref({ result: '', opinion: '' })
- // 各类报警预定义的审核结果选项
- const auditResultOptionsMap = {
- '设备报警': [
- { label: '确认报警 - 设备异常需维修', value: '确认报警' },
- { label: '解除报警 - 设备恢复正常', value: '解除报警' },
- { label: '误报处理 - 传感器误触发', value: '误报处理' },
- { label: '降级处理 - 下调报警等级', value: '降级处理' }
- ],
- '水位报警': [
- { label: '确认报警 - 水位超标需处置', value: '确认报警' },
- { label: '解除报警 - 水位已回落', value: '解除报警' },
- { label: '误报处理 - 监测数据异常', value: '误报处理' }
- ],
- '压力报警': [
- { label: '确认报警 - 管网压力异常', value: '确认报警' },
- { label: '解除报警 - 压力恢复正常', value: '解除报警' },
- { label: '误报处理 - 压力波动属正常范围', value: '误报处理' }
- ],
- '默认': [
- { label: '确认报警', value: '确认报警' },
- { label: '解除报警', value: '解除报警' },
- { label: '误报处理', value: '误报处理' }
- ]
- }
- const auditResultOptions = computed(() => {
- const type = currentAlarm.value.warningType || '默认'
- return auditResultOptionsMap[type] || auditResultOptionsMap['默认']
- })
- const auditFiles = ref([])
- const submitting = ref(false)
- async function openAudit(row) {
- currentAlarm.value = { ...row }
- auditForm.value = { result: '', opinion: '' }
- auditFiles.value = []
- auditVisible.value = true
- }
- function closeAudit() {
- auditVisible.value = false
- }
- function handleFileChange(file) {
- auditFiles.value.push(file)
- }
- function handleFileRemove(file) {
- const idx = auditFiles.value.findIndex(f => f.uid === file.uid)
- if (idx !== -1) auditFiles.value.splice(idx, 1)
- }
- async function submitAudit() {
- if (!auditForm.value.result) {
- ElMessage.warning('请选择审核结果')
- return
- }
- if (!auditForm.value.opinion) {
- ElMessage.warning('请输入审核意见')
- return
- }
- submitting.value = true
- try {
- // 调用 handleAlarm 真实接口处理报警
- const res = await handleAlarm(currentAlarm.value.id, userStore.name || 'admin', auditForm.value.opinion || auditForm.value.result)
- if (res.code === 200 || res.code === 0) {
- ElMessage.success('审核提交成功')
- auditVisible.value = false
- loadData()
- } else {
- ElMessage.error(res.msg || '审核提交失败')
- }
- } catch (error) {
- console.error('审核提交失败', error)
- ElMessage.error('审核提交失败')
- } finally {
- submitting.value = false
- }
- }
- // ==================== 查看详情弹窗 ====================
- const viewVisible = ref(false)
- const viewRow = ref({})
- async function openView(row) {
- viewRow.value = { ...row }
- viewVisible.value = true
- }
- // ==================== 地图初始化 ====================
- function initMap() {
- const container = document.getElementById('bjshMap')
- if (!container || typeof BMapGL === 'undefined') {
- console.warn('BMapGL 未加载或容器不存在')
- return
- }
- try {
- mapInstance = new BMapGL.Map('bjshMap')
- const centerPoint = new BMapGL.Point(110.393, 28.452)
- mapInstance.centerAndZoom(centerPoint, 15)
- mapInstance.enableScrollWheelZoom(true)
- refreshMapMarkers()
- mapInitialized = true
- } catch (e) {
- console.error('地图初始化失败:', e)
- }
- }
- function refreshMapMarkers() {
- if (!mapInstance) return
- mapInstance.clearOverlays()
- let firstPoint = null
- tableData.value.forEach(point => {
- const lng = parseFloat(point.longitude)
- const lat = parseFloat(point.latitude)
- if (!lng || !lat) return
- if (!firstPoint) firstPoint = { lng, lat }
- addMapMarker(point, lng, lat)
- })
- // 默认定位到第一个报警点位
- if (firstPoint) {
- mapInstance.centerAndZoom(new BMapGL.Point(firstPoint.lng, firstPoint.lat), 15)
- }
- }
- function addMapMarker(point, lng, lat) {
- const color = levelColorMap[point.warningLevel] || '#409eff'
- const levelTextStr = levelMap[point.warningLevel] || '一般'
- const statusTextStr = auditStatusMap[point.status] || '待审核'
- const statusColor = auditStatusColorMap[point.status] || '#e6a23c'
- const bPoint = new BMapGL.Point(lng, lat)
- const html = `<div style="text-align:center;cursor:pointer;">
- <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${point.equipmentName || '-'}</div>
- <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">${levelTextStr} | ${point.warningType || '-'}:${point.currentValue || '-'}</div>
- <div><img src="${locationIcon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
- </div>`
- const label = new BMapGL.Label(html, {
- position: bPoint,
- offset: new BMapGL.Size(-30, -55)
- })
- label.setStyle({
- border: 'none',
- background: 'transparent',
- padding: '0'
- })
- mapInstance.addOverlay(label)
- // 点击标记弹出信息窗口
- label.addEventListener('click', function () {
- const infoContent = `<div style="font-size:13px;line-height:1.8;">
- <div><b>${point.equipmentName || '-'}</b></div>
- <div>报警等级:<b style="color:${color}">${levelTextStr}</b></div>
- <div>报警时间:${point.warningTime || '-'}</div>
- <div>监测值:<b style="color:${color}">${point.warningType || '-'}:${point.currentValue || '-'}</b></div>
- <div>阈值:${point.thresholdValue || '-'}</div>
- <div>审核状态:<b style="color:${statusColor}">${statusTextStr}</b></div>
- </div>`
- const infoWindow = new BMapGL.InfoWindow(infoContent, {
- width: 220,
- title: ''
- })
- mapInstance.openInfoWindow(infoWindow, bPoint)
- })
- }
- // ==================== 生命周期 ====================
- onMounted(async () => {
- await loadData()
- nextTick(() => {
- initMap()
- })
- })
- </script>
- <style scoped>
- .bjsh-page {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .map-section {
- width: 100%;
- }
- .map-container {
- width: 100%;
- height: 350px;
- border: 1px solid #e4e7ed;
- border-radius: 4px;
- }
- .table-section {
- width: 100%;
- }
- .table-section .search-form {
- margin-bottom: 12px;
- }
- /* 弹窗内部样式 */
- .section-title {
- font-size: 15px;
- font-weight: bold;
- margin-bottom: 0;
- }
- .upload-tip {
- font-size: 12px;
- color: #909399;
- margin-top: 4px;
- }
- .audit-form-section {
- margin-top: 12px;
- padding: 12px;
- background: #f8f9fb;
- border-radius: 6px;
- }
- .audit-timeline-section {
- margin-top: 16px;
- }
- .timeline-node {
- font-weight: 600;
- font-size: 14px;
- color: #303133;
- }
- .timeline-desc {
- font-size: 12px;
- color: #606266;
- margin-top: 2px;
- }
- </style>
|