feat(ui): adopt Tailwind v4 design system and archive change
- Add tailwindcss v4 + @tailwindcss/vite to mall, shop-admin, admin - Add @vmall/shared/theme.css tokens with html[data-accent] presets - Add @vmall/ui kit (VBtn/VBadge/VField/VInput/VCard/VPanel/VTable/VPage, VAccentSwatch, useAccent) as a Nuxt module - Convert all three apps to kit + utilities; delete ui.css/mall.css and every <style scoped>; consoles get accent presets, mall locked to red - Fix VCard boolean prop default (padding) and PDP/store stale useAsyncData keys on param navigation - Archive adopt-tailwind-design-system; new frontend-ui capability spec
This commit is contained in:
@@ -38,181 +38,82 @@ const showMenu = computed(() => menuOpen.value && !isHome.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="hdr">
|
||||
<div class="w1200 hdr-top">
|
||||
<NuxtLink to="/" class="logo">VMall</NuxtLink>
|
||||
<div class="search-box">
|
||||
<input
|
||||
v-model="keywords"
|
||||
class="search-input"
|
||||
type="text"
|
||||
:placeholder="t('shell.searchPlaceholder')"
|
||||
@keyup.enter="search"
|
||||
/>
|
||||
<button class="search-btn" 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 class="cart-links">
|
||||
<NuxtLink to="/user" class="cart-link">{{ t("shell.myMall") }}</NuxtLink>
|
||||
<NuxtLink to="/cart" class="cart-link">
|
||||
<header class="bg-surface">
|
||||
<div class="mx-auto flex max-w-mall items-center gap-10 px-4 py-6">
|
||||
<NuxtLink to="/" class="text-[34px] font-extrabold leading-none tracking-[1px] text-primary 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="h-[38px] min-w-0 flex-1 border border-border border-r-0 bg-surface px-3 text-[12px] text-text outline-none focus:border-primary"
|
||||
type="text"
|
||||
:placeholder="t('shell.searchPlaceholder')"
|
||||
@keyup.enter="search"
|
||||
/>
|
||||
<button
|
||||
class="flex h-[38px] w-[38px] items-center justify-center border border-border bg-surface text-muted transition-colors hover:bg-primary 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="ml-7 flex h-[38px] items-center gap-5 border border-border px-5 text-[12px]">
|
||||
<NuxtLink to="/user" class="relative text-muted no-underline transition-colors hover:text-primary">{{ t("shell.myMall") }}</NuxtLink>
|
||||
<NuxtLink to="/cart" class="relative text-muted no-underline transition-colors hover:text-primary">
|
||||
{{ t("shell.myCart") }}
|
||||
<span v-if="cart.count > 0" class="dot">{{ cart.count }}</span>
|
||||
<span
|
||||
v-if="cart.count > 0"
|
||||
class="absolute -right-3 -top-3 rounded-sm bg-primary px-1 text-[11px] leading-4 text-white"
|
||||
>{{ cart.count }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prefs">
|
||||
<select :value="currency" :aria-label="t('common.currency')" @change="currency = ($event.target as HTMLSelectElement).value">
|
||||
|
||||
<div class="flex gap-2">
|
||||
<select
|
||||
:value="currency"
|
||||
:aria-label="t('common.currency')"
|
||||
class="h-[30px] min-w-[76px] rounded-md border border-border bg-surface px-2 text-[12px] text-text focus:border-primary focus:outline-2 focus:outline-primary/30"
|
||||
@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')" @change="setLocale(($event.target as HTMLSelectElement).value as 'en' | 'zh')">
|
||||
<select
|
||||
:value="locale"
|
||||
:aria-label="t('common.language')"
|
||||
class="h-[30px] min-w-[76px] rounded-md border border-border bg-surface px-2 text-[12px] text-text focus:border-primary focus:outline-2 focus:outline-primary/30"
|
||||
@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="nav">
|
||||
<div class="w1200 nav-in">
|
||||
|
||||
<nav class="h-10 bg-[#2d2d2d] text-sm leading-10 text-white">
|
||||
<div class="mx-auto flex max-w-mall px-4">
|
||||
<div
|
||||
class="nav-cats"
|
||||
class="relative w-[240px] cursor-pointer bg-primary pl-[15px] font-medium"
|
||||
@mouseenter="menuOpen = true"
|
||||
@mouseleave="menuOpen = false"
|
||||
>
|
||||
{{ t("shell.allCategories") }}
|
||||
<ShellCategoryMenu v-show="showMenu" />
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><NuxtLink to="/">{{ t("shell.nav.home") }}</NuxtLink></li>
|
||||
<li><NuxtLink to="/stores">{{ t("shell.nav.stores") }}</NuxtLink></li>
|
||||
<li><NuxtLink to="/seckill">{{ t("shell.nav.seckill") }}</NuxtLink></li>
|
||||
<li><NuxtLink to="/collective">{{ t("shell.nav.collective") }}</NuxtLink></li>
|
||||
<li><NuxtLink to="/integral">{{ t("shell.nav.integral") }}</NuxtLink></li>
|
||||
<li><NuxtLink to="/user">{{ t("shell.nav.help") }}</NuxtLink></li>
|
||||
<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>
|
||||
|
||||
<style scoped>
|
||||
.hdr {
|
||||
background: #fff;
|
||||
}
|
||||
.hdr-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
padding: 24px 0;
|
||||
}
|
||||
.logo {
|
||||
font-size: 34px;
|
||||
font-weight: 800;
|
||||
color: var(--mall-red);
|
||||
text-decoration: none;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.search-input {
|
||||
width: 434px;
|
||||
height: 38px;
|
||||
padding: 10px;
|
||||
outline: 0;
|
||||
border: 1px solid var(--mall-line);
|
||||
border-right: none;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
}
|
||||
.search-btn {
|
||||
border: 1px solid var(--mall-line);
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.search-btn:hover {
|
||||
color: #fff;
|
||||
background: var(--mall-nav);
|
||||
}
|
||||
.cart-links {
|
||||
display: flex;
|
||||
height: 38px;
|
||||
border: 1px solid var(--mall-line);
|
||||
margin-left: 30px;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
line-height: 38px;
|
||||
padding: 0 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
.cart-link {
|
||||
position: relative;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.cart-link:hover {
|
||||
color: var(--mall-red);
|
||||
}
|
||||
.dot {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: -14px;
|
||||
line-height: 16px;
|
||||
background: var(--mall-red);
|
||||
padding: 0 4px;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.prefs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.prefs select {
|
||||
height: 30px;
|
||||
min-width: 76px;
|
||||
padding: 0 6px;
|
||||
border: 1px solid var(--mall-line);
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
.nav {
|
||||
height: 40px;
|
||||
background: var(--mall-nav);
|
||||
font-size: 14px;
|
||||
line-height: 40px;
|
||||
color: #fff;
|
||||
}
|
||||
.nav-in {
|
||||
display: flex;
|
||||
}
|
||||
.nav-cats {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 240px;
|
||||
background: var(--mall-red);
|
||||
box-sizing: border-box;
|
||||
padding-left: 15px;
|
||||
}
|
||||
.nav-links {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 0 0 20px;
|
||||
}
|
||||
.nav-links a {
|
||||
line-height: 40px;
|
||||
padding: 0 30px;
|
||||
color: #fff;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
.nav-links a:hover {
|
||||
background: #000;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user