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

news2025/5/10 7:41:06

在做项目时发现,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 name

  const SPECIAL_CHARS_REGEXP = /([:\-_. /]+(.))/g // special chars regexp
  const 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 } = opt

  function 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>/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)
    }
  }
}

上面提到一个 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.coloradmin.cn/o/2372131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型系列(四)--- GPT2: Language Models are Unsupervised Multitask Learners​

论文链接&#xff1a; Language Models are Unsupervised Multitask Learners 点评&#xff1a; GPT-2采用了与GPT-1类似的架构&#xff0c;将参数规模增加到了15亿&#xff0c;并使用大规模的网页数据集WebText 进行训练。正如GPT-2 的论文所述&#xff0c;它旨在通过无监督语…

ABP vNext + EF Core 实战性能调优指南

ABP vNext EF Core 实战性能调优指南 &#x1f680; 目标 本文面向中大型 ABP vNext 项目&#xff0c;围绕查询性能、事务隔离、批量操作、缓存与诊断&#xff0c;系统性地给出优化策略和最佳实践&#xff0c;帮助读者快速定位性能瓶颈并落地改进。 &#x1f4d1; 目录 ABP vN…

高品质办公楼成都国际数字影像产业园核心业务​

成都国际数字影像产业园的核心业务&#xff0c;围绕构建专业化的数字影像文创产业生态系统展开&#xff0c;旨在打造高品质、高效率的产业发展平台。 产业集群构建与生态运营 园区核心业务聚焦于吸引和培育数字影像及相关文创领域的企业&#xff0c;形成产业集聚效应。具体包…

MindSpore框架学习项目-ResNet药物分类-构建模型

