170 lines
5.9 KiB
Vue
170 lines
5.9 KiB
Vue
<script setup lang="ts">
|
|
const { t, locale, locales, setLocale } = useI18n();
|
|
const { currency } = usePrefs();
|
|
const cart = useCartStore();
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const keywords = ref(typeof route.query.q === "string" ? route.query.q : "");
|
|
watch(
|
|
() => route.query.q,
|
|
(q) => {
|
|
keywords.value = typeof q === "string" ? q : "";
|
|
},
|
|
);
|
|
|
|
function search(): void {
|
|
const q = keywords.value.trim();
|
|
void router.push({ path: "/search", query: q ? { q } : {} });
|
|
}
|
|
|
|
const currencies = ref<string[]>(["USD"]);
|
|
const { $api } = useNuxtApp();
|
|
onMounted(async () => {
|
|
try {
|
|
const list = await $api.listCurrencies();
|
|
if (list.length > 0) currencies.value = list.filter((c) => c.enabled).map((c) => c.code);
|
|
} catch {
|
|
/* keep default */
|
|
}
|
|
void cart.refresh();
|
|
});
|
|
|
|
const menuOpen = ref(false);
|
|
const isHome = computed(() => route.path === "/");
|
|
// The home page pins the category menu in the hero row instead, so the header
|
|
// dropdown is hover-only and only on non-home pages.
|
|
const showMenu = computed(() => menuOpen.value && !isHome.value);
|
|
</script>
|
|
|
|
<template>
|
|
<header class="bg-surface">
|
|
<div class="max-w-mall mx-auto flex items-center gap-10 px-4 py-6">
|
|
<NuxtLink
|
|
to="/"
|
|
class="text-primary text-[34px] leading-none font-extrabold tracking-[1px] no-underline"
|
|
>VMall</NuxtLink
|
|
>
|
|
|
|
<div class="flex min-w-0 flex-1 items-center">
|
|
<div class="flex min-w-0 flex-1 items-center">
|
|
<input
|
|
v-model="keywords"
|
|
class="border-border bg-surface text-text focus:border-primary h-[38px] min-w-0 flex-1 border border-r-0 px-3 text-[12px] outline-none"
|
|
type="text"
|
|
:placeholder="t('shell.searchPlaceholder')"
|
|
@keyup.enter="search"
|
|
/>
|
|
<button
|
|
class="border-border bg-surface text-muted hover:bg-primary flex h-[38px] w-[38px] items-center justify-center border transition-colors hover:text-white"
|
|
type="button"
|
|
:aria-label="t('common.search')"
|
|
@click="search"
|
|
>
|
|
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
|
|
<path
|
|
d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 10-.7.7l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0A4.5 4.5 0 1114 9.5 4.5 4.5 0 019.5 14z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="border-border ml-7 flex h-[38px] items-center gap-5 border px-5 text-[12px]">
|
|
<NuxtLink
|
|
to="/user"
|
|
class="text-muted hover:text-primary relative no-underline transition-colors"
|
|
>{{ t("shell.myMall") }}</NuxtLink
|
|
>
|
|
<NuxtLink
|
|
to="/cart"
|
|
class="text-muted hover:text-primary relative no-underline transition-colors"
|
|
>
|
|
{{ t("shell.myCart") }}
|
|
<span
|
|
v-if="cart.count > 0"
|
|
class="bg-primary absolute -top-3 -right-3 rounded-sm px-1 text-[11px] leading-4 text-white"
|
|
>{{ cart.count }}</span
|
|
>
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
<select
|
|
:value="currency"
|
|
:aria-label="t('common.currency')"
|
|
class="border-border bg-surface text-text focus:border-primary focus:outline-primary/30 h-[30px] min-w-[76px] rounded-md border px-2 text-[12px] focus:outline-2"
|
|
@change="currency = ($event.target as HTMLSelectElement).value"
|
|
>
|
|
<option v-for="c in currencies" :key="c" :value="c">{{ c }}</option>
|
|
</select>
|
|
<select
|
|
:value="locale"
|
|
:aria-label="t('common.language')"
|
|
class="border-border bg-surface text-text focus:border-primary focus:outline-primary/30 h-[30px] min-w-[76px] rounded-md border px-2 text-[12px] focus:outline-2"
|
|
@change="setLocale(($event.target as HTMLSelectElement).value as 'en' | 'zh')"
|
|
>
|
|
<option v-for="l in locales" :key="l.code" :value="l.code">{{ l.name }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="h-10 bg-[#2d2d2d] text-sm leading-10 text-white">
|
|
<div class="max-w-mall mx-auto flex px-4">
|
|
<div
|
|
class="bg-primary relative w-[240px] cursor-pointer pl-[15px] font-medium"
|
|
@mouseenter="menuOpen = true"
|
|
@mouseleave="menuOpen = false"
|
|
>
|
|
{{ t("shell.allCategories") }}
|
|
<ShellCategoryMenu v-show="showMenu" />
|
|
</div>
|
|
<ul class="flex pl-5">
|
|
<li>
|
|
<NuxtLink
|
|
to="/"
|
|
class="block px-[30px] text-white no-underline transition-colors hover:bg-black"
|
|
>{{ t("shell.nav.home") }}</NuxtLink
|
|
>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
to="/stores"
|
|
class="block px-[30px] text-white no-underline transition-colors hover:bg-black"
|
|
>{{ t("shell.nav.stores") }}</NuxtLink
|
|
>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
to="/seckill"
|
|
class="block px-[30px] text-white no-underline transition-colors hover:bg-black"
|
|
>{{ t("shell.nav.seckill") }}</NuxtLink
|
|
>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
to="/collective"
|
|
class="block px-[30px] text-white no-underline transition-colors hover:bg-black"
|
|
>{{ t("shell.nav.collective") }}</NuxtLink
|
|
>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
to="/integral"
|
|
class="block px-[30px] text-white no-underline transition-colors hover:bg-black"
|
|
>{{ t("shell.nav.integral") }}</NuxtLink
|
|
>
|
|
</li>
|
|
<li>
|
|
<NuxtLink
|
|
to="/user"
|
|
class="block px-[30px] text-white no-underline transition-colors hover:bg-black"
|
|
>{{ t("shell.nav.help") }}</NuxtLink
|
|
>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
</template>
|