| 1234567891011121314151617181920212223242526 |
- <template>
- <div class="statistics-page">
- <section class="page-head"><div><span class="eyebrow">WARNING OPERATIONS</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-input v-model="warningSpecial" clearable placeholder="输入预警专项" class="special-input" @keyup.enter="loadData" /><el-button type="primary" @click="loadData">查询</el-button><el-button @click="reset">重置</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="statusChartRef" class="chart-box" /></el-card><el-card shadow="never"><template #header><div class="card-title"><span>处置动作分布</span><em>按动作类型</em></div></template><div ref="typeChartRef" class="chart-box" /></el-card></div>
- </div>
- </template>
- <script setup name="WarningDisposeStatus">
- 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 { getDisposalTypeStat, getDisposeStatusStat } from '@/api/warning/statistics'
- import { defaultRange, disposalTypeLabels, labelOf, rangeParams, responseData, statusLabels } from '../statisticsShared'
- const dateRange = ref(defaultRange()); const warningSpecial = ref(''); const loading = ref(false); const errorMessage = ref(''); const statusRows = ref([]); const typeRows = ref([]); const statusChartRef = ref(); const typeChartRef = ref(); let statusChart; let typeChart
- const count = rows => rows.reduce((total, row) => total + Number(row.status_count ?? row.statusCount ?? row.disposal_count ?? row.disposalCount ?? 0), 0)
- const metrics = computed(() => [{ label: '统计预警数', value: count(statusRows.value), note: '当前筛选周期' }, { label: '状态类型', value: statusRows.value.length, note: '非草稿状态' }, { label: '处置动作数', value: count(typeRows.value), note: '已记录动作' }, { label: '主要状态', value: statusRows.value[0] ? labelOf(statusLabels, statusRows.value[0].status) : '-', note: '数量最多' }])
- function renderCharts() { if (!statusChartRef.value || !typeChartRef.value) return; statusChart ||= echarts.init(statusChartRef.value); typeChart ||= echarts.init(typeChartRef.value); statusChart.setOption({ color: ['#2563eb', '#0f766e', '#f59e0b', '#ef4444'], tooltip: { trigger: 'item' }, series: [{ type: 'pie', radius: ['45%', '72%'], label: { formatter: '{b}\n{c} 条' }, data: statusRows.value.map(row => ({ name: labelOf(statusLabels, row.status), value: Number(row.status_count ?? row.statusCount ?? 0) })) }] }); typeChart.setOption({ color: ['#2563eb', '#0f766e', '#f59e0b', '#8b5cf6'], tooltip: { trigger: 'axis' }, grid: { left: 44, right: 24, top: 24, bottom: 36 }, xAxis: { type: 'category', data: typeRows.value.map(row => labelOf(disposalTypeLabels, row.disposal_type ?? row.disposalType)), axisLabel: { interval: 0 } }, yAxis: { type: 'value', minInterval: 1 }, series: [{ type: 'bar', barWidth: 32, data: typeRows.value.map(row => Number(row.disposal_count ?? row.disposalCount ?? 0)), itemStyle: { borderRadius: [6, 6, 0, 0] } }] }) }
- async function loadData() { loading.value = true; errorMessage.value = ''; try { const params = { ...rangeParams(dateRange.value), warningSpecial: warningSpecial.value || undefined }; const [statusResponse, typeResponse] = await Promise.all([getDisposeStatusStat(params), getDisposalTypeStat(params)]); statusRows.value = responseData(statusResponse, []); typeRows.value = responseData(typeResponse, []); await nextTick(); renderCharts() } catch (error) { errorMessage.value = error?.message || '统计数据加载失败'; ElMessage.error(errorMessage.value) } finally { loading.value = false } }
- function reset() { dateRange.value = defaultRange(); warningSpecial.value = ''; loadData() }
- function resize() { statusChart?.resize(); typeChart?.resize() }
- onMounted(() => { loadData(); window.addEventListener('resize', resize) }); onUnmounted(() => { window.removeEventListener('resize', resize); statusChart?.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:#2563eb}.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}.special-input{width:220px}.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:28px;color:#172b4d}.metric-card small{color:#9aa7b8}.chart-grid{display:grid;grid-template-columns:1fr 1fr;gap: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:330px}@media(max-width:900px){.metric-grid,.chart-grid{grid-template-columns:1fr 1fr}.filter-card{flex-wrap:wrap}}@media(max-width:600px){.metric-grid,.chart-grid{grid-template-columns:1fr}.page-head{align-items:flex-start;gap:12px;flex-direction:column}.filter-card>*{width:100%!important}}</style>
|