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 => {
            // 拼接显示文本:model - 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 => {
                     // 拼接显示文本:model - 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>
</style>