diff --git a/src/components/PackagePurchaseForm.vue b/src/components/PackagePurchaseForm.vue deleted file mode 100644 index 5f88b8d..0000000 --- a/src/components/PackagePurchaseForm.vue +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - 标准价: - - - - - - - - - - - - - 购买权益明细 - 当前账号无定制权限,明细不可修改 - - - - - - - - - - - - - - - - - diff --git a/src/pages/package/CustomerPackageDetail.vue b/src/pages/package/CustomerPackageDetail.vue index 786b5e6..3bd5453 100644 --- a/src/pages/package/CustomerPackageDetail.vue +++ b/src/pages/package/CustomerPackageDetail.vue @@ -15,16 +15,6 @@ - - - - - - - - - - @@ -56,8 +46,20 @@ 取消客户套餐 - - + + + + + + @@ -73,8 +75,8 @@ import type { GaoCustomerPackage } from "@/types/Package"; import type { OrderFulfillment } from "@/types/Fulfillment"; import type { SalesOrder } from "@/types/SalesOrder"; import { getErrorMessage } from "@/utils/error"; -import { formatPackageAmount, getCustomerPackageStatusText, getPackageQuantityText } from "@/utils/package"; -import { getFulfillmentStatusText, getFulfillmentTypeText, getSalesPaymentStatusText, getSalesRefundStatusText } from "@/utils/salesOrder"; +import { getCustomerPackageStatusText, getPackageQuantityText } from "@/utils/package"; +import { getFulfillmentStatusText, getFulfillmentTypeText } from "@/utils/salesOrder"; import { Message } from "@/utils/message"; import { permissionCodes } from "@/utils/permission"; @@ -92,7 +94,6 @@ const isCancelling = ref(false); const canCancel = computed(() => customerPackage.value?.status === "ACTIVE" && userStore.hasPermissions(permissionCodes.customerPackage.cancel)); const canCreateFulfillment = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create)); const canReadOrder = computed(() => userStore.hasPermissions(permissionCodes.salesOrder.read)); -const canReadFund = computed(() => userStore.hasPermissions(permissionCodes.fundRecord.read)); const canReadPackageConfig = computed(() => userStore.hasPermissions(permissionCodes.packageConfig.read)); const hasAvailableItem = computed(() => customerPackage.value?.itemList?.some(item => 0 < Number(item.availableQuantity)) ?? false); @@ -114,8 +115,15 @@ async function loadData(): Promise { } function getStatusTheme(status?: string): "success" | "warning" | "danger" | "default" { return status === "ACTIVE" ? "success" : status === "CANCELLED" ? "danger" : "default"; } function getFulfillmentSummary(item: OrderFulfillment): string { return `${item.deliveredAt ? Time.toDateTime(item.deliveredAt) : "时间未知"} · ${(item.itemList || []).length} 项`; } -async function toOrder(): Promise { if (order.value?.id) await router.push(`/management/sales-orders/${order.value.id}`); } -async function toPackageConfig(): Promise { if (customerPackage.value?.packageConfigId) await router.push(`/management/package-configs/${customerPackage.value.packageConfigId}`); }async function toFundRecords(): Promise { if (order.value?.id) await router.push({ path: "/management/fund-records", query: { salesOrderId: order.value.id } }); } +async function toOrder(): Promise { + if (order.value?.id) { + await router.push({ + path: `/management/sales-orders/${order.value.id}`, + query: { transitionDepth: String(getCurrentRouteDepth() + 1) } + }); + } +} +async function toPackageConfig(): Promise { if (customerPackage.value?.packageConfigId) await router.push(`/management/package-configs/${customerPackage.value.packageConfigId}`); } async function toFulfillment(id?: string): Promise { if (id) await router.push(`/management/order-fulfillments/${id}`); } async function toCreateFulfillment(): Promise { if (order.value?.id && customerPackage.value?.id) { @@ -127,12 +135,29 @@ async function toCreateFulfillment(): Promise { } function openCancel(): void { cancelReason.value = ""; cancelVisible.value = true; } async function confirmCancel(): Promise { - if (!customerPackage.value?.id || !cancelReason.value || isCancelling.value) return; + if (isCancelling.value) return; + const customerPackageId = customerPackage.value?.id; + const reason = cancelReason.value.trim(); + if (!customerPackageId) return; + if (!reason) { + Message.warning("取消原因不能为空"); + return; + } isCancelling.value = true; - try { await PackageAPI.cancel(customerPackage.value.id, cancelReason.value); cancelVisible.value = false; Message.success("客户套餐已取消"); await loadData(); } + try { await PackageAPI.cancel(customerPackageId, reason); cancelVisible.value = false; Message.success("客户套餐已取消"); await loadData(); } catch (error) { Message.error(getErrorMessage(error, "套餐取消失败")); } finally { isCancelling.value = false; } } +function getCurrentRouteDepth(): number { + const transitionDepth = Array.isArray(route.query.transitionDepth) + ? route.query.transitionDepth[0] + : route.query.transitionDepth; + const depth = Number(transitionDepth); + if (transitionDepth && Number.isInteger(depth) && 0 <= depth && depth <= 20) { + return depth; + } + return Number(route.meta.depth ?? 0); +} onMounted(loadData); onUnmounted(() => navBarStore.clearRight()); diff --git a/src/pages/package/PackagePurchase.vue b/src/pages/package/PackagePurchase.vue index 893339f..f06d53f 100644 --- a/src/pages/package/PackagePurchase.vue +++ b/src/pages/package/PackagePurchase.vue @@ -45,12 +45,74 @@ v-entrance-transition /> 套餐当前不可购买,请检查套餐状态和有效期 - + + + + + + 标准价: + + + + + + + + + + + + + 购买权益明细 + 当前账号无定制权限,明细不可修改 + + + + + + + + + + + + + + + + + + + + + + ([]); +const photoUrlList = computed(() => photoItems.value.map(item => item.url)); + +function buildAttachments(): PackageAttachment[] { + return photoItems.value.flatMap(item => item.tempFileId ? [{ + tempFileId: item.tempFileId, + attachType: item.attachType || "PHOTO" + }] : []); +} + const { isSaleable, canPriceOverride, @@ -284,9 +369,35 @@ const { selectedCustomerList, note, hasPermission: permissionCode => userStore.hasPermissions(permissionCode), - buildRegisteredAt: buildPurchasedAt + buildRegisteredAt: buildPurchasedAt, + buildAttachments }); +function getItemLabel(item: PackagePurchaseItem): string { + const configItem = config.value?.itemList?.find(configItem => configItem.productId === item.productId && configItem.productUnitId === item.productUnitId); + return configItem ? `${configItem.productNameSnapshot || "未命名产品"}` : "产品权益"; +} + +function getItemName(item: PackagePurchaseItem): string { + return config.value?.itemList?.find(configItem => configItem.productId === item.productId && configItem.productUnitId === item.productUnitId)?.productNameSnapshot || "未命名产品"; +} + +function getItemPhotoURL(item: PackagePurchaseItem): string { + const configItem = config.value?.itemList?.find(configItem => configItem.productId === item.productId && configItem.productUnitId === item.productUnitId); + return getProductPhotoURL(configItem?.product || {}); +} + +function getItemUnit(item: PackagePurchaseItem): string { + return config.value?.itemList?.find(configItem => configItem.productId === item.productId && configItem.productUnitId === item.productUnitId)?.unitSnapshot || ""; +} + +function updateItemQuantity(index: number, value: string | number): void { + packagePurchaseItemList.value = packagePurchaseItemList.value.map((item, itemIndex) => itemIndex === index ? { + ...item, + totalQuantity: String(value) + } : item); +} + async function submitRecord(): Promise { if (isSaving.value) { return; @@ -385,6 +496,87 @@ watch(config, value => { align-items: center; justify-content: flex-end; } + + .package-purchase-form { + gap: .75rem; + display: grid; + margin-top: 1rem; + + .config-summary { + gap: .25rem; + margin: 0 1rem; + padding: .5rem .75rem; + display: flex; + background: var(--td-error-color-1); + border-radius: .75rem; + flex-direction: column; + + .header { + display: flex; + justify-content: space-between; + + .price { + display: flex; + align-items: baseline; + + .label { + font-size: .75rem; + } + + .value { + font-size: 1.75rem; + line-height: 1; + } + } + } + } + + .items { + gap: .5rem; + margin: 0; + display: grid; + + .title { + font-size: .8125rem; + margin-bottom: .5rem; + } + + .item { + padding: 10px; + box-shadow: .25rem .25rem 1rem rgba(0, 0, 0, .08); + border-radius: 1rem; + + .avatar { + width: 2rem; + height: 2rem; + flex: none; + border-radius: .5rem; + } + + &::after { + display: none; + } + + .content { + display: flex; + height: 2rem; + align-items: center; + justify-content: space-between; + + .name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .unit { + font-size: 1rem; + } + } + } + } } .file-input { @@ -392,6 +584,13 @@ watch(config, value => { } } +:deep(.package-purchase-form .avatar .t-image__img) { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: inherit; +} + .selected-customer-popup { height: 70vh; overflow: auto; diff --git a/src/pages/package/usePackagePurchase.ts b/src/pages/package/usePackagePurchase.ts index 278ebaf..e5a2bfe 100644 --- a/src/pages/package/usePackagePurchase.ts +++ b/src/pages/package/usePackagePurchase.ts @@ -1,7 +1,7 @@ import { computed, ref, type Ref } from "vue"; import PackageAPI from "@/api/PackageAPI"; import type { Customer } from "@/types/Customer"; -import type { GaoCustomerPackage, GaoPackageConfig, PackagePurchaseItem } from "@/types/Package"; +import type { GaoCustomerPackage, GaoPackageConfig, PackageAttachment, PackagePurchaseItem } from "@/types/Package"; import { createPackageRequestNo, formatPackageAmountInput, normalizePackageQuantity, parsePackageAmount } from "@/utils/package"; import { permissionCodes } from "@/utils/permission"; import { Message } from "@/utils/message"; @@ -12,6 +12,7 @@ interface UsePackagePurchaseOptions { note: Ref; hasPermission: (permissionCode: string) => boolean; buildRegisteredAt: () => number; + buildAttachments: () => PackageAttachment[]; } /** 套餐配置直接购买流程,购买请求只依赖套餐配置 ID,不再关联购买事件。 */ @@ -76,6 +77,7 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) { customerId, saleAmount, purchasedAt: options.buildRegisteredAt(), + attachmentList: options.buildAttachments(), itemList: packagePurchaseItemList.value.map((item, index) => ({ productId: item.productId, productUnitId: item.productUnitId,