某前端基础库 前端图标系统完整分析文档
项目版本: v8.1.0
文档生成时间: 2025-01-19
技术栈: Vue 3 + TypeScript + Iconify + Lucide Vue Next
��� 目录
1. 架构概览
某前端基础库 采用 Iconify 作为图标核心引擎,结合 Lucide Vue Next 图标集,构建了一个分层、可扩展的图标系统。
┌─────────────────────────────────────────────────────────────────────────┐
│ 应用层 (Applications) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ admin │ │ wflow │ │ mobile │ │ file-mgr │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼────────────────┼───────────┘
│ │ │ │
┌─────────┴────────────────┴────────────────┴────────────────┴───────────┐
│ 组件层 (Component UI) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ VbenIcon │ │ IconPicker │ │ IconButton │ │
│ │ (业务组件) │ │ (图标选择器) │ │ (图标按钮) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼───────────────────────────────┘
│ │ │
┌─────────┴────────────────┴────────────────┴───────────────────────────────┐
│ 业务层 (@lib/icons) │
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
│ │ iconify/index.ts │ │ svg/load.ts │ │
│ │ (Iconify 在线图标注册) │ │ (自定义 SVG 图标加载) │ │
│ │ • MdiGithub │ │ • SvgBellIcon │ │
│ │ • MdiWechat │ │ • SvgCardIcon │ │
│ │ • ... │ │ • ... │ │
│ └──────────────┬──────────────┘ └──────────────┬──────────────┘ │
└─────────────────┼────────────────────────────────┼───────────────────────┘
│ │
┌─────────────────┴────────────────────────────────┴───────────────────────┐
│ 核心层 (@core/icons) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ • createIconifyIcon() - 图标工厂函数 │ │
│ │ • addIcon() / addCollection() - 图标注册 API │ │
│ │ • listIcons() - 图标列表查询 │ │
│ │ • lucide.ts - Lucide 图标集导出 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────┴─────────────────────────────────────────┐
│ 引擎层 (@iconify/vue) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ • Icon - 核心渲染组件 │ │
│ │ • addIcon() - 本地图标注册 │ │
│ │ • Iconify API 集成 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────┘核心设计理念
| 层级 | 职责 | 依赖 |
|---|
| 应用层 | 业务逻辑,直接使用图标组件 | Component UI, @lib/icons |
| 组件层 | 提供业务友好的图标组件封装 | @core/icons |
| 业务层 | 注册项目特定的图标 | @core/icons |
| 核心层 | 提供图标工厂函数和核心 API | @iconify/vue |
| 引擎层 | 图标渲染和 Iconify API 集成 | 无 |
2. 核心依赖
{
"@iconify/vue": "^4.1.1",
"lucide-vue-next": "^0.454.0"
}| 依赖包 | 版本 | 用途 |
|---|---|---|
@iconify/vue | ^4.1.1 | Iconify 图标引擎,提供核心渲染组件 |
lucide-vue-next | ^0.454.0 | Lucide 图标集,提供常用的 UI 图标 |
3. 目录结构
主项目/packages/
├── @core/base/icons/ # 核心图标包
│ ├── package.json
│ ├── build.config.ts
│ ├── dist/ # 构建输出
│ └── src/
│ ├── create-icon.ts # 图标工厂函数
│ ├── lucide.ts # Lucide 图标导出
│ └── index.ts # 统一导出
│
├── icons/ # 业务图标包
│ ├── package.json
│ ├── README.md
│ └── src/
│ ├── iconify/
│ │ └── index.ts # Iconify 在线图标注册
│ ├── svg/
│ │ ├── load.ts # SVG 自动加载器
│ │ ├── index.ts # SVG 图标导出
│ │ └── icons/ # SVG 文件目录
│ │ ├── antdv-logo.svg
│ │ ├── avatar-1.svg
│ │ ├── avatar-2.svg
│ │ ├── avatar-3.svg
│ │ ├── avatar-4.svg
│ │ ├── bell.svg
│ │ ├── cake.svg
│ │ ├── card.svg
│ │ └── download.svg
│ ├── icons/
│ │ └── empty-icon.vue # Vue 组件图标
│ └── index.ts # 统一导出
│
└── component-ui/src/icon/ # 图标 UI 组件
└── src/
└── icon.vue # VbenIcon 组件
其他相关位置:
├── @core/ui-kit/shadcn-ui/src/components/icon/
│ └── icon.vue # ShadcnUI Icon 组件 (支持 SVG URL、远程图标)
│
├── effects/common-ui/src/components/
│ └── icon-picker/
│ ├── icon-picker.vue # 图标选择器组件
│ └── icons.ts # Iconify API 获取工具
│
└── effects/layouts/src/widgets/
└── global-search/ # 全局搜索 (使用图标)4. 核心实现
4.1 createIconifyIcon 工厂函数
文件位置: @core/base/icons/src/create-icon.ts
这是整个图标系统的核心工厂函数,用于创建 Iconify 图标组件。
import { defineComponent, h } from 'vue';
import { Icon } from '@iconify/vue';
/**
* 创建 Iconify 图标组件
* @param icon - Iconify 图标标识符 (如: "mdi:github", "ant-design:home")
* @returns Vue 组件
*/
function createIconifyIcon(icon: string) {
return defineComponent({
name: `Icon-${icon}`,
setup(props, { attrs }) {
return () => h(Icon, { icon, ...props, ...attrs });
},
});
}
export { createIconifyIcon };工作原理:
- 接收 Iconify 图标标识符(如
"mdi:github") - 创建一个 Vue 组件,组件名称为
Icon-{icon} - 使用
h()函数渲染底层的Icon组件 - 透传所有 props 和 attrs
使用示例:
import { createIconifyIcon } from '@core/icons';
export const MdiGithub = createIconifyIcon('mdi:github');
export const AntDesignHome = createIconifyIcon('ant-design:home');
// 使用
<MdiGithub class="size-6" />4.2 SVG 自动加载器
文件位置: icons/src/svg/load.ts
自动扫描并加载 svg/icons/ 目录下的所有 SVG 文件,将其转换为 Iconify 图标。
import type { IconifyIconStructure } from '@core/icons';
import { addIcon } from '@core/icons';
let loaded = false;
if (!loaded) {
loadSvgIcons();
loaded = true;
}
/**
* 解析 SVG 字符串为 Iconify 图标结构
*/
function parseSvg(svgData: string): IconifyIconStructure {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(svgData, 'image/svg+xml');
const svgElement = xmlDoc.documentElement;
// 提取 SVG 内容(过滤文本节点)
const svgContent = [...svgElement.childNodes]
.filter((node) => node.nodeType === Node.ELEMENT_NODE)
.map((node) => new XMLSerializer().serializeToString(node))
.join('');
// 解析 viewBox
const viewBoxValue = svgElement.getAttribute('viewBox') || '';
const [left, top, width, height] = viewBoxValue.split(' ').map((val) => {
const num = Number(val);
return Number.isNaN(num) ? undefined : num;
});
return {
body: svgContent,
height,
left,
top,
width,
};
}
/**
* 自定义的 SVG 图片转化为组件
* @example ./svg/avatar.svg
* <Icon icon="svg:avatar"></Icon>
*/
async function loadSvgIcons() {
// 使用 Vite 的 import.meta.glob 自动导入所有 SVG
const svgEagers = import.meta.glob('./icons/**', {
eager: true,
query: '?raw',
});
await Promise.all(
Object.entries(svgEagers).map((svg) => {
const [key, body] = svg as [string, string | { default: string }];
// ./icons/xxxx.svg => xxxxxx
const start = key.lastIndexOf('/') + 1;
const end = key.lastIndexOf('.');
const iconName = key.slice(start, end);
return addIcon(`svg:${iconName}`, {
...parseSvg(typeof body === 'object' ? body.default : body),
});
}),
);
}工作原理:
- 使用
import.meta.glob自动扫描./icons/**目录 - 使用
?raw查询参数获取 SVG 原始字符串 - 解析 SVG 文件名作为图标名称(如
bell.svg→svg:bell) - 解析 SVG 的
viewBox属性获取尺寸信息 - 提取 SVG 内部内容(移除
<svg>标签本身) - 使用
addIcon()注册到 Iconify 系统
SVG 文件示例:
<!-- bell.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 419.23 419.23">
<circle cx="210.66" cy="209.62" r="203.61" fill="#fbc907"/>
<path d="..." fill="#f3a70f"/>
<!-- 更多 SVG 内容 -->
</svg>使用方式:
// 方式 1: 通过注册的组件
import { SvgBellIcon } from '@lib/icons';
<SvgBellIcon />
// 方式 2: 通过 Iconify 标识符
import { Icon } from '@iconify/vue';
<Icon icon="svg:bell" />4.3 VbenIcon 组件
文件位置: component-ui/src/icon/src/icon.vue
业务层图标组件,封装了 @iconify/vue 的 Icon 组件,提供更多业务特性。
<script setup lang="ts">
import { computed } from 'vue';
import { Icon } from '@iconify/vue';
interface Props {
// 自定义类名(支持字符串、对象、数组等Vue class绑定格式)
class?:
| Array<Record<string, boolean> | string>
| Record<string, boolean>
| string;
// 是否可点击
clickable?: boolean;
// 图标颜色(支持CSS颜色值,当使用Tailwind class时可不传)
color?: string;
// 图标悬停颜色(支持CSS颜色值,当使用Tailwind class时可不传)
hoverColor?: string;
// 图标名称
name: string;
// 图标大小,默认16px
size?: number | string;
}
const props = withDefaults(defineProps<Props>(), {
size: 16,
color: 'currentColor',
hoverColor: '',
clickable: false,
class: '',
});
const emit = defineEmits<{
(e: 'click', event: MouseEvent): void;
}>();
const handleClick = (event: MouseEvent) => {
if (props.clickable) {
event.stopPropagation();
event.preventDefault();
emit('click', event);
}
};
// 动态计算样式
const iconStyle = computed(() => ({
cursor: props.clickable ? 'pointer' : 'default',
color:
props.color && props.color !== 'currentColor' ? props.color : undefined,
}));
// 组合类名
const iconClass = computed(() => {
const baseClasses = ['iconify'];
if (props.clickable) {
baseClasses.push('clickable-icon');
}
if (props.class) {
if (typeof props.class === 'string') {
return [...baseClasses, ...props.class.split(' ').filter(Boolean)];
}
return [baseClasses.join(' '), props.class];
}
return baseClasses.join(' ');
});
</script>
<template>
<Icon
:class="iconClass"
:icon="name"
:width="size"
:height="size"
:style="iconStyle"
@click="handleClick"
/>
</template>
<style scoped>
.iconify {
transition: color 0.2s ease-in-out;
}
.clickable-icon:hover {
color: v-bind(hoverColor) !important;
}
/* 当使用Tailwind颜色类时,优先级更高 */
.iconify:where(.text-*) {
color: inherit !important;
}
</style>Props 说明:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | string | - | 图标名称(Iconify 格式) |
size | number | string | 16 | 图标大小(px) |
color | string | 'currentColor' | 图标颜色 |
hoverColor | string | '' | 悬停颜色 |
clickable | boolean | false | 是否可点击 |
class | string | object | array | '' | 自定义类名 |
Events:
| 事件 | 参数 | 说明 |
|---|---|---|
click | MouseEvent | 点击事件(仅在 clickable=true 时触发) |
使用示例:
<script setup lang="ts">
import { VbenIcon } from '@lib/component-ui';
const handleClick = () => {
console.log('Icon clicked!');
};
</script>
<template>
<!-- 基础用法 -->
<VbenIcon name="mdi:github" size="24" />
<!-- 带点击事件 -->
<VbenIcon
name="mdi:heart"
:clickable="true"
hover-color="red"
@click="handleClick"
/>
<!-- 自定义颜色和大小 -->
<VbenIcon
name="mdi:star"
size="32"
color="#fbbf24"
class="text-yellow-400"
/>
</template>4.4 ShadcnUI Icon 组件
文件位置: @core/ui-kit/shadcn-ui/src/components/icon/icon.vue
更强大的图标组件,支持多种图标来源(组件、SVG URL、远程图片、Iconify)。
<script setup lang="ts">
import type { Component } from 'vue';
import { computed, ref, watch } from 'vue';
import { IconDefault, IconifyIcon } from '@core/icons';
import {
isFunction,
isHttpUrl,
isObject,
isString,
} from '@core/shared/utils';
const props = defineProps<{
// 没有是否显示默认图标
fallback?: boolean;
icon?: Component | Function | string;
}>();
const svgContent = ref<string>('');
const isLoadingSvg = ref(false);
// 判断是否为 SVG URL
const isSvgUrl = computed(() => {
if (!isString(props.icon)) return false;
const iconStr = props.icon as string;
return isHttpUrl(iconStr) && iconStr.toLowerCase().endsWith('.svg');
});
// 判断是否为远程图标
const isRemoteIcon = computed(() => {
if (!isString(props.icon)) return false;
return isHttpUrl(props.icon) && !isSvgUrl.value;
});
// 判断是否为组件
const isComponent = computed(() => {
const { icon } = props;
return !isString(icon) && (isObject(icon) || isFunction(icon));
});
// 智能处理 SVG 颜色
const processedSvg = computed(() => {
if (!svgContent.value) return '';
let svg = svgContent.value;
const preservedColors = ['white', '#fff', '#ffffff', '#FFF', '#FFFFFF',
'black', '#000', '#000000'];
const replaceColor = (match: string, quote: string, color: string) => {
if (color === 'none' || preservedColors.includes(color.toLowerCase())) {
return match;
}
return `fill=${quote}currentColor${quote}`;
};
const replaceStroke = (match: string, quote: string, color: string) => {
if (color === 'none' || preservedColors.includes(color.toLowerCase())) {
return match;
}
return `stroke=${quote}currentColor${quote}`;
};
// 替换 fill 和 stroke 属性
svg = svg.replace(/fill=(["'])([^"']*)\1/gi, replaceColor);
svg = svg.replace(/stroke=(["'])([^"']*)\1/gi, replaceStroke);
// 移除固定的 width 和 height,保留 viewBox
svg = svg.replace(/\s+width="[^"]*"/g, '');
svg = svg.replace(/\s+height="[^"]*"/g, '');
return svg;
});
const svgStyle = computed(() => ({
display: 'inline-flex',
alignItems: 'center',
height: '100%',
verticalAlign: 'middle',
}));
// 加载 SVG 内容
const loadSvg = async (url: string) => {
try {
isLoadingSvg.value = true;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to load SVG: ${response.statusText}`);
}
const text = await response.text();
svgContent.value = text;
} catch (error) {
console.error('Error loading SVG:', error);
svgContent.value = '';
} finally {
isLoadingSvg.value = false;
}
};
watch(
() => props.icon,
(newIcon) => {
if (isSvgUrl.value && isString(newIcon) && newIcon) {
loadSvg(newIcon as string);
} else {
svgContent.value = '';
}
},
{ immediate: true },
);
</script>
<template>
<!-- 组件类型图标 -->
<component :is="icon as Component" v-if="isComponent" v-bind="$attrs" />
<!-- SVG URL 类型图标 -->
<span
v-else-if="isSvgUrl && processedSvg"
:style="svgStyle"
v-bind="$attrs"
v-html="processedSvg"
/>
<!-- 远程图片类型图标 -->
<img v-else-if="isRemoteIcon" :src="icon as string" v-bind="$attrs" />
<!-- Iconify 字符串类型图标 -->
<IconifyIcon v-else-if="icon" v-bind="$attrs" :icon="icon as string" />
<!-- 默认图标 -->
<IconDefault v-else-if="fallback" v-bind="$attrs" />
</template>支持的图标类型:
| 类型 | 示例 | 说明 |
|---|
| 组件 | <SvgBellIcon /> | Vue 组件 |
| SVG URL | "https://example.com/icon.svg" | 自动加载并处理颜色 |
| 远程图片 | "https://example.com/icon.png" | <img> 标签渲染 |
| Iconify | "mdi:github" | Iconify 图标 |
使用示例:
<script setup lang="ts">
import { VbenIcon as ShadcnIcon } from '@core/shadcn-ui';
import { SvgBellIcon } from '@lib/icons';
</script>
<template>
<!-- 组件类型 -->
<ShadcnIcon :icon="SvgBellIcon" />
<!-- SVG URL -->
<ShadcnIcon icon="https://cdn.example.com/icon.svg" />
<!-- 远程图片 -->
<ShadcnIcon icon="https://cdn.example.com/icon.png" />
<!-- Iconify -->
<ShadcnIcon icon="mdi:github" />
</template>4.5 IconPicker 组件
文件位置: effects/common-ui/src/components/icon-picker/icon-picker.vue
图标选择器组件,支持从 Iconify API 动态获取图标集。
主要特性:
- 自动从 Iconify API 获取图标集
- 支持搜索和分页
- 支持自定义图标列表
- 内置缓存机制
核心实现:
// icons.ts - Iconify API 获取工具
interface IconifyResponse {
prefix: string;
total: number;
title: string;
uncategorized?: string[];
categories?: Recordable<string[]>;
aliases?: Recordable<string>;
}
const ICONS_MAP: Recordable<string[]> = {};
const PENDING_REQUESTS: Recordable<Promise<string[]>> = {};
/**
* 通过 Iconify API 获取图标集数据
* @param prefix 图标集名称 (如: "ant-design", "mdi")
* @returns 图标名称列表
*/
export async function fetchIconsData(prefix: string): Promise<string[]> {
// 检查缓存
if (Reflect.has(ICONS_MAP, prefix) && ICONS_MAP[prefix]) {
return ICONS_MAP[prefix];
}
// 检查进行中的请求
if (Reflect.has(PENDING_REQUESTS, prefix) && PENDING_REQUESTS[prefix]) {
return PENDING_REQUESTS[prefix];
}
// 发起新请求
PENDING_REQUESTS[prefix] = (async () => {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response: IconifyResponse = await fetch(
`https://api.iconify.design/collection?prefix=${prefix}`,
{ signal: controller.signal },
).then((res) => res.json());
clearTimeout(timeoutId);
const list = response.uncategorized || [];
if (response.categories) {
for (const category in response.categories) {
list.push(...(response.categories[category] || []));
}
}
ICONS_MAP[prefix] = list.map((v) => `${prefix}:${v}`);
} catch (error) {
console.error(`Failed to fetch icons for prefix ${prefix}:`, error);
return [];
}
return ICONS_MAP[prefix];
})();
return PENDING_REQUESTS[prefix];
}使用示例:
<script setup lang="ts">
import { IconPicker } from '@lib/common-ui';
import { ref } from 'vue';
const selectedIcon = ref('mdi:github');
</script>
<template>
<!-- 使用 Iconify API 自动获取图标 -->
<IconPicker
v-model="selectedIcon"
prefix="mdi"
:page-size="36"
/>
<!-- 使用自定义图标列表 -->
<IconPicker
v-model="selectedIcon"
:icons="['mdi:github', 'mdi:google', 'mdi:facebook']"
/>
<!-- 禁用 API 自动获取 -->
<IconPicker
v-model="selectedIcon"
prefix="svg"
:auto-fetch-api="false"
/>
</template>5. 使用方式
5.1 直接使用 Iconify 字符串
最简单的方式,直接使用 Iconify 图标标识符。
<script setup lang="ts">
import { Icon } from '@iconify/vue';
</script>
<template>
<Icon icon="mdi:github" :width="24" :height="24" />
<Icon icon="ant-design:home-outlined" class="size-6" />
<Icon icon="carbon:logo-vue" />
</template>5.2 使用预导入的 Lucide 图标组件
Lucide 图标已预导入,可直接使用。
<script setup lang="ts">
import {
ArrowDown,
ArrowLeft,
ArrowRight,
ArrowUp,
Bell,
BookOpenText,
Check,
ChevronDown,
ChevronLeft,
ChevronRight,
Circle,
CircleAlert,
CircleCheckBig,
CircleHelp,
CircleX,
Copy,
Ellipsis,
Eye,
EyeOff,
Fullscreen,
Github,
LogOut,
MailCheck,
Menu,
Minimize,
Plus,
RotateCw,
Search,
Settings,
X,
} from '@lib/icons';
</script>
<template>
<BookOpenText class="size-6" />
<Github class="size-8 text-blue-500" />
<X class="size-4 text-red-500" />
</template>完整的 Lucide 图标列表:
主项目/packages/@core/base/icons/src/lucide.ts
5.3 使用自定义 SVG 图标
<script setup lang="ts">
import {
SvgAntdvLogoIcon,
SvgAvatar1Icon,
SvgAvatar2Icon,
SvgAvatar3Icon,
SvgAvatar4Icon,
SvgBellIcon,
SvgCakeIcon,
SvgCardIcon,
SvgDownloadIcon,
} from '@lib/icons';
</script>
<template>
<SvgBellIcon class="size-6" />
<SvgCardIcon class="size-8" />
<SvgDownloadIcon class="size-10" />
</template>5.4 使用 VbenIcon 组件(推荐)
<script setup lang="ts">
import { VbenIcon } from '@lib/component-ui';
</script>
<template>
<!-- 基础用法 -->
<VbenIcon name="mdi:github" size="24" />
<!-- 带点击事件 -->
<VbenIcon
name="mdi:heart"
:clickable="true"
hover-color="red"
@click="handleClick"
/>
<!-- 自定义颜色和大小 -->
<VbenIcon
name="mdi:star"
size="32"
color="#fbbf24"
/>
<!-- 结合 Tailwind CSS -->
<VbenIcon
name="mdi:check"
class="size-6 text-green-500"
/>
</template>5.5 使用 createIconifyIcon 创建图标组件
// iconify/index.ts
import { createIconifyIcon } from '@core/icons';
export const MdiGithub = createIconifyIcon('mdi:github');
export const MdiWechat = createIconifyIcon('mdi:wechat');
export const CarbonLogoVue = createIconifyIcon('carbon:logo-vue');
// 使用
import { MdiGithub, CarbonLogoVue } from '@lib/icons';
<MdiGithub class="size-6" />
<CarbonLogoVue class="size-8" />5.6 在数据配置中使用图标
// 菜单配置示例
const menus = [
{
path: '/dashboard',
icon: 'mdi:view-dashboard',
title: '仪表盘',
},
{
path: '/users',
icon: 'mdi:account-group',
title: '用户管理',
},
];
// 使用组件作为图标
import { BookOpenText, MdiGitlab } from '@lib/icons';
const menuItems = [
{
handler: () => openWindow('https://docs.example.com'),
icon: BookOpenText,
text: '文档',
},
{
handler: () => openWindow('https://github.com'),
icon: MdiGitlab,
text: 'GitHub',
},
];6. 添加自定义图标
6.1 添加 SVG 图标
步骤 1: 准备 SVG 文件
将 SVG 文件放到指定目录:
主项目/packages/icons/src/svg/icons/your-icon.svgSVG 文件要求:
- 必须包含
viewBox属性 - 尽量简化 SVG 内容
- 移除不必要的
<title>、<desc>等标签
<!-- good-icon.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" fill="currentColor"/>
</svg>步骤 2: 注册图标
在 [icons/src/svg/index.ts](主项目/packages/icons/src/svg/index.ts) 中导出:
import { createIconifyIcon } from '@core/icons';
import './load.js';
const SvgYourIconIcon = createIconifyIcon('svg:your-icon');
export {
SvgYourIconIcon,
// ... 其他图标
};步骤 3: 使用图标
<script setup lang="ts">
import { SvgYourIconIcon } from '@lib/icons';
</script>
<template>
<SvgYourIconIcon class="size-6" />
</template>6.2 添加 Iconify 在线图标
在 [icons/src/iconify/index.ts](主项目/packages/icons/src/iconify/index.ts) 中添加:
import { createIconifyIcon } from '@core/icons';
export * from '@core/icons';
// 支持任何 Iconify 图标集
export const MdiGithub = createIconifyIcon('mdi:github');
export const MdiWechat = createIconifyIcon('mdi:wechat');
export const AntDesignHome = createIconifyIcon('ant-design:home');
export const CarbonLogoVue = createIconifyIcon('carbon:logo-vue');6.3 添加 Vue 组件图标
对于复杂的图标,可以创建独立的 Vue 组件。
文件位置: icons/src/icons/your-icon.vue
<template>
<svg
height="41"
viewBox="0 0 64 41"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fill-rule="evenodd">
<!-- SVG 内容 -->
</g>
</svg>
</template>在 [icons/src/index.ts](主项目/packages/icons/src/index.ts) 中导出:
export { default as YourIcon } from './icons/your-icon.vue';7. 图标集对比
| 图标集 | 前缀 | 来源 | 图标数量 | 使用场景 | 示例 |
|---|
| Lucide | 无需前缀 | lucide-vue-next | 1000+ | 通用 UI 图标 | <ArrowLeft /> |
| Material Design Icons | mdi: | Iconify API | 200,000+ | Material Design 风格 | mdi:github |
| Ant Design | ant-design: | Iconify API | 4000+ | Ant Design 风格 | ant-design:home |
| Carbon | carbon: | Iconify API | 2000+ | IBM Carbon Design | carbon:logo-vue |
| FontAwesome | fa: | Iconify API | 2000+ | FontAwesome 风格 | fa:github |
| 自定义 SVG | svg: | 本地文件 | 按需添加 | 项目特定图标 | svg:bell |
常用图标集推荐
| 场景 | 推荐图标集 | 前缀 |
|---|
| 通用 UI | Lucide | 无需前缀 |
| 品牌图标 | Material Design Icons | mdi: |
| 中后台系统 | Ant Design | ant-design: |
| 现代化设计 | Carbon | carbon: |
| 社交媒体 | FontAwesome | fa: |
8. 最佳实践
8.1 图标选择原则
- 优先使用 Lucide 图标
- 已预导入,无需额外配置
- 风格统一,适合通用 UI
- 体积小,性能好
- 品牌图标使用 MDI
mdi:github,mdi:google,mdi:facebook- 图标丰富,覆盖主流品牌
- 项目特定图标使用 SVG
- 放置在
icons/src/svg/icons/目录 - 自动注册为
svg:xxx格式
8.2 性能优化
- 使用 Tree Shaking
// ✅ 推荐: 按需导入
import { Github, Plus } from '@lib/icons';
// ❌ 避免: 导入整个包
import * as Icons from '@lib/icons';- Iconify API 缓存
- IconPicker 组件已内置缓存
- 同一图标集只请求一次
- SVG 优化
- 移除不必要的元数据
- 简化路径
- 使用
currentColor实现颜色自适应
8.3 样式处理
- 使用 Tailwind CSS
<VbenIcon name="mdi:github" class="size-6 text-blue-500" />- 使用 style 属性
<VbenIcon name="mdi:github" :size="24" color="#3b82f6" />- 悬停效果
<VbenIcon
name="mdi:heart"
:clickable="true"
hover-color="red"
/>8.4 可访问性
- 添加 aria-label
<VbenIcon
name="mdi:close"
aria-label="关闭"
role="button"
/>- 使用语义化图标
<!-- ✅ 语义化 -->
<button aria-label="搜索">
<Search class="size-4" />
</button>
<!-- ❌ 非语义化 -->
<div>
<Search class="size-4" />
</div>9. 常见问题
Q1: 图标不显示?
可能原因:
- 图标名称错误
- 图标集前缀错误
- 网络问题(在线图标)
解决方案:
// 检查图标名称是否正确
console.log(listIcons('', 'mdi')); // 列出所有 mdi 图标
// 检查网络请求
fetch('https://api.iconify.design/collection?prefix=mdi')
.then(res => res.json())
.then(data => console.log(data));Q2: SVG 图标颜色不生效?
原因: SVG 中硬编码了颜色属性
解决方案:
<!-- ❌ 错误: 硬编码颜色 -->
<path fill="#ff0000" d="..." />
<!-- ✅ 正确: 使用 currentColor -->
<path fill="currentColor" d="..." />或使用 ShadcnUI Icon 组件,它会自动处理颜色。
Q3: 如何查看所有可用的图标?
import { listIcons } from '@lib/icons';
// 列出所有 Iconify 图标
console.log(listIcons('', 'mdi'));
console.log(listIcons('', 'ant-design'));
// 列出所有 SVG 图标
console.log(listIcons('', 'svg'));Q4: 如何离线使用图标?
Lucide 图标: 已内置,无需网络
SVG 图标: 已内置,无需网络
Iconify 在线图标: 需要下载图标集
// 下载图标集到本地
import { addCollection } from '@core/icons';
import mdiJson from './mdi.json';
addCollection(mdiJson);附录 A: 完整的 Lucide 图标列表
export {
ArrowDown,
ArrowLeft,
ArrowLeftToLine,
ArrowRightLeft,
ArrowRightToLine,
ArrowUp,
ArrowUpToLine,
Bell,
BookOpenText,
Check,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
Circle,
CircleAlert,
CircleCheckBig,
CircleHelp,
CircleX,
Copy,
CornerDownLeft,
Ellipsis,
Expand,
ExternalLink,
Eye,
EyeOff,
FoldHorizontal,
Fullscreen,
Github,
Grip,
GripVertical,
Menu as IconDefault,
Info,
InspectionPanel,
Languages,
LoaderCircle,
LockKeyhole,
LogOut,
MailCheck,
Maximize,
ArrowRightFromLine as MdiMenuClose,
ArrowLeftFromLine as MdiMenuOpen,
Menu,
Minimize,
Minimize2,
MoonStar,
Palette,
PanelLeft,
PanelRight,
Pin,
PinOff,
Plus,
RotateCw,
Search,
SearchX,
Settings,
Shrink,
Square,
SquareCheckBig,
SquareMinus,
Sun,
SunMoon,
SwatchBook,
UserRoundPen,
X,
} from 'lucide-vue-next';附录 B: 图标相关文件清单
| 文件路径 | 说明 |
|---|---|
[@core/base/icons/src/create-icon.ts](主项目/packages/@core/base/icons/src/create-icon.ts) | 图标工厂函数 |
[@core/base/icons/src/lucide.ts](主项目/packages/@core/base/icons/src/lucide.ts) | Lucide 图标导出 |
[icons/src/svg/load.ts](主项目/packages/icons/src/svg/load.ts) | SVG 自动加载器 |
[icons/src/iconify/index.ts](主项目/packages/icons/src/iconify/index.ts) | Iconify 图标注册 |
[component-ui/src/icon/src/icon.vue](主项目/packages/component-ui/src/icon/src/icon.vue) | VbenIcon 组件 |
[@core/ui-kit/shadcn-ui/src/components/icon/icon.vue](主项目/packages/@core/ui-kit/shadcn-ui/src/components/icon/icon.vue) | ShadcnUI Icon 组件 |
[effects/common-ui/src/components/icon-picker/icon-picker.vue](主项目/packages/effects/common-ui/src/components/icon-picker/icon-picker.vue) | IconPicker 组件 |
[effects/common-ui/src/components/icon-picker/icons.ts](主项目/packages/effects/common-ui/src/components/icon-picker/icons.ts) | Iconify API 工具 |
附录 C: 参考资源
文档版本: v1.0.0
最后更新: 2025-01-19
维护者: 某前端基础库 团队