/** * 从 Lucide SVG 图标生成高质量 PNG * 用法: node scripts/generate-icons.js */ const sharp = require('sharp') const fs = require('fs') const path = require('path') const ICONS_DIR = path.join(__dirname, '../client/src/static/icons') const LUCIDE_DIR = path.join(__dirname, '../node_modules/lucide-static/icons') // 颜色映射:hex -> RGB const COLORS = { '#2D1B1B': { r: 45, g: 27, b: 27 }, // 暖深棕 '#8B7E7E': { r: 139, g: 126, b: 126 }, // 灰色 '#BFB3B3': { r: 191, g: 179, b: 179 }, // 浅灰 '#E0D6D0': { r: 224, g: 214, b: 208 }, // 极浅灰 '#FF8C69': { r: 255, g: 140, b: 105 }, // 珊瑚橙 '#FF6B6B': { r: 255, g: 107, b: 107 }, // 红色 '#7BC67E': { r: 123, g: 198, b: 126 }, // 绿色 '#FFFFFF': { r: 255, g: 255, b: 255 }, // 白色 } // 图标配置:{ lucideName, variants: { colorHex: outputName } } const ICONS = [ // 导航 { lucide: 'arrow-left', variants: { '#2D1B1B': 'arrow-left-dark', '#8B7E7E': 'arrow-left-gray' } }, { lucide: 'arrow-right', variants: { '#8B7E7E': 'arrow-right-gray', '#E0D6D0': 'arrow-right-pale' } }, { lucide: 'chevron-right', variants: { '#BFB3B3': 'chevron-right-light' } }, // 操作 { lucide: 'plus', variants: { '#FFFFFF': 'plus-white' } }, { lucide: 'check', variants: { '#FFFFFF': 'check-white' } }, { lucide: 'edit', variants: { '#8B7E7E': 'edit-gray', '#BFB3B3': 'edit-light' } }, { lucide: 'trash-2', variants: { '#FF8C69': 'trash-orange', '#FF6B6B': 'trash-red', '#FFFFFF': 'trash-white' } }, { lucide: 'refresh-cw', variants: { '#8B7E7E': 'refresh-gray', '#FF8C69': 'refresh-orange' } }, // 功能 { lucide: 'calendar', variants: { '#8B7E7E': 'calendar-gray' } }, { lucide: 'bell', variants: { '#8B7E7E': 'bell-gray' } }, { lucide: 'user', variants: { '#8B7E7E': 'user-gray', '#FFFFFF': 'user-white' } }, { lucide: 'alert-circle', variants: { '#BFB3B3': 'alert-circle-light' } }, // 趋势 { lucide: 'trending-down', variants: { '#FF6B6B': 'trend-down-red' } }, { lucide: 'trending-up', variants: { '#7BC67E': 'trend-up-green' } }, ] // TabBar 图标(需要 normal + active 两套) const TABBAR_ICONS = [ { lucide: 'home', normal: 'home', active: 'home-active', normalColor: '#BFB3B3', activeColor: '#FF8C69' }, { lucide: 'bar-chart-2', normal: 'chart', active: 'chart-active', normalColor: '#BFB3B3', activeColor: '#FF8C69' }, { lucide: 'receipt', normal: 'bill', active: 'bill-active', normalColor: '#BFB3B3', activeColor: '#FF8C69' }, { lucide: 'user', normal: 'user', active: 'user-active', normalColor: '#BFB3B3', activeColor: '#FF8C69' }, ] const SIZE = 96 // 输出 PNG 尺寸(px) async function generateIcon(svgName, outputPath, color) { const svgPath = path.join(LUCIDE_DIR, `${svgName}.svg`) if (!fs.existsSync(svgPath)) { console.warn(`⚠ SVG not found: ${svgName}.svg`) return false } let svgContent = fs.readFileSync(svgPath, 'utf-8') // 替换 SVG 中的 stroke 颜色 const { r, g, b } = COLORS[color] svgContent = svgContent.replace(/stroke="[^"]*"/g, `stroke="rgb(${r},${g},${b})"`) // 也处理可能的 fill svgContent = svgContent.replace(/fill="none"/g, 'fill="none"') // 设置输出尺寸 svgContent = svgContent.replace(/width="[^"]*"/, `width="${SIZE}"`) svgContent = svgContent.replace(/height="[^"]*"/, `height="${SIZE}"`) await sharp(Buffer.from(svgContent)) .resize(SIZE, SIZE) .png() .toFile(outputPath) return true } async function main() { console.log('🎨 开始生成图标...\n') let total = 0 let success = 0 // 生成普通图标 for (const icon of ICONS) { for (const [color, outputName] of Object.entries(icon.variants)) { const outputPath = path.join(ICONS_DIR, `${outputName}.png`) total++ if (await generateIcon(icon.lucide, outputPath, color)) { console.log(` ✓ ${outputName}.png`) success++ } } } // 生成 TabBar 图标 for (const icon of TABBAR_ICONS) { for (const [outputName, color] of [[icon.normal, icon.normalColor], [icon.active, icon.activeColor]]) { const outputPath = path.join(ICONS_DIR, `${outputName}.png`) total++ if (await generateIcon(icon.lucide, outputPath, color)) { console.log(` ✓ ${outputName}.png (tabbar)`) success++ } } } console.log(`\n✅ 完成: ${success}/${total} 个图标已生成`) } main().catch(console.error)