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/salesDeliveryPrint.js |  276 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 276 insertions(+), 0 deletions(-)

diff --git a/src/views/salesManagement/salesLedger/components/salesDeliveryPrint.js b/src/views/salesManagement/salesLedger/components/salesDeliveryPrint.js
new file mode 100644
index 0000000..e23fd53
--- /dev/null
+++ b/src/views/salesManagement/salesLedger/components/salesDeliveryPrint.js
@@ -0,0 +1,276 @@
+const PRINT_TITLE = "閿�鍞彂璐у崟";
+
+const escapeHtml = (value) =>
+  String(value ?? "")
+    .replaceAll("&", "&amp;")
+    .replaceAll("<", "&lt;")
+    .replaceAll(">", "&gt;")
+    .replaceAll('"', "&quot;")
+    .replaceAll("'", "&#39;");
+
+const toNumber = (value) => {
+  const num = Number(value);
+  return Number.isFinite(num) ? num : 0;
+};
+
+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 getItemArea = (item) => toNumber(item?.area || item?.settleTotalArea || item?.actualTotalArea);
+
+const getOrderNo = (data, row, item) =>
+  item?.salesContractNo || item?.orderNo || data?.salesContractNo || row?.salesContractNo || "";
+
+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 normalizeInvoiceData = (raw, selectedRow) => {
+  const data = raw ?? {};
+  const groups = Array.isArray(data.groups) ? data.groups : [];
+  if (!groups.length) return data;
+
+  const items = groups.flatMap((group) =>
+    (Array.isArray(group?.items) ? group.items : []).map((item) => ({
+      ...item,
+      productDescription: group?.productName || item?.productDescription || "",
+      salesContractNo: group?.salesContractNo || item?.salesContractNo || "",
+      widthHeight: item?.widthHeight || "",
+    }))
+  );
+
+  return {
+    ...data,
+    items,
+    customerName: data.customerName || selectedRow?.customerName || "",
+    contactPerson: data.contactPerson || selectedRow?.contactPerson || "",
+    contactPhone: data.contactPhone || selectedRow?.contactPhone || "",
+    deliveryAddress:
+      data.companyAddress || data.deliveryAddress || data.shippingAddress || selectedRow?.deliveryAddress || "",
+    shipmentNo: data.externalOrderNo || data.shipmentNo || "",
+    register: data.orderMaker || data.register || selectedRow?.entryPersonName || "",
+    registerDate: data.executionDate || data.registerDate || data.entryDate || selectedRow?.entryDate || "",
+  };
+};
+
+const groupByProduct = (items, data, row) => {
+  const list = Array.isArray(items) ? items : [];
+  const map = new Map();
+  list.forEach((item) => {
+    const key = `${item?.productDescription || ""}__${getOrderNo(data, row, item)}`;
+    if (!map.has(key)) {
+      map.set(key, {
+        productName: item?.productDescription || "",
+        orderNo: getOrderNo(data, row, item),
+        items: [],
+      });
+    }
+    map.get(key).items.push(item);
+  });
+  return Array.from(map.values());
+};
+
+const renderItemRows = (items, startIndex) =>
+  items
+    .map((item, idx) => {
+      const sizeText = item?.widthHeight
+        ? escapeHtml(item.widthHeight)
+        : item?.width || item?.height
+          ? `${escapeHtml(item?.width)} * ${escapeHtml(item?.height)}`
+          : "";
+      return `
+      <tr>
+        <td>${startIndex + idx + 1}</td>
+        <td class="left">${escapeHtml(item?.floorCode)}</td>
+        <td>${sizeText}</td>
+        <td>${toNumber(item?.quantity) || ""}</td>
+        <td>${getItemArea(item) ? getItemArea(item).toFixed(2) : ""}</td>
+        <td class="left">${escapeHtml(item?.remark)}</td>
+        <td class="left">${escapeHtml(item?.processRequirement)}</td>
+      </tr>
+    `;
+    })
+    .join("");
+
+export const printSalesDeliveryNote = (rawData, selectedRow = {}) => {
+  const data = normalizeInvoiceData(rawData, selectedRow);
+  const allItems = Array.isArray(data.items) ? data.items : [];
+  const pageSize = 18;
+  const itemPages = splitItemsByPage(allItems, pageSize);
+  const totalPages = itemPages.length;
+
+  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: #222; }
+      .page { width: 198mm; margin: 0 auto; padding: 4mm 4mm 6mm; box-sizing: border-box; page-break-after: always; }
+      .page:last-child { page-break-after: auto; }
+      .head-top {
+        display: grid;
+        grid-template-columns: 1fr auto 1fr;
+        align-items: end;
+        margin-bottom: 1px;
+      }
+      .factory {
+        grid-column: 2;
+        text-align: center;
+        font-size: 20px;
+        font-weight: 700;
+        line-height: 1.2;
+      }
+      .page-mark {
+        grid-column: 3;
+        justify-self: end;
+        font-size: 12px;
+        margin-right: 8mm;
+        margin-bottom: 1px;
+      }
+      .head-mid {
+        display: grid;
+        grid-template-columns: 1fr auto 1fr;
+        align-items: center;
+        margin-bottom: 2px;
+      }
+      .head-mid-left { font-size: 13px; text-align: left; }
+      .head-mid-title { font-size: 20px; font-weight: 700; text-align: center; }
+      .head-mid-right { font-size: 13px; text-align: right; padding-right: 8mm; }
+      table { width: 100%; border-collapse: collapse; table-layout: fixed; border: 1px solid #222; }
+      td, th { border: 1px solid #222; padding: 2px 4px; font-size: 13px; text-align: center; vertical-align: middle; }
+      .left { text-align: left; }
+      .group-title td { font-weight: 700; }
+      .subtotal td, .total-row td { font-weight: 700; }
+      .empty td { height: 120px; color: #666; }
+      .footer { margin-top: 6px; display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 6px; font-size: 13px; }
+      @media print {
+        @page { size: A4 portrait; margin: 8mm; }
+        .page { width: 100%; margin: 0; padding: 0; }
+      }
+    </style>
+  </head>
+  <body>
+  ${itemPages
+    .map((pageItems, pageIndex) => {
+      const pageGroups = groupByProduct(pageItems, data, selectedRow);
+      let serial = pageIndex * pageSize;
+      const totalQty = toNumber(data.totalQuantity) || allItems.reduce((s, it) => s + toNumber(it?.quantity), 0);
+      const totalArea = toNumber(data.totalArea) || allItems.reduce((s, it) => s + getItemArea(it), 0);
+      return `
+    <div class="page">
+      <div class="head-top">
+        <div></div>
+        <div class="factory">楣ゅ澶╂矏閽㈠寲鐜荤拑鍘�</div>
+        <div class="page-mark">绗�${pageIndex + 1}椤�,鍏�${totalPages}椤�</div>
+      </div>
+      <div class="head-mid">
+        <div class="head-mid-left">瀵规柟鍗曞彿: ${escapeHtml(data.deliveryNo || data.shippingNo || selectedRow.expressNumber || "")}</div>
+        <div class="head-mid-title">閿�鍞彂璐у崟</div>
+        <div class="head-mid-right">鍙戣揣鍗曞彿: ${escapeHtml(data.shipmentNo || data.deliveryNo || "")}</div>
+      </div>
+      <table>
+        <tr>
+          <td class="left" colspan="4">瀹㈡埛鍚嶇О: ${escapeHtml(data.customerName || selectedRow.customerName || "")}</td>
+          <td class="left" colspan="3">鑱旂郴浜�: ${escapeHtml(data.contactPerson || selectedRow.contactPerson || "")}</td>
+        </tr>
+        <tr>
+          <td class="left" colspan="4">鍙戣揣鍦板潃: ${escapeHtml(data.deliveryAddress || data.shippingAddress || selectedRow.deliveryAddress || "")}</td>
+          <td class="left" colspan="3">鑱旂郴鐢佃瘽: ${escapeHtml(data.contactPhone || selectedRow.contactPhone || "")}</td>
+        </tr>
+        <tr>
+          <th style="width:8%;">搴忓彿</th>
+          <th style="width:22%;">妤煎眰缂栧彿</th>
+          <th style="width:20%;">瀹�(寮ч暱)*楂�</th>
+          <th style="width:10%;">鏁伴噺</th>
+          <th style="width:12%;">闈㈢Н</th>
+          <th style="width:10%;">澶囨敞</th>
+          <th style="width:18%;">鍔犲伐瑕佹眰</th>
+        </tr>
+        ${
+          pageGroups.length
+            ? pageGroups
+                .map((group) => {
+                  const subQty = group.items.reduce((s, it) => s + toNumber(it?.quantity), 0);
+                  const subArea = group.items.reduce((s, it) => s + getItemArea(it), 0);
+                  const rows = renderItemRows(group.items, serial);
+                  serial += group.items.length;
+                  return `
+          <tr class="group-title">
+            <td colspan="5" class="left">浜у搧鍚嶇О: ${escapeHtml(group.productName)}</td>
+            <td colspan="2" class="left">璁㈠崟缂栧彿: ${escapeHtml(group.orderNo)}</td>
+          </tr>
+          ${rows}
+          <tr class="subtotal">
+            <td colspan="3">灏忚:</td>
+            <td>${subQty || ""}</td>
+            <td>${subArea ? subArea.toFixed(2) : ""}</td>
+            <td colspan="2"></td>
+          </tr>
+                  `;
+                })
+                .join("")
+            : `<tr class="empty"><td colspan="7">鏆傛棤鏄庣粏</td></tr>`
+        }
+        ${
+          pageIndex === totalPages - 1
+            ? `
+        <tr class="total-row">
+          <td colspan="3">鍚堣:</td>
+          <td>${totalQty || ""}</td>
+          <td>${totalArea ? totalArea.toFixed(2) : ""}</td>
+          <td colspan="2"></td>
+        </tr>
+            `
+            : ""
+        }
+      </table>
+      ${
+        pageIndex === totalPages - 1
+          ? `
+      <div class="footer">
+        <div>鍒� 鍗� 鍛�: ${escapeHtml(data.register || selectedRow.entryPersonName || "")}</div>
+        <div>鍒跺崟鏃ユ湡: ${escapeHtml(formatDisplayDate(data.registerDate || data.entryDate || selectedRow.entryDate))}</div>
+        <div>瀹㈡埛绛惧瓧:</div>
+        <div>绛炬敹鏃ユ湡:</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