126 lines
3.0 KiB
Vue
126 lines
3.0 KiB
Vue
<template>
|
||
<div
|
||
class="flex items-center justify-center text-white font-bold shadow-md transition overflow-hidden"
|
||
:class="[sizeClass, roundedClass, { 'cursor-pointer hover:opacity-80': $attrs.onClick }]"
|
||
:style="customSizeStyle"
|
||
@click="$emit('click')"
|
||
>
|
||
<img
|
||
v-if="isImage"
|
||
:src="displayAvatar"
|
||
:alt="name || 'Avatar'"
|
||
class="w-full h-full object-cover"
|
||
@error="handleImageError"
|
||
/>
|
||
<span v-else>{{ avatar || name?.charAt(0).toUpperCase() || '?' }}</span>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { generateColor } from '@/utils/format'
|
||
import { resolveImageUrl } from '@/utils/image'
|
||
|
||
interface Props {
|
||
avatar?: string
|
||
name?: string
|
||
color?: string
|
||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'contact' | number
|
||
rounded?: 'full' | 'xl' | 'lg'
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
size: 'md',
|
||
rounded: 'full',
|
||
})
|
||
|
||
const imageError = ref(false)
|
||
|
||
/** 解析后的头像地址 */
|
||
const displayAvatar = computed(() => resolveImageUrl(props.avatar))
|
||
|
||
// 判断 avatar 是否是图片URL
|
||
const isImage = computed(() => {
|
||
const avatar = props.avatar
|
||
if (!avatar || imageError.value) return false
|
||
|
||
// 检查是否是 base64 图片
|
||
if (avatar.startsWith('data:image/')) {
|
||
return true
|
||
}
|
||
|
||
if (avatar.startsWith('http://') || avatar.startsWith('https://')) {
|
||
return true
|
||
}
|
||
|
||
if (avatar.startsWith('/')) {
|
||
return true
|
||
}
|
||
|
||
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp']
|
||
const lowerAvatar = avatar.toLowerCase()
|
||
if (imageExtensions.some(ext => lowerAvatar.endsWith(ext))) {
|
||
return true
|
||
}
|
||
|
||
return false
|
||
})
|
||
|
||
// 预定义尺寸映射
|
||
const sizeMap: Record<string, string> = {
|
||
xs: 'w-7 h-7 text-[10px]',
|
||
sm: 'w-9 h-9 text-xs',
|
||
md: 'w-10 h-10 text-sm',
|
||
lg: 'w-12 h-12 text-base',
|
||
xl: 'w-16 h-16 text-xl',
|
||
'2xl': 'w-24 h-24 text-2xl',
|
||
contact: 'w-11 h-11 text-lg',
|
||
}
|
||
|
||
// 尺寸 class(仅用于预定义尺寸)
|
||
const sizeClass = computed(() => {
|
||
if (typeof props.size === 'number') {
|
||
return '' // 数字尺寸使用 style
|
||
}
|
||
return sizeMap[props.size] || sizeMap.md
|
||
})
|
||
|
||
// 自定义尺寸样式(用于数字尺寸和背景色)
|
||
const customSizeStyle = computed(() => {
|
||
const style: Record<string, string> = {}
|
||
|
||
// 背景色
|
||
if (!isImage.value) {
|
||
style.background = props.color || generateColor(props.name || props.avatar || '')
|
||
}
|
||
|
||
// 数字尺寸
|
||
if (typeof props.size === 'number') {
|
||
style.width = `${props.size}px`
|
||
style.height = `${props.size}px`
|
||
// 根据尺寸计算字体大小
|
||
style.fontSize = `${Math.max(10, props.size * 0.4)}px`
|
||
}
|
||
|
||
return style
|
||
})
|
||
|
||
const roundedClass = computed(() => {
|
||
const rounded = {
|
||
full: 'rounded-full',
|
||
xl: 'rounded-xl',
|
||
lg: 'rounded-lg',
|
||
}
|
||
return rounded[props.rounded]
|
||
})
|
||
|
||
// 图片加载失败时的处理
|
||
function handleImageError() {
|
||
imageError.value = true
|
||
}
|
||
|
||
defineEmits<{
|
||
click: []
|
||
}>()
|
||
</script>
|