From d984ab17ed7fd6ae972bec8903abcc38117ef72e Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期三, 10 六月 2026 15:50:17 +0800
Subject: [PATCH] 3.固定产品。固定一个之后后面新增的时候自动带出固定的那个产品和规格型号。移除新增按钮,默认就空一列。新增一列保存之后就会又新增一列空的。

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

diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index b79d143..3d566d9 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -2,12 +2,12 @@
   <div class="app-container">
     <template v-if="isFormPageMode">
       <div class="sales-ledger-page-header">
-        <div>
-          <el-button class="sales-ledger-page-back"
-                     @click="exitFormPage()">杩斿洖鍙拌处</el-button>
+        <div class="sales-ledger-page-header-content">
           <div class="sales-ledger-page-title">{{ pageFormTitle }}</div>
           <div class="sales-ledger-page-subtitle">{{ pageFormSubtitle }}</div>
         </div>
+        <el-button class="sales-ledger-page-back"
+                   @click="exitFormPage()">杩斿洖鍙拌处</el-button>
       </div>
       <div class="sales-ledger-page-form">
         <el-form :model="form"
@@ -144,19 +144,19 @@
           <el-row>
             <el-form-item label="浜у搧淇℃伅锛�"
                           prop="entryDate">
-              <el-button type="primary"
-                         :disabled="hasEditingProductRow() || isReviewedEdit"
-                         @click="addProductInline">
-                娣诲姞
-              </el-button>
               <el-button plain
                          type="danger"
                          :disabled="isReviewedEdit"
                          @click="deleteProduct">鍒犻櫎</el-button>
+              <el-button plain
+                         type="primary"
+                         :disabled="isReviewedEdit"
+                         @click="pinSelectedProductRow">鍥哄畾</el-button>
             </el-form-item>
           </el-row>
           <el-table :data="productData"
                     border
+                    class="compact-product-table"
                     @selection-change="productSelected"
                     show-summary
                     :summary-method="summarizeProductTable">
@@ -170,7 +170,7 @@
                              width="60" />
             <el-table-column label="浜у搧澶х被"
                              prop="productCategory"
-                             min-width="160">
+                             min-width="120">
               <template #default="scope">
                 <el-tree-select v-if="scope.row.__editing"
                                 v-model="scope.row.__productCategoryId"
@@ -189,7 +189,7 @@
             </el-table-column>
             <el-table-column label="瑙勬牸鍨嬪彿"
                              prop="specificationModel"
-                             min-width="200">
+                             min-width="140">
               <template #default="scope">
                 <el-select v-if="scope.row.__editing"
                            v-model="scope.row.productModelId"
@@ -209,7 +209,7 @@
             </el-table-column>
             <el-table-column label="鍘氬害(mm)"
                              prop="thickness"
-                             min-width="160">
+                             min-width="95">
               <template #default="scope">
                 <el-input-number v-if="scope.row.__editing"
                                  controls-position="right"
@@ -226,7 +226,7 @@
             </el-table-column>
             <el-table-column label="妤煎眰缂栧彿"
                              prop="floorCode"
-                             min-width="250"
+                             min-width="120"
                              show-overflow-tooltip>
               <template #default="scope">
                 <el-input v-if="scope.row.__editing"
@@ -240,9 +240,10 @@
             </el-table-column>
             <el-table-column label="鍚◣鍗曚环(鍏�)"
                              prop="taxInclusiveUnitPrice"
-                             min-width="160">
+                             min-width="85">
               <template #default="scope">
                 <el-input-number v-if="scope.row.__editing"
+                                 controls-position="right"
                                  :step="0.01"
                                  :min="0"
                                  :precision="2"
@@ -257,7 +258,7 @@
             </el-table-column>
             <el-table-column label="瀹�(mm)"
                              prop="width"
-                             min-width="160">
+                             min-width="85">
               <template #default="scope">
                 <el-input-number v-if="scope.row.__editing"
                                  controls-position="right"
@@ -276,7 +277,7 @@
             </el-table-column>
             <el-table-column label="楂�(mm)"
                              prop="height"
-                             min-width="160">
+                             min-width="85">
               <template #default="scope">
                 <el-input-number v-if="scope.row.__editing"
                                  controls-position="right"
@@ -295,7 +296,7 @@
             </el-table-column>
             <el-table-column label="鏁伴噺"
                              prop="quantity"
