| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpSaleOrderApi } from '#/api/erp/sale/order'; |
| | | |
| | | import { computed, nextTick, ref } from 'vue'; |
| | | |
| | | import { Barcode, BarcodeFormatEnum } from '@vben/common-ui'; |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictLabel } from '@vben/hooks'; |
| | | import { useUserStore } from '@vben/stores'; |
| | | import { erpPriceMultiply, formatDateTime } from '@vben/utils'; |
| | | |
| | | import { getCustomerSimpleList } from '#/api/crm/customer'; |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | | import { getSaleOrder } from '#/api/erp/sale/order'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | |
| | | /** |
| | | * éå®è®¢åæå°ç»ä»¶ |
| | | * |
| | | * ç¨äºå¯¼åº/æå°éå®è®¢å详ç»ä¿¡æ¯ï¼ä¾ä¼ä¸çç« çåã |
| | | * æå°æ ·å¼åè WMS åºåºåæå°ï¼wms/order/shipment/modules/print.vueï¼ã |
| | | */ |
| | | defineOptions({ name: 'ErpSaleOrderPrint' }); |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | const printData = ref<Partial<ErpSaleOrderApi.SaleOrder>>({}); |
| | | /** éå®äººåæµç§°æ å° */ |
| | | const saleUserMap = ref<Map<number, string>>(new Map()); |
| | | /** ç»ç®è´¦æ·åç§°æ å° */ |
| | | const accountMap = ref<Map<number, string>>(new Map()); |
| | | /** 客æ·åç§°æ å°ï¼è¯¦æ
æ¥å£ä¸è¿å客æ·åç§°ï¼éæå®¢æ·ç¼å·è§£æï¼ */ |
| | | const customerMap = ref<Map<number, string>>(new Map()); |
| | | /** æå°æ¶é´ */ |
| | | const printTime = ref(''); |
| | | |
| | | /** 订åç¶æææ¡ï¼status 为空æ¶ä¸è°ç¨ getDictLabelï¼é¿å
å
é¨å¯¹ undefined æ§è¡ toString æ¥éï¼ */ |
| | | const statusText = computed(() => { |
| | | if (printData.value.status == null) { |
| | | return '-'; |
| | | } |
| | | const label = getDictLabel(DICT_TYPE.ERP_AUDIT_STATUS, printData.value.status); |
| | | return label || '-'; |
| | | }); |
| | | |
| | | /** åºåºç¶æææ¡ */ |
| | | const outStatusText = computed(() => { |
| | | switch (printData.value.outStatus) { |
| | | case 0: |
| | | return 'æªåºåº'; |
| | | case 1: |
| | | return 'å·²é¢ç'; |
| | | case 2: |
| | | return 'é¨ååºåº'; |
| | | case 3: |
| | | return 'å
¨é¨åºåº'; |
| | | default: |
| | | return '-'; |
| | | } |
| | | }); |
| | | |
| | | /** çäº§ç¶æææ¡ */ |
| | | const productionStatusText = computed(() => { |
| | | if (printData.value.needProduction !== 1) { |
| | | return 'æ éç产'; |
| | | } |
| | | return printData.value.productionStatus === 1 ? '已宿' : 'æªå®æ'; |
| | | }); |
| | | |
| | | /** éå®äººååç§° */ |
| | | const saleUserName = computed( |
| | | () => saleUserMap.value.get(printData.value.saleUserId!) || '-', |
| | | ); |
| | | |
| | | /** ç»ç®è´¦æ·åç§° */ |
| | | const accountName = computed( |
| | | () => accountMap.value.get(printData.value.accountId!) || '-', |
| | | ); |
| | | |
| | | /** 客æ·åç§°ï¼ä¼å
å详æ
è¿åç客æ·åç§°ï¼å¦åæå®¢æ·ç¼å·è§£æ */ |
| | | const customerName = computed( |
| | | () => |
| | | printData.value.customerName || |
| | | customerMap.value.get(printData.value.customerId!) || |
| | | '-', |
| | | ); |
| | | |
| | | /** å建人åç§°ï¼è¯¦æ
æ¥å£ä¸è¿åå建人åç§°ï¼æå建人ç¼å·è§£æ */ |
| | | const creatorName = computed( |
| | | () => |
| | | printData.value.creatorName || |
| | | saleUserMap.value.get(Number(printData.value.creator)) || |
| | | '-', |
| | | ); |
| | | |
| | | /** åä»· à æ°éï¼ä»»ä¸ä¸ºç©ºè¿å undefinedï¼ */ |
| | | function multiplyPrice(price?: number, count?: number) { |
| | | return price == null || count == null ? undefined : erpPriceMultiply(price, count); |
| | | } |
| | | |
| | | /** |
| | | * æç»è¡ï¼å端ä¸è¿åæç»è¡ç totalProductPrice/totalPriceï¼éæåä»·Ãæ°é计ç®ï¼ |
| | | * éé¢ = åä»· à æ°éï¼ç¨é¢ = éé¢ Ã ç¨çï¼å«ç¨éé¢ = éé¢ + ç¨é¢ï¼ |
| | | */ |
| | | const printRows = computed(() => |
| | | (printData.value.items || []).map((item) => { |
| | | const totalProductPrice = |
| | | item.totalProductPrice ?? multiplyPrice(item.productPrice, item.count) ?? 0; |
| | | const taxPrice = |
| | | item.taxPrice ?? multiplyPrice(totalProductPrice, (item.taxPercent ?? 0) / 100) ?? 0; |
| | | const totalPrice = item.totalPrice ?? totalProductPrice + taxPrice; |
| | | return { ...item, totalProductPrice, taxPrice, totalPrice }; |
| | | }), |
| | | ); |
| | | |
| | | /** æç»åè®¡ï¼æ°é/éé¢ä¼å
å订å头ï¼å端æå¨å¼ï¼ï¼ç¨é¢ä¸å«ç¨é颿æç»ç´¯å ï¼è®¢å头 totalPrice ä¸ºä¼æ åéé¢ï¼å¦å¨éé¢ä¿¡æ¯åºå±ç¤ºï¼ */ |
| | | const summaries = computed(() => { |
| | | const rows = printRows.value; |
| | | const count = rows.reduce((sum, item) => sum + (item.count || 0), 0); |
| | | const totalProductPrice = rows.reduce( |
| | | (sum, item) => sum + (item.totalProductPrice || 0), |
| | | 0, |
| | | ); |
| | | const taxPrice = rows.reduce((sum, item) => sum + (item.taxPrice || 0), 0); |
| | | const totalPrice = rows.reduce((sum, item) => sum + (item.totalPrice || 0), 0); |
| | | return { |
| | | count: printData.value.totalCount ?? count, |
| | | totalProductPrice: printData.value.totalProductPrice ?? totalProductPrice, |
| | | taxPrice, |
| | | totalPrice, |
| | | }; |
| | | }); |
| | | |
| | | /** ä¿ç两ä½å°æ°ï¼éé¢å±ç¤ºï¼ */ |
| | | function formatPrice(value?: number | null) { |
| | | return value === undefined || value === null || Number.isNaN(Number(value)) |
| | | ? '-' |
| | | : Number(value).toFixed(2); |
| | | } |
| | | |
| | | /** ä¿çä¸ä½å°æ°ï¼æ°éå±ç¤ºï¼ */ |
| | | function formatCount(value?: number | null) { |
| | | return value === undefined || value === null || Number.isNaN(Number(value)) |
| | | ? '-' |
| | | : Number(value).toFixed(3); |
| | | } |
| | | |
| | | /** çå¾
æ¡ç åæå° DOM 宿ç»å¶ï¼é¿å
æµè§å¨æå°å°æ§å
容 */ |
| | | function waitForPaint() { |
| | | return new Promise<void>((resolve) => { |
| | | requestAnimationFrame(() => { |
| | | requestAnimationFrame(() => resolve()); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | /** éåºæå°æ¨¡å¼ï¼æ¢å¤å½å页颿¾ç¤º */ |
| | | function removePrintMode() { |
| | | document.body.classList.remove('erp-sale-order-printing'); |
| | | } |
| | | |
| | | /** æå°éå®è®¢åï¼å è½½æ°æ®ååªå±ç¤ºæå°åºåï¼åè°ç¨æµè§å¨æå° */ |
| | | async function print(id: number) { |
| | | const [order, userList, accountList, customerList] = await Promise.all([ |
| | | getSaleOrder(id), |
| | | getSimpleUserList(), |
| | | getAccountSimpleList(), |
| | | getCustomerSimpleList(), |
| | | ]); |
| | | printData.value = order; |
| | | saleUserMap.value = new Map( |
| | | userList.map((item) => [Number(item.id), item.nickname || item.username]), |
| | | ); |
| | | accountMap.value = new Map( |
| | | accountList.map((item) => [Number(item.id), item.name]), |
| | | ); |
| | | customerMap.value = new Map( |
| | | customerList.map((item) => [Number(item.id), item.name]), |
| | | ); |
| | | printTime.value = formatDateTime(new Date()); |
| | | await nextTick(); |
| | | await waitForPaint(); |
| | | document.body.classList.add('erp-sale-order-printing'); |
| | | window.addEventListener('afterprint', removePrintMode, { once: true }); |
| | | window.print(); |
| | | } |
| | | |
| | | defineExpose({ print }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Teleport to="body"> |
| | | <div |
| | | id="erpSaleOrderPrint" |
| | | class="erp-sale-order-print pointer-events-none fixed left-0 top-0 z-[-1] w-full bg-white text-[#303133] opacity-0" |
| | | > |
| | | <div class="relative mb-2"> |
| | | <h2 class="m-0 text-center text-[1.6em] font-bold leading-[1.2]"> |
| | | éå®è®¢å |
| | | </h2> |
| | | <div v-if="printData.no" class="absolute right-0 top-0"> |
| | | <Barcode |
| | | :content="printData.no" |
| | | :display-value="false" |
| | | :format="BarcodeFormatEnum.CODE39" |
| | | :height="40" |
| | | :width="180" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <div |
| | | class="mb-3 grid grid-cols-3 gap-x-6 gap-y-2 border border-solid border-[#dcdfe6] p-3 text-sm leading-[1.6]" |
| | | > |
| | | <div>订ååå·ï¼{{ printData.no || '-' }}</div> |
| | | <div> |
| | | è®¢åæ¶é´ï¼{{ |
| | | formatDateTime(printData.orderTime) || '-' |
| | | }} |
| | | </div> |
| | | <div>客æ·ï¼{{ customerName }}</div> |
| | | <div>éå®äººåï¼{{ saleUserName }}</div> |
| | | <div>å
³èååï¼{{ printData.contractNo || '-' }}</div> |
| | | <div>ç»ç®è´¦æ·ï¼{{ accountName }}</div> |
| | | <div>订åç¶æï¼{{ statusText }}</div> |
| | | <div>åºåºç¶æï¼{{ outStatusText }}</div> |
| | | <div>çäº§ç¶æï¼{{ productionStatusText }}</div> |
| | | <div>å建人ï¼{{ creatorName }}</div> |
| | | <div> |
| | | å建æ¶é´ï¼{{ |
| | | formatDateTime(printData.createTime) || '-' |
| | | }} |
| | | </div> |
| | | <div class="col-span-3">夿³¨ï¼{{ printData.remark || '-' }}</div> |
| | | </div> |
| | | |
| | | <!-- 产åæç» --> |
| | | <table class="w-full border-collapse text-[13px] leading-[1.5]"> |
| | | <thead> |
| | | <tr> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold" |
| | | > |
| | | åºå· |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold" |
| | | > |
| | | 产ååç§°åè§æ ¼ |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold" |
| | | > |
| | | åä½ |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |
| | | > |
| | | æ°é |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |
| | | > |
| | | 产ååä»·(å
) |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |
| | | > |
| | | éé¢(å
) |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |
| | | > |
| | | ç¨ç(%) |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |
| | | > |
| | | ç¨é¢(å
) |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |
| | | > |
| | | å«ç¨éé¢(å
) |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold" |
| | | > |
| | | 夿³¨ |
| | | </th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="(item, index) in printRows" :key="item.id || index"> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ index + 1 }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | <div>{{ item.productName || '-' }}</div> |
| | | <div v-if="item.productSpecification" class="text-xs"> |
| | | {{ item.productSpecification }} |
| | | </div> |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ item.productUnitName || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-right"> |
| | | {{ formatCount(item.count) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-right"> |
| | | {{ formatPrice(item.productPrice) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-right"> |
| | | {{ formatPrice(item.totalProductPrice) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-right"> |
| | | {{ formatPrice(item.taxPercent) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-right"> |
| | | {{ formatPrice(item.taxPrice) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-right"> |
| | | {{ formatPrice(item.totalPrice) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ item.remark || '-' }} |
| | | </td> |
| | | </tr> |
| | | <tr v-if="printData.items && printData.items.length > 0"> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center" |
| | | > |
| | | å计 |
| | | </td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2" |
| | | ></td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2" |
| | | ></td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right" |
| | | > |
| | | {{ formatCount(summaries.count) }} |
| | | </td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2" |
| | | ></td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right" |
| | | > |
| | | {{ formatPrice(summaries.totalProductPrice) }} |
| | | </td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2" |
| | | ></td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right" |
| | | > |
| | | {{ formatPrice(summaries.taxPrice) }} |
| | | </td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right" |
| | | > |
| | | {{ formatPrice(summaries.totalPrice) }} |
| | | </td> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2" |
| | | ></td> |
| | | </tr> |
| | | <tr v-if="printRows.length === 0"> |
| | | <td |
| | | class="border border-solid border-[#dcdfe6] p-2 text-center" |
| | | colspan="10" |
| | | > |
| | | ææ æç» |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | |
| | | <!-- éé¢ä¿¡æ¯ --> |
| | | <div |
| | | class="mt-3 grid grid-cols-4 gap-x-6 gap-y-2 border border-solid border-[#dcdfe6] p-3 text-sm leading-[1.6]" |
| | | > |
| | | <div>伿 ç(%)ï¼{{ formatPrice(printData.discountPercent) }}</div> |
| | | <div>仿¬¾ä¼æ (å
)ï¼{{ formatPrice(printData.discountPrice) }}</div> |
| | | <div>伿 åéé¢(å
)ï¼{{ formatPrice(printData.totalPrice) }}</div> |
| | | <div>æ¶å订é(å
)ï¼{{ formatPrice(printData.depositPrice) }}</div> |
| | | </div> |
| | | |
| | | <!-- çç« æ --> |
| | | <div |
| | | class="mt-6 grid grid-cols-2 gap-x-10 gap-y-4 border border-dashed border-[#dcdfe6] p-4 text-sm leading-[1.8]" |
| | | > |
| | | <div> |
| | | 客æ·çç« /ç¾åï¼<span class="inline-block w-36 border-b border-solid border-[#303133]"></span> |
| | | </div> |
| | | <div> |
| | | å
¬å¸çç« /ç¾åï¼<span class="inline-block w-36 border-b border-solid border-[#303133]"></span> |
| | | </div> |
| | | <div> |
| | | æ¥æï¼<span class="inline-block w-36 border-b border-solid border-[#303133]"></span> |
| | | </div> |
| | | <div> |
| | | æ¥æï¼<span class="inline-block w-36 border-b border-solid border-[#303133]"></span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æå°ä¿¡æ¯ --> |
| | | <div |
| | | class="mt-4 flex justify-between text-xs text-[#909399]" |
| | | > |
| | | <div> |
| | | æå°äººåï¼{{ userStore.userInfo?.nickname || '-' }} |
| | | </div> |
| | | <div>æå°æ¶é´ï¼{{ printTime || '-' }}</div> |
| | | </div> |
| | | </div> |
| | | </Teleport> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | @page { |
| | | margin: 8mm 10mm; |
| | | } |
| | | |
| | | @media print { |
| | | :global(body.erp-sale-order-printing) { |
| | | padding: 0 !important; |
| | | margin: 0 !important; |
| | | -webkit-print-color-adjust: exact; |
| | | print-color-adjust: exact; |
| | | } |
| | | |
| | | :global(body.erp-sale-order-printing *) { |
| | | visibility: hidden !important; |
| | | } |
| | | |
| | | :global(body.erp-sale-order-printing .erp-sale-order-print), |
| | | :global(body.erp-sale-order-printing .erp-sale-order-print *) { |
| | | visibility: visible !important; |
| | | } |
| | | |
| | | :global(body.erp-sale-order-printing .erp-sale-order-print) { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | z-index: auto; |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | padding: 0 !important; |
| | | margin: 0 !important; |
| | | pointer-events: auto; |
| | | opacity: 1; |
| | | } |
| | | } |
| | | </style> |