|
@@ -0,0 +1,176 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, defineProps, withDefaults, h, onMounted } from 'vue';
|
|
|
+import * as AntdIcons from '@ant-design/icons-vue';
|
|
|
+import { hasIcon } from './register';
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'SvgIcon'
|
|
|
+});
|
|
|
+
|
|
|
+// 定义组件属性类型
|
|
|
+interface Props {
|
|
|
+ /** 图标名称 */
|
|
|
+ name: string;
|
|
|
+ /** 图标类型: 'iconfont' | 'antd' */
|
|
|
+ type?: 'iconfont' | 'antd';
|
|
|
+ /** 图标尺寸 (支持CSS单位) */
|
|
|
+ size?: string | number;
|
|
|
+ /** 图标颜色 */
|
|
|
+ color?: string;
|
|
|
+ /** 旋转动画 */
|
|
|
+ spin?: boolean;
|
|
|
+ /** 旋转角度 */
|
|
|
+ rotationDegree?: number;
|
|
|
+ /** 自定义类名 */
|
|
|
+ className?: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 设置默认属性值
|
|
|
+const props = withDefaults(defineProps<Props>(), {
|
|
|
+ type: 'iconfont',
|
|
|
+ size: '1em',
|
|
|
+ color: 'currentColor',
|
|
|
+ spin: false,
|
|
|
+ rotationDegree: 360,
|
|
|
+ className: ''
|
|
|
+});
|
|
|
+
|
|
|
+// 检查图标是否存在
|
|
|
+const iconExists = computed(() => {
|
|
|
+ if (props.type === 'iconfont') {
|
|
|
+ console.log(hasIcon(props.name), 'hasIcon(props.name)')
|
|
|
+ return hasIcon(props.name);
|
|
|
+ } else if (props.type === 'antd') {
|
|
|
+ return !!AntdIcons[props.name as keyof typeof AntdIcons];
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(props.name, 'props.name',props.type)
|
|
|
+ return false;
|
|
|
+});
|
|
|
+
|
|
|
+// 动态解析Ant Design图标
|
|
|
+const antdIcon = computed(() => {
|
|
|
+ if (props.type !== 'antd') return null;
|
|
|
+
|
|
|
+ const iconComponent = AntdIcons[props.name as keyof typeof AntdIcons] as any;
|
|
|
+
|
|
|
+ if (!iconComponent) {
|
|
|
+ console.warn(`Ant Design icon not found: ${props.name}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return iconComponent || null;
|
|
|
+});
|
|
|
+
|
|
|
+// 处理尺寸值,确保有单位
|
|
|
+const formattedSize = computed(() => {
|
|
|
+ if (typeof props.size === 'number') return `${props.size}px`;
|
|
|
+ return props.size;
|
|
|
+});
|
|
|
+
|
|
|
+// 组合类名
|
|
|
+const iconClasses = computed(() => [
|
|
|
+ props.className,
|
|
|
+ props.type === 'iconfont' ? 'iconfont-svg' : 'antd-icon',
|
|
|
+ { 'icon-spin': props.spin }
|
|
|
+]);
|
|
|
+
|
|
|
+// 自定义样式,包括旋转角度
|
|
|
+const customStyle = computed(() => {
|
|
|
+ const style: Record<string, any> = {
|
|
|
+ '--rotation-degree': `${props.rotationDegree}deg`,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (props.type === 'iconfont') {
|
|
|
+ style.width = formattedSize.value;
|
|
|
+ style.height = formattedSize.value;
|
|
|
+ style.color = props.color;
|
|
|
+ }
|
|
|
+
|
|
|
+ return style;
|
|
|
+});
|
|
|
+
|
|
|
+// 在开发环境下检查图标是否存在
|
|
|
+onMounted(() => {
|
|
|
+ if (process.env.NODE_ENV !== 'production' && !iconExists.value) {
|
|
|
+ console.warn(`Icon not found: ${props.name} (type: ${props.type})`);
|
|
|
+ }
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <!-- Iconfont SVG图标 -->
|
|
|
+
|
|
|
+ <svg
|
|
|
+ v-if="type === 'iconfont' && iconExists"
|
|
|
+ :class="iconClasses"
|
|
|
+ :style="customStyle"
|
|
|
+ aria-hidden="true"
|
|
|
+ >
|
|
|
+ <use :xlink:href="`#icon-${name}`" />
|
|
|
+ </svg>
|
|
|
+
|
|
|
+ <!-- Ant Design图标 -->
|
|
|
+ <component
|
|
|
+ v-else-if="type === 'antd' && antdIcon"
|
|
|
+ :is="antdIcon"
|
|
|
+ v-bind="{
|
|
|
+ spin,
|
|
|
+ style: {
|
|
|
+ color,
|
|
|
+ fontSize: formattedSize,
|
|
|
+ '--rotation-degree': `${rotationDegree}deg`
|
|
|
+ }
|
|
|
+ }"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 图标未找到时的回退 -->
|
|
|
+ <span
|
|
|
+ v-else
|
|
|
+ class="icon-fallback"
|
|
|
+ :style="{ fontSize: formattedSize }"
|
|
|
+ >
|
|
|
+ ?
|
|
|
+ </span>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.iconfont-svg {
|
|
|
+ display: inline-block;
|
|
|
+ vertical-align: -0.15em;
|
|
|
+ fill: currentColor;
|
|
|
+ overflow: hidden;
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.iconfont-svg:hover, .antd-icon:hover {
|
|
|
+ animation: icon-spin 1s infinite linear;
|
|
|
+}
|
|
|
+
|
|
|
+.antd-icon {
|
|
|
+ display: inline-block;
|
|
|
+ line-height: 1;
|
|
|
+ vertical-align: -0.125em;
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.icon-spin {
|
|
|
+ animation: icon-spin 1s infinite linear !important;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes icon-spin {
|
|
|
+ from { transform: rotate(0deg); }
|
|
|
+ to { transform: rotate(var(--rotation-degree, 360deg)); }
|
|
|
+}
|
|
|
+
|
|
|
+.icon-fallback {
|
|
|
+ display: inline-block;
|
|
|
+ width: 1em;
|
|
|
+ height: 1em;
|
|
|
+ line-height: 1;
|
|
|
+ text-align: center;
|
|
|
+ background-color: #f0f0f0;
|
|
|
+ border-radius: 2px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+</style>
|