From 57e75ea91e7210d058627e7930efc75526dab622 Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期五, 22 五月 2026 09:43:09 +0800
Subject: [PATCH] feat(product): 添加产品规格型号向下复制功能 - 在产品树形结构中添加向下复制按钮,仅在层级大于1时显示 - 实现handleDownCopy方法,支持将当前节点规格型号复制到同层级下一个节点 - 添加downCopyProductModel API接口,用于处理向下复制请求 - 添加相应的警告和成功消息提示

---
 src/views/salesManagement/salesLedger/index.vue |  228 ++++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 152 insertions(+), 76 deletions(-)

diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 740371d..3e9c0b2 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -34,6 +34,7 @@
 					<el-button @click="handleOut">瀵煎嚭</el-button>
 					<el-button type="danger" plain @click="handleDelete">鍒犻櫎</el-button>
 					<el-button type="primary" plain @click="handlePrint">鎵撳嵃</el-button>
+					<el-button type="primary" plain @click="handleExport">瀵煎嚭閫佽揣鍗�</el-button>
 				</div>
 			</div>
 			<el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
@@ -103,18 +104,21 @@
 					</template>
 				</el-table-column>
 				<el-table-column align="center" label="搴忓彿" type="index" width="60" />
-				<el-table-column label="閿�鍞悎鍚屽彿" prop="salesContractNo" width="180" show-overflow-tooltip />
 				<el-table-column label="瀹㈡埛鍚嶇О" prop="customerName" width="300" show-overflow-tooltip />
-				<el-table-column label="涓氬姟鍛�" prop="salesman" width="100" show-overflow-tooltip />
+				<el-table-column label="鍨嬪彿" prop="model" width="100" show-overflow-tooltip />
+				<el-table-column label="鐢靛帇" prop="voltage" width="100" show-overflow-tooltip />
+				<el-table-column label="qty" prop="qty" width="100" show-overflow-tooltip />
 				<el-table-column label="椤圭洰鍚嶇О" prop="projectName" width="180" show-overflow-tooltip />
-				<el-table-column label="浠樻鏂瑰紡" prop="paymentMethod" show-overflow-tooltip />
-				<el-table-column label="鍚堝悓閲戦(鍏�)" prop="contractAmount" width="220" show-overflow-tooltip
-					:formatter="formattedNumber" />
+        <el-table-column label="鍚堝悓閲戦(鍏�)" prop="contractAmount" width="220" show-overflow-tooltip
+                         :formatter="formattedNumber" />
+        <el-table-column label="浠樻鏂瑰紡" prop="paymentMethod" show-overflow-tooltip />
 				<el-table-column label="褰曞叆浜�" prop="entryPersonName" width="100" show-overflow-tooltip />
 				<el-table-column label="褰曞叆鏃ユ湡" prop="entryDate" width="120" show-overflow-tooltip />
 				<el-table-column label="绛捐鏃ユ湡" prop="executionDate" width="120" show-overflow-tooltip />
 				<el-table-column label="浜や粯鏃ユ湡" prop="deliveryDate" width="120" show-overflow-tooltip />
-				<el-table-column label="澶囨敞" prop="remarks" width="200" show-overflow-tooltip />
+        <el-table-column label="涓氬姟鍛�" prop="salesman" width="100" show-overflow-tooltip />
+        <el-table-column label="澶囨敞" prop="remarks" width="200" show-overflow-tooltip />
+        <el-table-column label="閿�鍞悎鍚屽彿" prop="salesContractNo" width="180" show-overflow-tooltip />
 				<el-table-column fixed="right" label="鎿嶄綔" min-width="100" align="center">
 					<template #default="scope">
 						<el-button link type="primary" size="small" @click="openForm('edit', scope.row)"
@@ -217,7 +221,7 @@
 					</el-col>
 					<el-col :span="12">
 						<el-form-item label="鏄惁鐢熶骇锛�" prop="produce">
-							<el-radio-group v-model="form.produce">
+							<el-radio-group v-model="form.produce" :disabled="operationType !== 'add'">
 								<el-radio :label="true">鏄�</el-radio>
 								<el-radio :label="false">鍚�</el-radio>
 							</el-radio-group>
@@ -340,10 +344,10 @@
 				</el-row>
 				<el-row :gutter="30">
 					<el-col :span="24">
