From 53e0b9466d3fdd3e5caf7c42e476fffdb468bc2a Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期五, 27 三月 2026 17:17:22 +0800
Subject: [PATCH] 1
---
src/views/salesManagement/salesLedger/components/salesOrderPrint.js | 443 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 443 insertions(+), 0 deletions(-)
diff --git a/src/views/salesManagement/salesLedger/components/salesOrderPrint.js b/src/views/salesManagement/salesLedger/components/salesOrderPrint.js
new file mode 100644
index 0000000..8e82a98
--- /dev/null
+++ b/src/views/salesManagement/salesLedger/components/salesOrderPrint.js
@@ -0,0 +1,443 @@
+const PRINT_TITLE = "閿�鍞鍗�";
+
+const formatDisplayDate = (value) => {
+ if (!value) return "";
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) return String(value);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ return `${year}/${month}/${day}`;
+};
+
+const getCurrentDateTime = () => {
+ const date = new Date();
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ return `${year}/${month}/${day}`;
+};
+
+const escapeHtml = (value) =>
+ String(value ?? "")
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """)
+ .replaceAll("'", "'");
+
+const toNumber = (value) => {
+ const num = Number(value);
+ return Number.isFinite(num) ? num : 0;
+};
+
+const formatMoney = (value) => {
+ const num = toNumber(value);
+ return num.toFixed(2);
+};
+
+const formatArea = (value) => {
+ const num = toNumber(value);
+ return num ? num.toFixed(2) : "";
+};
+
+const formatQty = (value) => {
+ const num = toNumber(value);
+ return num ? String(num) : "";
+};
+
+const getItemAmount = (item) => {
+ const fromPrice = toNumber(item?.taxInclusiveUnitPrice) * toNumber(item?.quantity);
+ const amount = toNumber(item?.taxInclusiveTotalPrice || item?.amount || fromPrice);
+ return amount;
+};
+
+const getItemArea = (item) =>
+ toNumber(item?.area || item?.settleTotalArea || item?.actualTotalArea);
+
+const getDeliveryAddress = (data) =>
+ data?.companyAddress || data?.deliveryAddress || data?.shippingAddress || data?.address || data?.shipAddress || "";
+
+const normalizeRequirementText = (value) => {
+ const text = String(value ?? "").trim();
+ if (!text) return "";
+ return text
+ .replace(/^鍔犲伐瑕佹眰鍜屽娉╗:锛歖\s*/g, "")
+ .replace(/^鍔犲伐瑕佹眰[:锛歖\s*/g, "")
+ .replace(/^澶囨敞[:锛歖\s*/g, "");
+};
+
+const extractOtherFees = (data, items) => {
+ const source = [];
+ if (Array.isArray(data?.otherFees)) source.push(...data.otherFees);
+ if (Array.isArray(data?.otherAmounts)) source.push(...data.otherAmounts);
+ if (Array.isArray(data?.otherAmountList)) source.push(...data.otherAmountList);
+ if (Array.isArray(data?.otherAmountProjects)) source.push(...data.otherAmountProjects);
+ if (Array.isArray(data?.salesProductProcessList)) source.push(...data.salesProductProcessList);
+ (Array.isArray(items) ? items : []).forEach((item) => {
+ if (Array.isArray(item?.salesProductProcessList)) source.push(...item.salesProductProcessList);
+ if (Array.isArray(item?.otherAmounts)) source.push(...item.otherAmounts);
+ });
+
+ const map = new Map();
+ source.forEach((fee) => {
+ const name = String(fee?.feeName || fee?.processName || fee?.name || fee?.itemName || "").trim();
+ if (!name) return;
+ const quantity = toNumber(fee?.quantity || fee?.num);
+ const unitPrice = toNumber(fee?.unitPrice || fee?.price);
+ const amount =
+ toNumber(fee?.amount || fee?.totalPrice) || (quantity && unitPrice ? quantity * unitPrice : 0);
+ const key = name;
+ if (!map.has(key)) {
+ map.set(key, { name, quantity: 0, unitPrice: 0, amount: 0 });
+ }
+ const row = map.get(key);
+ row.quantity += quantity;
+ row.unitPrice = unitPrice || row.unitPrice;
+ row.amount += amount;
+ });
+ return Array.from(map.values());
+};
+
+const renderOtherFeeRows = (rows) => {
+ const list = Array.isArray(rows) ? rows : [];
+ if (list.length === 0) {
+ return `<tr><td></td><td></td><td></td><td></td></tr>`;
+ }
+ return list
+ .map((row) => {
+ const name = escapeHtml(row.name);
+ const unitPrice = row.unitPrice ? formatMoney(row.unitPrice) : "";
+ const quantity = row.quantity ? String(row.quantity) : "";
+ const amount = row.amount ? formatMoney(row.amount) : "";
+ return `<tr><td class="other-fee-shift-left">${name}</td><td class="other-fee-shift-left">${unitPrice}</td><td class="other-fee-shift-left">${quantity}</td><td>${amount}</td></tr>`;
+ })
+ .join("");
+};
+
+const splitItemsByPage = (items, pageSize) => {
+ const list = Array.isArray(items) ? items : [];
+ if (list.length === 0) return [[]];
+ const pages = [];
+ for (let i = 0; i < list.length; i += pageSize) {
+ pages.push(list.slice(i, i + pageSize));
+ }
+ return pages;
+};
+
+const renderRows = (items, startIndex) => {
+ const list = Array.isArray(items) ? items : [];
+ if (list.length === 0) {
+ return `
+ <tr>
+ <td colspan="8" class="empty">鏆傛棤鏄庣粏</td>
+ </tr>
+ `;
+ }
+ return list
+ .map((item, idx) => {
+ const width = escapeHtml(item?.width);
+ const height = escapeHtml(item?.height);
+ const sizeText = width || height ? `${width}*${height}` : "";
+ const unitPrice = formatMoney(item?.taxInclusiveUnitPrice || item?.unitPrice);
+ const amount = formatMoney(getItemAmount(item));
+ return `
+ <tr>
+ <td>${startIndex + idx + 1}</td>
+ <td>${escapeHtml(item?.floorCode)}</td>
+ <td>${sizeText}</td>
+ <td>${formatQty(item?.quantity)}</td>
+ <td>${formatArea(item?.area || item?.settleTotalArea || item?.actualTotalArea)}</td>
+ <td>${unitPrice}</td>
+ <td>${amount}</td>
+ <td>${escapeHtml(item?.processRequirement)}</td>
+ </tr>
+ `;
+ })
+ .join("");
+};
+
+export const printSalesOrder = (orderData) => {
+ const data = orderData ?? {};
+ const items = Array.isArray(data.items) ? data.items : [];
+ const pageSize = 15;
+ const pages = splitItemsByPage(items, pageSize);
+ const totalPages = pages.length;
+
+ const subtotalQuantity = toNumber(data.subtotalQuantity);
+ const subtotalArea = toNumber(data.subtotalArea);
+ const subtotalAmount = toNumber(data.subtotalAmount);
+ const totalQuantity = toNumber(data.totalQuantity) || items.reduce((sum, item) => sum + toNumber(item?.quantity), 0);
+ const totalArea = toNumber(data.totalArea) || items.reduce((sum, item) => sum + getItemArea(item), 0);
+ const totalAmount = toNumber(data.totalAmount) || items.reduce((sum, item) => sum + getItemAmount(item), 0);
+ const otherFees = extractOtherFees(data, items);
+
+ const printWindow = window.open("", "_blank", "width=1200,height=900");
+ if (!printWindow) {
+ throw new Error("娴忚鍣ㄦ嫤鎴簡寮圭獥锛岃鍏佽寮圭獥鍚庨噸璇�");
+ }
+
+ const html = `
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <title>${PRINT_TITLE}</title>
+ <style>
+ body { margin: 0; padding: 0; font-family: "SimSun", serif; color: #111; }
+ .page {
+ width: 198mm;
+ min-height: 186mm;
+ margin: 0 auto;
+ padding: 5mm 4mm 16mm;
+ box-sizing: border-box;
+ page-break-after: always;
+ position: relative;
+ }
+ .page:last-child { page-break-after: auto; }
+ .title-main { text-align: center; font-size: 22px; font-weight: 700; letter-spacing: 1px; line-height: 1.15; margin-top: 1mm; }
+ .title-sub { text-align: center; font-size: 22px; font-weight: 700; letter-spacing: 6px; line-height: 1.15; margin: 1mm 0 4mm; }
+ table { width: 100%; border-collapse: collapse; table-layout: fixed; border: 1px solid #222; }
+ .detail-table {
+ border-right: 1px solid #222 !important;
+ }
+ .detail-table-wrap {
+ position: relative;
+ margin-top: -1px;
+ }
+ .detail-table-wrap::after {
+ content: none;
+ }
+ .sheet-wrap {
+ position: relative;
+ overflow: visible;
+ }
+ .sheet-wrap::after {
+ content: none;
+ }
+ td, th { border: 1px solid #222; padding: 2px 4px; font-size: 13px; text-align: center; vertical-align: middle; }
+ tr > td:first-child, tr > th:first-child { border-left: 1px solid #222 !important; }
+ tr > td:last-child, tr > th:last-child { border-right: 1px solid #222 !important; }
+ .left { text-align: left; }
+ .bold { font-weight: 700; }
+ .cell-title { font-weight: 700; white-space: nowrap; }
+ .product-row td { font-size: 13px; font-weight: 700; }
+ .empty { height: 140px; color: #777; }
+ .large-row td { height: 46px; vertical-align: top; }
+ .other-fee-content-row td { height: auto !important; }
+ .total-row td { font-weight: 700; font-size: 13px; line-height: 1.2; }
+ .footer { margin-top: 5px; display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; font-size: 13px; line-height: 1.7; }
+ .footer b { display: inline-block; min-width: 74px; }
+ .customer-sign-row {
+ margin-top: 6px;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ }
+ .customer-sign-cell { text-align: left; }
+ .customer-sign { font-size: 13px; font-weight: 700; line-height: 1.2; }
+ .right { text-align: right; }
+ .footer-page-right { display: block; text-align: right; font-size: 12px; }
+ .footer-pair {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ }
+ .other-fee-cell { white-space: normal; word-break: break-all; line-height: 1.35; vertical-align: top; }
+ .other-fee-header { font-weight: 700; }
+ .other-fee-header-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ align-items: center;
+ text-align: center;
+ }
+ .other-fee-header-row td { border-bottom: none !important; }
+ .other-fee-content-row td { border-top: none !important; }
+ .other-fee-inner {
+ width: 100%;
+ border-collapse: collapse;
+ table-layout: fixed;
+ border: none !important;
+ }
+ .other-fee-inner td {
+ border: none !important;
+ padding: 0 1px;
+ text-align: center;
+ vertical-align: middle;
+ white-space: normal;
+ word-break: break-all;
+ line-height: 1.1;
+ }
+ .other-fee-inner tr > td:first-child,
+ .other-fee-inner tr > td:last-child {
+ border-left: none !important;
+ border-right: none !important;
+ }
+ .other-fee-shift-left {
+ position: relative;
+ left: -30px;
+ }
+ @media print {
+ @page { size: A4 landscape; margin: 6mm; }
+ .page { width: 100%; margin: 0; padding: 0 0 14mm; min-height: 0; }
+ }
+ </style>
+ </head>
+ <body>
+ ${pages
+ .map((pageItems, pageIndex) => {
+ const isLastPage = pageIndex === totalPages - 1;
+ const startIndex = pageIndex * pageSize;
+ return `
+ <div class="page">
+ <div class="title-main">${escapeHtml(data.companyName || "楣ゅ澶╂矏閽㈠寲鐜荤拑鍘�")}</div>
+ <div class="title-sub">閿�鍞鍗�</div>
+
+ <div class="sheet-wrap">
+ <table>
+ <colgroup>
+ <col style="width: 14%;" />
+ <col style="width: 20%;" />
+ <col style="width: 20%;" />
+ <col style="width: 14%;" />
+ <col style="width: 32%;" />
+ </colgroup>
+ <tr>
+ <td class="cell-title">瀹㈡埛鍚嶇О:</td>
+ <td class="left">${escapeHtml(data.customerName)}</td>
+ <td class="cell-title">椤圭洰鍚嶇О:</td>
+ <td class="left">${escapeHtml(data.projectName)}</td>
+ <td class="left"><span class="cell-title">涓� 鍔� 鍛�:</span> ${escapeHtml(data.salesman)}</td>
+ </tr>
+ <tr>
+ <td class="cell-title">鍒跺崟鏃ユ湡:</td>
+ <td class="left">${escapeHtml(formatDisplayDate(data.executionDate || data.orderMakerDate || data.registerDate || data.entryDate))}</td>
+ <td class="cell-title">浜よ揣鏃ユ湡:</td>
+ <td class="left">${escapeHtml(formatDisplayDate(data.deliveryDate))}</td>
+ <td class="left"></td>
+ </tr>
+ <tr>
+ <td class="cell-title">閫佽揣鍦板潃:</td>
+ <td colspan="4" class="left">${escapeHtml(getDeliveryAddress(data))}</td>
+ </tr>
+ </table>
+
+ <div class="detail-table-wrap">
+ <table class="detail-table">
+ <colgroup>
+ <col style="width: 9%;" />
+ <col style="width: 12%;" />
+ <col style="width: 12%;" />
+ <col style="width: 7%;" />
+ <col style="width: 10%;" />
+ <col style="width: 8%;" />
+ <col style="width: 10%;" />
+ <col style="width: 32%;" />
+ </colgroup>
+ <tr>
+ <th>搴忓彿</th>
+ <th>妤煎眰缂栧彿</th>
+ <th>瀹�(寮ч暱)*楂�</th>
+ <th>鏁伴噺</th>
+ <th>缁撶畻闈㈢Н</th>
+ <th>鍗曚环</th>
+ <th>閲戦</th>
+ <th>鍔犲伐瑕佹眰</th>
+ </tr>
+ <tr class="product-row">
+ <td colspan="6" class="left">浜у搧鍚嶇О: ${escapeHtml(data.productName || items[0]?.productDescription)}</td>
+ <td colspan="2" class="left">璁㈠崟缂栧彿: ${escapeHtml(data.salesContractNo)}</td>
+ </tr>
+ ${renderRows(pageItems, startIndex)}
+ ${
+ isLastPage
+ ? `
+ <tr class="total-row">
+ <td colspan="3" class="left">灏忚:</td>
+ <td>${subtotalQuantity || totalQuantity || ""}</td>
+ <td>${subtotalArea ? subtotalArea.toFixed(2) : totalArea ? totalArea.toFixed(2) : ""}</td>
+ <td></td>
+ <td>${formatMoney(subtotalAmount || totalAmount)}</td>
+ <td></td>
+ </tr>
+ <tr class="total-row">
+ <td colspan="3" class="left">鍚堣:</td>
+ <td>${totalQuantity || ""}</td>
+ <td>${totalArea ? totalArea.toFixed(2) : ""}</td>
+ <td></td>
+ <td>${formatMoney(totalAmount)}</td>
+ <td></td>
+ </tr>
+ <tr class="other-fee-header-row">
+ <td colspan="5" class="left other-fee-header">
+ <div class="other-fee-header-grid">
+ <span>鍏朵粬璐圭敤</span>
+ <span>鍗曚环</span>
+ <span>鏁伴噺</span>
+ <span>閲戦</span>
+ </div>
+ </td>
+ <td colspan="3" class="left other-fee-header">鍔犲伐瑕佹眰鍜屽娉�:</td>
+ </tr>
+ <tr class="large-row other-fee-content-row">
+ <td colspan="5" class="left other-fee-cell">
+ <table class="other-fee-inner">
+ <colgroup>
+ <col style="width: 34%;" />
+ <col style="width: 22%;" />
+ <col style="width: 20%;" />
+ <col style="width: 24%;" />
+ </colgroup>
+ ${renderOtherFeeRows(otherFees)}
+ </table>
+ </td>
+ <td colspan="3" class="left other-fee-cell">${escapeHtml(normalizeRequirementText(data.remakes || data.remarks || data.orderProcessRequirement))}</td>
+ </tr>
+ <tr class="total-row">
+ <td colspan="8" class="left">鎬婚噾棰�: ${escapeHtml(data.totalAmountDisplay || `${formatMoney(totalAmount)}鍏僠)}</td>
+ </tr>
+ `
+ : `
+ <tr><td colspan="8" class="right">涓嬮〉缁�...</td></tr>
+ `
+ }
+ </table>
+ </div>
+ </div>
+
+ ${
+ isLastPage
+ ? `
+ <div class="customer-sign-row">
+ <span></span>
+ <span></span>
+ <span class="customer-sign customer-sign-cell">瀹㈡埛绛惧悕:</span>
+ </div>
+ <div class="footer">
+ <div><b>鍒跺崟鍛�:</b>${escapeHtml(data.orderMaker || data.register)}</div>
+ <div><b>瀹℃牳鍛�:</b>${escapeHtml(data.auditor)}</div>
+ <div><b>鎵撳嵃浜�:</b>${escapeHtml(data.printPeople || data.register)}</div>
+ <div><b>鍒跺崟鏃ユ湡:</b>${escapeHtml(formatDisplayDate(data.orderMakerDate || data.executionDate || data.registerDate || data.entryDate))}</div>
+ <div><b>瀹℃牳鏃ユ湡:</b>${escapeHtml(formatDisplayDate(data.auditDate))}</div>
+ <div class="footer-pair"><span><b>鎵撳嵃鏃堕棿:</b>${escapeHtml(data.printTime || getCurrentDateTime())}</span><span class="footer-page-right">绗�${pageIndex + 1}椤�,鍏�${totalPages}椤�</span></div>
+ </div>
+ `
+ : ""
+ }
+ </div>`;
+ })
+ .join("")}
+ </body>
+</html>
+`;
+
+ printWindow.document.write(html);
+ printWindow.document.close();
+ printWindow.onload = () => {
+ setTimeout(() => {
+ printWindow.focus();
+ printWindow.print();
+ printWindow.close();
+ }, 300);
+ };
+};
--
Gitblit v1.9.3