feat(data-quality): 财务七规则 governance-check 端点+前端Tab(最终方案)
This commit is contained in:
@@ -346,6 +346,8 @@ export const dataQualityApi = {
|
||||
deleteLog: (id: number) => api.delete(`/data-quality/logs/${id}`),
|
||||
// DAMA数据治理规则检查(财务七规则)
|
||||
checkGovernance: (params?: any) => api.get('/data-quality/check-governance', { params }),
|
||||
// 财务七规则检查(最终方案:评分+7规则+实体筛选,只读幂等)
|
||||
governanceCheck: (params?: any) => api.get('/data-quality/governance-check', { params }),
|
||||
}
|
||||
|
||||
export const biReportApi = {
|
||||
|
||||
+287
-242
@@ -2,260 +2,284 @@
|
||||
<div>
|
||||
<div class="flex-between section-gap">
|
||||
<span class="page-title">数据质量监控</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="runCheck" :loading="checking">执行全量检查</el-button>
|
||||
<el-button @click="loadLogs">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" class="section-gap">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card-inner">
|
||||
<div class="stat-value" style="color:#409eff;">{{ stats.total_kpis }}</div>
|
||||
<div class="stat-label">KPI总数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card-inner">
|
||||
<div class="stat-value" style="color:var(--bsc-learning);">{{ stats.open_logs }}</div>
|
||||
<div class="stat-label">未解决异常</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card-inner">
|
||||
<div class="stat-value" style="color:#f56c6c;">{{ stats.severity_counts?.critical ?? 0 }}</div>
|
||||
<div class="stat-label">严重异常</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card-inner">
|
||||
<div class="stat-value" style="color:#67c23a;">{{ stats.type_counts?.missing_data ?? 0 }}</div>
|
||||
<div class="stat-label">数据缺失</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</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">
|
||||
<el-card shadow="never">
|
||||
<template #header>异常类型分布</template>
|
||||
<v-chart :option="typeChartOption" autoresize style="height:200px;" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- DAMA 数据治理规则检查(财务七规则) -->
|
||||
<el-card shadow="never" class="section-gap">
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
|
||||
<span>🛡️ DAMA数据治理规则检查(财务七规则)</span>
|
||||
<el-tabs v-model="activeTab" class="section-gap" @tab-change="handleTabChange">
|
||||
<!-- ── Tab1: KPI质量监控 ── -->
|
||||
<el-tab-pane label="KPI质量监控" name="kpi">
|
||||
<div class="flex-between" style="margin-bottom:16px;">
|
||||
<span style="color:#909399;font-size:13px;">KPI值质量检查(缺失/骤变/持平/离群)</span>
|
||||
<div>
|
||||
<el-select v-model="govEntity" style="width:130px;margin-right:8px;" @change="runGovernanceCheck">
|
||||
<el-option label="全部实体" :value="0" />
|
||||
<el-option label="酣客" :value="1" />
|
||||
<el-option label="博海" :value="2" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="runGovernanceCheck" :loading="govChecking">重新检查</el-button>
|
||||
<el-button type="primary" @click="runCheck" :loading="checking">执行全量检查</el-button>
|
||||
<el-button @click="loadLogs">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6">
|
||||
<div style="text-align:center;padding:20px 0;">
|
||||
<el-progress type="dashboard" :percentage="govScore" :color="govScoreColor" :width="150">
|
||||
<template #default>
|
||||
<div style="font-size:32px;font-weight:700;line-height:1.2;">{{ govScore }}</div>
|
||||
<div style="font-size:12px;color:#999;">质量评分</div>
|
||||
</template>
|
||||
</el-progress>
|
||||
<div style="font-size:12px;color:#999;margin-top:10px;line-height:1.8;">
|
||||
检查时间: {{ govCheckedAt || '-' }}<br />
|
||||
通过 {{ govPassedCount }} / {{ govIssues.length }} 条规则
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<el-table :data="govIssues" size="small" border>
|
||||
<el-table-column type="expand">
|
||||
<template #default="{ row }">
|
||||
<div style="padding:10px 18px;background:#fafafa;">
|
||||
<div v-if="row.detail && row.detail.length" style="font-size:12px;line-height:24px;color:#606266;">
|
||||
<div v-for="(d, i) in row.detail" :key="i">• {{ d }}</div>
|
||||
</div>
|
||||
<div v-else style="font-size:12px;color:#67c23a;">✓ 未发现异常</div>
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" class="section-gap">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card-inner">
|
||||
<div class="stat-value" style="color:#409eff;">{{ stats.total_kpis }}</div>
|
||||
<div class="stat-label">KPI总数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card-inner">
|
||||
<div class="stat-value" style="color:var(--bsc-learning);">{{ stats.open_logs }}</div>
|
||||
<div class="stat-label">未解决异常</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card-inner">
|
||||
<div class="stat-value" style="color:#f56c6c;">{{ stats.severity_counts?.critical ?? 0 }}</div>
|
||||
<div class="stat-label">严重异常</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card-inner">
|
||||
<div class="stat-value" style="color:#67c23a;">{{ stats.type_counts?.missing_data ?? 0 }}</div>
|
||||
<div class="stat-label">数据缺失</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80">
|
||||
<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">
|
||||
<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="govRuleStatus(row)" size="small">{{ govRuleStatusText(row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规则" width="160">
|
||||
<template #default="{ row }">
|
||||
<span style="font-weight:600;">{{ govRuleName(row.rule) }}</span>
|
||||
<div style="font-size:11px;color:#999;">{{ row.rule }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="检查内容" min-width="240">
|
||||
<template #default="{ row }">{{ govRuleDesc(row.rule) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="级别" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.level === 'error' ? 'danger' : 'warning'" size="small">
|
||||
{{ row.level === 'error' ? '异常' : '警告' }}
|
||||
<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 label="异常数" width="90">
|
||||
<el-table-column prop="severity" label="严重度" width="80">
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: row.count > 0 ? '#f56c6c' : '#67c23a', fontWeight: 600 }">{{ row.count }}</span>
|
||||
<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-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-empty v-if="!loading && logs.length === 0" description="暂无质量异常日志" />
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 质量日志列表 -->
|
||||
<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>
|
||||
<!-- ── Tab2: 财务七规则 ── -->
|
||||
<el-tab-pane label="财务七规则" name="gov">
|
||||
<div v-if="govLoaded">
|
||||
<el-card shadow="never" class="section-gap">
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
|
||||
<span>🛡️ DAMA数据治理规则检查(财务七规则)</span>
|
||||
<div>
|
||||
<el-select v-model="govEntity" style="width:130px;margin-right:8px;" @change="runGovernanceCheck">
|
||||
<el-option label="全部实体" :value="0" />
|
||||
<el-option label="酣客" :value="1" />
|
||||
<el-option label="博海" :value="2" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="runGovernanceCheck" :loading="govChecking">重新检查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<!-- 评分大数字 -->
|
||||
<el-col :span="6">
|
||||
<div style="text-align:center;padding:16px 0;border-right:1px solid #f0f0f0;">
|
||||
<div :style="{ fontSize: '56px', fontWeight: 700, lineHeight: 1.2, color: govScoreColor }">
|
||||
{{ govScore }}
|
||||
</div>
|
||||
<div style="font-size:13px;color:#999;margin-top:4px;">质量评分(满分100)</div>
|
||||
<div style="font-size:12px;color:#999;margin-top:12px;line-height:1.8;">
|
||||
检查时间: {{ govCheckedAt || '-' }}<br />
|
||||
通过 {{ govPassedCount }} / 7 条规则<br />
|
||||
共扣 {{ govTotalDeduct }} 分
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 规则列表 -->
|
||||
<el-col :span="18">
|
||||
<el-table :data="govIssues" size="small" border>
|
||||
<el-table-column type="expand">
|
||||
<template #default="{ row }">
|
||||
<div style="padding:10px 18px;background:#fafafa;">
|
||||
<div v-if="row.detail && row.detail.length" style="font-size:12px;line-height:24px;color:#606266;">
|
||||
<div v-for="(d, i) in row.detail" :key="i">• {{ d }}</div>
|
||||
</div>
|
||||
<div v-else style="font-size:12px;color:#67c23a;">✓ 未发现异常</div>
|
||||
<div v-if="row.manual_detail && row.manual_detail.length" style="font-size:12px;line-height:24px;color:#e6a23c;margin-top:4px;">
|
||||
<div v-for="(d, i) in row.manual_detail" :key="'m'+i">⚠ {{ d }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="govRuleStatus(row)" size="small">{{ govRuleStatusText(row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规则" width="150">
|
||||
<template #default="{ row }">
|
||||
<span style="font-weight:600;">{{ govRuleName(row.rule) }}</span>
|
||||
<div style="font-size:11px;color:#999;">{{ row.rule }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="检查内容" min-width="240">
|
||||
<template #default="{ row }">{{ govRuleDesc(row.rule) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="级别" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.level === 'error' ? 'danger' : 'warning'" size="small">
|
||||
{{ row.level === 'error' ? '异常' : '警告' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="异常数" width="90">
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: govCountColor(row), fontWeight: 600 }">{{ row.count }}</span>
|
||||
<span v-if="row.manual_count" style="font-size:11px;color:#e6a23c;margin-left:4px;">(待确认{{ row.manual_count }})</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="扣分" width="70">
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: row.deducted > 0 ? '#f56c6c' : '#909399' }">-{{ row.deducted ?? 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</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-empty v-if="!loading && logs.length === 0" description="暂无质量异常日志" />
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -266,6 +290,9 @@ import VChart from 'vue-echarts'
|
||||
import 'echarts'
|
||||
import { dataQualityApi } from '../api/index'
|
||||
|
||||
const activeTab = ref('kpi')
|
||||
const govLoaded = ref(false)
|
||||
|
||||
const stats = ref<any>({})
|
||||
const logs = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
@@ -273,8 +300,9 @@ const checking = ref(false)
|
||||
const filterSeverity = ref('')
|
||||
const filterStatus = ref('')
|
||||
|
||||
// DAMA 数据治理规则检查状态
|
||||
// DAMA 数据治理规则检查状态(财务七规则)
|
||||
const govScore = ref(0)
|
||||
const govTotalDeduct = ref(0)
|
||||
const govIssues = ref<any[]>([])
|
||||
const govCheckedAt = ref('')
|
||||
const govEntity = ref(0)
|
||||
@@ -310,22 +338,30 @@ const staleDataColor = computed(() => {
|
||||
return '#f56c6c'
|
||||
})
|
||||
|
||||
// ── DAMA治理规则 computed ──
|
||||
// ── DAMA治理规则(财务七规则)computed ──
|
||||
const govPassedCount = computed(() => govIssues.value.filter((i: any) => i.count === 0).length)
|
||||
|
||||
const govScoreColor = computed(() => {
|
||||
if (govScore.value >= 90) return '#67c23a'
|
||||
if (govScore.value >= 60) return '#e6a23c'
|
||||
if (govScore.value >= 70) return '#e6a23c'
|
||||
return '#f56c6c'
|
||||
})
|
||||
|
||||
function govCountColor(row: any) {
|
||||
if (row.count > 0) return '#f56c6c'
|
||||
if ((row.manual_count ?? 0) > 0) return '#e6a23c'
|
||||
return '#67c23a'
|
||||
}
|
||||
|
||||
function govRuleStatus(row: any) {
|
||||
if (row.count > 0) return row.level === 'error' ? 'danger' : 'warning'
|
||||
if ((row.manual_count ?? 0) > 0) return 'warning' // 待人工确认
|
||||
return 'success'
|
||||
}
|
||||
|
||||
function govRuleStatusText(row: any) {
|
||||
if (row.count > 0) return row.level === 'error' ? '异常' : '警告'
|
||||
if ((row.manual_count ?? 0) > 0) return '待确认'
|
||||
return '通过'
|
||||
}
|
||||
|
||||
@@ -337,6 +373,14 @@ function govRuleDesc(rule: string) {
|
||||
return govRuleMeta.value?.[rule]?.desc || ''
|
||||
}
|
||||
|
||||
function handleTabChange(name: string | number) {
|
||||
// Tab2 懒加载:首次切换到"财务七规则"时才请求
|
||||
if (name === 'gov' && !govLoaded.value) {
|
||||
govLoaded.value = true
|
||||
runGovernanceCheck()
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try { const r: any = await dataQualityApi.stats(); stats.value = r } catch (e) {}
|
||||
}
|
||||
@@ -371,8 +415,9 @@ async function runGovernanceCheck() {
|
||||
try {
|
||||
const params: any = {}
|
||||
if (govEntity.value) params.entity_id = govEntity.value
|
||||
const r: any = await dataQualityApi.checkGovernance(params)
|
||||
const r: any = await dataQualityApi.governanceCheck(params)
|
||||
govScore.value = r.score ?? 0
|
||||
govTotalDeduct.value = r.total_deduct ?? 0
|
||||
govIssues.value = r.issues || []
|
||||
govCheckedAt.value = (r.checked_at || '').replace('T', ' ').slice(0, 19)
|
||||
govRuleMeta.value = r.rules_meta || {}
|
||||
@@ -396,7 +441,7 @@ async function updateStatus(id: number, status: string) {
|
||||
onMounted(() => {
|
||||
loadStats()
|
||||
loadLogs()
|
||||
runGovernanceCheck()
|
||||
// Tab2 懒加载:不主动请求 governance-check,切换到 Tab2 时触发
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user