474 lines
12 KiB
Vue
474 lines
12 KiB
Vue
<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>
|
||
|