probe-v2-tokens.mjs 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. // 票 15 取证(二):描边、状态词表配色、行业配色、面板内间距
  2. import { readFileSync } from 'node:fs'
  3. const dir = '.scratch/lanhu-cockpit-asset-replacement/research/raw-v2'
  4. const boards = [2, 3, 4, 5, 6, 7, 8, 9, 10]
  5. const hex = (c) => `#${[c.r, c.g, c.b].map((v) => Math.round(v * 255).toString(16).padStart(2, '0')).join('')}`
  6. const alpha = (c) => (c.a === undefined || c.a > 0.999 ? '' : `@${c.a.toFixed(2)}`)
  7. const load = (n) => JSON.parse(readFileSync(`${dir}/desktop-${String(n).padStart(2, '0')}.json`, 'utf8')).artboard
  8. // 1) 描边 / 边框
  9. const strokes = new Map()
  10. const borders = new Map()
  11. const radii = new Map()
  12. for (const n of boards) {
  13. ;(function walk(node) {
  14. for (const s of node.style?.strokes || []) if (s.type === 'color') strokes.set(hex(s.color) + alpha(s.color), (strokes.get(hex(s.color) + alpha(s.color)) || 0) + 1)
  15. for (const b of node.style?.borders || []) if (b.color) borders.set(hex(b.color) + alpha(b.color), (borders.get(hex(b.color) + alpha(b.color)) || 0) + 1)
  16. const r = node.radius
  17. if (r && (r.topLeft || r.topRight || r.bottomLeft || r.bottomRight)) {
  18. const k = `${r.topLeft}/${r.topRight}/${r.bottomRight}/${r.bottomLeft}`
  19. radii.set(k, (radii.get(k) || 0) + 1)
  20. }
  21. for (const c of node.layers || []) walk(c)
  22. })(load(n))
  23. }
  24. const top = (m, n) => [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([k, v]) => `${k} ×${v}`).join(' | ')
  25. console.log('=== strokes:', top(strokes, 12) || '(无)')
  26. console.log('=== borders:', top(borders, 12) || '(无)')
  27. console.log('=== 圆角:', top(radii, 10) || '(无)')
  28. // 2) 状态词表配色:文字内容命中状态词时,其颜色/字号/字体
  29. const statusWords = /^(在线|离线|正常|报警|预警|未启用|未关联|待接单|待派单|已派单|处置中|已完成|超时|一般|严重|紧急|提示|停用|故障)$/
  30. const statusHits = new Map()
  31. for (const n of boards) {
  32. ;(function walk(node) {
  33. if (node.type === 'textLayer' && node.text?.style?.content) {
  34. const t = String(node.text.style.content).trim()
  35. if (statusWords.test(t)) {
  36. const f = node.text.style.font
  37. const c = node.text.style.color
  38. const k = `${t} → ${c ? hex(c) + alpha(c) : '-'} ${f.size}px ${f.name}`
  39. statusHits.set(k, (statusHits.get(k) || 0) + 1)
  40. }
  41. }
  42. for (const ch of node.layers || []) walk(ch)
  43. })(load(n))
  44. }
  45. console.log('\n=== 状态词配色')
  46. console.log([...statusHits.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k} ×${v}`).join('\n') || '(无)')
  47. // 3) 行业配色:文字命中行业名
  48. const inds = /^(燃气|供水|排水|窨井|电力|热力|通信|燃气行业|供水行业)/
  49. const indHits = new Map()
  50. for (const n of boards) {
  51. ;(function walk(node) {
  52. if (node.type === 'textLayer' && node.text?.style?.content) {
  53. const t = String(node.text.style.content).trim()
  54. if (inds.test(t)) {
  55. const f = node.text.style.font
  56. const c = node.text.style.color
  57. const k = `${t} → ${c ? hex(c) + alpha(c) : '-'} ${f.size}px`
  58. indHits.set(k, (indHits.get(k) || 0) + 1)
  59. }
  60. }
  61. for (const ch of node.layers || []) walk(ch)
  62. })(load(n))
  63. }
  64. console.log('\n=== 行业配色')
  65. console.log([...indHits.entries()].sort((a, b) => b[1] - a[1]).slice(0, 25).map(([k, v]) => `${k} ×${v}`).join('\n') || '(无)')
  66. // 4) 同父兄弟之间的垂直/水平间距(间距 token 依据)
  67. const gaps = new Map()
  68. for (const n of boards) {
  69. ;(function walk(node) {
  70. const kids = (node.layers || []).filter((c) => c.visible !== false && c.frame)
  71. if (kids.length >= 2 && kids.length <= 12) {
  72. const sorted = [...kids].sort((a, b) => a.frame.top - b.frame.top)
  73. for (let i = 1; i < sorted.length; i += 1) {
  74. const g = Math.round(sorted[i].frame.top - (sorted[i - 1].frame.top + sorted[i - 1].frame.height))
  75. if (g > 0 && g < 60) gaps.set(g, (gaps.get(g) || 0) + 1)
  76. }
  77. }
  78. for (const c of node.layers || []) walk(c)
  79. })(load(n))
  80. }
  81. console.log('\n=== 兄弟垂直间距 Top 15:', top(gaps, 15))