wip(freight): migration 0017 + model/column-list groundwork for add-freight-templates
This commit is contained in:
@@ -19,11 +19,9 @@ async function refresh(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
[() => props.amountMinor, () => props.currency, currency],
|
||||
() => void refresh(),
|
||||
{ immediate: true },
|
||||
);
|
||||
watch([() => props.amountMinor, () => props.currency, currency], () => void refresh(), {
|
||||
immediate: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -9,7 +9,12 @@ const tone = computed<"green" | "blue" | "red" | "orange">(() => {
|
||||
});
|
||||
|
||||
const label = computed(() => {
|
||||
const root = props.kind === "order" ? "order.status" : props.kind === "shipment" ? "shipment.status" : "invoice.status";
|
||||
const root =
|
||||
props.kind === "order"
|
||||
? "order.status"
|
||||
: props.kind === "shipment"
|
||||
? "shipment.status"
|
||||
: "invoice.status";
|
||||
return `${root}.${props.status}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,11 +3,16 @@ defineProps<{ items: { label: string; to?: string }[] }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav aria-label="breadcrumb" class="mx-auto my-5 max-w-mall px-4 text-[13px] text-muted">
|
||||
<nav aria-label="breadcrumb" class="max-w-mall text-muted mx-auto my-5 px-4 text-[13px]">
|
||||
<template v-for="(item, i) in items" :key="i">
|
||||
<NuxtLink v-if="item.to" :to="item.to" class="no-underline transition-colors hover:text-primary">{{ item.label }}</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="item.to"
|
||||
:to="item.to"
|
||||
class="hover:text-primary no-underline transition-colors"
|
||||
>{{ item.label }}</NuxtLink
|
||||
>
|
||||
<span v-else class="text-text">{{ item.label }}</span>
|
||||
<span v-if="i < items.length - 1" class="mx-2 text-muted/60">/</span>
|
||||
<span v-if="i < items.length - 1" class="text-muted/60 mx-2">/</span>
|
||||
</template>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<{ images: { image: string; url?: string }[]; height?: number; interval?: number }>(), {
|
||||
height: 450,
|
||||
interval: 4000,
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{ images: { image: string; url?: string }[]; height?: number; interval?: number }>(),
|
||||
{
|
||||
height: 450,
|
||||
interval: 4000,
|
||||
},
|
||||
);
|
||||
|
||||
const index = ref(0);
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
@@ -34,7 +37,10 @@ onBeforeUnmount(() => {
|
||||
class="block h-full w-full bg-cover bg-center"
|
||||
:style="{ backgroundImage: `url(${img.image})` }"
|
||||
/>
|
||||
<div v-if="images.length > 1" class="absolute bottom-[14px] left-0 right-0 flex justify-center gap-2">
|
||||
<div
|
||||
v-if="images.length > 1"
|
||||
class="absolute right-0 bottom-[14px] left-0 flex justify-center gap-2"
|
||||
>
|
||||
<button
|
||||
v-for="(_, i) in images"
|
||||
:key="i"
|
||||
|
||||
@@ -3,7 +3,7 @@ withDefaults(defineProps<{ text?: string }>(), { text: "" });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="py-[60px] text-center text-muted/70">
|
||||
<div class="text-muted/70 py-[60px] text-center">
|
||||
<div class="mb-2.5 text-[40px] leading-none">∅</div>
|
||||
<p class="m-0">{{ text || $t("common.empty") }}</p>
|
||||
</div>
|
||||
|
||||
@@ -5,21 +5,30 @@ const emit = defineEmits<{ close: [] }>();
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="open" class="fixed inset-0 z-[900] flex items-center justify-center bg-black/40" @click.self="emit('close')">
|
||||
<div class="min-w-[420px] max-w-[720px] rounded-md bg-surface shadow-lg">
|
||||
<header v-if="title" class="flex items-center justify-between border-b border-border px-5 py-3.5 text-[15px]">
|
||||
<div
|
||||
v-if="open"
|
||||
class="fixed inset-0 z-[900] flex items-center justify-center bg-black/40"
|
||||
@click.self="emit('close')"
|
||||
>
|
||||
<div class="bg-surface max-w-[720px] min-w-[420px] rounded-md shadow-lg">
|
||||
<header
|
||||
v-if="title"
|
||||
class="border-border flex items-center justify-between border-b px-5 py-3.5 text-[15px]"
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="border-0 bg-transparent text-[20px] text-muted transition-colors hover:text-primary"
|
||||
class="text-muted hover:text-primary border-0 bg-transparent text-[20px] transition-colors"
|
||||
:aria-label="$t('common.cancel')"
|
||||
@click="emit('close')"
|
||||
>×</button>
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</header>
|
||||
<div class="p-5">
|
||||
<slot />
|
||||
</div>
|
||||
<footer v-if="$slots.footer" class="border-t border-border px-5 py-3 text-right">
|
||||
<footer v-if="$slots.footer" class="border-border border-t px-5 py-3 text-right">
|
||||
<slot name="footer" />
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -21,23 +21,33 @@ function go(p: number): void {
|
||||
<nav v-if="pages > 1" class="my-[30px] flex justify-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
class="h-[34px] min-w-[34px] rounded-sm border border-border bg-surface px-2 text-[13px] transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
||||
class="border-border bg-surface h-[34px] min-w-[34px] rounded-sm border px-2 text-[13px] transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
||||
:disabled="page <= 1"
|
||||
@click="go(page - 1)"
|
||||
>{{ $t("common.prev") }}</button>
|
||||
>
|
||||
{{ $t("common.prev") }}
|
||||
</button>
|
||||
<button
|
||||
v-for="p in window_"
|
||||
:key="p"
|
||||
type="button"
|
||||
class="h-[34px] min-w-[34px] rounded-sm border border-border px-2 text-[13px] transition-colors"
|
||||
:class="p === page ? 'border-primary bg-primary text-white' : 'bg-surface hover:border-primary hover:text-primary'"
|
||||
class="border-border h-[34px] min-w-[34px] rounded-sm border px-2 text-[13px] transition-colors"
|
||||
:class="
|
||||
p === page
|
||||
? 'border-primary bg-primary text-white'
|
||||
: 'bg-surface hover:border-primary hover:text-primary'
|
||||
"
|
||||
@click="go(p)"
|
||||
>{{ p }}</button>
|
||||
>
|
||||
{{ p }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="h-[34px] min-w-[34px] rounded-sm border border-border bg-surface px-2 text-[13px] transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
||||
class="border-border bg-surface h-[34px] min-w-[34px] rounded-sm border px-2 text-[13px] transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
||||
:disabled="page >= pages"
|
||||
@click="go(page + 1)"
|
||||
>{{ $t("common.next") }}</button>
|
||||
>
|
||||
{{ $t("common.next") }}
|
||||
</button>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
@@ -14,9 +14,9 @@ const sold = computed(() => props.product.sold_count);
|
||||
<template>
|
||||
<NuxtLink
|
||||
:to="`/goods/${product.slug}`"
|
||||
class="group block h-full no-underline text-inherit transition-shadow duration-200 hover:-translate-y-0.5 hover:shadow-lg"
|
||||
class="group block h-full text-inherit no-underline transition-shadow duration-200 hover:-translate-y-0.5 hover:shadow-lg"
|
||||
>
|
||||
<VCard :padded="false" class="h-full overflow-hidden border border-border bg-surface">
|
||||
<VCard :padded="false" class="border-border bg-surface h-full overflow-hidden border">
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<img
|
||||
:src="product.images[0] || '/mock/product-1.svg'"
|
||||
@@ -26,21 +26,27 @@ const sold = computed(() => props.product.sold_count);
|
||||
/>
|
||||
</div>
|
||||
<div class="px-[14px] pb-[14px]">
|
||||
<p class="m-0 h-[37px] overflow-hidden text-[13px] leading-[1.4] transition-colors group-hover:text-primary">
|
||||
<p
|
||||
class="group-hover:text-primary m-0 h-[37px] overflow-hidden text-[13px] leading-[1.4] transition-colors"
|
||||
>
|
||||
{{ pick(product.name, locale) }}
|
||||
</p>
|
||||
<p class="m-0 mb-2 mt-1 overflow-hidden text-ellipsis whitespace-nowrap text-[12px] text-muted/70">
|
||||
<p
|
||||
class="text-muted/70 m-0 mt-1 mb-2 overflow-hidden text-[12px] text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{{ pick(product.description, locale) }}
|
||||
</p>
|
||||
<p class="m-0">
|
||||
<span v-if="sku" class="mr-2 text-[16px] font-bold text-primary">
|
||||
<span v-if="sku" class="text-primary mr-2 text-[16px] font-bold">
|
||||
<PriceText :amount-minor="sku.price_minor" :currency="sku.currency" />
|
||||
</span>
|
||||
<s v-if="sku" class="text-[12px] text-muted/60">
|
||||
<s v-if="sku" class="text-muted/60 text-[12px]">
|
||||
<PriceText :amount-minor="sku.price_minor + 10000" :currency="sku.currency" />
|
||||
</s>
|
||||
</p>
|
||||
<p class="m-0 mt-1.5 text-[12px] text-muted/70">{{ $t("product.salesCount", { n: sold }) }}</p>
|
||||
<p class="text-muted/70 m-0 mt-1.5 text-[12px]">
|
||||
{{ $t("product.salesCount", { n: sold }) }}
|
||||
</p>
|
||||
</div>
|
||||
</VCard>
|
||||
</NuxtLink>
|
||||
|
||||
@@ -9,26 +9,30 @@ function set(v: number): void {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline-flex overflow-hidden rounded-md border border-border">
|
||||
<div class="border-border inline-flex overflow-hidden rounded-md border">
|
||||
<button
|
||||
type="button"
|
||||
class="h-8 w-[30px] border-0 bg-bg text-sm transition-colors hover:text-primary disabled:cursor-not-allowed disabled:opacity-40"
|
||||
class="bg-bg hover:text-primary h-8 w-[30px] border-0 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
||||
:disabled="modelValue <= 1"
|
||||
@click="set(modelValue - 1)"
|
||||
>−</button>
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<input
|
||||
:value="modelValue"
|
||||
type="number"
|
||||
:min="1"
|
||||
:max="max"
|
||||
class="h-8 w-12 [appearance:textfield] border-x border-border bg-surface text-center text-[13px] text-text outline-none focus:border-primary"
|
||||
class="border-border bg-surface text-text focus:border-primary h-8 w-12 [appearance:textfield] border-x text-center text-[13px] outline-none"
|
||||
@change="set(Number(($event.target as HTMLInputElement).value))"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="h-8 w-[30px] border-0 bg-bg text-sm transition-colors hover:text-primary disabled:cursor-not-allowed disabled:opacity-40"
|
||||
class="bg-bg hover:text-primary h-8 w-[30px] border-0 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40"
|
||||
:disabled="modelValue >= max"
|
||||
@click="set(modelValue + 1)"
|
||||
>+</button>
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,17 +3,18 @@ defineProps<{ steps: string[]; active: number }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ol class="mb-5 flex list-none border border-border bg-bg py-3.5">
|
||||
<ol class="border-border bg-bg mb-5 flex list-none border py-3.5">
|
||||
<li
|
||||
v-for="(s, i) in steps"
|
||||
:key="s"
|
||||
class="flex-1 text-center text-[13px] text-muted/70"
|
||||
:class="i === active ? 'font-semibold text-primary' : ''"
|
||||
class="text-muted/70 flex-1 text-center text-[13px]"
|
||||
:class="i === active ? 'text-primary font-semibold' : ''"
|
||||
>
|
||||
<span
|
||||
class="mr-2 inline-block h-[22px] w-[22px] rounded-full text-center text-[12px] leading-[22px] text-white"
|
||||
:class="i < active ? 'bg-success' : i === active ? 'bg-primary' : 'bg-[#ddd]'"
|
||||
>{{ i + 1 }}</span>
|
||||
>{{ i + 1 }}</span
|
||||
>
|
||||
<span>{{ s }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
@@ -5,15 +5,21 @@ const emit = defineEmits<{ "update:modelValue": [key: string] }>();
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex border-b border-border">
|
||||
<div class="border-border flex border-b">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
type="button"
|
||||
class="border-0 border-b-2 border-transparent bg-transparent px-6 py-3 text-sm text-muted transition-colors"
|
||||
:class="tab.key === modelValue ? 'border-primary font-semibold text-primary' : 'hover:text-primary'"
|
||||
class="text-muted border-0 border-b-2 border-transparent bg-transparent px-6 py-3 text-sm transition-colors"
|
||||
:class="
|
||||
tab.key === modelValue
|
||||
? 'border-primary text-primary font-semibold'
|
||||
: 'hover:text-primary'
|
||||
"
|
||||
@click="emit('update:modelValue', tab.key)"
|
||||
>{{ tab.label }}</button>
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="py-5">
|
||||
<slot :active="modelValue" />
|
||||
|
||||
Reference in New Issue
Block a user