| 1234567891011121314151617181920212223242526 |
- <template>
- <div class="statistics-page"><section class="page-head"><div><span class="eyebrow">RESPONSE PERFORMANCE</span><h1>接警效率</h1><p>按预警类型、处置状态和时间分析接警响应效率。</p></div><el-button type="primary" :loading="loading" @click="loadData"><el-icon><Refresh /></el-icon>刷新统计</el-button></section>
- <el-card class="filter-card" shadow="never"><el-date-picker v-model="dateRange" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" /><el-select v-model="warningType" clearable placeholder="预警类型" class="filter-select"><el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select><el-select v-model="status" clearable placeholder="处置状态" class="filter-select"><el-option v-for="(label,value) in statusLabels" :key="value" :label="label" :value="value" /></el-select><el-input v-model="warningSpecial" clearable placeholder="输入预警专项" class="special-input"/><el-button type="primary" @click="loadData">查询</el-button></el-card>
- <el-alert v-if="errorMessage" :title="errorMessage" type="error" show-icon closable @close="errorMessage = ''" />
- <div class="metric-grid"><div v-for="metric in metrics" :key="metric.label" class="metric-card"><span>{{ metric.label }}</span><strong>{{ metric.value }}</strong><small>{{ metric.note }}</small></div></div>
- <div class="chart-grid"><el-card shadow="never"><template #header><div class="card-title"><span>接警时效区间</span><em>从发布时间至首次接收</em></div></template><div ref="rangeRef" class="chart-box" /></el-card><el-card shadow="never"><template #header><div class="card-title"><span>按类型平均接警时效</span><em>小时</em></div></template><div ref="typeRef" class="chart-box" /></el-card></div>
- <el-card shadow="never"><template #header><div class="card-title"><span>接警效率明细</span><em>{{ total }} 条</em></div></template><el-table v-loading="loading" :data="rows" stripe><el-table-column prop="warningNo" label="预警单号" min-width="150"/><el-table-column label="类型" min-width="120"><template #default="{row}">{{ typeLabel(row.warning_type||row.warningType) }}</template></el-table-column><el-table-column label="首次接警" min-width="160"><template #default="{row}">{{ row.receive_time||row.receiveTime||'-' }}</template></el-table-column><el-table-column label="接警时长" width="120"><template #default="{row}"><b>{{ row.receive_hours??row.receiveHours??'-' }}</b> 小时</template></el-table-column><el-table-column label="状态" width="100"><template #default="{row}"><WarningStatusTag :status="row.status">{{ statusLabels[row.status] || row.status }}</WarningStatusTag></template></el-table-column></el-table><pagination v-show="total > 0" v-model:page="query.pageNum" v-model:limit="query.pageSize" :total="total" @pagination="loadPage" /></el-card>
- </div>
- </template>
- <script setup name="WarningReceiveEfficiency">
- import WarningStatusTag from "../../components/WarningStatusTag.vue"
- import { loadWarningReferences } from '../statisticsShared'
- import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
- import { ElMessage } from 'element-plus'
- import { Refresh } from '@element-plus/icons-vue'
- import * as echarts from 'echarts'
- import { getReceiveEfficiencyOverview, getReceiveEfficiencyRange, getReceiveEfficiencyType, pageReceiveEfficiency } from '@/api/warning/statistics'
- import { defaultRange, labelOf, rangeParams, responseData, rowsOf, safeNumber, statusLabels, totalOf, warningTypeLabel, warningTypeOptions, warningTypeLabels } from '../statisticsShared'
- const dateRange=ref(defaultRange());const warningType=ref('');const warningSpecial=ref('');const status=ref('');const loading=ref(false);const errorMessage=ref('');const overview=ref({});const rangeRows=ref([]);const typeRows=ref([]);const rows=ref([]);const total=ref(0);const query=ref({pageNum:1,pageSize:10});const rangeRef=ref();const typeRef=ref();let rangeChart;let typeChart
- const typeOptions=warningTypeOptions;const typeLabel=value=>labelOf(warningTypeLabels,value);const read=(key, fallback='-')=>overview.value[key]??overview.value[key.replace(/_([a-z])/g,(_,c)=>c.toUpperCase())]??fallback;const metrics=computed(()=>[{label:'平均接警时长',value:safeNumber(read('avg_hours'),2),note:'小时'},{label:'最长接警时长',value:safeNumber(read('max_hours'),2),note:'小时'},{label:'最短接警时长',value:safeNumber(read('min_hours'),2),note:'小时'},{label:'有效接警数',value:read('total_count',0),note:'已接收预警'}])
- function render(){if(!rangeRef.value||!typeRef.value)return;rangeChart ||= echarts.init(rangeRef.value);typeChart ||= echarts.init(typeRef.value);rangeChart.setOption({tooltip:{trigger:'axis'},grid:{left:46,right:18,top:22,bottom:38},xAxis:{type:'category',data:rangeRows.value.map(r=>r.time_range||r.timeRange)},yAxis:{type:'value',minInterval:1},series:[{type:'bar',barWidth:32,data:rangeRows.value.map(r=>Number(r.warning_count??r.warningCount??0)),itemStyle:{color:'#2563eb',borderRadius:[6,6,0,0]}}]});typeChart.setOption({tooltip:{trigger:'axis'},grid:{left:55,right:18,top:22,bottom:54},xAxis:{type:'category',data:typeRows.value.map(r=>typeLabel(r.warning_type||r.warningType)),axisLabel:{rotate:25}},yAxis:{type:'value',name:'小时'},series:[{type:'bar',barWidth:28,data:typeRows.value.map(r=>Number(r.avg_receive_hours??r.avgReceiveHours??0)),itemStyle:{color:'#dc2626',borderRadius:[6,6,0,0]}}]})}
- async function loadData(){loading.value=true;errorMessage.value='';try{ await loadWarningReferences();const params={...rangeParams(dateRange.value),warningType:warningType.value||undefined,warningSpecial:warningSpecial.value||undefined,status:status.value||undefined};const [a,b,c]=await Promise.all([getReceiveEfficiencyOverview(params),getReceiveEfficiencyRange(params),getReceiveEfficiencyType(params)]);overview.value=responseData(a,{});rangeRows.value=responseData(b,[]);typeRows.value=responseData(c,[]);await loadPage();await nextTick();render()}catch(error){errorMessage.value=error?.message||'接警效率加载失败';ElMessage.error(errorMessage.value)}finally{loading.value=false}}
- async function loadPage(){const res=await pageReceiveEfficiency({...rangeParams(dateRange.value),warningType:warningType.value||undefined,warningSpecial:warningSpecial.value||undefined,status:status.value||undefined,sortType:'desc',...query.value});rows.value=rowsOf(res).map(row=>({...row,warningNo:row.warningNo??row.warning_no}));total.value=totalOf(res)}
- function resize(){rangeChart?.resize();typeChart?.resize()};onMounted(()=>{loadData();window.addEventListener('resize',resize)});onUnmounted(()=>{window.removeEventListener('resize',resize);rangeChart?.dispose();typeChart?.dispose()})
- </script>
- <style scoped lang="scss">.statistics-page{padding:22px;background:#f4f7fb;min-height:calc(100vh - 84px);color:#172b4d}.page-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:18px}.eyebrow{font-size:11px;letter-spacing:2px;color:#dc2626}.page-head h1{margin:6px 0 4px;font-size:26px}.page-head p{margin:0;color:#718096}.filter-card{display:flex;gap:12px;align-items:center;margin-bottom:16px}.filter-select{width:150px}.special-input{width:180px}.metric-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:16px}.metric-card{padding:18px 20px;background:#fff;border:1px solid #e6ebf2;border-radius:8px;display:flex;flex-direction:column;gap:8px}.metric-card span{font-size:13px;color:#718096}.metric-card strong{font-size:26px;color:#172b4d}.metric-card small{color:#9aa7b8}.chart-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}.card-title{display:flex;justify-content:space-between;font-weight:600}.card-title em{font-style:normal;font-size:12px;color:#94a3b8;font-weight:400}.chart-box{height:290px}@media(max-width:1000px){.filter-card{flex-wrap:wrap}.metric-grid,.chart-grid{grid-template-columns:1fr 1fr}}@media(max-width:600px){.metric-grid,.chart-grid{grid-template-columns:1fr}.filter-card>*{width:100%!important}.page-head{align-items:flex-start;gap:12px;flex-direction:column}}</style>
|