-						<el-form-item label="瑙勬牸鍨嬪彿锛�" prop="productModelId">
-							<el-select v-model="productForm.productModelId" placeholder="璇烽�夋嫨" clearable
+						<el-form-item label="鐢靛帇锛�" prop="stockId">
+							<el-select v-model="productForm.stockId" placeholder="璇烽�夋嫨" clearable
 								@change="getProductModel" filterable>
-								<el-option v-for="item in modelOptions" :key="item.id" :label="item.model"
+								<el-option v-for="item in modelOptions" :key="item.id" :label="item.label"
 									:value="item.id" />
 							</el-select>
 						</el-form-item>
@@ -446,7 +450,7 @@
 						鏆傛棤鎵撳嵃鏁版嵁
 					</div>
 					<div v-else
-						style="text-align: center; padding: 10px; color: #666; font-size: 16px; background: #e8f4fd; margin-bottom: 10px;">
+						style="text-align: center; padding: 10px; color: #666; font-size: 18px; background: #e8f4fd; margin-bottom: 10px;">
 						鍏� {{ printData.length }} 鏉℃暟鎹緟鎵撳嵃
 					</div>
 					<div v-for="(item, index) in printData" :key="index" class="print-page">
@@ -503,7 +507,7 @@
 								</div>
 							</div>
 
-							<span style="font-size: 20px; margin-top: 5px;">璐х墿璇︾粏淇℃伅锛�</span>
+							<span style="font-size: 18px; margin-top: 5px;">璐х墿璇︾粏淇℃伅锛�</span>
 							<div class="table-section">
 								<table class="product-table">
 									<thead>
@@ -528,14 +532,14 @@
 											<td>{{ item.salesContractNo || '' }}</td>
 										</tr>
 										<!-- 娣诲姞绌虹櫧琛屼互纭繚鑷冲皯5琛� -->
-										<tr v-for="n in Math.max(0, 5 - (item.products ? item.products.length : 0))" :key="'empty-' + n" style="height: 25px;">
-											<td style="height: 35px;"></td>
-											<td style="height: 35px;"></td>
-											<td style="height: 35px;"></td>
-											<td style="height: 35px;"></td>
-											<td style="height: 35px;"></td>
-											<td style="height: 35px;"></td>
-											<td style="height: 35px;"></td>
+										<tr v-for="n in Math.max(0, 5 - (item.products ? item.products.length : 0))" :key="'empty-' + n">
+											<td>&nbsp;</td>
+											<td>&nbsp;</td>
+											<td>&nbsp;</td>
+											<td>&nbsp;</td>
+											<td>&nbsp;</td>
+											<td>&nbsp;</td>
+											<td>&nbsp;</td>
 										</tr>
 									</tbody>
 									<tfoot>
@@ -548,14 +552,14 @@
 								</table>
 
 								<div style="width: 80px; display: flex; flex-direction: column;">
-                  <div style="height: 35px; border-bottom: 1px dashed #000; text-align: center; display: flex; align-items: center; justify-content: center;">鍏卞洓鑱�</div>
-                  <div style="height: 35px; border-bottom: 1px dashed #000; text-align: center; display: flex; align-items: center; justify-content: center;">瀛樻牴</div>
-                  <div style="height: 35px; border-bottom: 1px dashed #000; text-align: center; display: flex; align-items: center; justify-content: center;">瀹㈡埛</div>
-                  <div style="height: 35px; border-bottom: 1px dashed #000; text-align: center; display: flex; align-items: center; justify-content: center;">璁拌处</div>
-                  <div style="height: 35px; text-align: center; display: flex; align-items: center; justify-content: center;">浠撳簱</div>
+                  <div style="height: 33px; border-bottom: 1px dashed #000; text-align: center; display: flex; align-items: center; justify-content: center;">鍏卞洓鑱�</div>
+                  <div style="height: 28px; border-bottom: 1px dashed #000; text-align: center; display: flex; align-items: center; justify-content: center;">瀛樻牴</div>
+                  <div style="height: 29px; border-bottom: 1px dashed #000; text-align: center; display: flex; align-items: center; justify-content: center;">瀹㈡埛</div>
+                  <div style="height: 29px; border-bottom: 1px dashed #000; text-align: center; display: flex; align-items: center; justify-content: center;">璁拌处</div>
+                  <div style="height: 28px; text-align: center; display: flex; align-items: center; justify-content: center;">浠撳簱</div>
 								</div>
 							</div>
