日期 : 2025-12-12
技术栈: Vue 3 + TypeScript + iconfont
前言
在大型前端项目中,图标管理是一个看似简单却容易失控的问题。随着业务迭代,往往会出现多套图标方案并存的情况:有人用 SVG 文件,有人用 iconfont,有人直接用图片......这不仅增加了维护成本,还容易导致图标风格不统一、打包体积膨胀等问题。
本文将分享我们在 CMC Link IBS Web 项目中进行的一次图标系统重构实践,核心目标是:统一图标来源,降低维护成本,提升开发体验。
一、问题诊断:混乱的图标现状
1.1 现状分析
重构前,项目中存在两套并行的图标方案:
css
图标来源
├── iconfont(阿里图标库)
│ ├── iconfont.css → Font Class 模式
│ └── iconfont.js → Symbol 模式(支持彩色)
│
└── 本地 SVG 图标
└── src/assets/icons/ → 150+ 个 SVG 文件
└── vite-plugin-svg-spritemap 处理
1.2 痛点总结
| 问题 | 影响 |
|---|---|
| 双重维护 | 新增图标需要决定放哪里,老员工用 SVG,新员工用 iconfont |
| 处理逻辑复杂 | SVG 需要 SVGO 插件处理 fill/stroke/width/height 属性 |
| 彩色图标识别困难 | 需要通过文件名约定(c- 前缀)或内容分析来判断 |
| 构建依赖 | 额外引入 @spiriit/vite-plugin-svg-spritemap 依赖 |
| 心智负担 | 开发者需要了解两套方案的差异和适用场景 |
1.3 核心矛盾
markdown
开发效率 vs 技术债务
↓
每次新增图标都在累积技术债
↓
维护成本随项目规模线性增长
二、方案设计:单一数据源架构
2.1 设计原则
- 单一数据源:所有图标统一从 iconfont 获取
- 向后兼容:现有代码无需修改即可工作
- 渐进迁移:支持新旧写法并存,逐步过渡
- 开发体验优先:新增图标流程简化
2.2 架构设计
scss
┌─────────────────────────────────────────────────┐
│ 使用层 │
│ <SvgIcon name="search" /> (旧代码,无需修改) │
│ <CmcIcon name="icon-search" /> (新代码) │
└──────────────────┬──────────────────────────────┘
│
┌──────────────────▼──────────────────────────────┐
│ SvgIcon (兼容层) │
│ - 接收旧的 name/icon 属性 │
│ - 通过映射表转换为 iconfont 名称 │
│ - 自动判断彩色/单色 │
│ - 内部渲染 CmcIcon │
└──────────────────┬──────────────────────────────┘
│
┌──────────────────▼──────────────────────────────┐
│ CmcIcon (核心组件) │
│ - Font Class 模式(单色,可改颜色) │
│ - Symbol 模式(彩色,保留原色) │
└──────────────────┬──────────────────────────────┘
│
┌──────────────────▼──────────────────────────────┐
│ iconfont 资源 (单一数据源) │
│ - iconfont.css (Font Class) │
│ - iconfont.js (Symbol) │
└─────────────────────────────────────────────────┘
2.3 关键设计决策
决策1:为什么选择 iconfont 作为单一数据源?
| 方案 | 优势 | 劣势 |
|---|---|---|
| 本地 SVG | 完全可控、离线可用 | 需要构建处理、维护成本高 |
| iconfont | 在线管理、团队协作、支持彩色 | 依赖外部服务 |
选择 iconfont 的原因:
- 已有成熟的图标库(400+ 图标)
- 支持 Symbol 模式(彩色图标)
- 团队协作友好(设计师可直接上传)
- 无需额外构建插件
决策2:兼容层设计
不破坏现有代码是重构的底线。通过代理模式,让旧的 SvgIcon 组件内部调用新的 CmcIcon:
vue
<!-- SvgIcon.vue - 兼容层 -->
<script setup lang="ts">
import CmcIcon from '../CmcIcon/CmcIcon.vue'
import { getIconfontName, isColorfulIcon } from '../CmcIcon/icon-mapping'
// ... props 定义
const iconfontName = computed(() => getIconfontName(rawIconName.value))
const colorful = computed(() => isColorfulIcon(rawIconName.value))
</script>
<template>
<CmcIcon
:name="iconfontName"
:size="size"
:color="color"
:colorful="colorful"
/>
</template>
决策3:映射表策略
对于名称不一致的情况,通过映射表解决:
typescript
// icon-mapping.ts
export const SVG_TO_ICONFONT_MAP: Record<string, string> = {
'dingcangicon': 'icon-menu-dingcang',
'billoflading': 'icon-menu-tidan',
// ...
}
export const COLORFUL_ICONS = new Set([
'menu-chukou',
'menu-jinkou',
'USD', 'CNY', 'EUR',
// ...
])
三、实现细节
3.1 CmcIcon 核心组件
vue
<script lang="ts" setup>
interface Props {
name: string // 图标名称(需带 icon- 前缀)
size?: number | string // 尺寸,默认 16px
color?: string // 颜色(仅单色有效)
colorful?: boolean // 是否为彩色图标
}
const props = withDefaults(defineProps<Props>(), {
size: 16,
color: 'currentColor',
colorful: false,
})
</script>
<template>
<!-- 彩色图标:Symbol 模式 -->
<svg v-if="colorful" class="cmc-icon" :style="{ width: sizeValue, height: sizeValue }">
<use :xlink:href="`#${iconName}`" />
</svg>
<!-- 单色图标:Font Class 模式 -->
<i v-else class="cmc-icon iconfont-cmc" :class="iconName" :style="{ fontSize: sizeValue, color }" />
</template>
3.2 iconfont 的两种模式
Font Class 模式(单色图标):
- 通过 CSS 类名引用图标
- 支持
color属性动态改变颜色 - 文件:
iconfont.css
Symbol 模式(彩色图标):
- 通过 SVG
<use>引用 - 保留图标原始颜色
- 文件:
iconfont.js
vue
<!-- 单色:可通过 color 控制颜色 -->
<CmcIcon name="icon-search" color="red" />
<!-- 彩色:保留原始多色 -->
<CmcIcon name="icon-menu-chukou" colorful />
3.3 清理冗余代码
移除了不再需要的构建配置:
diff
// build/plugins.ts
- import VitePluginSVGSpritemap from '@spiriit/vite-plugin-svg-spritemap'
export function createVitePlugins() {
return [
// ...其他插件
- createSvgIconsPlugin(), // 移除 SVG 处理插件
]
}
- // 移除 138 行 SVG 处理代码
- function createSvgIconsPlugin() { ... }
- function processMonoIcon() { ... }
- function processRootIcon() { ... }
- function traverseSvgNodes() { ... }
四、收益分析
4.1 量化收益
| 指标 | 重构前 | 重构后 | 变化 |
|---|---|---|---|
| 图标来源 | 2 套 | 1 套 | -50% |
| 构建依赖 | +1 | 0 | -100% |
| plugins.ts 代码行数 | 241 | 103 | -57% |
| 新增图标步骤 | 5 步 | 3 步 | -40% |
4.2 定性收益
- 降低心智负担:开发者只需了解一套方案
- 简化新增流程:上传 iconfont → 更新资源 → 使用
- 减少构建时间:移除 SVGO 处理环节
- 代码更简洁:核心组件 < 100 行
4.3 新增图标流程对比
重构前(SVG 方案):
- 获取 SVG 文件
- 判断是单色还是彩色
- 如果单色,手动处理 fill/stroke 属性
- 放入对应目录(mono/ 或 colorful/)
- 使用
<SvgIcon name="xxx" />
重构后(iconfont 方案):
- 上传到 iconfont 项目
- 下载更新资源文件
- 使用
<CmcIcon name="icon-xxx" />
五、经验总结
5.1 重构原则
- 向后兼容是底线:通过兼容层保证现有代码正常工作
- 渐进式迁移:新代码用新方案,旧代码按需迁移
- 单一数据源:避免多源并存的混乱
- 简化优于完美:够用就好,不过度设计
5.2 技术选型思考
选择 iconfont 而非自建 SVG 方案的核心原因:
- 团队协作:设计师可直接在 iconfont 管理图标
- 成本效益:利用现有成熟方案,避免重复造轮子
- 彩色支持:Symbol 模式原生支持多色图标
5.3 适用场景
本方案适合:
- 已在使用 iconfont 的项目
- 团队规模中等以上,需要设计师协作
- 图标更新频繁的业务系统
不太适合:
- 对离线可用性要求极高的场景
- 图标需要复杂动画的场景
- 完全私有化部署、无法访问外网的环境
六、后续优化方向
- 自动化更新:编写脚本自动从 iconfont 拉取最新资源
- 类型安全:生成图标名称的 TypeScript 类型定义
- 按需加载:对于大型图标库,考虑按需加载策略
- 文档自动化:从 iconfont.json 自动生成图标文档
结语
图标系统看似是个小问题,但在大型项目中却能显著影响开发效率和代码质量。这次重构的核心思路是:识别技术债务 → 设计兼容方案 → 统一数据源 → 渐进式迁移。
希望本文的实践经验能为你的项目提供一些参考。记住,最好的架构不是最复杂的,而是最适合团队的。
本文基于公司项目的真实重构实践整理,如有问题欢迎讨论。