145 lines
7.7 KiB
JavaScript
145 lines
7.7 KiB
JavaScript
/**
|
||
* 🎯 100 个科技感头像候选预览页(资源库 lucide 源直选,无需渲染)
|
||
* 生成 preview-100.html:每张候选以最终头像样式展示(深蓝渐变圆底 + 白色线性图标),
|
||
* 支持勾选,底部可复制已选编号,供用户筛选后批量渲染 PNG。
|
||
*
|
||
* 用法:node scripts/gen-100-avatars-preview.js
|
||
*/
|
||
'use strict';
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const vm = require('vm');
|
||
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
const OUT_DIR = path.join(ROOT, 'output', 'agent-avatars-png');
|
||
|
||
// —— 1. 加载 lucide 源 ——
|
||
global.window = global;
|
||
const lucideFile = path.join(ROOT, 'js', 'data', 'sources', 'lucide.js');
|
||
vm.runInThisContext(fs.readFileSync(lucideFile, 'utf8'), { filename: lucideFile });
|
||
const lucideIcons = window['HGB_Source_lucide'].icons;
|
||
const byName = new Map(lucideIcons.map(x => [x.name, x]));
|
||
|
||
// —— 2. 精选 100 个(按主题分组)——
|
||
const GROUPS = [
|
||
{ label: '🤖 智能核心', items: ['bot', 'brain-circuit', 'brain', 'cpu', 'circuit-board', 'sparkles', 'sparkle', 'atom', 'orbit', 'radar', 'satellite', 'antenna', 'signal', 'wifi', 'bluetooth', 'usb', 'cable', 'plug', 'power', 'battery', 'battery-charging', 'battery-full'] },
|
||
{ label: '⌨️ 代码开发', items: ['terminal', 'code', 'code-xml', 'braces', 'brackets', 'file-code', 'git-branch', 'git-merge', 'git-pull-request', 'command', 'keyboard', 'monitor', 'laptop', 'smartphone', 'server', 'database', 'hard-drive'] },
|
||
{ label: '☁️ 网络云数据', items: ['cloud', 'cloud-upload', 'cloud-download', 'cloud-cog', 'cloud-lightning', 'network', 'share-2', 'link', 'globe', 'send', 'mail', 'message-circle', 'message-square', 'phone', 'mic', 'camera', 'video', 'qr-code', 'rss'] },
|
||
{ label: '🛡️ 安全防护', items: ['lock', 'key', 'key-round', 'shield', 'shield-check', 'shield-half', 'target', 'crosshair', 'eye', 'scan-face', 'scan'] },
|
||
{ label: '⚙️ 智能工具', items: ['settings', 'settings-2', 'cog', 'wrench', 'layers', 'layout-grid', 'box', 'package', 'blocks', 'puzzle', 'gem', 'diamond', 'sliders-horizontal', 'equal'] },
|
||
{ label: '🔷 抽象几何', items: ['hexagon', 'triangle', 'circle', 'octagon', 'infinity', 'asterisk', 'zap', 'activity', 'gauge', 'compass', 'navigation', 'navigation-2', 'map-pin', 'map'] },
|
||
{ label: '🚀 空间探索', items: ['rocket', 'plane', 'telescope'] }
|
||
];
|
||
|
||
let list = [];
|
||
const dup = new Set();
|
||
for (const g of GROUPS) {
|
||
for (const name of g.items) {
|
||
if (dup.has(name)) continue;
|
||
dup.add(name);
|
||
const icon = byName.get(name);
|
||
if (!icon) { console.warn('⚠️ 缺失:', name); continue; }
|
||
list.push({ name, id: icon.id, body: icon.svg, group: g.label });
|
||
}
|
||
}
|
||
console.log('候选数:', list.length);
|
||
|
||
// —— 3. 生成预览页 ——
|
||
const S = 24, BG_FROM = '#0b1220', BG_TO = '#16294a';
|
||
function avatarSvg(body) {
|
||
return `<svg viewBox="0 0 1024 1024" width="128" height="128">
|
||
<defs><linearGradient id="g${list.length}" x1="0" y1="0" x2="1" y2="1">
|
||
<stop offset="0%" stop-color="${BG_FROM}"/><stop offset="100%" stop-color="${BG_TO}"/></linearGradient>
|
||
<clipPath id="c${list.length}"><circle cx="512" cy="512" r="512"/></clipPath></defs>
|
||
<rect width="1024" height="1024" fill="url(#g${list.length})" clip-path="url(#c${list.length})"/>
|
||
<circle cx="512" cy="512" r="452" fill="none" stroke="#3b82f6" stroke-width="5" opacity=".45"/>
|
||
<g transform="translate(512 512) scale(20) translate(-12 -12)" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${body}</g>
|
||
</svg>`;
|
||
}
|
||
|
||
let cards = '';
|
||
let idx = 1;
|
||
for (const g of GROUPS) {
|
||
const members = list.filter(x => x.group === g.label);
|
||
if (!members.length) continue;
|
||
cards += `<div class="group-title">${g.label}(${members.length})</div>\n<div class="row">\n`;
|
||
for (const m of members) {
|
||
const n = String(idx).padStart(2, '0');
|
||
cards += `<label class="card" data-idx="${idx}">
|
||
<input type="checkbox" class="cb" value="${idx}" data-name="${m.name}">
|
||
${avatarSvg(m.body)}
|
||
<div class="no">#${n}</div>
|
||
<div class="name">${m.name}</div>
|
||
</label>\n`;
|
||
idx++;
|
||
}
|
||
cards += '</div>\n';
|
||
}
|
||
|
||
const html = `<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>🎯 100 个科技感头像候选 · 勾选后批量渲染</title>
|
||
<style>
|
||
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background:#0f1115; color:#e8eaed; margin:0; padding:24px; }
|
||
h1 { font-size:20px; margin:0 0 4px; }
|
||
.sub { color:#9aa0a6; font-size:13px; margin-bottom:16px; }
|
||
.toolbar { position:sticky; top:0; background:#0f1115ee; padding:10px 0; z-index:9; border-bottom:1px solid #2a2f3a; margin-bottom:16px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
|
||
.toolbar button { background:#2563eb; color:#fff; border:none; border-radius:6px; padding:6px 14px; font-size:13px; cursor:pointer; }
|
||
.toolbar .count { color:#8ab4f8; font-size:13px; }
|
||
.group-title { font-size:14px; color:#9aa0a6; margin:18px 0 10px; }
|
||
.row { display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:12px; }
|
||
.card { background:#1b1e26; border:1px solid #2a2f3a; border-radius:12px; padding:12px 8px 10px; text-align:center; cursor:pointer; position:relative; transition:border-color .15s; }
|
||
.card:hover { border-color:#3b82f6; }
|
||
.card.sel { border-color:#22c55e; background:#1a2b1f; }
|
||
.card svg { width:96px; height:96px; border-radius:50%; display:block; margin:0 auto; }
|
||
.card .no { font-size:11px; color:#80868b; margin-top:8px; }
|
||
.card .name { font-size:11px; color:#bdc1c6; word-break:break-all; }
|
||
.card .cb { position:absolute; top:8px; left:8px; width:16px; height:16px; accent-color:#22c55e; }
|
||
.out { margin-top:16px; }
|
||
.out textarea { width:100%; height:56px; background:#1b1e26; color:#8ab4f8; border:1px solid #2a2f3a; border-radius:8px; padding:8px; font-size:12px; box-sizing:border-box; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>🎯 100 个科技感头像候选</h1>
|
||
<div class="sub">资源库 lucide 源直选 · 点击卡片勾选(默认全选)· 确认后在下方复制编号,交由渲染为 PNG</div>
|
||
<div class="toolbar">
|
||
<button onclick="sel(true)">全选</button>
|
||
<button onclick="sel(false)">清空</button>
|
||
<span class="count" id="count">已选 0/100</span>
|
||
<button onclick="copySel()">复制已选编号</button>
|
||
</div>
|
||
<div id="groups">${cards}</div>
|
||
<div class="out">
|
||
<textarea id="selText" placeholder="已选编号将显示在这里…" readonly></textarea>
|
||
</div>
|
||
<script>
|
||
const cards = document.querySelectorAll('.card');
|
||
const countEl = document.getElementById('count');
|
||
const selText = document.getElementById('selText');
|
||
function update() {
|
||
const sel = [...cards].filter(c => c.querySelector('.cb').checked);
|
||
cards.forEach(c => c.classList.toggle('sel', c.querySelector('.cb').checked));
|
||
countEl.textContent = '已选 ' + sel.length + '/' + cards.length;
|
||
selText.value = sel.map(c => c.dataset.idx).join(',');
|
||
}
|
||
function sel(v) { cards.forEach(c => c.querySelector('.cb').checked = v); update(); }
|
||
function copySel() {
|
||
if (!selText.value) return alert('请先勾选头像');
|
||
navigator.clipboard.writeText(selText.value).then(() => alert('已复制编号:' + selText.value)).catch(() => alert(selText.value));
|
||
}
|
||
cards.forEach(c => c.addEventListener('click', e => {
|
||
if (e.target.tagName === 'INPUT') return;
|
||
const cb = c.querySelector('.cb'); cb.checked = !cb.checked; update();
|
||
}));
|
||
sel(true);
|
||
</script>
|
||
</body>
|
||
</html>`;
|
||
|
||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||
fs.writeFileSync(path.join(OUT_DIR, 'preview-100.html'), html, 'utf8');
|
||
console.log('🎉 已生成 → output/agent-avatars-png/preview-100.html(候选 ' + list.length + ' 个)');
|