当前位置: 首页 > web >正文

将 iconfont 图标转换成element-plus也能使用的图标组件

在做项目时发现,element-plus的图标组件,不能像文档示例中那样使用 iconfont 的图标。经过研究发现,element-plus的图标封装成了vue组件,组件内容是一个svg,然后以组件的方式引入和调用图标。根据这个思路,我写了一个脚本,将iconfont图标,转换成vue组件,这样就可以完美地统一项目中图标的使用方式了。

ElementPlus 图标的两种使用方式

先来回顾一下 element-plus 使用图标的两种方式

  1. 第一种是直接以组件调用的方式使用
<el-icon><Plus /></el-icon>
  1. 第二种是作为组件的props使用
<el-button type="danger" :icon="Delete" circle />import { Delete } from '@element-plus/icons-vue'

读取 iconfont 图标内容

iconfont 能够生成svg图标,生成的内容在 iconfont.js 文件中
在这里插入图片描述
从图中可以看到,svg内容是一个字符串,只需要读取这个字符串,然后将每一个图标取出,就可以用来生成vue组件了。

核心代码:

import fs from 'fs'
import { JSDOM } from 'jsdom'
import { normalizePath } from 'vite'let file = ''
try {// 读取 iconfont.js 文件内容file = fs.readFileSync(normalizePath(sourcePath), 'utf-8')
} catch (e) {writeIconfontJsFile()modifyImportFile()return
}
// 提取svg字符串
const svgReg = /<svg>[\s\S]*?<\/svg>/g
const [svgStr] = file.match(svgReg)
// 解析svg字符串为dom对象
const dom = new JSDOM(svgStr)const document = dom.window.document
// 获取所有的symbol元素,每个symbol代表一个图标
const symbols = document.querySelectorAll('symbol')
if (!symbols.length) {return
}const iconsNames = []let str = `import { defineComponent, h } from 'vue'\n\n`symbols.forEach((symbol) => {// 获取图标名称,并转换为驼峰命名const iconName = toCamelCase(symbol.getAttribute('id')).replace(/_$/, '').replace(/-$/, '')iconsNames.push(iconName)// 获取viewBox和path,并生成对应的vue组件代码const viewBox = symbol.getAttribute('viewBox')const path = symbol.querySelectorAll('path')const pathArr = []path.forEach((p) => {const d = p.getAttribute('d')pathArr.push(`h('path', { d: '${d}' })`)})str += `const ${iconName} = defineComponent({ name: '${iconName}', render() { return h('svg', { viewBox: '${viewBox}' }, [${pathArr}]) }})\n\n`
})str += `export {\n\t${iconsNames.join(',\n\t')}\n}`
// 写入文件
writeIconfontJsFile(str)
modifyImportFile(iconsNames)

生成的结果如下

import { defineComponent, h } from 'vue'const IconChehuisekuai = defineComponent({ name: 'IconChehuisekuai', render() { return h('svg', { viewBox: '0 0 1024 1024' }, [h('path', { d: 'M64 347.552L320 128v448z' }),h('path', { d: 'M265.472 896v-112h377.824a200 200 0 1 0 0-400H240V272h403.296c172.32 0 312 139.68 312 312S815.616 896 643.296 896H265.472z' })]) }})export {IconChehuisekuai,
}

完整代码

以 vite 插件的方式使用

