自定义svg图标,封装成icon组件像element-ui icon组件似得,通过图标名称来使用图标,也可自定义类名控制。

  1. // 密码图标 名称为自己准备svg文件名称 下面需要loader配置 支持事件
  2. <svg-icon icon-class="password" class-name="custom-class" @click="fn"></svg-icon>
  3. // 支持使用外链的形式引入 svg。例如:
  4. <svg-icon icon-class="https://xxxx.svg />

4-1 什么是Svg Sprite

将多个 svg 打包成svg-sprite。svg雪碧图。类似于CSS中的Sprite技术。图标图形整合在一起,实际呈现的时候准确显示特定图标。
阅读资料

4-2 准备svg文件

根目录下创建src/icons目录 将svg图标文件放到@/icons/svg文件下面,svg文件压缩包
image.png
创建文件src/icons/index.ts 全局注册svg icon组件入口 现在还没开发 稍后

  1. import { App } from 'vue'
  2. import SvgIcon from '@/components/SvgIcon/index.vue'
  3. // 使用require.context 加载./svg目录下所有svg文件
  4. const files = import.meta.globEager<any>("./svg/*.svg")
  5. //如果上面这句不行就把上面这句注释掉,使用下面这句
  6. import'virtual:svg-icons-register'
  7. export default (app: App) => {
  8. // 全局注册svg-icon组件
  9. app.component('svg-icon', SvgIcon)
  10. }

如果.globEager报错有两种试配置vite/client,一种可以在tsconfig.json中配置 “types”: [“vite/client”],一种是在shims-vue.d.ts中添加在 /// 链接在这里 https://vitejs.cn/guide/features.html#client-types 另外还要globEager内部是依赖了fast-glob模块,也要安装一下 npm install fast-glob —save

src/icons/svgo.yml 配置文件
svgo svg 压缩处理优化配置文件。详情看4-7 svg优化

  1. plugins:
  2. - removeAttrs:
  3. attrs:
  4. - 'fill'
  5. - 'fill-rule'

4-3 配置vite-plugin-svg-icons

用来根据导入的 svg 文件自动生成 symbol 标签并插入 html

  1. npm install vite-plugin-svg-icons -D

修改 vite.config.js 配置文件

  1. import { defineConfig } from 'vite'
  2. import vue from '@vitejs/plugin-vue'
  3. import path, { resolve } from 'path';
  4. import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
  5. // https://vitejs.dev/config/
  6. export default defineConfig({
  7. plugins: [
  8. vue(),
  9. createSvgIconsPlugin({
  10. iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')],
  11. symbolId: 'icon-[dir]-[name]',
  12. inject: 'body-last',
  13. customDomId: '__svg__icons__dom__'
  14. })
  15. ],
  16. resolve: {
  17. alias: {
  18. '@': resolve(__dirname, 'src')
  19. }
  20. }
  21. })

4-4 开发svg icon组件

src/components下创建 SvgIcon/index.vue
image.png
SvgIcon/index.vue

  1. <template>
  2. <!-- 如果iconClass是带协议的图标链接 则通过style属性方式渲染-->
  3. <div
  4. class="svg-icon svg-external-icon"
  5. v-if="isExt"
  6. :style="styleExternalIcon"
  7. v-bind="$attrs"
  8. ></div>
  9. <!-- SVG icon 通过名称使用 -->
  10. <svg v-else :class="svgClass" aria-hidden="true" v-bind="$attrs">
  11. <!--
  12. SVG中的use元素可以调用其他SVG文件的元素,<use xlink:href="#symbolId"></use>
  13. -->
  14. <use :xlink:href="iconName" />
  15. </svg>
  16. </template>
  17. <script setup lang="ts">
  18. import { isExternal } from '@/utils/validate'
  19. import { computed } from 'vue'
  20. const props = defineProps<{ iconClass: string,className?:string }>()
  21. // 是否是带协议的图片链接
  22. const isExt = computed(() => isExternal(props.iconClass || ''))
  23. // 拼接成symbolId 在loader配置中指定了symbolId格式 icon-图标名称
  24. const iconName = computed(() => `#icon-${props.iconClass}`)
  25. // 添加类名 props.className外部传入自定义类名
  26. const svgClass = computed(() =>
  27. props.className ? `svg-icon ${props.className}` : 'svg-icon'
  28. )
  29. // 如果iconClass是带协议的图标链接 则通过style css属性方式渲染
  30. const styleExternalIcon = computed(() => ({
  31. mask: `url(${props.iconClass}) no-repeat 50% 50%`,
  32. '-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%`
  33. }))
  34. </script>
  35. <style scoped>
  36. .svg-icon {
  37. width: 1em;
  38. height: 1em;
  39. vertical-align: -0.15em;
  40. fill: currentColor;
  41. overflow: hidden;
  42. }
  43. .svg-external-icon {
  44. background-color: currentColor;
  45. mask-size: cover !important;
  46. display: inline-block;
  47. }
  48. </style>

vue3.0中关于$listeners移除 文档说明

@/utils/validate.ts 工具方法

创建src/utils/validate.ts

  1. // 判断路径是不是带协议的外链
  2. export const isExternal = (path: string): boolean => {
  3. return /^(https?:|mailto:|tel:)/.test(path)
  4. }

4-5 main.ts通过use安装icon组件

