probe-v2-main-board.mjs 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. // 票 15 取证:主画板候选(Desktop-6 总体概览)与全稿对比 —— 画板底色、面板填充、标题/正文文字色
  2. import { readFileSync } from 'node:fs'
  3. const dir = '.scratch/lanhu-cockpit-asset-replacement/research/raw-v2'
  4. const hex = (c) => `#${[c.r, c.g, c.b].map((v) => Math.round(v * 255).toString(16).padStart(2, '0')).join('')}`
  5. const alpha = (c) => (c.a === undefined || c.a > 0.999 ? '' : `@${c.a.toFixed(2)}`)
  6. function load(n) {
  7. return JSON.parse(readFileSync(`${dir}/desktop-${String(n).padStart(2, '0')}.json`, 'utf8')).artboard
  8. }
  9. // 1. 画板根节点自身的填充(= 页面底色)
  10. for (const n of [2, 3, 4, 5, 6, 7, 8, 9, 10]) {
  11. const ab = load(n)
  12. const rootFills = (ab.style?.fills || []).filter((f) => f.type === 'color').map((f) => hex(f.color) + alpha(f.color))
  13. const big = []
  14. ;(function walk(node) {
  15. const w = node.frame?.width ?? 0
  16. const h = node.frame?.height ?? 0
  17. if (w >= 200 && h >= 100) {
  18. for (const f of node.style?.fills || []) {
  19. if (f.type === 'color') big.push({ n: node.name, w: Math.round(w), h: Math.round(h), c: hex(f.color) + alpha(f.color) })
  20. }
  21. }
  22. for (const c of node.layers || []) walk(c)
  23. })(ab)
  24. console.log(`\n=== ${ab.name} ${ab.frame.width}x${ab.frame.height}`)
  25. console.log(` 根填充: ${rootFills.join(', ') || '(无)'}`)
  26. console.log(` 大色块(≥200x100) 前 14: ${big.slice(0, 14).map((b) => `${b.n} ${b.w}x${b.h} ${b.c}`).join(' | ')}`)
  27. }
  28. // 2. 文字层级:按字号聚合文字颜色(看设计稿的文字色阶)
  29. const bySize = new Map()
  30. for (const n of [2, 3, 4, 5, 6, 7, 8, 9, 10]) {
  31. const ab = load(n)
  32. ;(function walk(node) {
  33. if (node.type === 'textLayer' && node.text?.style?.font?.size) {
  34. const size = node.text.style.font.size
  35. const c = node.text.style.color
  36. if (c) {
  37. const key = `${size}px ${hex(c)}${alpha(c)}`
  38. const cur = bySize.get(key) || { count: 0, sample: '' }
  39. cur.count += 1
  40. if (!cur.sample) cur.sample = String(node.text.style.content || '').slice(0, 12)
  41. bySize.set(key, cur)
  42. }
  43. }
  44. for (const ch of node.layers || []) walk(ch)
  45. })(ab)
  46. }
  47. console.log('\n=== 文字:字号×颜色 Top 30')
  48. console.log(
  49. [...bySize.entries()]
  50. .sort((a, b) => b[1].count - a[1].count)
  51. .slice(0, 30)
  52. .map(([k, v]) => `${k} ×${v.count} 例:${v.sample}`)
  53. .join('\n'),
  54. )
  55. // 3. 面板标题候选:文字内容含「总览/概览/预警/监测」的节点,取其字号与颜色
  56. console.log('\n=== 标题类文字样本(内容含 总览|概览|预警|监测|档案)')
  57. const seen = new Set()
  58. for (const n of [6, 2, 5]) {
  59. const ab = load(n)
  60. ;(function walk(node) {
  61. if (node.type === 'textLayer' && node.text?.style?.content) {
  62. const t = String(node.text.style.content)
  63. if (/总览|概览|预警|监测|档案|信息|分析/.test(t) && !seen.has(`${ab.name}:${t}`)) {
  64. seen.add(`${ab.name}:${t}`)
  65. const f = node.text.style.font
  66. const c = node.text.style.color
  67. console.log(` [${ab.name}] "${t}" ${f.name} ${f.size}px w${f.fontWeight} ${c ? hex(c) + alpha(c) : '-'} frame=${Math.round(node.frame.width)}x${Math.round(node.frame.height)}`)
  68. }
  69. }
  70. for (const ch of node.layers || []) walk(ch)
  71. })(ab)
  72. }