// 批量分析 9 个画板 FigmaJSON,输出汇总 JSON + 紧凑表格 import { readFileSync, writeFileSync, readdirSync } from 'node:fs' import { join } from 'node:path' const dir = '.scratch/lanhu-cockpit-asset-replacement/research/raw' const files = readdirSync(dir).filter((f) => /^desktop-\d+\.json$/.test(f)).sort() function hex(c) { const h = (v) => Math.round(v * 255).toString(16).padStart(2, '0') return `#${h(c.r)}${h(c.g)}${h(c.b)}` } const summary = [] for (const file of files) { const j = JSON.parse(readFileSync(join(dir, file), 'utf8')) const ab = j.artboard const fonts = new Map() const slices = [] const images = [] const colors = new Map() const textSamples = [] let nodeCount = 0 function walk(node, path) { nodeCount += 1 const p = path ? `${path}/${node.name}` : node.name if (node.type === 'textLayer' && node.text) { const f = node.text.style?.font if (f && f.name) { const key = `${f.name} | ${f.size}px | w${f.fontWeight ?? ''}` fonts.set(key, (fonts.get(key) || 0) + 1) } if (textSamples.length < 12 && node.text.style?.content) textSamples.push(String(node.text.style.content).slice(0, 20)) for (const r of node.text.runs || []) { const rf = r.font || r.style?.font if (rf && rf.name) { const key = `${rf.name} | ${rf.size}px | w${rf.fontWeight ?? ''}` fonts.set(key, (fonts.get(key) || 0) + 1) } } } if (node.hasExportImage || node.hasExportDDSImage) { slices.push({ path: p, w: Math.round(node.frame?.width ?? 0), h: Math.round(node.frame?.height ?? 0), dds: !!node.hasExportDDSImage }) } for (const f of node.style?.fills || []) { if (f.type === 'color' && f.color) { const c = hex(f.color) colors.set(c, (colors.get(c) || 0) + 1) } const url = f.ddsImage?.imageUrl || f.image?.imageUrl || f.imageUrl if (f.type === 'image' && url) { images.push({ path: p, url, w: Math.round(node.frame?.width ?? 0), h: Math.round(node.frame?.height ?? 0) }) } } for (const ch of node.layers || []) walk(ch, p) } for (const l of ab.layers || []) walk(l, '') summary.push({ file, artboard: ab.name, frame: { w: ab.frame.width, h: ab.frame.height }, rootExportFlags: { hasExportImage: ab.hasExportImage, hasExportDDSImage: ab.hasExportDDSImage }, nodeCount, sliceCount: slices.length, ddsSliceCount: slices.filter((s) => s.dds).length, topSlices: slices.filter((s) => s.w >= 24 && s.h >= 24).slice(0, 12), fonts: [...fonts.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => ({ font: k, count: v })), imageRefCount: images.length, images: images.map((i) => ({ path: i.path, w: i.w, h: i.h, url: i.url })), topColors: [...colors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 16).map(([k, v]) => ({ color: k, count: v })), assetFiles: j.assets || [], textSamples, }) } writeFileSync(join(dir, 'analysis-summary.json'), JSON.stringify(summary, null, 1)) console.log('artboard | frame | nodes | slices(dds) | fonts | imgFills | colors | assets') for (const s of summary) { console.log( `${s.artboard} | ${s.frame.w}x${s.frame.h} | ${s.nodeCount} | ${s.sliceCount}(${s.ddsSliceCount}) | ${s.fonts.length} | ${s.imageRefCount} | ${s.topColors.length} | ${s.assetFiles.length}`, ) } const allFonts = new Set() for (const s of summary) for (const f of s.fonts) allFonts.add(f.font.split(' | ')[0]) console.log('\nall font families:', [...allFonts].join(' / '))