177 lines
7.2 KiB
Vue
177 lines
7.2 KiB
Vue
<template>
|
||
<view class="tj-icon-wrap" :class="[`tj-icon-wrap--${size}`]">
|
||
<image
|
||
v-if="!useFallback"
|
||
class="tj-icon"
|
||
:class="[`tj-icon--${name}`, `tj-icon--${size}`]"
|
||
:src="iconSrc"
|
||
mode="aspectFit"
|
||
@error="onImageError"
|
||
/>
|
||
<text
|
||
v-else
|
||
class="tj-icon-fallback"
|
||
:class="[`tj-icon-fallback--${size}`]"
|
||
:style="{ color }"
|
||
>{{ fallbackGlyph }}</text>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, ref } from 'vue'
|
||
import { svgToDataUrl } from '../utils/svgDataUrl.js'
|
||
|
||
const props = defineProps({
|
||
name: { type: String, required: true },
|
||
size: { type: String, default: 'md' },
|
||
color: { type: String, default: '#204E2B' }
|
||
})
|
||
|
||
const useFallback = ref(false)
|
||
|
||
/** Lucide 风格描边路径 */
|
||
const ICON_PATHS = {
|
||
view: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',
|
||
ticket: '<path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/>',
|
||
calendar: '<rect width="18" height="18" x="3" y="4" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/>',
|
||
flame: '<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>',
|
||
'check-circle': '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/>',
|
||
glucose: '<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"/>',
|
||
heart: '<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/>',
|
||
activity: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
|
||
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
||
user: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
|
||
person: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
|
||
send: '<path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/>',
|
||
'alert-triangle': '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
||
minus: '<path d="M5 12h14"/>',
|
||
droplet: '<path d="M12 22a7 7 0 0 0 7-7c0-2-1-3.5-2.5-5.5C15 7 12 2 12 2S9 7 7.5 9.5 5 13 5 15a7 7 0 0 0 7 7z"/>',
|
||
sparkles: '<path d="m12 3-1.9 5.8L4 12l5.8 1.9L12 21l1.9-5.8L20 12l-5.8-1.9L12 3Z"/><path d="M5 3v4M19 17v4M3 5h4M17 19h4"/>',
|
||
trophy: '<path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20 17 22M18 2H6v7a6 6 0 0 0 12 0V2Z"/>',
|
||
share: '<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13"/>',
|
||
plus: '<path d="M5 12h14M12 5v14"/>',
|
||
refresh: '<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8M3 3v5h5M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16M21 21v-5h-5"/>',
|
||
volume: '<path d="M11 5 6 9H2v6h4l5 4V5zM15.54 8.46a5 5 0 0 1 0 7.07M19.07 4.93a10 10 0 0 1 0 14.14"/>',
|
||
pause: '<rect width="4" height="16" x="14" y="4" rx="1"/><rect width="4" height="16" x="6" y="4" rx="1"/>',
|
||
play: '<polygon points="6 3 20 12 6 21 6 3"/>',
|
||
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>',
|
||
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41-1.41M17.66 6.34l1.41-1.41M6.34 4.93l1.41 1.41"/>',
|
||
moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
|
||
sunset: '<path d="M12 10V2M18.364 5.636l-2.12 2.12M5.636 18.364l2.12-2.12M22 18h-3M5 18H2M18.364 18.364l-2.12-2.12M5.636 5.636l2.12 2.12M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"/>',
|
||
mic: '<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/>',
|
||
camera: '<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/>',
|
||
bulb: '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1.3.5 2.6 1.5 3.5.8.8 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
|
||
leaf: '<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"/><path d="M2 21c0-3 1.85-5.36 5.08-6"/>',
|
||
utensils: '<path d="M3 2v7c0 1.1.9 2 2 2a2 2 0 0 0 2-2V2"/><path d="M7 2v20"/><path d="M21 15V2a5 5 0 0 0-3 4.5v6a2 2 0 0 0 2 2h1Z"/><path d="M18 15v7"/>',
|
||
egg: '<path d="M12 22c4.97 0 8-3.27 8-7.31C20 9.65 16.42 2 12 2S4 9.65 4 14.69C4 18.73 7.03 22 12 22Z"/>',
|
||
home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
|
||
'plus-circle': '<circle cx="12" cy="12" r="10"/><path d="M8 12h8M12 8v8"/>',
|
||
'chevron-right': '<path d="m9 18 6-6-6-6"/>'
|
||
}
|
||
|
||
/** 图片加载失败时的 emoji 回退 */
|
||
const ICON_FALLBACK = {
|
||
view: '👁',
|
||
ticket: '🎫',
|
||
calendar: '📅',
|
||
flame: '🔥',
|
||
'check-circle': '✓',
|
||
glucose: '💧',
|
||
heart: '❤',
|
||
activity: '🏃',
|
||
users: '👥',
|
||
user: '👤',
|
||
person: '👤',
|
||
send: '➤',
|
||
'alert-triangle': '⚠',
|
||
minus: '—',
|
||
droplet: '💧',
|
||
sparkles: '✨',
|
||
trophy: '🏆',
|
||
share: '↗',
|
||
plus: '+',
|
||
refresh: '↻',
|
||
volume: '🔊',
|
||
pause: '⏸',
|
||
play: '▶',
|
||
info: '!',
|
||
sun: '☀',
|
||
moon: '🌙',
|
||
sunset: '☀',
|
||
mic: '🎤',
|
||
camera: '📷',
|
||
bulb: '💡',
|
||
leaf: '🥬',
|
||
utensils: '🍴',
|
||
egg: '🍳',
|
||
home: '🏠',
|
||
'plus-circle': '⊕',
|
||
'chevron-right': '›'
|
||
}
|
||
|
||
const strokeColor = computed(() => {
|
||
const c = String(props.color || '#204E2B').trim()
|
||
return /^#[0-9A-Fa-f]{3,8}$/.test(c) ? c : '#204E2B'
|
||
})
|
||
|
||
const iconSrc = computed(() => {
|
||
const path = ICON_PATHS[props.name] || ICON_PATHS.view
|
||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${strokeColor.value}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`
|
||
return svgToDataUrl(svg)
|
||
})
|
||
|
||
const fallbackGlyph = computed(() => ICON_FALLBACK[props.name] || ICON_FALLBACK.view)
|
||
|
||
function onImageError() {
|
||
useFallback.value = true
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.tj-icon-wrap {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.tj-icon-wrap--sm {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
.tj-icon-wrap--md {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
.tj-icon-wrap--lg {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
}
|
||
.tj-icon-wrap--xl {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
}
|
||
.tj-icon {
|
||
display: block;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.tj-icon-fallback {
|
||
display: block;
|
||
line-height: 1;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
}
|
||
.tj-icon-fallback--sm {
|
||
font-size: 24rpx;
|
||
}
|
||
.tj-icon-fallback--md {
|
||
font-size: 30rpx;
|
||
}
|
||
.tj-icon-fallback--lg {
|
||
font-size: 36rpx;
|
||
}
|
||
.tj-icon-fallback--xl {
|
||
font-size: 42rpx;
|
||
}
|
||
</style>
|