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