-              <div style="margin: 10px 0; font-size: 20px;">
+              <div style="margin: 10px 0; font-size: 16px;">
                 <span>澶囨敞锛� 璐靛叕鍙稿湪鏀惰揣鍚庤鍗冲埢鏍稿疄鏁伴噺鍙婂搧璐紝鑻ユ湁寮傝锛岃鍦�3鏃ュ唴鎻愬嚭锛屽惁鍒欒涓烘敹濡ャ��</span>
               </div>
               <div class="footer">
@@ -658,17 +662,17 @@
 import FormDialog from '@/components/Dialog/FormDialog.vue';
 import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
 import {
-	ledgerListPage,
-	productList,
-	customerList,
-	addOrUpdateSalesLedger,
-	getSalesLedgerWithProducts,
-	delLedger,
-	addOrUpdateSalesLedgerProduct,
-	delProduct,
-	delLedgerFile, getProductInventory,
+  ledgerListPage,
+  productList,
+  customerList,
+  addOrUpdateSalesLedger,
+  getSalesLedgerWithProducts,
+  delLedger,
+  addOrUpdateSalesLedgerProduct,
+  delProduct,
+  delLedgerFile, getProductInventory,
 } from "@/api/salesManagement/salesLedger.js";
-import { modelList, productTreeList } from "@/api/basicData/product.js";
+import { finishedProductList } from "@/api/basicData/product.js";
 import useFormData from "@/hooks/useFormData.js";
 import dayjs from "dayjs";
 import { getCurrentDate } from "@/utils/index.js";
@@ -682,6 +686,7 @@
 const userList = ref([]);
 const customerOption = ref([]);
 const productOptions = ref([]);
