feat: 数据治理 — 入库约束+元数据卡片+编码清洗+审计看板
This commit is contained in:
@@ -44,6 +44,73 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据审计看板 -->
|
||||
<el-row :gutter="16" class="section-gap">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never">
|
||||
<template #header>📊 KPI完整度评分</template>
|
||||
<div style="text-align:center;padding:12px 0;">
|
||||
<div :style="{ fontSize: '36px', fontWeight: 700, color: completenessColor }">
|
||||
{{ stats.completeness?.score ?? '-' }}%
|
||||
</div>
|
||||
<div style="font-size:13px;color:#999;margin-top:4px;">
|
||||
完整 {{ stats.completeness?.complete ?? 0 }} / 总计 {{ stats.completeness?.total ?? 0 }}
|
||||
</div>
|
||||
<div style="font-size:12px;color:#f56c6c;margin-top:8px;">
|
||||
缺失元数据: {{ stats.completeness?.missing_metadata ?? 0 }} 个KPI
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="stats.completeness?.score ?? 0"
|
||||
:stroke-width="12"
|
||||
:color="completenessColor"
|
||||
style="margin-top:12px;"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never">
|
||||
<template #header>📉 缺失率统计</template>
|
||||
<div style="text-align:center;padding:12px 0;">
|
||||
<div style="font-size:36px;font-weight:700;color:#e6a23c;">
|
||||
{{ stats.data_missing?.rate ?? '-' }}%
|
||||
</div>
|
||||
<div style="font-size:13px;color:#999;margin-top:4px;">
|
||||
无数据值的KPI: {{ stats.data_missing?.count ?? 0 }}
|
||||
</div>
|
||||
<div style="font-size:12px;color:#999;margin-top:4px;">
|
||||
总计 {{ stats.data_missing?.total ?? 0 }} 个KPI
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="stats.data_missing?.rate ?? 0"
|
||||
:stroke-width="12"
|
||||
color="#e6a23c"
|
||||
style="margin-top:12px;"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never">
|
||||
<template #header>⚠️ 超30天未更新预警</template>
|
||||
<div style="text-align:center;padding:12px 0;">
|
||||
<div :style="{ fontSize: '36px', fontWeight: 700, color: staleDataColor }">
|
||||
{{ stats.stale_data?.count ?? 0 }}
|
||||
</div>
|
||||
<div style="font-size:13px;color:#999;margin-top:4px;">
|
||||
个KPI超过 {{ stats.stale_data?.threshold_days ?? 180 }} 天未更新
|
||||
</div>
|
||||
<el-tag v-if="(stats.stale_data?.count ?? 0) > 0" type="danger" size="small" style="margin-top:8px;">
|
||||
建议立即检查数据源
|
||||
</el-tag>
|
||||
<el-tag v-else type="success" size="small" style="margin-top:8px;">
|
||||
数据更新正常
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 异常类型分布 -->
|
||||
<el-row :gutter="16" class="section-gap">
|
||||
<el-col :span="24">
|
||||
@@ -147,6 +214,20 @@ const typeChartOption = computed(() => ({
|
||||
}],
|
||||
}))
|
||||
|
||||
const completenessColor = computed(() => {
|
||||
const s = stats.value.completeness?.score ?? 0
|
||||
if (s >= 80) return '#67c23a'
|
||||
if (s >= 50) return '#e6a23c'
|
||||
return '#f56c6c'
|
||||
})
|
||||
|
||||
const staleDataColor = computed(() => {
|
||||
const c = stats.value.stale_data?.count ?? 0
|
||||
if (c === 0) return '#67c23a'
|
||||
if (c <= 5) return '#e6a23c'
|
||||
return '#f56c6c'
|
||||
})
|
||||
|
||||
async function loadStats() {
|
||||
try { const r: any = await dataQualityApi.stats(); stats.value = r } catch (e) {}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,37 @@
|
||||
</el-row>
|
||||
<el-form-item><el-button type="primary" @click="save">保存</el-button></el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 元数据卡片 -->
|
||||
<el-card shadow="never" style="margin-top:16px;">
|
||||
<template #header>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<span>📋 元数据卡片</span>
|
||||
<el-tag v-if="!metadataComplete" type="warning" size="small" effect="dark">⚠️ 元数据不完整</el-tag>
|
||||
<el-tag v-else type="success" size="small" effect="dark">✅ 完整</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="计算公式" :span="2">
|
||||
<span v-if="kpi.formula" style="font-family:monospace;">{{ kpi.formula }}</span>
|
||||
<el-tag v-else type="warning" size="small">缺失</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据来源">
|
||||
<span v-if="kpi.data_source && kpi.data_source !== '待补充'">{{ kpi.data_source }}</span>
|
||||
<el-tag v-else type="warning" size="small">{{ kpi.data_source || '缺失' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据责任人">
|
||||
<span v-if="kpi.data_owner && kpi.data_owner !== '待指定'">{{ kpi.data_owner }}</span>
|
||||
<el-tag v-else type="warning" size="small">{{ kpi.data_owner || '缺失' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">{{ kpi.unit || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="目标值">{{ kpi.target_value ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数据源类型">{{ kpi.data_source_type || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="维度">{{ dimLabel(kpi.dimension) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="频率">{{ kpi.frequency || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="类别" :span="2">{{ catLabel(kpi.category) || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="历史数据" name="history">
|
||||
@@ -354,6 +385,14 @@ function dimLabel(d: string) { return ({ finance: '财务', customer: '客户',
|
||||
function catLabel(c: string) { return CAT_MAP[c] || c }
|
||||
function dimTagType(d: string) { return ({ finance: '', customer: 'success', process: 'warning', learning: 'info' } as any)[d] || '' }
|
||||
|
||||
const metadataComplete = computed(() => {
|
||||
if (!kpi.value) return false
|
||||
const k = kpi.value
|
||||
return !!(k.formula && k.formula.trim() && k.data_source && k.data_source.trim() && k.data_source !== '待补充'
|
||||
&& k.data_owner && k.data_owner.trim() && k.data_owner !== '待指定'
|
||||
&& k.unit && k.unit.trim() && k.target_value !== null && k.target_value !== undefined)
|
||||
})
|
||||
|
||||
const chartOption = computed(() => ({
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: { type: 'category', data: values.value.map(v => v.period) },
|
||||
|
||||
Reference in New Issue
Block a user