| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- // 票 15 取证(二):描边、状态词表配色、行业配色、面板内间距
- import { readFileSync } from 'node:fs'
- const dir = '.scratch/lanhu-cockpit-asset-replacement/research/raw-v2'
- const boards = [2, 3, 4, 5, 6, 7, 8, 9, 10]
- const hex = (c) => `#${[c.r, c.g, c.b].map((v) => Math.round(v * 255).toString(16).padStart(2, '0')).join('')}`
- const alpha = (c) => (c.a === undefined || c.a > 0.999 ? '' : `@${c.a.toFixed(2)}`)
- const load = (n) => JSON.parse(readFileSync(`${dir}/desktop-${String(n).padStart(2, '0')}.json`, 'utf8')).artboard
- // 1) 描边 / 边框
- const strokes = new Map()
- const borders = new Map()
- const radii = new Map()
- for (const n of boards) {
- ;(function walk(node) {
- 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)
- 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)
- const r = node.radius
- if (r && (r.topLeft || r.topRight || r.bottomLeft || r.bottomRight)) {
- const k = `${r.topLeft}/${r.topRight}/${r.bottomRight}/${r.bottomLeft}`
- radii.set(k, (radii.get(k) || 0) + 1)
- }
- for (const c of node.layers || []) walk(c)
- })(load(n))
- }
- const top = (m, n) => [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([k, v]) => `${k} ×${v}`).join(' | ')
- console.log('=== strokes:', top(strokes, 12) || '(无)')
- console.log('=== borders:', top(borders, 12) || '(无)')
- console.log('=== 圆角:', top(radii, 10) || '(无)')
- // 2) 状态词表配色:文字内容命中状态词时,其颜色/字号/字体
- const statusWords = /^(在线|离线|正常|报警|预警|未启用|未关联|待接单|待派单|已派单|处置中|已完成|超时|一般|严重|紧急|提示|停用|故障)$/
- const statusHits = new Map()
- for (const n of boards) {
- ;(function walk(node) {
- if (node.type === 'textLayer' && node.text?.style?.content) {
- const t = String(node.text.style.content).trim()
- if (statusWords.test(t)) {
- const f = node.text.style.font
- const c = node.text.style.color
- const k = `${t} → ${c ? hex(c) + alpha(c) : '-'} ${f.size}px ${f.name}`
- statusHits.set(k, (statusHits.get(k) || 0) + 1)
- }
- }
- for (const ch of node.layers || []) walk(ch)
- })(load(n))
- }
- console.log('\n=== 状态词配色')
- console.log([...statusHits.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k} ×${v}`).join('\n') || '(无)')
- // 3) 行业配色:文字命中行业名
- const inds = /^(燃气|供水|排水|窨井|电力|热力|通信|燃气行业|供水行业)/
- const indHits = new Map()
- for (const n of boards) {
- ;(function walk(node) {
- if (node.type === 'textLayer' && node.text?.style?.content) {
- const t = String(node.text.style.content).trim()
- if (inds.test(t)) {
- const f = node.text.style.font
- const c = node.text.style.color
- const k = `${t} → ${c ? hex(c) + alpha(c) : '-'} ${f.size}px`
- indHits.set(k, (indHits.get(k) || 0) + 1)
- }
- }
- for (const ch of node.layers || []) walk(ch)
- })(load(n))
- }
- console.log('\n=== 行业配色')
- console.log([...indHits.entries()].sort((a, b) => b[1] - a[1]).slice(0, 25).map(([k, v]) => `${k} ×${v}`).join('\n') || '(无)')
- // 4) 同父兄弟之间的垂直/水平间距(间距 token 依据)
- const gaps = new Map()
- for (const n of boards) {
- ;(function walk(node) {
- const kids = (node.layers || []).filter((c) => c.visible !== false && c.frame)
- if (kids.length >= 2 && kids.length <= 12) {
- const sorted = [...kids].sort((a, b) => a.frame.top - b.frame.top)
- for (let i = 1; i < sorted.length; i += 1) {
- const g = Math.round(sorted[i].frame.top - (sorted[i - 1].frame.top + sorted[i - 1].frame.height))
- if (g > 0 && g < 60) gaps.set(g, (gaps.get(g) || 0) + 1)
- }
- }
- for (const c of node.layers || []) walk(c)
- })(load(n))
- }
- console.log('\n=== 兄弟垂直间距 Top 15:', top(gaps, 15))
|