通用管理后台组件库-1-Icon组件集成

55 阅读3分钟

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>

实现效果:

image.png

(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>

实现效果:

image.png