Files
nl_cms-view/src/layouts/Header.vue
2025-07-29 12:43:09 +08:00

266 lines
7.9 KiB
Vue

<template>
<div>
<!-- 顶部导航栏 -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<!-- Logo和品牌 -->
<div class="flex items-center">
<router-link to="/" class="flex items-center">
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center mr-3">
<span class="text-white font-bold text-lg">C</span>
</div>
<span class="text-xl font-bold text-gray-900">CMS系统</span>
</router-link>
</div>
<!-- 桌面端导航菜单 -->
<nav class="hidden md:flex space-x-8">
<router-link
to="/"
class="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors"
:class="{ 'text-blue-600': $route.path === '/' }"
>
首页
</router-link>
<router-link
to="/articles"
class="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors"
:class="{ 'text-blue-600': $route.path.startsWith('/articles') }"
>
技术文章
</router-link>
<router-link
to="/news"
class="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors"
:class="{ 'text-blue-600': $route.path.startsWith('/news') }"
>
新闻资讯
</router-link>
<router-link
to="/about"
class="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors"
:class="{ 'text-blue-600': $route.path === '/about' }"
>
关于我们
</router-link>
<router-link
to="/contact"
class="text-gray-700 hover:text-blue-600 px-3 py-2 text-sm font-medium transition-colors"
:class="{ 'text-blue-600': $route.path === '/contact' }"
>
联系我们
</router-link>
</nav>
<!-- 右侧操作区 -->
<div class="flex items-center space-x-4">
<!-- 搜索按钮 -->
<button
@click="searchOpen = !searchOpen"
class="text-gray-500 hover:text-gray-700 p-2 rounded-lg hover:bg-gray-100"
>
<SearchIcon class="w-5 h-5" />
</button>
<!-- 管理员入口 -->
<router-link
to="/admin"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
>
管理后台
</router-link>
<!-- 移动端菜单按钮 -->
<button
@click="mobileMenuOpen = !mobileMenuOpen"
class="md:hidden text-gray-500 hover:text-gray-700 p-2 rounded-lg hover:bg-gray-100"
>
<MenuIcon v-if="!mobileMenuOpen" class="w-6 h-6" />
<XIcon v-else class="w-6 h-6" />
</button>
</div>
</div>
<!-- 搜索栏 -->
<div v-if="searchOpen" class="py-4 border-t border-gray-200">
<div class="relative">
<SearchIcon class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
v-model="searchKeyword"
type="text"
placeholder="搜索文章、新闻..."
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
@keyup.enter="handleSearch"
/>
</div>
</div>
</div>
<!-- 移动端菜单 -->
<div v-if="mobileMenuOpen" class="md:hidden bg-white border-t border-gray-200">
<div class="px-2 pt-2 pb-3 space-y-1">
<router-link
to="/"
class="block px-3 py-2 text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-gray-50 rounded-md"
@click="mobileMenuOpen = false"
>
首页
</router-link>
<router-link
to="/articles"
class="block px-3 py-2 text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-gray-50 rounded-md"
@click="mobileMenuOpen = false"
>
技术文章
</router-link>
<router-link
to="/news"
class="block px-3 py-2 text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-gray-50 rounded-md"
@click="mobileMenuOpen = false"
>
新闻资讯
</router-link>
<router-link
to="/about"
class="block px-3 py-2 text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-gray-50 rounded-md"
@click="mobileMenuOpen = false"
>
关于我们
</router-link>
<router-link
to="/contact"
class="block px-3 py-2 text-base font-medium text-gray-700 hover:text-blue-600 hover:bg-gray-50 rounded-md"
@click="mobileMenuOpen = false"
>
联系我们
</router-link>
</div>
</div>
</header>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// 图标组件
const SearchIcon = {
template: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>`
}
const MenuIcon = {
template: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>`
}
const XIcon = {
template: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>`
}
// 响应式数据
const searchOpen = ref(false)
const mobileMenuOpen = ref(false)
const searchKeyword = ref('')
// 处理搜索
const handleSearch = () => {
if (searchKeyword.value.trim()) {
router.push({
path: '/search',
query: { q: searchKeyword.value }
})
searchOpen.value = false
searchKeyword.value = ''
}
}
// 监听路由变化,关闭移动端菜单
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(() => route.path, () => {
mobileMenuOpen.value = false
searchOpen.value = false
})
</script>
<style scoped>
/* 导航链接激活状态 */
.router-link-active {
color: #2563eb;
}
/* 移动端菜单动画 */
.mobile-menu-enter-active,
.mobile-menu-leave-active {
transition: all 0.3s ease;
}
.mobile-menu-enter-from,
.mobile-menu-leave-to {
opacity: 0;
transform: translateY(-10px);
}
/* 搜索栏动画 */
.search-enter-active,
.search-leave-active {
transition: all 0.3s ease;
}
.search-enter-from,
.search-leave-to {
opacity: 0;
transform: translateY(-10px);
}
/* 响应式优化 */
@media (max-width: 768px) {
.hidden.md\:flex {
display: none;
}
.md\:hidden {
display: block;
}
}
/* 悬停效果 */
.hover\:bg-gray-100:hover {
background-color: #f3f4f6;
}
.hover\:text-blue-600:hover {
color: #2563eb;
}
.hover\:bg-blue-700:hover {
background-color: #1d4ed8;
}
/* 过渡动画 */
.transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
/* 粘性定位 */
.sticky {
position: sticky;
}
/* 阴影效果 */
.shadow-sm {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
/* Z-index层级 */
.z-50 {
z-index: 50;
}
</style>