Files
Neo-ZQYY/scripts/ops/extract_h5_styles.py

213 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
提取 board-finance H5 原型「经营一览」板块的精确 computed style
配置与 screenshot_h5_pages.py 一致iPhone 15 Pro Max (430×932, DPR:3)
输出:每个关键元素的 padding/margin/gap/lineHeight/fontSize/height
"""
import asyncio
import json
from pathlib import Path
from playwright.async_api import async_playwright
BASE_URL = "http://127.0.0.1:5500/docs/h5_ui/pages"
OUT_DIR = Path(__file__).resolve().parents[2] / "export" / "h5_style_extract"
OUT_DIR.mkdir(parents=True, exist_ok=True)
EXTRACT_JS = """
() => {
const section = document.getElementById('section-overview');
if (!section) return { error: 'section-overview not found' };
const results = [];
function extract(el, label) {
const cs = getComputedStyle(el);
return {
label,
tag: el.tagName,
className: el.className?.substring?.(0, 80) || '',
text: el.textContent?.trim()?.substring(0, 40) || '',
paddingTop: cs.paddingTop,
paddingBottom: cs.paddingBottom,
paddingLeft: cs.paddingLeft,
paddingRight: cs.paddingRight,
marginTop: cs.marginTop,
marginBottom: cs.marginBottom,
marginLeft: cs.marginLeft,
marginRight: cs.marginRight,
gap: cs.gap,
rowGap: cs.rowGap,
columnGap: cs.columnGap,
lineHeight: cs.lineHeight,
fontSize: cs.fontSize,
fontWeight: cs.fontWeight,
height: cs.height,
width: cs.width,
borderTop: cs.borderTopWidth,
borderBottom: cs.borderBottomWidth,
display: cs.display,
gridTemplateColumns: cs.gridTemplateColumns || '',
};
}
// 1. 板块根
results.push(extract(section, 'section-overview (root)'));
// 2. 板块头 .summary-header
const header = section.querySelector('.summary-header');
if (header) {
results.push(extract(header, 'summary-header'));
// 头部内子元素
header.querySelectorAll('h3, p, span').forEach((el, i) => {
results.push(extract(el, `header-child-${i} (${el.tagName})`));
});
}
// 3. 板块正文 .summary-content
const content = section.querySelector('.summary-content');
if (content) {
results.push(extract(content, 'summary-content'));
}
// 4. 收入概览子标题行
const subLabels = section.querySelectorAll('.summary-content > div > .flex.items-center.gap-2');
subLabels.forEach((el, i) => {
results.push(extract(el, `sub-label-row-${i}`));
el.querySelectorAll('span').forEach((s, j) => {
results.push(extract(s, `sub-label-row-${i}-span-${j}`));
});
});
// 5. 3列网格
const grid3 = section.querySelector('.grid.grid-cols-3');
if (grid3) {
results.push(extract(grid3, 'grid-cols-3'));
// 每个 cell
grid3.querySelectorAll(':scope > div').forEach((cell, i) => {
results.push(extract(cell, `grid3-cell-${i}`));
// cell 内的标签行和值
cell.querySelectorAll('p, span, div').forEach((el, j) => {
results.push(extract(el, `grid3-cell-${i}-child-${j}`));
});
});
}
// 6. 确认收入行 (bg-white/10 rounded-xl)
const confirmedRow = section.querySelector('.bg-white\\\\/10');
// Tailwind 的 class 含 / 不好选,用更通用的方式
const allRoundedXl = section.querySelectorAll('.rounded-xl');
allRoundedXl.forEach((el, i) => {
results.push(extract(el, `rounded-xl-${i}`));
el.querySelectorAll('p, span').forEach((c, j) => {
results.push(extract(c, `rounded-xl-${i}-child-${j}`));
});
});
// 7. 分割线
const divider = section.querySelector('.border-t');
if (divider) {
results.push(extract(divider, 'divider (border-t)'));
}
// 8. 2列网格
const grid2 = section.querySelector('.grid.grid-cols-2');
if (grid2) {
results.push(extract(grid2, 'grid-cols-2'));
grid2.querySelectorAll(':scope > div').forEach((cell, i) => {
results.push(extract(cell, `grid2-cell-${i}`));
cell.querySelectorAll('p, span, div').forEach((el, j) => {
results.push(extract(el, `grid2-cell-${i}-child-${j}`));
});
});
}
// 9. AI 洞察
const aiSection = section.querySelector('.ai-insight-section');
if (aiSection) {
results.push(extract(aiSection, 'ai-insight-section'));
const aiHeader = aiSection.querySelector('.ai-insight-header');
if (aiHeader) {
results.push(extract(aiHeader, 'ai-insight-header'));
aiHeader.querySelectorAll('span, div').forEach((el, i) => {
results.push(extract(el, `ai-header-child-${i}`));
});
}
aiSection.querySelectorAll('p').forEach((el, i) => {
results.push(extract(el, `ai-body-p-${i}`));
});
}
return results;
}
"""
async def main():
print("=" * 60)
print("H5 样式提取 — iPhone 15 Pro Max (430×932, DPR:3)")
print("=" * 60)
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=False,
args=["--hide-scrollbars"],
)
context = await browser.new_context(
viewport={"width": 430, "height": 932},
device_scale_factor=3,
)
page = await context.new_page()
# 验证 DPR
dpr = await page.evaluate("() => window.devicePixelRatio")
print(f"devicePixelRatio = {dpr}")
assert dpr == 3
# 导航
url = f"{BASE_URL}/board-finance.html"
await page.goto(url, wait_until="load", timeout=15000)
await page.wait_for_timeout(2500) # Tailwind CDN JIT 渲染
# 截图(对照用)
screenshot_path = OUT_DIR / "board-finance-overview.png"
# 先尝试截取板块区域
section = page.locator("#section-overview")
await section.screenshot(path=str(screenshot_path))
print(f"截图: {screenshot_path}")
# 提取样式
styles = await page.evaluate(EXTRACT_JS)
# 保存
out_path = OUT_DIR / "board-finance-overview-styles.json"
with open(out_path, "w", encoding="utf-8") as f:
json.dump(styles, f, ensure_ascii=False, indent=2)
print(f"样式数据: {out_path}")
print(f"共提取 {len(styles)} 个元素")
# 打印关键摘要
print("\n" + "=" * 60)
print("关键间距摘要px×2×0.875 转 rpx")
print("=" * 60)
for item in styles:
label = item['label']
# 只打印有意义的容器级元素
if any(k in label for k in ['header', 'content', 'grid', 'divider', 'confirmed', 'rounded', 'ai-insight', 'sub-label-row']):
if 'child' not in label:
pt = item['paddingTop']
pb = item['paddingBottom']
mt = item['marginTop']
mb = item['marginBottom']
gap = item['gap']
print(f" {label}:")
print(f" padding: {pt} / {pb} (T/B) | margin: {mt} / {mb} (T/B) | gap: {gap}")
await browser.close()
print(f"\n✅ 完成,详细数据见 {out_path}")
if __name__ == "__main__":
asyncio.run(main())