Icon组件集成
常用插件:@iconify/vue & @iconify/json、vite-plugin-svg-icons、unplugin-icons
常用图标库:iconfont、iconify、elementplus、antdesign
1. @iconify/vue
(1) 在线使用
import { Icon } from '@iconify/vue'
<Icon icon="la:js" width="64" height="64" />
(2) 离线使用:下载svg图片、svg代码、@iconify/json中的json图标集
2. iconfont
(1) 在线使用
// iconfont,我的项目中链接地址,可放在index.html中,直接使用图标
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_3529472_eaokahih66k.css">
也可封装成组件NetIcon.vue
<template>
<i :class="className"></i>
</template>
<script lang="ts" setup>
import type { NetIconProps } from './type'
// iconfont在线图标,iconfont中我的项目,在线链接,使用项目中图标,可以直接显示图标
// <link rel="stylesheet" href="//at.alicdn.com/t/c/font_3529472_eaokahih66k.css">
const props = withDefaults(defineProps<NetIconProps>(), {
url: '//at.alicdn.com/t/c/font_3529472_eaokahih66k.css',
prefix: 'iconfont-',
type: '',
fontFamily: 'iconfont'
})
onBeforeMount(() => {
// <link rel="stylesheet" href="//at.alicdn.com/t/c/font_3529472_eaokahih66k.css">
const existLink = document.querySelector(`link[href="${props.url}"]`)
if (!existLink) {
const link = document.createElement('link')
link.href = props.url
link.rel = 'stylesheet'
document.head.appendChild(link)
}
})
const className = computed(() => `${props.fontFamily} ${props.prefix}${props.type}`)
</script>
使用时:
<NetIcon url="//at.alicdn.com/t/c/font_3529472_eaokahih66k.css" type="wuwangluo" />
(2)离线使用:下载单独的svg或者svg样式包
3. 使用vite-plugin-svg-icons插件
说明:主要针对离线情况,本地svg图片的使用
(1)插件配置
import { defineConfig, loadEnv } from 'vite'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
// 也可以手动加载特定文件
return {
plugins: [
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]'
}),
]
}
})
(2)封装svg-icon组件,使用本地svg文件
<template>
<svg aria-hidden="true">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script lang="ts" setup>
// vite-plugin-svg-icons插件,配置icon文件夹路径,直接使用icon
const props = defineProps({
prefix: {
type: String,
default: 'icon'
},
type: {
type: String,
required: true
},
color: {
type: String,
default: 'currentColor'
}
})
const symbolId = computed(() => `#${props.prefix}-${props.type}`)
</script>
使用时:
// 结合unocss原子样式使用,方便改颜色和宽高
<svg-icon type="biaoqing01" class="text-blue w-15 h-15" />
4. Icon 图标集合案例
(1)Elementplus icon图标仿写
//IconList.vue
<template>
<ul
:class="[
type === 'picker'
? 'grid grid-cols-[repeat(auto-fill,minmax(1.825rem,1fr))]'
: 'flex flex-wrap',
'border-l',
'border-t',
'rounded'
]"
>
<li
:class="[
'border-r border-b flex flex-col items-center justify-center cursor-pointer',
itemClass
]"
v-for="(i, index) in iconData"
:key="index"
@click="handleClick(collection + ':' + i, index)"
>
<Icon
:icon="collection + ':' + i"
:class="[
iconClass,
{ [activeClass]: modelValue ? modelValue === collection + ':' + i : choose === index }
]"
></Icon>
<div class="mt-2 text-sm" v-if="showText">{{ toCamelCase(i) }}</div>
</li>
</ul>
</template>
<script setup lang="ts">
// import json from '@iconify/json/json/ep.json'
// console.log(Object.keys(json.icons))
// 将ep下的图标名称放在icon-ep.json文件内,做element图标静态展示
import data from './icon-ep.json'
import { loadIcons, Icon } from '@iconify/vue'
import type { IconListType } from './type'
// 选的图标回显
const modelValue = defineModel()
const props = withDefaults(defineProps<IconListType>(), {
iconData: () => data,
collection: 'ep',
iconClass: 'text-3xl',
itemClass: 'w-1/8 hover:bg-sky-100 pt-5 pb-5',
activeClass: '',
showText: true,
type: 'list'
})
onBeforeMount(async () => {
// 批量预加载图标
await loadIcons(props.iconData)
})
const choose = ref(-1)
// 图标字符串名称转为驼峰格式,如ep-xxx => EpXxx
function toCamelCase(str: string): string {
// 处理空字符串
if (!str) return ''
// 按连字符分割字符串
const parts: string[] = str.split('-')
// 对每个部分进行处理
const result: string[] = parts.map((part: string) => {
// 如果是空字符串,直接返回
if (!part) return ''
// 获取首字母
const firstChar: string = part.charAt(0)
// 如果是数字,保持原样
if (/\d/.test(firstChar)) {
return firstChar + part.slice(1).toLowerCase()
}
// 如果是字母,首字母大写,其余小写
return firstChar.toUpperCase() + part.slice(1).toLowerCase()
})
// 连接所有部分
return result.join('')
}
const emits = defineEmits(['click'])
// 点击获取svg代码或图标名称
const handleClick = async (icon: string, index: number) => {
// 选中给个颜色
choose.value = index
// 双向数据绑定,用于回显选中的图标
modelValue.value = icon
emits('click', icon)
}
</script>
<style scoped></style>
IconList使用,ep-icon-list.vue
<template>
<el-switch
v-model="copyTypeFlag"
class="mb-2"
active-text="复制Icon名称"
inactive-text="复制SVG图标"
/>
<IconList @click="handleClick" />
</template>
<script setup lang="ts">
import { loadIcon } from '@iconify/vue'
const copyTypeFlag = ref(true)
const source = ref('')
const { copy, copied } = useClipboard({ source })
// 复制图标编码转为svg
function objectToSvg(obj: any) {
const { width, height, body, hFlip, vFlip, rotate, top, left } = obj
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"
transform="translate(${left},${top}) rotate(${rotate})${hFlip ? ' scaleX(-1, 1)' : ''}${vFlip ? ' scaleY(1, -1)' : ''}">${body}</svg>`
return svgString
}
const handleClick = async (icon: any) => {
if (!copyTypeFlag.value) {
// 复制svg代码
const svgString = await loadIcon(icon)
source.value = objectToSvg(svgString)
} else {
// 复制icon名称
source.value = icon
}
copy()
copied && ElMessage.success('复制成功')
}
</script>
<style scoped></style>
实现效果:
(2)实现一个弹窗选图标的案例
// IconPicker.vue
<template>
<div>
<el-button type="primary" @click="() => toggle(true)">
<slot>选择图标</slot>
</el-button>
<!-- 选择图标弹窗 -->
<el-dialog title="选择图标" v-model="show" width="49%">
<IconList
v-model="test"
item-class="hover:bg-sky-100"
icon-class="text-2xl"
:show-text="false"
activeClass="text-[#409EFF]"
@click="handleClick"
type="picker"
/>
<div class="py-2 mt-2 flex align-middle">
<!-- 设置动态颜色和图标大小 -->
<el-color-picker v-model="color" class="mr-5" />
<el-input-number v-model="num" :step="1" />
</div>
<!-- 选中图标预览 -->
<div class="mt-2 flex items-center">
<span class="pr-2">选中图标:</span>
<Icon :icon="iconRef" :style="{ color, fontSize: `${num}px` }" />
<span class="pl-2">{{ iconRef }}</span>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="() => toggle(false)">取 消</el-button>
<el-button type="primary" @click="handleConfirm">确 定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import type { IconPickerSubmitDataType } from './type'
const [show, toggle] = useToggle(false)
const color = ref('#409eff')
const num = ref(16)
const iconRef = ref('')
const test = ref('ep:apple')
const emit = defineEmits<{
submit: [IconPickerSubmitDataType]
cancle: []
}>()
const handleClick = (icon: string) => {
iconRef.value = icon
}
// 确定选择图标
const handleConfirm = () => {
toggle(false)
emit('submit', { icon: iconRef.value, color: color.value, fontSize: num.value })
}
</script>
<style scoped></style>
使用IconPicker,ep-icon-picker.vue
<template>
<div>
<IconPicker @submit="handleSubmit" />
</div>
</template>
<script setup lang="ts">
const handleSubmit = (data: string) => {
console.log('🚀 ~ handleSubmit ~ data:', data)
}
</script>
<style scoped></style>
实现效果: