Home hero becomes a 450px flex row with a pinned 240x450 category rail (ShellCategoryMenu `pinned` mode) left of the 960px center-cropped carousel; hovering a top-level category expands the mega-menu over the carousel. The header no longer collapses on scroll, and its category dropdown is now hover-only on non-home pages. OpenSpec change: openspec/changes/pin-home-category-menu BREAKING CHANGE: the header logo/search/cart bar no longer auto-hides past 200px of scroll, and the home page no longer shows the header category dropdown (it is pinned in the hero row instead).
162 lines
3.5 KiB
Vue
162 lines
3.5 KiB
Vue
<script setup lang="ts">
|
|
import { topCategories, childCategories } from "~/mock/data";
|
|
import { t as pick } from "@vmall/shared";
|
|
|
|
const props = withDefaults(defineProps<{ pinned?: boolean }>(), { pinned: false });
|
|
|
|
const { locale } = useI18n();
|
|
const open = ref(false);
|
|
const hoverId = ref<string | null>(null);
|
|
|
|
const cats = computed(() =>
|
|
topCategories().map((c) => ({
|
|
cat: c,
|
|
children: childCategories(c.id).map((cc) => ({ cat: cc, grandchildren: childCategories(cc.id) })),
|
|
})),
|
|
);
|
|
|
|
const active = computed(() => cats.value.find((g) => g.cat.id === hoverId.value) ?? null);
|
|
|
|
defineExpose({ open });
|
|
</script>
|
|
|
|
<template>
|
|
<div class="catmenu" :class="{ pinned: props.pinned }" @mouseleave="hoverId = null">
|
|
<ul class="lvl1">
|
|
<li
|
|
v-for="g in cats"
|
|
:key="g.cat.id"
|
|
:class="{ on: hoverId === g.cat.id }"
|
|
@mouseenter="hoverId = g.cat.id"
|
|
>
|
|
<NuxtLink :to="`/search?category=${g.cat.id}`" class="c1">{{ pick(g.cat.name, locale) }}</NuxtLink>
|
|
<span class="c2s">
|
|
<NuxtLink
|
|
v-for="cc in g.children.slice(0, 3)"
|
|
:key="cc.cat.id"
|
|
:to="`/search?category=${cc.cat.id}`"
|
|
class="c2"
|
|
>{{ pick(cc.cat.name, locale) }}</NuxtLink>
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
<div v-if="active" class="sub">
|
|
<div v-for="cc in active.children" :key="cc.cat.id" class="sub-row">
|
|
<NuxtLink :to="`/search?category=${cc.cat.id}`" class="sub-title">{{ pick(cc.cat.name, locale) }}</NuxtLink>
|
|
<div class="sub-items">
|
|
<NuxtLink
|
|
v-for="gc in cc.grandchildren"
|
|
:key="gc.id"
|
|
:to="`/search?category=${gc.id}`"
|
|
>{{ pick(gc.name, locale) }}</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.catmenu {
|
|
position: absolute;
|
|
top: 40px;
|
|
left: 0;
|
|
z-index: 700;
|
|
display: flex;
|
|
width: 960px;
|
|
}
|
|
/* pinned: in-flow left rail of the home hero row, flyout overlays the carousel */
|
|
.catmenu.pinned {
|
|
position: relative;
|
|
top: auto;
|
|
left: auto;
|
|
flex: none;
|
|
width: 240px;
|
|
height: 450px;
|
|
}
|
|
.lvl1 {
|
|
width: 240px;
|
|
margin: 0;
|
|
padding: 8px 0;
|
|
list-style: none;
|
|
background: rgba(30, 30, 30, 0.92);
|
|
min-height: 434px;
|
|
}
|
|
.lvl1 li {
|
|
padding: 10px 15px;
|
|
cursor: pointer;
|
|
}
|
|
.lvl1 li.on {
|
|
background: #fff;
|
|
}
|
|
.lvl1 li.on .c1,
|
|
.lvl1 li.on .c2 {
|
|
color: var(--mall-red);
|
|
}
|
|
.c1 {
|
|
display: block;
|
|
color: #fff;
|
|
font-size: 14px;
|
|
text-decoration: none;
|
|
}
|
|
.c2s {
|
|
display: block;
|
|
margin-top: 2px;
|
|
}
|
|
.c2 {
|
|
color: #bbb;
|
|
font-size: 12px;
|
|
margin-right: 8px;
|
|
text-decoration: none;
|
|
}
|
|
.c2:hover {
|
|
color: #fff;
|
|
}
|
|
.sub {
|
|
flex: 1;
|
|
background: #fff;
|
|
box-shadow: var(--mall-shadow);
|
|
padding: 20px 30px;
|
|
min-height: 434px;
|
|
}
|
|
.sub-row {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--mall-line);
|
|
padding: 12px 0;
|
|
}
|
|
.sub-row:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
.sub-title {
|
|
width: 120px;
|
|
font-weight: 600;
|
|
color: var(--mall-ink);
|
|
text-decoration: none;
|
|
}
|
|
.sub-items a {
|
|
display: inline-block;
|
|
margin: 0 18px 8px 0;
|
|
color: var(--mall-muted);
|
|
font-size: 13px;
|
|
text-decoration: none;
|
|
}
|
|
.sub-items a:hover {
|
|
color: var(--mall-red);
|
|
}
|
|
/* pinned overrides: 240x450 rail, flyout opens to the right over the carousel */
|
|
.catmenu.pinned .lvl1 {
|
|
box-sizing: border-box;
|
|
height: 450px;
|
|
min-height: 450px;
|
|
}
|
|
.catmenu.pinned .sub {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 240px;
|
|
box-sizing: border-box;
|
|
width: 700px;
|
|
height: 450px;
|
|
min-height: 450px;
|
|
overflow: auto;
|
|
}
|
|
</style>
|