Files
vmall/apps/shop-admin/pages/aftersales/index.vue
T

97 lines
3.7 KiB
Vue

<script setup lang="ts">
import type { Aftersale, AftersaleStatus } from "@vmall/shared";
definePageMeta({ middleware: "auth" });
const { $api } = useNuxtApp();
const { locale, t: translate } = useI18n();
const { load: loadMoney, fmt } = useMoney();
const aftersales = ref<Aftersale[]>([]);
const status = ref<"" | AftersaleStatus>("");
const loading = ref(true);
const error = ref("");
function statusClass(value: AftersaleStatus): "green" | "red" | "orange" | "blue" | "gray" {
if (value === "refunded") return "green";
if (value === "rejected") return "red";
if (value === "cancelled") return "gray";
if (value === "pending") return "orange";
return "blue";
}
function shortId(id: string): string {
return id.slice(0, 8);
}
function formatDate(value: string): string {
return new Date(value).toLocaleString(locale.value === "zh" ? "zh-CN" : "en-US");
}
async function loadAftersales(): Promise<void> {
loading.value = true;
error.value = "";
try {
aftersales.value = await $api.shop.listAftersales(status.value || undefined);
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : translate("common.error");
} finally {
loading.value = false;
}
}
onMounted(() => {
loadMoney();
loadAftersales();
});
</script>
<template>
<VPage :title="$t('aftersale.title')">
<div class="mb-4 flex items-center gap-3">
<label for="aftersale-status" class="text-sm font-medium">{{ $t("shop.filterStatus") }}</label>
<select
id="aftersale-status"
v-model="status"
class="rounded-md border border-border bg-surface px-3 py-2 text-sm text-text focus:border-primary focus:outline-2 focus:outline-primary/30"
@change="loadAftersales"
>
<option value="">{{ $t("common.all") }}</option>
<option value="pending">{{ $t("aftersale.status.pending") }}</option>
<option value="approved">{{ $t("aftersale.status.approved") }}</option>
<option value="buyer_shipping">{{ $t("aftersale.status.buyer_shipping") }}</option>
<option value="merchant_confirmed">{{ $t("aftersale.status.merchant_confirmed") }}</option>
<option value="refunded">{{ $t("aftersale.status.refunded") }}</option>
<option value="rejected">{{ $t("aftersale.status.rejected") }}</option>
<option value="cancelled">{{ $t("aftersale.status.cancelled") }}</option>
</select>
</div>
<div v-if="error" class="my-2 text-sm text-danger" role="alert">{{ error }}</div>
<p v-if="loading" class="text-muted">{{ $t("common.loading") }}</p>
<VCard v-else-if="!aftersales.length" class="text-muted">{{ $t("common.empty") }}</VCard>
<VTable v-else>
<thead>
<tr>
<th>{{ $t("aftersale.buyer") }}</th>
<th>{{ $t("shop.kind") }}</th>
<th>{{ $t("aftersale.amount") }}</th>
<th>{{ $t("shop.created") }}</th>
<th>{{ $t("common.status") }}</th>
<th>{{ $t("common.actions") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="aftersale in aftersales" :key="aftersale.id">
<td class="font-mono text-xs" :title="aftersale.user_id">{{ shortId(aftersale.user_id) }}</td>
<td>{{ $t(`aftersale.kind.${aftersale.kind}`) }}</td>
<td>{{ fmt(aftersale.amount_minor, aftersale.currency) }}</td>
<td>{{ formatDate(aftersale.created_at) }}</td>
<td><VBadge :tone="statusClass(aftersale.status)">{{ $t(`aftersale.status.${aftersale.status}`) }}</VBadge></td>
<td>
<NuxtLink :to="`/aftersales/${aftersale.id}`" class="text-primary hover:underline">{{ $t("shop.details") }}</NuxtLink>
</td>
</tr>
</tbody>
</VTable>
</VPage>
</template>