705 lines
14 KiB
Vue
Raw Normal View History

2025-07-22 14:39:45 +08:00
<template>
<div class="header-container">
<!-- Logo区域 -->
<div class="logo-section">
<div class="logo" @click="$router.push('/')">
<img src="/logo/logo1.png" alt="Logo" class="logo-image" />
</div>
</div>
<!-- 导航菜单 -->
<div class="nav-menu" :class="{ 'mobile-open': mobileMenuOpen }">
<div class="nav-item" :class="{ active: activeKey === 'home' }" @click="handleMenuSelect('home')">
{{ t('header.home') }}
</div>
<div class="nav-item" :class="{ active: activeKey === 'courses' }" @click="handleMenuSelect('courses')">
<img src="/nav-icons/火.png" alt="" class="nav-icon" />
{{ t('header.courses') }}
</div>
<div class="nav-item" :class="{ active: activeKey === 'training' }" @click="handleMenuSelect('training')">
{{ t('header.training') }}
</div>
<div class="nav-item" :class="{ active: activeKey === 'practice' }" @click="handleMenuSelect('practice')">
{{ t('header.resources') }}
</div>
<div class="nav-item" :class="{ active: activeKey === 'resources' }" @click="handleMenuSelect('resources')">
{{ t('header.learningPaths') }}
</div>
<div class="nav-item" :class="{ active: activeKey === 'activities' }" @click="handleMenuSelect('activities')">
{{ t('header.about') }}
<img src="/nav-icons/new.png" alt="new" class="new-badge" />
</div>
</div>
<!-- 搜索框 -->
<div class="search-section">
<div class="search-box">
<img src="/nav-icons/搜索.png" alt="" class="search-icon" />
<input type="text" placeholder="请输入感兴趣的课程" class="search-input" />
</div>
</div>
<!-- 移动端汉堡菜单按钮 -->
<div class="mobile-menu-toggle" @click="toggleMobileMenu">
<n-icon size="24">
<MenuOutline v-if="!mobileMenuOpen" />
<CloseOutline v-else />
</n-icon>
</div>
<!-- 右侧操作区域 -->
<div class="header-actions">
<!-- 切换语言 -->
<div class="action-item language-switcher" @click="toggleLanguageDropdown" ref="languageSwitcherRef">
<img src="/nav-icons/矩形.png" alt="" class="action-icon" />
<span>{{ t('header.languageSwitch') }}</span>
<div v-if="showLanguageDropdown" class="language-dropdown">
<div class="language-option" @click.stop="switchLanguage('zh')">
<span class="language-text">{{ t('languageDropdown.switchToChinese') }}</span>
</div>
<div class="language-option" @click.stop="switchLanguage('en')">
<span class="language-text">{{ t('languageDropdown.switchToEnglish') }}</span>
</div>
</div>
</div>
<!-- 学习中心 -->
<div class="action-item">
<img src="/nav-icons/学习中心.png" alt="" class="action-icon" />
<span>{{ t('header.learningCenter') }}</span>
</div>
<!-- 管理端 -->
<div class="action-item">
<img src="/nav-icons/管理端.png" alt="" class="action-icon" />
<span>{{ t('header.management') }}</span>
</div>
<!-- 登录/注册按钮 -->
<div v-if="!userStore.isLoggedIn" class="auth-buttons">
<div class="auth-combined-btn">
<span class="auth-login" @click="$router.push('/login')">{{ t('header.login') }}</span>
<span class="auth-divider">|</span>
<span class="auth-register" @click="$router.push('/register')">{{ t('header.register') }}</span>
</div>
</div>
<!-- 用户菜单 -->
<div v-else class="user-menu">
<n-dropdown :options="userMenuOptions" @select="handleUserMenuSelect">
<div class="user-info">
<n-avatar
:src="userStore.user?.avatar"
:fallback-src="'https://via.placeholder.com/32'"
size="small"
/>
<span class="username">{{ userStore.user?.username }}</span>
</div>
</n-dropdown>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, h, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useUserStore } from '@/stores/user'
import { useCourseStore } from '@/stores/course'
import {
PersonOutline,
LogOutOutline,
SettingsOutline,
MenuOutline,
CloseOutline
} from '@vicons/ionicons5'
const router = useRouter()
const { t, locale } = useI18n()
const userStore = useUserStore()
const courseStore = useCourseStore()
// 移动端菜单状态
const mobileMenuOpen = ref(false)
// 当前激活的菜单项
const activeKey = ref('home')
// 搜索查询
const searchQuery = ref('')
// 语言切换相关
const showLanguageDropdown = ref(false)
const languageSwitcherRef = ref<HTMLElement | null>(null)
// 切换移动端菜单
const toggleMobileMenu = () => {
mobileMenuOpen.value = !mobileMenuOpen.value
}
// 切换语言下拉框
const toggleLanguageDropdown = () => {
showLanguageDropdown.value = !showLanguageDropdown.value
}
// 切换语言
const switchLanguage = (lang: string) => {
locale.value = lang
localStorage.setItem('locale', lang)
showLanguageDropdown.value = false
console.log('语言已切换到:', lang === 'zh' ? '中文' : '英文')
}
// 用户菜单选项
const userMenuOptions = computed(() => [
{
label: t('header.profile'),
key: 'profile',
icon: () => h(PersonOutline)
},
{
label: t('header.settings'),
key: 'settings',
icon: () => h(SettingsOutline)
},
{
type: 'divider'
},
{
label: t('header.logout'),
key: 'logout',
icon: () => h(LogOutOutline)
}
])
// 处理菜单选择
const handleMenuSelect = (key: string) => {
activeKey.value = key
// 关闭移动菜单
mobileMenuOpen.value = false
switch (key) {
case 'home':
router.push('/')
break
case 'courses':
router.push('/courses')
break
case 'training':
// 暂时跳转到首页
router.push('/')
break
case 'practice':
// 暂时跳转到首页
router.push('/')
break
case 'resources':
// 暂时跳转到首页
router.push('/')
break
case 'activities':
// 暂时跳转到首页
router.push('/')
break
}
}
// 处理用户菜单选择
const handleUserMenuSelect = (key: string) => {
switch (key) {
case 'profile':
router.push('/profile')
break
case 'settings':
// TODO: 实现设置页面
break
case 'logout':
userStore.logout()
router.push('/')
break
}
}
// 处理搜索
const handleSearch = () => {
if (searchQuery.value.trim()) {
courseStore.searchQuery = searchQuery.value
router.push('/courses')
}
}
// 点击外部关闭下拉框
const handleClickOutside = (event: Event) => {
if (languageSwitcherRef.value && !languageSwitcherRef.value.contains(event.target as Node)) {
showLanguageDropdown.value = false
}
}
// 生命周期钩子
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
<style scoped>
.header-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: none;
margin: 0;
padding: 0 30px;
height: 100%;
background: white;
position: relative;
z-index: 1001;
}
/* Logo区域 */
.logo-section {
flex-shrink: 0;
margin-right: 40px;
}
.logo {
display: flex;
align-items: center;
cursor: pointer;
gap: 8px;
}
.logo:hover {
opacity: 0.8;
}
.logo-image {
width: 72px;
height: 61px;
object-fit: contain;
}
.nav-icon {
max-width: 20px;
max-height: 20px;
width: auto;
height: auto;
margin-right: 4px;
object-fit: contain;
}
.action-icon {
max-width: 18px;
max-height: 18px;
width: auto;
height: auto;
object-fit: contain;
}
/* 导航菜单 */
.nav-menu {
display: flex;
align-items: center;
gap: 40px;
flex: 1;
margin-right: 40px;
}
.nav-item {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 12px;
font-size: 14px;
font-weight: 400;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: #333;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
position: relative;
height: 24px;
}
/* 两个字的导航项:首页 */
.nav-item:nth-child(1) {
width: 36px;
}
/* 四个字的导航项:热门好课、专题训练、师资力量、精选资源 */
.nav-item:nth-child(2),
.nav-item:nth-child(3),
.nav-item:nth-child(4),
.nav-item:nth-child(5) {
width: 72px;
}
/* 两个字的导航项:活动 */
.nav-item:nth-child(6) {
width: 36px;
padding-right: 16px; /* 为HOT标签留出空间 */
}
.nav-item:hover {
color: #1890ff;
}
.nav-item.active {
color: #1890ff;
font-weight: 400;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
position: relative;
}
.nav-item.active::after {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 2px;
background-color: #1890ff;
border-radius: 1px;
}
.new-badge {
position: absolute;
top: -6px;
right: -12px;
max-width: 24px;
max-height: 24px;
width: auto;
height: auto;
object-fit: contain;
z-index: 10;
}
/* 搜索区域 */
.search-section {
display: flex;
align-items: center;
margin-right: 40px;
}
.search-box {
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 20px;
padding: 8px 16px;
width: 280px;
transition: all 0.2s;
}
.search-box:hover {
background: #eeeeee;
}
.search-box:focus-within {
background: white;
box-shadow: 0 0 0 2px #1890ff;
}
.search-icon {
max-width: 18px;
max-height: 18px;
width: auto;
height: auto;
margin-right: 8px;
object-fit: contain;
}
.search-input {
flex: 1;
border: none;
background: transparent;
outline: none;
font-size: 14px;
color: #333;
}
.search-input::placeholder {
color: #999;
}
/* 移动端汉堡菜单按钮 */
.mobile-menu-toggle {
display: none;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
cursor: pointer;
border-radius: 6px;
transition: background-color 0.2s;
}
.mobile-menu-toggle:hover {
background: rgba(0, 0, 0, 0.05);
}
/* 右侧操作区域 */
.header-actions {
display: flex;
align-items: center;
gap: 20px;
flex-shrink: 0;
}
.action-item {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 8px;
font-size: 13px;
color: #666;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s;
white-space: nowrap;
}
.action-item:hover {
color: #1890ff;
background: #f0f8ff;
}
/* 语言切换器 */
.language-switcher {
position: relative;
cursor: pointer;
}
.language-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #e8e8e8;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 1000;
margin-top: 4px;
min-width: 120px;
}
.language-option {
padding: 8px 12px;
font-size: 13px;
color: #666;
cursor: pointer;
transition: all 0.2s;
border-bottom: 1px solid #f5f5f5;
}
.language-option:last-child {
border-bottom: none;
}
.language-option:hover {
background: #f0f8ff;
color: #1890ff;
}
.language-text {
white-space: nowrap;
}
/* 认证按钮 */
.auth-buttons {
display: flex;
align-items: center;
gap: 12px;
}
.auth-combined-btn {
display: flex;
align-items: center;
background: #1890ff;
color: white;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
font-weight: 400;
cursor: pointer;
transition: all 0.2s;
}
.auth-combined-btn:hover {
background: #40a9ff;
}
.auth-login,
.auth-register {
padding: 0 8px;
cursor: pointer;
transition: all 0.2s;
}
.auth-login:hover,
.auth-register:hover {
opacity: 0.8;
}
.auth-divider {
color: rgba(255, 255, 255, 0.6);
margin: 0 4px;
font-weight: 300;
}
/* 用户菜单 */
.user-menu {
display: flex;
align-items: center;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
}
.user-info:hover {
background: #f0f8ff;
}
.username {
font-size: 14px;
color: #333;
white-space: nowrap;
}
/* 大屏幕 */
@media (min-width: 1200px) {
.header-container {
padding: 0 32px;
}
.search-input {
width: 280px;
}
}
/* 平板 */
@media (max-width: 992px) {
.header-container {
padding: 0 20px;
}
.nav-menu {
margin: 0 20px;
max-width: 300px;
}
.search-input {
width: 200px;
}
}
/* 小平板 */
@media (max-width: 768px) {
.header-container {
padding: 0 16px;
}
.nav-menu {
display: none;
position: fixed;
top: 64px;
left: 0;
right: 0;
background: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 999;
flex-direction: column;
padding: 16px 0;
}
.nav-menu.mobile-open {
display: flex;
}
.nav-menu .nav-item {
padding: 12px 24px;
border-radius: 0;
justify-content: flex-start;
}
.mobile-menu-toggle {
display: flex;
}
.search-input {
width: 160px;
}
.logo-text {
display: none;
}
.user-actions {
gap: 12px;
}
}
/* 手机 */
@media (max-width: 576px) {
.header-container {
padding: 0 12px;
}
.search-input {
width: 120px;
}
.user-actions {
gap: 8px;
}
.username {
display: none;
}
.auth-buttons {
gap: 4px;
}
}
/* 小手机 */
@media (max-width: 480px) {
.header-container {
padding: 0 8px;
}
.logo {
font-size: 16px;
}
.search-input {
width: 100px;
}
.user-actions {
gap: 6px;
}
}
/* 全屏模式样式现在在App.vue中统一管理 */
</style>