-                             min-width="150">
+                             min-width="85">
               <template #default="scope">
                 <el-input-number v-if="scope.row.__editing"
                                  controls-position="right"
@@ -314,7 +315,7 @@
             </el-table-column>
             <el-table-column label="缁撶畻鍗曠墖闈㈢Н(銕�)"
                              prop="settlePieceArea"
-                             min-width="200">
+                             min-width="120">
               <template #default="scope">
                 <el-input-number v-if="scope.row.__editing"
                                  controls-position="right"
@@ -332,7 +333,7 @@
             </el-table-column>
             <el-table-column label="闈㈢Н(m虏)"
                              prop="actualTotalArea"
-                             min-width="200">
+                             min-width="110">
               <template #default="scope">
                 <el-input-number v-if="scope.row.__editing"
                                  controls-position="right"
@@ -348,7 +349,7 @@
             </el-table-column>
             <el-table-column label="绋庣巼(%)"
                              prop="taxRate"
-                             min-width="120">
+                             min-width="80">
               <template #default="scope">
                 <el-select v-if="scope.row.__editing"
                            v-model="scope.row.taxRate"
@@ -374,14 +375,14 @@
             <el-table-column label="鍚◣鎬讳环(鍏�)"
                              prop="taxInclusiveTotalPrice"
                              :formatter="formattedNumber"
-                             min-width="120" />
+                             min-width="100" />
             <el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
                              prop="taxExclusiveTotalPrice"
                              :formatter="formattedNumber"
-                             min-width="120" />
+                             min-width="100" />
             <el-table-column label="鍔犲伐瑕佹眰"
                              prop="processRequirement"
-                             min-width="160"
+                             min-width="100"
                              show-overflow-tooltip>
               <template #default="scope">
                 <el-input v-if="scope.row.__editing"
@@ -395,7 +396,7 @@
             </el-table-column>
             <el-table-column label="鍙戠エ绫诲瀷"
                              prop="invoiceType"
-                             min-width="120">
+                             min-width="90">
               <template #default="scope">
                 <el-select v-if="scope.row.__editing"
                            v-model="scope.row.invoiceType"
@@ -413,7 +414,7 @@
             </el-table-column>
             <el-table-column label="澶囨敞"
                              prop="remark"
-                             min-width="140"
+                             min-width="100"
                              show-overflow-tooltip>
               <template #default="scope">
                 <el-input v-if="scope.row.__editing"
@@ -427,7 +428,7 @@
             </el-table-column>
             <el-table-column label="閲嶇"
                              prop="heavyBox"
-                             min-width="100">
+                             min-width="80">
               <template #default="scope">
                 <el-input v-if="scope.row.__editing"
                           v-model="scope.row.heavyBox"
@@ -440,7 +441,7 @@
             </el-table-column>
             <el-table-column fixed="right"
                              label="鎿嶄綔"
-                             min-width="220"
+                             min-width="150"
                              align="center">
               <template #default="scope">
                 <template v-if="scope.row.__editing">
@@ -557,7 +558,7 @@
                                size="small"
                                :disabled="isProductShipped(scope.row)"
                                @click="copyProductInline(scope.row, scope.$index)">
-                      澶嶅埗鏂板缓
+                      澶嶅埗
                     </el-button>
                   </template>
                   <el-popover :width="560"
@@ -1200,20 +1201,20 @@
           <el-form-item label="浜у搧淇℃伅锛�"
                         prop="entryDate">
             <el-button v-if="operationType !== 'view'"
-                       type="primary"
-                       :disabled="hasEditingProductRow() || isReviewedEdit"
-                       @click="addProductInline">
-              娣诲姞
-            </el-button>
-            <el-button v-if="operationType !== 'view'"
                        plain
                        type="danger"
                        :disabled="isReviewedEdit"
                        @click="deleteProduct">鍒犻櫎</el-button>
+            <el-button v-if="operationType !== 'view'"
+                       plain
+                       type="primary"
+                       :disabled="isReviewedEdit"
+                       @click="pinSelectedProductRow">鍥哄畾</el-button>
           </el-form-item>
         </el-row>
         <el-table :data="productData"
                   border
+                  class="compact-product-table"
                   @selection-change="productSelected"
                   show-summary
                   :summary-method="summarizeProductTable">
