topfans/frontend/pages/components/Avatar.vue
2026-05-07 14:12:07 +08:00

226 lines
5.1 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>
<view class="avatar-wrapper" :style="wrapperStyle">
<view class="avatar-circle" :style="avatarStyle">
<image class="avatar-image" :src="avatarImage" mode="aspectFill"></image>
</view>
<view class="level-badge" v-if="showLevel && level" :style="badgeStyle">
<text class="level-text" :style="badgeTextStyle">LV{{ level }}</text>
</view>
</view>
</template>
<script setup>
import { computed, ref, watch, onMounted } from 'vue';
// 定义 props
const props = defineProps({
// 用户ID优先使用
userId: {
type: [String, Number],
default: ''
},
// 用户昵称(作为备用)
nickname: {
type: String,
default: ''
},
// 自定义头像URL来自OSS或已解析的预签名URL
avatarUrl: {
type: String,
default: ''
},
// 头像尺寸单位rpx
size: {
type: Number,
default: 100
},
// 是否显示等级徽章
showLevel: {
type: Boolean,
default: false
},
// 等级
level: {
type: [String, Number],
default: 0
},
// 边框宽度单位rpx
borderWidth: {
type: Number,
default: 4
},
// 是否需要缓存true=用户自己的头像需要缓存false=好友头像不缓存)
enableCache: {
type: Boolean,
default: true
}
});
// 本地头像路径
const localAvatarUrl = ref('');
// 获取用户自己的头像直接使用后端返回的URL
const fetchOwnAvatar = () => {
if (!props.avatarUrl) {
localAvatarUrl.value = '';
return;
}
// 直接使用后端返回的 URL
localAvatarUrl.value = props.avatarUrl;
};
// 获取好友头像直接使用传入的URL
const fetchFriendAvatar = () => {
localAvatarUrl.value = props.avatarUrl || '';
};
// 根据enableCache决定使用哪种获取方式
const fetchAvatar = () => {
if (props.enableCache) {
fetchOwnAvatar();
} else {
fetchFriendAvatar();
}
};
// 监听avatarUrl变化
watch(() => props.avatarUrl, () => {
fetchAvatar();
});
// 监听enableCache变化
watch(() => props.enableCache, () => {
fetchAvatar();
});
// 组件挂载时获取头像
onMounted(() => {
if (props.avatarUrl) {
fetchAvatar();
}
});
// 根据用户ID或昵称生成默认头像路径保证同一用户头像一致
const getDefaultAvatar = () => {
const avatarCount = 7; // static/avatar 中有7张图片
let index = 1; // 默认使用第一张
if (props.userId) {
// 优先使用用户ID生成索引
index = (parseInt(props.userId) || 0) % avatarCount + 1;
} else if (props.nickname) {
// 如果没有ID使用昵称的字符码生成索引
let hash = 0;
for (let i = 0; i < props.nickname.length; i++) {
hash = props.nickname.charCodeAt(i) + ((hash << 5) - hash);
}
index = Math.abs(hash) % avatarCount + 1;
}
return `/static/avatar/${index}.jpeg`;
};
// 头像图片源优先使用头像URL否则使用默认头像
const avatarImage = computed(() => {
if (localAvatarUrl.value) {
return localAvatarUrl.value;
}
return getDefaultAvatar();
});
// 包装器样式
const wrapperStyle = computed(() => ({
position: 'relative',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexShrink: 0
}));
// 头像圆圈样式
const avatarStyle = computed(() => {
// 根据尺寸调整阴影大小
const shadowSize = props.size >= 160 ? '0 8rpx 32rpx rgba(0, 0, 0, 0.2)' : '0 4rpx 16rpx rgba(0, 0, 0, 0.2)';
return {
width: `${props.size}rpx`,
height: `${props.size}rpx`,
borderWidth: `${props.borderWidth}rpx`,
boxShadow: shadowSize
};
});
// 徽章样式(根据头像大小自适应)
const badgeStyle = computed(() => {
// 根据头像大小调整徽章样式
if (props.size >= 160) {
// 大头像profile页面
return {
top: '-10rpx',
right: '-10rpx',
borderRadius: '20rpx',
padding: '6rpx 16rpx',
border: '3rpx solid rgba(255, 255, 255, 0.8)',
boxShadow: '0 4rpx 12rpx rgba(0, 0, 0, 0.3)'
};
} else {
// 小头像(好友列表等)
const offset = -props.size * 0.18; // 徽章偏移量
return {
top: `${offset}rpx`,
right: `${offset}rpx`,
borderRadius: '10rpx',
padding: '0 8rpx',
border: '2rpx solid rgba(255, 255, 255, 0.8)',
boxShadow: '0 2rpx 8rpx rgba(0, 0, 0, 0.3)'
};
}
});
// 徽章文字样式(根据头像大小自适应)
const badgeTextStyle = computed(() => {
// 根据头像大小调整文字大小
const fontSize = props.size >= 160 ? 22 : 18;
return {
fontSize: `${fontSize}rpx`
};
});
</script>
<style scoped>
.avatar-wrapper {
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.avatar-circle {
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border-style: solid;
border-color: rgba(255, 255, 255, 0.3);
overflow: hidden;
}
.avatar-image {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.level-badge {
position: absolute;
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
}
.level-text {
font-weight: bold;
color: #e6e6e6;
line-height: 1;
}
</style>