/** * 从两套 iconfont CDN CSS(与 setIconfont 历史配置一致)合并类名与 unicode, * 生成 iconfont.json + 双字体 iconfont.css,并下载 woff2/ttf 到 src/assets/iconfont。 * 规则顺序:2298093 为基础,3882322 覆盖同名类(与页面先插 229 再插 388 时一致)。 * * 使用:node scripts/merge-iconfont-from-cdn.mjs */ import fs from 'node:fs'; import path from 'node:path'; import https from 'node:https'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(__dirname, '..'); const outDir = path.join(root, 'src', 'assets', 'iconfont'); const URL_229_CSS = 'https://at.alicdn.com/t/font_2298093_y6u00apwst.css'; const URL_388_CSS = 'https://at.alicdn.com/t/c/font_3882322_9ah7y8m9175.css'; const FONT_FILES = [ { url: 'https://at.alicdn.com/t/font_2298093_y6u00apwst.woff2?t=1627014681704', name: 'font-2298093.woff2' }, { url: 'https://at.alicdn.com/t/font_2298093_y6u00apwst.ttf?t=1627014681704', name: 'font-2298093.ttf' }, { url: 'https://at.alicdn.com/t/c/font_3882322_9ah7y8m9175.woff2?t=1676037377315', name: 'font-3882322.woff2' }, { url: 'https://at.alicdn.com/t/c/font_3882322_9ah7y8m9175.ttf?t=1676037377315', name: 'font-3882322.ttf' }, ]; function fetchText(url) { return new Promise((resolve, reject) => { https .get(url, (res) => { if (res.statusCode === 301 || res.statusCode === 302) { fetchText(res.headers.location).then(resolve).catch(reject); return; } if (res.statusCode !== 200) { reject(new Error(`GET ${url} ${res.statusCode}`)); return; } const chunks = []; res.on('data', (c) => chunks.push(c)); res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); }) .on('error', reject); }); } function downloadFile(url, dest) { return new Promise((resolve, reject) => { const file = fs.createWriteStream(dest); https .get(url, (res) => { if (res.statusCode === 301 || res.statusCode === 302) { file.close(); fs.unlink(dest, () => {}); downloadFile(res.headers.location, dest).then(resolve).catch(reject); return; } if (res.statusCode !== 200) { file.close(); fs.unlink(dest, () => {}); reject(new Error(`GET ${url} ${res.statusCode}`)); return; } res.pipe(file); file.on('finish', () => file.close(resolve)); }) .on('error', (e) => { file.close(); fs.unlink(dest, () => {}); reject(e); }); }); } /** @returns {Map} className (e.g. icon-foo) -> hex 如 e670(无反斜杠) */ function parseIconRules(css) { const map = new Map(); const re = /\.(icon-[a-zA-Z0-9_-]+):before\s*\{[^}]*?content:\s*"([^"]+)"/g; let m; while ((m = re.exec(css))) { let hex = m[2].replace(/^\\+/, ''); map.set(m[1], hex); } return map; } /** 合并:388 覆盖 229 */ function mergeMaps(base229, overlay388) { const unicode = new Map(base229); const source = new Map(); for (const k of base229.keys()) source.set(k, '2298093'); for (const [k, v] of overlay388) { unicode.set(k, v); source.set(k, '3882322'); } return { unicode, source }; } function buildCss({ unicode, source }) { const lines = [ '/* 由 scripts/merge-iconfont-from-cdn.mjs 生成;双字体与历史双 CDN 叠加顺序一致(388 覆盖同名类) */', '@font-face {', ' font-family: "iconfont-2298093";', " src: url('./font-2298093.woff2') format('woff2'),", " url('./font-2298093.ttf') format('truetype');", '}', '@font-face {', ' font-family: "iconfont-3882322";', " src: url('./font-3882322.woff2') format('woff2'),", " url('./font-3882322.ttf') format('truetype');", '}', '.iconfont {', ' font-size: 16px;', ' font-style: normal;', ' -webkit-font-smoothing: antialiased;', ' -moz-osx-font-smoothing: grayscale;', '}', '', ]; const sorted = [...unicode.entries()].sort((a, b) => a[0].localeCompare(b[0])); for (const [cls, hex] of sorted) { const fam = source.get(cls) === '3882322' ? 'iconfont-3882322' : 'iconfont-2298093'; const short = cls.replace(/^icon-/, ''); lines.push(`.${cls}:before {`); lines.push(` font-family: "${fam}" !important;`); lines.push(` content: "\\${hex}";`); lines.push('}'); lines.push(''); } return lines.join('\n'); } function buildJson({ unicode, source }) { const glyphs = [...unicode.entries()] .sort((a, b) => a[0].localeCompare(b[0])) .map(([cls, hex], idx) => { const font_class = cls.replace(/^icon-/, ''); const dec = parseInt(hex, 16); return { icon_id: String(100000 + idx), name: font_class, font_class, unicode: hex, unicode_decimal: dec, _source_project: source.get(cls), }; }); // e-icon-picker / 后端若校验字段,去掉非标准字段 for (const g of glyphs) delete g._source_project; return { id: 'merged-2298093-3882322', name: 'dvadmin3-merged', font_family: 'iconfont', css_prefix_text: 'icon-', description: '合并自 iconfont 项目 2298093 + 3882322,与菜单 icon 字段一致', glyphs, }; } async function main() { fs.mkdirSync(outDir, { recursive: true }); console.log('Fetching CSS…'); const [css229, css388] = await Promise.all([fetchText(URL_229_CSS), fetchText(URL_388_CSS)]); const map229 = parseIconRules(css229); const map388 = parseIconRules(css388); const merged = mergeMaps(map229, map388); console.log(`Icons: 2298093=${map229.size}, 3882322=${map388.size}, merged=${merged.unicode.size}`); console.log('Downloading font files…'); for (const f of FONT_FILES) { const dest = path.join(outDir, f.name); await downloadFile(f.url, dest); console.log(' saved', f.name); } const cssOut = buildCss(merged); const jsonOut = buildJson(merged); fs.writeFileSync(path.join(outDir, 'iconfont.css'), cssOut, 'utf8'); fs.writeFileSync(path.join(outDir, 'iconfont.json'), JSON.stringify(jsonOut, null, 2), 'utf8'); console.log('Wrote iconfont.css, iconfont.json'); } main().catch((e) => { console.error(e); process.exit(1); });