feat(freight): shop freight templates, server-side checkout fees, company dictionary (add-freight-templates)
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { t as localized } from "@vmall/shared";
|
||||
import type { Order, OrderStatus, Shipment, ShipmentItemBody } from "@vmall/shared";
|
||||
import type {
|
||||
Order,
|
||||
OrderStatus,
|
||||
Shipment,
|
||||
ShipmentItemBody,
|
||||
ShippingCompany,
|
||||
} from "@vmall/shared";
|
||||
|
||||
definePageMeta({ middleware: "auth" });
|
||||
|
||||
@@ -11,9 +17,11 @@ const route = useRoute();
|
||||
const orderId = computed(() => String(route.params.id));
|
||||
const order = ref<Order | null>(null);
|
||||
const shipments = ref<Shipment[]>([]);
|
||||
const companies = ref<ShippingCompany[]>([]);
|
||||
const quantities = ref<Record<string, number>>({});
|
||||
const carrier = ref("");
|
||||
const trackingNo = ref("");
|
||||
const shippingCompanyCode = ref("");
|
||||
const loading = ref(true);
|
||||
const busy = ref(false);
|
||||
const actionId = ref("");
|
||||
@@ -34,10 +42,16 @@ function formatDate(value: string): string {
|
||||
return new Date(value).toLocaleString(locale.value === "zh" ? "zh-CN" : "en-US");
|
||||
}
|
||||
|
||||
function companyName(code: string | null | undefined): string {
|
||||
if (!code) return "—";
|
||||
const company = companies.value.find((item) => item.code === code);
|
||||
return company ? localized(company.name, locale.value) : code;
|
||||
}
|
||||
|
||||
function shippedQuantity(itemId: string): number {
|
||||
return shipments.value
|
||||
.filter((shipment) => shipment.order_id === orderId.value)
|
||||
.flatMap((shipment) => shipment.items)
|
||||
.flatMap((shipment) => shipment.items ?? [])
|
||||
.filter((item) => item.order_item_id === itemId)
|
||||
.reduce((total, item) => total + item.qty, 0);
|
||||
}
|
||||
@@ -63,12 +77,14 @@ async function loadOrder(): Promise<void> {
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const [loadedOrder, loadedShipments] = await Promise.all([
|
||||
const [loadedOrder, loadedShipments, loadedCompanies] = await Promise.all([
|
||||
findShopOrder(),
|
||||
$api.shop.listShipments(),
|
||||
$api.listShippingCompanies(),
|
||||
]);
|
||||
order.value = loadedOrder;
|
||||
shipments.value = loadedShipments.filter((shipment) => shipment.order_id === orderId.value);
|
||||
companies.value = loadedCompanies;
|
||||
const nextQuantities: Record<string, number> = {};
|
||||
loadedOrder.items.forEach((item) => {
|
||||
nextQuantities[item.id] = Math.max(
|
||||
@@ -76,7 +92,7 @@ async function loadOrder(): Promise<void> {
|
||||
item.qty -
|
||||
loadedShipments
|
||||
.filter((shipment) => shipment.order_id === orderId.value)
|
||||
.flatMap((shipment) => shipment.items)
|
||||
.flatMap((shipment) => shipment.items ?? [])
|
||||
.filter((shipmentItem) => shipmentItem.order_item_id === item.id)
|
||||
.reduce((total, shipmentItem) => total + shipmentItem.qty, 0),
|
||||
);
|
||||
@@ -94,6 +110,10 @@ async function createShipment(): Promise<void> {
|
||||
error.value = translate("common.required");
|
||||
return;
|
||||
}
|
||||
if (!shippingCompanyCode.value) {
|
||||
error.value = translate("shop.shippingCompanyRequired");
|
||||
return;
|
||||
}
|
||||
const items: ShipmentItemBody[] = order.value.items
|
||||
.map((item) => ({ order_item_id: item.id, qty: quantities.value[item.id] ?? 0 }))
|
||||
.filter((item) => item.qty > 0);
|
||||
@@ -109,9 +129,11 @@ async function createShipment(): Promise<void> {
|
||||
carrier.value.trim(),
|
||||
trackingNo.value.trim(),
|
||||
items,
|
||||
shippingCompanyCode.value,
|
||||
);
|
||||
carrier.value = "";
|
||||
trackingNo.value = "";
|
||||
shippingCompanyCode.value = "";
|
||||
await loadOrder();
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : translate("common.error");
|
||||
@@ -208,6 +230,19 @@ onMounted(() => {
|
||||
<VField :label="$t('shop.trackingNo')">
|
||||
<VInput id="tracking-no" v-model="trackingNo" required />
|
||||
</VField>
|
||||
<VField :label="$t('shop.shippingCompany')">
|
||||
<select
|
||||
id="shipping-company"
|
||||
v-model="shippingCompanyCode"
|
||||
required
|
||||
class="border-border bg-surface text-text focus:border-primary focus:outline-primary/30 w-full rounded-md border px-3 py-2 text-sm focus:outline-2"
|
||||
>
|
||||
<option value="" disabled>{{ $t("shop.selectShippingCompany") }}</option>
|
||||
<option v-for="company in companies" :key="company.code" :value="company.code">
|
||||
{{ localized(company.name, locale) }}
|
||||
</option>
|
||||
</select>
|
||||
</VField>
|
||||
</div>
|
||||
<h3 class="mb-3 text-base font-semibold">{{ $t("shop.shipmentQuantities") }}</h3>
|
||||
<div
|
||||
@@ -246,6 +281,7 @@ onMounted(() => {
|
||||
<tr>
|
||||
<th>{{ $t("shipment.shipmentNo") }}</th>
|
||||
<th>{{ $t("shop.carrier") }}</th>
|
||||
<th>{{ $t("shop.shippingCompany") }}</th>
|
||||
<th>{{ $t("shop.trackingNo") }}</th>
|
||||
<th>{{ $t("common.status") }}</th>
|
||||
<th>{{ $t("common.actions") }}</th>
|
||||
@@ -255,6 +291,7 @@ onMounted(() => {
|
||||
<tr v-for="shipment in shipments" :key="shipment.id">
|
||||
<td>{{ shipment.shipment_no }}</td>
|
||||
<td>{{ shipment.carrier }}</td>
|
||||
<td>{{ companyName(shipment.shipping_company_code) }}</td>
|
||||
<td>{{ shipment.tracking_no }}</td>
|
||||
<td>
|
||||
<VBadge :tone="shipmentStatusClass(shipment.status)">{{
|
||||
|
||||
Reference in New Issue
Block a user