Files
13-PingZhi126KVGLKG-MES_Man…/src/icons/generate-icon-gallery.js

151 lines
7.5 KiB
JavaScript

"use strict";
const fs = require("fs");
const path = require("path");
const iconsDir = path.resolve(__dirname, "svg");
const outFile = path.resolve(__dirname, "gallery.html");
if (!fs.existsSync(iconsDir)) {
console.error(`SVG directory not found: ${iconsDir}`);
process.exit(1);
}
const entries = fs.readdirSync(iconsDir, { withFileTypes: true })
.filter(d => d.isFile() && d.name.toLowerCase().endsWith(".svg"))
.map(d => ({ name: d.name, size: fs.statSync(path.join(iconsDir, d.name)).size }))
.sort((a, b) => a.name.localeCompare(b.name, "zh", { sensitivity: "base", numeric: true }));
const empty = entries.filter(e => e.size === 0);
const nonEmpty = entries.filter(e => e.size > 0);
const escapeHtml = (s) => String(s)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#39;");
function renderCard(file, kind) {
const display = file.name.replace(/\.svg$/i, "");
const safe = escapeHtml(display);
const dataName = display.toLowerCase();
const src = "svg/" + encodeURIComponent(file.name);
if (kind === 'empty') {
return `\n <div class="card empty" data-name="${escapeHtml(dataName)}">\n <div class="preview placeholder"></div>\n <div class="name" title="${safe}">${safe}</div>\n </div>`;
}
return `\n <div class="card" data-name="${escapeHtml(dataName)}">\n <div class="preview"><img loading="lazy" src="${src}" alt="${safe}" onerror="this.closest('div.card').classList.add('error')"></div>\n <div class="name" title="${safe}">${safe}</div>\n </div>`;
}
const rowsNonEmpty = nonEmpty.map(f => renderCard(f, 'icon')).join("");
const rowsEmpty = empty.map(f => renderCard(f, 'empty')).join("");
const html = `<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>SVG 图标预览</title>
<style>
:root { --gap: 12px; --size: 64px; --border: #e5e7eb; --text: #111827; --muted: #6b7280; --error: #ef4444; --checker-a:#e5e7eb; --checker-b:#ffffff; }
* { box-sizing: border-box; }
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; color: var(--text); }
header { position: sticky; top: 0; background: #fff; border-bottom: 1px solid var(--border); padding: 14px 16px; display: flex; align-items: center; gap: 12px; z-index: 10; }
h1 { margin: 0; font-size: 16px; font-weight: 600; }
.search { flex: 1; }
.search input { width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; }
.count { font-size: 12px; color: var(--muted); white-space: nowrap; }
main { padding: 16px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--gap); }
.card { border: 1px solid var(--border); border-radius: 12px; padding: 12px; display: flex; flex-direction: column; align-items: center; gap: 10px; background: #fff; }
.preview { width: var(--size); height: var(--size); border-radius: 10px; display: flex; align-items: center; justify-content: center; background: repeating-conic-gradient(var(--checker-a) 0 25%, var(--checker-b) 0 50%) 50% / 12px 12px; }
.preview img { max-width: 100%; max-height: 100%; filter: drop-shadow(0 0 1px rgba(0,0,0,.45)) drop-shadow(0 0 2px rgba(0,0,0,.25)); }
.name { text-align: center; font-size: 12px; color: var(--muted); width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card.error { border-color: var(--error); }
.card.error .name { color: var(--error); }
.card.empty { border-color: #f59e0b; }
.card.empty .preview { background: repeating-conic-gradient(#fef3c7 0 25%, #fff7ed 0 50%) 50% / 12px 12px; }
footer { padding: 16px; color: var(--muted); text-align: center; font-size: 12px; }
.toolbar { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 12px; }
.size-input { width: 120px; }
select, button, label.switch { border: 1px solid var(--border); background: #fff; padding: 6px 8px; border-radius: 6px; }
label.switch { display: inline-flex; align-items: center; gap: 6px; }
</style>
</head>
<body>
<header>
<h1>SVG 图标预览</h1>
<div class="toolbar">
<label>大小: <input class="size-input" type="range" min="24" max="160" value="64"></label>
<label>背景:
<select id="bg">
<option value="checker" selected>棋盘</option>
<option value="white">白色</option>
<option value="gray">浅灰</option>
<option value="dark">深色</option>
</select>
</label>
<label class="switch"><input id="toggleEmpty" type="checkbox"> 显示空文件(${empty.length})</label>
</div>
<div class="search"><input id="q" type="search" placeholder="搜索图标名称...(支持中英文)" autofocus></div>
<div class="count"><span id="shown"></span>/<span id="total"></span></div>
</header>
<main>
<div id="grid" class="grid">${rowsNonEmpty}${rowsEmpty}\n </div>
</main>
<footer>共 ${nonEmpty.length} 个有效图标,${empty.length} 个空文件。默认隐藏空文件,你可在工具栏勾选显示。</footer>
<script>
const q = document.getElementById('q');
const grid = document.getElementById('grid');
const cards = Array.from(grid.querySelectorAll('.card'));
const total = document.getElementById('total');
const shown = document.getElementById('shown');
const bg = document.getElementById('bg');
const toggleEmpty = document.getElementById('toggleEmpty');
total.textContent = String(cards.length);
function applyFilter() {
const term = q.value.trim().toLowerCase();
let visible = 0;
for (const el of cards) {
const isEmpty = el.classList.contains('empty');
const matches = !term || el.dataset.name.includes(term);
const show = matches && (toggleEmpty.checked || !isEmpty);
el.style.display = show ? '' : 'none';
if (show) visible++;
}
shown.textContent = String(visible);
}
q.addEventListener('input', applyFilter);
toggleEmpty.addEventListener('change', applyFilter);
applyFilter();
const sizeInput = document.querySelector('.size-input');
function applySize() { document.documentElement.style.setProperty('--size', sizeInput.value + 'px'); }
sizeInput.addEventListener('input', applySize);
applySize();
function applyBg() {
const v = bg.value;
if (v === 'checker') {
document.documentElement.style.setProperty('--checker-a', '#e5e7eb');
document.documentElement.style.setProperty('--checker-b', '#ffffff');
} else if (v === 'white') {
document.documentElement.style.setProperty('--checker-a', '#ffffff');
document.documentElement.style.setProperty('--checker-b', '#ffffff');
} else if (v === 'gray') {
document.documentElement.style.setProperty('--checker-a', '#e5e7eb');
document.documentElement.style.setProperty('--checker-b', '#e5e7eb');
} else if (v === 'dark') {
document.documentElement.style.setProperty('--checker-a', '#111827');
document.documentElement.style.setProperty('--checker-b', '#1f2937');
}
}
bg.addEventListener('change', applyBg);
applyBg();
</script>
</body>
</html>`;
const utf8NoBom = new (require('util').TextEncoder)();
fs.writeFileSync(outFile, html, { encoding: "utf8" });
console.log(`Generated: ${outFile} with ${nonEmpty.length} icons and ${empty.length} empty files`);