@@ -1228,7 +1229,7 @@
                            width="60" />
           <el-table-column label="浜у搧澶х被"
                            prop="productCategory"
-                           min-width="160">
+                           min-width="120">
             <template #default="scope">
               <el-tree-select v-if="scope.row.__editing"
                               v-model="scope.row.__productCategoryId"
@@ -1247,7 +1248,7 @@
           </el-table-column>
           <el-table-column label="瑙勬牸鍨嬪彿"
                            prop="specificationModel"
-                           min-width="200">
+                           min-width="140">
             <template #default="scope">
               <el-select v-if="scope.row.__editing"
                          v-model="scope.row.productModelId"
@@ -1267,7 +1268,7 @@
           </el-table-column>
           <el-table-column label="鍘氬害(mm)"
                            prop="thickness"
-                           min-width="160">
+                           min-width="95">
             <template #default="scope">
               <el-input-number v-if="scope.row.__editing"
                                controls-position="right"
@@ -1284,7 +1285,7 @@
           </el-table-column>
           <el-table-column label="妤煎眰缂栧彿"
                            prop="floorCode"
-                           min-width="250"
+                           min-width="120"
                            show-overflow-tooltip>
             <template #default="scope">
               <el-input v-if="scope.row.__editing"
@@ -1298,7 +1299,7 @@
           </el-table-column>
           <el-table-column label="鍚◣鍗曚环(鍏�)"
                            prop="taxInclusiveUnitPrice"
-                           min-width="160">
+                           min-width="105">
             <template #default="scope">
               <el-input-number v-if="scope.row.__editing"
                                :step="0.01"
@@ -1315,7 +1316,7 @@
           </el-table-column>
           <el-table-column label="瀹�(mm)"
                            prop="width"
-                           min-width="160">
+                           min-width="85">
             <template #default="scope">
               <el-input-number v-if="scope.row.__editing"
                                controls-position="right"
@@ -1334,7 +1335,7 @@
           </el-table-column>
           <el-table-column label="楂�(mm)"
                            prop="height"
-                           min-width="160">
+                           min-width="85">
             <template #default="scope">
               <el-input-number v-if="scope.row.__editing"
                                controls-position="right"
@@ -1353,7 +1354,7 @@
           </el-table-column>
           <el-table-column label="鏁伴噺"
                            prop="quantity"
-                           min-width="150">
+                           min-width="85">
             <template #default="scope">
               <el-input-number v-if="scope.row.__editing"
                                controls-position="right"
@@ -1372,7 +1373,7 @@
           </el-table-column>
           <el-table-column label="缁撶畻鍗曠墖闈㈢Н(銕�)"
                            prop="settlePieceArea"
-                           min-width="200">
+                           min-width="120">
             <template #default="scope">
               <el-input-number v-if="scope.row.__editing"
                                controls-position="right"
@@ -1390,7 +1391,7 @@
           </el-table-column>
           <el-table-column label="闈㈢Н(m虏)"
                            prop="actualTotalArea"
-                           min-width="200">
+                           min-width="110">
             <template #default="scope">
               <el-input-number v-if="scope.row.__editing"
                                controls-position="right"
@@ -1406,7 +1407,7 @@
           </el-table-column>
           <el-table-column label="绋庣巼(%)"
                            prop="taxRate"
-                           min-width="120">
+                           min-width="80">
             <template #default="scope">
               <el-select v-if="scope.row.__editing"
                          v-model="scope.row.taxRate"
@@ -1432,14 +1433,14 @@
           <el-table-column label="鍚◣鎬讳环(鍏�)"
                            prop="taxInclusiveTotalPrice"
                            :formatter="formattedNumber"
-                           min-width="120" />
+                           min-width="100" />
           <el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
                            prop="taxExclusiveTotalPrice"
                            :formatter="formattedNumber"
-                           min-width="120" />
+                           min-width="100" />
           <el-table-column label="鍔犲伐瑕佹眰"
                            prop="processRequirement"
-                           min-width="160"
+                           min-width="100"
                            show-overflow-tooltip>
             <template #default="scope">
               <el-input v-if="scope.row.__editing"
@@ -1453,7 +1454,7 @@
           </el-table-column>
           <el-table-column label="鍙戠エ绫诲瀷"
                            prop="invoiceType"
-                           min-width="120">
+                           min-width="90">
             <template #default="scope">
               <el-select v-if="scope.row.__editing"
                          v-model="scope.row.invoiceType"
