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
+65
View File
@@ -0,0 +1,65 @@
<script setup lang="ts">
import type { Invoice } from "@vmall/shared";
const { $api } = useNuxtApp();
const { locale } = useI18n();
const { formatAmount } = usePrice();
const invoices = ref<Invoice[]>([]);
const amounts = 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 loadInvoices(): Promise<void> {
loading.value = true;
error.value = "";
try {
invoices.value = await $api.listMyInvoices();
const entries = await Promise.all(invoices.value.map(async (invoice) => [invoice.id, await formatAmount(invoice.amount_minor, invoice.currency)] as const));
amounts.value = Object.fromEntries(entries);
} catch (value) {
error.value = errorMessage(value);
invoices.value = [];
} finally {
loading.value = false;
}
}
watch(locale, () => {
if (invoices.value.length > 0) void loadInvoices();
});
onMounted(() => void loadInvoices());
definePageMeta({ middleware: "auth" });
</script>
<template>
<section>
<h1 class="page-title">{{ $t("invoice.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 && invoices.length === 0" class="card empty-state">{{ $t("common.empty") }}</div>
<table v-if="invoices.length > 0" class="table">
<thead><tr><th>{{ $t("invoice.invoiceNo") }}</th><th>{{ $t("order.orderNo") }}</th><th>{{ $t("invoice.invoiceTitle") }}</th><th>{{ $t("invoice.kind") }}</th><th>{{ $t("invoice.amount") }}</th><th>{{ $t("common.status") }}</th><th>{{ $t("invoice.status.issued") }}</th></tr></thead>
<tbody>
<tr v-for="item in invoices" :key="item.id">
<td>{{ item.invoice_no || $t("mall.notAvailable") }}</td>
<td>{{ item.order_no || $t("mall.notAvailable") }}</td>
<td>{{ item.title }}</td>
<td>{{ $t(`invoice.${item.kind}`) }}</td>
<td>{{ amounts[item.id] || $t("common.loading") }}</td>
<td><StatusBadge :status="item.status" kind="invoice" /></td>
<td>{{ item.issued_at ? new Date(item.issued_at).toLocaleString(locale === "zh" ? "zh-CN" : "en-US") : $t("mall.notAvailable") }}</td>
</tr>
</tbody>
</table>
</section>
</template>
<style scoped>
.empty-state { text-align: center; }
@media (max-width: 760px) { .table { display: block; overflow-x: auto; white-space: nowrap; } }
</style>