Commit f05e53ba authored by Data Governance Dev's avatar Data Governance Dev

feat(web3): 结果表按任务配置「默认展示」过滤列

任务配置里某字段「默认展示」未勾选 → 结果表不展示该列。
- DataQualityView.vue: resultFields 映射时带上 showDefault
- ResultTable.vue: allColumns 加 filter((c) => c.showDefault !== false)
- 兜底 `!== false`:未传 showDefault 的列仍展示,向后兼容 mock
- 被隐藏列的错误信息仍通过「备注」列汇总展示,不丢信息

测试:
- test_show_default_filter.py(playwright 端到端验证)
- test_edit_network.py(诊断编辑按钮触发的 API)
parent d0416a21
......@@ -139,8 +139,10 @@ const props = defineProps({
const emit = defineEmits(['page-change'])
// 业务列 + 自动追加的「备注」列
// 任务配置里「默认展示」未勾选的列(showDefault === false)→ 不进结果表
// 兜底:`showDefault !== false` —— 未传 showDefault 的列(mock / 老调用方)全部展示,向后兼容
const allColumns = computed(() => [
...props.columns,
...props.columns.filter((c) => c.showDefault !== false),
{ key: '__reason', cn: '备注' },
])
......
......@@ -168,6 +168,7 @@ async function onQuery() {
cn: f.field_key, // 后端没存中文名,先用英文 key 占位
en: '',
rules: f.rules || 0, // 数字 → 表头显示「⚠ N规则」
showDefault: f.show_default !== false, // 任务配置「默认展示」未勾选 → 结果表不展示该列
}))
lastResult.value = r
if (r.scanned === 0) {
......
"""
诊断:点击「编辑」按钮触发的 API 调用。
记录所有 network 请求 + 弹窗打开后的 form 状态。
"""
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from playwright.sync_api import sync_playwright
def main():
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
executable_path=r"C:\Users\duanqi\scoop\apps\googlechrome\151.0.7922.109\chrome.exe",
)
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
page = ctx.new_page()
requests = []
page.on("request", lambda req: requests.append({
"method": req.method,
"url": req.url,
"post_data": req.post_data,
}))
responses = []
page.on("response", lambda res: responses.append({
"status": res.status,
"url": res.url,
}))
# 1. 打开任务配置页(需要先确保有任务,列表才会渲染)
page.goto("http://localhost:5175/#/config", wait_until="networkidle")
page.wait_for_selector(".config-list tbody tr", timeout=10_000)
# 清空之前收集的请求(只要点编辑按钮之后的)
requests.clear()
responses.clear()
# 2. 点第一个任务的「编辑」按钮
page.locator(".config-list tbody tr").first.locator("button.btn-text").first.click()
page.wait_for_timeout(2000) # 等异步 getTask 完成
# 3. 报告
print("=== 点编辑后触发的请求 ===")
for i, r in enumerate(requests):
print(f" [{i+1}] {r['method']} {r['url']}")
if r['post_data']:
print(f" body: {r['post_data'][:200]}")
print("\n=== 响应 ===")
for r in responses:
print(f" {r['status']} {r['url']}")
# 4. 检查弹窗内 form 状态
form_state = page.evaluate("""() => {
const inputs = document.querySelectorAll('.modal .text-input, .modal .select-input, .modal textarea')
const out = []
inputs.forEach((el) => {
const prev = el.closest('.form-row')
const label = prev?.querySelector('.form-label')?.textContent?.trim() || '?'
out.push({ label: label.slice(0, 20), value: el.value || '' })
})
return out
}""")
print("\n=== 弹窗 form 状态 ===")
for f in form_state:
print(f" {f['label']:18} = {f['value']!r}")
page.screenshot(path="web3/tests/edit_network.png", full_page=True)
browser.close()
if __name__ == "__main__":
main()
\ No newline at end of file
"""
验证:结果表列过滤 —— 任务配置「默认展示」未勾选的字段不展示。
直接通过 Vue setupState 注入 resultFields,绕开 runQuery 网络(不依赖真实 DB)。
期望:
- 6 个字段里,id_card_no / contact_phone showDefault=false → 不渲染列头
- 实际渲染列头 = 4 个(user_id / person_name / worker_type / team_name)+ 1 个「备注」= 5 个
"""
import sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from playwright.sync_api import sync_playwright
INJECT_JS = r"""
() => {
// 抓所有 vnode 的 setupState,找有 resultFields 的那个
const root = document.querySelector('#app').__vue_app__;
let target = null;
function walk(vnode, depth) {
if (!vnode || depth > 12) return;
if (vnode.component) {
const s = vnode.component.setupState;
if (s && Array.isArray(s.resultFields) && 'rows' in s) {
target = s;
return;
}
walk(vnode.component.subTree, depth + 1);
}
if (Array.isArray(vnode.children)) {
vnode.children.forEach((c) => walk(c, depth + 1));
}
}
walk(root._instance.subTree, 0);
if (!target) return { error: 'no setupState with resultFields found' };
target.resultFields = [
{ key: 'user_id', cn: 'user_id', en: '', rules: 0, showDefault: true },
{ key: 'person_name', cn: 'person_name', en: '', rules: 0, showDefault: true },
{ key: 'id_card_no', cn: 'id_card_no', en: '', rules: 1, showDefault: false },
{ key: 'contact_phone', cn: 'contact_phone', en: '', rules: 0, showDefault: false },
{ key: 'worker_type', cn: 'worker_type', en: '', rules: 0, showDefault: true },
{ key: 'team_name', cn: 'team_name', en: '', rules: 0, showDefault: true },
];
target.rows = [
{ user_id: 'U001', person_name: '张三', id_card_no: '11010119900101123X',
contact_phone: '13800138000', worker_type: '木工', team_name: 'A组',
errorCells: [], issues: [], checked: false, __row_index: 0 },
{ user_id: 'U002', person_name: '李四', id_card_no: 'invalid',
contact_phone: '13900139000', worker_type: '电工', team_name: 'B组',
errorCells: ['id_card_no'],
issues: [{ field: 'id_card_no', desc: '18位身份证' }],
checked: false, __row_index: 1 },
];
target.lastResult = { scanned: 2, bad_rows: 1, total: 2 };
return { ok: true, injected_count: target.resultFields.length };
}
"""
HEADERS_JS = r"""
() => {
const ths = document.querySelectorAll('.result-table .dq-table thead th');
return Array.from(ths).map((th) => th.textContent.trim().replace(/\s+/g, ' '));
}
"""
ROWS_JS = r"""
() => {
const trs = document.querySelectorAll('.result-table .dq-table tbody tr');
return Array.from(trs).slice(0, 3).map((tr) =>
Array.from(tr.querySelectorAll('td')).map((td) =>
td.textContent.trim().replace(/\s+/g, ' ').slice(0, 30)
)
);
}
"""
def main():
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
executable_path=r"C:\Users\duanqi\scoop\apps\googlechrome\151.0.7922.109\chrome.exe",
)
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
page = ctx.new_page()
page.goto("http://localhost:5175/#/quality/id-card", wait_until="networkidle")
page.wait_for_selector(".q-card", timeout=10_000)
injected = page.evaluate(INJECT_JS)
print("inject result:", injected)
page.wait_for_timeout(400)
headers = page.evaluate(HEADERS_JS)
print(f"\nrendered columns ({len(headers)}):")
for h in headers:
print(f" - {h}")
rows = page.evaluate(ROWS_JS)
print(f"\nfirst row cells ({len(rows[0]) if rows else 0}):")
for r in rows:
print(f" {r}")
visible_keys = [h.split()[0] for h in headers if '备注' not in h]
hidden_must_not_appear = ['id_card_no', 'contact_phone']
ok_flag = all((k not in visible_keys) for k in hidden_must_not_appear)
print(f"\nvisible keys: {visible_keys}")
print(f"hidden not in visible: {ok_flag}")
page.screenshot(path="web3/tests/show_default_filter.png", full_page=True)
print("\nscreenshot: web3/tests/show_default_filter.png")
if ok_flag and len(headers) == 5:
print("\n[OK] show_default=false columns are correctly hidden")
else:
print(f"\n[FAIL] expected 5 columns (4 business + 1 reason), got {len(headers)}; ok_flag={ok_flag}")
print(f" full headers: {headers}")
browser.close()
if __name__ == "__main__":
main()
\ 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