feat: three nuxt frontends, demo seed, rounding + money-exponent + rate-cast fixes, archived specs
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import type { Order, Paged } from "@vmall/shared";
|
||||
|
||||
const { $api } = useNuxtApp();
|
||||
const { locale } = useI18n();
|
||||
const { formatAmount } = usePrice();
|
||||
const page = ref(1);
|
||||
const result = ref<Paged<Order> | null>(null);
|
||||
const totals = ref<Record<string, string>>({});
|
||||
const loading = ref(true);
|
||||
const error = ref("");
|
||||
|
||||
function errorMessage(value: unknown): string {
|
||||
return value instanceof Error ? value.message : $t("mall.loadFailed");
|
||||
}
|
||||
|
||||
async function loadOrders(): Promise<void> {
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
result.value = await $api.listMyOrders(page.value);
|
||||
const entries = await Promise.all(result.value.items.map(async (order) => [order.id, await formatAmount(order.total_minor, order.currency)] as const));
|
||||
totals.value = Object.fromEntries(entries);
|
||||
} catch (value) {
|
||||
error.value = errorMessage(value);
|
||||
result.value = null;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function changePage(next: number): Promise<void> {
|
||||
if (next < 1 || next > totalPages.value) return;
|
||||
page.value = next;
|
||||
await loadOrders();
|
||||
}
|
||||
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil((result.value?.total ?? 0) / (result.value?.per_page ?? 20))));
|
||||
|
||||
watch(locale, () => {
|
||||
if (result.value) void loadOrders();
|
||||
});
|
||||
onMounted(() => void loadOrders());
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<h1 class="page-title">{{ $t("order.title") }}</h1>
|
||||
<p v-if="loading" class="muted">{{ $t("common.loading") }}</p>
|
||||
<p v-if="error" class="error-text" role="alert">{{ error }}</p>
|
||||
<div v-if="!loading && result && result.items.length === 0" class="card empty-state">{{ $t("common.empty") }}</div>
|
||||
<table v-if="result && result.items.length > 0" class="table">
|
||||
<thead><tr><th>{{ $t("order.orderNo") }}</th><th>{{ $t("mall.created") }}</th><th>{{ $t("mall.items") }}</th><th>{{ $t("common.total") }}</th><th>{{ $t("common.status") }}</th><th>{{ $t("common.actions") }}</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="order in result.items" :key="order.id">
|
||||
<td>{{ order.order_no }}</td>
|
||||
<td>{{ new Date(order.created_at).toLocaleString(locale === "zh" ? "zh-CN" : "en-US") }}</td>
|
||||
<td>{{ order.items.length }}</td>
|
||||
<td>{{ totals[order.id] || $t("common.loading") }}</td>
|
||||
<td><StatusBadge :status="order.status" kind="order" /></td>
|
||||
<td><NuxtLink :to="`/orders/${order.id}`">{{ $t("mall.details") }}</NuxtLink></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="result && result.items.length > 0" class="row between pagination">
|
||||
<button class="btn" :disabled="page <= 1" @click="changePage(page - 1)">{{ $t("common.prev") }}</button>
|
||||
<span class="muted">{{ $t("common.page") }} {{ page }} / {{ totalPages }}</span>
|
||||
<button class="btn" :disabled="page >= totalPages" @click="changePage(page + 1)">{{ $t("common.next") }}</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pagination { margin-top: 16px; }
|
||||
.empty-state { text-align: center; }
|
||||
@media (max-width: 700px) { .table { display: block; overflow-x: auto; white-space: nowrap; } }
|
||||
</style>
|
||||
Reference in New Issue
Block a user