/*** 将iconfont转换为vue组件的插件* 在vite.config.js中使用* 如果引入新的iconfont后没有变化,重启试一下* @example* import transformIconfontToComponent from './script/vite-plugin/transform-iconfont-to-component.js'* plugins: [transformIconfontToComponent({ sourcePath: 'src/assets/iconfont/iconfont.js', targetPath: 'src/assets/vue-icons/iconfont.js', importPath: 'src/assets/vue-icons/index.js' })]** importPath文件中需要添加占位注释,如下:* 导入位置* /* import iconfont start *\/* /* import iconfont end *\/* 全局注册位置* /* register iconfont start *\/* /* register iconfont end *\/* 导出位置* /* export iconfont start *\/* /* export iconfont end *\/** 使用以上注释时,\ 需要删除掉*/import fs from 'fs'
import { JSDOM } from 'jsdom'
import { normalizePath } from 'vite'/*** 驼峰化, 支持:下划线(_)、小数点(.)、空格( )、冒号(:)、中横线(-)、斜杠(\) 等多种连续分隔符混合使用场景* @param {String} name 需要转换的名称* @param {Boolean} isFirstUppercase 是否首字母大写,默认否* @returns {String} 转换后的名称*/
function toCamelCase(name, isFirstUppercase = true) {if (!name) return nameconst SPECIAL_CHARS_REGEXP = /([:\-_. /]+(.))/g // special chars regexpconst result = name.replace(SPECIAL_CHARS_REGEXP, function (_, separator, letter, offset) {return offset ? letter.toUpperCase() : letter})// 大驼峰if (isFirstUppercase) {return result.replace(/^\w/, (p1) => p1.toUpperCase())}return result
}export default function (opt) {/*** @param {String} sourcePath 图标字体文件路径* @param {String} targetPath 生成的组件文件路径* @param {String} importPath 导入targetPath的路径*/const { sourcePath, targetPath, importPath } = optfunction writeIconfontJsFile(str = '') {// 写入文件fs.writeFileSync(normalizePath(targetPath), str)}function modifyImportFile(iconsNames = []) {// 修改注入文件,注册组件let indexFileStr = fs.readFileSync(normalizePath(importPath), 'utf-8')// 找到注入位置的标志符号,然后插入导入语句const importIconfontStartFlag = '/* import iconfont start */'const importIconfontEndFlag = '/* import iconfont end */'const index1 = indexFileStr.indexOf(importIconfontStartFlag)const index2 = indexFileStr.indexOf(importIconfontEndFlag)const importIconfontStr = iconsNames?.length? `\nimport {\n\t${iconsNames.join(',\n\t')}\n} from '${normalizePath(targetPath).replace(/^src/,'@')}'\n`: '\n'indexFileStr =indexFileStr.slice(0, index1 + importIconfontStartFlag.length) +importIconfontStr +indexFileStr.slice(index2)// 找到注册位置的标志符号,然后插入注册语句const registerIconfontStartFlag = '/* register iconfont start */'const registerIconfontEndFlag = '/* register iconfont end */'const index3 = indexFileStr.indexOf(registerIconfontStartFlag)const index4 = indexFileStr.indexOf(registerIconfontEndFlag)const registerIconfontStr = iconsNames?.length? `\n${iconsNames.map((name) => `app.component('${name}', ${name})`).join('\n')}\n`: '\n'indexFileStr =indexFileStr.slice(0, index3 + registerIconfontStartFlag.length) +registerIconfontStr +indexFileStr.slice(index4)// 找到导出位置的标志符号,然后插入导出语句const exportIconfontStartFlag = '/* export iconfont start */'const exportIconfontEndFlag = '/* export iconfont end */'const index5 = indexFileStr.indexOf(exportIconfontStartFlag)const index6 = indexFileStr.indexOf(exportIconfontEndFlag)const exportIconfontStr = iconsNames?.length? `\nexport {\n\t${iconsNames.join(',\n\t')}\n}\n`: '\n'indexFileStr =indexFileStr.slice(0, index5 + exportIconfontStartFlag.length) +exportIconfontStr +indexFileStr.slice(index6)fs.writeFileSync(normalizePath(importPath), indexFileStr)}return {name: 'transform-iconfont-to-component',buildStart() {let file = ''try {// 读取 iconfont.js 文件内容file = fs.readFileSync(normalizePath(sourcePath), 'utf-8')} catch (e) {writeIconfontJsFile()modifyImportFile()return}// 提取svg字符串const svgReg = /<svg>[\s\S]*?<\/svg>/gconst [svgStr] = file.match(svgReg)// 解析svg字符串为dom对象const dom = new JSDOM(svgStr)const document = dom.window.document// 获取所有的symbol元素,每个symbol代表一个图标const symbols = document.querySelectorAll('symbol')if (!symbols.length) {return}const iconsNames = []let str = `import { defineComponent, h } from 'vue'\n\n`symbols.forEach((symbol) => {// 获取图标名称,并转换为驼峰命名const iconName = toCamelCase(symbol.getAttribute('id')).replace(/_$/, '').replace(/-$/, '')iconsNames.push(iconName)// 获取viewBox和path,并生成对应的vue组件代码const viewBox = symbol.getAttribute('viewBox')const path = symbol.querySelectorAll('path')const pathArr = []path.forEach((p) => {const d = p.getAttribute('d')pathArr.push(`h('path', { d: '${d}' })`)})str += `const ${iconName} = defineComponent({ name: '${iconName}', render() { return h('svg', { viewBox: '${viewBox}' }, [${pathArr}]) }})\n\n`})str += `export {\n\t${iconsNames.join(',\n\t')}\n}`// 写入文件writeIconfontJsFile(str)modifyImportFile(iconsNames)}}
}

上面提到一个 importPath 的导入文件,这个文件将生成的图标组件集中导入,然后进行全局注册,内容如下:

/*** 自定义图标、iconfont图标、element-plus图标的注册文件** 注意:* iconfont 图标名称由iconfont名称驼峰化,后面不加_。如 icon-name => IconName** 示例:** 引入自定义图标:* import { SunnyFill } from '@/assets/vue-icons'** element-plus图标:* import { Search } from '@element-plus/icons-vue'*/
/* eslint-disable */
import * as ElementPlusIconsVue from '@element-plus/icons-vue'import MonthFill from './components/MonthFill.vue'// 下面这行注释不能删除,vite自定义插件会在这里插入内容
/* import iconfont start */
import {IconChehuisekuai,
} from '@/assets/vue-icons/iconfont.js'
/* import iconfont end */export default {install(app) {// 注册element-plus图标for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)}// 注册自定义图标app.component('MonthFill', MonthFill)// 下面这行注释不能删除,vite自定义插件会在这里插入内容/* register iconfont start */app.component('IconChehuisekuai', IconChehuisekuai)/* register iconfont end */}
}export { MonthFill }// 下面这行注释不能删除,vite自定义插件会在这里插入内容
/* export iconfont start */
export {IconChehuisekuai,
}
/* export iconfont end */
http://www.xdnf.cn/news/5033.html

相关文章:

  • Spring Cloud 以Gateway实现限流(自定义返回内容)
  • 经过多年发展,中国机械工业已经具备了独特的国际比较优势
  • 鱼眼摄像头(一)多平面格式 单缓冲读取图像并显示
  • DeepSeek“智”造:解锁旅游行业新玩法
  • 【Spring AI 实战】基于 Docker Model Runner 构建本地化 AI 聊天服务:从配置到函数调用全解析
  • 手撕红黑树的 左旋 与 右旋
  • 全球首套100米分辨率城市与农村居住区栅格数据(2000-2020)
  • AI文旅|暴雨打造旅游新体验
  • 如何优化系统启动时间--基于米尔瑞萨MYD-YG2LX开发板
  • linux ptrace 图文详解(八) gdb跟踪被调试程序的子线程、子进程
  • Python 中方法命名中下划线的使用规则
  • 深入解析:思维链模型在大语言模型中的应用与实践
  • 力扣-21.合并两个有序链表
  • 抓取大站数据与反爬策略
  • 掌握单元测试:提升软件质量的关键步骤
  • 基于HTML+JavaScript+CSS实现教学网站
  • 免布线视频桩:智慧城市停车降本增效的破局利器
  • 进入虚拟机单用户模式(Linux系统故障排查)
  • 用前端视角理解 GraphQL 与 REST 的互补逻辑
  • AD原理图复制较多元器件时报错:“InvalidParameter Exception Occurred In Copy”
  • 神经元和神经网络定义
  • 设置GO程序在离线情况下读取本地缓存的模块
  • Rust 中的 Move、Copy 和 Clone:深度剖析
  • 深入探索Laravel框架中的Blade模板引擎
  • python中的celery和其他分布式任务队列
  • 数据结构每日一题day17(链表)★★★★★
  • 公开模型一切,优于DeepSeek-R1,英伟达开源Llama-Nemotron家族
  • Linux系统使用vscode格式化shell脚本
  • spring5.x讲解介绍
  • LeetCode-双指针-盛最多水的容器