add product

This commit is contained in:
Timi
2026-09-03 14:27:40 +08:00
parent 7f669621fb
commit 333999fe21
63 changed files with 5662 additions and 2210 deletions
+117
View File
@@ -0,0 +1,117 @@
<template>
<div v-can="permissionCodes.salesOrder.read" class="sales-order-detail">
<template v-if="order">
<customer-profile-card v-if="order.customer" class="customer" :customer="order.customer" v-entrance-transition />
<section class="hero" v-entrance-transition="35">
<span class="eyebrow" v-text="'销售订单'" />
<div class="hero-title">
<h1 v-text="order.orderNo || order.id || '未命名订单'" />
<t-tag :content="getSalesOrderStatusText(order.status)" variant="light-outline" :theme="getStatusTheme(order.status)" />
</div>
<div class="hero-meta">
<span v-text="getSalesOrderTypeText(order.orderType)" />
<strong v-text="formatPackageAmount(order.saleAmount)" />
</div>
</section>
<t-cell-group title="订单信息" theme="card" v-entrance-transition="70">
<t-cell title="订单号" :note="order.orderNo || order.id" />
<t-cell title="订单类型" :note="getSalesOrderTypeText(order.orderType)" />
<t-cell title="订单状态">
<template #note><t-tag :content="getSalesOrderStatusText(order.status)" variant="light-outline" :theme="getStatusTheme(order.status)" /></template>
</t-cell>
<t-cell title="下单时间" :note="order.purchasedAt ? Time.toDateTime(order.purchasedAt) : '未知'" />
<t-cell v-if="order.sourceEventRecord?.id" title="来源登记记录" arrow @click="toEventRecord" />
<t-cell v-if="order.remark" title="备注" :note="order.remark" />
</t-cell-group>
<t-cell-group v-if="order.itemList?.length" title="订单明细" theme="card" v-entrance-transition="140">
<t-cell
v-for="item in order.itemList"
:key="item.id"
:title="item.productNameSnapshot || '未命名产品'"
:description="`${item.quantity || 0} ${item.unitSnapshot || ''} · 已履约 ${item.fulfilledQuantity || 0} · 待履约 ${item.reservedQuantity || 0}`"
:note="formatPackageAmount(item.lineAmount)"
/>
</t-cell-group>
<sales-order-finance-panel :order="order" :can-read-fund="canReadFund" @fund="toFundRecords" @record="toFundDetail" />
<t-cell-group title="统一履约" theme="card" v-entrance-transition="210">
<t-cell title="履约记录" note="查看本单所有履约单" arrow @click="toFulfillmentList" />
<t-cell v-if="canCreateFulfillment && order.orderType === 'DIRECT_PRODUCT' && order.status === 'ACTIVE'" title="创建履约单" note="直接产品订单统一履约" arrow @click="toCreateFulfillment" />
</t-cell-group>
<section class="actions" v-entrance-transition="280">
<t-button v-if="canReceive" theme="primary" @click="toPayment">登记收款</t-button>
<t-button v-if="canRefund" theme="danger" variant="outline" @click="toRefund">登记退款</t-button>
<t-button v-if="canConfirmNoRefund" theme="default" variant="outline" @click="toNoRefund">确认无需退款</t-button>
<t-button v-if="canCancel" theme="danger" variant="outline" @click="toCancel">取消订单</t-button>
</section>
</template>
<t-empty v-else :description="loading ? '正在加载订单' : '订单不存在'" />
</div>
</template>
<script setup lang="ts">
import { Time } from "timi-web";
import { useUserStore } from "timi-web-api";
import SalesOrderAPI from "@/api/SalesOrderAPI";
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
import SalesOrderFinancePanel from "@/pages/salesOrder/components/SalesOrderFinancePanel.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { SalesOrder } from "@/types/SalesOrder";
import { formatPackageAmount } from "@/utils/package";
import { getErrorMessage } from "@/utils/error";
import { getSalesOrderStatusText, getSalesOrderTypeText, getStatusTheme } from "@/utils/salesOrder";
import { Message } from "@/utils/message";
import { permissionCodes } from "@/utils/permission";
defineOptions({ name: "SalesOrderDetail" });
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const order = ref<SalesOrder>();
const loading = ref(false);
const canReadFund = computed(() => userStore.hasPermissions(permissionCodes.fundRecord.read));
const canCreateFulfillment = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create));
const canReceive = computed(() => !!order.value && order.value.status !== "CANCELLED" && order.value.paymentStatus !== "PAID" && userStore.hasPermissions(permissionCodes.fundReceive.create));
const canRefund = computed(() => !!order.value && order.value.status === "CANCELLED" && order.value.refundStatus === "PENDING" && userStore.hasPermissions(permissionCodes.fund.refund));
const canConfirmNoRefund = computed(() => !!order.value && order.value.status === "CANCELLED" && order.value.refundStatus === "PENDING" && userStore.hasPermissions(permissionCodes.fund.refund));
const canCancel = computed(() => !!order.value && order.value.status === "ACTIVE" && userStore.hasPermissions(permissionCodes.salesOrder.cancel));
async function loadData(): Promise<void> {
const id = String(route.params.id || "");
if (!id) return;
loading.value = true;
try { order.value = await SalesOrderAPI.detail(id); }
catch (error) { Message.error(getErrorMessage(error, "订单加载失败")); }
finally { loading.value = false; }
}
async function toEventRecord(): Promise<void> { if (order.value?.sourceEventRecord?.id) await router.push(`/management/registration-event-records/${order.value.sourceEventRecord.id}`); }
async function toFundRecords(): Promise<void> { if (order.value?.id) await router.push({ path: "/management/fund-records", query: { salesOrderId: order.value.id } }); }
async function toFundDetail(id: string): Promise<void> { await router.push(`/management/fund-records/${id}`); }
async function toFulfillmentList(): Promise<void> { if (order.value?.id) await router.push({ path: "/management/order-fulfillments", query: { salesOrderId: order.value.id } }); }
async function toCreateFulfillment(): Promise<void> { if (order.value?.id) await router.push({ path: "/management/order-fulfillments/create", query: { salesOrderId: order.value.id } }); }
async function toPayment(): Promise<void> { if (order.value?.id) await router.push(`/management/sales-orders/${order.value.id}/payment`); }
async function toRefund(): Promise<void> { if (order.value?.id) await router.push(`/management/sales-orders/${order.value.id}/refund`); }
async function toNoRefund(): Promise<void> { if (order.value?.id) await router.push(`/management/sales-orders/${order.value.id}/no-refund`); }
async function toCancel(): Promise<void> { if (order.value?.id) await router.push(`/management/sales-orders/${order.value.id}/cancel`); }
onMounted(loadData);
onUnmounted(() => navBarStore.clearRight());
</script>
<style scoped lang="less">
.sales-order-detail { gap: 1rem; padding: calc(var(--app-nav-height) + 1rem) 0 2rem; display: flex; flex-direction: column;
.customer { margin: 0 1rem; }
.hero { margin: 0 1rem; padding: 1rem; background: linear-gradient(135deg, #fff7ed, #eff6ff); box-shadow: var(--td-shadow-1); border-radius: 1.25rem;
.eyebrow { color: #c2410c; display: block; font-size: .6875rem; font-weight: 700; letter-spacing: .08em; }
.hero-title { gap: .5rem; display: flex; margin-top: .35rem; align-items: center; justify-content: space-between; }
h1 { margin: 0; min-width: 0; font-size: 1.2rem; line-height: 1.35; }
.hero-meta { gap: .75rem; display: flex; margin-top: .6rem; color: var(--td-text-color-secondary); align-items: center; justify-content: space-between; }
strong { color: #c2410c; font-size: 1.2rem; white-space: nowrap; }
}
.actions { gap: .5rem; padding: 0 1rem; display: flex; flex-wrap: wrap; }
.actions .t-button { flex: 1 1 8rem; }
}
:global(html.theme-dark) .sales-order-detail .hero { background: linear-gradient(135deg, rgba(194, 65, 12, .2), rgba(37, 99, 235, .18));
.eyebrow, strong { color: #fdba74; }
}
</style>