|
|
@@ -7,27 +7,31 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <div v-if="notice" class="inline-toast">
|
|
|
+ <el-icon><CircleCheck /></el-icon>{{ notice }}
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 汇聚指标卡片 -->
|
|
|
- <section class="ingest-metrics">
|
|
|
+ <section class="ingest-metrics" v-loading="metricsLoading">
|
|
|
<article>
|
|
|
<span>已接入县区节点</span>
|
|
|
- <strong>14 <small>/ 14</small></strong>
|
|
|
+ <strong>{{ metrics?.connectedNodes ?? '-' }} <small>/ {{ metrics?.totalNodes ?? '-' }}</small></strong>
|
|
|
<p><i></i>上报链路全部在线</p>
|
|
|
</article>
|
|
|
<article>
|
|
|
<span>近 1 分钟接收记录</span>
|
|
|
- <strong>1,286 <small>条</small></strong>
|
|
|
+ <strong>{{ metrics?.recentRecords?.split(' ')[0] ?? '-' }} <small>{{ metrics?.recentRecords?.split(' ')[1] ?? '' }}</small></strong>
|
|
|
<p><i></i>峰值 2,400 条 / 分</p>
|
|
|
</article>
|
|
|
<article>
|
|
|
<span>核心库累计数据</span>
|
|
|
- <strong>8,642 <small>万条</small></strong>
|
|
|
+ <strong>{{ metrics?.totalData?.split(' ')[0] ?? '-' }} <small>{{ metrics?.totalData?.split(' ')[1] ?? '' }}</small></strong>
|
|
|
<p><i></i>冷热分层存储正常</p>
|
|
|
</article>
|
|
|
<article class="attention">
|
|
|
<span>待核验数据</span>
|
|
|
- <strong>37 <small>项</small></strong>
|
|
|
- <p><i></i>GPS 时效与设备字段</p>
|
|
|
+ <strong>{{ metrics?.pendingReview ?? '-' }} <small>项</small></strong>
|
|
|
+ <p><i></i>{{ metrics?.pendingNote ?? 'GPS 时效与设备字段' }}</p>
|
|
|
</article>
|
|
|
</section>
|
|
|
|
|
|
@@ -61,10 +65,10 @@
|
|
|
<span>按统一数据模型沉淀县区上传记录,支持全字段追溯与质量审计</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="domain-grid">
|
|
|
+ <div class="domain-grid" v-loading="domainsLoading">
|
|
|
<article
|
|
|
v-for="(domain, index) in dataDomains"
|
|
|
- :key="domain.name"
|
|
|
+ :key="domain.id"
|
|
|
class="domain-item"
|
|
|
:class="{ 'domain-item--attention': domain.status === 'attention' }"
|
|
|
>
|
|
|
@@ -103,14 +107,14 @@
|
|
|
</el-button>
|
|
|
</header>
|
|
|
<div class="table-scroll county-upload-table-scroll">
|
|
|
- <el-table :data="countyUploads" class="data-table county-upload-table" table-layout="fixed" height="304">
|
|
|
+ <el-table :data="countyUploads" class="data-table county-upload-table" table-layout="fixed" height="304" v-loading="countyLoading">
|
|
|
<el-table-column type="index" label="序号" align="center" />
|
|
|
<el-table-column label="县区">
|
|
|
<template #default="{ row }"><strong>{{ row.district }}</strong></template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="source" label="上报节点" />
|
|
|
- <el-table-column prop="volume" label="近 5 分钟接收" />
|
|
|
- <el-table-column prop="latest" label="最近入库" />
|
|
|
+ <el-table-column prop="volume" label="近 5 分钟接收" class-name="mono" />
|
|
|
+ <el-table-column prop="latest" label="最近入库" class-name="mono" />
|
|
|
<el-table-column label="已覆盖数据">
|
|
|
<template #default="{ row }"><span>{{ row.coverage }}</span></template>
|
|
|
</el-table-column>
|
|
|
@@ -130,7 +134,7 @@
|
|
|
<span>接口与存储状态</span>
|
|
|
</div>
|
|
|
</header>
|
|
|
- <div class="realtime-ingest-list">
|
|
|
+ <div class="realtime-ingest-list" v-loading="realtimeLoading">
|
|
|
<article v-for="item in realtimeIngestMetrics" :key="item.label">
|
|
|
<span class="realtime-ingest-icon" aria-hidden="true">
|
|
|
<el-icon :size="18"><component :is="iconMap[item.icon]" /></el-icon>
|
|
|
@@ -157,15 +161,15 @@
|
|
|
<span class="live-indicator"><i></i>实时接收</span>
|
|
|
</header>
|
|
|
<div class="table-scroll">
|
|
|
- <el-table :data="paginatedRecentData" class="data-table" table-layout="fixed">
|
|
|
+ <el-table :data="recentData" class="data-table" table-layout="fixed" v-loading="recentLoading">
|
|
|
<el-table-column type="index" label="序号" width="72" align="center" />
|
|
|
<el-table-column label="数据类型" min-width="130">
|
|
|
<template #default="{ row }"><strong>{{ row.type }}</strong></template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="source" label="上传来源" min-width="180" />
|
|
|
- <el-table-column prop="id" label="记录标识" min-width="180" />
|
|
|
+ <el-table-column prop="recordId" label="记录标识" min-width="180" class-name="mono" />
|
|
|
<el-table-column prop="fields" label="关键字段" min-width="260" />
|
|
|
- <el-table-column prop="receivedAt" label="接收时间" min-width="120" />
|
|
|
+ <el-table-column prop="receivedAt" label="接收时间" min-width="120" class-name="mono" />
|
|
|
<el-table-column label="存储结果" min-width="110">
|
|
|
<template #default="{ row }">
|
|
|
<el-tag :type="row.result === '已入库' ? 'success' : 'warning'" size="small">{{ row.result }}</el-tag>
|
|
|
@@ -178,8 +182,10 @@
|
|
|
v-model:current-page="recentTablePage"
|
|
|
v-model:page-size="recentTablePageSize"
|
|
|
:page-sizes="[5, 10, 20, 50]"
|
|
|
- :total="recentData.length"
|
|
|
+ :total="recentTotal"
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @current-change="loadRecentData"
|
|
|
+ @size-change="loadRecentData"
|
|
|
/>
|
|
|
</div>
|
|
|
</section>
|
|
|
@@ -187,7 +193,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed } from 'vue'
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
import {
|
|
|
Connection,
|
|
|
Monitor,
|
|
|
@@ -207,18 +213,56 @@ import {
|
|
|
MapLocation
|
|
|
} from '@element-plus/icons-vue'
|
|
|
import {
|
|
|
- dataDomains,
|
|
|
- ingestSteps,
|
|
|
- countyUploads,
|
|
|
- realtimeIngestMetrics,
|
|
|
- recentData
|
|
|
-} from '@/data/emergency'
|
|
|
+ getDataHubMetrics,
|
|
|
+ getDataDomains,
|
|
|
+ getCountyUploads,
|
|
|
+ getRealtimeMetrics,
|
|
|
+ getRecentData,
|
|
|
+ type DataHubMetrics,
|
|
|
+ type DataDomain,
|
|
|
+ type CountyUpload,
|
|
|
+ type RealtimeMetric,
|
|
|
+ type RecentData
|
|
|
+} from '@/api/emergency/data-hub'
|
|
|
|
|
|
const notice = ref('')
|
|
|
|
|
|
+// 汇聚指标
|
|
|
+const metrics = ref<DataHubMetrics | null>(null)
|
|
|
+const metricsLoading = ref(false)
|
|
|
+
|
|
|
+// 核心数据域
|
|
|
+const dataDomains = ref<DataDomain[]>([])
|
|
|
+const domainsLoading = ref(false)
|
|
|
+
|
|
|
+// 县区上传状态
|
|
|
+const countyUploads = ref<CountyUpload[]>([])
|
|
|
+const countyLoading = ref(false)
|
|
|
+
|
|
|
+// 实时监测指标
|
|
|
+const realtimeIngestMetrics = ref<RealtimeMetric[]>([])
|
|
|
+const realtimeLoading = ref(false)
|
|
|
+
|
|
|
+// 最近接收数据(服务端分页)
|
|
|
+const recentData = ref<RecentData[]>([])
|
|
|
+const recentLoading = ref(false)
|
|
|
+const recentTablePage = ref(1)
|
|
|
+const recentTablePageSize = ref(5)
|
|
|
+const recentTotal = ref(0)
|
|
|
+
|
|
|
// 链路步骤图标
|
|
|
const ingestStepIcons = [Upload, Lock, Switch, CircleCheck, Coin, Box]
|
|
|
|
|
|
+// 链路步骤(固定流程说明)
|
|
|
+const ingestSteps = [
|
|
|
+ ['县区上传', '14 个节点并发上报'],
|
|
|
+ ['安全接入', '身份校验与签名验真'],
|
|
|
+ ['标准映射', '字段、编码与主数据匹配'],
|
|
|
+ ['质量核验', '完整性、时效性、一致性'],
|
|
|
+ ['核心存储', '主题库与轨迹时序库'],
|
|
|
+ ['业务服务', '概览、资源与任务查询'],
|
|
|
+]
|
|
|
+
|
|
|
// 数据域图标
|
|
|
const domainIcons = [OfficeBuilding, User, Van, Timer, FirstAidKit, MapLocation]
|
|
|
|
|
|
@@ -230,18 +274,82 @@ const iconMap: Record<string, any> = {
|
|
|
'file-check': Document,
|
|
|
}
|
|
|
|
|
|
-// 最近接收分页
|
|
|
-const recentTablePage = ref(1)
|
|
|
-const recentTablePageSize = ref(5)
|
|
|
-const paginatedRecentData = computed(() => {
|
|
|
- const start = (recentTablePage.value - 1) * recentTablePageSize.value
|
|
|
- return recentData.slice(start, start + recentTablePageSize.value)
|
|
|
-})
|
|
|
+async function loadMetrics() {
|
|
|
+ metricsLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await getDataHubMetrics()
|
|
|
+ metrics.value = res.data
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载汇聚指标失败', e)
|
|
|
+ } finally {
|
|
|
+ metricsLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadDomains() {
|
|
|
+ domainsLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await getDataDomains()
|
|
|
+ dataDomains.value = res.data
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载核心数据域失败', e)
|
|
|
+ } finally {
|
|
|
+ domainsLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadCountyUploads() {
|
|
|
+ countyLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await getCountyUploads()
|
|
|
+ countyUploads.value = res.data
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载县区上传状态失败', e)
|
|
|
+ } finally {
|
|
|
+ countyLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadRealtimeMetrics() {
|
|
|
+ realtimeLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await getRealtimeMetrics()
|
|
|
+ realtimeIngestMetrics.value = res.data
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载实时监测指标失败', e)
|
|
|
+ } finally {
|
|
|
+ realtimeLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadRecentData() {
|
|
|
+ recentLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await getRecentData({
|
|
|
+ pageNum: recentTablePage.value,
|
|
|
+ pageSize: recentTablePageSize.value
|
|
|
+ })
|
|
|
+ recentData.value = res.rows
|
|
|
+ recentTotal.value = res.total
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载最近接收数据失败', e)
|
|
|
+ } finally {
|
|
|
+ recentLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
function showNotice(message: string) {
|
|
|
notice.value = message
|
|
|
setTimeout(() => { notice.value = '' }, 2200)
|
|
|
}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadMetrics()
|
|
|
+ loadDomains()
|
|
|
+ loadCountyUploads()
|
|
|
+ loadRealtimeMetrics()
|
|
|
+ loadRecentData()
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
@@ -262,6 +370,19 @@ function showNotice(message: string) {
|
|
|
.subtitle { margin: 0; font-size: 13px; color: #909399; }
|
|
|
}
|
|
|
|
|
|
+.inline-toast {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding: 10px 16px;
|
|
|
+ background: #f0f9eb;
|
|
|
+ border: 1px solid #e1f3d8;
|
|
|
+ border-radius: 6px;
|
|
|
+ color: #67c23a;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
/* 汇聚指标 */
|
|
|
.ingest-metrics {
|
|
|
display: grid;
|
|
|
@@ -639,4 +760,8 @@ function showNotice(message: string) {
|
|
|
0%, 100% { opacity: 1; }
|
|
|
50% { opacity: 0.3; }
|
|
|
}
|
|
|
+
|
|
|
+:deep(.mono .cell) {
|
|
|
+ font-family: Bahnschrift, 'DIN Alternate', 'Courier New', monospace;
|
|
|
+}
|
|
|
</style>
|