Files
official_network/src/components/ihead.vue
张成 928ce80159 1
2026-04-28 18:16:26 +08:00

574 lines
12 KiB
Vue

<template>
<div>
<div class="header bottomShadow" :class="{ 'mini': isMiniHeader }">
<div class="content pageWidth">
<!-- 主要导航区域 -->
<div class="bottom desktop">
<div class="nav-container">
<div class="logoCell">
<router-link to="/index" aria-label="返回首页">
<img class="logo" :src="image_paths.logo" alt="丙维科技">
</router-link>
</div>
<div class="mainMenuCell">
<nav class="mainMenu">
<div
class="nav-item"
v-for="(menu, index) in menus"
:key="menu.name"
@mouseenter="openDropdown(index)"
@mouseleave="closeDropdown"
@focusin="openDropdown(index)"
>
<router-link
:to="menu.url"
class="nav-link"
:aria-expanded="menu.child.length > 0 ? String(activeDropdownIndex === index) : null"
:aria-haspopup="menu.child.length > 0 ? 'true' : null"
@keydown.native.enter.prevent="handleNavEnter(menu, index)"
@keydown.native.esc.prevent="closeDropdown"
>
{{ menu.name }}
<span v-if="menu.child.length > 0" class="downCaret"></span>
</router-link>
<div
class="dropDown bottomShadowSegment"
:class="{ visible: activeDropdownIndex === index }"
v-if="menu.child.length > 0"
>
<router-link :to="sonMenu.url" v-for="sonMenu in menu.child" :key="sonMenu.name"
class="dropdown-item"
@keydown.native.esc.prevent="closeDropdown">
{{ sonMenu.name }}
</router-link>
</div>
</div>
</nav>
</div>
</div>
</div>
<!-- 移动端导航 -->
<div class="bottom headerBottom mobile">
<div class="mobile-nav">
<div class="logoCell">
<router-link to="/index" aria-label="返回首页" @click.native="closeMobileMenu">
<img class="logo" :src="image_paths.mobile_logo" alt="丙维科技">
</router-link>
</div>
<div class="mobileIconsCell">
<button
type="button"
id="hamburger"
class="mobile-menu-button"
ref="mobileMenuButton"
:aria-expanded="mobileMenuOpen ? 'true' : 'false'"
aria-controls="mobileMenu"
aria-label="打开或关闭菜单"
@click.stop="toggleMobileMenu"
>
<img :src="image_paths.hamburger" alt="">
</button>
</div>
</div>
</div>
</div>
<!-- 移动端菜单 -->
<div id="mobileMenu" class="headerMobileMenu bread" :class="{ active: mobileMenuOpen }" @click.self="closeMobileMenu">
<div class="menuDummy" @click="closeMobileMenu"></div>
<div class="menuContent" @click.stop>
<ul>
<li v-for="menu in menus" :key="menu.name">
<router-link
:to="menu.url"
v-if="menu.child.length === 0"
ref="mobileMenuLink"
@click.native="closeMobileMenu"
>{{ menu.name }}</router-link>
<span v-else class="menu-label hasSubmenu">{{ menu.name }}</span>
<ul class="subMenu" v-if="menu.child.length > 0">
<li v-for="sonMenu in menu.child" :key="sonMenu.name">
<router-link class="hasSubmenu" :to="sonMenu.url" @click.native="closeMobileMenu">{{ sonMenu.name }}</router-link>
</li>
</ul>
</li>
</ul>
<div class="goBack">
<button type="button" class="linkGoBack flexVerticalCenter" @click="closeMobileMenuAndScrollTop">
<img :src="image_paths.arrow_back" alt="" aria-hidden="true">
<span>返回</span>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { getMainMenu } from '../config/menu'
import { img } from '@/utils/image'
export default {
name: 'ihead',
data() {
return {
isMiniHeader: false,
mobileMenuOpen: false,
activeDropdownIndex: null,
menus: getMainMenu()
}
},
computed: {
image_paths() {
return {
logo: img('logo.png'),
mobile_logo: img('logo.png'),
hamburger: img('svg/ic-hamburger.svg'),
arrow_back: img('svg/ic-arrow-back.svg')
}
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
document.addEventListener('keydown', this.handleKeydown)
this.handleScroll()
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
document.removeEventListener('keydown', this.handleKeydown)
},
methods: {
handleScroll() {
this.isMiniHeader = window.pageYOffset > 200
},
toggleMobileMenu() {
this.mobileMenuOpen = !this.mobileMenuOpen
if (this.mobileMenuOpen) {
this.$nextTick(() => {
const firstLink = this.$refs.mobileMenuLink && this.$refs.mobileMenuLink[0]
if (firstLink && firstLink.$el) {
firstLink.$el.focus()
}
})
}
},
closeMobileMenu() {
this.mobileMenuOpen = false
},
closeMobileMenuAndRestoreFocus() {
this.closeMobileMenu()
this.$nextTick(() => {
if (this.$refs.mobileMenuButton) {
this.$refs.mobileMenuButton.focus()
}
})
},
closeMobileMenuAndScrollTop() {
this.closeMobileMenu()
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
})
},
handleKeydown(e) {
if (e.key === 'Escape') {
this.closeDropdown()
if (this.mobileMenuOpen) {
this.closeMobileMenuAndRestoreFocus()
}
}
},
openDropdown(index) {
if (this.menus[index] && this.menus[index].child.length > 0) {
this.activeDropdownIndex = index
}
},
closeDropdown() {
this.activeDropdownIndex = null
},
handleNavEnter(menu, index) {
if (menu.child.length > 0) {
this.openDropdown(index)
return
}
this.$router.push(menu.url)
}
}
}
</script>
<style scoped>
/* 使用更高优先级覆盖 common.css 的样式 */
/* 重置旧表格布局样式 */
.header table,
.header table tbody,
.header table tbody tr,
.header table tbody tr td {
display: block;
width: auto;
height: auto;
border: none;
padding: 0;
margin: 0;
}
/* 头部基础样式 */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(16px);
transition: all 0.3s ease;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
/* 覆盖旧表格布局 */
display: block;
height: auto;
}
.header.mini {
background: rgba(255, 255, 255, 0.98);
box-shadow: 0 12px 32px rgba(16, 24, 40, 0.08);
}
/* 主要导航区域 - 覆盖旧样式 */
.bottom {
padding: 15px 0;
/* 覆盖旧表格布局 */
display: block;
height: auto;
}
.header .content {
max-width: 1180px;
margin: 0 auto;
padding: 0 24px;
box-sizing: border-box;
}
.nav-container {
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
}
.logoCell {
flex-shrink: 0;
margin: 0 32px 0 0;
}
.logoCell .logo {
height: 50px;
transition: transform 0.3s ease;
}
.logoCell a {
display: inline-flex;
align-items: center;
}
.logoCell .logo:hover {
transform: scale(1.05);
}
.mainMenuCell {
flex: 1;
display: flex;
justify-content: flex-start;
}
/* 主导航菜单 */
.mainMenu {
display: flex;
gap: 0;
justify-content: flex-start;
align-items: center;
}
.nav-item {
position: relative;
}
.nav-link {
display: block;
padding: 14px 22px;
color: #1f2937;
text-decoration: none;
font-weight: 500;
font-size: 16px;
transition: all 0.3s ease;
border-radius: 6px;
position: relative;
white-space: nowrap;
}
.nav-link:hover {
color: #2563eb;
background: rgba(37, 99, 235, 0.08);
}
.nav-link::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
background: #2563eb;
transition: all 0.3s ease;
transform: translateX(-50%);
}
.nav-link:hover::after {
width: 80%;
}
/* 下拉菜单 */
.dropDown {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
background: white;
border-radius: 8px;
box-shadow: 0 18px 42px rgba(16, 24, 40, 0.14);
padding: 15px 0;
min-width: 200px;
opacity: 0;
visibility: hidden;
transform: translateX(-50%) translateY(-10px);
transition: all 0.3s ease;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.nav-item:hover .dropDown,
.nav-item:focus-within .dropDown,
.dropDown.visible {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(0);
}
.dropdown-item {
display: block;
padding: 12px 25px;
color: #2c3e50;
text-decoration: none;
transition: all 0.3s ease;
border-radius: 0;
text-align: center;
}
.dropdown-item:hover {
background: rgba(37, 99, 235, 0.08);
color: #2563eb;
padding-left: 30px;
}
/* 下拉箭头 */
.downCaret {
display: inline-block;
margin-left: 5px;
width: 7px;
height: 7px;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(45deg) translateY(-2px);
transition: transform 0.3s ease;
}
.nav-item:hover .downCaret,
.nav-item:focus-within .downCaret {
transform: rotate(225deg) translateY(-1px);
}
/* 移动端导航 */
.mobile-nav {
display: flex;
justify-content: space-between;
align-items: center;
}
.mobile .logo {
height: 40px;
}
#hamburger {
width: 24px;
height: 24px;
padding: 0;
border: 0;
background: transparent;
cursor: pointer;
transition: transform 0.3s ease;
}
#hamburger img {
display: block;
width: 100%;
height: 100%;
}
#hamburger:hover {
transform: scale(1.1);
}
/* 移动端菜单 */
.headerMobileMenu {
position: fixed;
top: 0;
left: -100%;
width: 100%;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
transition: left 0.3s ease;
z-index: 1001;
}
.headerMobileMenu.active {
left: 0;
}
.menuDummy {
position: absolute;
inset: 0;
}
.menuContent {
position: relative;
background: white;
width: 80%;
height: 100%;
padding: 20px;
overflow-y: auto;
}
.menuContent ul {
list-style: none;
padding: 0;
}
.menuContent li {
margin: 15px 0;
}
.menuContent a,
.menuContent .menu-label {
display: block;
padding: 15px;
color: #2c3e50;
text-decoration: none;
font-weight: 500;
border-radius: 8px;
transition: all 0.3s ease;
}
.menuContent a:hover,
.menuContent .menu-label:hover {
background: rgba(37, 99, 235, 0.08);
color: #2563eb;
}
.subMenu {
margin-left: 20px;
border-left: 2px solid #e9ecef;
padding-left: 15px;
}
.goBack {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e9ecef;
}
.linkGoBack {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 15px;
border: 0;
background: transparent;
color: #6c757d;
text-decoration: none;
font-weight: 500;
cursor: pointer;
}
.linkGoBack:hover {
color: #2563eb;
}
/* 响应式设计 */
@media (max-width: 768px) {
.desktop {
display: none;
}
.mobile {
display: block;
}
.header .content {
padding: 0 16px;
}
.nav-container {
gap: 1rem;
}
}
@media (min-width: 769px) {
.mobile {
display: none;
}
.desktop {
display: block;
}
.nav-container {
gap: 2rem;
}
}
@media (max-width: 1200px) {
.nav-container {
gap: 1rem;
}
.nav-link {
padding: 12px 20px;
font-size: 15px;
}
}
@media (max-width: 1024px) {
.nav-link {
padding: 10px 15px;
font-size: 14px;
}
.logoCell .logo {
height: 45px;
}
}
/* 使用逻辑属性实现更好的间距 */
.nav-container {
gap: 2rem;
}
/* 使用逻辑属性实现更好的边框 */
.header {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
</style>