Azure图标集实战:SVG命名、四种接入、主题与避坑体检
发布时间:2026/10/23 19:33:15 锦皓数字建站

简介面向前端开发、界面设计与技术文档编写者的 SVG 图标素材合集聚焦 Azure 与 Microsoft 相关产品标识同时收录多种品牌徽标与抽象通用符号可用于云服务架构图、产品界面、演示文稿与文档配图的快速取用。压缩包约 2.57MB上游暂未提供文件总数与类型明细内容以 SVG 矢量文件为主全部经过清理与优化并启用了 removeDimensions 插件便于直接嵌入页面或导入设计工具后自由缩放而不失真。三套主要 Azure 图标集之间存在部分重叠各自又保留独有图标可按需选取省去自行合并去重的麻烦。资源整理自公开互联网与微软相关站点作者已说明不主张所有权与版权仅供合理使用范围内的参考。已有 220 人学习下载适合希望统一视觉风格、减少重复绘制成本的开发者与设计师。1. 一套 Azure 图标集解决的到底是什么问题从截图拼图到可缩放矢量做云架构图和运维后台的人多半踩过同一个坑拓扑图里的虚拟机、存储、网关图标是从不同地方截图拼起来的放大导出 PDF 边缘全糊产品线换个主题色还得整套重画。要根治靠的不是多找几张高清图而是一套命名规整、矢量可缩放、能按产品线检索的 SVG 图标集iconset。这份 Azure 与 Microsoft 系图标集合把散落的产品符号收成统一的 18×18 画布矢量文件编号可排序、文件名可检索、内联进页面还能跟着主题变色。适合三类人——画架构图与方案文档的技术写作者、做云管平台和监控大盘的前端、需要把图标塞进 PDF 报告和 PPT 的售前运维。后面按拆包看结构、接进页面、主题接管、踩坑排查、上线前体检的顺序走每一步都能直接抄。2. 拆包看结构命名编码、viewBox 与 defs 里的三个细节拿到压缩包后最先要弄清的不是“哪个图标好看”而是这批 SVG 到底以什么形态存在。很多人解压后双击打开某个文件看到一片空白或者黑乎乎一坨就判断文件损坏了——不是坏了而是这类图标把颜色、尺寸、上下文都交给了宿主页面浏览器单独打开时没有任何外部环境给它补位。所以第一步永远是先跑两条命令把文件“看不懂”的部分量化出来再决定怎么入库。2.1 目录分层与文件名编码把编号当检索键用Azure 架构图标的发布形态通常是平铺目录文件名形如00001-icon-service-App-Services.svg前缀是五位编号中间是类别标识后面是产品名的 kebab-case 写法。这套编码并非装饰它是稳定的排序键也是稳定的检索键。产品改名时编号不动按字母序排就会乱成一锅粥按编号排历史列表永远是连续的。入库前我会先建一张映射表把“文件名能提供的信息”和“页面真正需要的信息”分开字段含义示例前台用途prefix五位分类编号00001图标选择器排序、分组kind图标类别icon-service/icon-general区分产品图标与通用符号slug产品名 kebab-casevirtual-machines组件 id、symbol id、检索关键词ext扩展名.svg构建期白名单过滤真正落到代码里前台只认slug编号只活在选择器里。这样一来产品名改一次只需要改索引文件不用动页面里几十处use引用。很多人图省事直接拿文件名当组件名等到Virtual Machines被改成Virtual Machine那天整个仓库的 grep 就开始了。批量规范化文件名有个容易翻车的点产品名里带空格、括号、大写混排直接mv会把带空格的路径拆成两个参数。稳妥写法是用find -print0配合read -d # 批量规范化产品名转 kebab-case统一小写去掉首尾连字符 mkdir -p ./icons find ./raw-icons -name *.svg -print0 | while IFS read -r -d f; do base$(basename $f .svg) # tr 转小写sed 把非字母数字压成单个连字符再剪掉首尾多余的 - slug$(printf %s $base | tr [:upper:] [:lower:] \ | sed -E s/[^a-z0-9]/-/g; s/^-//; s/-$//) cp $f ./icons/${slug}.svg done-print0与read -r -d 是一对用 NUL 而不是换行做分隔符路径里有空格也不会被切开。-r关掉反斜杠转义IFS保持为空避免read把行首行尾的空白吃掉。sed里两段s/^-//和s/-$//必须分开写写成s/^-|-$//g在 BSD sed 上行为不一致。这段跑完再diff一下文件数量确认没有因为重名被覆盖——cp是静默覆盖的不会报错。2.2 viewBox 与画布尺寸为什么不能直接看 width/height这批图标绝大多数用viewBox0 0 18 18也就是 18 单位见方的画布。顶层有时带width18 height18有时干脆没有——没有反而是好事说明尺寸完全交给 CSS。判断该按什么倍数渲染看的是 viewBox不是文件头里的 width。18 这个数字很关键。它不是一个“看起来随便定的”值而是一个能被 2、3、6、9 整除的栅格。渲染到 18px、36px、54px 时图形边缘恰好压在整数像素上一旦你把它塞进 16px 的容器缩放比变成 0.888原来落在整数坐标上的描边就落到 0.44 像素这种位置浏览器做抗锯齿效果就是边缘发灰、细线糊成一片。这是最常见的“图标不清晰”来源跟文件质量毫无关系。入库前先跑一条统计命令把画布不一致的文件全揪出来# 统计每个文件的 viewBox 分布找出非 0 0 18 18 的异类 grep -oh viewBox[^]* icons/*.svg | sort | uniq -c | sort -rn | head -20 # 再看有多少文件在顶层硬编码了 width/height会妨碍 CSS 控制 grep -l svg[^]*\bwidth icons/*.svg | wc -lgrep -o只输出匹配段-h去掉文件名前缀这样uniq -c才能正确计数。如果结果里出现0 0 48 48或0 0 24 24这类画布说明图标来自不同批次混用会让同一行图标视觉重量不一致通常做法是统一重设 viewBox 或者至少在前端按画布分档设置尺寸别硬塞进同一个width。我一般会把尺寸档位固定成画布整数倍写进设计变量里而不是让每个页面自己写 px:root { /* 18 是画布基准尺寸只取整数倍避免半像素抗锯齿 */ --icon-size-xs: 18px; --icon-size-sm: 24px; /* 非整数倍但视觉重量可控 */ --icon-size-md: 36px; --icon-size-lg: 54px; }如果业务确实必须用 16/20 这类尺寸工具栏按钮常见那就接受轻微模糊或者改用描边更粗的那一档图标。这是取舍不是 bug。2.3 defs、渐变与 id每个文件都是一个独立命名空间第三个细节藏在defs里。产品图标里不少带渐变、裁剪、遮罩结构大致是这样svg xmlnshttp://www.w3.org/2000/svg viewBox0 0 18 18 defs linearGradient idg1 x10 y10 x21 y21 stop offset0 stop-color#50a0e0/ stop offset1 stop-color#0b5a94/ /linearGradient clipPath idc1rect x2 y2 width14 height14 rx2//clipPath /defs path dM2 3h14v12H2z fillurl(#g1) clip-pathurl(#c1)/ /svg单独打开这个文件时g1和c1的作用域是整份文档天然不冲突。可一旦你把两个这样的 SVG 内联进同一个 HTMLDOM 里就有了两个idg1。浏览器遇到重复 id 时只取第一个结果第二个图标也用上了第一个的渐变——视觉上就是“串色”。这个问题的隐蔽之处在于它时好时坏页面里图标顺序一换串色的对象就变了排查时容易怀疑到缓存和构建工具头上。所以处理策略要前置如果走内联路线必须在生成阶段做 id 作用域化第 3 章给脚本如果走img或 sprite 外部引用路线天然隔离不用管。选型在拆包阶段就该定下来别等做完一半再改。另外留意 svgo 的cleanupIds插件它默认会把g1压成a、b、c这种极短 id单文件体积是下来了但每个文件都叫a内联后冲突概率 100%。这不是 svgo 的错是它假设你在做独立文件。压缩配置里要么关掉cleanupIds的minify要么加prefix这个坑在第 5 章会展开。3. 接进页面的四条路img、background、inline 与 symbol sprite 怎么选拆包阶段定的 harness 决定后面所有工作量。图标接入方式没有绝对优劣只有“要不要改色”“要不要复用”“要不要进构建”这三问的答案不同。我按改色需求和复用规模两条轴来选基本不会错。3.1 img 与 CSS background最省事但颜色是死的!-- 装饰性图标alt 留空屏幕阅读器跳过 -- img classicon src/icons/virtual-machines.svg alt width18 height18 !-- 语义性图标alt 写清楚它代表什么不要写“图标” -- img classicon src/icons/storage-account.svg alt存储账户 width18 height18img的边界很明确SVG 在图片上下文里是一份独立文档CSS 的fill、color进不去只能用filter曲线救国。所以凡是需要 hover 变色、禁用态变灰、深色模式换色的场景这条路直接排除。它的优势是零配置、天然隔离、浏览器缓存友好适合纯文档类页面和邮件模板。background版本同理只是把图标当装饰纹理用.icon-vm { display: inline-block; width: 18px; height: 18px; /* contain 保证不裁切no-repeat 防平铺center 对齐 */ background: url(/icons/virtual-machines.svg) center / contain no-repeat; }一个细节background方案下图标不算文档内容无障碍工具完全读不到。要么加roleimg和aria-label在宿主元素上要么老老实实留一段可见文字。别拿background去承载“唯一的语义信息”。3.2 内联 SVG能改色、能加动效代价是 id 污染内联是唯一能让图标完整继承 CSS 的方案。把文件内容直接贴进模板根节点给 class路径颜色交给currentColorbutton classtoolbar-btn typebutton svg classicon viewBox0 0 18 18 roleimg aria-label新建虚拟机 path dM2 3h14v12H2z fillcurrentColor/ /svg span新建虚拟机/span /button.icon { width: 18px; height: 18px; flex: none; /* 防止在 flex 容器里被文字挤扁 */ vertical-align: -0.125em; /* 让基线跟文字对齐具体值随字号微调 */ color: #0b6a9e; /* 通过 color 驱动 currentColor */ } .toolbar-btn:hover .icon { color: #084c73; } .toolbar-btn[disabled] .icon { color: #9aa4b0; }flex: none这条很容易被忽略。图标放在 flex 行里时默认flex-shrink: 1容器一挤宽高就被压缩成非整数渲染立刻发虚。vertical-align的负值是把图标往下压让它的视觉中心跟小写字母 x 的高度对齐不同字体取值不同别照抄。代价就是 2.3 节说的 id 冲突。解决办法是在模板渲染前做一次 id 作用域化用一个稳定的 scope 前缀用 slug 或它的短哈希保证同一图标多次插入时前缀一致import re def scope_ids(svg: str, scope: str) - str: 给内联 SVG 的 id 与所有引用加唯一前缀避免同页图标互相污染。 scope 用 slug 或其短哈希同一图标多次插入时前缀稳定不会重复定义。 for old in set(re.findall(rid([^]), svg)): new f{scope}-{old} svg svg.replace(fid{old}, fid{new}) # 三种引用写法都要覆盖url(#x)、xlink:href#x、href#x svg svg.replace(furl(#{old}), furl(#{new})) svg svg.replace(fxlink:href#{old}, fxlink:href#{new}) svg svg.replace(fhref#{old}, fhref#{new}) return svg这段的顺序有讲究必须先处理url(#x)再处理href#x否则url(#x)被href规则误伤的概率虽然低但字符串已经变了replace就找不到了。用set去重是必要的同一个 id 可能在defs和use里各出现一次。这个函数放在构建期跑不要放到浏览器运行时——线上每渲染一个图标做一次正则替换列表页几十上百个图标就是明显的卡顿。3.3 symbol sprite use一次注入全站复用图标规模超过三四十个、页面里反复出现同一批时内联会让 HTML 体积膨胀得很难看。这时用 sprite把每个 SVG 包成symbol id...整份文档注入一次后面全是引用。# svg-sprite 生成 symbol 形式的雪碧图产物为单个 icons.svg npx svg-sprite \ --symbol \ --symbol-dest dist \ --symbol-sprite icons.svg \ icons/*.svg--symbol指定输出形式--symbol-dest是产物目录--symbol-sprite是产物文件名。生成的 symbol id 默认取自文件名正好和 2.1 节的 slug 对上。引用侧svg classicon aria-hiddentrue use href/dist/icons.svg#virtual-machines/use /svgaria-hiddentrue是必须的use会被读屏软件当成一个空图形加了这个属性它才会被跳过语义交给旁边的可见文字。这里有个绕不开的限制部分浏览器在file://协议或跨域场景下不允许use引用外部文档图标直接不显示控制台只有一条警告。稳妥做法是在应用启动时把 sprite 拉回来内联注入// 启动时把 sprite 注入 DOM规避外部 use 在协议/同源上的限制 fetch(/dist/icons.svg) .then(res res.text()) .then(txt { const box document.createElement(div); box.style.display none; // 不占布局只提供 symbol 定义 box.innerHTML txt; document.body.insertBefore(box, document.body.firstChild); });用innerHTML是因为symbol定义必须被解析进 DOM 才生效textContent塞进去只是一段文本。前提是 sprite 由你自己构建、内容可信如果图标来自外部上传这条路径必须换成带白名单的解析器不能直接innerHTML。3.4 打包器插件把压缩和 symbol 化塞进构建流程手写构建脚本适合一次性迁移长期维护还是交给打包器。以 Vite 为例vite-plugin-svg-icons能在构建期完成 svgo 压缩、symbol 包装和自动注入三件事// vite.config.js import path from node:path import { createSvgIconsPlugin } from vite-plugin-svg-icons export default { plugins: [ createSvgIconsPlugin({ // 图标源目录插件会扫描其中所有 .svg iconDirs: [path.resolve(process.cwd(), src/icons)], // 生成的 symbol id 模板[name] 会被替换为文件名 symbolId: icon-[name], svgoOptions: { plugins: [ { name: preset-default, params: { // 关键保留 viewBox否则图标缩放会退化成按 width/height 拉伸 overrides: { removeViewBox: false } } } ] } }) ] }三个参数里最容易出事的是svgoOptions。preset-default自带一组默认开启的插件其中removeViewBox在某些版本里是开着的历史包袱它会把viewBox删掉只留宽高图标在响应式容器里就完全失去等比缩放能力。overrides是唯一可靠的关法别指望在顶层写removeViewBox: false能生效。symbolId用icon-[name]是为了和手写use的命名习惯对齐团队里混用两套命名是最容易出“找不到图标”的原因。4. 主题与状态色用 currentColor 和 CSS 变量接管图标配色图标接进来只是开始真正决定它能不能长期用的是配色策略。这批图标分两类单色符号线条、简单形状和多色产品图标带渐变、多色块。前者应该完全交给 CSS后者必须保留原色。混在一起用同一套规则必然翻车。4.1 单色图标接管currentColor 是唯一正解单色图标的路径颜色应该全部写成fillcurrentColor这样颜色由最近的color属性决定天然继承、天然支持 hover 和禁用态。原文件里如果写死了fill#0b6a9e需要在入库时批量替换——注意只替换单色图标多色图标动了就废了。批量替换可以用 svgo 的convertColors但它会替换所有颜色值用之前必须先按目录把单色图标挑出来// svgo.config.js —— 只对 mono 目录生效的压缩配置 module.exports { plugins: [ { name: preset-default, params: { overrides: { removeViewBox: false, // 保 viewBox cleanupIds: { minify: false } // 保留 id内联时自己加前缀 } } }, { name: removeDimensions }, // 去掉顶层 width/height尺寸交给 CSS { name: convertColors, params: { currentColor: /^#(0b6a9e|1a1a1a|333333)$/i } // 只转指定的深色 } ] }convertColors的currentColor参数接受正则只把匹配到的色值换成currentColor其余保留。这比无差别替换安全得多。removeDimensions去掉的是顶层宽高viewBox留着两者分工明确前者让 CSS 接管尺寸后者保证缩放比例。即使路径上写了fill也可以用 CSS 强制覆盖但作用域必须收窄/* 只对单色图标类生效绝不能作用于多色产品图标 */ .icon--mono path, .icon--mono rect, .icon--mono circle { fill: currentColor; } .icon--mono [stroke] { stroke: currentColor; }CSS 属性选择器抓[stroke]是为了处理描边式图标——这类图标fillnone靠stroke出形只改 fill 完全没反应。很多人卡在这里以为图标坏了其实是抓错了属性。4.2 深色模式与多色图标不能一键反色单色图标在深色模式下只需换变量值:root { --icon-primary: #0b6a9e; --icon-muted: #6b7280; } media (prefers-color-scheme: dark) { :root { --icon-primary: #7cc4ea; /* 深色底上提亮保持对比度 */ --icon-muted: #98a2b3; } } .icon { color: var(--icon-primary); }用prefers-color-scheme而不是 JS 切 class是因为前者在首屏渲染前就生效不会出现“先亮后暗”的闪烁。如果产品需要手动切换主题那就把变量挂在html[data-themedark]上两套并存媒体查询只作为默认值。多色图标在深色底上是另一回事。这批产品图标里带浅灰、白色元素的不少直接放到深色背景上会“缺一块”看起来像是渲染失败。常见做法不是反色而是给它垫一个浅色底片.icon-tile { display: inline-grid; place-items: center; width: 36px; height: 36px; border-radius: 6px; background: #f3f6fa; /* 多色图标永远放在可控底色上 */ } media (prefers-color-scheme: dark) { .icon-tile { background: #1b2430; } /* 只做轻微提亮别用 invert(1)渐变会彻底变形 */ .icon-tile .icon { filter: brightness(1.15) saturate(0.95); } }place-items: center是align-itemsjustify-items的简写一行搞定居中。filter那条要克制brightness超过 1.2 白色区域会过曝saturate低于 0.9 品牌色就不准了。invert(1)这种操作对多色渐变图标是灾难渐变方向和色相会一起翻掉看起来像另一个产品。4.3 状态语义先定色板再谈图标图标颜色一旦散落在各页面硬编码三个月后没人说得清“为什么这里用蓝色那里用青色”。我一般先定一张状态色板图标只引用语义变量不直接写色值语义CSS 变量浅色值深色值典型场景主操作--icon-primary#0b6a9e#7cc4ea工具栏、主按钮次要--icon-muted#6b7280#98a2b3辅助操作、说明文字旁成功--icon-success#15803d#4ade80运行中、健康警告--icon-warning#b45309#fbbf24降级、配额接近上限危险--icon-danger#b42318#f87171停止、删除、异常/* 图标只消费语义变量页面不出现裸色值 */ .icon--success { color: var(--icon-success); } .icon--danger { color: var(--icon-danger); }这张表的价值在于约束当有人想在页面里写color: #15803d时review 阶段就能拦下来让他改成icon--success。图标本身的形状不变颜色换主题时整体跟着走不需要逐个文件改。5. 避坑与排查五个让图标当场翻车的现场这一章全是踩过的坑按“现象 → 原因 → 解决”写每一条都能对上具体文件和一个可验证的判断方法。遇到类似症状时先按顺序核对比盲改配置快得多。5.1 压缩后图标变形或整块变黑现象跑完 svgo一部分图标从原来的渐变形状变成纯黑方块或者边框消失只剩主体。原因preset-default里几个插件默认开启组合起来破坏了引用关系。cleanupIds把linearGradient idg1压成ida同时把fillurl(#g1)同步改成url(#a)——单文件没问题但只要同一页面出现第二个被压过的图标两个a撞车后者的渐变引用就指向了前者。removeViewBox则会把画布信息删掉缩放直接按 width/height 拉伸比例一错就变形。还有一种更隐蔽的removeUnknownsAndDefaults会删掉“看起来是默认值”的属性某些描边宽度和fill-rule就在它的清理范围内。解决在 svgo 配置里显式覆盖不要用默认值赌运气。removeViewBox: false必关cleanupIds要么minify: false要么加prefix改动后必须做一次视觉回归——拿压缩前后的文件列表逐个渲染截图对比只看文件体积下降是自欺欺人。稳妥的验证方式是把同一批图标全部内联到一张测试页上一次性看有没有串色和变形比单文件打开有效得多。5.2 同一页面多个图标串色现象单独打开每个图标都正常内联到同一页后有的图标用了别人的渐变有的图标渐变完全丢失。原因2.3 节说过的 id 冲突。浏览器的url(#x)解析走的是整份文档重复 id 时取文档中第一个匹配项。图标数量越多、顺序越不稳定表现越随机所以经常被误判成“样式加载顺序问题”。解决内联前跑 3.2 节的scope_idsscope 用 slug。验证方法很直接在控制台执行document.querySelectorAll([id])然后统计重复值一条命令就能确认// 在控制台粘贴返回页面上所有重复 id 及其出现次数 const seen new Map(); document.querySelectorAll([id]).forEach(el { seen.set(el.id, (seen.get(el.id) || 0) 1); }); console.table([...seen].filter(([, n]) n 1).map(([id, n]) ({ id, count: n })));若列表为空但仍有串色那就不是 id 问题往 CSS 的fill继承方向查——父级svg上的fill会被没写 fill 的 path 继承。5.3 小尺寸下边缘发虚、细线断裂现象18px 画布的图标放到 16px 或 20px 的按钮里看起来糊一层灰1px 描边偶尔断开。原因2.2 节讲过的非整数倍缩放。18 缩到 16 是 0.888描边落在 0.44、1.33 这种位置抗锯齿后就是灰边。另外如果图标根节点被父容器flex压缩成 15.6px 这种值问题会更严重因为浏览器内部还会再做一次舍入。解决第一把尺寸改成画布整数倍18/36/54第二给图标加flex: none防止被挤压第三确实必须用 16px 的场景改用描边更粗的那一档图标或者接受轻微模糊。有个反面做法是加shape-renderingcrispEdges它会让曲线和圆角直接变成锯齿得不偿失只在纯直角图标上偶尔可用。5.4 改了 fill 完全没反应现象CSS 里写了.icon path { fill: red }页面上颜色纹丝不动开发者工具里能看到规则被划掉或者根本没应用。原因通常有三种。一是原文件路径上带了内联stylefill:#xxx内联样式优先级高于外部选择器只能靠!important或者构建期清掉。二是 CSS 规则作用在img引用的 SVG 上——那是独立文档选择器根本进不去这条规则从一开始就不可能生效。三是原文件里带style块里面写了.cls-1{fill:#xxx}它在 SVG 文档内部生效外部选择器打不过它。解决构建期统一处理别在运行时硬碰。用 svgo 的inlineStyles和minifyStyles把style块内联到属性上再统一改写成currentColor确认接入方式不是img检查文件里有没有残留style。判断方法很简单grep -c style 文件和grep -c style 文件两个都是 0 才说明干净。5.5 导出 PDF 或打印时图标消失现象页面里显示正常CtrlP 打印预览或者导出 PDF 后图标位置只剩空白或者变成一串方框。原因use引用的外部 sprite 在打印渲染器里加载时机不同常常来不及解析background-image默认也不打印。还有一种图标尺寸依赖em单位打印样式表把字号重置成pt后图标被算成了极小值。解决打印样式里给图标显式回退。最省事的做法是打印时改用内联 SVG 或 data URI 形式的 background同时把尺寸单位写死成pxmedia print { /* 外部 sprite 在打印渲染器里不可靠改用内联背景并锁死像素尺寸 */ .icon-sprite { display: none; } .icon-fallback { display: inline-block; width: 18px; height: 18px; background: url(/icons/virtual-machines.svg) center / contain no-repeat; } }导出前一定要用真实打印预览验证一遍尤其是浏览器自带的“另存为 PDF”它的渲染路径跟window.print()不完全一致。只看屏幕效果就交付这类问题一定会在客户那边爆出来。6. 上线前跑一遍体检40 行脚本卡住 90% 的图标问题前面五章的问题八成可以在提交前用一个静态检查脚本拦下来。我把它挂在pre-commit上跑一次不到一秒比事后翻车再回头查便宜太多。脚本不追求全只查四件事文件名规范、viewBox 存在、顶层宽高是否清除、id 是否重复跨文件查重因为内联才是主要用法。import re, sys from pathlib import Path from xml.etree import ElementTree as ET ## 文件名规范五位编号 类别 kebab-case 产品名 NAME_RE re.compile(r^\d{5}-icon-(service|general)-[a-z0-9](-[a-z0-9])*\.svg$) def check(path: Path, id_owner: dict) - list[str]: errs, raw [], path.read_text(encodingutf-8) root ET.fromstring(raw) if not NAME_RE.match(path.name): errs.append(命名不符合 00000-icon-service-name.svg) if viewBox not in root.attrib: # 缺 viewBox 无法等比缩放 errs.append(缺少 viewBox) for attr in (width, height): # 顶层宽高会挡住 CSS if attr in root.attrib: errs.append(f顶层硬编码 {attr}) for el in root.iter(): # 跨文件 id 查重 _id el.get(id) if _id and _id in id_owner and id_owner[_id] ! path.name: errs.append(fid {_id} 与 {id_owner[_id]} 冲突) elif _id: id_owner[_id] path.name return errs def main(root_dir: str) - int: id_owner, bad {}, 0 for p in sorted(Path(root_dir).rglob(*.svg)): errs check(p, id_owner) if errs: bad 1 print(f[FAIL] {p.name}: {; .join(errs)}) print(fchecked done, {bad} file(s) with issues) return 1 if bad else 0 if __name__ __main__: sys.exit(main(sys.argv[1] if len(sys.argv) 1 else ./icons))NAME_RE里的[a-z0-9](-[a-z0-9])*比[a-z0-9-]严格能挡住a--b和a-这类脏名字。id_owner字典跨文件累积同一个 id 出现在两个文件里就立刻报冲突——这是 5.2 节那条坑的前置拦截。返回值用sys.exit透出才能在 CI 和pre-commit里被正确识别为失败。跑法就一行接入 husky 时把python scripts/check_icons.py ./src/icons写进pre-commit钩子即可。误报率高的规则要慎用比如“禁止任何fill属性”就过于激进多色图标必须有 fill。规则宁少勿滥一条稳定生效的规则比十条天天被绕过有用得多。最后说个习惯。早些年我接手一个云管后台图标是运营同学从各处凑的交付前没人管上线一周后客户反馈“导出 PDF 图标全没了”我和同事通宵翻了两百多个文件最后发现是三个文件带了各自的内联style块导致 CSS 覆盖失效、打印回退也没写。从那以后我每次接图标资源第一件事就是跑一遍体检脚本第二件事是内联到一张测试页上肉眼过一遍第三步才是往业务里接。前面两步加起来不到十分钟能省掉的返工往往是一整天。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。