@@ -1471,7 +1472,7 @@
           </el-table-column>
           <el-table-column label="澶囨敞"
                            prop="remark"
-                           min-width="140"
+                           min-width="100"
                            show-overflow-tooltip>
             <template #default="scope">
               <el-input v-if="scope.row.__editing"
@@ -1485,7 +1486,7 @@
           </el-table-column>
           <el-table-column label="閲嶇"
                            prop="heavyBox"
-                           min-width="100">
+                           min-width="80">
             <template #default="scope">
               <el-input v-if="scope.row.__editing"
                         v-model="scope.row.heavyBox"
@@ -1498,7 +1499,7 @@
           </el-table-column>
           <el-table-column fixed="right"
                            label="鎿嶄綔"
-                           min-width="220"
+                           min-width="150"
                            align="center"
                            v-if="operationType !== 'view'">
             <template #default="scope">
@@ -1616,7 +1617,7 @@
                              size="small"
                              :disabled="isProductShipped(scope.row)"
                              @click="copyProductInline(scope.row, scope.$index)">
-                    澶嶅埗鏂板缓
+                    澶嶅埗
                   </el-button>
                 </template>
                 <el-popover :width="560"
@@ -2534,6 +2535,7 @@
   const productData = ref([]);
   const selectedRows = ref([]);
   const productSelectedRows = ref([]);
+  const pinnedProductTemplate = ref(null);
   const userList = ref([]);
   const userListApprove = ref([]);
   const customerOption = ref([]);
@@ -2857,16 +2859,56 @@
     return (productData.value || []).some(r => r && r.__editing);
   };
 
