Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
db-tools
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
yzy
db-tools
Commits
a9fe801f
Commit
a9fe801f
authored
Aug 05, 2026
by
Data Governance Dev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(ui): 治理→分析改名、国标清单移至弹窗、进度条/日志上下布局、空字段Tab改为按表汇总可展开
parent
8a88a956
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
138 additions
and
69 deletions
+138
-69
web/static/index.html
web/static/index.html
+109
-67
web/static/style.css
web/static/style.css
+29
-2
No files found.
web/static/index.html
View file @
a9fe801f
...
...
@@ -3,7 +3,7 @@
<head>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<title>
数据
治理
工具
</title>
<title>
数据
分析
工具
</title>
<!-- 前端依赖全部本地化(避免 CDN 被墙/慢) -->
<!-- Element Plus CSS -->
<link
rel=
"stylesheet"
href=
"/static/lib/element-plus/index.css"
>
...
...
@@ -23,12 +23,12 @@
<div
class=
"header-inner"
>
<div
class=
"brand"
>
<el-icon><DataAnalysis
/></el-icon>
<span
class=
"brand-title"
>
数据
治理
工具
</span>
<span
class=
"brand-title"
>
数据
分析
工具
</span>
<el-tag
size=
"small"
type=
"info"
effect=
"plain"
>
v1.0
</el-tag>
</div>
<div
class=
"header-actions"
>
<el-button
@
click=
"loadHistory"
:icon=
"Refresh"
>
历史记录
</el-button>
<el-button
@
click=
"
loadStandards
"
:icon=
"Medal"
>
国标清单
</el-button>
<el-button
@
click=
"
showStandardsDialog = true
"
:icon=
"Medal"
>
国标清单
</el-button>
</div>
</div>
</el-header>
...
...
@@ -113,7 +113,7 @@
<el-icon><CircleCheck
/></el-icon>
测试连接
</el-button>
<el-button
type=
"success"
@
click=
"startJob"
:disabled=
"!connected"
:loading=
"submitting"
>
<el-icon><VideoPlay
/></el-icon>
开始
治理
<el-icon><VideoPlay
/></el-icon>
开始
分析
</el-button>
<el-button
v-if=
"currentJob"
@
click=
"cancelJob"
type=
"danger"
plain
>
<el-icon><Close
/></el-icon>
取消任务
...
...
@@ -123,44 +123,35 @@
</el-form>
</el-card>
<!-- 步骤 2:国标清单 -->
<el-card
class=
"section-card"
shadow=
"hover"
>
<template
#header
>
<div
class=
"card-header"
>
<el-icon><Medal
/></el-icon>
<span
class=
"card-title"
>
② 已注册的国标校验标准({{ standards.length }} 项)
</span>
</div>
</template>
<el-table
:data=
"standards"
stripe
size=
"small"
>
<el-table-column
prop=
"id"
label=
"标准编号"
width=
"120"
/>
<el-table-column
prop=
"name"
label=
"标准名称"
width=
"240"
/>
<el-table-column
prop=
"description"
label=
"校验说明"
/>
<el-table-column
label=
"适用字段名"
>
<template
#default
="{
row
}"
>
<el-tag
v-for=
"f in row.applies_to_fields"
:key=
"f"
size=
"small"
type=
"info"
effect=
"plain"
style=
"margin-right: 4px"
>
{{ f }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 步骤 3:进度 + 日志 -->
<!-- 步骤 2:进度 + 日志(上下结构) -->
<el-card
v-if=
"currentJob"
class=
"section-card"
shadow=
"hover"
>
<template
#header
>
<div
class=
"card-header"
>
<el-icon><Loading
/></el-icon>
<span
class=
"card-title"
>
③ 治理
进度
</span>
<span
class=
"card-title"
>
② 分析
进度
</span>
<el-tag
:type=
"statusTagType"
>
{{ jobStatus.status }}
</el-tag>
</div>
</template>
<!-- 上:进度条 + 状态信息 -->
<div
class=
"progress-block"
>
<el-progress
:percentage=
"jobStatus.progress_pct"
:status=
"jobStatus.status === 'failed' ? 'exception' : (jobStatus.status === 'completed' ? 'success' : '')"
/>
<div
class=
"progress-info"
>
<div>
当前: {{ jobStatus.current_step_title || '—' }}
</div>
<div>
已完成: {{ jobStatus.steps_completed.length }} / {{ jobStatus.steps_planned.length }}
</div>
<div
v-if=
"jobStatus.error"
class=
"error-info"
>
错误: {{ jobStatus.error }}
</div>
</div>
<el-collapse
v-model=
"logOpen"
class=
"log-collapse"
>
<el-collapse-item
title=
"实时日志(点击展开)"
name=
"logs"
>
<div
class=
"log-container"
>
</div>
<!-- 下:实时日志(默认展开) -->
<div
class=
"log-block"
>
<div
class=
"log-header"
>
<span
class=
"log-title"
>
实时日志({{ logs.length }} 条)
</span>
<el-button
size=
"small"
text
@
click=
"logExpanded = !logExpanded"
>
{{ logExpanded ? '收起' : '展开' }}
</el-button>
</div>
<div
v-show=
"logExpanded"
class=
"log-container"
>
<div
v-for=
"(log, idx) in logs"
:key=
"idx"
:class=
"['log-line', `log-${log.level.toLowerCase()}`]"
>
<span
class=
"log-ts"
>
{{ log.ts.substring(11, 19) }}
</span>
<span
class=
"log-level"
>
{{ log.level }}
</span>
...
...
@@ -168,16 +159,15 @@
<span
class=
"log-msg"
>
{{ log.message }}
</span>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</el-card>
<!-- 步骤
4
:结果展示(多级表格) -->
<!-- 步骤
3
:结果展示(多级表格) -->
<el-card
v-if=
"jobResult"
class=
"section-card"
shadow=
"hover"
>
<template
#header
>
<div
class=
"card-header"
>
<el-icon><DataLine
/></el-icon>
<span
class=
"card-title"
>
④ 治理
结果
</span>
<span
class=
"card-title"
>
③ 分析
结果
</span>
<div>
<el-button
v-if=
"jobResult.reports.markdown"
size=
"small"
@
click=
"downloadReport('md')"
>
<el-icon><Download
/></el-icon>
Markdown
...
...
@@ -203,6 +193,10 @@
<div
class=
"kpi-value"
>
{{ jobResult.overview.high_empty_fields }}
</div>
<div
class=
"kpi-label"
>
高空字段
</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-value"
>
{{ jobResult.overview.missing_comments }}
</div>
<div
class=
"kpi-label"
>
缺注释字段
</div>
...
...
@@ -278,30 +272,57 @@
<!-- 大范围空字段 -->
<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} 个高空字段`"
type=
"info"
:closable=
"false"
/>
<el-table
:data=
"jobResult.sections.empty_fields?.summary?.high_empty_fields || []"
stripe
size=
"small"
style=
"margin-top: 12px"
>
<el-table-column
prop=
"table_name"
label=
"表名"
width=
"180"
fixed
/>
<el-table-column
prop=
"column_name"
label=
"字段名"
width=
"160"
fixed
/>
<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"
>
按表统计
</h4>
<el-table
:data=
"jobResult.sections.empty_fields?.per_table || []"
stripe
size=
"small"
>
<el-table-column
type=
"expand"
>
<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=
"注释"
width=
"160"
/>
<el-table-column
prop=
"total_rows"
label=
"总行数"
width=
"100"
sortable
/>
<el-table-column
label=
"空值数"
width=
"100"
sortable
>
<template
#default
="{
row
}"
>
{{ row.empty_count }}
</template>
<el-table-column
prop=
"column_comment"
label=
"注释"
/>
<el-table-column
prop=
"empty_count"
label=
"空值数"
width=
"100"
/>
<el-table-column
label=
"空值率"
width=
"150"
>
<template
#default
="{
row:
f
}"
>
<el-progress
:percentage=
"Math.round(f.empty_rate * 100)"
:stroke-width=
"12"
status=
"exception"
/>
</template>
</el-table-column>
</el-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=
"注释"
/>
<el-table-column
prop=
"empty_count"
label=
"空值数"
width=
"100"
/>
<el-table-column
label=
"空值率"
width=
"150"
>
<template
#default
="{
row:
f
}"
>
<el-progress
:percentage=
"Math.round(f.empty_rate * 100)"
:stroke-width=
"12"
status=
"warning"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column
label=
"空值率"
width=
"120"
sortable
>
<el-table-column
prop=
"table_name"
label=
"表名"
width=
"200"
/>
<el-table-column
prop=
"table_comment"
label=
"表注释"
/>
<el-table-column
prop=
"total_rows"
label=
"总行数"
width=
"100"
sortable
/>
<el-table-column
prop=
"high_count"
label=
"高空字段"
width=
"110"
sortable
>
<template
#default
="{
row
}"
>
<el-progress
:percentage=
"Math.round(row.empty_rate * 100)"
:stroke-width=
"12"
:status=
"row.empty_rate >= 0.8 ? 'exception' : 'warning'"
/>
<el-tag
v-if=
"row.high_count > 0"
type=
"danger"
size=
"small"
>
{{ row.high_count }}
</el-tag>
<span
v-else
class=
"muted"
>
0
</span>
</template>
</el-table-column>
<el-table-column
label=
"LLM 分类"
width=
"160"
>
<el-table-column
prop=
"mid_count"
label=
"中空字段"
width=
"110"
sortable
>
<template
#default
="{
row
}"
>
<el-tag
v-if=
"row.llm_classification"
:type=
"row.llm_classification === 'missing' ? 'danger' : 'success'"
size=
"small"
>
{{ row.llm_classification }}
</el-tag>
<span
v-else
class=
"muted"
>
—
</span>
<el-tag
v-if=
"row.mid_count > 0"
type=
"warning"
size=
"small"
>
{{ row.mid_count }}
</el-tag>
<span
v-else
class=
"muted"
>
0
</span>
</template>
</el-table-column>
<el-table-column
prop=
"llm_reasoning"
label=
"LLM 判断依据"
/>
</el-table>
</el-tab-pane>
...
...
@@ -397,7 +418,7 @@
<template
#header
>
<div
class=
"card-header"
>
<el-icon><Histogram
/></el-icon>
<span
class=
"card-title"
>
⑤ 历史治理
记录({{ history.length }} 次)
</span>
<span
class=
"card-title"
>
④ 历史分析
记录({{ history.length }} 次)
</span>
</div>
</template>
<el-table
:data=
"history"
stripe
size=
"small"
>
...
...
@@ -423,6 +444,26 @@
</el-card>
</el-main>
</el-container>
<!-- 国标清单对话框 -->
<el-dialog
v-model=
"showStandardsDialog"
title=
"已注册的国标校验标准"
width=
"700px"
>
<div
v-if=
"standards.length === 0"
style=
"text-align: center; padding: 20px; color: #909399;"
>
国标清单为空,
<el-link
type=
"primary"
@
click=
"loadStandards"
>
点击刷新
</el-link>
</div>
<div
v-else
class=
"standards-list"
>
<div
v-for=
"(s, idx) in standards"
:key=
"s.id"
class=
"standard-item"
style=
"margin-bottom: 16px; padding: 12px; background: #fafafa; border-radius: 6px;"
>
<div
class=
"standard-header"
style=
"display: flex; align-items: center; gap: 8px; margin-bottom: 6px;"
>
<el-tag
size=
"small"
type=
"warning"
effect=
"dark"
>
{{ s.id }}
</el-tag>
<span
class=
"standard-name"
style=
"font-weight: 600;"
>
{{ s.name }}
</span>
</div>
<div
class=
"standard-desc"
style=
"font-size: 13px; color: #606266; margin-bottom: 8px;"
>
{{ s.description }}
</div>
<div
class=
"standard-fields"
>
<span
style=
"font-size: 12px; color: #909399;"
>
适用字段:
</span>
<el-tag
v-for=
"f in s.applies_to_fields"
:key=
"f"
size=
"small"
type=
"info"
effect=
"plain"
style=
"margin-right: 4px;"
>
{{ f }}
</el-tag>
</div>
</div>
</div>
</el-dialog>
</div>
<script>
...
...
@@ -465,7 +506,8 @@
error
:
null
,
});
const
logs
=
ref
([]);
const
logOpen
=
ref
(
false
);
const
logExpanded
=
ref
(
true
);
const
showStandardsDialog
=
ref
(
false
);
let
logEventSource
=
null
;
let
pollTimer
=
null
;
...
...
@@ -516,7 +558,7 @@
}
}
// ── 启动
治理
──
// ── 启动
分析
──
async
function
startJob
()
{
submitting
.
value
=
true
;
jobResult
.
value
=
null
;
...
...
@@ -564,7 +606,7 @@
if
(
r
.
status
===
'
completed
'
)
{
clearInterval
(
pollTimer
);
await
loadResult
(
jobId
);
ElementPlus
.
ElMessage
.
success
(
'
治理
完成
'
);
ElementPlus
.
ElMessage
.
success
(
'
分析
完成
'
);
loadHistory
();
}
else
if
(
r
.
status
===
'
failed
'
||
r
.
status
===
'
cancelled
'
)
{
clearInterval
(
pollTimer
);
...
...
@@ -600,7 +642,7 @@
async
function
downloadHistoryReport
(
row
,
fmt
)
{
const
ext
=
fmt
===
'
docx
'
?
'
docx
'
:
'
md
'
;
const
filename
=
`数据
治理
报告_
${
row
.
database
}
.
${
ext
}
`
;
const
filename
=
`数据
分析
报告_
${
row
.
database
}
.
${
ext
}
`
;
window
.
open
(
`/api/reports/
${
row
.
database
}
/
${
row
.
run_id
}
/
${
filename
}
`
,
'
_blank
'
);
}
...
...
@@ -622,7 +664,7 @@
form
,
testing
,
submitting
,
connected
,
testResult
,
stepsList
,
standards
,
currentJob
,
jobStatus
,
statusTagType
,
logs
,
log
Open
,
logs
,
log
Expanded
,
showStandardsDialog
,
jobResult
,
activeTab
,
history
,
testConnection
,
startJob
,
cancelJob
,
...
...
web/static/style.css
View file @
a9fe801f
/* 数据
治理
工具样式 */
/* 数据
分析
工具样式 */
*
{
box-sizing
:
border-box
;
...
...
@@ -100,7 +100,14 @@ body {
margin-left
:
8px
;
}
/* ── 进度条 ── */
/* ── 进度条(上) ── */
.progress-block
{
margin-bottom
:
20px
;
}
.progress-block
.el-progress
{
margin-bottom
:
8px
;
}
.progress-info
{
display
:
flex
;
gap
:
24px
;
...
...
@@ -114,6 +121,25 @@ body {
font-weight
:
500
;
}
/* ── 日志区(下) ── */
.log-block
{
border-top
:
1px
solid
#ebeef5
;
padding-top
:
12px
;
}
.log-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
8px
;
}
.log-title
{
font-size
:
13px
;
font-weight
:
600
;
color
:
#606266
;
}
.log-collapse
{
margin-top
:
12px
;
}
...
...
@@ -189,6 +215,7 @@ body {
.kpi-card.kpi-tables
{
border-left-color
:
#1890ff
;
}
.kpi-card.kpi-fields
{
border-left-color
:
#13c2c2
;
}
.kpi-card.kpi-empty
{
border-left-color
:
#faad14
;
}
.kpi-card.kpi-empty-mid
{
border-left-color
:
#e6a23c
;
}
.kpi-card.kpi-missing
{
border-left-color
:
#fa8c16
;
}
.kpi-card.kpi-length
{
border-left-color
:
#f5222d
;
}
.kpi-card.kpi-std
{
border-left-color
:
#eb2f96
;
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment