Files
nl-um-vue-ts/src/components/common/Avatar.vue
2026-07-08 08:17:58 +08:00

126 lines
3.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>