2025-02-14 23:14:54 +08:00
|
|
|
|
<script setup>
|
2025-02-26 18:27:47 +08:00
|
|
|
|
import {ref, onMounted, onBeforeUnmount} from 'vue'
|
2025-02-14 23:14:54 +08:00
|
|
|
|
import store from "../../store/index.js";
|
|
|
|
|
|
2025-02-26 18:27:47 +08:00
|
|
|
|
const avatarRef = ref(null)
|
|
|
|
|
const redCoverRef = ref(null);
|
|
|
|
|
let animationFrame = null
|
|
|
|
|
let rotateSpeed = 0
|
|
|
|
|
let rotateValue = 0
|
|
|
|
|
let redValue = 0;
|
|
|
|
|
let maxSpeed = 16 // 最大旋转速度(度/帧)
|
|
|
|
|
let shakeIntensity = 0 // 抖动强度
|
|
|
|
|
let isHovering = false
|
|
|
|
|
|
|
|
|
|
const animate = () => {
|
|
|
|
|
if (!isHovering || !avatarRef.value || !redCoverRef.value) return
|
|
|
|
|
|
|
|
|
|
// 加速逻辑
|
|
|
|
|
rotateSpeed += 0.01;
|
|
|
|
|
rotateValue += rotateSpeed;
|
|
|
|
|
|
|
|
|
|
if (rotateSpeed > maxSpeed) {
|
|
|
|
|
// 达到最大速度后开始增加抖动强度
|
|
|
|
|
shakeIntensity = Math.min(shakeIntensity + 0.2, 500)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
redValue = Math.min(redValue + 0.0001, 0.8);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 生成抖动偏移
|
|
|
|
|
const shakeX = (Math.random() - 0.5) * shakeIntensity
|
|
|
|
|
const shakeY = (Math.random() - 0.5) * shakeIntensity
|
|
|
|
|
|
|
|
|
|
// 应用变换
|
|
|
|
|
avatarRef.value.style.transform = `
|
|
|
|
|
translate(${shakeX}px, ${shakeY}px)
|
|
|
|
|
rotate(${rotateValue}deg)
|
|
|
|
|
`
|
|
|
|
|
redCoverRef.value.style.opacity = redValue;
|
|
|
|
|
|
|
|
|
|
animationFrame = requestAnimationFrame(animate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetState = () => {
|
|
|
|
|
rotateSpeed = 0
|
|
|
|
|
redValue = 0;
|
|
|
|
|
shakeIntensity = 0
|
|
|
|
|
if (avatarRef.value) {
|
|
|
|
|
avatarRef.value.style.transform = 'none'
|
|
|
|
|
redCoverRef.value.style.opacity = '0'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleMouseEnter = () => {
|
|
|
|
|
isHovering = true
|
|
|
|
|
animationFrame = requestAnimationFrame(animate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleMouseLeave = () => {
|
|
|
|
|
isHovering = false;
|
|
|
|
|
rotateSpeed = 0
|
|
|
|
|
rotateValue = 0
|
|
|
|
|
cancelAnimationFrame(animationFrame)
|
|
|
|
|
resetState()
|
|
|
|
|
}
|
2025-02-14 23:14:54 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div class="user-info-card">
|
2025-02-26 18:27:47 +08:00
|
|
|
|
<div class="avatar" ref="avatarRef"
|
|
|
|
|
@mouseenter="handleMouseEnter"
|
|
|
|
|
@mouseleave="handleMouseLeave">
|
|
|
|
|
<div class="red-overlay" ref="redCoverRef"/>
|
|
|
|
|
<img
|
|
|
|
|
|
|
|
|
|
:src="store.getters.profileImage"
|
|
|
|
|
alt="User Avatar"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/>
|
2025-02-14 23:14:54 +08:00
|
|
|
|
</div>
|
|
|
|
|
<div class="user-info">
|
|
|
|
|
<h3>{{ store.state.userInfo.username }}</h3>
|
|
|
|
|
<p>注册日期:{{ store.state.userInfo.birth?.split(' ')[0] || '未知' }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.user-info-card {
|
|
|
|
|
width: 100%;
|
2025-02-26 18:27:47 +08:00
|
|
|
|
height: 100%;
|
2025-02-14 23:14:54 +08:00
|
|
|
|
overflow-y: auto;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2025-02-26 18:27:47 +08:00
|
|
|
|
justify-content: center;
|
2025-02-14 23:14:54 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
padding: 30px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-info-card h3 button {
|
|
|
|
|
color: #fff;
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.theme-light .user-info-card h3 {
|
|
|
|
|
color: #252525;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-info-card p {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
2025-02-26 18:27:47 +08:00
|
|
|
|
.red-overlay {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
background: rgba(255, 0, 0, 0);
|
|
|
|
|
mix-blend-mode: multiply;
|
|
|
|
|
transition: background 0.25s ease-in-out;
|
|
|
|
|
}
|
2025-02-14 23:14:54 +08:00
|
|
|
|
.avatar {
|
2025-02-26 18:27:47 +08:00
|
|
|
|
position: relative;
|
2025-02-14 23:14:54 +08:00
|
|
|
|
width: 120px;
|
|
|
|
|
height: 120px;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
margin-bottom: 20px;
|
2025-02-26 18:27:47 +08:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
transition: all 0.5s ease;
|
2025-02-14 23:14:54 +08:00
|
|
|
|
}
|
2025-02-26 18:27:47 +08:00
|
|
|
|
.red-overlay {
|
|
|
|
|
z-index: 10;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
background: rgb(255, 0, 0);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
mix-blend-mode: darken;
|
2025-02-14 23:14:54 +08:00
|
|
|
|
}
|
2025-02-26 18:27:47 +08:00
|
|
|
|
.avatar img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
transition: transform 0.1s linear;
|
|
|
|
|
will-change: transform; /* 优化动画性能 */
|
2025-02-14 23:14:54 +08:00
|
|
|
|
|
2025-02-26 18:27:47 +08:00
|
|
|
|
}
|
|
|
|
|
</style>
|