在Vue3项目中,我们经常需要使用各种图标来美化界面。传统做法是手动维护一个图标数组,但随着项目规模扩大,这种方式会变得非常麻烦。每次新增或删除图标都需要修改代码,既容易出错又影响开发效率。
我最近在一个后台管理系统项目中就遇到了这个问题。系统需要支持管理员自定义菜单图标,最初我手动维护了30多个图标的数组。但随着需求变更,图标数量很快突破100个,手动维护变得苦不堪言。更糟的是,设计师还要求图标可以热更新,不需要重新部署就能生效。
这时候Vite的import.meta.glob API就派上用场了。它可以动态扫描指定目录下的所有SVG文件,自动生成图标列表。实测下来,这种方式有三大优势:
首先确保你已经安装了Node.js 16+版本。然后通过以下命令创建项目:
bash复制npm create vite@latest my-project --template vue-ts
cd my-project
npm install
安装ElementPlus和相关的依赖:
bash复制npm install element-plus @element-plus/icons-vue
Vite默认可以处理SVG文件,但我们需要做一些额外配置来支持symbol方式的图标引用。在vite.config.ts中添加以下内容:
typescript复制import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
{
name: 'svg-transform',
transform(code, id) {
if (id.endsWith('.svg')) {
return `export default ${JSON.stringify(code)}`
}
}
}
]
})
这个配置会让Vite把SVG文件内容作为字符串导入,方便我们后续处理。
在src/assets目录下创建svg/menu文件夹,把所有菜单图标SVG文件放在这里。建议图标命名采用kebab-case风格,比如user-profile.svg。
每个SVG文件需要做一个小改造,在svg标签上添加id属性:
xml复制<svg id="user-profile" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<!-- 图标路径数据 -->
</svg>
在组件中,我们使用import.meta.glob来动态加载所有图标:
typescript复制import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent({
setup() {
const iconNames = ref<string[]>([])
const selectedIcon = ref('')
onMounted(async () => {
const modules = import.meta.glob('../assets/svg/menu/*.svg', {
eager: true,
as: 'raw'
})
for (const path in modules) {
const fileName = path.split('/').pop()?.replace('.svg', '') || ''
iconNames.value.push(fileName)
// 将SVG内容注入DOM
const svgContainer = document.createElement('div')
svgContainer.innerHTML = modules[path] as string
document.body.appendChild(svgContainer)
}
})
return { iconNames, selectedIcon }
}
})
这段代码做了三件事:
创建一个通用的IconSvg组件来渲染图标:
vue复制<template>
<svg class="svg-icon" aria-hidden="true">
<use :xlink:href="`#${name}`" />
</svg>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'IconSvg',
props: {
name: {
type: String,
required: true
}
}
})
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
现在我们可以实现完整的图标选择器了:
vue复制<template>
<el-select
v-model="selectedIcon"
placeholder="请选择图标"
style="width: 100%"
>
<template #prefix>
<el-icon v-if="selectedIcon" color="#606266">
<icon-svg :name="selectedIcon" />
</el-icon>
</template>
<el-option
v-for="name in iconNames"
:key="name"
:label="name"
:value="name"
>
<div style="display: flex; align-items: center">
<el-icon><icon-svg :name="name" /></el-icon>
<span style="margin-left: 8px">{{ name }}</span>
</div>
</el-option>
</el-select>
</template>
这里有几个关键点:
当图标数量很多时(比如超过50个),可以考虑分页加载。修改图标加载逻辑:
typescript复制const loadIcons = async (page = 1, pageSize = 20) => {
const allModules = import.meta.glob('../assets/svg/menu/*.svg')
const keys = Object.keys(allModules)
const pageModules = keys.slice((page - 1) * pageSize, page * pageSize)
const loaded = await Promise.all(
pageModules.map(path => import(/* @vite-ignore */ path))
)
// 处理加载的图标...
}
图标不显示问题:确保SVG文件有正确的id属性,并且已经成功注入到DOM中。可以在浏览器开发者工具中检查是否存在对应的symbol元素。
样式冲突问题:ElementPlus默认会给prefix插槽中的图标添加灰色,可以通过自定义样式覆盖:
css复制.el-select .el-icon {
color: inherit !important;
}
图标更新问题:开发环境下,Vite会热更新图标变化。但在生产环境,可能需要手动刷新页面才能看到新图标。
这个方案不仅适用于菜单图标选择,还可以应用到很多其他场景:
用户头像选择:让用户从预设头像库中选择
主题图标切换:不同主题使用不同的图标集
可视化配置:拖拽式界面构建工具中的图标选择
我在最近的项目中还扩展了这个方案,支持从远程服务器动态加载图标。基本思路是:
这种混合方案既保持了开发的便利性,又增加了灵活性。实际使用中,建议添加缓存机制避免重复下载。