-  const buildEmptyInlineProductRow = () => ({
+  const isPlaceholderRowDirty = row => {
+    if (!row || !row.__placeholder) return false;
+    const fieldsToCheck = [
+      "width",
+      "height",
+      "quantity",
+      "settlePieceArea",
+      "taxInclusiveUnitPrice",
+      "taxRate",
+      "invoiceType",
+      "floorCode",
+      "processRequirement",
+      "remark",
+      "heavyBox",
+    ];
+    return fieldsToCheck.some(key => {
+      const value = row[key];
+      return value !== null && value !== undefined && value !== "";
+    });
+  };
+
+  const getPlaceholderRowIndex = () =>
+    (productData.value || []).findIndex(r => r && r.__placeholder);
+
+  const discardPlaceholderRowIfPristine = () => {
+    const index = getPlaceholderRowIndex();
+    if (index === -1) return true;
+    const row = productData.value[index];
+    if (isPlaceholderRowDirty(row)) {
+      proxy.$modal.msgWarning("璇峰厛淇濆瓨鎴栧彇娑堝綋鍓嶅緟褰曞叆琛�");
+      return false;
+    }
+    productData.value.splice(index, 1);
+    if (editingProductRow.value === row) {
+      editingProductRow.value = null;
+    }
+    return true;
+  };
+
+  const buildEmptyInlineProductRow = (prefill = {}) => ({
     id: null,
     __tempKey: `__temp_${Date.now()}_${Math.random().toString(16).slice(2)}`,
     __editing: true,
     __isNew: true,
-    __productCategoryId: null,
-    productCategory: "",
-    productModelId: null,
-    specificationModel: "",
-    thickness: null,
+    __placeholder: true,
+    __productCategoryId: prefill.__productCategoryId ?? null,
+    productCategory: prefill.productCategory ?? "",
+    productModelId: prefill.productModelId ?? null,
+    specificationModel: prefill.specificationModel ?? "",
+    thickness: prefill.thickness ?? null,
     quantity: null,
     taxInclusiveUnitPrice: null,
     taxRate: "",
@@ -2888,19 +2930,89 @@
     heavyBox: "",
   });
 
+  const getPinnedProductPrefill = async () => {
+    if (!pinnedProductTemplate.value) return {};
+    await getProductOptions();
+    if (pinnedProductTemplate.value.__productCategoryId) {
+      const models = await modelList({
+        id: pinnedProductTemplate.value.__productCategoryId,
+      });
+      modelOptions.value = models || [];
+    }
+    return { ...pinnedProductTemplate.value };
+  };
+
+  const appendEditablePlaceholderRow = async () => {
+    if (operationType.value === "view" || isReviewedEdit.value) return;
+    const existingPlaceholder = (productData.value || []).find(
+      r => r && r.__placeholder
+    );
+    if (existingPlaceholder) {
+      if (!existingPlaceholder.__editing) existingPlaceholder.__editing = true;
+      editingProductRow.value = existingPlaceholder;
+      productForm.value = existingPlaceholder;
+      return;
+    }
+    if (hasEditingProductRow()) return;
+    await getProductOptions();
+    await fetchOtherAmountSelectOptions(true);
+    const prefill = await getPinnedProductPrefill();
+    const row = buildEmptyInlineProductRow(prefill);
+    productData.value.push(row);
+    editingProductRow.value = row;
+    productForm.value = row;
+  };
+
+  const pinSelectedProductRow = async () => {
+    if (operationType.value === "view" || isReviewedEdit.value) return;
+    if (!productSelectedRows.value || productSelectedRows.value.length !== 1) {
+      proxy.$modal.msgWarning("璇烽�夋嫨涓�鏉′骇鍝佹暟鎹繘琛屽浐瀹�");
+      return;
+    }
+    const selectedRow = productSelectedRows.value[0];
+    const categoryId =
+      selectedRow.__productCategoryId ??
+      findNodeIdByLabel(productOptions.value, selectedRow.productCategory) ??
+      null;
+    if (!selectedRow.productCategory || !selectedRow.specificationModel) {
+      proxy.$modal.msgWarning("璇峰厛閫夋嫨瀹屾暣鐨勪骇鍝佸ぇ绫诲拰瑙勬牸鍨嬪彿鍐嶅浐瀹�");
+      return;
+    }
+    if (!categoryId || !selectedRow.productModelId) {
+      proxy.$modal.msgWarning("璇峰厛淇濆瓨褰撳墠浜у搧鍚庡啀鍥哄畾");
+      return;
+    }
+    pinnedProductTemplate.value = {
+      __productCategoryId: categoryId,
+      productCategory: selectedRow.productCategory ?? "",
+      productModelId: selectedRow.productModelId ?? null,
+      specificationModel: selectedRow.specificationModel ?? "",
+      thickness:
+        selectedRow.thickness !== null &&
+        selectedRow.thickness !== undefined &&
+        selectedRow.thickness !== ""
+          ? Number(selectedRow.thickness)
+          : null,
+    };
+    proxy.$modal.msgSuccess("鍥哄畾鎴愬姛锛屽悗缁細鑷姩甯﹀嚭璇ヤ骇鍝佸拰瑙勬牸鍨嬪彿");
+    const editingRow = (productData.value || []).find(r => r?.__editing);
+    if (
+      editingRow &&
+      !editingRow.productCategory &&
+      !editingRow.productModelId &&
+      !editingRow.specificationModel
+    ) {
+      Object.assign(editingRow, await getPinnedProductPrefill());
+    }
+  };
+
   const addProductInline = async () => {
     if (operationType.value === "view") return;
     if (hasEditingProductRow()) {
       proxy.$modal.msgWarning("璇峰厛淇濆瓨鎴栧彇娑堝綋鍓嶇紪杈戣");
       return;
     }
-    await getProductOptions();
-    await fetchOtherAmountSelectOptions(true);
-    const row = buildEmptyInlineProductRow();
-    productData.value.push(row);
-    editingProductRow.value = row;
-    // 璁╃幇鏈夌殑璁$畻/鍏朵粬閲戦閫昏緫澶嶇敤褰撳墠琛�
-    productForm.value = row;
+    await appendEditablePlaceholderRow();
   };
 
   const copyProductInline = async row => {
@@ -2910,14 +3022,19 @@
       proxy.$modal.msgWarning("宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝佷笉鑳藉鍒�");
       return;
     }
-    if (hasEditingProductRow()) {
+    const hasBlockingEditingRow = (productData.value || []).some(
+      item => item && item.__editing && !item.__placeholder
+    );
+    if (hasBlockingEditingRow) {
       proxy.$modal.msgWarning("璇峰厛淇濆瓨鎴栧彇娑堝綋鍓嶇紪杈戣");
       return;
     }
+    if (!discardPlaceholderRowIfPristine()) return;
     await getProductOptions();
     await fetchOtherAmountSelectOptions(true);
 
     const copied = buildEmptyInlineProductRow();
+    copied.__placeholder = false;
     copied.__productCategoryId =
       row.__productCategoryId ??
       findNodeIdByLabel(productOptions.value, row.productCategory) ??
@@ -2987,6 +3104,7 @@
       proxy.$modal.msgWarning("宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝佷笉鑳界紪杈�");
       return;
     }
+    if (!discardPlaceholderRowIfPristine()) return;
     stopOtherEditingRows();
     await getProductOptions();
     await fetchOtherAmountSelectOptions(true);
@@ -3130,11 +3248,13 @@
     if (model?.model) row.specificationModel = model.model;
 
     if (operationType.value === "edit") {
+      row.__placeholder = false;
       // 鍙拌处宸插瓨鍦細璧板師鎺ュ彛淇濆瓨鍒板悗绔紝鍐嶅洖鎷夊埛鏂�
       const payload = { ...row, salesLedgerId: currentId.value, type: 1 };
       delete payload.__backup;
       delete payload.__editing;
       delete payload.__isNew;
+      delete payload.__placeholder;
       delete payload.__productCategoryId;
       delete payload.__tempKey;
       await addOrUpdateSalesLedgerProduct(payload);
@@ -3144,19 +3264,22 @@
           productData.value = res.productData;
         }
       );
+      stopOtherEditingRows();
+      await appendEditablePlaceholderRow();
     } else {
       // 鏂板鍙拌处锛氫粎鍦ㄦ湰鍦� productData 鐢熸晥锛屾渶缁堥殢鍙拌处涓�璧锋彁浜�
       row.__isNew = false;
+      row.__placeholder = false;
       row.__editing = false;
       delete row.__backup;
+      stopOtherEditingRows();
+      await appendEditablePlaceholderRow();
     }
