fix: 战略地图P0修复 — KPI code统一+放开同层连线+数据迁移
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
||||
<h2 style="margin:0;">数据质量监控</h2>
|
||||
<div>
|
||||
<el-button type="primary" @click="runCheck" :loading="checking">执行全量检查</el-button>
|
||||
<el-button @click="loadLogs">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px;">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:28px;font-weight:600;color:#409eff;">{{ stats.total_kpis }}</div>
|
||||
<div style="font-size:13px;color:#999;">KPI总数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:28px;font-weight:600;color:#e6a23c;">{{ stats.open_logs }}</div>
|
||||
<div style="font-size:13px;color:#999;">未解决异常</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:28px;font-weight:600;color:#f56c6c;">{{ stats.severity_counts?.critical ?? 0 }}</div>
|
||||
<div style="font-size:13px;color:#999;">严重异常</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:28px;font-weight:600;color:#67c23a;">{{ stats.type_counts?.missing_data ?? 0 }}</div>
|
||||
<div style="font-size:13px;color:#999;">数据缺失</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 异常类型分布 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px;">
|
||||
<el-col :span="24">
|
||||
<el-card shadow="never">
|
||||
<template #header>异常类型分布</template>
|
||||
<v-chart :option="typeChartOption" autoresize style="height:200px;" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 质量日志列表 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<span>质量异常日志</span>
|
||||
<div>
|
||||
<el-select v-model="filterSeverity" placeholder="严重程度" clearable size="small" style="width:140px;margin-right:8px;" @change="loadLogs">
|
||||
<el-option label="严重" value="critical" />
|
||||
<el-option label="警告" value="warning" />
|
||||
<el-option label="一般" value="info" />
|
||||
</el-select>
|
||||
<el-select v-model="filterStatus" placeholder="状态" clearable size="small" style="width:120px;" @change="loadLogs">
|
||||
<el-option label="未处理" value="open" />
|
||||
<el-option label="已处理" value="resolved" />
|
||||
<el-option label="已忽略" value="ignored" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="logs" size="small" border v-loading="loading">
|
||||
<el-table-column prop="kpi_name" label="KPI" min-width="150" />
|
||||
<el-table-column prop="kpi_code" label="编码" width="100" />
|
||||
<el-table-column prop="check_type" label="异常类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.check_type === 'missing_data' ? 'danger' : row.check_type === 'abnormal_change' ? 'warning' : 'info'" size="small">
|
||||
{{ row.check_type === 'missing_data' ? '数据缺失' : row.check_type === 'abnormal_change' ? '环比骤变' : row.check_type === 'flat_data' ? '连续持平' : '值异常' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="severity" label="严重度" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.severity === 'critical' ? 'danger' : row.severity === 'warning' ? 'warning' : 'info'" size="small">{{ row.severity }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="detail" label="详情" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.check_type === 'missing_data'">缺失{{ row.detail?.missing_months }}个月数据,最新: {{ row.detail?.latest_period ?? '无' }}</div>
|
||||
<div v-else-if="row.check_type === 'abnormal_change'">变化率 {{ row.detail?.change_pct }}%,当前: {{ row.detail?.current_value }},上月: {{ row.detail?.previous_value }}</div>
|
||||
<div v-else-if="row.check_type === 'flat_data'">连续3期持平: {{ row.detail?.flat_value }}</div>
|
||||
<div v-else-if="row.check_type === 'value_outlier'">Z-Score: {{ row.detail?.z_score }},均值: {{ row.detail?.mean }}</div>
|
||||
<div v-else>{{ JSON.stringify(row.detail) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="suggestion" label="建议操作" min-width="200" />
|
||||
<el-table-column prop="status" label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'open' ? 'danger' : row.status === 'resolved' ? 'success' : 'info'" size="small">
|
||||
{{ row.status === 'open' ? '未处理' : row.status === 'resolved' ? '已解决' : '已忽略' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="发现时间" width="160" />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status === 'open'" size="small" type="success" plain @click="updateStatus(row.id, 'resolved')">已解决</el-button>
|
||||
<el-button v-if="row.status === 'open'" size="small" @click="updateStatus(row.id, 'ignored')">忽略</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import VChart from 'vue-echarts'
|
||||
import 'echarts'
|
||||
import { dataQualityApi } from '../api/index'
|
||||
|
||||
const stats = ref<any>({})
|
||||
const logs = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const checking = ref(false)
|
||||
const filterSeverity = ref('')
|
||||
const filterStatus = ref('')
|
||||
|
||||
const typeChartOption = computed(() => ({
|
||||
tooltip: { trigger: 'item' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: [
|
||||
{ name: '数据缺失', value: stats.value.type_counts?.missing_data || 0, itemStyle: { color: '#f56c6c' } },
|
||||
{ name: '环比骤变', value: stats.value.type_counts?.abnormal_change || 0, itemStyle: { color: '#e6a23c' } },
|
||||
{ name: '连续持平', value: stats.value.type_counts?.flat_data || 0, itemStyle: { color: '#409eff' } },
|
||||
{ name: '值异常', value: stats.value.type_counts?.value_outlier || 0, itemStyle: { color: '#909399' } },
|
||||
].filter(d => d.value > 0),
|
||||
label: { show: true, formatter: '{b}: {c}' },
|
||||
}],
|
||||
}))
|
||||
|
||||
async function loadStats() {
|
||||
try { const r: any = await dataQualityApi.stats(); stats.value = r } catch (e) {}
|
||||
}
|
||||
|
||||
async function loadLogs() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {}
|
||||
if (filterSeverity.value) params.severity = filterSeverity.value
|
||||
if (filterStatus.value) params.status = filterStatus.value
|
||||
const r: any = await dataQualityApi.logs(params)
|
||||
logs.value = r.data || []
|
||||
} catch (e) { ElMessage.error('加载日志失败') }
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function runCheck() {
|
||||
checking.value = true
|
||||
try {
|
||||
const r: any = await dataQualityApi.check()
|
||||
ElMessage.success(`检查完成: ${r.issues_found}个问题, 新增${r.new_logs}条记录`)
|
||||
await loadStats()
|
||||
await loadLogs()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.detail || '检查失败')
|
||||
}
|
||||
checking.value = false
|
||||
}
|
||||
|
||||
async function updateStatus(id: number, status: string) {
|
||||
try {
|
||||
await dataQualityApi.updateLog(id, { status })
|
||||
ElMessage.success('更新成功')
|
||||
await loadLogs()
|
||||
await loadStats()
|
||||
} catch (e) { ElMessage.error('更新失败') }
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadStats()
|
||||
loadLogs()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user