wip(freight): migration 0017 + model/column-list groundwork for add-freight-templates

This commit is contained in:
Chengdong Zhang
2026-09-23 17:39:45 +08:00
parent a83e857bdf
commit 5b426486ac
105 changed files with 6193 additions and 1566 deletions
+15 -7
View File
@@ -66,7 +66,8 @@ defineExpose({ open });
:to="`/search?category=${g.cat.id}`"
class="block text-sm no-underline"
:class="hoverId === g.cat.id ? 'text-primary' : 'text-white'"
>{{ pick(g.cat.name, locale) }}</NuxtLink>
>{{ pick(g.cat.name, locale) }}</NuxtLink
>
<span class="mt-0.5 block">
<NuxtLink
v-for="cc in g.children.slice(0, 3)"
@@ -74,24 +75,31 @@ defineExpose({ open });
:to="`/search?category=${cc.cat.id}`"
class="mr-2 text-[12px] no-underline"
:class="hoverId === g.cat.id ? 'text-primary' : 'text-[#bbb] hover:text-white'"
>{{ pick(cc.cat.name, locale) }}</NuxtLink>
>{{ pick(cc.cat.name, locale) }}</NuxtLink
>
</span>
</li>
</ul>
<div v-if="active" :class="subClass">
<div v-for="cc in active.children" :key="cc.cat.id" class="flex border-b border-border py-3 last:border-b-0">
<div
v-for="cc in active.children"
:key="cc.cat.id"
class="border-border flex border-b py-3 last:border-b-0"
>
<NuxtLink
:to="`/search?category=${cc.cat.id}`"
class="w-[120px] shrink-0 text-[14px] font-semibold text-text no-underline"
>{{ pick(cc.cat.name, locale) }}</NuxtLink>
class="text-text w-[120px] shrink-0 text-[14px] font-semibold no-underline"
>{{ pick(cc.cat.name, locale) }}</NuxtLink
>
<div>
<NuxtLink
v-for="gc in cc.grandchildren"
:key="gc.id"
:to="`/search?category=${gc.id}`"
class="mb-2 mr-[18px] inline-block text-[13px] text-muted no-underline transition-colors hover:text-primary"
>{{ pick(gc.name, locale) }}</NuxtLink>
class="text-muted hover:text-primary mr-[18px] mb-2 inline-block text-[13px] no-underline transition-colors"
>{{ pick(gc.name, locale) }}</NuxtLink
>
</div>
</div>
</div>
+9 -5
View File
@@ -19,24 +19,28 @@ onBeforeUnmount(() => window.removeEventListener("scroll", onScroll));
<template>
<footer class="bg-surface pt-2 text-center">
<div class="mx-auto max-w-mall px-4">
<ul class="mx-auto mt-10 flex w-full max-w-[720px] list-none justify-between border-t border-border pt-10 text-sm text-text">
<div class="max-w-mall mx-auto px-4">
<ul
class="border-border text-text mx-auto mt-10 flex w-full max-w-[720px] list-none justify-between border-t pt-10 text-sm"
>
<li>{{ t("shell.footer.v1") }}</li>
<li>{{ t("shell.footer.v2") }}</li>
<li>{{ t("shell.footer.v3") }}</li>
<li>{{ t("shell.footer.v4") }}</li>
</ul>
<div class="pb-5 pt-[30px] text-[12px] leading-7 text-muted">
<div class="text-muted pt-[30px] pb-5 text-[12px] leading-7">
<p class="m-0">{{ t("shell.footer.copyright") }}</p>
<p class="m-0">{{ t("shell.footer.icp") }}</p>
</div>
</div>
<button
v-show="visible"
class="fixed bottom-[60px] right-10 z-[800] h-11 w-11 rounded-md border border-border bg-surface text-lg text-muted transition-colors hover:border-primary hover:bg-primary hover:text-white"
class="border-border bg-surface text-muted hover:border-primary hover:bg-primary fixed right-10 bottom-[60px] z-[800] h-11 w-11 rounded-md border text-lg transition-colors hover:text-white"
type="button"
:aria-label="t('shell.backToTop')"
@click="toTop"
>↑</button>
>
↑
</button>
</footer>
</template>
+70 -20
View File
@@ -39,38 +39,52 @@ const showMenu = computed(() => menuOpen.value && !isHome.value);
<template>
<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="max-w-mall mx-auto flex items-center gap-10 px-4 py-6">
<NuxtLink
to="/"
class="text-primary text-[34px] leading-none font-extrabold tracking-[1px] 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"
class="border-border bg-surface text-text focus:border-primary h-[38px] min-w-0 flex-1 border border-r-0 px-3 text-[12px] outline-none"
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"
class="border-border bg-surface text-muted hover:bg-primary flex h-[38px] w-[38px] items-center justify-center border transition-colors 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" />
<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">
<div class="border-border ml-7 flex h-[38px] items-center gap-5 border px-5 text-[12px]">
<NuxtLink
to="/user"
class="text-muted hover:text-primary relative no-underline transition-colors"
>{{ t("shell.myMall") }}</NuxtLink
>
<NuxtLink
to="/cart"
class="text-muted hover:text-primary relative no-underline transition-colors"
>
{{ t("shell.myCart") }}
<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>
class="bg-primary absolute -top-3 -right-3 rounded-sm px-1 text-[11px] leading-4 text-white"
>{{ cart.count }}</span
>
</NuxtLink>
</div>
</div>
@@ -79,7 +93,7 @@ const showMenu = computed(() => menuOpen.value && !isHome.value);
<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"
class="border-border bg-surface text-text focus:border-primary focus:outline-primary/30 h-[30px] min-w-[76px] rounded-md border px-2 text-[12px] focus:outline-2"
@change="currency = ($event.target as HTMLSelectElement).value"
>
<option v-for="c in currencies" :key="c" :value="c">{{ c }}</option>
@@ -87,7 +101,7 @@ const showMenu = computed(() => menuOpen.value && !isHome.value);
<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"
class="border-border bg-surface text-text focus:border-primary focus:outline-primary/30 h-[30px] min-w-[76px] rounded-md border px-2 text-[12px] focus:outline-2"
@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>
@@ -96,9 +110,9 @@ const showMenu = computed(() => menuOpen.value && !isHome.value);
</div>
<nav class="h-10 bg-[#2d2d2d] text-sm leading-10 text-white">
<div class="mx-auto flex max-w-mall px-4">
<div class="max-w-mall mx-auto flex px-4">
<div
class="relative w-[240px] cursor-pointer bg-primary pl-[15px] font-medium"
class="bg-primary relative w-[240px] cursor-pointer pl-[15px] font-medium"
@mouseenter="menuOpen = true"
@mouseleave="menuOpen = false"
>
@@ -106,12 +120,48 @@ const showMenu = computed(() => menuOpen.value && !isHome.value);
<ShellCategoryMenu v-show="showMenu" />
</div>
<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>
<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>
+66 -26
View File
@@ -4,18 +4,26 @@ const { t } = useI18n();
</script>
<template>
<div class="h-[30px] border-b border-border bg-[#f9f9f9] text-[12px] leading-[30px] text-muted">
<div class="mx-auto flex h-full max-w-mall items-center justify-between px-4">
<div class="border-border text-muted h-[30px] border-b bg-[#f9f9f9] text-[12px] leading-[30px]">
<div class="max-w-mall mx-auto flex h-full items-center justify-between px-4">
<ul class="flex items-center">
<li><NuxtLink to="/" class="px-1 hover:text-primary">{{ t("shell.officialSite") }}</NuxtLink></li>
<li class="px-1 text-border">|</li>
<li><NuxtLink to="/" class="px-1 hover:text-primary">{{ t("shell.mallHome") }}</NuxtLink></li>
<li class="px-1 text-border">|</li>
<li><NuxtLink to="/" class="px-1 hover:text-primary">{{ t("shell.contact") }}</NuxtLink></li>
<li class="px-1 text-border">|</li>
<li><NuxtLink to="/" class="px-1 hover:text-primary">{{ t("shell.about") }}</NuxtLink></li>
<li class="px-1 text-border">|</li>
<li class="px-1 text-primary">{{ t("shell.city") }}</li>
<li>
<NuxtLink to="/" class="hover:text-primary px-1">{{ t("shell.officialSite") }}</NuxtLink>
</li>
<li class="text-border px-1">|</li>
<li>
<NuxtLink to="/" class="hover:text-primary px-1">{{ t("shell.mallHome") }}</NuxtLink>
</li>
<li class="text-border px-1">|</li>
<li>
<NuxtLink to="/" class="hover:text-primary px-1">{{ t("shell.contact") }}</NuxtLink>
</li>
<li class="text-border px-1">|</li>
<li>
<NuxtLink to="/" class="hover:text-primary px-1">{{ t("shell.about") }}</NuxtLink>
</li>
<li class="text-border px-1">|</li>
<li class="text-primary px-1">{{ t("shell.city") }}</li>
</ul>
<ul class="flex items-center">
<!-- The token lives in localStorage, so the server cannot know whether
@@ -26,28 +34,60 @@ const { t } = useI18n();
<template v-if="session.isLoggedIn">
<li class="px-1">
{{ t("shell.welcome") }}
<NuxtLink to="/user" class="text-primary hover:text-primary-hover">{{ session.user?.display_name }}</NuxtLink>
<NuxtLink to="/user" class="text-primary hover:text-primary-hover">{{
session.user?.display_name
}}</NuxtLink>
</li>
<li class="text-border px-1">|</li>
<li>
<NuxtLink to="/user" class="hover:text-primary px-1">{{
t("shell.userCenter")
}}</NuxtLink>
</li>
<li class="text-border px-1">|</li>
<li class="hover:text-primary cursor-pointer px-1" @click="session.logout()">
{{ t("shell.logout") }}
</li>
<li class="px-1 text-border">|</li>
<li><NuxtLink to="/user" class="px-1 hover:text-primary">{{ t("shell.userCenter") }}</NuxtLink></li>
<li class="px-1 text-border">|</li>
<li class="cursor-pointer px-1 hover:text-primary" @click="session.logout()">{{ t("shell.logout") }}</li>
</template>
<template v-else>
<li><NuxtLink to="/login" class="px-1 text-primary hover:text-primary-hover">{{ t("shell.login") }}</NuxtLink></li>
<li><NuxtLink to="/register" class="px-1 hover:text-primary">{{ t("shell.register") }}</NuxtLink></li>
<li>
<NuxtLink to="/login" class="text-primary hover:text-primary-hover px-1">{{
t("shell.login")
}}</NuxtLink>
</li>
<li>
<NuxtLink to="/register" class="hover:text-primary px-1">{{
t("shell.register")
}}</NuxtLink>
</li>
</template>
<template #fallback>
<li><NuxtLink to="/login" class="px-1 text-primary hover:text-primary-hover">{{ t("shell.login") }}</NuxtLink></li>
<li><NuxtLink to="/register" class="px-1 hover:text-primary">{{ t("shell.register") }}</NuxtLink></li>
<li>
<NuxtLink to="/login" class="text-primary hover:text-primary-hover px-1">{{
t("shell.login")
}}</NuxtLink>
</li>
<li>
<NuxtLink to="/register" class="hover:text-primary px-1">{{
t("shell.register")
}}</NuxtLink>
</li>
</template>
</ClientOnly>
<li class="px-1 text-border">|</li>
<li><NuxtLink to="/stores" class="px-1 text-primary hover:text-primary-hover">{{ t("shell.sellerJoin") }}</NuxtLink></li>
<li class="px-1 text-border">|</li>
<li><NuxtLink to="/" class="px-1 hover:text-primary">{{ t("shell.mobile") }}</NuxtLink></li>
<li class="px-1 text-border">|</li>
<li><NuxtLink to="/" class="px-1 hover:text-primary">{{ t("shell.appDownload") }}</NuxtLink></li>
<li class="text-border px-1">|</li>
<li>
<NuxtLink to="/stores" class="text-primary hover:text-primary-hover px-1">{{
t("shell.sellerJoin")
}}</NuxtLink>
</li>
<li class="text-border px-1">|</li>
<li>
<NuxtLink to="/" class="hover:text-primary px-1">{{ t("shell.mobile") }}</NuxtLink>
</li>
<li class="text-border px-1">|</li>
<li>
<NuxtLink to="/" class="hover:text-primary px-1">{{ t("shell.appDownload") }}</NuxtLink>
</li>
</ul>
</div>
</div>