Commit 735a46e9 authored by Data Governance Dev's avatar Data Governance Dev

fix(web): 修复概览/表注释展示为空问题,重整空字段与冗余字段 tab

后端
- models.py: OverviewStats 补充 mid_empty_fields / standards_applied 字段,
  避免被 routes.py 的字段过滤机制误删
- orchestrator.py: _build_overview 修正 nested overview_meta 取值(三层嵌套,
  之前一直兜底到 0 → 表/字段总数显示 0)
- step4_empty_fields.py: 修复 table_comment 始终为空(之前从列级 row 取
  table_comment,应从 table_summary 取),新增 per_table.total_fields 与
  field_record 用同一份 table_comment_map

前端 (index.html / style.css)
- 隐藏历史记录入口(保留后端接口,便于以后恢复)
- 移除「中空字段」KPI 卡片(位于中间不符合浏览习惯)
- 大范围空字段 tab:
  * 字段明细(平铺:等级/表名/表注释/字段名/字段注释/类型/空值数/空值率)
    上移到主视图位置
  * 按表统计下移为辅助视图,表名+表注释合并到一列避免错位
- 冗余字段 tab:把 prop 改成与后端实际数据一致的
  field/table_count/classification/reasoning/recommendation/source,并
  给 llm_failed / true_redundancy / suspicious / common_business /
  common_base 各加色 tag 显著标识
