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
+88 -24
View File
@@ -16,7 +16,8 @@ function shopName(shopId: string): string {
return shop ? pick(shop.name, locale.value) : t("user.shop");
}
const activeFilter = ref("all");const orders = ref<Order[]>([]);
const activeFilter = ref("all");
const orders = ref<Order[]>([]);
const page = ref(1);
const total = ref(0);
const perPage = ref(10);
@@ -43,18 +44,23 @@ const tabs = computed(() => [
const filteredOrders = computed(() => {
const statuses = filterStatuses[activeFilter.value] ?? [];
return statuses.length === 0 ? orders.value : orders.value.filter((order) => statuses.includes(order.status));
return statuses.length === 0
? orders.value
: orders.value.filter((order) => statuses.includes(order.status));
});
function setInitialFilter(): void {
const queryStatus = String(route.query.status ?? "");
const mapped = queryStatus === "paid" || queryStatus === "fulfilling"
? "pending_shipment"
: queryStatus === "shipped"
? "pending_receipt"
: queryStatus === "pending_payment" || queryStatus === "completed" || queryStatus === "cancelled"
? queryStatus
: "all";
const mapped =
queryStatus === "paid" || queryStatus === "fulfilling"
? "pending_shipment"
: queryStatus === "shipped"
? "pending_receipt"
: queryStatus === "pending_payment" ||
queryStatus === "completed" ||
queryStatus === "cancelled"
? queryStatus
: "all";
activeFilter.value = mapped;
}
@@ -103,30 +109,89 @@ onMounted(() => {
<template>
<VCard class="min-h-[560px]">
<h1 class="mb-4 text-xl font-bold text-text">{{ t("user.ordersTitle") }}</h1>
<h1 class="text-text mb-4 text-xl font-bold">{{ t("user.ordersTitle") }}</h1>
<UiTabs :model-value="activeFilter" :tabs="tabs" @update:model-value="changeFilter">
<template #default>
<div v-if="loading" class="py-[30px] text-muted">{{ t("common.loading") }}</div>
<div v-if="loading" class="text-muted py-[30px]">{{ t("common.loading") }}</div>
<UiEmptyState v-else-if="filteredOrders.length === 0" :text="t('user.noOrders')" />
<div v-else class="space-y-3.5">
<VCard v-for="order in filteredOrders" :key="order.id" :padded="false" class="overflow-hidden">
<header class="flex items-center justify-between gap-2.5 border-b border-border bg-bg px-3.5 py-3 text-xs text-muted max-sm:items-start">
<div class="flex min-w-0 items-center gap-3.5 max-sm:flex-col max-sm:items-start max-sm:gap-1"><strong class="text-[13px] font-semibold text-text">{{ shopName(order.shop_id) }}</strong><span>{{ t("user.orderNo") }} {{ order.order_no }}</span><span>{{ t("user.createdAt") }} {{ order.created_at.slice(0, 10) }}</span></div>
<VCard
v-for="order in filteredOrders"
:key="order.id"
:padded="false"
class="overflow-hidden"
>
<header
class="border-border bg-bg text-muted flex items-center justify-between gap-2.5 border-b px-3.5 py-3 text-xs max-sm:items-start"
>
<div
class="flex min-w-0 items-center gap-3.5 max-sm:flex-col max-sm:items-start max-sm:gap-1"
>
<strong class="text-text text-[13px] font-semibold">{{
shopName(order.shop_id)
}}</strong
><span>{{ t("user.orderNo") }} {{ order.order_no }}</span
><span>{{ t("user.createdAt") }} {{ order.created_at.slice(0, 10) }}</span>
</div>
<StatusBadge :status="order.status" kind="order" />
</header>
<div class="px-3.5 py-1">
<div v-for="item in order.items" :key="item.id" class="flex items-center gap-3 border-b border-border py-2.5 last:border-b-0">
<img class="h-[54px] w-[54px] border border-border object-contain" :src="item.image || '/mock/product-1.svg'" :alt="pick(item.product_name, locale)" />
<div class="flex min-w-0 flex-1 flex-col gap-1 text-[13px]"><span class="truncate">{{ pick(item.product_name, locale) }}</span><small class="text-[11px] text-muted">{{ item.sku_code }} × {{ item.qty }}</small></div>
<PriceText class="text-[13px] text-muted" :amount-minor="item.unit_price_minor" :currency="order.currency" />
<div
v-for="item in order.items"
:key="item.id"
class="border-border flex items-center gap-3 border-b py-2.5 last:border-b-0"
>
<img
class="border-border h-[54px] w-[54px] border object-contain"
:src="item.image || '/mock/product-1.svg'"
:alt="pick(item.product_name, locale)"
/>
<div class="flex min-w-0 flex-1 flex-col gap-1 text-[13px]">
<span class="truncate">{{ pick(item.product_name, locale) }}</span
><small class="text-muted text-[11px]"
>{{ item.sku_code }} × {{ item.qty }}</small
>
</div>
<PriceText
class="text-muted text-[13px]"
:amount-minor="item.unit_price_minor"
:currency="order.currency"
/>
</div>
</div>
<footer class="flex items-center justify-between gap-3 border-t border-border px-3.5 py-3 text-xs text-muted max-sm:flex-col max-sm:items-start">
<span>{{ t("user.orderTotal") }} <strong class="text-primary"><PriceText :amount-minor="order.total_minor" :currency="order.currency" /></strong></span>
<footer
class="border-border text-muted flex items-center justify-between gap-3 border-t px-3.5 py-3 text-xs max-sm:flex-col max-sm:items-start"
>
<span
>{{ t("user.orderTotal") }}
<strong class="text-primary"
><PriceText
:amount-minor="order.total_minor"
:currency="order.currency" /></strong
></span>
<div class="flex gap-2">
<VBtn v-if="order.status === 'pending_payment'" variant="primary" size="sm" type="button" :disabled="workingId === order.id" @click="pay(order)">{{ t("user.payNow") }}</VBtn>
<VBtn v-if="order.status === 'pending_payment'" size="sm" type="button" :disabled="workingId === order.id" @click="cancel(order)">{{ t("user.cancelOrder") }}</VBtn>
<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>
<VBtn
v-if="order.status === 'pending_payment'"
variant="primary"
size="sm"
type="button"
:disabled="workingId === order.id"
@click="pay(order)"
>{{ t("user.payNow") }}</VBtn
>
<VBtn
v-if="order.status === 'pending_payment'"
size="sm"
type="button"
:disabled="workingId === order.id"
@click="cancel(order)"
>{{ t("user.cancelOrder") }}</VBtn
>
<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>
</footer>
</VCard>
@@ -136,4 +201,3 @@ onMounted(() => {
</UiTabs>
</VCard>
</template>