feat: three nuxt frontends, demo seed, rounding + money-exponent + rate-cast fixes, archived specs

This commit is contained in:
Chengdong Zhang
2026-09-17 13:34:38 +08:00
parent dc9fd31c5e
commit 653f13161a
85 changed files with 4327 additions and 106 deletions
+78
View File
@@ -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>