- 修复"\"LLM 失败\""导致的 template compile 错误引发的白屏
  (HTML 属性里 \ 不会被当 JS 转义,把 " 换成「 」即可)
- .app-main 宽度 1400 → 1800px,table 更舒展
parent d442e8fb
...@@ -99,9 +99,11 @@ class OverviewStats(BaseModel): ...@@ -99,9 +99,11 @@ class OverviewStats(BaseModel):
total_tables: int = 0 total_tables: int = 0
total_fields: int = 0 total_fields: int = 0
high_empty_fields: int = 0 high_empty_fields: int = 0
mid_empty_fields: int = 0
missing_comments: int = 0 missing_comments: int = 0
length_issues: int = 0 length_issues: int = 0
standard_violations: int = 0 standard_violations: int = 0
standards_applied: int = 0
merge_candidates: int = 0 merge_candidates: int = 0
redundancy_fields: int = 0 redundancy_fields: int = 0
executed_at: Optional[datetime] = None executed_at: Optional[datetime] = None
......
...@@ -345,7 +345,11 @@ def _generate_reports(run_dir: Path, sections: dict, overview: dict, ...@@ -345,7 +345,11 @@ def _generate_reports(run_dir: Path, sections: dict, overview: dict,
# ── Overview 构造 ── # ── Overview 构造 ──
def _build_overview(sections: dict, cfg: DBConfig) -> dict: def _build_overview(sections: dict, cfg: DBConfig) -> dict:
"""从各 section 汇总概览数据""" """从各 section 汇总概览数据"""
overview_meta = sections.get("overview_meta", {}) # step1 返回的 data 嵌套了三层:sections["overview_meta"] =
# {"data_dictionary": [...], "table_summary": [...], "overview_meta": {total_tables, total_fields, ...}}
# 统计字段在最内层 overview_meta 里
overview_meta_root = sections.get("overview_meta", {})
overview_meta = overview_meta_root.get("overview_meta", overview_meta_root) if isinstance(overview_meta_root, dict) else {}
empty = sections.get("empty_fields", {}).get("summary", {}) empty = sections.get("empty_fields", {}).get("summary", {})
missing = sections.get("missing_comments", {}).get("summary", {}) missing = sections.get("missing_comments", {}).get("summary", {})
length = sections.get("length_issues", {}).get("summary", {}) length = sections.get("length_issues", {}).get("summary", {})
......
...@@ -46,6 +46,21 @@ def run_step4(cfg: DBConfig, log: Callable | None = None, ...@@ -46,6 +46,21 @@ def run_step4(cfg: DBConfig, log: Callable | None = None,
for row in columns: for row in columns:
by_table[row["table_name"]].append(row) by_table[row["table_name"]].append(row)
# 表级注释来自 table_summary(INFORMATION_SCHEMA.TABLES),
# 列级 row(COLUMNS)没有 table_comment 字段
table_comment_map: dict[str, str] = {}
for t in table_summary:
name = t.get("table_name") or t.get("TABLE_NAME")
if not name:
continue
comment = (
t.get("table_comment")
or t.get("TABLE_COMMENT")
or ""
)
if comment:
table_comment_map[name] = comment
loader = get_sql_loader() loader = get_sql_loader()
high_count = 0 high_count = 0
mid_count = 0 mid_count = 0
...@@ -118,7 +133,7 @@ def run_step4(cfg: DBConfig, log: Callable | None = None, ...@@ -118,7 +133,7 @@ def run_step4(cfg: DBConfig, log: Callable | None = None,
empty_rate = empty_count / row_count if row_count else 0 empty_rate = empty_count / row_count if row_count else 0
field_record = { field_record = {
"table_name": table, "table_name": table,
"table_comment": col.get("table_comment", ""), "table_comment": table_comment_map.get(table, ""),
"column_name": col["column_name"], "column_name": col["column_name"],
"data_type": col["data_type"], "data_type": col["data_type"],
"column_type": col["column_type"], "column_type": col["column_type"],
...@@ -140,10 +155,12 @@ def run_step4(cfg: DBConfig, log: Callable | None = None, ...@@ -140,10 +155,12 @@ def run_step4(cfg: DBConfig, log: Callable | None = None,
if high_in_table or mid_in_table: if high_in_table or mid_in_table:
tables_with_issues.add(table) tables_with_issues.add(table)
total_fields_in_table = len(by_table[table])
per_table.append({ per_table.append({
"table_name": table, "table_name": table,
"table_comment": by_table[table][0].get("table_comment", ""), "table_comment": table_comment_map.get(table, ""),
"total_rows": row_count, "total_rows": row_count,
"total_fields": total_fields_in_table,
"high_empty_fields": high_in_table, "high_empty_fields": high_in_table,
"mid_empty_fields": mid_in_table, "mid_empty_fields": mid_in_table,
"high_count": len(high_in_table), "high_count": len(high_in_table),
......
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
<el-tag size="small" type="info" effect="plain">v1.0</el-tag> <el-tag size="small" type="info" effect="plain">v1.0</el-tag>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<el-button @click="loadHistory" :icon="Refresh">历史记录</el-button>
<el-button @click="showStandardsDialog = true" :icon="Medal">国标清单</el-button> <el-button @click="showStandardsDialog = true" :icon="Medal">国标清单</el-button>
</div> </div>
</div> </div>
...@@ -193,10 +192,6 @@ ...@@ -193,10 +192,6 @@
<div class="kpi-value">{{ jobResult.overview.high_empty_fields }}</div> <div class="kpi-value">{{ jobResult.overview.high_empty_fields }}</div>
<div class="kpi-label">高空字段</div> <div class="kpi-label">高空字段</div>
</div> </div>
<div class="kpi-card kpi-empty-mid">
<div class="kpi-value">{{ jobResult.overview.mid_empty_fields }}</div>
<div class="kpi-label">中空字段</div>
</div>
<div class="kpi-card kpi-missing"> <div class="kpi-card kpi-missing">
<div class="kpi-value">{{ jobResult.overview.missing_comments }}</div> <div class="kpi-value">{{ jobResult.overview.missing_comments }}</div>
<div class="kpi-label">缺注释字段</div> <div class="kpi-label">缺注释字段</div>
...@@ -262,30 +257,115 @@ ...@@ -262,30 +257,115 @@
<!-- 冗余字段 --> <!-- 冗余字段 -->
<el-tab-pane label="冗余字段" name="redundancy"> <el-tab-pane label="冗余字段" name="redundancy">
<el-alert type="info" :closable="false" style="margin-bottom: 12px"
:title="`共 ${(jobResult.sections.merge_candidates?.redundancy_fields || []).length} 个高频字段(出现在 ≥ 10 张表),LLM 已给出分类与处置建议;标为「LLM 失败」的需人工核对`" />
<el-table :data="jobResult.sections.merge_candidates?.redundancy_fields || []" stripe size="small"> <el-table :data="jobResult.sections.merge_candidates?.redundancy_fields || []" stripe size="small">
<el-table-column prop="field" label="字段名" width="200" /> <el-table-column prop="field" label="字段名" width="160" fixed>
<el-table-column prop="table_count" label="出现表数" width="120" sortable /> <template #default="{ row }">
<el-table-column prop="risk" label="风险" width="200" /> <code style="background: #f5f7fa; padding: 1px 6px; border-radius: 3px; color: #e6a23c;">{{ row.field }}</code>
<el-table-column prop="suggestion" label="建议" /> </template>
</el-table-column>
<el-table-column prop="table_count" label="出现表数" width="100" sortable align="right" />
<el-table-column prop="classification" label="分类" width="140">
<template #default="{ row }">
<el-tag v-if="row.classification === 'true_redundancy'" type="danger" size="small" effect="dark">真冗余</el-tag>
<el-tag v-else-if="row.classification === 'suspicious'" type="warning" size="small" effect="dark">疑似冗余</el-tag>
<el-tag v-else-if="row.classification === 'common_business'" type="success" size="small" effect="plain">业务共享</el-tag>
<el-tag v-else-if="row.classification === 'common_base'" type="info" size="small" effect="plain">通用基础</el-tag>
<el-tag v-else type="danger" size="small" effect="plain" style="font-style: italic">LLM 失败</el-tag>
</template>
</el-table-column>
<el-table-column prop="reasoning" label="判断依据" min-width="240" show-overflow-tooltip />
<el-table-column prop="recommendation" label="建议" min-width="220" show-overflow-tooltip>
<template #default="{ row }">
<span>{{ row.recommendation }}</span>
</template>
</el-table-column>
<el-table-column prop="source" label="来源" width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.source === 'llm'" type="success" size="small" effect="plain">LLM</el-tag>
<el-tag v-else-if="row.source === 'llm_failed'" type="danger" size="small" effect="plain">LLM 失败</el-tag>
<el-tag v-else size="small" effect="plain">{{ row.source }}</el-tag>
</template>
</el-table-column>
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
<!-- 大范围空字段 --> <!-- 大范围空字段 -->
<el-tab-pane label="大范围空字段" name="empty"> <el-tab-pane label="大范围空字段" name="empty">
<el-alert :title="`扫描 ${jobResult.sections.empty_fields?.summary?.total_tables_scanned || 0} 张表,发现 ${jobResult.sections.empty_fields?.summary?.high_empty_fields_count || 0} 个高空字段、${jobResult.sections.empty_fields?.summary?.mid_empty_fields_count || 0} 个中空字段,涉及 ${jobResult.sections.empty_fields?.summary?.tables_with_issues_count || 0} 张表`" type="info" :closable="false" /> <el-alert :title="`扫描 ${jobResult.sections.empty_fields?.summary?.total_tables_scanned || 0} 张表,发现 ${jobResult.sections.empty_fields?.summary?.high_empty_fields_count || 0} 个高空字段、${jobResult.sections.empty_fields?.summary?.mid_empty_fields_count || 0} 个中空字段,涉及 ${jobResult.sections.empty_fields?.summary?.tables_with_issues_count || 0} 张表`" type="info" :closable="false" />
<!-- 字段明细:把每个空字段摊开显示(表名 + 字段名 + 字段注释) -->
<h4 style="margin-top: 16px">字段明细(共 {{ flatEmptyFields.length }} 条,默认按空值率倒序)</h4>
<el-table :data="flatEmptyFields" stripe size="small" :default-sort="{ prop: 'empty_rate', order: 'descending' }" max-height="600">
<el-table-column prop="level" label="等级" width="80" sortable align="center">
<template #default="{ row }">
<el-tag v-if="row.level === 'high'" type="danger" size="small" effect="dark">高空</el-tag>
<el-tag v-else-if="row.level === 'mid'" type="warning" size="small" effect="plain">中空</el-tag>
</template>
</el-table-column>
<el-table-column prop="table_name" label="表名" width="220" show-overflow-tooltip fixed>
<template #default="{ row }">
<strong style="color: #303133">{{ row.table_name }}</strong>
</template>
</el-table-column>
<el-table-column prop="table_comment" label="表注释" min-width="220" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.table_comment" style="color: #606266; font-size: 12px">{{ row.table_comment }}</span>
<span v-else class="muted" style="font-style: italic; font-size: 12px">(无注释)</span>
</template>
</el-table-column>
<el-table-column prop="column_name" label="字段名" width="180" show-overflow-tooltip fixed>
<template #default="{ row }">
<code style="background: #f5f7fa; padding: 1px 6px; border-radius: 3px; color: #e6a23c;">{{ row.column_name }}</code>
</template>
</el-table-column>
<el-table-column prop="column_comment" label="字段注释" min-width="220" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.column_comment">{{ row.column_comment }}</span>
<el-tag v-else size="small" type="info" effect="plain" style="font-style: italic">(无注释)</el-tag>
</template>
</el-table-column>
<el-table-column prop="data_type" label="类型" width="100" />
<el-table-column prop="empty_count" label="空值数" width="120" sortable align="right">
<template #default="{ row }">
{{ row.empty_count.toLocaleString() }} <span class="muted" style="font-size: 11px">/ {{ row.total_rows.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column prop="empty_rate" label="空值率" width="220" sortable align="center">
<template #default="{ row }">
<div style="display: flex; align-items: center; gap: 6px;">
<el-progress
:percentage="Math.round(row.empty_rate * 100)"
:stroke-width="14"
:status="row.level === 'high' ? 'exception' : 'warning'"
:show-text="false"
style="flex: 1;" />
<span style="min-width: 48px; text-align: right;">
<strong :style="{ color: row.level === 'high' ? '#f56c6c' : '#e6a23c' }">{{ Math.round(row.empty_rate * 100) }}%</strong>
</span>
</div>
</template>
</el-table-column>
</el-table>
<!-- 按表汇总(可展开查看字段明细) --> <!-- 按表汇总(可展开查看字段明细) -->
<h4 style="margin-top: 16px">按表统计</h4> <h4 style="margin-top: 24px">按表统计(默认按高空字段数倒序,点击「展开」查看该表下的字段)</h4>
<el-table :data="jobResult.sections.empty_fields?.per_table || []" stripe size="small"> <el-alert
<el-table-column type="expand"> 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-column type="expand" width="40">
<template #default="{ row }"> <template #default="{ row }">
<div v-if="row.high_empty_fields && row.high_empty_fields.length > 0"> <div v-if="row.high_empty_fields && row.high_empty_fields.length > 0">
<h5 style="margin: 8px 0; color: #f56c6c">🔴 高空字段(空值率 ≥ 80%)</h5> <h5 style="margin: 8px 0; color: #f56c6c">🔴 高空字段(空值率 ≥ 80%)</h5>
<el-table :data="row.high_empty_fields" size="small" border> <el-table :data="row.high_empty_fields" size="small" border>
<el-table-column prop="column_name" label="字段名" width="160" /> <el-table-column prop="column_name" label="字段名" width="160" />
<el-table-column prop="data_type" label="类型" width="100" /> <el-table-column prop="data_type" label="类型" width="100" />
<el-table-column prop="column_comment" label="注释" /> <el-table-column prop="column_comment" label="注释" show-overflow-tooltip />
<el-table-column prop="empty_count" label="空值数" width="100" /> <el-table-column prop="empty_count" label="空值数" width="100" />
<el-table-column label="空值率" width="150"> <el-table-column label="空值率" width="180">
<template #default="{ row: f }"> <template #default="{ row: f }">
<el-progress :percentage="Math.round(f.empty_rate * 100)" :stroke-width="12" status="exception" /> <el-progress :percentage="Math.round(f.empty_rate * 100)" :stroke-width="12" status="exception" />
</template> </template>
...@@ -297,9 +377,9 @@ ...@@ -297,9 +377,9 @@
<el-table :data="row.mid_empty_fields" size="small" border> <el-table :data="row.mid_empty_fields" size="small" border>
<el-table-column prop="column_name" label="字段名" width="160" /> <el-table-column prop="column_name" label="字段名" width="160" />
<el-table-column prop="data_type" label="类型" width="100" /> <el-table-column prop="data_type" label="类型" width="100" />
<el-table-column prop="column_comment" label="注释" /> <el-table-column prop="column_comment" label="注释" show-overflow-tooltip />
<el-table-column prop="empty_count" label="空值数" width="100" /> <el-table-column prop="empty_count" label="空值数" width="100" />
<el-table-column label="空值率" width="150"> <el-table-column label="空值率" width="180">
<template #default="{ row: f }"> <template #default="{ row: f }">
<el-progress :percentage="Math.round(f.empty_rate * 100)" :stroke-width="12" status="warning" /> <el-progress :percentage="Math.round(f.empty_rate * 100)" :stroke-width="12" status="warning" />
</template> </template>
...@@ -308,18 +388,45 @@ ...@@ -308,18 +388,45 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="table_name" label="表名" width="200" /> <!-- 表名 + 表注释 合并在一列,避免错位/挤列 -->
<el-table-column prop="table_comment" label="表注释" /> <el-table-column label="表名 / 表注释" min-width="320" show-overflow-tooltip>
<el-table-column prop="total_rows" label="总行数" width="100" sortable />
<el-table-column prop="high_count" label="高空字段" width="110" sortable>
<template #default="{ row }"> <template #default="{ row }">
<el-tag v-if="row.high_count > 0" type="danger" size="small">{{ row.high_count }}</el-tag> <div>
<span v-else class="muted">0</span> <strong style="color: #303133">{{ row.table_name }}</strong>
<div v-if="row.table_comment" class="muted" style="font-size: 12px; margin-top: 2px;">
{{ row.table_comment }}
</div>
<div v-else class="muted" style="font-size: 12px; margin-top: 2px; font-style: italic;">
(无注释)
</div>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="mid_count" label="中空字段" width="110" sortable> <el-table-column prop="total_rows" label="总行数" width="110" sortable align="right">
<template #default="{ row }"> <template #default="{ row }">
<el-tag v-if="row.mid_count > 0" type="warning" size="small">{{ row.mid_count }}</el-tag> <span :class="row.total_rows === 0 ? 'muted' : ''">{{ row.total_rows.toLocaleString() }}</span>
</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">
<template #default="{ row }">
<div style="display: flex; align-items: center; gap: 6px;">
<el-progress
:percentage="Math.round(row.high_count / Math.max(row.total_fields, 1) * 100)"
:stroke-width="14"
:status="row.high_count > 0 ? 'exception' : ''"
:show-text="false"
style="flex: 1;" />
<span style="min-width: 56px; text-align: right;">
<strong style="color: #f56c6c">{{ row.high_count }}</strong>
<span class="muted"> / {{ row.total_fields }}</span>
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="mid_count" label="中空" width="90" sortable align="center">
<template #default="{ row }">
<el-tag v-if="row.mid_count > 0" type="warning" size="small" effect="plain">{{ row.mid_count }}</el-tag>
<span v-else class="muted">0</span> <span v-else class="muted">0</span>
</template> </template>
</el-table-column> </el-table-column>
...@@ -413,35 +520,7 @@ ...@@ -413,35 +520,7 @@
</el-tabs> </el-tabs>
</el-card> </el-card>
<!-- 历史记录 --> <!-- 历史记录已隐藏 -->
<el-card class="section-card" shadow="hover">
<template #header>
<div class="card-header">
<el-icon><Histogram /></el-icon>
<span class="card-title">④ 历史分析记录({{ history.length }} 次)</span>
</div>
</template>
<el-table :data="history" stripe size="small">
<el-table-column prop="run_id" label="运行 ID" width="200" />
<el-table-column prop="database" label="数据库" width="160" />
<el-table-column prop="executed_at" label="执行时间" width="200" />
<el-table-column prop="duration_sec" label="耗时 (秒)" width="100" />
<el-table-column label="概览">
<template #default="{ row }">
<el-tag size="small" type="info">{{ row.stats.total_tables || 0 }} 表 / {{ row.stats.total_fields || 0 }} 字段</el-tag>
<el-tag size="small" type="warning" style="margin-left: 4px">{{ row.stats.high_empty_fields || 0 }} 高空</el-tag>
<el-tag size="small" type="danger" style="margin-left: 4px">{{ row.stats.standard_violations || 0 }} 违规</el-tag>
</template>
</el-table-column>
<el-table-column label="下载">
<template #default="{ row }">
<el-button v-if="row.has_markdown" size="small" link @click="downloadHistoryReport(row, 'md')">MD</el-button>
<el-button v-if="row.has_docx" size="small" link type="primary" @click="downloadHistoryReport(row, 'docx')">Word</el-button>
<span v-if="!row.has_markdown && !row.has_docx" class="muted">—</span>
</template>
</el-table-column>
</el-table>
</el-card>
</el-main> </el-main>
</el-container> </el-container>
...@@ -520,9 +599,6 @@ ...@@ -520,9 +599,6 @@
const jobResult = ref(null); const jobResult = ref(null);
const activeTab = ref('merge'); const activeTab = ref('merge');
// ── 历史 ──
const history = ref([]);
// ── 加载步骤 ── // ── 加载步骤 ──
async function loadSteps() { async function loadSteps() {
try { try {
...@@ -572,7 +648,6 @@ ...@@ -572,7 +648,6 @@
currentJob.value = r.job_id; currentJob.value = r.job_id;
subscribeLogs(r.job_id); subscribeLogs(r.job_id);
pollStatus(r.job_id); pollStatus(r.job_id);
loadHistory();
} catch (e) { } catch (e) {
ElementPlus.ElMessage.error('启动失败: ' + e); ElementPlus.ElMessage.error('启动失败: ' + e);
} finally { } finally {
...@@ -607,7 +682,6 @@ ...@@ -607,7 +682,6 @@
clearInterval(pollTimer); clearInterval(pollTimer);
await loadResult(jobId); await loadResult(jobId);
ElementPlus.ElMessage.success('分析完成'); ElementPlus.ElMessage.success('分析完成');
loadHistory();
} else if (r.status === 'failed' || r.status === 'cancelled') { } else if (r.status === 'failed' || r.status === 'cancelled') {
clearInterval(pollTimer); clearInterval(pollTimer);
ElementPlus.ElMessage.warning(`任务${r.status === 'failed' ? '失败' : '已取消'}`); ElementPlus.ElMessage.warning(`任务${r.status === 'failed' ? '失败' : '已取消'}`);
...@@ -640,24 +714,44 @@ ...@@ -640,24 +714,44 @@
window.open(url.replace(/^.*outputs/, '/api/reports'), '_blank'); window.open(url.replace(/^.*outputs/, '/api/reports'), '_blank');
} }
async function downloadHistoryReport(row, fmt) { // ── 按表汇总统计(用于 alert 标题) ──
const ext = fmt === 'docx' ? 'docx' : 'md'; const perTableStats = computed(() => {
const filename = `数据分析报告_${row.database}.${ext}`; const rows = jobResult.value?.sections?.empty_fields?.per_table || [];
window.open(`/api/reports/${row.database}/${row.run_id}/${filename}`, '_blank'); let totalHigh = 0, totalMid = 0, noComment = 0;
for (const r of rows) {
totalHigh += (r.high_count || 0);
totalMid += (r.mid_count || 0);
if (!r.table_comment) noComment += 1;
} }
return {
tablesWithIssues: rows.length,
totalHigh,
totalMid,
totalHighCells: totalHigh,
totalMidCells: totalMid,
tablesNoComment: noComment,
};
});
// ── 历史 ── // ── 空字段明细(把 per_table 中的 high+mid 摊平成单条字段列表) ──
async function loadHistory() { const flatEmptyFields = computed(() => {
try { const rows = jobResult.value?.sections?.empty_fields?.per_table || [];
const r = await fetch('/api/history?limit=50').then(r => r.json()); const out = [];
history.value = r.items; for (const t of rows) {
} catch (e) {} const tableComment = t.table_comment || "";
for (const f of (t.high_empty_fields || [])) {
out.push({ ...f, table_name: t.table_name, table_comment: tableComment });
}
for (const f of (t.mid_empty_fields || [])) {
out.push({ ...f, table_name: t.table_name, table_comment: tableComment });
} }
}
return out;
});
onMounted(() => { onMounted(() => {
loadSteps(); loadSteps();
loadStandards(); loadStandards();
loadHistory();
}); });
return { return {
...@@ -665,11 +759,10 @@ ...@@ -665,11 +759,10 @@
stepsList, standards, stepsList, standards,
currentJob, jobStatus, statusTagType, currentJob, jobStatus, statusTagType,
logs, logExpanded, showStandardsDialog, logs, logExpanded, showStandardsDialog,
jobResult, activeTab, jobResult, activeTab, perTableStats, flatEmptyFields,
history,
testConnection, startJob, cancelJob, testConnection, startJob, cancelJob,
downloadReport, downloadHistoryReport, downloadReport,
loadHistory, loadStandards, loadStandards,
}; };
} }
}; };
......
...@@ -67,7 +67,8 @@ body { ...@@ -67,7 +67,8 @@ body {
} }
.app-main { .app-main {
max-width: 1400px; width: 100%;
max-width: 1800px;
margin: 0 auto; margin: 0 auto;
padding: 20px; padding: 20px;
} }
......
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