<template>
  <div class="water-page alarm-page">
    <div class="page-heading"><div class="page-title"><span class="page-title-icon"><el-icon><component :is="pageIcon" /></el-icon></span><div><h2>{{ title }}</h2><span class="muted">{{ subtitle }}</span></div></div><el-button :icon="Refresh" :loading="loading" @click="loadData">刷新</el-button></div>
    <div v-if="mode === 'manage'" class="metric-grid"><div class="metric"><span>实时报警</span><strong class="danger">{{ stats.realtimeAlarmCount ?? stats.realtimeCount ?? 0 }}</strong><small>条</small></div><div class="metric"><span>今日报警</span><strong>{{ stats.todayAlarmCount ?? stats.todayCount ?? 0 }}</strong><small>条</small></div><div class="metric"><span>设备异常率</span><strong>{{ stats.deviceAbnormalRate ?? stats.abnormalRate ?? '-' }}</strong><small>%</small></div><div class="metric"><span>平均响应时间</span><strong>{{ stats.avgResponseMinutes ?? stats.averageResponseMinutes ?? '-' }}</strong><small>分钟</small></div></div>
    <div v-if="mode === 'map'" class="metric-grid"><div class="metric"><el-icon class="metric-icon"><Bell /></el-icon><span>报警总数</span><strong>{{ mapAlarmCount }}</strong><small>条</small></div><div class="metric"><el-icon class="metric-icon"><Warning /></el-icon><span>高等级报警</span><strong class="danger">{{ mapCriticalCount }}</strong><small>条</small></div><div class="metric"><el-icon class="metric-icon"><CircleCheck /></el-icon><span>已处理报警</span><strong>{{ mapResolvedCount }}</strong><small>条</small></div><div class="metric"><el-icon class="metric-icon"><Location /></el-icon><span>地图点位</span><strong>{{ mappedCount }}</strong><small>个</small></div></div>
    <el-card v-if="mode === 'manage'" shadow="never" class="realtime-card"><div class="card-title"><span>实时异常卡片</span><span class="muted">设备、位置与阈值</span></div><div v-if="!realtime.length" class="empty-inline">当前无未处理报警</div><div v-else class="realtime-grid"><button v-for="item in realtime" :key="item.id || item.alarmId" class="realtime-item" @click="openDetail(item)"><div class="realtime-head"><el-tag :type="levelType(item.alarmLevel)">{{ item.alarmLevelText || levelText(item.alarmLevel) }}</el-tag><b>{{ item.deviceName || item.deviceCode || '监测设备' }}</b><span>{{ item.alarmTime || '-' }}</span></div><div class="realtime-body"><span>{{ item.equipmentLocation || '位置暂无' }}</span><strong>{{ item.actualValue ?? '-' }}</strong><small>阈值 {{ thresholdText(item) }}</small></div></button></div></el-card>
    <el-card shadow="never" class="filter-card"><el-form :inline="true" :model="query"><el-form-item label="关键字"><el-input v-model="query.keyword" clearable placeholder="设备编码/报警类型" /></el-form-item><el-form-item label="报警等级"><el-select v-model="query.alarmLevel" clearable placeholder="全部"><el-option v-for="item in levels" :key="item.value" v-bind="item" /></el-select></el-form-item><el-form-item v-if="mode !== 'threshold'" label="状态"><el-select v-model="query.alarmStatus" clearable placeholder="全部"><el-option label="未处理" :value="0" /><el-option label="已处理" :value="1" /></el-select></el-form-item><el-form-item><el-button type="primary" @click="loadData">查询</el-button><el-button @click="resetQuery">重置</el-button></el-form-item></el-form></el-card>
    <el-card v-if="mode !== 'map'" shadow="never" class="table-card"><div class="card-title"><span>{{ mode === 'threshold' ? '阈值配置清单' : mode === 'audit' ? '待审核报警清单' : '报警历史清单' }}</span><el-button v-if="mode === 'threshold'" type="primary" size="small" @click="openThreshold">新增阈值</el-button></div><el-table :data="page.records" border stripe v-loading="loading"><el-table-column type="index" label="#" width="55"/><el-table-column prop="alarmCode" label="报警编码" min-width="145"/><el-table-column prop="deviceCode" label="设备编码" min-width="125"/><el-table-column prop="warningType" label="报警类型" min-width="130"/><el-table-column v-if="mode !== 'threshold'" prop="alarmLevel" label="报警等级" width="100"><template #default="{row}"><el-tag :type="levelType(row.alarmLevel)">{{ row.alarmLevelText || levelText(row.alarmLevel) }}</el-tag></template></el-table-column><el-table-column v-if="mode === 'threshold'" prop="warningCode" label="预警编码"/><el-table-column v-if="mode === 'threshold'" label="阈值" min-width="170"><template #default="{row}">{{ thresholdText(row) }} <span class="muted">{{ periodText(row) }}</span></template></el-table-column><el-table-column v-if="mode !== 'threshold'" prop="alarmTime" label="触发时间" min-width="165"/><el-table-column v-if="mode !== 'threshold'" prop="alarmStatus" label="状态" width="95"><template #default="{row}"><el-tag :type="Number(row.alarmStatus) === 1 ? 'success' : 'danger'">{{ Number(row.alarmStatus) === 1 ? '已处理' : '未处理' }}</el-tag></template></el-table-column><el-table-column label="操作" width="220" fixed="right"><template #default="{row}"><el-button link type="primary" @click="openDetail(row)">{{ mode === 'threshold' ? '编辑' : mode === 'audit' ? '审核' : '详情' }}</el-button><el-button v-if="mode === 'manage' && Number(row.alarmStatus) !== 1" link type="success" @click="handleResolve(row)">标记处理</el-button><el-button v-if="mode === 'manage' && Number(row.alarmStatus) !== 1" link type="warning" @click="openDispatch(row)">派单</el-button><el-button v-if="mode === 'threshold'" link type="danger" @click="handleDelete(row)">删除</el-button></template></el-table-column></el-table><el-pagination v-model:current-page="query.pageNum" v-model:page-size="query.pageSize" :total="page.total" layout="total, sizes, prev, pager, next" :page-sizes="[10,20,50]" @change="loadData"/></el-card>
    <el-card v-else shadow="never" class="water-panel map-card"><div class="card-title"><span><el-icon><MapLocation /></el-icon> 监测报警一张图</span><span class="muted">按报警等级着色</span></div><div class="map-workspace"><div class="water-map alarm-map"><div v-for="point in mapPoints" :key="point.alarmId || point.id" class="alarm-point" :class="`level-${point.alarmLevel || 1}`" :style="pointStyle(point)" @click="openDetail(point)"><span></span><b>{{ point.deviceCode || point.name || '报警点' }}</b></div><el-empty v-if="!mapPoints.length" description="暂无报警点位"/></div><aside class="map-side-panel"><h3>报警图层与图例</h3><div class="map-legend"><div class="map-legend-item"><i class="map-legend-dot level-dot-1" />一级报警</div><div class="map-legend-item"><i class="map-legend-dot level-dot-2" />二级报警</div><div class="map-legend-item"><i class="map-legend-dot level-dot-3" />三级报警</div><div class="map-legend-item"><i class="map-legend-dot level-dot-4" />四级报警</div></div><div class="alarm-detail"><h3>选中报警</h3><template v-if="selectedPoint.id || selectedPoint.alarmId"><div class="detail-name">{{ selectedPoint.deviceCode || selectedPoint.name || '报警点' }}</div><el-descriptions :column="1" border size="small"><el-descriptions-item label="报警等级"><el-tag :type="levelType(selectedPoint.alarmLevel)">{{ selectedPoint.alarmLevelText || levelText(selectedPoint.alarmLevel) }}</el-tag></el-descriptions-item><el-descriptions-item label="报警类型">{{ selectedPoint.warningType || selectedPoint.alarmType || '-' }}</el-descriptions-item><el-descriptions-item label="触发时间">{{ selectedPoint.alarmTime || selectedPoint.createTime || '-' }}</el-descriptions-item><el-descriptions-item label="状态"><el-tag :type="Number(selectedPoint.alarmStatus) === 1 ? 'success' : 'danger'">{{ Number(selectedPoint.alarmStatus) === 1 ? '已处理' : '未处理' }}</el-tag></el-descriptions-item><el-descriptions-item label="位置">{{ selectedPoint.equipmentLocation || selectedPoint.location || '-' }}</el-descriptions-item></el-descriptions><el-button class="detail-action" type="primary" link @click="openDetail(selectedPoint)">查看完整详情</el-button></template><el-empty v-else description="点击点位查看详情" :image-size="65" /></div></aside></div></el-card>

    <el-drawer v-model="detailVisible" class="detail-drawer alarm-detail-drawer" :title="mode === 'threshold' ? '阈值配置' : mode === 'audit' ? '报警审核' : '报警详情'" direction="rtl" size="720px" append-to-body>
      <template v-if="mode === 'threshold'"><el-form :model="thresholdForm" label-width="100px"><el-form-item label="设备编码"><el-input v-model="thresholdForm.deviceCode"/></el-form-item><el-form-item label="预警分类"><el-select v-model="thresholdForm.warningType" allow-create filterable><el-option v-for="item in warningTypes" :key="item" :label="item" :value="item" /></el-select></el-form-item><el-form-item label="预警编码"><el-input v-model="thresholdForm.warningCode"/></el-form-item><el-form-item label="阈值下限"><el-input-number v-model="thresholdForm.minValue" :precision="3"/></el-form-item><el-form-item label="阈值上限"><el-input-number v-model="thresholdForm.maxValue" :precision="3"/></el-form-item><el-form-item label="分时生效"><el-time-picker v-model="thresholdForm.periodStart" format="HH:mm" value-format="HH:mm" placeholder="全天"/><span class="range-sep">至</span><el-time-picker v-model="thresholdForm.periodEnd" format="HH:mm" value-format="HH:mm" placeholder="全天"/></el-form-item></el-form></template>
      <template v-else><el-descriptions :column="2" border><el-descriptions-item v-for="(value,key) in detailEntries" :key="key" :label="key">{{ value ?? '-' }}</el-descriptions-item></el-descriptions><div v-if="mode === 'audit'" class="audit-form"><el-form :model="auditForm" label-width="90px"><el-form-item label="审核动作"><el-radio-group v-model="auditForm.auditAction"><el-radio :value="1">审核</el-radio><el-radio :value="2">解除</el-radio></el-radio-group></el-form-item><el-form-item label="审核结果"><el-radio-group v-model="auditForm.auditResult"><el-radio :value="1">属实/通过</el-radio><el-radio :value="2">误报/解除</el-radio></el-radio-group></el-form-item><el-form-item label="审核意见"><el-input v-model="auditForm.auditOpinion" type="textarea" :rows="3" maxlength="500" show-word-limit/></el-form-item><el-form-item label="附件地址"><el-input v-model="auditForm.attachUrl" placeholder="可填写多个地址，以逗号分隔"/></el-form-item></el-form></div><div v-if="mode === 'audit'" class="flow-section"><div class="section-label">审核记录</div><el-timeline><el-timeline-item v-for="item in audits" :key="item.auditId || item.id" :timestamp="item.auditTime || item.createTime"><b>{{ item.auditUserName || item.auditUser || '系统用户' }}</b><span> {{ auditResultText(item.auditResult) }}：{{ item.auditOpinion || '-' }}</span></el-timeline-item><el-timeline-item v-if="!audits.length" timestamp="暂无记录">尚未提交审核</el-timeline-item></el-timeline><div class="section-label">全流程跟踪</div><el-timeline><el-timeline-item v-for="item in flow" :key="item.id || item.nodeId || item.time" :timestamp="item.time || item.createTime">{{ item.name || item.nodeName || item.description || '-' }}</el-timeline-item></el-timeline></div></template>
      <template #footer><el-button @click="detailVisible=false">取消</el-button><el-button v-if="mode === 'audit'" type="primary" @click="submitAudit">提交审核</el-button><el-button v-else-if="mode === 'threshold'" type="primary" @click="saveDetail">保存</el-button></template>
    </el-drawer>
    <el-dialog v-model="dispatchVisible" title="报警维修派单" width="520px"><el-form :model="dispatchForm" label-width="100px"><el-form-item label="工单类型"><el-select v-model="dispatchForm.orderType"><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="dispatchForm.orderLevel"><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-input v-model="dispatchForm.orderDesc" type="textarea" :rows="3"/></el-form-item><el-form-item label="计划完成时间"><el-date-picker v-model="dispatchForm.planFinishTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"/></el-form-item></el-form><template #footer><el-button @click="dispatchVisible=false">取消</el-button><el-button type="primary" @click="submitDispatch">提交派单</el-button></template></el-dialog>
  </div>
