474 lines
12 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>
<nav class="navbar">
<div class="navbar-container">
<!-- Logo 区域 -->
<router-link to="/" class="navbar-logo">
<div class="navbar-logo-icon">
<img v-if="logoUrl" :src="logoUrl" alt="Logo" class="logo-image" />
<span v-else>🎨</span>
</div>
<span class="navbar-logo-text">{{ siteName }}</span>
</router-link>
<!-- 导航菜单 -->
<div class="navbar-menu">
<router-link
v-for="item in menuItems"
:key="item.path"
:to="item.path"
class="nav-item"
:class="{ active: isActive(item.path) }"
>
{{ item.name }}
</router-link>
</div>
<!-- 右侧操作区 -->
<div class="navbar-actions">
<!-- 用户头像 - 放在最右边 -->
<a-dropdown>
<div class="navbar-user">
<div class="navbar-user-avatar">
<img v-if="userInfo.avatar" :src="userInfo.avatar" alt="avatar" class="avatar-image" />
<span v-else class="avatar-text">{{ userInfo.name?.charAt(0) }}</span>
</div>
</div>
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item key="profile" @click.stop>
<Icon name="user" size="sm" style="margin-right: 8px;" />
个人中心
</a-menu-item>
<a-menu-item v-if="isAdmin" key="settings" @click.stop>
<Icon name="settings" size="sm" style="margin-right: 8px;" />
系统设置
</a-menu-item>
<a-menu-divider />
<a-menu-item key="logout" @click.stop>
<Icon name="logout" size="sm" style="margin-right: 8px;" />
退出登录
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<!-- 设置 - 仅管理员可见 -->
<div
v-if="navbarConfigStore.config.actions.settings && isAdmin"
class="navbar-icon"
@click="handleSettings"
title="系统设置"
>
<Icon name="settings" size="md" />
</div>
<!-- 成就 -->
<div
v-if="navbarConfigStore.config.actions.achievement"
class="navbar-icon"
@click="handleAchievements"
title="成就中心"
>
<Icon name="trophy" size="md" />
</div>
<!-- 签到 -->
<div
v-if="navbarConfigStore.config.actions.checkin"
class="navbar-icon"
@click="handleCheckIn"
title="每日签到"
>
<Icon name="calendar" size="md" />
</div>
<!-- 通知铃铛 -->
<NotificationBell v-if="navbarConfigStore.config.actions.notification" />
</div>
</div>
</nav>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useSiteSettingsStore } from '@/stores/siteSettings'
import { useNavbarConfigStore } from '@/stores/navbarConfig'
import NotificationBell from '@/components/NotificationBell.vue'
import Icon from '@/components/Icon.vue'
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
const siteSettingsStore = useSiteSettingsStore()
const navbarConfigStore = useNavbarConfigStore()
// 原始菜单项配置
const allMenuItems = [
{ name: '任务大厅', path: '/tasks', key: 'tasks' },
{ name: '设计师大厅', path: '/designer-hall', key: 'designer_hall' },
{ name: '激励中心', path: '/incentive', key: 'incentive' },
{ name: '数据分析', path: '/analytics', key: 'analytics' }
]
// 根据配置过滤显示的菜单项
const menuItems = computed(() => {
const config = navbarConfigStore.config.menuItems
return allMenuItems.filter(item => config[item.key as keyof typeof config])
})
// 用户信息
const userInfo = computed(() => ({
name: authStore.userName || '用户',
avatar: authStore.userInfo?.avatar || ''
}))
// 判断是否是管理员
const isAdmin = computed(() => {
// 优先检查roleIds(最可靠)
if (authStore.userInfo?.roleIds) {
const roleIds = authStore.userInfo.roleIds.split(',').map((id: string) => id.trim())
// roleId 2 = ADMIN, 1 = SUPER_ADMIN
const isAdminByRoleIds = roleIds.includes('2') || roleIds.includes('1')
if (isAdminByRoleIds) {
console.log('[Navbar] 通过roleIds判断为管理员:', roleIds)
return true
}
}
// 其次从userInfo.roles获取角色列表
const roles = authStore.userInfo?.roles || authStore.userRoles || []
// 检查是否包含管理员角色(ADMIN 或 SUPER_ADMIN)
const hasAdminRole = roles.some((role: any) => {
const roleCode = typeof role === 'string' ? role : role.roleCode || role.code || ''
return roleCode === 'ADMIN' || roleCode === 'SUPER_ADMIN'
})
if (hasAdminRole) {
console.log('[Navbar] 通过roles判断为管理员:', roles)
return true
}
// 调试日志
console.log('[Navbar] 不是管理员 - roleIds:', authStore.userInfo?.roleIds, 'roles:', roles)
return false
})
// 判断菜单是否激活
const isActive = (path: string) => {
return route.path === path
}
// 处理签到点击
const handleCheckIn = () => {
router.push('/checkin')
}
// 处理成就点击
const handleAchievements = () => {
router.push('/achievements')
}
// 处理菜单点击
const handleMenuClick = (info: any) => {
const key = info?.key || info
console.log('菜单点击事件:', key, info)
if (key === 'profile') {
router.push('/profile')
} else if (key === 'settings') {
router.push('/settings')
} else if (key === 'logout') {
handleLogout()
}
}
// 处理设置点击
const handleSettings = () => {
router.push('/settings')
}
// 处理退出登录
const handleLogout = async () => {
await authStore.logout()
}
// 使用全局store的网站设置
const siteName = computed(() => siteSettingsStore.siteName)
const logoUrl = computed(() => siteSettingsStore.logoUrl)
// 监听网站设置更新
const handleSiteSettingsUpdate = (event: Event) => {
const customEvent = event as CustomEvent
if (customEvent.detail) {
siteSettingsStore.updateSiteSettings(
customEvent.detail.siteName || 'DesignHub',
customEvent.detail.logoUrl || ''
)
}
}
// 监听导航栏配置更新
const handleNavbarConfigUpdate = () => {
navbarConfigStore.loadConfig()
}
// 组件挂载时获取用户信息
onMounted(() => {
if (authStore.isLoggedIn && !authStore.userInfo) {
authStore.fetchUserInfo()
}
// 监听网站设置更新事件
window.addEventListener('siteSettingsUpdated', handleSiteSettingsUpdate as EventListener)
// 监听导航栏配置更新事件
window.addEventListener('navbarConfigUpdated', handleNavbarConfigUpdate)
// 监听storage事件(跨标签页同步)
window.addEventListener('storage', (e) => {
if (e.key === 'siteName' || e.key === 'logoUrl') {
siteSettingsStore.loadSiteSettings()
}
})
// 加载网站设置
siteSettingsStore.loadSiteSettings()
// 加载导航栏配置
navbarConfigStore.loadConfig()
})
// 组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('siteSettingsUpdated', handleSiteSettingsUpdate as EventListener)
window.removeEventListener('navbarConfigUpdated', handleNavbarConfigUpdate)
})
</script>
<style lang="scss" scoped>
// 导航栏容器(Cursor 风格:更简洁的导航栏)
.navbar {
height: var(--navbar-height);
background: rgba($bg-secondary, 0.8);
border-bottom: 1px solid $border-divider;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: $z-index-dropdown;
backdrop-filter: blur(20px) saturate(180%);
transition: all $transition-base $ease-out;
}
// 导航栏内容容器 - 与页面内容宽度一致
.navbar-container {
max-width: $container-max-width;
margin: 0 auto;
padding: 0 $space-xl;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
@media (max-width: $breakpoint-tablet) {
padding: 0 $space-lg;
}
@media (max-width: $breakpoint-mobile) {
padding: 0 $space-md;
}
}
// Logo 区域
.navbar-logo {
display: flex;
align-items: center;
gap: 12px;
font-size: 20px;
font-weight: $font-weight-semibold;
color: $text-primary;
cursor: pointer;
user-select: none;
text-decoration: none;
transition: all $transition-base $ease-out;
padding: 4px 8px;
border-radius: $radius-md;
&:hover {
color: $brand-primary;
background: $bg-tertiary;
.navbar-logo-icon {
transform: scale(1.05);
}
}
.navbar-logo-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: $radius-small;
background: transparent;
font-size: 18px;
overflow: hidden;
transition: transform $transition-base $ease-out;
.logo-image {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.navbar-logo-text {
@media (max-width: $breakpoint-mobile) {
display: none;
}
}
}
// 导航菜单
.navbar-menu {
display: flex;
gap: $space-sm;
@media (max-width: $breakpoint-tablet) {
display: none;
}
}
.nav-item {
padding: $space-sm $space-md;
border-radius: $radius-md;
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-secondary;
cursor: pointer;
transition: all $transition-base $ease-out;
white-space: nowrap;
&:hover {
color: $text-primary;
background: $bg-tertiary;
}
&.active {
color: $brand-primary;
background: rgba($brand-primary, 0.08);
}
}
// 右侧操作区
.navbar-actions {
display: flex;
flex-direction: row-reverse; // 从右到左排列
align-items: center;
gap: $space-md;
}
.navbar-icon {
width: 40px;
height: 40px;
border-radius: $radius-full;
display: flex;
align-items: center;
justify-content: center;
color: $text-secondary;
cursor: pointer;
position: relative;
transition: all $transition-base $ease-out;
font-size: 18px;
&:hover {
background: $bg-card;
color: $text-primary;
}
.badge {
position: absolute;
top: 6px;
right: 6px;
min-width: 18px;
height: 18px;
padding: 0 5px;
background: $color-error;
border-radius: $radius-full;
font-size: $font-size-xs;
font-weight: $font-weight-medium;
color: $text-white;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
}
// 用户区域
.navbar-user {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: 12px; // 和其他图标保持间距
.navbar-user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid rgba($text-secondary, 0.3); // 默认灰色边框
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
transition: all $transition-base $ease-out;
background: $bg-tertiary;
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-text {
color: $text-primary;
font-size: 18px; // 和功能按钮图标大小一致
font-weight: $font-weight-semibold;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
}
&:hover .navbar-user-avatar {
border-color: $brand-primary; // 悬停时变绿色
transform: scale(1.05);
}
}
// 用户下拉菜单样式 - 黑色背景
:deep(.ant-dropdown-menu) {
background: $bg-primary !important;
border: 1px solid $border-primary !important;
.ant-dropdown-menu-item {
color: $text-primary !important;
&:hover {
background: $bg-card !important;
}
}
.ant-dropdown-menu-divider {
background: $border-divider !important;
}
}
</style>