@topmindspace/tms-skills 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -134
- package/README.md +1 -1
- package/bin/tms-skills.js +160 -160
- package/package.json +48 -48
- package/top-ppt-html/README.md +254 -254
- package/top-ppt-html/SKILL.md +111 -111
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
- package/top-ppt-html/assets/pptx-export.js +1944 -1944
- package/top-ppt-html/assets/style-gallery.html +589 -589
- package/top-ppt-html/assets/templates/architecture.html +3728 -3728
- package/top-ppt-html/assets/templates/engine.css +840 -840
- package/top-ppt-html/assets/templates/presentation.html +3738 -3738
- package/top-ppt-html/assets/templates/research.html +4017 -4017
- package/top-ppt-html/assets/templates/ui.js +520 -520
- package/top-ppt-html/evals/prompts.csv +15 -15
- package/top-ppt-html/evals/rubric.schema.json +25 -25
- package/top-ppt-html/evals/run_evals.py +220 -220
- package/top-ppt-html/evals/trace.example.json +16 -16
- package/top-ppt-html/package-lock.json +207 -0
- package/top-ppt-html/package.json +35 -35
- package/top-ppt-html/references/charts-basic.md +624 -624
- package/top-ppt-html/references/charts-discipline.md +108 -108
- package/top-ppt-html/references/charts-extended.md +482 -482
- package/top-ppt-html/references/charts.md +28 -28
- package/top-ppt-html/references/components-atoms.md +624 -624
- package/top-ppt-html/references/components.md +30 -30
- package/top-ppt-html/references/content-rules.md +490 -490
- package/top-ppt-html/references/design-system-engine.md +235 -235
- package/top-ppt-html/references/design-system.md +478 -478
- package/top-ppt-html/references/failure-modes.md +214 -214
- package/top-ppt-html/references/high-fidelity.md +127 -127
- package/top-ppt-html/references/icons.md +397 -397
- package/top-ppt-html/references/industry-benchmark.md +105 -105
- package/top-ppt-html/references/infographics-stats.md +308 -308
- package/top-ppt-html/references/infographics-structure.md +226 -226
- package/top-ppt-html/references/infographics.md +43 -43
- package/top-ppt-html/references/layout-grammar.md +315 -315
- package/top-ppt-html/references/layouts-architecture.md +108 -108
- package/top-ppt-html/references/layouts-combo.md +600 -600
- package/top-ppt-html/references/layouts-research.md +160 -160
- package/top-ppt-html/references/modes.md +254 -254
- package/top-ppt-html/references/outline-design.md +275 -275
- package/top-ppt-html/references/playbook.md +266 -266
- package/top-ppt-html/references/pptx-export.md +209 -209
- package/top-ppt-html/references/reform-plan.md +252 -252
- package/top-ppt-html/references/styles.md +370 -370
- package/top-ppt-html/references/tech-design.md +138 -138
- package/top-ppt-html/scripts/audit_css.py +109 -109
- package/top-ppt-html/scripts/audit_docs.py +176 -176
- package/top-ppt-html/scripts/audit_skill.py +220 -220
- package/top-ppt-html/scripts/audit_styles.py +351 -351
- package/top-ppt-html/scripts/build_examples.py +2276 -2276
- package/top-ppt-html/scripts/build_pptx.js +2380 -2380
- package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
- package/top-ppt-html/scripts/checks_html.py +127 -127
- package/top-ppt-html/scripts/cross_verify.py +294 -294
- package/top-ppt-html/scripts/env_probe.py +158 -158
- package/top-ppt-html/scripts/extract_model.py +210 -210
- package/top-ppt-html/scripts/extract_snippet.py +374 -374
- package/top-ppt-html/scripts/gen_channel_a.js +214 -214
- package/top-ppt-html/scripts/layout-constants.json +3377 -3377
- package/top-ppt-html/scripts/layout_slots.json +830 -830
- package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
- package/top-ppt-html/scripts/measure_height.py +178 -178
- package/top-ppt-html/scripts/model-schema.json +547 -547
- package/top-ppt-html/scripts/negative_tests.py +307 -307
- package/top-ppt-html/scripts/package_skill.py +291 -291
- package/top-ppt-html/scripts/prepare_images.py +341 -341
- package/top-ppt-html/scripts/probe_image_export.py +188 -188
- package/top-ppt-html/scripts/quality_gate.py +301 -301
- package/top-ppt-html/scripts/regression.py +308 -308
- package/top-ppt-html/scripts/render_compare.py +275 -275
- package/top-ppt-html/scripts/render_from_model.py +698 -698
- package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
- package/top-ppt-html/scripts/section-file-map.json +104 -104
- package/top-ppt-html/scripts/sync_runtime.py +662 -662
- package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
- package/top-ppt-html/scripts/validate_report.py +1456 -1456
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""TopPPT HTML · HTML 页高与容器裁切真值测量(可选 · 需 playwright)
|
|
4
|
-
|
|
5
|
-
静态估算(validate_report 页高溢出)是交付前硬门禁的保守近似,但它看不到
|
|
6
|
-
真实布局:「表格被半幅容器横向裁掉一列」这类缺陷在 strict 全绿时照样发生。
|
|
7
|
-
本脚本用无头浏览器量真实布局高度与容器裁切,供深度交付 / 排版争议时对照。
|
|
8
|
-
|
|
9
|
-
用法:
|
|
10
|
-
python scripts/measure_height.py report.html
|
|
11
|
-
python scripts/measure_height.py report.html --json
|
|
12
|
-
python scripts/measure_height.py report.html --width 1920 --height 1080
|
|
13
|
-
|
|
14
|
-
缺 playwright / chromium 时打印指引并 exit 0(不阻断交付链)。
|
|
15
|
-
"""
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
import argparse
|
|
19
|
-
import json
|
|
20
|
-
import sys
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
25
|
-
except Exception:
|
|
26
|
-
pass
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def main() -> int:
|
|
30
|
-
ap = argparse.ArgumentParser(description='HTML 页高真值测量(Playwright)')
|
|
31
|
-
ap.add_argument('html', help='报告 HTML 路径')
|
|
32
|
-
ap.add_argument('--width', type=int, default=1920)
|
|
33
|
-
ap.add_argument('--height', type=int, default=1080)
|
|
34
|
-
ap.add_argument('--json', action='store_true')
|
|
35
|
-
ap.add_argument('--tolerance-px', type=int, default=8,
|
|
36
|
-
help='band 高度相对视口的容差(超过则记 overflow)')
|
|
37
|
-
args = ap.parse_args()
|
|
38
|
-
|
|
39
|
-
html = Path(args.html)
|
|
40
|
-
if not html.exists():
|
|
41
|
-
print(f'错误:HTML 不存在 {html}')
|
|
42
|
-
return 2
|
|
43
|
-
|
|
44
|
-
try:
|
|
45
|
-
from playwright.sync_api import sync_playwright
|
|
46
|
-
except ImportError:
|
|
47
|
-
# 托管 Python 可能未装 playwright;回落到本机已装 playwright 的解释器(不阻断)
|
|
48
|
-
import shutil
|
|
49
|
-
import subprocess
|
|
50
|
-
candidates = []
|
|
51
|
-
for name in ('python', 'python3', 'py'):
|
|
52
|
-
exe = shutil.which(name)
|
|
53
|
-
if exe:
|
|
54
|
-
candidates.append(exe)
|
|
55
|
-
# 常见用户安装路径不写死绝对盘符/用户名;仅用 PATH / py launcher
|
|
56
|
-
for extra in (
|
|
57
|
-
r'py',
|
|
58
|
-
):
|
|
59
|
-
if Path(extra).exists() or shutil.which(extra):
|
|
60
|
-
candidates.append(extra)
|
|
61
|
-
alt = None
|
|
62
|
-
for exe in candidates:
|
|
63
|
-
try:
|
|
64
|
-
probe = subprocess.run(
|
|
65
|
-
[exe, '-c', 'import playwright; print(1)'],
|
|
66
|
-
capture_output=True, text=True, timeout=8)
|
|
67
|
-
if probe.returncode == 0 and '1' in (probe.stdout or ''):
|
|
68
|
-
alt = exe
|
|
69
|
-
break
|
|
70
|
-
except Exception:
|
|
71
|
-
continue
|
|
72
|
-
if not alt:
|
|
73
|
-
print('跳过:未安装 playwright(pip install playwright && playwright install chromium)')
|
|
74
|
-
print('静态估算仍由 validate_report.py --strict 把关。')
|
|
75
|
-
return 0
|
|
76
|
-
# 用有 playwright 的解释器重跑本脚本(避免在无 playwright 的解释器里空转)
|
|
77
|
-
import subprocess as sp
|
|
78
|
-
cmd = [alt, str(Path(__file__).resolve()), str(html),
|
|
79
|
-
'--width', str(args.width), '--height', str(args.height),
|
|
80
|
-
'--tolerance-px', str(args.tolerance_px)]
|
|
81
|
-
if args.json:
|
|
82
|
-
cmd.append('--json')
|
|
83
|
-
raise SystemExit(sp.call(cmd))
|
|
84
|
-
|
|
85
|
-
url = html.resolve().as_uri()
|
|
86
|
-
results = []
|
|
87
|
-
with sync_playwright() as p:
|
|
88
|
-
try:
|
|
89
|
-
browser = p.chromium.launch(headless=True)
|
|
90
|
-
except Exception as e:
|
|
91
|
-
print(f'跳过:无法启动 chromium({e})。请执行 playwright install chromium。')
|
|
92
|
-
return 0
|
|
93
|
-
page = browser.new_page(viewport={'width': args.width, 'height': args.height})
|
|
94
|
-
page.goto(url, wait_until='load', timeout=60000)
|
|
95
|
-
# 关掉工具栏对页高的影响:尽量贴近演示全屏内容区
|
|
96
|
-
page.evaluate("""() => {
|
|
97
|
-
const bar = document.querySelector('.bar');
|
|
98
|
-
if (bar) bar.style.display = 'none';
|
|
99
|
-
document.documentElement.style.setProperty('--bar-h', '0px');
|
|
100
|
-
}""")
|
|
101
|
-
page.wait_for_timeout(200)
|
|
102
|
-
bands = page.evaluate("""(tol) => {
|
|
103
|
-
const vh = window.innerHeight;
|
|
104
|
-
const nodes = [...document.querySelectorAll('section.band')];
|
|
105
|
-
return nodes.map((el, i) => {
|
|
106
|
-
const r = el.getBoundingClientRect();
|
|
107
|
-
const h = Math.round(el.offsetHeight);
|
|
108
|
-
const flow = el.classList.contains('band--flow');
|
|
109
|
-
const id = el.id || ('band-' + (i + 1));
|
|
110
|
-
return {
|
|
111
|
-
index: i + 1,
|
|
112
|
-
id,
|
|
113
|
-
height: h,
|
|
114
|
-
viewport: vh,
|
|
115
|
-
overflow: !flow && h > vh + tol,
|
|
116
|
-
flow,
|
|
117
|
-
title: (el.querySelector('.shead__title, h1, h2')?.textContent || '').trim().slice(0, 40),
|
|
118
|
-
};
|
|
119
|
-
});
|
|
120
|
-
}""", args.tolerance_px)
|
|
121
|
-
# 容器级裁切真值:静态估算只看页高,看不到「表格/卡片被归属容器横向或纵向裁掉」——
|
|
122
|
-
# 裁切比压缩更糟(数据直接不可见),且 strict 全绿时依然可能发生。
|
|
123
|
-
clipped = page.evaluate("""() => {
|
|
124
|
-
const sel = '.card,.panel,.metric,.sowhat,.exhibit,.kpi,.note,.tbl-wrap,td,th';
|
|
125
|
-
const out = [];
|
|
126
|
-
document.querySelectorAll(sel).forEach(el => {
|
|
127
|
-
if (getComputedStyle(el).overflow === 'visible') return;
|
|
128
|
-
const dw = el.scrollWidth - el.clientWidth;
|
|
129
|
-
const dh = el.scrollHeight - el.clientHeight;
|
|
130
|
-
if (dw > 3 || dh > 3) {
|
|
131
|
-
const band = el.closest('section.band');
|
|
132
|
-
out.push({
|
|
133
|
-
container: (el.className || el.tagName).toString().slice(0, 30),
|
|
134
|
-
page: band ? ([...document.querySelectorAll('section.band')].indexOf(band) + 1) : 0,
|
|
135
|
-
overflowX: dw > 3 ? dw : 0,
|
|
136
|
-
overflowY: dh > 3 ? dh : 0,
|
|
137
|
-
text: (el.textContent || '').trim().slice(0, 24),
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
return out;
|
|
142
|
-
}""")
|
|
143
|
-
browser.close()
|
|
144
|
-
|
|
145
|
-
over = [b for b in bands if b['overflow']]
|
|
146
|
-
payload = {
|
|
147
|
-
'file': str(html),
|
|
148
|
-
'viewport': f'{args.width}x{args.height}',
|
|
149
|
-
'bands': len(bands),
|
|
150
|
-
'overflow_count': len(over),
|
|
151
|
-
'overflow': over,
|
|
152
|
-
'clipped_count': len(clipped),
|
|
153
|
-
'clipped': clipped,
|
|
154
|
-
'details': bands,
|
|
155
|
-
}
|
|
156
|
-
if args.json:
|
|
157
|
-
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
158
|
-
else:
|
|
159
|
-
print(f'页高真值测量 · {html.name} @ {args.width}×{args.height}')
|
|
160
|
-
print(f' band 数 {len(bands)} · 超一屏 {len(over)} · 容器裁切 {len(clipped)}')
|
|
161
|
-
for b in bands:
|
|
162
|
-
mark = '✗' if b['overflow'] else ('·' if b['flow'] else '✓')
|
|
163
|
-
print(f' {mark} 第{b["index"]}页 {b["id"]:16} {b["height"]:4}px {b["title"]}')
|
|
164
|
-
if clipped:
|
|
165
|
-
print(' ── 容器级裁切(内容越过归属容器,铁律 11)──')
|
|
166
|
-
for c in clipped[:8]:
|
|
167
|
-
axis = f'横向 {c["overflowX"]}px' if c['overflowX'] else f'纵向 {c["overflowY"]}px'
|
|
168
|
-
print(f' ✗ 第{c["page"]}页 {c["container"]} {axis} 「{c["text"]}」')
|
|
169
|
-
print(' 处理:换组合版式/减列/缩表 → 调容器宽度 → 拆页;不要靠容器滚动条遮掩')
|
|
170
|
-
if over:
|
|
171
|
-
print(' 处理:列表化/精炼 → 压缩间距 → 多列 → 拆页;长结构页加 band--flow')
|
|
172
|
-
if not over and not clipped:
|
|
173
|
-
print(' 结论:全部固定页高 band 未超视口,且无容器级裁切')
|
|
174
|
-
return 0 if not over and not clipped else 1
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if __name__ == '__main__':
|
|
178
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""TopPPT HTML · HTML 页高与容器裁切真值测量(可选 · 需 playwright)
|
|
4
|
+
|
|
5
|
+
静态估算(validate_report 页高溢出)是交付前硬门禁的保守近似,但它看不到
|
|
6
|
+
真实布局:「表格被半幅容器横向裁掉一列」这类缺陷在 strict 全绿时照样发生。
|
|
7
|
+
本脚本用无头浏览器量真实布局高度与容器裁切,供深度交付 / 排版争议时对照。
|
|
8
|
+
|
|
9
|
+
用法:
|
|
10
|
+
python scripts/measure_height.py report.html
|
|
11
|
+
python scripts/measure_height.py report.html --json
|
|
12
|
+
python scripts/measure_height.py report.html --width 1920 --height 1080
|
|
13
|
+
|
|
14
|
+
缺 playwright / chromium 时打印指引并 exit 0(不阻断交付链)。
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main() -> int:
|
|
30
|
+
ap = argparse.ArgumentParser(description='HTML 页高真值测量(Playwright)')
|
|
31
|
+
ap.add_argument('html', help='报告 HTML 路径')
|
|
32
|
+
ap.add_argument('--width', type=int, default=1920)
|
|
33
|
+
ap.add_argument('--height', type=int, default=1080)
|
|
34
|
+
ap.add_argument('--json', action='store_true')
|
|
35
|
+
ap.add_argument('--tolerance-px', type=int, default=8,
|
|
36
|
+
help='band 高度相对视口的容差(超过则记 overflow)')
|
|
37
|
+
args = ap.parse_args()
|
|
38
|
+
|
|
39
|
+
html = Path(args.html)
|
|
40
|
+
if not html.exists():
|
|
41
|
+
print(f'错误:HTML 不存在 {html}')
|
|
42
|
+
return 2
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
from playwright.sync_api import sync_playwright
|
|
46
|
+
except ImportError:
|
|
47
|
+
# 托管 Python 可能未装 playwright;回落到本机已装 playwright 的解释器(不阻断)
|
|
48
|
+
import shutil
|
|
49
|
+
import subprocess
|
|
50
|
+
candidates = []
|
|
51
|
+
for name in ('python', 'python3', 'py'):
|
|
52
|
+
exe = shutil.which(name)
|
|
53
|
+
if exe:
|
|
54
|
+
candidates.append(exe)
|
|
55
|
+
# 常见用户安装路径不写死绝对盘符/用户名;仅用 PATH / py launcher
|
|
56
|
+
for extra in (
|
|
57
|
+
r'py',
|
|
58
|
+
):
|
|
59
|
+
if Path(extra).exists() or shutil.which(extra):
|
|
60
|
+
candidates.append(extra)
|
|
61
|
+
alt = None
|
|
62
|
+
for exe in candidates:
|
|
63
|
+
try:
|
|
64
|
+
probe = subprocess.run(
|
|
65
|
+
[exe, '-c', 'import playwright; print(1)'],
|
|
66
|
+
capture_output=True, text=True, timeout=8)
|
|
67
|
+
if probe.returncode == 0 and '1' in (probe.stdout or ''):
|
|
68
|
+
alt = exe
|
|
69
|
+
break
|
|
70
|
+
except Exception:
|
|
71
|
+
continue
|
|
72
|
+
if not alt:
|
|
73
|
+
print('跳过:未安装 playwright(pip install playwright && playwright install chromium)')
|
|
74
|
+
print('静态估算仍由 validate_report.py --strict 把关。')
|
|
75
|
+
return 0
|
|
76
|
+
# 用有 playwright 的解释器重跑本脚本(避免在无 playwright 的解释器里空转)
|
|
77
|
+
import subprocess as sp
|
|
78
|
+
cmd = [alt, str(Path(__file__).resolve()), str(html),
|
|
79
|
+
'--width', str(args.width), '--height', str(args.height),
|
|
80
|
+
'--tolerance-px', str(args.tolerance_px)]
|
|
81
|
+
if args.json:
|
|
82
|
+
cmd.append('--json')
|
|
83
|
+
raise SystemExit(sp.call(cmd))
|
|
84
|
+
|
|
85
|
+
url = html.resolve().as_uri()
|
|
86
|
+
results = []
|
|
87
|
+
with sync_playwright() as p:
|
|
88
|
+
try:
|
|
89
|
+
browser = p.chromium.launch(headless=True)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f'跳过:无法启动 chromium({e})。请执行 playwright install chromium。')
|
|
92
|
+
return 0
|
|
93
|
+
page = browser.new_page(viewport={'width': args.width, 'height': args.height})
|
|
94
|
+
page.goto(url, wait_until='load', timeout=60000)
|
|
95
|
+
# 关掉工具栏对页高的影响:尽量贴近演示全屏内容区
|
|
96
|
+
page.evaluate("""() => {
|
|
97
|
+
const bar = document.querySelector('.bar');
|
|
98
|
+
if (bar) bar.style.display = 'none';
|
|
99
|
+
document.documentElement.style.setProperty('--bar-h', '0px');
|
|
100
|
+
}""")
|
|
101
|
+
page.wait_for_timeout(200)
|
|
102
|
+
bands = page.evaluate("""(tol) => {
|
|
103
|
+
const vh = window.innerHeight;
|
|
104
|
+
const nodes = [...document.querySelectorAll('section.band')];
|
|
105
|
+
return nodes.map((el, i) => {
|
|
106
|
+
const r = el.getBoundingClientRect();
|
|
107
|
+
const h = Math.round(el.offsetHeight);
|
|
108
|
+
const flow = el.classList.contains('band--flow');
|
|
109
|
+
const id = el.id || ('band-' + (i + 1));
|
|
110
|
+
return {
|
|
111
|
+
index: i + 1,
|
|
112
|
+
id,
|
|
113
|
+
height: h,
|
|
114
|
+
viewport: vh,
|
|
115
|
+
overflow: !flow && h > vh + tol,
|
|
116
|
+
flow,
|
|
117
|
+
title: (el.querySelector('.shead__title, h1, h2')?.textContent || '').trim().slice(0, 40),
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}""", args.tolerance_px)
|
|
121
|
+
# 容器级裁切真值:静态估算只看页高,看不到「表格/卡片被归属容器横向或纵向裁掉」——
|
|
122
|
+
# 裁切比压缩更糟(数据直接不可见),且 strict 全绿时依然可能发生。
|
|
123
|
+
clipped = page.evaluate("""() => {
|
|
124
|
+
const sel = '.card,.panel,.metric,.sowhat,.exhibit,.kpi,.note,.tbl-wrap,td,th';
|
|
125
|
+
const out = [];
|
|
126
|
+
document.querySelectorAll(sel).forEach(el => {
|
|
127
|
+
if (getComputedStyle(el).overflow === 'visible') return;
|
|
128
|
+
const dw = el.scrollWidth - el.clientWidth;
|
|
129
|
+
const dh = el.scrollHeight - el.clientHeight;
|
|
130
|
+
if (dw > 3 || dh > 3) {
|
|
131
|
+
const band = el.closest('section.band');
|
|
132
|
+
out.push({
|
|
133
|
+
container: (el.className || el.tagName).toString().slice(0, 30),
|
|
134
|
+
page: band ? ([...document.querySelectorAll('section.band')].indexOf(band) + 1) : 0,
|
|
135
|
+
overflowX: dw > 3 ? dw : 0,
|
|
136
|
+
overflowY: dh > 3 ? dh : 0,
|
|
137
|
+
text: (el.textContent || '').trim().slice(0, 24),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return out;
|
|
142
|
+
}""")
|
|
143
|
+
browser.close()
|
|
144
|
+
|
|
145
|
+
over = [b for b in bands if b['overflow']]
|
|
146
|
+
payload = {
|
|
147
|
+
'file': str(html),
|
|
148
|
+
'viewport': f'{args.width}x{args.height}',
|
|
149
|
+
'bands': len(bands),
|
|
150
|
+
'overflow_count': len(over),
|
|
151
|
+
'overflow': over,
|
|
152
|
+
'clipped_count': len(clipped),
|
|
153
|
+
'clipped': clipped,
|
|
154
|
+
'details': bands,
|
|
155
|
+
}
|
|
156
|
+
if args.json:
|
|
157
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
158
|
+
else:
|
|
159
|
+
print(f'页高真值测量 · {html.name} @ {args.width}×{args.height}')
|
|
160
|
+
print(f' band 数 {len(bands)} · 超一屏 {len(over)} · 容器裁切 {len(clipped)}')
|
|
161
|
+
for b in bands:
|
|
162
|
+
mark = '✗' if b['overflow'] else ('·' if b['flow'] else '✓')
|
|
163
|
+
print(f' {mark} 第{b["index"]}页 {b["id"]:16} {b["height"]:4}px {b["title"]}')
|
|
164
|
+
if clipped:
|
|
165
|
+
print(' ── 容器级裁切(内容越过归属容器,铁律 11)──')
|
|
166
|
+
for c in clipped[:8]:
|
|
167
|
+
axis = f'横向 {c["overflowX"]}px' if c['overflowX'] else f'纵向 {c["overflowY"]}px'
|
|
168
|
+
print(f' ✗ 第{c["page"]}页 {c["container"]} {axis} 「{c["text"]}」')
|
|
169
|
+
print(' 处理:换组合版式/减列/缩表 → 调容器宽度 → 拆页;不要靠容器滚动条遮掩')
|
|
170
|
+
if over:
|
|
171
|
+
print(' 处理:列表化/精炼 → 压缩间距 → 多列 → 拆页;长结构页加 band--flow')
|
|
172
|
+
if not over and not clipped:
|
|
173
|
+
print(' 结论:全部固定页高 band 未超视口,且无容器级裁切')
|
|
174
|
+
return 0 if not over and not clipped else 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == '__main__':
|
|
178
|
+
sys.exit(main())
|