</template>

<script setup>
import { computed, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Bell, CircleCheck, Location, MapLocation, Refresh, Warning } from '@element-plus/icons-vue'
import { auditAlarm, deleteThresholds, dispatchAlarm, getAlarmDetail, getAlarmFlow, getAlarmStats, listAlarmAudits, listAlarmHistory, listAlarmMapPoints, listRealtimeAlarms, listThresholds, resolveAlarm, saveThresholds, updateThreshold } from '@/api/pipeNetwork/waterSupply'
import { normalizeMapPoints, normalizePage, unwrapResponse } from '@/utils/waterSupplyModel'
const props = defineProps({ mode: { type: String, default: 'manage' }, title: { type: String, required: true }, subtitle: { type: String, default: '供水监测报警全流程管理' } }); const query = ref({ pageNum: 1, pageSize: 10, keyword: '', alarmLevel: undefined, alarmStatus: undefined }); const page = ref({ records: [], total: 0 }); const stats = ref({}); const realtime = ref([]); const mapPoints = ref([]); const loading = ref(false); const detailVisible = ref(false); const detail = ref({}); const selectedPoint = computed(() => detail.value || {}); const audits = ref([]); const flow = ref([]); const thresholdForm = ref({}); const dispatchVisible = ref(false); const dispatchForm = ref({ orderType: 1, orderLevel: 2, orderDesc: '', planFinishTime: '' }); const auditForm = ref({ auditAction: 1, auditResult: 1, auditOpinion: '', attachUrl: '' })
const pageIcon = computed(() => props.mode === 'map' ? MapLocation : props.mode === 'threshold' ? Warning : Bell)
const levels = [{ label: '一级', value: 1 }, { label: '二级', value: 2 }, { label: '三级', value: 3 }, { label: '四级', value: 4 }]; const warningTypes = ['压力预警', '流量预警', '漏失预警', '设备离线', '水质异常']
const mapAlarmCount = computed(() => mapPoints.value.length); const mapCriticalCount = computed(() => mapPoints.value.filter(point => Number(point.alarmLevel) <= 2).length); const mapResolvedCount = computed(() => mapPoints.value.filter(point => Number(point.alarmStatus) === 1).length); const mappedCount = computed(() => mapPoints.value.filter(point => Number.isFinite(Number(point.mapX)) && Number.isFinite(Number(point.mapY))).length)
const detailEntries = computed(() => Object.fromEntries(Object.entries(detail.value || {}).filter(([key]) => !['geometry', 'attachUrl'].includes(key)).slice(0, 22))); const levelText = value => ({ 1: '一级报警', 2: '二级报警', 3: '三级报警', 4: '四级报警' }[value] || '未知等级'); const levelType = value => ({ 1: 'danger', 2: 'warning', 3: 'primary', 4: 'success' }[value] || 'info'); const thresholdText = row => row.minValue != null || row.maxValue != null ? `${row.minValue ?? '-'} ~ ${row.maxValue ?? '-'}` : (row.warningThreshold ?? row.threshold ?? '-'); const periodText = row => row.periodStart || row.periodEnd || row.period_start || row.period_end ? `(${row.periodStart || row.period_start || '--:--'}-${row.periodEnd || row.period_end || '--:--'})` : '(全天)'; const auditResultText = value => Number(value) === 2 ? '误报/已解除' : '属实/已通过'
async function loadData() { loading.value = true; try { if (props.mode === 'manage') { const [summary, alarms, history] = await Promise.all([getAlarmStats(), listRealtimeAlarms(), listAlarmHistory({ ...query.value })]); stats.value = unwrapResponse(summary) || {}; realtime.value = Array.isArray(unwrapResponse(alarms)) ? unwrapResponse(alarms) : []; page.value = normalizePage(history) } else if (props.mode === 'audit') page.value = normalizePage(await listAlarmHistory({ ...query.value, alarmStatus: 0 })); else if (props.mode === 'map') mapPoints.value = normalizeMapPoints(unwrapResponse(await listAlarmMapPoints()) || []); else page.value = normalizePage(await listThresholds({ ...query.value })) } catch (error) { page.value = { records: [], total: 0 }; realtime.value = []; mapPoints.value = []; console.warn(`[${props.title}] 数据加载失败`, error) } finally { loading.value = false } }
const resetQuery = () => { query.value = { pageNum: 1, pageSize: 10, keyword: '', alarmLevel: undefined, alarmStatus: undefined }; loadData() }; async function openDetail(row) { detail.value = row; detailVisible.value = true; if (props.mode === 'threshold') { thresholdForm.value = { ...row }; return } const id = row.id || row.alarmId; if (!id) return; try { const [detailResponse, auditResponse, flowResponse] = await Promise.all([getAlarmDetail(id), props.mode === 'audit' ? listAlarmAudits(id) : Promise.resolve(null), props.mode === 'audit' ? getAlarmFlow(id) : Promise.resolve(null)]); detail.value = unwrapResponse(detailResponse) || row; audits.value = unwrapResponse(auditResponse) || []; flow.value = unwrapResponse(flowResponse) || [] } catch (error) { detail.value = row } }
async function handleResolve(row) { try { await resolveAlarm(row.id || row.alarmId, '前端确认处理'); ElMessage.success('报警已标记处理'); loadData() } catch (error) { ElMessage.error('处理失败，请检查权限或服务状态') } }; function openDispatch(row) { dispatchForm.value = { alarmId: row.id || row.alarmId, orderType: 1, orderLevel: Number(row.alarmLevel) || 2, orderDesc: `${row.warningType || '监测'}异常，请现场核实处理`, planFinishTime: '' }; dispatchVisible.value = true }; async function submitDispatch() { try { await dispatchAlarm(dispatchForm.value); ElMessage.success('报警派单已提交'); dispatchVisible.value = false; loadData() } catch (error) { ElMessage.error('派单失败，请检查权限或服务状态') } }; async function submitAudit() { try { await auditAlarm({ alarmId: detail.value.id || detail.value.alarmId, ...auditForm.value }); ElMessage.success('审核已提交'); detailVisible.value = false; loadData() } catch (error) { ElMessage.error('审核提交失败，请检查权限或服务状态') } }; function openThreshold() { thresholdForm.value = { warningType: '压力预警', periodStart: '', periodEnd: '' }; detailVisible.value = true }; async function saveDetail() { try { if (thresholdForm.value.id) await updateThreshold(thresholdForm.value); else await saveThresholds({ deviceCodes: [thresholdForm.value.deviceCode], thresholds: [thresholdForm.value] }); ElMessage.success('阈值已保存'); detailVisible.value = false; loadData() } catch (error) { ElMessage.error('阈值保存失败') } }; async function handleDelete(row) { try { await deleteThresholds([row.id]); ElMessage.success('阈值已删除'); loadData() } catch (error) { ElMessage.error('阈值删除失败') } }; const pointStyle = point => ({ left: `${point.mapX ?? 50}%`, top: `${point.mapY ?? 50}%` }); onMounted(loadData)
</script>

