vue3按需加载动态icon

1、问题:

element-plus中icon跟element中的icon完全不同,使用也很多变化,最重要的一点就是按需动态加载了,主要用到unplugin-icons这个库

根据按需加载icon的使用方法,是无法满足动态加载的

<template>
        <IEpPlus />
</template>

因为比如侧边栏列表的icon等很多都需要动态加载,就会发现以下代码是无法按需加载icon的

<script setup >
let obj = reactive([{ icon: 'IEpPlus' }, { icon: 'IEpMinus' }, { icon: 'IEpHouse' }, { icon: 'IEpDelete' }])
</script>
<template>
        <div v-for="(item, i) in obj" :key="i">
            <component :is="item.icon" />
        </div>
</template>

2、思路:

加载动态图标主要靠vue的component组件,而component导入大致分为两种,一个是注册为组件,然后传入组件名称字符串,第二个则是直接传入组件或者是html

所以其实解决问题方式就可以分成两类去解决:1、注册组件。2、传入组件

3、处理

注册解决法:网上很多的方案都是注册为vue组件,然后再使用。这样做是可以解决问题,但我不是很推荐,因为你会把没有用到的icon也注册进去,那也没必要使用按需加载了,而且自定义图标要另外的插件注册

// main.ts
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

组件传入法:配合unplugin-auto-import和unplugin-vue-components使用,它们让你不用手写import,极其方便

//vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import path from 'path'
import Icons from 'unplugin-icons/vite'
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
import IconsResolver from 'unplugin-icons/resolver'
export default defineConfig({
  css: {
  },
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue', 'vue-router'],
      resolvers: [
        IconsResolver({
          enabledCollections: ['my', 'ep'],
        }),
        ElementPlusResolver()
      ],
    }),
    Components({
      resolvers: [
        IconsResolver({
          enabledCollections: ['my', 'ep'],
        }),
        ElementPlusResolver()
      ],
    }),
    Icons({
      compiler: "vue3",
      autoInstall: true,
      customCollections: {
        'my': FileSystemIconLoader('./src/assets/svg', svg => {
          svg = svg.replace(/fill="[A-Za-z0-9#(),]*"/g, '')
          svg = svg.replace(/width="[A-Za-z0-9]*"/g, '')
          svg = svg.replace(/height="[A-Za-z0-9]*"/g, '')
          svg = svg.replace(/^<svg /, '<svg fill="currentColor" ')
          return svg
        })
      }
    })
  ],
  resolve: {
    alias: {
      "~@": __dirname,
      "@": path.resolve(__dirname, "./src")
    }
  },
})

然后在组件中使用,不再是字符串的方式,而是直接引用

<script setup >
let obj = reactive([{ icon: IEpPlus }, { icon: IEpMinus }, { icon: IEpHouse }, { icon: IEpDelete }])
</script>

<template>
        <div v-for="(item, i) in obj" :key="i">
            <component :is="item.icon" />
        </div>
</template>

在js中使用也一样,比如在路由中使用,要加个shallowRef,不然会报错,最后把这个导入component就行了

//router.js
const routes = [
...
    {
    ...
        meta: {
            icon:shallowRef(IEpPlus)
}
    ...
     }
...
]

 如果是通过接口获取icon,比如后端发送[{ icon: 'IEpPlus' } ,{ icon: 'IEpMinus' } ]过来,要怎么处理呢

那可以设置一个全局的对象来中转,比如下面pinia配置的例子:

//pinia设置全局对象 根据自己需求设置
export const useCounterStore = defineStore('counter', {
    state: () => ({ 
        iconData:{
            //可以是引入的图标比如element-plus的ep
            'IEpPlus': IEpPlus,
            'IEpMinus': IEpMinus,
            ...
            //也可以是自定义的图标,比如my
            'IMyHome': IMyHome,
            'IMyAdd': IMyAdd
            ...
        }
     }),
    actions: {
        getIcon(str) {
            return this.iconData[str]
        },
    },
})

两种方法使用,一种是直接在v-for上使用,另一种是在获取数据时使用

<script setup >
import axios from 'axios';
import { useCounterStore } from '@/store'
const store = useCounterStore()
let data1=[],data2=[]
onMounted(()=>{
    //获取[{ icon: 'IEpPlus' } ,{ icon: 'IEpMinus' } ,{ icon: 'IMyHome' } ,{ icon: 'IMyAdd' } ]数据
    axios.get('/icon/xxx').then(res => {
     //处理
        data1=res.map(e=>{
            e.icon= store.getIcon(e.icon)
            return e
        })
     //不处理
        data2 = res
    })
})
</script>

<template>
    <!-- 方式一 -->
   <div v-for="(item,k) in data1" :key="k">
        <component :is="item.icon" />
   </div>
   <!-- 方式二 -->
    <div v-for="(item, k) in data2" :key="k">
        <component :is="store.getIcon(item.icon)" />
    </div>
</template>
posted @ 2023-03-09 23:25  Pavetr  阅读(4464)  评论(6编辑  收藏  举报