main.ts导入src/icons/index.ts 并use安装

  1. import { createApp } from 'vue'
  2. import router from './router/index';
  3. import ElementPlus from 'element-plus';
  4. import store from './store'
  5. import App from './App.vue'
  6. import 'normalize.css/normalize.css'
  7. import '@/styles/index.scss'
  8. import 'virtual:svg-icons-register'
  9. import initSvgIcon from '@/icons/index'
  10. createApp(App)
  11. .use(store)
  12. .use(ElementPlus)
  13. .use(initSvgIcon)
  14. .use(router)
  15. .mount('#app')

4-6 简单引用下

在views/dashboard/index.vue试用下

  1. <template>
  2. <div>
  3. <h1>Dashboard page</h1>
  4. <svg-icon icon-class="bug"></svg-icon>
  5. <!-- icon-class svg图标名称 class-name 额外的自定义类名 @click绑定事件 -->
  6. <svg-icon icon-class="404" class-name="custom-class" @click="sayHi"></svg-icon>
  7. </div>
  8. </template>
  9. <script setup lang="ts">
  10. const sayHi = () => {
  11. alert('hi svg')
  12. }
  13. </script>
  14. <style lang="scss">
  15. .custom-class { // 自定义样式404
  16. font-size: 200px;
  17. color: green;
  18. }
  19. </style>

icon-class分别是我们src/icons/svg里面的图标的名称
image.png

最终效果
image.png
点击404icon 出来弹窗
image.png

4-7 Svg优化

svgo是svg 压缩处理优化工具。我们很多网上下载或者Sketch导出的 svg 会有很多冗余无用的信息,大大的增加了 svg 的尺寸,我们可以使用svgo对它进行优化。

我们在创建src/icons/svgo.yml配置文件
安装svgo,注意需要指定版本号

  1. npm i -D svgo@1.3.2

package.json添加npm scripts

  1. {
  2. ...
  3. "scripts": {
  4. ...,
  5. "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
  6. },
  7. }

运行npm run svgo 压缩优化
svgo地址 https://github.com/svg/svgo

4-8 tsconfig.json配置

  1. {
  2. "compilerOptions": {
  3. "target": "esnext",
  4. "module": "esnext",
  5. "moduleResolution": "node",
  6. "esModuleInterop": true,
  7. "forceConsistentCasingInFileNames": true,
  8. "strict": true,
  9. "skipLibCheck": true,
  10. "types": ["vite/client"],
  11. "baseUrl": ".",
  12. "paths": {
  13. "@/*": ["src/*"]
  14. }
  15. },
  16. "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
  17. }

4-9 自动注册路由(选做)

可以使用vite-plugin-pages插件自动注册路由
vite-plugin-pages 会自动把 pages 目录中的 .vue 和 .md 文件生成对应的路由,并且我们可以利用 markdown 的 front-matter 来为路由提供一些额外信息

第一步安装相关的模块

npm install vite-plugin-pages @types/fs-extra @types/node fs-extra gray-matter -D

然后修改vite.config.js配置文件

  1. import { defineConfig } from 'vite'
  2. import vue from '@vitejs/plugin-vue'
  3. import path, { resolve } from 'path';
  4. import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
  5. import VitePages from "vite-plugin-pages";
  6. import fs from "fs-extra";
  7. //gray-matter 的功能,可以获取相关文件中的 front-matter,并将其处理为一个对象
  8. import matter from "gray-matter";
  9. export default defineConfig({
  10. plugins: [
  11. vue(),
  12. createSvgIconsPlugin({
  13. iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')],
  14. symbolId: 'icon-[dir]-[name]',
  15. inject: 'body-last',
  16. customDomId: '__svg__icons__dom__'
  17. }),
  18. VitePages({
  19. extensions: ["vue", "md"],//需要包含的文件类型,这里显然是 .vue 和 .md 文件
  20. pagesDir: "src/views",//寻找文件的目录,这里选择了项目根目录下的 views 目录
  21. extendRoute(route) {//提供一个方法,对每个文件产生路由做一些加工,这里是对 route.meta 的处理
  22. const path = resolve(__dirname, route.component.slice(1));
  23. const md = fs.readFileSync(path, "utf-8");
  24. const { data } = matter(md);
  25. route.meta = Object.assign(route.meta || {}, { frontmatter: data });
  26. return route;
  27. },
  28. }),
  29. ],
  30. resolve: {
  31. alias: {
  32. '@': resolve(__dirname, 'src')
  33. }
  34. }
  35. })

再修改路由配置

参考链接src\router\index.ts

  1. import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
  2. import Layout from '@/layout/index.vue'
  3. import children from "pages-generated"; // vite-plugin-pages 生成的路由信息
  4. const routes: Array<RouteRecordRaw> = [
  5. {
  6. path: '/',
  7. component: Layout,
  8. redirect: '/dashboard',
  9. children
  10. }
  11. ]
  12. const router = createRouter({
  13. history: createWebHashHistory(),
  14. routes
  15. })
  16. export default router

在 TS 中,直接从 pages-generated 导入会引起类型错误,需要在 tsconfig.jsoncompilerOptions.types 数组中加入 vite-plugin-pages/client 来加载对应的声明文件

本节参考源码

https://gitee.com/zhufengpeixun/vue3-admin2

对于每节文章有问题需要补充评论的 大家可以写在每节下方评论处 感谢