wip(freight): migration 0017 + model/column-list groundwork for add-freight-templates
This commit is contained in:
+128
-25
@@ -59,56 +59,159 @@ const counts = computed(() => ({
|
||||
}));
|
||||
|
||||
const statusLinks = computed(() => [
|
||||
{ key: "pendingPayment", label: t("user.pendingPayment"), count: counts.value.pendingPayment, to: "/user/orders?status=pending_payment" },
|
||||
{ key: "pendingShipment", label: t("user.pendingShipment"), count: counts.value.pendingShipment, to: "/user/orders?status=paid" },
|
||||
{ key: "pendingReceipt", label: t("user.pendingReceipt"), count: counts.value.pendingReceipt, to: "/user/orders?status=shipped" },
|
||||
{ key: "completed", label: t("user.completed"), count: counts.value.completed, to: "/user/orders?status=completed" },
|
||||
{ key: "afterSale", label: t("user.afterSale"), count: counts.value.afterSale, to: "/user/orders?status=after-sale" },
|
||||
{
|
||||
key: "pendingPayment",
|
||||
label: t("user.pendingPayment"),
|
||||
count: counts.value.pendingPayment,
|
||||
to: "/user/orders?status=pending_payment",
|
||||
},
|
||||
{
|
||||
key: "pendingShipment",
|
||||
label: t("user.pendingShipment"),
|
||||
count: counts.value.pendingShipment,
|
||||
to: "/user/orders?status=paid",
|
||||
},
|
||||
{
|
||||
key: "pendingReceipt",
|
||||
label: t("user.pendingReceipt"),
|
||||
count: counts.value.pendingReceipt,
|
||||
to: "/user/orders?status=shipped",
|
||||
},
|
||||
{
|
||||
key: "completed",
|
||||
label: t("user.completed"),
|
||||
count: counts.value.completed,
|
||||
to: "/user/orders?status=completed",
|
||||
},
|
||||
{
|
||||
key: "afterSale",
|
||||
label: t("user.afterSale"),
|
||||
count: counts.value.afterSale,
|
||||
to: "/user/orders?status=after-sale",
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<VCard class="pb-0">
|
||||
<h1 class="mb-4 text-xl font-bold text-text">{{ t("user.dashboard") }}</h1>
|
||||
<h1 class="text-text mb-4 text-xl font-bold">{{ t("user.dashboard") }}</h1>
|
||||
<div class="mb-5 grid gap-3 sm:grid-cols-3">
|
||||
<div class="border border-border bg-primary-soft p-[18px]"><span class="block text-xs text-muted">{{ t("user.statsBalance") }}</span><strong class="mt-2.5 block text-xl text-primary"><PriceText v-if="stats" :amount-minor="stats.balance_minor" :currency="stats.currency" /><template v-else>{{ t("common.loading") }}</template></strong></div>
|
||||
<div class="border border-border bg-primary-soft p-[18px]"><span class="block text-xs text-muted">{{ t("user.statsPoints") }}</span><strong class="mt-2.5 block text-xl text-primary">{{ stats ? stats.points : t("common.loading") }}</strong></div>
|
||||
<div class="border border-border bg-primary-soft p-[18px]"><span class="block text-xs text-muted">{{ t("user.statsFrozen") }}</span><strong class="mt-2.5 block text-xl text-primary"><PriceText v-if="stats" :amount-minor="stats.frozen_minor" :currency="stats.currency" /><template v-else>{{ t("common.loading") }}</template></strong></div>
|
||||
<div class="border-border bg-primary-soft border p-[18px]">
|
||||
<span class="text-muted block text-xs">{{ t("user.statsBalance") }}</span
|
||||
><strong class="text-primary mt-2.5 block text-xl"
|
||||
><PriceText
|
||||
v-if="stats"
|
||||
:amount-minor="stats.balance_minor"
|
||||
:currency="stats.currency"
|
||||
/><template v-else>{{ t("common.loading") }}</template></strong
|
||||
>
|
||||
</div>
|
||||
<div class="border-border bg-primary-soft border p-[18px]">
|
||||
<span class="text-muted block text-xs">{{ t("user.statsPoints") }}</span
|
||||
><strong class="text-primary mt-2.5 block text-xl">{{
|
||||
stats ? stats.points : t("common.loading")
|
||||
}}</strong>
|
||||
</div>
|
||||
<div class="border-border bg-primary-soft border p-[18px]">
|
||||
<span class="text-muted block text-xs">{{ t("user.statsFrozen") }}</span
|
||||
><strong class="text-primary mt-2.5 block text-xl"
|
||||
><PriceText
|
||||
v-if="stats"
|
||||
:amount-minor="stats.frozen_minor"
|
||||
:currency="stats.currency"
|
||||
/><template v-else>{{ t("common.loading") }}</template></strong
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 border-t border-border sm:grid-cols-5">
|
||||
<NuxtLink v-for="item in statusLinks" :key="item.key" :to="item.to" class="flex flex-col items-center gap-2 px-2 py-4 text-xs text-muted no-underline hover:text-primary sm:border-l sm:border-border first:sm:border-l-0"><span>{{ item.label }}</span><b class="text-lg font-semibold text-primary">{{ item.count }}</b></NuxtLink>
|
||||
<div class="border-border grid grid-cols-3 border-t sm:grid-cols-5">
|
||||
<NuxtLink
|
||||
v-for="item in statusLinks"
|
||||
:key="item.key"
|
||||
:to="item.to"
|
||||
class="text-muted hover:text-primary sm:border-border flex flex-col items-center gap-2 px-2 py-4 text-xs no-underline sm:border-l first:sm:border-l-0"
|
||||
><span>{{ item.label }}</span
|
||||
><b class="text-primary text-lg font-semibold">{{ item.count }}</b></NuxtLink
|
||||
>
|
||||
</div>
|
||||
</VCard>
|
||||
|
||||
<VCard>
|
||||
<h2 class="mb-4 flex items-center justify-between text-lg font-semibold text-text">{{ t("user.recentOrders") }} <NuxtLink class="text-sm font-normal text-primary no-underline" to="/user/orders">{{ t("user.viewAll") }} ›</NuxtLink></h2>
|
||||
<div v-if="loading" class="py-5 text-muted">{{ t("common.loading") }}</div>
|
||||
<h2 class="text-text mb-4 flex items-center justify-between text-lg font-semibold">
|
||||
{{ t("user.recentOrders") }}
|
||||
<NuxtLink class="text-primary text-sm font-normal no-underline" to="/user/orders"
|
||||
>{{ t("user.viewAll") }} ›</NuxtLink
|
||||
>
|
||||
</h2>
|
||||
<div v-if="loading" class="text-muted py-5">{{ t("common.loading") }}</div>
|
||||
<UiEmptyState v-else-if="orders.length === 0" :text="t('user.noOrders')" />
|
||||
<div v-else class="space-y-3">
|
||||
<article v-for="order in orders.slice(0, 3)" :key="order.id" class="border border-border">
|
||||
<header class="flex items-center justify-between gap-3 bg-bg px-3 py-2.5 text-xs text-muted"><span>{{ t("user.orderNo") }} {{ order.order_no }}</span><StatusBadge :status="order.status" kind="order" /></header>
|
||||
<article v-for="order in orders.slice(0, 3)" :key="order.id" class="border-border border">
|
||||
<header
|
||||
class="bg-bg text-muted flex items-center justify-between gap-3 px-3 py-2.5 text-xs"
|
||||
>
|
||||
<span>{{ t("user.orderNo") }} {{ order.order_no }}</span
|
||||
><StatusBadge :status="order.status" kind="order" />
|
||||
</header>
|
||||
<div class="flex items-center gap-4 p-3 max-sm:flex-wrap">
|
||||
<div class="flex min-w-[140px] gap-1"><img v-for="item in order.items" :key="item.id" class="h-11 w-11 border border-border object-contain" :src="item.image || '/mock/product-1.svg'" :alt="pick(item.product_name, locale)" /></div>
|
||||
<div class="flex-1 text-xs text-muted">{{ t("user.createdAt") }} {{ order.created_at.slice(0, 10) }}</div>
|
||||
<div class="text-right text-xs"><span class="mb-1 block text-muted">{{ t("user.orderTotal") }}</span><PriceText class="font-semibold text-primary" :amount-minor="order.total_minor" :currency="order.currency" /></div>
|
||||
<NuxtLink class="inline-flex items-center rounded-md border border-border bg-surface px-2.5 py-1 text-[13px] font-medium text-text no-underline" :to="`/user/orders/${order.id}`">{{ t("user.viewDetails") }}</NuxtLink>
|
||||
<div class="flex min-w-[140px] gap-1">
|
||||
<img
|
||||
v-for="item in order.items"
|
||||
:key="item.id"
|
||||
class="border-border h-11 w-11 border object-contain"
|
||||
:src="item.image || '/mock/product-1.svg'"
|
||||
:alt="pick(item.product_name, locale)"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-muted flex-1 text-xs">
|
||||
{{ t("user.createdAt") }} {{ order.created_at.slice(0, 10) }}
|
||||
</div>
|
||||
<div class="text-right text-xs">
|
||||
<span class="text-muted mb-1 block">{{ t("user.orderTotal") }}</span
|
||||
><PriceText
|
||||
class="text-primary font-semibold"
|
||||
:amount-minor="order.total_minor"
|
||||
:currency="order.currency"
|
||||
/>
|
||||
</div>
|
||||
<NuxtLink
|
||||
class="border-border bg-surface text-text inline-flex items-center rounded-md border px-2.5 py-1 text-[13px] font-medium no-underline"
|
||||
:to="`/user/orders/${order.id}`"
|
||||
>{{ t("user.viewDetails") }}</NuxtLink
|
||||
>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</VCard>
|
||||
|
||||
<VCard>
|
||||
<h2 class="mb-4 text-lg font-semibold text-text">{{ t("user.favoriteProducts") }} ({{ favoriteProductTotal }})</h2>
|
||||
<h2 class="text-text mb-4 text-lg font-semibold">
|
||||
{{ t("user.favoriteProducts") }} ({{ favoriteProductTotal }})
|
||||
</h2>
|
||||
<UiEmptyState v-if="favoriteProducts.length === 0" :text="t('user.noFavorites')" />
|
||||
<div v-else class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
<NuxtLink v-for="item in favoriteProducts" :key="item.id" :to="`/goods/${item.product.slug}`" class="block border border-border p-2.5 text-text no-underline hover:border-primary">
|
||||
<img class="mb-2 block h-[120px] w-full object-contain" :src="item.product.image || '/mock/product-1.svg'" :alt="pick(item.product.name, locale)" />
|
||||
<span class="block h-[34px] overflow-hidden text-xs leading-[17px]">{{ pick(item.product.name, locale) }}</span>
|
||||
<PriceText v-if="item.product.price_minor !== null && item.product.currency" class="mt-2 block text-sm font-semibold text-primary" :amount-minor="item.product.price_minor" :currency="item.product.currency" />
|
||||
<NuxtLink
|
||||
v-for="item in favoriteProducts"
|
||||
:key="item.id"
|
||||
:to="`/goods/${item.product.slug}`"
|
||||
class="border-border text-text hover:border-primary block border p-2.5 no-underline"
|
||||
>
|
||||
<img
|
||||
class="mb-2 block h-[120px] w-full object-contain"
|
||||
:src="item.product.image || '/mock/product-1.svg'"
|
||||
:alt="pick(item.product.name, locale)"
|
||||
/>
|
||||
<span class="block h-[34px] overflow-hidden text-xs leading-[17px]">{{
|
||||
pick(item.product.name, locale)
|
||||
}}</span>
|
||||
<PriceText
|
||||
v-if="item.product.price_minor !== null && item.product.currency"
|
||||
class="text-primary mt-2 block text-sm font-semibold"
|
||||
:amount-minor="item.product.price_minor"
|
||||
:currency="item.product.currency"
|
||||
/>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user