简介这份PPT资源面向网络拓扑图绘制人员、售前方案工程师及信息安全从业者聚焦于绿盟科技风格的图标素材整理解决Visio等工具中缺少统一规范网络设备图标的痛点。压缩包内仅含1个PPT文件体积约1.26MB以幻灯片形式集中呈现各类拓扑图图标便于直接复制到Visio或PPT中复用。内容涵盖NSFOCUS自有图标、Cisco产品图标路由器、交换机、集线器、ATM交换机等、IBM主机与集群控制器、WAN设备、终端与服务器、介质线路、建筑与行业场景等多组分类并配有目录页与致谢页结构清晰、检索方便。目前已有910人学习下载适合需要快速搭建规范拓扑图、统一图例风格或补充绿盟视觉素材的读者参考使用。1. 一份图标库 PPT 为什么值得当成工程资产来做很多团队做安全产品界面时图标都是随手从某个素材站扒一套改改颜色就上线。结果半年后要加一个新模块发现原图标库缺对应语义新图标风格又对不上最后整个界面像补丁摞补丁。我见过一个做态势感知大屏的项目前期图标是三个人分别找的上线评审时被指出「同一排里三个图标线宽都不一样」返工两周。所以当我看到「常用图标库_绿盟.ppt」这个标题时第一反应不是「这不就是个素材包」而是——它其实是一份图标资产的规格说明书哪些图标、什么语义、什么尺寸、什么配色、怎么命名、怎么交付。把这份 PPT 里的信息拆成可执行的工程规范才是它真正的价值。这篇笔记面向的是需要维护一套长期可用图标库的前端、UI 和交付工程师尤其是做安全类产品界面的团队。下面我会按「先立规范、再动手做、最后讲坑」的顺序把一套图标库从 PPT 变成代码资产的全过程讲清楚。2. 从 PPT 到图标资产先定语义分层和命名规则2.1 为什么图标库必须先做语义分层而不是先画图大多数人拿到图标需求第一件事是打开设计工具开始画。这是最容易翻车的地方。图标库的本质不是「一堆好看的图形」而是一套语义到图形的映射表。如果语义没定清楚画得再精致也会在扩展时崩掉。我一般会把图标按使用场景分成三层层级用途典型数量变更频率基础操作层增删改查、导入导出、刷新、关闭2040极低状态语义层成功、警告、危险、离线、加载中1020低业务对象层资产、漏洞、告警、策略、日志、拓扑节点3080中分层的好处是基础操作层可以跨产品复用状态语义层跟设计规范绑定业务对象层允许各产品线自己扩展但必须继承前两层的风格约束。PPT 里如果只给了图标清单没给分层那这份清单在工程上就是不可维护的——你没法判断新增一个图标该放哪、该谁审批。命名规则同样要在画图之前定死。常见做法是层级_语义_变体比如action_search、status_warning_filled、object_asset_server。变体后缀统一用_filled、_outlined、_two-tone三种不要出现_solid、_line、_duotone混用。这个规则看起来啰嗦但它是后面自动化脚本能跑起来的前提。2.2 把 PPT 里的图标清单转成结构化数据PPT 本身不是给机器读的所以第一步是把它变成一份结构化清单。我通常用一个 YAML 文件来承载因为它比 JSON 好写注释比 CSV 好表达嵌套。# icons.yaml —— 图标库的唯一数据源 version: 1.0 grid: 24 # 设计栅格 24x24 stroke_width: 1.5 # 描边宽度filled 变体忽略此项 layers: action: - name: action_search cn: 搜索 variants: [outlined, filled] tags: [查询, 检索] - name: action_export cn: 导出 variants: [outlined] tags: [下载, 输出] status: - name: status_warning cn: 警告 variants: [filled, two-tone] tags: [告警, 注意] object: - name: object_asset_server cn: 服务器资产 variants: [outlined, filled] tags: [主机, 节点]这份 YAML 是整个流程的单一事实来源。后面生成 SVG、生成字体、生成组件、生成文档全部从它派生。参数说明grid决定所有图标的画布尺寸必须统一否则对齐会出玄学问题stroke_width只对描边类变体生效filled 变体在脚本里要跳过这个字段variants数组决定这个图标要产出几个文件不要写all这种模糊值显式列出才能做校验。2.3 命名校验脚本在 CI 里拦住不合规的图标光有规则没用得让机器来查。下面这个脚本我一般放在仓库的scripts/目录配合 Git hook 或 CI 跑。# scripts/validate_icons.py import re import sys import yaml # 命名规则层级_语义_变体全小写下划线分隔 NAME_PATTERN re.compile(r^(action|status|object)_[a-z0-9](_[a-z0-9])*$) VALID_VARIANTS {outlined, filled, two-tone} def validate(path): with open(path, encodingutf-8) as f: data yaml.safe_load(f) errors [] seen set() for layer, items in data.get(layers, {}).items(): for item in items: name item[name] # 1. 命名格式校验 if not NAME_PATTERN.match(name): errors.append(f命名不合规: {name}) # 2. 前缀必须和所属层级一致 if not name.startswith(layer _): errors.append(f前缀与层级不符: {name} 在 {layer} 下) # 3. 重名校验 if name in seen: errors.append(f重复命名: {name}) seen.add(name) # 4. 变体合法性 for v in item.get(variants, []): if v not in VALID_VARIANTS: errors.append(f非法变体: {name} - {v}) return errors if __name__ __main__: errs validate(sys.argv[1] if len(sys.argv) 1 else icons.yaml) if errs: print(\n.join(errs)) sys.exit(1) print(图标清单校验通过)逻辑说明脚本做了四件事——命名格式、前缀与层级一致性、重名检测、变体白名单。参数上NAME_PATTERN里的(action|status|object)要和 YAML 里的layers键保持同步新增层级时两处都要改。失败时看什么CI 报错会直接打印不合规的图标名按提示改 YAML 即可不要绕过校验。这个脚本的价值在于它把「命名规范」从口头约定变成了硬约束新人提交图标时不会再靠 reviewer 肉眼盯。3. 用脚本把图标清单批量产出 SVG 和组件3.1 从 YAML 生成 SVG 骨架的批量脚本清单校验通过后下一步是产出实际图形。设计稿通常是 Figma 或 Sketch 导出的 SVG但导出文件里往往带着编辑器私有属性、多余的id、未合并的路径。我一般写一个清洗脚本把设计稿 SVG 统一成规范格式。# scripts/build_svg.py import os import re import yaml from pathlib import Path SRC_DIR Path(design/exports) # 设计稿导出目录 OUT_DIR Path(dist/svg) # 产物目录 # 需要剔除的编辑器私有属性 STRIP_ATTRS re.compile(r\s(data-name|id|class|xmlns:xlink)[^]*) def clean_svg(raw: str, size: int, stroke: float) - str: svg STRIP_ATTRS.sub(, raw) # 统一画布尺寸和 viewBox svg re.sub(rwidth[^]*, fwidth{size}, svg, count1) svg re.sub(rheight[^]*, fheight{size}, svg, count1) svg re.sub(rviewBox[^]*, fviewBox0 0 {size} {size}, svg, count1) # 统一描边宽度仅描边类 svg re.sub(rstroke-width[^]*, fstroke-width{stroke}, svg) return svg def build(manifest_path: str): data yaml.safe_load(open(manifest_path, encodingutf-8)) size, stroke data[grid], data[stroke_width] count 0 for layer, items in data[layers].items(): for item in items: for variant in item[variants]: src SRC_DIR / f{item[name]}_{variant}.svg if not src.exists(): print(f[缺失] {src}) continue out OUT_DIR / layer / f{item[name]}_{variant}.svg out.parent.mkdir(parentsTrue, exist_okTrue) out.write_text(clean_svg(src.read_text(encodingutf-8), size, stroke), encodingutf-8) count 1 print(f共产出 {count} 个 SVG) if __name__ __main__: build(icons.yaml)逻辑说明clean_svg做三件事——剔除编辑器私有属性、统一画布尺寸、统一描边宽度。参数上size和stroke直接读 YAML改一处全局生效。STRIP_ATTRS里的id一定要删否则多个 SVG 内联到同一页面时会互相覆盖样式这是最常见的翻车点之一。失败时看什么如果某个图标报「缺失」说明设计稿文件名和 YAML 里的name_variant对不上去design/exports目录核对命名即可。3.2 生成 React 组件让前端不再手写 SVGSVG 有了但前端直接用img引 SVG 会丢失颜色控制能力。常见做法是生成组件把fill和stroke暴露成 props。// scripts/gen-component.js const fs require(fs); const path require(path); const SRC dist/svg; const OUT src/components/icons; // 把 SVG 里的硬编码颜色替换成 currentColor function toComponent(name, svg) { const body svg .replace(/svg[^]*/, ) .replace(/\/svg/, ) .replace(/fill(?!none)[^]*/g, fillcurrentColor) .replace(/stroke(?!none)[^]*/g, strokecurrentColor) .trim(); const comp name.split(_).map(s s[0].toUpperCase() s.slice(1)).join(); return import React from react; export const ${comp} ({ size 24, color currentColor, ...rest }) ( svg width{size} height{size} viewBox0 0 24 24 fillnone stroke{color} {...rest} ${body} /svg ); ; } function walk(dir) { for (const f of fs.readdirSync(dir)) { const full path.join(dir, f); if (fs.statSync(full).isDirectory()) walk(full); else if (f.endsWith(.svg)) { const name f.replace(.svg, ); fs.mkdirSync(OUT, { recursive: true }); fs.writeFileSync(path.join(OUT, name .jsx), toComponent(name, fs.readFileSync(full, utf-8))); } } } walk(SRC); console.log(组件生成完成);逻辑说明核心是把 SVG 里的硬编码颜色替换成currentColor这样组件就能继承父级文字颜色也支持通过colorprop 覆盖。参数上size默认 24和 YAML 里的grid保持一致viewBox写死0 0 24 24如果以后改栅格这里要同步。失败时看什么如果生成的组件颜色不生效检查原 SVG 里是不是用了stylefill:#xxx这种内联样式正则匹配不到需要额外处理。3.3 图标字体方案什么时候该用什么时候别碰有些老项目还在用图标字体iconfont因为兼容性好、体积小。但我的血泪经验是新项目别用图标字体。原因有三个——字体渲染在不同系统上会有亚像素差异多色图标做不了无障碍支持差。只有在需要兼容很老的浏览器、或者图标数量极大且都是单色时才考虑字体方案。如果确实要做流程是SVG 清洗 → 转字体用常见的字体生成工具→ 生成 CSS 映射表。映射表要从 YAML 自动生成不要手写# 伪代码示意具体工具按团队现有链路选 svg2font --input dist/svg --output dist/font/icons.ttf --name icons生成的 CSS 里每个图标的content码点要和 YAML 里的name建立映射建议用脚本生成一份icon-map.json前端通过name查码点而不是直接写\e601这种魔法数字。这样以后增删图标前端代码不用改。4. 图标库落地时的避坑清单4.1 坑一同一图标在不同产品里语义漂移现象object_asset这个图标A 产品用来表示「主机资产」B 产品用来表示「所有资产」C 产品用来表示「资产分组」。用户跨产品使用时完全懵。原因图标库没有语义冻结机制各产品线按自己理解复用。解决在 YAML 里给每个图标加cn和tags字段并写一份语义说明文档。新增产品线要用某个图标时必须先在文档里确认语义一致不一致就新建图标不要复用。我一般还会在 CI 里加一条检查如果某个图标的tags被两个以上产品线引用就提示 review。4.2 坑二描边宽度不统一导致视觉重量不一致现象一排图标里有的看起来粗有的看起来细明明都是 24x24。原因设计稿导出时描边宽度没统一有的 1.5 有的 2脚本清洗时又只改了stroke-width属性没处理路径本身的视觉补偿。解决在 YAML 里锁定stroke_width清洗脚本强制覆盖。但要注意有些图标比如圆形在 1.5 描边下会显得比方形细这是光学错觉需要在设计阶段做视觉补偿脚本层面解决不了。常见做法是给圆形类图标单独加optical_compensation字段清洗时按字段微调。4.3 坑三SVG 内联后 id 冲突现象页面上同时用了两个图标其中一个的渐变或裁剪路径失效了。原因两个 SVG 里都有idclip0内联到同一 DOM 后浏览器只认第一个。解决清洗脚本里必须删掉所有id或者给每个id加文件名前缀。我倾向于直接删因为图标 SVG 里大部分id都是编辑器自动生成的删掉不影响渲染。如果确实有clipPath依赖id就改成id{name}_clip这种带前缀的形式。4.4 坑四图标清单和实际文件不同步现象YAML 里写了 80 个图标实际只产出 76 个缺的 4 个没人发现直到前端用的时候报错。原因设计稿交付和清单更新是两条线没有强制校验。解决在构建脚本里加一步「清单 vs 产物」比对数量对不上直接 fail。下面这段加在build_svg.py末尾即可# 构建后校验清单声明的变体数 vs 实际产出数 expected sum(len(i[variants]) for items in data[layers].values() for i in items) actual len(list(OUT_DIR.rglob(*.svg))) assert expected actual, f清单声明 {expected} 个实际产出 {actual} 个4.5 坑五颜色硬编码导致暗色模式失效现象产品要上暗色模式图标全是黑的在深色背景上看不见。原因SVG 里写死了fill#333组件生成时没替换成currentColor。解决组件生成脚本里的颜色替换正则要覆盖fill、stroke、style三种写法。另外fillnone和strokenone要排除否则会把不该替换的也换掉。上线前用暗色背景跑一遍视觉回归比看代码靠谱。5. 让图标库长期可维护的两个进阶技巧5.1 用视觉回归测试锁住图标渲染图标库最怕的是「改了一个图标影响了另一个」。我一般会引入视觉回归测试把每个图标渲染成固定尺寸的 PNG和基线图做像素比对差异超过阈值就报警。// 伪代码基于常见截图比对工具 const { toMatchImageSnapshot } require(jest-image-snapshot); expect.extend({ toMatchImageSnapshot }); test(icon visual regression, async () { for (const name of iconNames) { const png await renderToPng(name, { size: 48, color: #000 }); expect(png).toMatchImageSnapshot({ customSnapshotIdentifier: name, failureThreshold: 0.02, // 允许 2% 像素差异 failureThresholdType: percent, }); } });参数说明size用 48 而不是 24是为了放大细节差异24 像素下很多问题看不出来failureThreshold设 2%太低会因抗锯齿差异误报太高会漏掉真实问题。基线图要提交到仓库每次改图标后 review 差异图确认是有意改动再更新基线。5.2 图标检索页让设计师和前端自己找图标图标多了以后最大的成本是「找」。我一般会从 YAML 自动生成一个静态检索页支持按名称、中文名、标签搜索点击可复制组件名或 SVG 代码。# scripts/gen_gallery.py import yaml, json from pathlib import Path data yaml.safe_load(open(icons.yaml, encodingutf-8)) items [] for layer, lst in data[layers].items(): for i in lst: items.append({ name: i[name], cn: i.get(cn, ), tags: i.get(tags, []), layer: layer, variants: i[variants], }) html f!DOCTYPE html htmlheadmeta charsetutf-8title图标库/title/head body input idq placeholder搜索名称/中文/标签 stylewidth:320px;padding:8px div idgrid styledisplay:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:16px;margin-top:16px/div script const ICONS {json.dumps(items, ensure_asciiFalse)}; const grid document.getElementById(grid); function render(list) {{ grid.innerHTML list.map(i div styletext-align:center;border:1px solid #eee;padding:12px;border-radius:8px img src../dist/svg/${{i.layer}}/${{i.name}}_${{i.variants[0]}}.svg width32 height32 div stylefont-size:12px;margin-top:8px${{i.cn}}/div code stylefont-size:11px;color:#888${{i.name}}/code /div).join(); }} document.getElementById(q).addEventListener(input, e {{ const k e.target.value.toLowerCase(); render(ICONS.filter(i i.name.includes(k) || i.cn.includes(k) || i.tags.some(t t.includes(k)) )); }}); render(ICONS); /script /body/html Path(dist/gallery.html).write_text(html, encodingutf-8)逻辑说明脚本从 YAML 读数据生成一个纯静态 HTML不依赖任何后端。搜索支持名称、中文名、标签三个维度。参数上grid-template-columns的minmax(120px, 1fr)控制每列最小宽度图标多的时候会自动换行。这个页面可以直接部署到内网设计师和前端自己查省掉大量「这个图标叫什么」的沟通成本。5.3 版本管理图标库也要打 tag图标库是会被引用的资产一旦有产品用了某个图标就不能随便删或改名。我的习惯是每次发布打一个 git tag比如icons-v1.2.0删除或重命名图标必须走 major 版本并在 CHANGELOG 里写清楚「哪个图标被替换成了哪个」。前端依赖图标库时锁版本不要用latest。这样即使图标库迭代老产品也不会突然编译失败。最后说个我自己的教训早期做图标库时我总觉得「规范太麻烦先画了再说」结果第三个产品接入时光是统一命名和描边就花了两周。后来我把 YAML 清单和校验脚本作为第一步画图反而成了最轻松的部分。图标库这件事前期多花一天定规则后期能省一个月返工。希望帮到你。本文还有配套的精品资源点击获取