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).
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
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);
|
||||
@@ -19,7 +21,7 @@ defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="catmenu" @mouseleave="hoverId = null">
|
||||
<div class="catmenu" :class="{ pinned: props.pinned }" @mouseleave="hoverId = null">
|
||||
<ul class="lvl1">
|
||||
<li
|
||||
v-for="g in cats"
|
||||
@@ -62,6 +64,15 @@ defineExpose({ open });
|
||||
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;
|
||||
@@ -131,4 +142,20 @@ defineExpose({ open });
|
||||
.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>
|
||||
|
||||
@@ -32,19 +32,14 @@ onMounted(async () => {
|
||||
|
||||
const menuOpen = ref(false);
|
||||
const isHome = computed(() => route.path === "/");
|
||||
const collapsed = ref(false);
|
||||
onMounted(() => {
|
||||
const onScroll = (): void => {
|
||||
collapsed.value = window.scrollY > 200;
|
||||
};
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
});
|
||||
const showMenu = computed(() => menuOpen.value || (isHome.value && !collapsed.value));
|
||||
// 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="hdr">
|
||||
<div v-show="!collapsed" class="w1200 hdr-top">
|
||||
<div class="w1200 hdr-top">
|
||||
<NuxtLink to="/" class="logo">VMall</NuxtLink>
|
||||
<div class="search-box">
|
||||
<input
|
||||
|
||||
@@ -9,7 +9,10 @@ const floors = computed(() => homeFloors().filter((f) => f.products.length > 0))
|
||||
<template>
|
||||
<div>
|
||||
<div class="w1200 hero">
|
||||
<UiCarousel :images="MOCK_BANNERS" :height="450" />
|
||||
<ShellCategoryMenu pinned />
|
||||
<div class="hero-slider">
|
||||
<UiCarousel :images="MOCK_BANNERS" :height="450" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w1200 strip">
|
||||
@@ -49,7 +52,14 @@ const floors = computed(() => homeFloors().filter((f) => f.products.length > 0))
|
||||
|
||||
<style scoped>
|
||||
.hero {
|
||||
display: flex;
|
||||
height: 450px;
|
||||
margin-top: 0;
|
||||
background: #fff;
|
||||
}
|
||||
.hero-slider {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.strip {
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-17
|
||||
@@ -0,0 +1,29 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
The mall home page shows the category menu as a hover dropdown that auto-hides once the shopper scrolls 200px, and the hero carousel spans the full 1200px grid. Referencing tigshop's B2B2C storefront, the category tree should be a permanent left rail beside the banner so shoppers always see the catalog structure without hovering or scrolling back up. Additionally, the header's scroll-collapse hides the logo/search/cart bar on scroll, which hurts usability.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Home page hero becomes a flex row: a pinned 240px category sidebar (reusing `ShellCategoryMenu`) left of the carousel; both are 450px tall and in-flow, not overlaid.
|
||||
- Carousel narrows from 1200px to ~950px; banner images (1200×450) are center-cropped left/right at fixed 450px height.
|
||||
- Hovering a top-level category in the pinned sidebar expands the white mega-menu panel to the right, overlaying the carousel (same content as today's flyout).
|
||||
- Non-home pages keep the hover dropdown under the nav "全部商品分类" entry, unchanged.
|
||||
- **BREAKING** (visual): the header no longer auto-hides on scroll — the `collapsed`/`scrollY > 200` logic is removed entirely, so the logo/search/cart bar and category menu never auto-hide.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
(none)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `frontend-mall`: the "Mock PC home page" requirement gains a pinned category sidebar beside the cropped carousel; the "B2B2C mall-style PC storefront shell" requirement changes the category mega-menu to hover-only on non-home pages and removes scroll-based header auto-hide.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- No changes to mock category data, banner assets, or the search/category filtering logic.
|
||||
- No changes to apps/shop-admin, apps/admin, the shared package, or the backend.
|
||||
- No right-side user rail, no extra promo tiles in the hero (options B/C/D from the visual proposal were rejected; see `docs/visual-proposal/home-hero-options.html`).
|
||||
@@ -0,0 +1,33 @@
|
||||
# Spec Delta
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Mock PC home page
|
||||
The mall home page SHALL render a hero row composed of a pinned 240px category sidebar on the left and a hero carousel filling the remainder of the 1200px grid, both 450px tall and occupying layout space (not overlaid). The sidebar SHALL list all top-level categories with up to three child links each; hovering a top-level category SHALL expand the mega-menu panel to the right over the carousel. Banner images SHALL render at fixed 450px height, center-cropped horizontally to the narrower carousel width. Below the hero, the page SHALL render a six-item quick-link strip with promotion tiles and bilingual product floors with a left advert and product-card grid.
|
||||
|
||||
#### Scenario: shopper lands on home
|
||||
- **WHEN** `/` loads in mock mode
|
||||
- **THEN** the category sidebar is visible to the left of the carousel without any hover or click, the carousel renders center-cropped banners at 450px height, and the quick links, promotions and every non-empty product floor render fixed mock content
|
||||
|
||||
#### Scenario: sidebar stays while scrolling
|
||||
- **WHEN** a shopper scrolls the home page beyond 200px
|
||||
- **THEN** the category sidebar remains rendered in the hero row and does not auto-hide
|
||||
|
||||
#### Scenario: expand a category
|
||||
- **WHEN** a shopper hovers a top-level category in the pinned sidebar
|
||||
- **THEN** the mega-menu panel expands to the right, overlaying the carousel with that category's child and grandchild links
|
||||
|
||||
### Requirement: B2B2C mall-style PC storefront shell
|
||||
The mall SHALL render a buyer-facing desktop shell modeled on a classic B2B2C PC mall: a 30px utility bar, logo/search/cart header, dark primary navigation with a hover category mega-menu, a 1200px content grid, and a value-proposition footer. The header SHALL remain fully visible while scrolling; no part of the shell SHALL auto-hide based on scroll position. The category mega-menu SHALL appear as a hover dropdown under the navigation "All Categories" entry on every page except the home page, where it is instead pinned in the hero row. The visual language SHALL use #ca151e for brand/price/active states, #f5f5f5 section backgrounds, gray hairline borders, compact controls, and product-card hover lift/shadow. The implementation SHALL use Nuxt-native semantic components and SHALL NOT depend on Element Plus.
|
||||
|
||||
#### Scenario: shopper opens any mall page
|
||||
- **WHEN** a shopper navigates to a buyer-facing route
|
||||
- **THEN** the shared desktop shell wraps the route content and its nav/search/cart controls are usable
|
||||
|
||||
#### Scenario: header persists while scrolling
|
||||
- **WHEN** a shopper scrolls any mall page
|
||||
- **THEN** the logo/search/cart header bar remains visible and is never collapsed or hidden by scroll position
|
||||
|
||||
#### Scenario: hover categories on a non-home page
|
||||
- **WHEN** a shopper hovers "All Categories" in the navigation on a page other than `/`
|
||||
- **THEN** the category mega-menu dropdown appears below the navigation and hides again on mouse leave
|
||||
@@ -0,0 +1,20 @@
|
||||
# Tasks
|
||||
|
||||
## 1. CategoryMenu pinned mode
|
||||
|
||||
- [x] 1.1 Add a `pinned` boolean prop to `apps/mall/components/shell/CategoryMenu.vue`; when `pinned` is true, render `.catmenu` in-flow (`position: relative; top: auto; width: 240px`) instead of the absolute 960px dropdown, and keep the default (dropdown) rendering unchanged for the header use
|
||||
- [x] 1.2 In pinned mode, keep the `.lvl1` sidebar at 240px wide and 450px tall, and reposition the `.sub` flyout as `position: absolute; left: 240px; top: 0; width: ~700px; height: 450px` so it overlays the carousel area to the right; keep existing hover state, links, and i18n untouched
|
||||
|
||||
## 2. Home hero row
|
||||
|
||||
- [x] 2.1 In `apps/mall/pages/index.vue`, change the `.hero` block to a flex row: `<ShellCategoryMenu pinned />` followed by `UiCarousel`; fix the row height at 450px so the sidebar and carousel align
|
||||
- [x] 2.2 Ensure banner images center-crop horizontally at the new ~950px carousel width (fixed 450px height, `object-fit: cover` — verify `UiCarousel` already applies this, adjust if not); do not modify the mock SVG assets
|
||||
|
||||
## 3. Header cleanup
|
||||
|
||||
- [x] 3.1 In `apps/mall/components/shell/SiteHeader.vue`, remove the `collapsed` ref, the scroll listener, and the `v-show="!collapsed"` on `.hdr-top`; reduce `showMenu` to `menuOpen && !isHome` so the header mega-menu is hover-only and suppressed on the home page, where the category menu is pinned in the hero row instead (per `specs/frontend-mall/spec.md`; decision confirmed during apply)
|
||||
|
||||
## 4. Verification
|
||||
|
||||
- [x] 4.1 Run `pnpm --filter @vmall/mall build` and confirm it passes
|
||||
- [x] 4.2 Start the mall dev server and verify in a real browser: home shows the pinned sidebar left of the cropped carousel with no hover; scrolling past 200px keeps both the header and sidebar visible; hovering a top-level category expands the panel over the carousel; a non-home page (e.g. `/search`) shows the hover dropdown and no pinned sidebar
|
||||
Reference in New Issue
Block a user