203 lines
3.9 KiB
Vue
203 lines
3.9 KiB
Vue
<!--
|
||
默认头像组件
|
||
参考 zt-vue-element 的 CropperAvatar 实现
|
||
支持默认头像逻辑:头像URL优先,否则显示用户昵称首字母
|
||
-->
|
||
<template>
|
||
<view class="avatar-container" @click="handleClick">
|
||
<!-- 有头像时显示图片 -->
|
||
<image
|
||
v-if="avatarUrl"
|
||
:src="avatarUrl"
|
||
:style="avatarStyle"
|
||
class="avatar-image"
|
||
mode="aspectFill"
|
||
@error="handleImageError"
|
||
/>
|
||
|
||
<!-- 无头像时显示首字母头像 -->
|
||
<view
|
||
v-else
|
||
class="avatar-initial"
|
||
:style="[avatarStyle, initialStyle]"
|
||
>
|
||
{{ initialText }}
|
||
</view>
|
||
|
||
<!-- 编辑按钮 -->
|
||
<view v-if="editable && !readonly" class="edit-mask">
|
||
<text class="edit-icon">+</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, ref, watch } from 'vue'
|
||
|
||
// 组件属性
|
||
const props = defineProps({
|
||
// 头像URL
|
||
src: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 用户昵称,用于生成首字母
|
||
nickname: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 头像尺寸
|
||
size: {
|
||
type: [String, Number],
|
||
default: 80
|
||
},
|
||
// 是否可编辑
|
||
editable: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// 是否只读
|
||
readonly: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// 背景色(用于首字母头像)
|
||
bgColor: {
|
||
type: String,
|
||
default: '#0055A2'
|
||
},
|
||
// 文字色(用于首字母头像)
|
||
textColor: {
|
||
type: String,
|
||
default: '#ffffff'
|
||
},
|
||
// 自定义初始字符
|
||
initial: {
|
||
type: String,
|
||
default: ''
|
||
}
|
||
})
|
||
|
||
// 事件定义
|
||
const emit = defineEmits(['click', 'error'])
|
||
|
||
// 响应式数据
|
||
const avatarUrl = ref(props.src)
|
||
const imageError = ref(false)
|
||
|
||
// 计算属性
|
||
const avatarStyle = computed(() => {
|
||
const size = typeof props.size === 'number' ? `${props.size}rpx` : props.size
|
||
return {
|
||
width: size,
|
||
height: size,
|
||
borderRadius: '50%'
|
||
}
|
||
})
|
||
|
||
const initialStyle = computed(() => ({
|
||
backgroundColor: props.bgColor,
|
||
color: props.textColor,
|
||
fontSize: `${Math.floor(props.size * 0.4)}rpx`,
|
||
fontWeight: 'bold'
|
||
}))
|
||
|
||
// 生成初始字符逻辑
|
||
const initialText = computed(() => {
|
||
// 如果提供了自定义初始字符,使用它
|
||
if (props.initial) {
|
||
return props.initial.charAt(0).toUpperCase()
|
||
}
|
||
|
||
// 如果有昵称,取首字母
|
||
if (props.nickname) {
|
||
// 处理中文和英文
|
||
const firstChar = props.nickname.charAt(0)
|
||
// 如果是中文,直接使用
|
||
if (/[\u4e00-\u9fa5]/.test(firstChar)) {
|
||
return firstChar
|
||
}
|
||
// 如果是英文,转大写
|
||
return firstChar.toUpperCase()
|
||
}
|
||
|
||
// 默认返回用户图标
|
||
return '用'
|
||
})
|
||
|
||
// 监听src变化
|
||
watch(() => props.src, (newSrc) => {
|
||
avatarUrl.value = newSrc
|
||
imageError.value = false
|
||
}, { immediate: true })
|
||
|
||
// 方法
|
||
const handleClick = () => {
|
||
if (!props.readonly) {
|
||
emit('click')
|
||
}
|
||
}
|
||
|
||
const handleImageError = () => {
|
||
imageError.value = true
|
||
avatarUrl.value = '' // 清空URL,显示首字母头像
|
||
emit('error')
|
||
}
|
||
|
||
// 暴露方法给父组件
|
||
defineExpose({
|
||
refresh: () => {
|
||
avatarUrl.value = props.src
|
||
imageError.value = false
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.avatar-container {
|
||
position: relative;
|
||
display: inline-block;
|
||
overflow: hidden;
|
||
|
||
&:hover .edit-mask {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.avatar-image {
|
||
display: block;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.avatar-initial {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: var(--theme-primary, #0055A2);
|
||
color: #ffffff;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
user-select: none;
|
||
}
|
||
|
||
.edit-mask {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.edit-icon {
|
||
color: #ffffff;
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
}
|
||
</style> |