Compare commits
3 Commits
75344b2ee3
...
ab1e94b25e
Author | SHA1 | Date | |
---|---|---|---|
|
ab1e94b25e | ||
|
7466bcdcf7 | ||
|
f6b2956ac3 |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 24 KiB |
@ -19,17 +19,46 @@
|
|||||||
/>
|
/>
|
||||||
</NConfigProvider>
|
</NConfigProvider>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<div class="lang-switch-container">
|
||||||
|
<div class="lang-switch" @click.stop="toggleLanguagePicker">
|
||||||
|
<span class="lang-label">{{ currentLanguageLabel }}</span>
|
||||||
|
<svg
|
||||||
|
:class="{ rotated: showLanguagePicker }"
|
||||||
|
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>
|
||||||
|
<LanguagePicker
|
||||||
|
v-if="showLanguagePicker"
|
||||||
|
v-model="selectedLanguage"
|
||||||
|
:options="languageOptions"
|
||||||
|
@select="handleSelectLanguage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NLayoutHeader>
|
</NLayoutHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import FiEELogo from "@/assets/image/header/logo.png";
|
import FiEELogo from "@/assets/image/header/logo.png";
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||||
import { NMenu, NLayoutHeader, NImage, NConfigProvider } from "naive-ui";
|
import { NMenu, NLayoutHeader, NImage, NConfigProvider } from "naive-ui";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
||||||
|
import LanguagePicker from "@/components/languagePicker/size1440/index.vue";
|
||||||
|
|
||||||
const themeOverrides = {
|
const themeOverrides = {
|
||||||
Menu: {
|
Menu: {
|
||||||
@ -51,7 +80,7 @@ const themeOverrides = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 使用统一的菜单配置
|
// 使用统一的菜单配置
|
||||||
@ -60,6 +89,42 @@ const selectedKey = ref(null);
|
|||||||
|
|
||||||
const isScrolled = ref(false);
|
const isScrolled = ref(false);
|
||||||
|
|
||||||
|
// 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 toggleLanguagePicker = () => {
|
||||||
|
showLanguagePicker.value = !showLanguagePicker.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeLanguagePicker = () => {
|
||||||
|
showLanguagePicker.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectLanguage = (lang) => {
|
||||||
|
locale.value = lang;
|
||||||
|
localStorage.setItem("language", lang);
|
||||||
|
closeLanguagePicker();
|
||||||
|
selectedLanguage.value = locale.value;
|
||||||
|
};
|
||||||
|
|
||||||
// 递归查找菜单项
|
// 递归查找菜单项
|
||||||
function findMenuOptionByKey(options, key) {
|
function findMenuOptionByKey(options, key) {
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
@ -88,10 +153,12 @@ const handleScroll = () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener("scroll", handleScroll);
|
window.addEventListener("scroll", handleScroll);
|
||||||
|
window.addEventListener("click", closeLanguagePicker);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("scroll", handleScroll);
|
window.removeEventListener("scroll", handleScroll);
|
||||||
|
window.removeEventListener("click", closeLanguagePicker);
|
||||||
});
|
});
|
||||||
|
|
||||||
//点击回到首页
|
//点击回到首页
|
||||||
@ -247,6 +314,24 @@ const handleToHome = () => {
|
|||||||
transform: translateY(0) scale(1);
|
transform: translateY(0) scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lang-switch-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
&.rotated {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<style>
|
<style>
|
||||||
.header-menu .n-menu .n-menu-item-content .n-menu-item-content-header {
|
.header-menu .n-menu .n-menu-item-content .n-menu-item-content-header {
|
||||||
|
@ -19,17 +19,46 @@
|
|||||||
/>
|
/>
|
||||||
</NConfigProvider>
|
</NConfigProvider>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<div class="lang-switch-container">
|
||||||
|
<div class="lang-switch" @click.stop="toggleLanguagePicker">
|
||||||
|
<span class="lang-label">{{ currentLanguageLabel }}</span>
|
||||||
|
<svg
|
||||||
|
:class="{ rotated: showLanguagePicker }"
|
||||||
|
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>
|
||||||
|
<LanguagePicker
|
||||||
|
v-if="showLanguagePicker"
|
||||||
|
v-model="selectedLanguage"
|
||||||
|
:options="languageOptions"
|
||||||
|
@select="handleSelectLanguage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NLayoutHeader>
|
</NLayoutHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import FiEELogo from "@/assets/image/header/logo.png";
|
import FiEELogo from "@/assets/image/header/logo.png";
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||||
import { NMenu, NLayoutHeader, NImage, NConfigProvider } from "naive-ui";
|
import { NMenu, NLayoutHeader, NImage, NConfigProvider } from "naive-ui";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
||||||
|
import LanguagePicker from "@/components/languagePicker/size1920/index.vue";
|
||||||
|
|
||||||
const themeOverrides = {
|
const themeOverrides = {
|
||||||
Menu: {
|
Menu: {
|
||||||
@ -50,7 +79,7 @@ const themeOverrides = {
|
|||||||
optionColorHoverInverted: "#fddfea", // 反转主题下的子菜单悬停背景色
|
optionColorHoverInverted: "#fddfea", // 反转主题下的子菜单悬停背景色
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const { t } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 使用统一的菜单配置
|
// 使用统一的菜单配置
|
||||||
@ -59,6 +88,40 @@ const selectedKey = ref(null);
|
|||||||
|
|
||||||
const isScrolled = ref(false);
|
const isScrolled = ref(false);
|
||||||
|
|
||||||
|
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 toggleLanguagePicker = () => {
|
||||||
|
showLanguagePicker.value = !showLanguagePicker.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeLanguagePicker = () => {
|
||||||
|
showLanguagePicker.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectLanguage = (lang) => {
|
||||||
|
locale.value = lang;
|
||||||
|
localStorage.setItem("language", lang);
|
||||||
|
closeLanguagePicker();
|
||||||
|
selectedLanguage.value = locale.value;
|
||||||
|
};
|
||||||
|
|
||||||
// 递归查找菜单项
|
// 递归查找菜单项
|
||||||
function findMenuOptionByKey(options, key) {
|
function findMenuOptionByKey(options, key) {
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
@ -87,10 +150,12 @@ const handleScroll = () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener("scroll", handleScroll);
|
window.addEventListener("scroll", handleScroll);
|
||||||
|
window.addEventListener("click", closeLanguagePicker);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("scroll", handleScroll);
|
window.removeEventListener("scroll", handleScroll);
|
||||||
|
window.removeEventListener("click", closeLanguagePicker);
|
||||||
});
|
});
|
||||||
|
|
||||||
//点击回到首页
|
//点击回到首页
|
||||||
@ -240,4 +305,27 @@ const handleToHome = () => {
|
|||||||
transform: translateY(0) scale(1);
|
transform: translateY(0) scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-switch-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
&.rotated {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -12,23 +12,42 @@
|
|||||||
preview-disabled
|
preview-disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="header-right">
|
||||||
class="menu-btn"
|
<div class="lang-switch" @click="openLanguagePicker">
|
||||||
:class="{ 'menu-open': showMenu }"
|
<span class="lang-label">{{ currentLanguageLabel }}</span>
|
||||||
@click="toggleMenu"
|
<svg
|
||||||
>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<img
|
width="7"
|
||||||
v-if="showMenu"
|
height="4"
|
||||||
src="@/assets/image/375/menu-close.png"
|
viewBox="0 0 7 4"
|
||||||
alt="menu"
|
fill="none"
|
||||||
class="menu-icon"
|
>
|
||||||
/>
|
<path
|
||||||
<img
|
fill-rule="evenodd"
|
||||||
v-else
|
clip-rule="evenodd"
|
||||||
src="@/assets/image/375/menu-open.png"
|
d="M3.5 4L7 0L0 0L3.5 4Z"
|
||||||
alt="menu"
|
fill="black"
|
||||||
class="menu-icon"
|
/>
|
||||||
/>
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-btn"
|
||||||
|
:class="{ 'menu-open': showMenu }"
|
||||||
|
@click="toggleMenu"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="showMenu"
|
||||||
|
src="@/assets/image/375/menu-close.png"
|
||||||
|
alt="menu"
|
||||||
|
class="menu-icon"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
src="@/assets/image/375/menu-open.png"
|
||||||
|
alt="menu"
|
||||||
|
class="menu-icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NLayoutHeader>
|
</NLayoutHeader>
|
||||||
@ -47,16 +66,24 @@
|
|||||||
</NConfigProvider>
|
</NConfigProvider>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
<LanguagePicker
|
||||||
|
v-if="showLanguagePicker"
|
||||||
|
v-model="selectedLanguage"
|
||||||
|
:options="languageOptions"
|
||||||
|
@close="showLanguagePicker = false"
|
||||||
|
@confirm="handleConfirmLanguage"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import FiEELogo from "@/assets/image/header/logo.png";
|
import FiEELogo from "@/assets/image/header/logo.png";
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||||
import { NMenu, NLayoutHeader, NImage, NIcon, NConfigProvider } from "naive-ui";
|
import { NMenu, NLayoutHeader, NImage, NIcon, NConfigProvider } from "naive-ui";
|
||||||
import { MenuSharp, CloseSharp } from "@vicons/ionicons5";
|
import { MenuSharp, CloseSharp } from "@vicons/ionicons5";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
||||||
|
import LanguagePicker from "@/components/languagePicker/index.vue";
|
||||||
const themeOverrides = {
|
const themeOverrides = {
|
||||||
Menu: {
|
Menu: {
|
||||||
itemTextColorHover: "#000",
|
itemTextColorHover: "#000",
|
||||||
@ -67,13 +94,42 @@ const themeOverrides = {
|
|||||||
itemColorActiveHover: "#fff8fb",
|
itemColorActiveHover: "#fff8fb",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const { t } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const isScrolled = ref(false);
|
const isScrolled = ref(false);
|
||||||
const showMenu = ref(false);
|
const showMenu = ref(false);
|
||||||
const selectedKey = ref(null);
|
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 = () => {
|
||||||
|
showLanguagePicker.value = true;
|
||||||
|
selectedLanguage.value = locale.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmLanguage = (lang) => {
|
||||||
|
locale.value = lang;
|
||||||
|
localStorage.setItem("language", lang);
|
||||||
|
showLanguagePicker.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
showMenu.value = !showMenu.value;
|
showMenu.value = !showMenu.value;
|
||||||
};
|
};
|
||||||
@ -150,6 +206,19 @@ const handleToHome = () => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lang-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 11 * 5.12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.lang-caret {
|
||||||
|
font-size: 10 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
@ -259,4 +328,9 @@ const handleToHome = () => {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-50px);
|
transform: translateY(-50px);
|
||||||
}
|
}
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24 * 5.12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -8,23 +8,42 @@
|
|||||||
<div class="logo" @click="handleToHome">
|
<div class="logo" @click="handleToHome">
|
||||||
<NImage class="logo-image" :src="FiEELogo" preview-disabled />
|
<NImage class="logo-image" :src="FiEELogo" preview-disabled />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="header-right">
|
||||||
class="menu-btn"
|
<div class="lang-switch" @click="openLanguagePicker">
|
||||||
:class="{ 'menu-open': showMenu }"
|
<span class="lang-label">{{ currentLanguageLabel }}</span>
|
||||||
@click="toggleMenu"
|
<svg
|
||||||
>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<img
|
width="7"
|
||||||
v-if="showMenu"
|
height="4"
|
||||||
src="@/assets/image/768/menu-close.png"
|
viewBox="0 0 7 4"
|
||||||
alt="menu"
|
fill="none"
|
||||||
class="menu-icon"
|
>
|
||||||
/>
|
<path
|
||||||
<img
|
fill-rule="evenodd"
|
||||||
v-else
|
clip-rule="evenodd"
|
||||||
src="@/assets/image/768/menu-open.png"
|
d="M3.5 4L7 0L0 0L3.5 4Z"
|
||||||
alt="menu"
|
fill="black"
|
||||||
class="menu-icon"
|
/>
|
||||||
/>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</NLayoutHeader>
|
</NLayoutHeader>
|
||||||
@ -43,16 +62,24 @@
|
|||||||
</NConfigProvider>
|
</NConfigProvider>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
<LanguagePicker
|
||||||
|
v-if="showLanguagePicker"
|
||||||
|
v-model="selectedLanguage"
|
||||||
|
:options="languageOptions"
|
||||||
|
@close="showLanguagePicker = false"
|
||||||
|
@confirm="handleConfirmLanguage"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import FiEELogo from "@/assets/image/header/logo.png";
|
import FiEELogo from "@/assets/image/header/logo.png";
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||||
import { NMenu, NLayoutHeader, NImage, NIcon, NConfigProvider } from "naive-ui";
|
import { NMenu, NLayoutHeader, NImage, NIcon, NConfigProvider } from "naive-ui";
|
||||||
import { MenuSharp, CloseSharp } from "@vicons/ionicons5";
|
import { MenuSharp, CloseSharp } from "@vicons/ionicons5";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
||||||
|
import LanguagePicker from "@/components/languagePicker/index.vue";
|
||||||
const themeOverrides = {
|
const themeOverrides = {
|
||||||
Menu: {
|
Menu: {
|
||||||
itemTextColorHover: "#000",
|
itemTextColorHover: "#000",
|
||||||
@ -63,13 +90,42 @@ const themeOverrides = {
|
|||||||
itemColorActiveHover: "#fff8fb",
|
itemColorActiveHover: "#fff8fb",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const { t } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const isScrolled = ref(false);
|
const isScrolled = ref(false);
|
||||||
const showMenu = ref(false);
|
const showMenu = ref(false);
|
||||||
const selectedKey = ref(null);
|
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 = () => {
|
||||||
|
showLanguagePicker.value = true;
|
||||||
|
selectedLanguage.value = locale.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmLanguage = (lang) => {
|
||||||
|
locale.value = lang;
|
||||||
|
localStorage.setItem("language", lang);
|
||||||
|
showLanguagePicker.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
showMenu.value = !showMenu.value;
|
showMenu.value = !showMenu.value;
|
||||||
};
|
};
|
||||||
@ -146,6 +202,22 @@ const handleToHome = () => {
|
|||||||
justify-content: space-between;
|
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 {
|
.logo {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-left: 11 * 2.5px;
|
margin-left: 11 * 2.5px;
|
||||||
|
34
src/components/languagePicker/index.vue
Normal file
34
src/components/languagePicker/index.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useWindowSize } from "@vueuse/core";
|
||||||
|
|
||||||
|
import size375 from "./size375/index.vue";
|
||||||
|
import size768 from "./size768/index.vue";
|
||||||
|
import size1440 from "./size1440/index.vue";
|
||||||
|
import size1920 from "./size1920/index.vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const viewComponent = computed(() => {
|
||||||
|
const viewWidth = width.value;
|
||||||
|
if (viewWidth <= 450) {
|
||||||
|
return size375;
|
||||||
|
} else if (viewWidth <= 835) {
|
||||||
|
return size768;
|
||||||
|
} else if (viewWidth <= 1640) {
|
||||||
|
return size1440;
|
||||||
|
} else if (viewWidth <= 1920 || viewWidth > 1920) {
|
||||||
|
return size1920;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component :is="viewComponent" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
59
src/components/languagePicker/size1440/index.vue
Normal file
59
src/components/languagePicker/size1440/index.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="language-popover">
|
||||||
|
<div
|
||||||
|
v-for="opt in options"
|
||||||
|
:key="opt.value"
|
||||||
|
class="picker-item"
|
||||||
|
:class="{ active: opt.value === modelValue }"
|
||||||
|
@click="selectLanguage(opt.value)"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
modelValue: { type: String, required: true },
|
||||||
|
options: { type: Array, required: true },
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "select"]);
|
||||||
|
|
||||||
|
function selectLanguage(lang) {
|
||||||
|
emit("update:modelValue", lang);
|
||||||
|
emit("select", lang);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.language-popover {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 10px);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 8px;
|
||||||
|
z-index: 1000;
|
||||||
|
width: max-content;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
.picker-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: #fddfea;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
59
src/components/languagePicker/size1920/index.vue
Normal file
59
src/components/languagePicker/size1920/index.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="language-popover">
|
||||||
|
<div
|
||||||
|
v-for="opt in options"
|
||||||
|
:key="opt.value"
|
||||||
|
class="picker-item"
|
||||||
|
:class="{ active: opt.value === modelValue }"
|
||||||
|
@click="selectLanguage(opt.value)"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
modelValue: { type: String, required: true },
|
||||||
|
options: { type: Array, required: true },
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "select"]);
|
||||||
|
|
||||||
|
function selectLanguage(lang) {
|
||||||
|
emit("update:modelValue", lang);
|
||||||
|
emit("select", lang);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.language-popover {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 10px);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 8px;
|
||||||
|
z-index: 1000;
|
||||||
|
width: max-content;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
.picker-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: #fddfea;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
141
src/components/languagePicker/size375/index.vue
Normal file
141
src/components/languagePicker/size375/index.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div class="picker-mask" @click.self="emit('close')">
|
||||||
|
<div class="picker-panel">
|
||||||
|
<div class="picker-title">
|
||||||
|
{{ t("home.nav.please_select") }}
|
||||||
|
<svg
|
||||||
|
@click="emit('close')"
|
||||||
|
style="cursor: pointer"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M0.666016 9.74935C0.666016 4.59469 4.84469 0.416016 9.99935 0.416016C15.154 0.416016 19.3327 4.59469 19.3327 9.74935C19.3327 14.904 15.154 19.0827 9.99935 19.0827C4.84469 19.0827 0.666016 14.904 0.666016 9.74935Z"
|
||||||
|
fill="#CCCCCC"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.833 5.84961C13.1273 5.55596 13.6042 5.55565 13.8965 5.84863C14.1907 6.14223 14.1893 6.61848 13.8965 6.91211L11.0615 9.74609L13.9043 12.5898C14.1973 12.8848 14.1986 13.3607 13.9043 13.6543C13.6114 13.947 13.1344 13.9471 12.8408 13.6543L9.99707 10.8105L7.1582 13.6504C6.86386 13.9444 6.38729 13.9446 6.09375 13.6504C5.8002 13.3574 5.80045 12.8809 6.09473 12.5859L8.93359 9.74707L6.10254 6.91602C5.80956 6.62236 5.80889 6.1452 6.10254 5.85156C6.39486 5.55817 6.87209 5.55802 7.16699 5.85156L9.99805 8.68262L12.833 5.84961Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="language-list">
|
||||||
|
<div
|
||||||
|
v-for="opt in options"
|
||||||
|
:key="opt.value"
|
||||||
|
class="picker-item"
|
||||||
|
:class="{ active: opt.value === localValue }"
|
||||||
|
@click="localValue = opt.value"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="picker-actions">
|
||||||
|
<button class="picker-confirm" @click="confirm">
|
||||||
|
{{ t("home.nav.confirm_select") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, Teleport } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: String, required: true },
|
||||||
|
options: { type: Array, required: true },
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "close", "confirm"]);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const localValue = ref(props.modelValue);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(v) => {
|
||||||
|
localValue.value = v;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function confirm() {
|
||||||
|
emit("update:modelValue", localValue.value);
|
||||||
|
emit("confirm", localValue.value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.picker-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.picker-panel {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 3 * 5.12px 14 * 5.12px rgba(0, 0, 0, 0.16);
|
||||||
|
padding: 16 * 5.12px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.picker-title {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
color: #455363;
|
||||||
|
margin-bottom: 16 * 5.12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.language-list {
|
||||||
|
border-top: 1px solid #ededed;
|
||||||
|
border-bottom: 1px solid #ededed;
|
||||||
|
}
|
||||||
|
.picker-item {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
color: #9da3ad;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 44 * 5.12px;
|
||||||
|
border-top: 1px solid #f2f2f2;
|
||||||
|
}
|
||||||
|
.picker-item:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
.picker-item.active {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.picker-actions {
|
||||||
|
margin-top: 16 * 5.12px;
|
||||||
|
}
|
||||||
|
.picker-confirm {
|
||||||
|
width: 100%;
|
||||||
|
height: 44 * 5.12px;
|
||||||
|
background: #ff7bac;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8 * 5.12px;
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
}
|
||||||
|
</style>
|
141
src/components/languagePicker/size768/index.vue
Normal file
141
src/components/languagePicker/size768/index.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div class="picker-mask" @click.self="emit('close')">
|
||||||
|
<div class="picker-panel">
|
||||||
|
<div class="picker-title">
|
||||||
|
{{ t("home.nav.please_select") }}
|
||||||
|
<svg
|
||||||
|
@click="emit('close')"
|
||||||
|
style="cursor: pointer"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M0.666016 9.74935C0.666016 4.59469 4.84469 0.416016 9.99935 0.416016C15.154 0.416016 19.3327 4.59469 19.3327 9.74935C19.3327 14.904 15.154 19.0827 9.99935 19.0827C4.84469 19.0827 0.666016 14.904 0.666016 9.74935Z"
|
||||||
|
fill="#CCCCCC"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.833 5.84961C13.1273 5.55596 13.6042 5.55565 13.8965 5.84863C14.1907 6.14223 14.1893 6.61848 13.8965 6.91211L11.0615 9.74609L13.9043 12.5898C14.1973 12.8848 14.1986 13.3607 13.9043 13.6543C13.6114 13.947 13.1344 13.9471 12.8408 13.6543L9.99707 10.8105L7.1582 13.6504C6.86386 13.9444 6.38729 13.9446 6.09375 13.6504C5.8002 13.3574 5.80045 12.8809 6.09473 12.5859L8.93359 9.74707L6.10254 6.91602C5.80956 6.62236 5.80889 6.1452 6.10254 5.85156C6.39486 5.55817 6.87209 5.55802 7.16699 5.85156L9.99805 8.68262L12.833 5.84961Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="language-list">
|
||||||
|
<div
|
||||||
|
v-for="opt in options"
|
||||||
|
:key="opt.value"
|
||||||
|
class="picker-item"
|
||||||
|
:class="{ active: opt.value === localValue }"
|
||||||
|
@click="localValue = opt.value"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="picker-actions">
|
||||||
|
<button class="picker-confirm" @click="confirm">
|
||||||
|
{{ t("home.nav.confirm_select") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, Teleport } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: String, required: true },
|
||||||
|
options: { type: Array, required: true },
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "close", "confirm"]);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const localValue = ref(props.modelValue);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(v) => {
|
||||||
|
localValue.value = v;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function confirm() {
|
||||||
|
emit("update:modelValue", localValue.value);
|
||||||
|
emit("confirm", localValue.value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.picker-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.picker-panel {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 3 * 2.5px 14 * 2.5px rgba(0, 0, 0, 0.16);
|
||||||
|
padding: 16 * 2.5px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.picker-title {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 2.5px;
|
||||||
|
color: #455363;
|
||||||
|
margin-bottom: 16 * 2.5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.language-list {
|
||||||
|
border-top: 1px solid #ededed;
|
||||||
|
border-bottom: 1px solid #ededed;
|
||||||
|
}
|
||||||
|
.picker-item {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14 * 2.5px;
|
||||||
|
color: #9da3ad;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 44 * 2.5px;
|
||||||
|
border-top: 1px solid #f2f2f2;
|
||||||
|
}
|
||||||
|
.picker-item:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
.picker-item.active {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.picker-actions {
|
||||||
|
margin-top: 16 * 2.5px;
|
||||||
|
}
|
||||||
|
.picker-confirm {
|
||||||
|
width: 100%;
|
||||||
|
height: 44 * 2.5px;
|
||||||
|
background: #ff7bac;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8 * 2.5px;
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 2.5px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -26,8 +26,8 @@ function getBrowserLanguage() {
|
|||||||
return 'en' // 默认英语
|
return 'en' // 默认英语
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接设为英文
|
// 读取本地或浏览器语言,默认英文
|
||||||
const savedLanguage = 'en'
|
const savedLanguage = (typeof localStorage !== 'undefined' && localStorage.getItem('language')) || getBrowserLanguage()
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false, // 使用 Composition API
|
legacy: false, // 使用 Composition API
|
||||||
locale: savedLanguage,
|
locale: savedLanguage,
|
||||||
@ -37,7 +37,6 @@ const i18n = createI18n({
|
|||||||
zh,
|
zh,
|
||||||
ja,
|
ja,
|
||||||
'zh-TW': zhTW,
|
'zh-TW': zhTW,
|
||||||
de
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export default {
|
|||||||
home: "Home",
|
home: "Home",
|
||||||
company: "Company Overview",
|
company: "Company Overview",
|
||||||
businessintroduction: "Business Introduction",
|
businessintroduction: "Business Introduction",
|
||||||
please_select: "Please Select",
|
please_select: "Select Language",
|
||||||
confirm_select: "Confirm Selection",
|
confirm_select: "Confirm Selection",
|
||||||
investor: "Investor Guide",
|
investor: "Investor Guide",
|
||||||
},
|
},
|
||||||
|
@ -25,128 +25,233 @@ async function handleSubmit(e) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<main class="email-alerts-375">
|
||||||
class="min-h-60vh flex flex-col items-center justify-center relative px-4 py-8"
|
<!-- Header -->
|
||||||
>
|
<template v-if="!submitted">
|
||||||
<!-- Card -->
|
<div class="title-block">
|
||||||
<div
|
<div class="title-bar"></div>
|
||||||
class="w-full max-w-90vw p-4 bg-white/95 rounded-2xl shadow-lg animate-bounce-in"
|
<h2 class="title">E-Mail Alerts</h2>
|
||||||
>
|
<p class="subtitle">* Required Fields</p>
|
||||||
<template v-if="!submitted">
|
</div>
|
||||||
<h2
|
<!-- Card -->
|
||||||
class="text-xl font-bold text-#ff7bac mb-2 text-center tracking-wide"
|
<div class="card">
|
||||||
>
|
<form class="form" @submit="handleSubmit">
|
||||||
E-Mail Alerts
|
<div class="form-field">
|
||||||
</h2>
|
<label>* First Name</label>
|
||||||
<p class="text-xs text-gray-500 mb-4 text-center">* Required Fields</p>
|
<input v-model="form.firstName" type="text" />
|
||||||
<form class="flex flex-col gap-3" @submit="handleSubmit">
|
|
||||||
<div>
|
|
||||||
<label class="block text-gray-700 font-semibold mb-1 text-sm"
|
|
||||||
>* First Name</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model="form.firstName"
|
|
||||||
type="text"
|
|
||||||
class="w-full px-3 py-2 rounded-lg ring-8 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="form-field">
|
||||||
<label class="block text-gray-700 font-semibold mb-1 text-sm"
|
<label>* Last Name</label>
|
||||||
>* Last Name</label
|
<input v-model="form.lastName" type="text" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model="form.lastName"
|
|
||||||
type="text"
|
|
||||||
class="w-full px-3 py-2 rounded-lg ring-8 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="form-field">
|
||||||
<label class="block text-gray-700 font-semibold mb-1 text-sm"
|
<label>* Email</label>
|
||||||
>* Email</label
|
<input v-model="form.email" type="email" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model="form.email"
|
|
||||||
type="email"
|
|
||||||
class="w-full px-3 py-2 rounded-lg ring-8 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="form-field">
|
||||||
<label class="block text-gray-700 font-semibold mb-1 text-sm"
|
<label>* Company</label>
|
||||||
>* Company</label
|
<input v-model="form.company" type="text" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model="form.company"
|
|
||||||
type="text"
|
|
||||||
class="w-full px-3 py-2 rounded-lg ring-8 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="form-field">
|
||||||
<label class="block text-gray-700 font-semibold mb-1 text-sm"
|
<label>* Phone</label>
|
||||||
>Phone</label
|
<input v-model="form.phone" type="tel" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-model="form.phone"
|
|
||||||
type="tel"
|
|
||||||
class="w-full px-3 py-2 rounded-lg ring-8 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button type="submit" class="submit">Submit</button>
|
||||||
type="submit"
|
|
||||||
class="w-full py-3 rounded-xl text-white font-bold text-base active:scale-95 transition-all duration-200 animate-bounce-in animate-delay-200 mt-2 submit-btn"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</div>
|
||||||
<template v-else>
|
</template>
|
||||||
<div
|
<template v-else>
|
||||||
class="flex flex-col items-center justify-center min-h-[200px] animate-bounce-in"
|
<div class="success-block">
|
||||||
>
|
<div class="title-block">
|
||||||
<span
|
<div class="title-bar"></div>
|
||||||
class="i-mdi:check-circle-outline text-green-500 text-4xl mb-3"
|
<h2 class="title">Submitted successfully!</h2>
|
||||||
></span>
|
<p class="subtitle">The information you submitted is as follows:</p>
|
||||||
<h2 class="text-lg font-bold text-#ff7bac mb-2">
|
</div>
|
||||||
Submitted successfully!
|
|
||||||
</h2>
|
<div class="success-card">
|
||||||
<div class="text-gray-700 text-sm mb-3">
|
<div class="success-card-item">
|
||||||
The information you submitted is as follows:
|
<div class="font-semibold success-card-label">First Name:</div>
|
||||||
|
{{ form.firstName || "Not filled in" }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="success-card-item">
|
||||||
class="w-full bg-white/90 rounded-xl shadow p-3 space-y-1 text-gray-800 text-sm"
|
<div class="font-semibold success-card-label">Last Name:</div>
|
||||||
>
|
{{ form.lastName || "Not filled in" }}
|
||||||
<div>
|
</div>
|
||||||
<span class="font-semibold">First Name:</span
|
<div class="success-card-item">
|
||||||
>{{ form.firstName }}
|
<div class="font-semibold success-card-label">Email:</div>
|
||||||
</div>
|
{{ form.email || "Not filled in" }}
|
||||||
<div>
|
</div>
|
||||||
<span class="font-semibold">Last Name:</span>{{ form.lastName }}
|
<div class="success-card-item">
|
||||||
</div>
|
<div class="font-semibold success-card-label">Company:</div>
|
||||||
<div>
|
{{ form.company || "Not filled in" }}
|
||||||
<span class="font-semibold">Email:</span>{{ form.email }}
|
</div>
|
||||||
</div>
|
<div class="success-card-item">
|
||||||
<div>
|
<div class="font-semibold success-card-label">Phone:</div>
|
||||||
<span class="font-semibold">Company:</span>{{ form.company }}
|
{{ form.phone || "Not filled in" }}
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="font-semibold">Phone:</span
|
|
||||||
>{{ form.phone || "(Not filled)" }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="font-semibold">Alert Type:</span
|
|
||||||
>{{
|
|
||||||
form.alertType === "all" ? "All Alerts" : "Customize Alerts"
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="submitted-bg"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
/* Keep mobile background simple */
|
/* 375*5.12px design exact *5.12px values from Figma */
|
||||||
.submit-btn {
|
.email-alerts-375 {
|
||||||
background: linear-gradient(to right, #ff7bac, #00ffff);
|
max-width: 343 * 5.12px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24 * 5.12px; /* spacing between header and card */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-block {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8 * 5.12px;
|
||||||
|
padding: 0 16 * 5.12px;
|
||||||
|
margin-top: 47 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-bar {
|
||||||
|
width: 58 * 5.12px;
|
||||||
|
height: 7 * 5.12px;
|
||||||
|
background: #ff7bac; /* 主题色-粉色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
color: #000000; /* 标题色 */
|
||||||
|
font-family: "PingFang SC", Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 24 * 5.12px; /* 手机端主标题 */
|
||||||
|
line-height: 24 * 5.12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
color: #455363; /* 正文色 */
|
||||||
|
font-family: "PingFang SC", Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14 * 5.12px; /* 移动端正文 */
|
||||||
|
line-height: 14 * 5.12px;
|
||||||
|
letter-spacing: 0.48 * 5.12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 16 * 5.12px;
|
||||||
|
box-shadow: 0 * 5.12px 3 * 5.12px 14 * 5.12px 0 * 5.12px rgba(0, 0, 0, 0.16);
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 24 * 5.12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field > label {
|
||||||
|
color: #000000;
|
||||||
|
font-family: "PingFang SC", Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
line-height: 14 * 5.12px;
|
||||||
|
letter-spacing: 0.48 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field > input {
|
||||||
|
width: 100%;
|
||||||
|
height: 38 * 5.12px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1 * 5.12px solid #e0e0e6;
|
||||||
|
border-radius: 8 * 5.12px;
|
||||||
|
padding: 0 12 * 5.12px;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
width: 100%;
|
||||||
|
height: 48 * 5.12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8 * 5.12px;
|
||||||
|
background: #ff7bac;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: "PingFang SC", Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 20 * 5.12px; /* 手机端三级标题 */
|
||||||
|
line-height: 20 * 5.12px;
|
||||||
|
letter-spacing: 1.2 * 5.12px; /* 按钮字间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* success view simple alignment */
|
||||||
|
.success-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-title {
|
||||||
|
margin: 0 0 8 * 5.12px 0;
|
||||||
|
color: #ff7bac;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-subtitle {
|
||||||
|
margin-bottom: 12 * 5.12px;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-card {
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 12 * 5.12px;
|
||||||
|
box-shadow: 0 1 * 5.12px 3 * 5.12px rgba(0, 0, 0, 0.08);
|
||||||
|
padding: 56 * 5.12px 38 * 5.12px;
|
||||||
|
min-height: 484 * 5.12px;
|
||||||
|
margin-top: 24 * 5.12px;
|
||||||
|
}
|
||||||
|
.submitted-bg {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 186 * 5.12px;
|
||||||
|
background-image: url("@/assets/image/375/email-alerts-submit.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: bottom;
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
.success-card-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16 * 5.12px;
|
||||||
|
}
|
||||||
|
.success-card-label {
|
||||||
|
width: 92 * 5.12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user