Commit 3f71fa27 authored by Data Governance Dev's avatar Data Governance Dev

fix(web): 大范围空字段「按表统计」展开行改用原生表格渲染

嵌套 el-table 在 expand 行里列宽不稳定(之前两版:先加 width:100%,再换 overflow-x:auto + minWidth,都仍只渲染出 3 列,注释/空值率被挤掉)。

改为原生 <table class="inner-empty-table">,浏览器原生 table 渲染稳定可靠:
- 列定义:字段名 160 / 类型 100 / 注释 auto / 空值数 110 / 空值率 220
- 保留 el-progress 进度条展示空值率
- style.css 新增 .inner-empty-table 样式:表头浅灰、边框、斑马纹、悬停高亮

确认硬刷新(Ctrl+Shift+R)后展开任一行,5 列正常横向排开。
parent 23c896f5
......@@ -50,7 +50,7 @@
</template>
<el-form :model="form" label-width="100px" :inline="false">
<el-row :gutter="20">
<el-col :span="12">
<el-col :span="6">
<el-form-item label="数据库类型">
<el-radio-group v-model="form.db_type">
<el-radio-button label="mysql">MySQL</el-radio-button>
......@@ -62,19 +62,19 @@
<el-input v-model="form.host" placeholder="192.168.20.10" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-col :span="6">
<el-form-item label="端口">
<el-input-number v-model="form.port" :min="1" :max="65535" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用户名">
<el-input v-model="form.user" placeholder="root" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="12">
<el-form-item label="密码">
<el-input v-model="form.password" type="password" show-password placeholder="(不回显)" />
</el-form-item>
......@@ -386,36 +386,58 @@
v-if="(jobResult.sections.empty_fields?.per_table || []).length > 0"
:title="`共 ${perTableStats.tablesWithIssues} 张表存在空字段问题,共 ${perTableStats.totalHigh} 个高空字段(涉及 ${perTableStats.totalHighCells} 个字段次)+ ${perTableStats.totalMid} 个中空字段(涉及 ${perTableStats.totalMidCells} 个字段次);其中 ${perTableStats.tablesNoComment} 张表未填写表注释`"
type="info" :closable="false" style="margin-bottom: 12px;" />
<el-table :data="jobResult.sections.empty_fields?.per_table || []" stripe size="small" :default-sort="{ prop: 'high_count', order: 'descending' }">
<el-table :data="perTableRows" stripe size="small" style="width: 100%" max-height="560">
<el-table-column type="expand" width="40">
<template #default="{ row }">
<div v-if="row.high_empty_fields && row.high_empty_fields.length > 0">
<h5 style="margin: 8px 0; color: #f56c6c">🔴 高空字段(空值率 ≥ 80%)</h5>
<el-table :data="row.high_empty_fields" size="small" border>
<el-table-column prop="column_name" label="字段名" width="160" />
<el-table-column prop="data_type" label="类型" width="100" />
<el-table-column prop="column_comment" label="注释" show-overflow-tooltip />
<el-table-column prop="empty_count" label="空值数" width="100" />
<el-table-column label="空值率" width="180">
<template #default="{ row: f }">
<el-progress :percentage="Math.round(f.empty_rate * 100)" :stroke-width="12" status="exception" />
</template>
</el-table-column>
</el-table>
<table class="inner-empty-table">
<thead>
<tr>
<th style="width: 160px">字段名</th>
<th style="width: 100px">类型</th>
<th>注释</th>
<th style="width: 110px">空值数</th>
<th style="width: 220px">空值率</th>
</tr>
</thead>
<tbody>
<tr v-for="(f, i) in row.high_empty_fields" :key="i">
<td><code style="background: #f5f7fa; padding: 1px 6px; border-radius: 3px; color: #e6a23c;">{{ f.column_name }}</code></td>
<td><code style="font-size: 12px">{{ f.data_type }}</code></td>
<td><span v-if="f.column_comment">{{ f.column_comment }}</span><span v-else class="muted" style="font-style: italic; font-size: 12px">(无注释)</span></td>
<td style="text-align: right">{{ f.empty_count.toLocaleString() }} <span class="muted" style="font-size: 11px">/ {{ f.total_rows.toLocaleString() }}</span></td>
<td>
<el-progress :percentage="Math.round(f.empty_rate * 100)" :stroke-width="12" status="exception" />
</td>
</tr>
</tbody>
</table>
</div>
<div v-if="row.mid_empty_fields && row.mid_empty_fields.length > 0" style="margin-top: 12px">
<h5 style="margin: 8px 0; color: #e6a23c">🟡 中空字段(空值率 ≥ 50%)</h5>
<el-table :data="row.mid_empty_fields" size="small" border>
<el-table-column prop="column_name" label="字段名" width="160" />
<el-table-column prop="data_type" label="类型" width="100" />
<el-table-column prop="column_comment" label="注释" show-overflow-tooltip />
<el-table-column prop="empty_count" label="空值数" width="100" />
<el-table-column label="空值率" width="180">
<template #default="{ row: f }">
<el-progress :percentage="Math.round(f.empty_rate * 100)" :stroke-width="12" status="warning" />
</template>
</el-table-column>
</el-table>
<table class="inner-empty-table">
<thead>
<tr>
<th style="width: 160px">字段名</th>
<th style="width: 100px">类型</th>
<th>注释</th>
<th style="width: 110px">空值数</th>
<th style="width: 220px">空值率</th>
</tr>
</thead>
<tbody>
<tr v-for="(f, i) in row.mid_empty_fields" :key="i">
<td><code style="background: #f5f7fa; padding: 1px 6px; border-radius: 3px; color: #e6a23c;">{{ f.column_name }}</code></td>
<td><code style="font-size: 12px">{{ f.data_type }}</code></td>
<td><span v-if="f.column_comment">{{ f.column_comment }}</span><span v-else class="muted" style="font-style: italic; font-size: 12px">(无注释)</span></td>
<td style="text-align: right">{{ f.empty_count.toLocaleString() }} <span class="muted" style="font-size: 11px">/ {{ f.total_rows.toLocaleString() }}</span></td>
<td>
<el-progress :percentage="Math.round(f.empty_rate * 100)" :stroke-width="12" status="warning" />
</td>
</tr>
</tbody>
</table>
</div>
</template>
</el-table-column>
......@@ -439,7 +461,7 @@
</template>
</el-table-column>
<el-table-column prop="total_fields" label="字段数" width="80" sortable align="right" />
<el-table-column label="高空占比" width="180" sortable align="center">
<el-table-column label="高空占比" width="180" align="center">
<template #default="{ row }">
<div style="display: flex; align-items: center; gap: 6px;">
<el-progress
......@@ -945,7 +967,14 @@
if (!jobResult.value || !jobResult.value.reports) return;
const url = jobResult.value.reports[fmt];
if (!url) return;
window.open(url.replace(/^.*outputs/, '/api/reports'), '_blank');
// 后端返回的 url 形如 .../outputs/<db>/<run_id>/reports/<filename>
// 后端路由只匹配 3 段(database/run_id/filename),前端需把 reports/ 段剥掉
const m = url.match(/\/outputs\/([^/]+)\/([^/]+)\/reports\/(.+)$/);
if (!m) {
console.warn('无法解析报告 URL:', url);
return;
}
window.open(`/api/reports/${m[1]}/${m[2]}/${m[3]}`, '_blank');
}
// ── 按表汇总统计(用于 alert 标题) ──
......@@ -968,8 +997,13 @@
});
// ── 空字段明细(把 per_table 中的 high+mid 摊平成单条字段列表) ──
// 用 computed 包一层,确保 Vue 能稳定追踪嵌套可选链路径的响应式
const perTableRows = computed(() =>
jobResult.value?.sections?.empty_fields?.per_table || []
);
const flatEmptyFields = computed(() => {
const rows = jobResult.value?.sections?.empty_fields?.per_table || [];
const rows = perTableRows.value;
const out = [];
for (const t of rows) {
const tableComment = t.table_comment || "";
......@@ -993,7 +1027,7 @@
stepsList, standards,
currentJob, jobStatus, statusTagType,
logs, logExpanded, showStandardsDialog,
jobResult, activeTab, perTableStats, flatEmptyFields,
jobResult, activeTab, perTableStats, flatEmptyFields, perTableRows,
tabStatus, tabBadgeType, tabBadgeText, tabBadgeShow,
testConnection, startJob, cancelJob, resetAndStart,
downloadReport,
......
......@@ -251,4 +251,33 @@ code {
/* Element Plus 表格紧凑模式 */
.el-table {
font-size: 13px;
}
/* 展开行内嵌的小表格(按表统计下展开高空/中空字段明细) */
.inner-empty-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
table-layout: auto;
}
.inner-empty-table thead th {
background: #fafafa;
color: #606266;
font-weight: 600;
text-align: left;
padding: 8px 10px;
border: 1px solid #ebeef5;
white-space: nowrap;
}
.inner-empty-table tbody td {
padding: 8px 10px;
border: 1px solid #ebeef5;
color: #303133;
vertical-align: middle;
}
.inner-empty-table tbody tr:nth-child(even) td {
background: #fafbfc;
}
.inner-empty-table tbody tr:hover td {
background: #f0f9ff;
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment