优化
This commit is contained in:
@@ -4,11 +4,6 @@ import { useUserStore } from '@/stores/user'
|
||||
import UserDropdown from '@/components/UserDropdown.vue'
|
||||
import BrandLogo from '@/components/BrandLogo.vue'
|
||||
|
||||
const styles = {
|
||||
background: 'var(--color-gray-900)',
|
||||
color: 'var(--color-text-inverse)'
|
||||
}
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 计算是否应该显示用户组件
|
||||
@@ -19,29 +14,17 @@ const shouldShowUser = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="header-box" :style="styles">
|
||||
<div>
|
||||
<div class="h-[70px] flex items-center">
|
||||
<div class="flex items-center gap-3 flex-1 pl-[30px]">
|
||||
<BrandLogo :size="40" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4 pr-[35px]">
|
||||
<template v-if="shouldShowUser">
|
||||
<UserDropdown />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<header
|
||||
class="fixed top-0 left-0 right-0 z-[100]
|
||||
h-[70px] flex items-center px-[30px]
|
||||
bg-gray-900 text-white
|
||||
border-b border-border"
|
||||
>
|
||||
<div class="flex items-center gap-3 flex-1">
|
||||
<BrandLogo :size="40" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4 pr-1">
|
||||
<UserDropdown v-if="shouldShowUser" />
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.header-box {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { UserOutlined, LogoutOutlined } from '@ant-design/icons-vue'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 根据用户名生成稳定的渐变色 - 使用更协调的蓝金配色
|
||||
// 根据用户名生成稳定的渐变色
|
||||
const avatarGradient = computed(() => {
|
||||
const name = userStore.displayName || 'User'
|
||||
const gradients = [
|
||||
@@ -18,7 +26,6 @@ const avatarGradient = computed(() => {
|
||||
'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
|
||||
'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)'
|
||||
]
|
||||
// 根据用户名生成稳定的索引
|
||||
let hash = 0
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
hash = name.charCodeAt(i) + ((hash << 5) - hash)
|
||||
@@ -26,12 +33,8 @@ const avatarGradient = computed(() => {
|
||||
return gradients[Math.abs(hash) % gradients.length]
|
||||
})
|
||||
|
||||
const handleMenuClick = ({ key }) => {
|
||||
if (key === 'profile') {
|
||||
router.push('/user/profile')
|
||||
} else if (key === 'logout') {
|
||||
handleLogout()
|
||||
}
|
||||
const handleProfile = () => {
|
||||
router.push('/user/profile')
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
@@ -45,178 +48,68 @@ async function handleLogout() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-dropdown placement="bottomRight" :trigger="['hover']">
|
||||
<div class="user-trigger">
|
||||
<div class="user-avatar-ring">
|
||||
<div class="user-avatar-wrapper">
|
||||
<img
|
||||
v-if="userStore.displayAvatar"
|
||||
class="user-avatar"
|
||||
:src="userStore.displayAvatar"
|
||||
alt="avatar"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="user-avatar-placeholder"
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
class="group flex items-center gap-3 rounded-full px-3 py-1.5 pl-1.5
|
||||
bg-white/5 border border-white/10
|
||||
cursor-pointer outline-none
|
||||
transition-all duration-250 ease-out
|
||||
hover:bg-white/10 hover:border-white/20 hover:-translate-y-0.5
|
||||
data-[state=open]:bg-white/10 data-[state=open]:border-white/20"
|
||||
>
|
||||
<!-- 头像容器 -->
|
||||
<div class="relative w-9 h-9">
|
||||
<!-- 渐变环 - hover 时显示 -->
|
||||
<div
|
||||
class="absolute -inset-0.5 rounded-full opacity-0 group-hover:opacity-100
|
||||
transition-all duration-400 -z-10"
|
||||
style="background: conic-gradient(from 0deg, rgba(59, 130, 246, 0.8), rgba(99, 102, 241, 0.8), rgba(139, 92, 246, 0.8), rgba(59, 130, 246, 0.8))"
|
||||
/>
|
||||
<!-- 头像背景遮罩 -->
|
||||
<div class="absolute inset-0 rounded-full bg-gray-900 -z-5" />
|
||||
|
||||
<!-- 头像 -->
|
||||
<Avatar class="w-9 h-9 relative z-10">
|
||||
<AvatarImage v-if="userStore.displayAvatar" :src="userStore.displayAvatar" alt="avatar" />
|
||||
<AvatarFallback
|
||||
class="flex items-center justify-center text-white font-bold text-[15px]"
|
||||
:style="{ background: avatarGradient }"
|
||||
>
|
||||
<span class="avatar-text">{{ userStore.displayName?.charAt(0)?.toUpperCase() || 'U' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-dot"></div>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<span class="user-name">{{ userStore.displayName || '用户' }}</span>
|
||||
</div>
|
||||
<svg class="dropdown-arrow" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
{{ userStore.displayName?.charAt(0)?.toUpperCase() || 'U' }}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<template #overlay>
|
||||
<a-menu class="user-menu" @click="handleMenuClick">
|
||||
<a-menu-item key="profile">
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
个人中心
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout" danger>
|
||||
<template #icon>
|
||||
<LogoutOutlined />
|
||||
</template>
|
||||
退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<!-- 在线状态点 -->
|
||||
<div
|
||||
class="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full bg-green-500
|
||||
border-2 border-gray-900 z-20
|
||||
shadow-[0_0_0_2px_rgba(34,197,94,0.3)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 用户名 -->
|
||||
<span class="text-sm font-medium text-white truncate max-w-[100px]">
|
||||
{{ userStore.displayName || '用户' }}
|
||||
</span>
|
||||
|
||||
<!-- 下拉箭头 -->
|
||||
<Icon
|
||||
icon="lucide:chevron-down"
|
||||
class="w-4 h-4 text-white/50 shrink-0 transition-transform duration-250
|
||||
group-hover:rotate-180 group-data-[state=open]:rotate-180"
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end" class="min-w-[160px]">
|
||||
<DropdownMenuItem @select="handleProfile" class="gap-2">
|
||||
<Icon icon="lucide:user" class="w-4 h-4 opacity-70" />
|
||||
个人中心
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive" @select="handleLogout" class="gap-2">
|
||||
<Icon icon="lucide:log-out" class="w-4 h-4 opacity-70" />
|
||||
退出登录
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.user-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 6px 12px 6px 6px;
|
||||
border-radius: 40px;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
|
||||
.user-avatar-ring::before {
|
||||
opacity: 1;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar-ring {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -2px;
|
||||
border-radius: 50%;
|
||||
background: conic-gradient(
|
||||
from 0deg,
|
||||
rgba(59, 130, 246, 0.8),
|
||||
rgba(99, 102, 241, 0.8),
|
||||
rgba(139, 92, 246, 0.8),
|
||||
rgba(59, 130, 246, 0.8)
|
||||
);
|
||||
opacity: 0;
|
||||
transition: all 0.4s ease;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--color-gray-900);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar-wrapper {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.user-avatar,
|
||||
.user-avatar-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-avatar-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #22c55e;
|
||||
border: 2px solid var(--color-gray-900);
|
||||
border-radius: 50%;
|
||||
z-index: 3;
|
||||
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
transition: transform 0.25s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@internationalized/date": "^3.12.0",
|
||||
"@types/node": "^25.0.6",
|
||||
"aplayer": "^1.10.1",
|
||||
"axios": "^1.12.2",
|
||||
|
||||
Reference in New Issue
Block a user