- Mock adapter implementing @vmall/shared ApiClient (localStorage-persisted cart/orders/invoices), runtime switch via mockApi flag (default on) - Fixed bilingual mock catalog: 3-level categories, 24 products w/ SKUs, stores, brands, banners, floors, seckill/collective/integral, comments, coupons, addresses, seeded orders/shipments/invoices - B2B2C mall shell: top bar, logo/search/cart header, dark nav + category mega-menu, value-prop footer, back-to-top; 1200px grid, #ca151e theme - UI primitives replacing element-plus: carousel, pagination, breadcrumb, qty stepper, rating stars, modal, tabs, step bar, product card - Pages: home floors, search (filters/sort/paging), goods detail (SKU picker, store rail, review tabs), cart -> checkout -> pay -> success, auth pages, user center (dashboard/orders/addresses/favorites/coupons/ invoices), stores, seckill, collective, integral - i18n split into per-domain locale modules (en+zh) - OpenSpec change mall-pc-storefront-replica archived; all specs green
135 lines
2.8 KiB
Vue
135 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { topCategories, childCategories } from "~/mock/data";
|
|
import { t as pick } from "@vmall/shared";
|
|
|
|
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" @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;
|
|
}
|
|
.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);
|
|
}
|
|
</style>
|