Files
vmall/apps/mall/pages/index.vue
T
james ee2f421709 feat(mall): pin home category menu beside hero carousel
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).
2026-09-17 14:17:46 +00:00

155 lines
3.3 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.
<script setup lang="ts">
import { t as pick } from "@vmall/shared";
import { MOCK_BANNERS, MOCK_PROMOS, MOCK_QUICK_LINKS, homeFloors } from "~/mock/data";
const { locale, t } = useI18n();
const floors = computed(() => homeFloors().filter((f) => f.products.length > 0));
</script>
<template>
<div>
<div class="w1200 hero">
<ShellCategoryMenu pinned />
<div class="hero-slider">
<UiCarousel :images="MOCK_BANNERS" :height="450" />
</div>
</div>
<div class="w1200 strip">
<ul class="quick">
<li v-for="q in MOCK_QUICK_LINKS" :key="q.url + pick(q.label, 'en')">
<NuxtLink :to="q.url">
<svg viewBox="0 0 24 24" width="26" height="26" fill="currentColor"><path :d="q.glyph" /></svg>
<span>{{ pick(q.label, locale) }}</span>
</NuxtLink>
</li>
</ul>
<div class="promos">
<NuxtLink v-for="p in MOCK_PROMOS" :key="p.image" :to="p.url">
<img :src="p.image" :alt="t('home.hotPromo')" loading="lazy" />
</NuxtLink>
</div>
</div>
<div class="section-bg">
<section v-for="floor in floors" :key="floor.categoryId" class="w1200 floor">
<header class="floor-head">
<h2>{{ pick(floor.name, locale) }}</h2>
<NuxtLink :to="`/search?category=${floor.categoryId}`" class="more">{{ t("home.viewMore") }} </NuxtLink>
</header>
<div class="floor-body">
<NuxtLink :to="floor.advUrl" class="floor-adv">
<img :src="floor.advImage" :alt="pick(floor.name, locale)" loading="lazy" />
</NuxtLink>
<div class="floor-grid">
<UiProductCard v-for="p in floor.products" :key="p.id" :product="p" />
</div>
</div>
</section>
</div>
</div>
</template>
<style scoped>
.hero {
display: flex;
height: 450px;
margin-top: 0;
background: #fff;
}
.hero-slider {
flex: 1;
min-width: 0;
}
.strip {
display: flex;
gap: 20px;
margin-top: 20px;
margin-bottom: 20px;
}
.quick {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--mall-line);
border: 1px solid var(--mall-line);
list-style: none;
margin: 0;
padding: 0;
width: 240px;
}
.quick li {
background: #fff;
text-align: center;
}
.quick a {
display: block;
padding: 14px 0;
color: var(--mall-muted);
text-decoration: none;
font-size: 12px;
}
.quick a:hover {
color: var(--mall-red);
}
.quick svg {
display: block;
margin: 0 auto 6px;
}
.promos {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.promos img {
width: 100%;
height: 160px;
object-fit: cover;
display: block;
}
.floor {
margin-bottom: 30px;
}
.floor-head {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 2px solid var(--mall-red);
margin-bottom: 0;
}
.floor-head h2 {
font-size: 18px;
margin: 0;
padding: 10px 0;
}
.more {
font-size: 12px;
color: var(--mall-muted);
text-decoration: none;
}
.more:hover {
color: var(--mall-red);
}
.floor-body {
display: flex;
gap: 0;
background: #fff;
}
.floor-adv img {
width: 234px;
height: 614px;
display: block;
object-fit: cover;
}
.floor-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1px;
background: var(--mall-line);
}
.floor-grid :deep(.card) {
border: 0;
}
</style>