feat(aftersale): per-line refund/return flow with ledger-backed completion (add-aftersale-refunds)
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import type { Aftersale } from "@vmall/shared";
|
||||
import { t as pick } from "@vmall/shared";
|
||||
|
||||
definePageMeta({ middleware: "auth" });
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
const { $api } = useNuxtApp();
|
||||
const rows = ref<Aftersale[]>([]);
|
||||
const loading = ref(true);
|
||||
const failed = ref(false);
|
||||
|
||||
function tone(status: Aftersale["status"]): "green" | "blue" | "red" | "orange" {
|
||||
if (status === "refunded") return "green";
|
||||
if (["approved", "buyer_shipping", "merchant_confirmed"].includes(status)) return "blue";
|
||||
if (["rejected", "cancelled"].includes(status)) return "red";
|
||||
return "orange";
|
||||
}
|
||||
|
||||
function statusLabel(status: Aftersale["status"]): string {
|
||||
return t(`user.aftersaleStatus_${status}`);
|
||||
}
|
||||
|
||||
async function load(): Promise<void> {
|
||||
loading.value = true;
|
||||
failed.value = false;
|
||||
try {
|
||||
rows.value = await $api.listMyAftersales();
|
||||
} catch {
|
||||
failed.value = true;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => void load());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard class="min-h-[560px]">
|
||||
<div class="mb-4 flex items-center justify-between gap-3">
|
||||
<h1 class="m-0 text-xl font-bold text-text">{{ t("user.aftersalesTitle") }}</h1>
|
||||
<NuxtLink class="text-sm text-primary no-underline" to="/user/orders">{{ t("user.myOrders") }}</NuxtLink>
|
||||
</div>
|
||||
<div v-if="loading" class="py-5 text-muted">{{ t("common.loading") }}</div>
|
||||
<p v-else-if="failed" class="py-5 text-sm text-danger">{{ t("user.aftersaleLoadFailed") }}</p>
|
||||
<UiEmptyState v-else-if="rows.length === 0" :text="t('user.noAftersales')" />
|
||||
<VTable v-else>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ t("user.aftersaleOrder") }}</th>
|
||||
<th>{{ t("user.aftersaleType") }}</th>
|
||||
<th>{{ t("user.aftersaleAmount") }}</th>
|
||||
<th>{{ t("user.aftersaleStatus") }}</th>
|
||||
<th>{{ t("user.aftersaleCreatedAt") }}</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in rows" :key="row.id">
|
||||
<td>
|
||||
<NuxtLink class="text-primary no-underline" :to="`/user/aftersales/${row.id}`">{{ row.order_id }}</NuxtLink>
|
||||
</td>
|
||||
<td>{{ t(row.kind === "refund_only" ? "user.refundOnly" : "user.returnRefund") }}</td>
|
||||
<td><PriceText :amount-minor="row.amount_minor" :currency="row.currency" /></td>
|
||||
<td><VBadge :tone="tone(row.status)">{{ statusLabel(row.status) }}</VBadge></td>
|
||||
<td class="text-muted">{{ row.created_at.slice(0, 10) }}</td>
|
||||
<td><NuxtLink class="text-primary no-underline" :to="`/user/aftersales/${row.id}`">{{ t("user.viewDetails") }}</NuxtLink></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</VCard>
|
||||
</template>
|
||||
Reference in New Issue
Block a user