fiee-official-website/src/components/customHeader/size768/index.vue
2025-10-15 18:26:36 +08:00

303 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 通用页头 -->
<NLayoutHeader
class="custom-header"
:class="{ 'header-scrolled': isScrolled }"
>
<div class="header-container">
<div class="logo" @click="handleToHome">
<NImage class="logo-image" :src="FiEELogo" preview-disabled />
</div>
<div class="header-right">
<div v-show="!showMenu" class="lang-switch" @click="openLanguagePicker">
<span class="lang-label">{{ currentLanguageLabel }}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="7"
height="4"
viewBox="0 0 7 4"
fill="none"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3.5 4L7 0L0 0L3.5 4Z"
fill="black"
/>
</svg>
</div>
<div
class="menu-btn"
:class="{ 'menu-open': showMenu }"
@click="toggleMenu"
>
<img
v-if="showMenu"
src="@/assets/image/768/menu-close.png"
alt="menu"
class="menu-icon"
/>
<img
v-else
src="@/assets/image/768/menu-open.png"
alt="menu"
class="menu-icon"
/>
</div>
</div>
</div>
</NLayoutHeader>
<transition name="fade-slide">
<div v-if="showMenu" class="mobile-menu-wrapper" @click.self="closeMenu">
<NConfigProvider :theme-overrides="themeOverrides">
<NMenu
mode="vertical"
:options="menuOptions"
:inverted="isScrolled"
class="mobile-menu"
accordion
v-model:value="selectedKey"
@update:value="handleMenuSelect"
/>
</NConfigProvider>
</div>
</transition>
<LanguagePicker
v-if="showLanguagePicker"
v-model="selectedLanguage"
:options="languageOptions"
@close="showLanguagePicker = false"
@confirm="handleConfirmLanguage"
/>
</template>
<script setup>
import FiEELogo from "@/assets/image/header/logo.png";
import { ref, onMounted, onUnmounted, computed } from "vue";
import { NMenu, NLayoutHeader, NImage, NIcon, NConfigProvider } from "naive-ui";
import { MenuSharp, CloseSharp } from "@vicons/ionicons5";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
import LanguagePicker from "@/components/languagePicker/index.vue";
const themeOverrides = {
Menu: {
itemTextColorHover: "#000",
itemTextColorActive: "#FF7BAC",
itemTextColorActiveHover: "#fff8fb",
itemColorHover: "#FDDFE9",
itemColorActive: "#fff",
itemColorActiveHover: "#fff8fb",
},
};
const { t, locale } = useI18n();
const router = useRouter();
const isScrolled = ref(false);
const showMenu = ref(false);
const selectedKey = ref(null);
// language picker
const showLanguagePicker = ref(false);
const selectedLanguage = ref(
localStorage.getItem("language") || locale.value || "en"
);
const languageOptions = computed(() => [
{ label: t("language.ja"), value: "ja", key: "ja" },
{ label: t("language.en"), value: "en", key: "en" },
{ label: t("language.zh"), value: "zh", key: "zh" },
{ label: t("language.zhTW"), value: "zh-TW", key: "zh-TW" },
]);
const currentLanguageLabel = computed(() => {
const found = languageOptions.value.find(
(opt) => opt.value === (locale.value || "en")
);
return found ? found.label : "English";
});
const openLanguagePicker = () => {
if (showMenu.value) {
return;
}
showLanguagePicker.value = true;
selectedLanguage.value = locale.value;
};
const handleConfirmLanguage = (lang) => {
locale.value = lang;
localStorage.setItem("language", lang);
showLanguagePicker.value = false;
};
const toggleMenu = () => {
showMenu.value = !showMenu.value;
};
const closeMenu = () => {
showMenu.value = false;
};
// 递归查找菜单项
function findMenuOptionByKey(options, key) {
for (const option of options) {
if (option.key === key) return option;
if (option.children) {
const found = findMenuOptionByKey(option.children, key);
if (found) return found;
}
}
return null;
}
// 菜单点击跳转
const handleMenuSelect = (key) => {
const option = findMenuOptionByKey(menuOptions.value, key);
if (option && option.href) {
router.push(option.href);
showMenu.value = false; // 跳转后收起菜单
}
};
// 使用统一的菜单配置
const { menuOptions } = useHeaderMenuConfig();
// 监听滚动事件
const handleScroll = () => {
//滚动距离大于100*2.5px时处理对应的header样式
isScrolled.value = window.scrollY >= 100;
};
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
onUnmounted(() => {
window.removeEventListener("scroll", handleScroll);
});
//点击回到首页
const handleToHome = () => {
router.push("/");
selectedKey.value = null; // 重置菜单选中状态
showMenu.value = false; // 在移动端同时关闭菜单
};
</script>
<style scoped lang="scss">
.custom-header {
transition: all 0.3s ease;
background: transparent;
height: 60 * 2.5px;
&.header-scrolled {
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 2 * 2.5px 8 * 2.5px rgba(0, 0, 0, 0.1);
}
}
.header-container {
width: 100%;
min-width: 0;
box-sizing: border-box;
padding: 0 59 * 2.5px;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.header-right {
display: flex;
align-items: center;
gap: 24 * 2.5px;
}
.lang-switch {
display: flex;
align-items: center;
gap: 11 * 2.5px;
font-weight: 600;
font-size: 14 * 2.5px;
cursor: pointer;
user-select: none;
}
.logo {
flex-shrink: 0;
margin-left: 11 * 2.5px;
display: flex;
align-items: center;
}
.logo-image {
width: 120 * 2.5px;
height: 27 * 2.5px;
}
.menu-btn {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 75 * 2.5px;
padding: 20 * 2.5px;
border-radius: 30 * 2.5px;
background: transparent;
user-select: none;
transition: background 0.2s;
position: relative;
width: 56 * 2.5px;
.menu-icon {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(0deg);
opacity: 1;
transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
}
.menu-icon-close {
opacity: 0;
transform: translate(-50%, -50%) rotate(-90deg) scale(0.8);
}
&.menu-open {
.menu-icon-menu {
opacity: 0;
transform: translate(-50%, -50%) rotate(90deg) scale(0.8);
}
.menu-icon-close {
opacity: 1;
transform: translate(-50%, -50%) rotate(0deg) scale(1);
}
}
}
.mobile-menu-wrapper {
position: fixed;
top: 60 * 2.5px;
left: 0;
width: 100vw;
background: #fff;
z-index: 1100;
box-shadow: 0 30 * 2.5px 40 * 2.5px rgba(0, 0, 0, 0.08);
padding: 40 * 2.5px;
max-height: 1500 * 2.5px;
overflow-y: auto;
:deep(.n-menu-item) {
font-weight: 600;
}
}
.fade-slide-enter-active,
.fade-slide-leave-active {
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-slide-enter-from,
.fade-slide-leave-to {
opacity: 0;
transform: translateY(-50 * 2.5px);
}
</style>