719 lines
15 KiB
Vue
719 lines
15 KiB
Vue
<script setup>
|
||
import { ref, reactive, onMounted, computed, nextTick, watch } from "vue";
|
||
import { useI18n } from "#imports";
|
||
import { useRoute, useRouter } from "#imports";
|
||
import { useNavigation } from "@/composables/useNavigation.js";
|
||
import { useLanguageSwitch } from "@/composables/useLanguageSwitch.js";
|
||
|
||
const { locale, t } = useI18n();
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const { navigateTo, localePath } = useNavigation();
|
||
const { switchLanguage, initLanguage } = useLanguageSwitch();
|
||
|
||
// 语言显示
|
||
const langToShow = computed(() => {
|
||
switch (locale.value) {
|
||
case "en":
|
||
return "English";
|
||
case "cn":
|
||
return "中文";
|
||
default:
|
||
return "English";
|
||
}
|
||
});
|
||
|
||
// 语言列表
|
||
const langList = ref([
|
||
{ name: "简体中文", code: "cn", checked: false },
|
||
{ name: "English", code: "en", checked: false },
|
||
]);
|
||
|
||
// 菜单数据 - 使用计算属性自动响应语言变化
|
||
const menuItems = computed(() => [
|
||
{
|
||
name: t("nav.home"),
|
||
enName: "home",
|
||
path: "/",
|
||
active: false,
|
||
},
|
||
{
|
||
name: t("nav.about"),
|
||
enName: "about",
|
||
path: "/about-us",
|
||
active: false,
|
||
},
|
||
{
|
||
name: t("nav.products"),
|
||
enName: "products",
|
||
path: "/product",
|
||
active: false,
|
||
},
|
||
{
|
||
name: t("nav.contact"),
|
||
enName: "contact",
|
||
path: "/contact-us",
|
||
active: false,
|
||
},
|
||
]);
|
||
|
||
// 移动端菜单控制
|
||
const isMobileMenuOpen = ref(false);
|
||
const langPopup = ref(false);
|
||
|
||
// 语言切换下拉显示控制
|
||
const showLangDropdown = ref(false);
|
||
|
||
// 语言切换防抖
|
||
const isSwitching = ref(false);
|
||
|
||
// 语言切换
|
||
const chooseLanguage = async (lang) => {
|
||
if (lang.code === locale.value || isSwitching.value) {
|
||
showLangDropdown.value = false;
|
||
return;
|
||
}
|
||
|
||
try {
|
||
isSwitching.value = true;
|
||
// 使用新的语言切换方法,传递正确的参数
|
||
await switchLanguage(lang.code, router);
|
||
} catch (error) {
|
||
console.warn('Language switch failed:', error);
|
||
} finally {
|
||
isSwitching.value = false;
|
||
showLangDropdown.value = false;
|
||
}
|
||
};
|
||
|
||
// 菜单点击
|
||
const handleMenuClick = (item) => {
|
||
// 使用统一的导航方法
|
||
navigateTo(item.path);
|
||
isMobileMenuOpen.value = false;
|
||
};
|
||
|
||
// 设置当前活跃菜单
|
||
const setActiveMenu = () => {
|
||
const currentPath = route.path;
|
||
// 重置所有状态
|
||
const items = menuItems.value;
|
||
items.forEach((item) => {
|
||
item.active = false;
|
||
});
|
||
|
||
// 根据路径设置激活状态
|
||
if (currentPath === "/" || currentPath === "/cn") {
|
||
const homeItem = items.find((item) => item.enName === "home");
|
||
if (homeItem) {
|
||
homeItem.active = true;
|
||
}
|
||
} else if (currentPath.includes("/about-us")) {
|
||
const aboutItem = items.find((item) => item.enName === "about");
|
||
if (aboutItem) {
|
||
aboutItem.active = true;
|
||
}
|
||
} else if (currentPath.includes("/product")) {
|
||
const productItem = items.find((item) => item.enName === "products");
|
||
if (productItem) {
|
||
productItem.active = true;
|
||
}
|
||
} else if (currentPath.includes("/contact-us")) {
|
||
const contactItem = items.find((item) => item.enName === "contact");
|
||
if (contactItem) {
|
||
contactItem.active = true;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 切换移动端菜单
|
||
const toggleMobileMenu = () => {
|
||
isMobileMenuOpen.value = !isMobileMenuOpen.value;
|
||
};
|
||
|
||
// 处理logo点击
|
||
const handleLogoClick = () => {
|
||
// 使用统一的导航方法跳转到首页
|
||
navigateTo("/");
|
||
isMobileMenuOpen.value = false;
|
||
};
|
||
|
||
onMounted(() => {
|
||
// 初始化语言
|
||
initLanguage();
|
||
|
||
// 更新语言列表选中状态
|
||
langList.value.forEach((item) => {
|
||
item.checked = item.code === locale.value;
|
||
});
|
||
|
||
// 确保在下一个 tick 中设置激活菜单
|
||
nextTick(() => {
|
||
setActiveMenu();
|
||
});
|
||
});
|
||
|
||
// 监听语言变化,更新语言列表选中状态
|
||
watch(() => locale.value, (newLocale) => {
|
||
langList.value.forEach((item) => {
|
||
item.checked = item.code === newLocale;
|
||
});
|
||
}, { immediate: true });
|
||
|
||
// 监听路由变化,更新激活状态
|
||
watch(
|
||
() => route.path,
|
||
(newPath) => {
|
||
setTimeout(() => {
|
||
setActiveMenu();
|
||
}, 50);
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
</script>
|
||
|
||
<template>
|
||
<ClientOnly>
|
||
<header class="navbar">
|
||
<div class="navbar-container">
|
||
<!-- Logo -->
|
||
<div class="navbar-logo" @click="handleLogoClick">
|
||
<img src="/logo.png" alt="明阳良光" class="logo-image" />
|
||
</div>
|
||
|
||
<!-- 右侧区域:菜单+语言切换 -->
|
||
<div class="navbar-right">
|
||
<!-- 桌面端菜单 -->
|
||
<nav class="navbar-menu desktop-menu">
|
||
<ul class="menu-list">
|
||
<li
|
||
v-for="item in menuItems"
|
||
:key="item.enName"
|
||
class="menu-item"
|
||
:class="{ active: item.active }"
|
||
@click="handleMenuClick(item)"
|
||
>
|
||
{{ item.name }}
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
|
||
<!-- 语言切换和移动端按钮 -->
|
||
<div class="navbar-actions">
|
||
<!-- 语言切换 -->
|
||
<div
|
||
class="language-container"
|
||
@mouseenter="showLangDropdown = true"
|
||
@mouseleave="showLangDropdown = false"
|
||
@click="showLangDropdown = !showLangDropdown"
|
||
>
|
||
<div class="language-selector">
|
||
<img
|
||
class="lang-earth-icon"
|
||
src="/static/home/earch.png"
|
||
:alt="langToShow"
|
||
:title="langToShow"
|
||
/>
|
||
<span class="lang-text">{{ langToShow }}</span>
|
||
<svg
|
||
class="lang-icon"
|
||
:class="{ rotate: showLangDropdown }"
|
||
width="12"
|
||
height="12"
|
||
viewBox="0 0 12 12"
|
||
>
|
||
<path
|
||
d="M2 4l4 4 4-4"
|
||
stroke="currentColor"
|
||
stroke-width="2"
|
||
fill="none"
|
||
/>
|
||
</svg>
|
||
</div>
|
||
|
||
<!-- 语言下拉菜单 -->
|
||
<div
|
||
class="lang-dropdown"
|
||
v-show="showLangDropdown"
|
||
@click.stop
|
||
>
|
||
<div
|
||
v-for="lang in langList"
|
||
:key="lang.code"
|
||
class="lang-option"
|
||
:class="{ active: lang.checked, disabled: isSwitching }"
|
||
@click="chooseLanguage(lang)"
|
||
>
|
||
{{ lang.name }}
|
||
<span v-if="isSwitching && lang.checked" class="loading-dot">...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 移动端菜单按钮 -->
|
||
<button class="mobile-menu-btn" @click="toggleMobileMenu">
|
||
<span
|
||
class="hamburger-line"
|
||
:class="{ active: isMobileMenuOpen }"
|
||
></span>
|
||
<span
|
||
class="hamburger-line"
|
||
:class="{ active: isMobileMenuOpen }"
|
||
></span>
|
||
<span
|
||
class="hamburger-line"
|
||
:class="{ active: isMobileMenuOpen }"
|
||
></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 移动端菜单 -->
|
||
<div class="mobile-menu" :class="{ open: isMobileMenuOpen }">
|
||
<ul class="mobile-menu-list">
|
||
<li
|
||
v-for="item in menuItems"
|
||
:key="item.enName"
|
||
class="mobile-menu-item"
|
||
:class="{ active: item.active }"
|
||
@click="handleMenuClick(item)"
|
||
>
|
||
{{ item.name }}
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<!-- 移动端背景遮罩 -->
|
||
<div
|
||
class="mobile-overlay"
|
||
:class="{ show: isMobileMenuOpen }"
|
||
@click="isMobileMenuOpen = false"
|
||
></div>
|
||
</header>
|
||
</ClientOnly>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.navbar {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: #ffffff;
|
||
backdrop-filter: blur(10px);
|
||
border-bottom: 1px solid #e0e0e0;
|
||
z-index: 1000;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
pointer-events: auto;
|
||
line-height: 1.2;
|
||
|
||
&-container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 20px;
|
||
height: 50px;
|
||
gap: 60px;
|
||
pointer-events: auto;
|
||
|
||
/* 4K 和超大屏幕 */
|
||
@media (min-width: 2560px) {
|
||
max-width: 1800px;
|
||
padding: 0 60px;
|
||
}
|
||
|
||
/* 1920px 屏幕 */
|
||
@media (min-width: 1920px) and (max-width: 2559px) {
|
||
max-width: 1200px;
|
||
padding: 0 50px;
|
||
}
|
||
|
||
/* 1440px 屏幕 */
|
||
@media (min-width: 1440px) and (max-width: 1919px) {
|
||
max-width: 1200px;
|
||
padding: 0 40px;
|
||
}
|
||
|
||
/* 1024px 屏幕 */
|
||
@media (max-width: 1024px) {
|
||
padding: 0 30px;
|
||
}
|
||
|
||
/* 768px 屏幕 */
|
||
@media (max-width: 768px) {
|
||
padding: 0 20px;
|
||
height: 50px;
|
||
gap: 30px;
|
||
}
|
||
|
||
/* 480px 屏幕 */
|
||
@media (max-width: 480px) {
|
||
padding: 0 16px;
|
||
}
|
||
|
||
/* 375px 屏幕 */
|
||
@media (max-width: 375px) {
|
||
padding: 0 12px;
|
||
}
|
||
}
|
||
|
||
&-logo {
|
||
cursor: pointer;
|
||
transition: transform 0.2s ease;
|
||
|
||
&:hover {
|
||
transform: scale(1.02);
|
||
}
|
||
|
||
.logo-image {
|
||
height: 36px;
|
||
width: auto;
|
||
object-fit: contain;
|
||
|
||
@media (max-width: 768px) {
|
||
height: 32px;
|
||
}
|
||
}
|
||
}
|
||
|
||
&-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 40px;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
&-menu {
|
||
&.desktop-menu {
|
||
@media (max-width: 768px) {
|
||
display: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
&-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
pointer-events: auto;
|
||
|
||
@media (max-width: 768px) {
|
||
gap: 16px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 菜单样式
|
||
.menu-list {
|
||
display: flex;
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
gap: 0;
|
||
align-items: center;
|
||
}
|
||
|
||
.menu-item {
|
||
position: relative;
|
||
padding: 16px 20px;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
color: #333333;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
white-space: nowrap;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
line-height: 1.4;
|
||
|
||
// 下划线样式
|
||
&::after {
|
||
content: "";
|
||
position: absolute;
|
||
bottom: 12px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 0;
|
||
height: 2px;
|
||
background-color: #000000;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
&:hover {
|
||
color: #000000;
|
||
|
||
&::after {
|
||
width: 30px;
|
||
bottom: 12px;
|
||
}
|
||
}
|
||
|
||
&.active {
|
||
color: #000000;
|
||
font-weight: 600;
|
||
|
||
// 激活时显示下划线
|
||
&::after {
|
||
width: 25px;
|
||
bottom: 12px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
padding: 14px 16px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
padding: 12px 14px;
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
|
||
// 语言容器
|
||
.language-container {
|
||
position: relative;
|
||
z-index: 1001;
|
||
}
|
||
|
||
// 语言选择器
|
||
.language-selector {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 12px 16px;
|
||
background: transparent;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
z-index: 1001;
|
||
pointer-events: auto;
|
||
border-radius: 4px;
|
||
|
||
&:hover {
|
||
background: rgba(0, 0, 0, 0.02);
|
||
}
|
||
|
||
.lang-earth-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
object-fit: contain;
|
||
filter: brightness(0.6);
|
||
}
|
||
|
||
.lang-text {
|
||
font-size: 15px;
|
||
color: #333333;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
transition: opacity 0.2s ease;
|
||
}
|
||
|
||
.lang-icon {
|
||
color: #666666;
|
||
transition: transform 0.3s ease;
|
||
pointer-events: none;
|
||
width: 12px;
|
||
height: 12px;
|
||
|
||
&.rotate {
|
||
transform: rotate(180deg);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
padding: 8px 12px;
|
||
gap: 6px;
|
||
|
||
.lang-earth-icon {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
.lang-text {
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.lang-dropdown {
|
||
position: absolute;
|
||
top: 100%;
|
||
right: 0;
|
||
margin-top: 0;
|
||
background: white;
|
||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||
overflow: hidden;
|
||
min-width: 140px;
|
||
z-index: 1002;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
|
||
|
||
.lang-option {
|
||
padding: 10px 14px;
|
||
font-size: 12px;
|
||
color: #000000;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
pointer-events: auto;
|
||
|
||
&:hover {
|
||
background: rgba(0, 0, 0, 0.04);
|
||
color: #000000;
|
||
}
|
||
|
||
&.active {
|
||
background: rgba(0, 0, 0, 0.08);
|
||
color: #000000;
|
||
font-weight: 600;
|
||
}
|
||
|
||
&.disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.loading-dot {
|
||
font-size: 10px;
|
||
color: #4f8cef;
|
||
animation: loading 1.5s infinite;
|
||
}
|
||
}
|
||
|
||
@keyframes loading {
|
||
0%, 20% { opacity: 0; }
|
||
50% { opacity: 1; }
|
||
80%, 100% { opacity: 0; }
|
||
}
|
||
|
||
// 移动端菜单按钮
|
||
.mobile-menu-btn {
|
||
display: none;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
width: 30px;
|
||
height: 30px;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
|
||
@media (max-width: 768px) {
|
||
display: flex;
|
||
}
|
||
|
||
.hamburger-line {
|
||
width: 20px;
|
||
height: 2px;
|
||
background: #000000;
|
||
border-radius: 1px;
|
||
transition: all 0.3s ease;
|
||
margin: 2px 0;
|
||
|
||
&:nth-child(1).active {
|
||
transform: rotate(45deg) translate(5px, 5px);
|
||
}
|
||
|
||
&:nth-child(2).active {
|
||
opacity: 0;
|
||
}
|
||
|
||
&:nth-child(3).active {
|
||
transform: rotate(-45deg) translate(7px, -6px);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 移动端菜单
|
||
.mobile-menu {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
background: white;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||
transform: translateY(-100%);
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transition: all 0.3s ease;
|
||
|
||
&.open {
|
||
transform: translateY(0);
|
||
opacity: 1;
|
||
visibility: visible;
|
||
}
|
||
|
||
&-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
&-item {
|
||
padding: 16px 20px;
|
||
font-size: 16px;
|
||
color: #000000;
|
||
cursor: pointer;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
transition: all 0.2s ease;
|
||
|
||
&:hover {
|
||
background: rgba(0, 0, 0, 0.04);
|
||
color: #000000;
|
||
}
|
||
|
||
&.active {
|
||
color: #000000;
|
||
background: rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 移动端背景遮罩
|
||
.mobile-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transition: all 0.3s ease;
|
||
z-index: -1;
|
||
|
||
&.show {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
}
|
||
}
|
||
|
||
/* 响应式调整 */
|
||
@media (max-width: 768px) {
|
||
.navbar {
|
||
&-container {
|
||
.logo-image {
|
||
height: 34px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|