目录 2.构建模型 2.1定义模型类 2.1.1 基础块ResidualBlockBase ResidualBlockBase代码解析 2.1.2 瓶颈块ResidualBlock ResidualBlock代码解释 2.1.3 构建层 构建层代码说明 2.1.4 定义不同组合(block&#xff0c;layer_nums)的ResNet网络实现 ResNet组建类代码解析…

【Spring Boot】Spring Boot + Thymeleaf搭建mvc项目

Spring Boot Thymeleaf搭建mvc项目 1. 创建Spring Boot项目2. 配置pom.xml3. 配置Thymeleaf4. 创建Controller5. 创建Thymeleaf页面6. 创建Main启动类7. 运行项目8. 测试结果扩展&#xff1a;添加静态资源 1. 创建Spring Boot项目 打开IntelliJ IDEA → New Project → 选择M…

学习spring boot-拦截器Interceptor,过滤器Filter

目录 拦截器Interceptor 过滤器Filter 关于过滤器的前置知识可以参考&#xff1a; 过滤器在springboot项目的应用 一&#xff0c;使用WebfilterServletComponentScan 注解 1 创建过滤器类实现Filter接口 2 在启动类中添加 ServletComponentScan 注解 二&#xff0c;创建…

雷赛伺服L7-EC

1电子齿轮比&#xff1a; 电机圈脉冲1万 &#xff08;pa11的值 x 4倍频&#xff09; 2电机刚性&#xff1a; pa003 或者 0x2003 // 立即生效的 3LED显示&#xff1a; PA5.28 1 电机速度 4精度&#xff1a; PA14 //默认30&#xff0c;超过3圈er18…

阅文集团C++面试题及参考答案

能否不使用锁保证多线程安全&#xff1f; 在多线程编程中&#xff0c;锁&#xff08;如互斥锁、信号量&#xff09;是实现线程同步的传统方式&#xff0c;但并非唯一方式。不使用锁保证多线程安全的核心思路是避免共享状态、使用原子操作或采用线程本地存储。以下从几个方面详…

AVL树:保持平衡的高效二叉搜索树

目录 一、AVL树的概念 1. 二叉搜索树的局限性 2. AVL树的定义 二、AVL树节点结构 三、AVL树的插入操作 1. 插入流程 2. 代码实现片段 四、AVL树的旋转调整 1. 左单旋&#xff08;RR型&#xff09; 2. 右单旋&#xff08;LL型&#xff09; 3. 左右双旋&#xff08;LR型…

Webpack基本用法学习总结

Webpack 基本使用核心概念处理样式资源步骤&#xff1a; 处理图片资源修改图片输出文件目录 自动清空上次打包的内容EslintBabel处理HTML资源搭建开发服务器生产模式提取css文件为单独文件问题&#xff1a; Css压缩HTML压缩 小结1高级SourceMap开发模式生产模式 HMROneOfInclud…

阿里云服务器数据库故障排查指南?

阿里云服务器数据库故障排查指南? 以下是针对阿里云服务器&#xff08;如ECS自建数据库或阿里云RDS等托管数据库&#xff09;的故障排查指南&#xff0c;涵盖常见问题的定位与解决方案&#xff1a; 一、数据库连接失败 检查网络连通性 ECS自建数据库 确认安全组规则放行数据库…

数图闪耀2025深圳CCFA中国零售博览会:AI+零售数字化解决方案引发现场热潮

展会时间&#xff1a;2025年5月8日—10日 地点&#xff1a;深圳国际会展中心&#xff08;宝安新馆&#xff09; 【深圳讯】5月8日&#xff0c;亚洲规模最大的零售行业盛会——2025 CCFA中国零售博览会在深圳盛大开幕。本届展会汇聚全球25个国家和地区的900余家参展商&#xff…

LeetCode 1722. 执行交换操作后的最小汉明距离 题解

示例&#xff1a; 输入&#xff1a;source [1,2,3,4], target [2,1,4,5], allowedSwaps [[0,1],[2,3]] 输出&#xff1a;1 解释&#xff1a;source 可以按下述方式转换&#xff1a; - 交换下标 0 和 1 指向的元素&#xff1a;source [2,1,3,4] - 交换下标 2 和 3 指向的元…

linux ptrace 图文详解(八) gdb跟踪被调试程序的子线程、子进程

目录 一、gdb跟踪被调试程序的fork、pthread_create操作 二、实现原理 三、代码实现 四、总结 &#xff08;代码&#xff1a;linux 6.3.1&#xff0c;架构&#xff1a;arm64&#xff09; One look is worth a thousand words. —— Tess Flanders 相关链接&#xff1a; …

游戏:用python写梦幻西游脚本(谢苏)

《梦幻西游》是一款受欢迎的网络游戏&#xff0c;许多玩家希望通过脚本来增强游戏体验&#xff0c;比如自动打怪、自动治疗等。本文将为您展示一个用Python编写简单《梦幻西游》自动打怪脚本的方案。 需求分析 1.1 具体问题 在《梦幻西游》中&#xff0c;玩家需要频繁与怪物进行…

Spring Boot 3.x集成SaToken使用swagger3+knife4j 4.X生成接口文档

说一说Spring Boot 3.X集成SaToken使用swagger3并使用第三方的knife4j踩过的坑&#xff0c;废话不多说直接上正题&#xff0c;SaToken的我就不贴了 第一步当然是要先导入相关的依赖&#xff0c;包括swagger和knife4j&#xff0c;如下 <dependency><groupId>com.gi…

用Python监控金价并实现自动提醒!附完整源码

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【星海网址导航】&#x1f4bb;香港大宽带-4H4G 20M只要36/月&#x1f449; 点此查看详情 在日常投资中&#xff0c;很多朋友喜欢在一些平台买点黄金&#xff0c;低买高卖赚点小差价。但黄金价格实时波动频繁&#xf…

ChatTempMail - AI驱动的免费临时邮箱服务

在当今数字世界中&#xff0c;保护在线隐私的需求日益增长。ChatTempMail应运而生&#xff0c;作为一款融合人工智能技术的新一代临时邮箱服务&#xff0c;它不仅提供传统临时邮箱的基本功能&#xff0c;还通过AI技术大幅提升了用户体验。 核心功能与特性 1. AI驱动的智能邮件…

掌握单元测试:提升软件质量的关键步骤

介绍 测试&#xff1a;是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。 阶段划分&#xff1a;单元测试、集成测试、系统测试、验收测试。 测试方法&#xff1a;白盒测试、黑盒测试及灰盒测试。 单元测试&#xff1a;就是针对最小的功能单元&#xff08;方法&…

YOLOv1模型架构、损失值、NMS极大值抑制

文章目录 前言一、YOLO系列v11、核心思想2、流程解析 二、损失函数1、位置误差2、置信度误差3、类别概率损失 三、NMS&#xff08;非极大值抑制&#xff09;总结YOLOv1的优缺点 前言 YOLOv1&#xff08;You Only Look Once: Unified, Real-Time Object Detection&#xff09;由…