-
-    stopOtherEditingRows();
   };
 
-  const cancelProductInline = (row, index) => {
+  const cancelProductInline = async (row, index) => {
     if (!row) return;
-    if (row.__isNew) {
+    if (row.__placeholder) {
       productData.value.splice(index, 1);
     } else if (row.__backup) {
       const restored = JSON.parse(JSON.stringify(row.__backup));
@@ -3166,9 +3289,11 @@
       Object.assign(row, restored);
       row.id = keepId;
       row.__editing = false;
+      row.__placeholder = false;
       delete row.__backup;
     }
     stopOtherEditingRows();
+    await appendEditablePlaceholderRow();
   };
 
   const openOtherAmountInline = async row => {
@@ -3844,6 +3969,7 @@
     productData.value = [];
     selectedQuotation.value = null;
     fileList.value = [];
+    pinnedProductTemplate.value = null;
     let userLists = await userListNoPage();
     userList.value = userLists.data;
     customerList().then(res => {
@@ -3864,6 +3990,7 @@
     form.value.customerRemarks = detail.customerRemarks ?? detail.customer_remarks ?? "";
     productData.value = detail.productData || [];
     form.value.deliveryDate = dayjs(form.value.entryDate).add(7, "day").format("YYYY-MM-DD");
+    await appendEditablePlaceholderRow();
     dialogFormVisible.value = !keepPageMode;
   };
 
@@ -4358,6 +4485,7 @@
     productData.value = [];
     fileList.value = [];
     selectedQuotation.value = null;
+    pinnedProductTemplate.value = null;
 
     const userLists = await userListNoPage();
     userList.value = userLists.data;
@@ -4372,6 +4500,7 @@
     form.value.deliveryDate = dayjs(form.value.entryDate)
       .add(7, "day")
       .format("YYYY-MM-DD");
+    await appendEditablePlaceholderRow();
   };
 
   const initEditFormState = async rowId => {
@@ -4381,6 +4510,7 @@
     productData.value = [];
     fileList.value = [];
     selectedQuotation.value = null;
+    pinnedProductTemplate.value = null;
 
     const userLists = await userListNoPage();
     userList.value = userLists.data;
@@ -4397,6 +4527,7 @@
     productData.value = form.value.productData || [];
     fileList.value = form.value.salesLedgerFiles || [];
     isReviewedEdit.value = Number(res?.reviewStatus) === 1;
+    await appendEditablePlaceholderRow();
   };
 
   const enterAddPage = async detail => {
@@ -4663,18 +4794,21 @@
         console.log("productData.value--", productData.value);
         // 琛屽唴缂栬緫鏈繚瀛樻椂涓嶅厑璁告彁浜わ紝閬垮厤鑴忔暟鎹�/涓存椂瀛楁杩涘叆鍚庣
         const hasEditingRow = (productData.value || []).some(
-          r => r && r.__editing
+          r => r && r.__editing && !r.__placeholder
         );
         if (hasEditingRow) {
           proxy.$modal.msgWarning("浜у搧淇℃伅瀛樺湪鏈繚瀛樼殑缂栬緫琛岋紝璇峰厛淇濆瓨鎴栧彇娑�");
           return;
         }
         if (productData.value !== null && productData.value.length > 0) {
-          const cleanedProducts = (productData.value || []).map(p => {
+          const cleanedProducts = (productData.value || [])
+            .filter(p => p && !p.__placeholder)
+            .map(p => {
             if (!p || typeof p !== "object") return p;
             const {
               __editing,
               __isNew,
+              __placeholder,
               __backup,
               __productCategoryId,
               __tempKey,
@@ -4685,6 +4819,10 @@
             rest.quantity = Number(rest.quantity ?? 0) || 0;
             return rest;
           });
+          if (cleanedProducts.length === 0) {
+            proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
+            return;
+          }
           form.value.productData = proxy.HaveJson(cleanedProducts);
         } else {
           proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
@@ -4909,10 +5047,35 @@
           productData.value.splice(index, 1);
         }
       });
+      appendEditablePlaceholderRow();
     } else {
+      const placeholderRows = productSelectedRows.value.filter(
+        item => item?.__placeholder
+      );
+      if (placeholderRows.length > 0) {
+        placeholderRows.forEach(selectedRow => {
+          const index = productData.value.findIndex(product => {
+            if (!product || !selectedRow) return false;
+            return (
+              product.__tempKey &&
+              selectedRow.__tempKey &&
+              String(product.__tempKey) === String(selectedRow.__tempKey)
+            );
+          });
+          if (index !== -1) {
+            productData.value.splice(index, 1);
+          }
+        });
+        appendEditablePlaceholderRow();
+      }
       let ids = [];
-      if (productSelectedRows.value.length > 0) {
-        ids = productSelectedRows.value.map(item => item.id);
+      const persistedRows = productSelectedRows.value.filter(
+        item => !item?.__placeholder && item?.id !== null && item?.id !== undefined
+      );
+      if (persistedRows.length > 0) {
+        ids = persistedRows.map(item => item.id);
+      } else {
+        return;
       }
       ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
         confirmButtonText: "纭",
@@ -6031,6 +6194,10 @@
     margin-bottom: 16px;
   }
 
+  .sales-ledger-page-header-content {
+    flex: 1;
+  }
+
   .sales-ledger-page-title {
     font-size: 22px;
     font-weight: 600;
@@ -6039,7 +6206,7 @@
   }
 
   .sales-ledger-page-back {
-    margin-bottom: 12px;
+    flex-shrink: 0;
   }
 
   .sales-ledger-page-subtitle {
@@ -6059,6 +6226,50 @@
     justify-content: center;
     gap: 16px;
     margin-top: 24px;
+  }
+
+  .compact-product-table {
+    :deep(.el-table__cell) {
+      padding: 4px 0;
+      font-size: 12px;
+    }
+
+    :deep(.cell) {
+      line-height: 1.2;
+    }
+
+    :deep(.el-input__wrapper) {
+      padding: 0 8px;
+    }
+
+    :deep(.el-input-number .el-input__wrapper) {
+      padding-left: 8px;
+      padding-right: 34px;
+    }
+
+    :deep(.el-input-number),
+    :deep(.el-select),
+    :deep(.el-input),
+    :deep(.el-tree-select) {
+      font-size: 12px;
+    }
+
+    :deep(.el-input-number .el-input__inner) {
+      height: 28px;
+      font-size: 12px;
+      text-align: left;
+    }
+
+    :deep(.el-select .el-input__inner),
+    :deep(.el-input .el-input__inner) {
+      height: 28px;
+    }
+
+    :deep(.el-button--small),
+    :deep(.el-button.is-link) {
+      font-size: 12px;
+      padding: 2px 4px;
+    }
   }
 
   .ledger-qr-dialog {
@@ -6152,4 +6363,4 @@
     margin-right: 0;
     margin-bottom: 8px;
   }
-</style>
+</style>
\ No newline at end of file

--
Gitblit v1.9.3