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

107 lines
3.8 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="border-border bg-surface text-text focus:border-primary focus:outline-primary/30 rounded-md border px-3 py-2 text-sm focus:outline-2"
@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="text-danger my-2 text-sm" 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>