<style scoped>
.water-page{padding:20px;background:#f5f7fa;min-height:calc(100vh - 84px)}.page-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.page-heading h2{margin:0 0 5px;font-size:20px}.muted{font-size:13px;color:#909399}.filter-card,.realtime-card{margin-bottom:16px}.filter-card :deep(.el-form-item){margin-bottom:0}.metric-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:16px}.metric{background:#fff;padding:15px 18px;border:1px solid #ebeef5}.metric span{display:block;color:#909399;font-size:13px}.metric strong{font-size:24px;line-height:34px}.metric small{margin-left:5px;color:#909399}.danger{color:#e45656}.card-title{display:flex;justify-content:space-between;align-items:center;font-weight:600;margin-bottom:14px}.realtime-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.realtime-item{border:1px solid #edf0f5;background:#fff;text-align:left;padding:12px;cursor:pointer}.realtime-item:hover{border-color:#8ab8e8;background:#f8fbff}.realtime-head{display:flex;align-items:center;gap:7px}.realtime-head b{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#303133;font-size:13px}.realtime-head span{color:#909399;font-size:11px}.realtime-body{display:grid;grid-template-columns:1.3fr .7fr;gap:4px;margin-top:10px;color:#606266;font-size:12px}.realtime-body strong{font-size:20px;color:#303133;text-align:right}.realtime-body small{grid-column:1/-1;color:#909399}.empty-inline{color:#909399;font-size:13px;padding:10px 0}.table-card :deep(.el-pagination){justify-content:flex-end;margin-top:16px}.range-sep{margin:0 8px;color:#909399}.map-card{min-height:560px}.alarm-map{position:relative;height:500px;background:linear-gradient(135deg,#edf5ff,#f8fbff);border:1px solid #dfe9f7;overflow:hidden}.alarm-point{position:absolute;transform:translate(-50%,-50%);cursor:pointer;text-align:center}.alarm-point span{display:block;width:18px;height:18px;border-radius:50%;border:3px solid #fff;box-shadow:0 0 0 5px currentColor;background:currentColor}.alarm-point b{font-size:11px;color:#334155;white-space:nowrap}.level-1{color:#f56c6c}.level-2{color:#e6a23c}.level-3{color:#409eff}.level-4{color:#67c23a}.audit-form,.flow-section{border-top:1px solid #ebeef5;margin-top:18px;padding-top:15px}.section-label{font-weight:600;color:#303133;margin:8px 0 12px}.flow-section :deep(.el-timeline){padding-left:5px}@media(max-width:1000px){.realtime-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.metric-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:650px){.realtime-grid{grid-template-columns:1fr}.page-heading{align-items:flex-start;gap:12px;flex-direction:column}}
</style>
<style lang="scss">
@use './waterSupplyPageTheme.scss';
.alarm-page .map-workspace .alarm-map { height: auto; min-height: 560px; border: 0; background-color: #f8fbff; background-image: linear-gradient(#eaf0f6 1px, transparent 1px), linear-gradient(90deg, #eaf0f6 1px, transparent 1px); background-size: 40px 40px; }
.alarm-page .alarm-map { min-height: 560px; height: auto; border: 0; background-color: #f8fbff; background-image: linear-gradient(#eaf0f6 1px, transparent 1px), linear-gradient(90deg, #eaf0f6 1px, transparent 1px); background-size: 40px 40px; }
.alarm-page .level-dot-1 { background: #f56c6c; }
.alarm-page .level-dot-2 { background: #e6a23c; }
.alarm-page .level-dot-3 { background: #409eff; }
.alarm-page .level-dot-4 { background: #67c23a; }
.alarm-page .alarm-detail { margin-top: 24px; padding-top: 16px; border-top: 1px solid #e1ebf3; }
.alarm-page .detail-name { margin-bottom: 10px; color: #17324d; font-weight: 700; }
.alarm-page .detail-action { margin-top: 10px; }
.alarm-detail-drawer :deep(.el-drawer__body){overflow:auto}.alarm-detail-drawer :deep(.el-descriptions__label){white-space:nowrap}.alarm-detail-drawer :deep(.el-descriptions__content){word-break:break-word;overflow-wrap:anywhere}
@media(max-width:768px){.alarm-detail-drawer{width:100%!important}}
</style>