+const productOptionsRaw = ref([]); // 淇濆瓨鍘熷鏁版嵁锛堝寘鍚� children锛�
 const modelOptions = ref([]);
 const tableLoading = ref(false);
 const page = reactive({
@@ -916,31 +921,71 @@
 // 鑾峰彇浜у搧澶х被tree鏁版嵁
 const getProductOptions = () => {
 	// 杩斿洖 Promise锛屼究浜庡湪缂栬緫浜у搧鏃剁瓑寰呭姞杞藉畬鎴�
-	return productTreeList().then((res) => {
-		productOptions.value = convertIdToValue(res);
+	return finishedProductList({}).then((res) => {
+		// 淇濆瓨鍘熷鏁版嵁锛堝寘鍚� children锛夌敤浜庡悗缁彁鍙栫數鍘嬪垪琛�
+		productOptionsRaw.value = res || [];
+		// 浜у搧澶х被涓嬫媺閫夐」
+		productOptions.value = (res || []).map(item => ({
+			...item,
+			value: item.productId,  // 鐢� productId 浣滀负 value
+			children: undefined     // 鍘绘帀涓嬫媺绠ご
+		}));
 		return productOptions.value;
 	});
 };
 const formattedNumber = (row, column, cellValue) => {
 	return parseFloat(cellValue).toFixed(2);
 };
-// 鑾峰彇tree瀛愭暟鎹�
+// 鑾峰彇tree瀛愭暟鎹紙浠� children 涓彁鍙栫數鍘嬪垪琛級
 const getModels = (value) => {
 	productForm.value.productCategory = findNodeById(productOptions.value, value);
-	modelList({ id: value }).then((res) => {
-		modelOptions.value = res;
-	});
+	// 浠庡師濮嬫暟鎹腑鎵惧埌閫変腑鐨勮妭鐐癸紙鍖呭惈 children锛�
+	const selectedNode = productOptionsRaw.value.find(item => item.productId === value);
+	if (selectedNode && selectedNode.children && selectedNode.children.length > 0) {
+		// 鎻愬彇 children 涓殑鍨嬪彿鏁版嵁锛屾嫾鎺ユ樉绀烘枃鏈�
+		modelOptions.value = selectedNode.children
+			.map(item => {
+				// 鎷兼帴鏄剧ず鏂囨湰锛歮odel - processCategory - voltage锛堣繃婊ょ┖瀛楃涓诧級
+				const parts = [];
+				if (item.model && item.model.trim()) parts.push(item.model);
+				if (item.processCategory && item.processCategory.trim()) parts.push(item.processCategory);
+				if (item.voltage && item.voltage.trim()) parts.push(item.voltage);
+				return {
+					...item,
+					id: item.stockId,  // 鐢� stockId 浣滀负鍞竴鏍囪瘑
+					productModelId: item.productModelId,  // 淇濆瓨 productModelId 鐢ㄤ簬鎻愪氦
+					label: parts.join('-')
+				};
+			})
+			.filter(item => item.label);  // 杩囨护鎺� label 涓虹┖鐨勯�夐」
+	} else {
+		modelOptions.value = [];
+	}
 };
 const getProductModel = (value) => {
 	const index = modelOptions.value.findIndex((item) => item.id === value);
 	if (index !== -1) {
-		productForm.value.specificationModel = modelOptions.value[index].model;
-		productForm.value.unit = modelOptions.value[index].unit;
-		productForm.value.materialCode = modelOptions.value[index].materialCode;
+		const selectedItem = modelOptions.value[index];
+		// 瑙勬牸鍨嬪彿瀛樺偍鎷兼帴鍊硷紝鐢ㄤ簬琛ㄦ牸鏄剧ず
+		productForm.value.specificationModel = selectedItem.label;
+		productForm.value.unit = selectedItem.unit;
+		productForm.value.materialCode = selectedItem.materialCode;
+		// 淇濆瓨鐢靛帇淇℃伅
+		productForm.value.voltage = selectedItem.voltage;
+		// 淇濆瓨宸ュ簭绫诲埆
+		productForm.value.processCategory = selectedItem.processCategory;
+		// 淇濆瓨 productModelId 鐢ㄤ簬鎻愪氦
+		productForm.value.productModelId = selectedItem.productModelId;
+		// 淇濆瓨 stockId 鐢ㄤ簬鎻愪氦
+		productForm.value.stockId = selectedItem.stockId;
 	} else {
 		productForm.value.specificationModel = null;
 		productForm.value.unit = null;
 		productForm.value.materialCode = null;
+		productForm.value.voltage = null;
+		productForm.value.processCategory = null;
+		productForm.value.productModelId = null;
+		productForm.value.stockId = null;
 	}
 };
 const findNodeById = (nodes, productId) => {
@@ -959,13 +1004,13 @@
 };
 function convertIdToValue(data) {
 	return data.map((item) => {
-		const { id, children, ...rest } = item;
+		const { productId, children, ...rest } = item;
 		const newItem = {
 			...rest,
-			value: id, // 灏� id 鏀逛负 value
+			value: productId, // 浜у搧澶х被鐢� productId 浣滀负 value
 		};
 		if (children && children.length > 0) {
-			newItem.children = convertIdToValue(children);
+			newItem.children = children;  // children 淇濇寔鍘熸牱
 		}
 
 		return newItem;
@@ -1264,22 +1309,36 @@
 	if (type === "edit") {
 		productForm.value = { ...row };
 		productIndex.value = index;
-		// 缂栬緫鏃舵牴鎹骇鍝佸ぇ绫诲悕绉板弽鏌� tree 鑺傜偣 id锛屽苟鍔犺浇瑙勬牸鍨嬪彿鍒楄〃
+		// 缂栬緫鏃舵牴鎹骇鍝佸ぇ绫诲悕绉板弽鏌� tree 鑺傜偣 id锛屽苟鍔犺浇鐢靛帇鍒楄〃
 		try {
 			const options = productOptions.value && productOptions.value.length > 0
 				? productOptions.value
 				: await getProductOptions();
 			const categoryId = findNodeIdByLabel(options, productForm.value.productCategory);
 			if (categoryId) {
-				const models = await modelList({ id: categoryId });
-				modelOptions.value = models || [];
-				// 鏍规嵁褰撳墠瑙勬牸鍨嬪彿鍚嶇О鍙嶆煡骞惰缃� productModelId锛屼究浜庝笅鎷夋鏄剧ず宸查�夊��
-				const currentModel = (modelOptions.value || []).find(
-					(m) => m.model === productForm.value.specificationModel
-				);
-				if (currentModel) {
-					productForm.value.productModelId = currentModel.id;
+				// 浠庡師濮嬫暟鎹腑鎵惧埌閫変腑鐨勮妭鐐癸紝鎻愬彇 children
+				const selectedNode = productOptionsRaw.value.find(item => item.productId === categoryId);
+				if (selectedNode && selectedNode.children && selectedNode.children.length > 0) {
+					modelOptions.value = selectedNode.children
+						.map(item => {
+							// 鎷兼帴鏄剧ず鏂囨湰锛歮odel - processCategory - voltage锛堣繃婊ょ┖瀛楃涓诧級
+							const parts = [];
+							if (item.model && item.model.trim()) parts.push(item.model);
+							if (item.processCategory && item.processCategory.trim()) parts.push(item.processCategory);
+							if (item.voltage && item.voltage.trim()) parts.push(item.voltage);
+							return {
+								...item,
+								id: item.stockId,  // 鐢� stockId 浣滀负鍞竴鏍囪瘑
+								productModelId: item.productModelId,
+								label: parts.join('-')
+							};
+						})
+						.filter(item => item.label);  // 杩囨护鎺� label 涓虹┖鐨勯�夐」
 				}
+				// 鏍规嵁褰撳墠 stockId 璁剧疆閫変腑鍊硷紙鐢ㄤ簬涓嬫媺妗嗗洖鏄撅級
+				productForm.value.stockId = row.stockId;
+				// 鍚屾椂淇濆瓨 productModelId
+				productForm.value.productModelId = row.productModelId;
 			}
 		} catch (e) {
 			// 鍔犺浇澶辫触鏃朵繚鎸佸彲缂栬緫锛屼笉涓柇寮圭獥
@@ -1471,6 +1530,25 @@
 		});
 };
 
+const handleExport = () => {
+  if (selectedRows.value.length === 0) {
+    proxy.$modal.msgWarning("璇烽�夋嫨瑕佸鍑洪�佽揣鍗曠殑鏁版嵁");
+    return;
+  }
+
+  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+    confirmButtonText: "纭",
+    cancelButtonText: "鍙栨秷",
+    type: "warning",
+  })
+      .then(() => {
+        proxy.download("/sales/ledger/exportShippingNote", {ids: selectedRows.value.map((item) => item.id).join(",")}, "閫佽揣鍗�.xlsx");
+      })
+      .catch(() => {
+        proxy.$modal.msg("宸插彇娑�");
+      });
+}
+
 // 鎵撳嵃鍔熻兘
 const handlePrint = async () => {
 	if (selectedRows.value.length === 0) {
@@ -1562,22 +1640,20 @@
           width: 100%;
           height: 100%;
           font-size: 18px;
-          line-height: 1.5;
+          line-height: 1;
           display: flex;
           flex-direction: column;
           color: #000;
         }
         .header {
           text-align: center;
-          margin-bottom: 8px;
         }
         .company-name {
-          font-size: 32px;
+          font-size: 30px;
           font-weight: bold;
-          margin-bottom: 4px;
         }
         .document-title {
-          font-size: 28px;
+          font-size: 25px;
           font-weight: bold;
           letter-spacing: 10px;
         }
@@ -1587,17 +1663,17 @@
           align-items: center;
         }
         .info-row {
-          line-height: 26px;
+          line-height: 20px;
         }
         .label {
           font-weight: bold;
           min-width: 60px;
-          font-size: 20px;
+          font-size: 18px;
         }
         .value {
           margin-right: 20px;
           min-width: 80px;
-          font-size: 20px;
+          font-size: 18px;
         }
         .table-section {
           margin-bottom: 4px;
@@ -1611,13 +1687,13 @@
         }
         .product-table th, .product-table td {
           border: 1px solid #000;
-          padding: 6px;
+          padding: 3px;
           text-align: center;
-          font-size: 20px;
-          line-height: 1.4;
+          font-size: 18px;
+          line-height: 1.2;
         }
         .product-table th {
-          font-size: 20px;
+          font-size: 19px;
           font-weight: bold;
         }
         .total-value {
@@ -1627,7 +1703,7 @@
           margin: 10px 0;
         }
         .sign-section {
-          margin-top: 15px;
+          margin-top: 10px;
         }
         .footer {
            display: flex;
@@ -1638,17 +1714,17 @@
             width: 50%;
           }
           .info-row {
-            line-height: 26px;
+            line-height: 20px;
           }
           .label {
             font-weight: bold;
             min-width: 60px;
-            font-size: 20px;
+            font-size: 18px;
           }
           .value {
             margin-right: 20px;
             min-width: 80px;
-            font-size: 20px;
+            font-size: 18px;
           }
         }
         @media print {
@@ -1662,7 +1738,7 @@
             padding-right: 10mm;
             page-break-inside: avoid;
             page-break-after: always;
-            font-size: 20px;
+            font-size: 18px;
           }
           .print-page:last-child {
             page-break-after: avoid;
@@ -2231,7 +2307,7 @@
 		th,
 		td {
 			border: 1px solid #000;
-			padding: 6px;
+			padding: 3px;
 			text-align: center;
 			font-size: 16px;
 			line-height: 1.4;
@@ -2327,4 +2403,4 @@
 		page-break-after: avoid;
 	}
 }
-</style>
\ No newline at end of file
+</style>

--
Gitblit v1.9.3