Files
longli-mp/scripts/build-icons.mjs
2026-09-18 10:37:46 +08:00

135 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 图标生成脚本:微信小程序不支持内联 <svg>,因此把初设图里的 SVG symbol
* 编译成 WXSS 的 background-imagebase64 data-uri按「名称 + 色调」生成类名。
*
* 用法node scripts/build-icons.mjs
* 产出src/styles/icons.scss
*/
import fs from "node:fs";
import path from "node:path";
const ROOT = path.resolve(import.meta.dirname, "..");
const OUT = path.join(ROOT, "src/styles/icons.scss");
/** 颜色 token与 design-token.scss 保持一致) */
const TONES = {
ink: "#14181C",
ink2: "#6B7280",
ink3: "#9CA3AF",
white: "#FFFFFF",
brand: "#00B578",
brandDeep: "#009A67",
accent: "#FF8A3D",
accentDeep: "#C9550C",
danger: "#E5484D",
};
/** stroke 型图标24×24 线性图标stroke-width 1.9 */
const STROKE_ICONS = {
pin: '<path d="M12 21s7-6.1 7-11a7 7 0 1 0-14 0c0 4.9 7 11 7 11z"/><circle cx="12" cy="10" r="2.4"/>',
arrow: '<path d="M4.5 12h15"/><path d="M13.5 6l6 6-6 6"/>',
chev: '<path d="M9.5 5.5 16 12l-6.5 6.5"/>',
back: '<path d="M14.5 5.5 8 12l6.5 6.5"/>',
phone:
'<path d="M4.2 4.4h3.9l1.9 4.8-2.4 1.5a12.2 12.2 0 0 0 5.8 5.8l1.5-2.4 4.8 1.9v3.9a1 1 0 0 1-1.1 1A16.8 16.8 0 0 1 3.2 5.5a1 1 0 0 1 1-1.1z"/>',
copy: '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15.5V5.5a1.5 1.5 0 0 1 1.5-1.5h10"/>',
gift: '<path d="M20 12v8.5H4V12"/><path d="M2.5 7.5h19V12h-19z"/><path d="M12 7.5v13"/><path d="M12 7.5H8.6a2.3 2.3 0 1 1 0-4.6C11.6 2.9 12 7.5 12 7.5z"/><path d="M12 7.5h3.4a2.3 2.3 0 1 0 0-4.6C12.4 2.9 12 7.5 12 7.5z"/>',
home: '<path d="M4 11.2 12 4.4l8 6.8v8.3a1 1 0 0 1-1 1h-4.2v-6H9.2v6H5a1 1 0 0 1-1-1z"/>',
ticket:
'<path d="M3.5 9.6a2 2 0 0 1 2-2h13a2 2 0 0 1 2 2v1.2a1.4 1.4 0 0 0 0 2.8v1.2a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2v-1.2a1.4 1.4 0 0 0 0-2.8z"/><path d="M13.6 7.6v2M13.6 13v2"/>',
user: '<circle cx="12" cy="8.2" r="3.9"/><path d="M4.6 20.6a7.4 7.4 0 0 1 14.8 0"/>',
check: '<path d="M5.5 12.6 10 17l8.5-9"/>',
close: '<path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/>',
doc: '<path d="M6.5 3.5h7.5l4.5 4.5v12.5h-12z"/><path d="M14 3.5V8h4.5"/><path d="M9 13h6M9 16.5h4.5"/>',
shield: '<path d="M12 3.5 19.5 6v5.6c0 4.6-3.6 7.6-7.5 8.9-3.9-1.3-7.5-4.3-7.5-8.9V6z"/><path d="M9.2 12.2l2.2 2.2 3.6-3.6"/>',
info: '<circle cx="12" cy="12" r="8.6"/><path d="M12 11v5.2"/><path d="M12 7.8h.01"/>',
headset:
'<path d="M4.5 15v-3a7.5 7.5 0 0 1 15 0v3"/><path d="M4.5 13.6h2.2v5H5.6a1.1 1.1 0 0 1-1.1-1.1z"/><path d="M19.5 13.6h-2.2v5h1.1a1.1 1.1 0 0 0 1.1-1.1z"/><path d="M17.3 18.6v.6a2 2 0 0 1-2 2h-2.8"/>',
logout: '<path d="M15 12H4"/><path d="M11.4 8.4 15 12l-3.6 3.6"/><path d="M15.5 4.5h3a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-3"/>',
trash:
'<path d="M4.5 7h15"/><path d="M9.5 7V4.8h5V7"/><path d="M6.5 7l.9 13h9.2l.9-13"/><path d="M10.5 10.5v6M13.5 10.5v6"/>',
share:
'<path d="M12 16V3.8"/><path d="M8.2 7.6 12 3.8l3.8 3.8"/><path d="M5 13.6V19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5.4"/>',
clock: '<circle cx="12" cy="12" r="8.6"/><path d="M12 7.4v4.8l3.2 1.9"/>',
map: '<path d="M9 4.5 3.5 6.6v13L9 17.4l6 2.1 5.5-2.1v-13L15 6.6z"/><path d="M9 4.5v12.9M15 6.6v12.9"/>',
food: '<path d="M6.6 3.6v5M9.4 3.6v5"/><path d="M8 3.6v16.8"/><path d="M6.6 8.6h2.8"/><path d="M16.4 3.6c1.7 0 2.8 1.3 2.8 3.1 0 1.9-1.1 3.1-2.8 3.1s-2.8-1.2-2.8-3.1c0-1.8 1.1-3.1 2.8-3.1z"/><path d="M16.4 9.8v10.6"/>',
bed: '<path d="M3.6 18.6V6.4"/><path d="M3.6 15h16.8v3.6"/><path d="M20.4 15v-2.6a1.6 1.6 0 0 0-1.6-1.6h-7V15"/><circle cx="7.6" cy="12.1" r="1.7"/>',
peak: '<path d="M2.8 19.2 9 8.4l3.6 6.2 2.2-3.4 6.4 8z"/>',
people:
'<circle cx="9.2" cy="8.4" r="3.2"/><path d="M3.6 19.4a5.6 5.6 0 0 1 11.2 0"/><path d="M15.8 5.6a3.2 3.2 0 0 1 0 6"/><path d="M17.4 14.8a5.6 5.6 0 0 1 3 4.6"/>',
};
/** fill 型图标(实心) */
const FILL_ICONS = {
wx: '<path d="M8.9 3.4C5.2 3.4 2.2 5.9 2.2 9c0 1.8 1 3.4 2.6 4.4l-.7 2.4 2.8-1.4c.6.2 1.3.3 2 .3h.5a5.4 5.4 0 0 1-.2-1.5c0-3 3-5.3 6.8-5.3h.5C16 5.3 12.7 3.4 8.9 3.4z"/><path d="M15.9 8.8c-3.4 0-6.1 2-6.1 4.4 0 1.5.9 2.8 2.3 3.6l-.6 1.9 2.3-1.1c.5.1 1.1.2 1.6.2 3.4 0 6.1-2 6.1-4.6s-2.7-4.4-5.6-4.4z"/>',
};
/** 需要生成的「图标 × 色调」组合(只生成用得到的,控制体积) */
const MATRIX = {
pin: ["ink3", "brand", "white"],
arrow: ["white", "brandDeep", "ink"],
chev: ["ink3", "white", "brand", "ink2", "accentDeep"],
back: ["ink", "white", "brand"],
phone: ["white", "brandDeep"],
copy: ["brandDeep", "brand"],
gift: ["white", "brandDeep", "accent"],
home: ["ink3", "brand"],
ticket: ["ink3", "brand", "accent", "white", "ink2"],
user: ["ink3", "brand", "white", "ink2"],
check: ["white", "brand"],
close: ["ink3", "white"],
doc: ["ink2", "brand"],
shield: ["ink2", "white", "brand"],
info: ["ink2", "brand"],
headset: ["ink2", "brand"],
logout: ["ink2", "white"],
trash: ["ink2", "danger"],
share: ["white"],
clock: ["brand", "ink3", "white"],
map: ["brandDeep", "white"],
food: ["brandDeep", "accent", "white", "brand"],
bed: ["accent", "brand", "white", "brandDeep"],
peak: ["accent", "brand", "white"],
people: ["ink3", "brand", "white", "brandDeep"],
wx: ["white"],
};
const buildSvg = (name, body, color, type) => {
const style =
type === "fill"
? `fill='${color}' stroke='none'`
: `fill='none' stroke='${color}' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'`;
return `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' ${style}>${body}</svg>`;
};
const toDataUri = (svg) => `data:image/svg+xml;base64,${Buffer.from(svg, "utf8").toString("base64")}`;
const lines = [
"/* 由 scripts/build-icons.mjs 生成,请勿手改 */",
"",
".ai{display:block;flex:none;background-repeat:no-repeat;background-position:center;background-size:100% 100%}",
"",
];
let count = 0;
for (const [name, tones] of Object.entries(MATRIX)) {
const type = FILL_ICONS[name] ? "fill" : "stroke";
const body = FILL_ICONS[name] || STROKE_ICONS[name];
if (!body) throw new Error(`未定义的图标:${name}`);
for (const tone of tones) {
const color = TONES[tone];
if (!color) throw new Error(`未定义的色调:${tone}`);
if (type === "stroke") {
// stroke 型图标 stroke-width 随字号缩放会变细,统一用 1.9
}
const uri = toDataUri(buildSvg(name, body, color, type));
lines.push(`.ai-${name}.t-${tone}{background-image:url("${uri}")}`);
count += 1;
}
}
lines.push("");
fs.writeFileSync(OUT, lines.join("\n"), "utf8");
const kb = (fs.statSync(OUT).size / 1024).toFixed(1);
console.log(`icons.scss 已生成:${count} 条规则 / ${kb} KB`);