liding
2026-04-07 c0790dfa4a3a7a30be3c6ecdf94799267e46948d
src/views/procurementManagement/procurementLedger/index.vue
@@ -4,42 +4,65 @@
      <div>
        <el-form :model="searchForm" :inline="true">
          <el-form-item label="供应商名称:">
            <el-input v-model="searchForm.supplierName" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
            <el-input
              v-model="searchForm.supplierName"
              placeholder="请输入"
              clearable
              prefix-icon="Search"
              @change="handleQuery"
            />
          </el-form-item>
          <el-form-item label="采购合同号:">
            <el-input
                v-model="searchForm.purchaseContractNumber"
                style="width: 240px"
                placeholder="请输入"
                @change="handleQuery"
                clearable
                :prefix-icon="Search"
              v-model="searchForm.purchaseContractNumber"
              style="width: 240px"
              placeholder="请输入"
              @change="handleQuery"
              clearable
              :prefix-icon="Search"
            />
          </el-form-item>
          <el-form-item label="销售合同号:">
            <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
            <el-input
              v-model="searchForm.salesContractNo"
              placeholder="请输入"
              clearable
              prefix-icon="Search"
              @change="handleQuery"
            />
          </el-form-item>
          <el-form-item label="项目名称:">
            <el-input v-model="searchForm.projectName" placeholder="请输入" clearable prefix-icon="Search"
                      @change="handleQuery" />
            <el-input
              v-model="searchForm.projectName"
              placeholder="请输入"
              clearable
              prefix-icon="Search"
              @change="handleQuery"
            />
          </el-form-item>
          <el-form-item label="录入日期:">
            <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                            placeholder="请选择" clearable @change="changeDaterange" />
            <el-date-picker
              v-model="searchForm.entryDate"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              type="daterange"
              placeholder="请选择"
              clearable
              @change="changeDaterange"
            />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="handleQuery"> 搜索 </el-button>
          </el-form-item>
        </el-form>
      </div>
    </div>
    <div class="table_list">
      <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
      <div
        style="display: flex; justify-content: flex-end; margin-bottom: 20px"
      >
        <el-button type="primary" @click="openForm('add')">新增台账</el-button>
        <el-button type="success" @click="openScanAddDialog">扫码新增</el-button>
        <el-button type="primary" plain @click="handleImport">导入</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
@@ -53,7 +76,7 @@
        show-summary
        :summary-method="summarizeMainTable"
        @expand-change="expandChange"
        height="calc(100vh - 18.5em)"
        height="calc(100vh - 21.5em)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column type="expand">
@@ -73,6 +96,7 @@
              <el-table-column label="产品大类" prop="productCategory" />
              <el-table-column label="规格型号" prop="specificationModel" />
              <el-table-column label="单位" prop="unit" />
              <el-table-column label="料号" prop="materialCode" />
              <el-table-column label="数量" prop="quantity" />
              <el-table-column label="税率(%)" prop="taxRate" />
              <el-table-column
@@ -97,25 +121,46 @@
        <el-table-column
          label="采购合同号"
          prop="purchaseContractNumber"
          width="200"
          width="160"
          show-overflow-tooltip
        />
        <el-table-column
          label="销售合同号"
          prop="salesContractNo"
          width="200"
          width="160"
          show-overflow-tooltip
        />
        <el-table-column
          label="供应商名称"
          width="240"
          prop="supplierName"
          width="160"
          show-overflow-tooltip
        />
        <el-table-column
          label="项目名称"
          prop="projectName"
          width="420"
          width="320"
          show-overflow-tooltip
        />
        <el-table-column
          label="审批状态"
          prop="approvalStatus"
          width="100"
          show-overflow-tooltip
        >
          <template #default="scope">
            <el-tag
              :type="getApprovalStatusType(scope.row.approvalStatus)"
              size="small"
            >
              {{ approvalStatusText[scope.row.approvalStatus] || "未知状态" }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="签订日期"
          prop="executionDate"
          width="100"
          show-overflow-tooltip
        />
        <el-table-column
@@ -127,45 +172,47 @@
        <el-table-column
          label="合同金额(元)"
          prop="contractAmount"
           width="200"
          width="200"
          show-overflow-tooltip
          :formatter="formattedNumber"
        />
        <el-table-column
          label="录入人"
          prop="recorderName"
           width="100"
          width="120"
          show-overflow-tooltip
        />
        <el-table-column
          label="录入日期"
          prop="entryDate"
           width="100"
          width="100"
          show-overflow-tooltip
        />
        <el-table-column
          fixed="right"
          label="操作"
          min-width="150"
          align="center"
        >
          label="备注"
          prop="remarks"
          width="200"
          show-overflow-tooltip
        />
        <el-table-column fixed="right" label="操作" width="120" align="center">
          <template #default="scope">
            <el-button
              link
              type="primary"
              size="small"
              @click="openForm('edit', scope.row)"
                     :disabled="scope.row.receiptPaymentAmount>0 || scope.row.recorderName !== userStore.nickName"
              :disabled="
                scope.row.approvalStatus !== 1 && scope.row.approvalStatus !== 4
              "
              >编辑</el-button
            >
            <el-button
              link
              type="success"
              type="primary"
              size="small"
              @click="showQRCode(scope.row)"
              >生成二维码</el-button
              @click="downLoadFile(scope.row)"
              >附件</el-button
            >
          </template>
        </el-table-column>
      </el-table>
@@ -178,11 +225,14 @@
        @pagination="paginationChange"
      />
    </div>
    <el-dialog
    <FormDialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增采购台账页面' : '编辑采购台账页面'"
      width="70%"
      :width="'70%'"
      :operation-type="operationType"
      @close="closeDia"
      @confirm="submitForm"
      @cancel="closeDia"
    >
      <el-form
        :model="form"
@@ -206,6 +256,7 @@
              <el-select
                v-model="form.salesLedgerId"
                placeholder="请选择"
                filterable
                clearable
                @change="salesLedgerChange"
              >
@@ -225,6 +276,7 @@
              <el-select
                v-model="form.supplierId"
                placeholder="请选择"
                filterable
                clearable
              >
                <el-option
@@ -232,48 +284,18 @@
                  :key="item.id"
                  :label="item.supplierName"
                  :value="item.id"
                />
                  >{{
                    item.supplierName + "---" + item.supplierType
                  }}</el-option
                >
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
            <el-form-item label="项目名称" prop="projectName">
              <el-input
                v-model="form.projectName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="录入人:" prop="recorderId">
              <el-select
                v-model="form.recorderId"
                placeholder="请选择"
                clearable
                disabled
              >
                <el-option
                  v-for="item in userList"
                  :key="item.userId"
                  :label="item.nickName"
                  :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入日期:" prop="entryDate">
              <el-date-picker
                disabled
                style="width: 100%"
                v-model="form.entryDate"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择"
                clearable
              />
            </el-form-item>
@@ -289,6 +311,111 @@
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="签订日期:" prop="executionDate">
              <el-date-picker
                style="width: 100%"
                v-model="form.executionDate"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="录入人:" prop="recorderId">
              <el-select
                v-model="form.recorderId"
                placeholder="请选择"
                clearable
                filterable
              >
                <el-option
                  v-for="item in userList"
                  :key="item.userId"
                  :label="item.nickName"
                  :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入日期:" prop="entryDate">
              <el-date-picker
                style="width: 100%"
                v-model="form.entryDate"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item>
              <template #label>
                <div
                  style="
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    width: 100%;
                  "
                >
                  <span>审批人选择:</span>
                  <el-button
                    type="primary"
                    size="small"
                    @click="addApproverNode"
                    icon="Plus"
                    >新增节点</el-button
                  >
                </div>
              </template>
              <div class="approver-nodes-container">
                <div
                  v-for="(node, index) in approverNodes"
                  :key="node.id"
                  class="approver-node-item"
                >
                  <div class="approver-node-header">
                    <span class="approver-node-label"
                      >审批节点 {{ index + 1 }}</span
                    >
                    <el-button
                      v-if="approverNodes.length > 1"
                      type="danger"
                      size="small"
                      text
                      @click="removeApproverNode(index)"
                      icon="Delete"
                      >删除</el-button
                    >
                  </div>
                  <el-select
                    v-model="node.userId"
                    placeholder="请选择审批人"
                    filterable
                    style="width: 100%"
                  >
                    <el-option
                      v-for="user in userList"
                      :key="user.userId"
                      :label="user.nickName"
                      :value="user.userId"
                    />
                  </el-select>
                </div>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-form-item label="产品信息:" prop="entryDate">
@@ -299,6 +426,70 @@
              >删除</el-button
            >
          </el-form-item>
          <div
            class="select-button-group"
            style="width: 500px; margin: 20px 0"
            v-if="operationType === 'add'"
          >
            <el-select
              filterable
              allow-create
              :reserve-keyword="true"
              :default-first-option="false"
              clearable
              v-model="templateName"
              :input-value="filterInputValue"
              @filter-change="onTemplateFilterChange"
              @change="onTemplateChange"
              @focus="getTemplateList"
              style="width: 500px"
              placeholder="请选择模版或者输入新的模版名称后选择"
              class="no-arrow-select"
            >
              <el-option
                v-for="item in templateList"
                :key="item.id || item.value"
                :label="item.templateName"
                :value="item.templateName"
              >
                <div
                  style="
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                  "
                >
                  <span>{{ item.templateName }}</span>
                  <el-icon
                    v-if="item.id"
                    class="delete-icon"
                    @click.stop="handleDeleteTemplate(item)"
                    style="
                      cursor: pointer;
                      color: #f56c6c;
                      font-size: 14px;
                      margin-left: 8px;
                    "
                  >
                    <Delete />
                  </el-icon>
                </div>
              </el-option>
            </el-select>
            <!-- 按钮:与 Select 高度匹配,去掉左侧边框,无缝衔接 -->
            <el-button
              size="small"
              style="height: 32px; margin-left: 8px"
              @click="handleButtonClick"
              :disabled="
                !templateName ||
                templateName.trim() === '' ||
                (!currentTemplateId && isTemplateNameDuplicate)
              "
            >
              保存
            </el-button>
          </div>
        </el-row>
        <el-table
          :data="productData"
@@ -316,9 +507,16 @@
          />
          <el-table-column label="产品大类" prop="productCategory" />
          <el-table-column label="规格型号" prop="specificationModel" />
          <el-table-column label="料号" prop="materialCode" />
          <el-table-column label="批号" prop="batchNo" />
          <el-table-column label="单位" prop="unit" width="70" />
          <el-table-column label="数量" prop="quantity" width="70" />
               <el-table-column label="库存预警数量" prop="warnNum" width="120" show-overflow-tooltip />
          <el-table-column
            label="库存预警数量"
            prop="warnNum"
            width="120"
            show-overflow-tooltip
          />
          <el-table-column label="税率(%)" prop="taxRate" width="80" />
          <el-table-column
            label="含税单价(元)"
@@ -338,6 +536,15 @@
            :formatter="formattedNumber"
            width="150"
          />
          <!--          <el-table-column label="是否质检"-->
          <!--                           prop="isChecked"-->
          <!--                           width="150">-->
          <!--            <template #default="scope">-->
          <!--              <el-tag :type="scope.row.isChecked ? 'success' : 'info'">-->
          <!--                {{ scope.row.isChecked ? '是' : '否' }}-->
          <!--              </el-tag>-->
          <!--            </template>-->
          <!--          </el-table-column>-->
          <el-table-column
            fixed="right"
            label="操作"
@@ -357,9 +564,9 @@
        </el-table>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="备注·:" prop="remark">
            <el-form-item label="备注·:" prop="remarks">
              <el-input
                v-model="form.remark"
                v-model="form.remarks"
                placeholder="请输入"
                clearable
                type="textarea"
@@ -370,7 +577,7 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:" prop="remark">
            <el-form-item label="附件材料:" prop="purchaseLedgerFiles">
              <el-upload
                v-model:file-list="fileList"
                :action="upload.url"
@@ -395,18 +602,50 @@
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog
    </FormDialog>
    <!-- 导入弹窗 -->
    <FormDialog
      v-model="importUpload.open"
      :title="importUpload.title"
      :width="'600px'"
      @close="importUpload.open = false"
      @confirm="submitImportFile"
      @cancel="importUpload.open = false"
    >
      <el-upload
        ref="importUploadRef"
        :limit="1"
        accept=".xlsx,.xls"
        :action="importUpload.url"
        :headers="importUpload.headers"
        :before-upload="importUpload.beforeUpload"
        :on-success="importUpload.onSuccess"
        :on-error="importUpload.onError"
        :on-progress="importUpload.onProgress"
        :on-change="importUpload.onChange"
        :auto-upload="false"
        drag
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <template #tip>
          <div class="el-upload__tip">
            仅支持 xls/xlsx,大小不超过 10MB。
            <el-button link type="primary" @click="downloadTemplate"
              >下载导入模板</el-button
            >
          </div>
        </template>
      </el-upload>
    </FormDialog>
    <FormDialog
      v-model="productFormVisible"
      :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
      width="40%"
      :width="'40%'"
      :operation-type="productOperationType"
      @close="closeProductDia"
      @confirm="submitProduct"
      @cancel="closeProductDia"
    >
      <el-form
        :model="productForm"
@@ -449,6 +688,25 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="料号:" prop="materialCode">
              <el-input
                v-model="productForm.materialCode"
                placeholder="请输入"
                disabled
              />
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="批号:" prop="batchNo">
              <el-input
                v-model="productForm.batchNo"
                clearable
                placeholder="选填,可输入批号;留空将自动生成"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
@@ -460,20 +718,20 @@
              />
            </el-form-item>
          </el-col>
               <el-col :span="12">
                  <el-form-item label="税率(%):" prop="taxRate">
                     <el-select
                        v-model="productForm.taxRate"
                        placeholder="请选择"
                        clearable
                        @change="mathNum"
                     >
                        <el-option label="1" value="1" />
                        <el-option label="6" value="6" />
                        <el-option label="13" value="13" />
                     </el-select>
                  </el-form-item>
               </el-col>
          <el-col :span="12">
            <el-form-item label="税率(%):" prop="taxRate">
              <el-select
                v-model="productForm.taxRate"
                placeholder="请选择"
                clearable
                @change="mathNum"
              >
                <el-option label="1" value="1" />
                <el-option label="6" value="6" />
                <el-option label="13" value="13" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
@@ -482,25 +740,27 @@
                v-model="productForm.taxInclusiveUnitPrice"
                :precision="2"
                :step="0.1"
                :min="0"
                clearable
                style="width: 100%"
                @change="mathNum"
              />
            </el-form-item>
          </el-col>
               <el-col :span="12">
                  <el-form-item label="数量:" prop="quantity">
                     <el-input-number
                        :step="0.1"
                        clearable
                        :precision="2"
                        style="width: 100%"
                        v-model="productForm.quantity"
                        placeholder="请输入"
                        @change="mathNum"
                     />
                  </el-form-item>
               </el-col>
          <el-col :span="12">
            <el-form-item label="数量:" prop="quantity">
              <el-input-number
                :step="0.1"
                clearable
                :precision="2"
                :min="0"
                style="width: 100%"
                v-model="productForm.quantity"
                placeholder="请输入"
                @change="mathNum"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
@@ -509,6 +769,7 @@
                v-model="productForm.taxInclusiveTotalPrice"
                :precision="2"
                :step="0.1"
                :min="0"
                clearable
                style="width: 100%"
                @change="reverseMathNum('taxInclusiveTotalPrice')"
@@ -520,8 +781,13 @@
              label="不含税总价(元):"
              prop="taxExclusiveTotalPrice"
            >
              <el-input
              <el-input-number
                v-model="productForm.taxExclusiveTotalPrice"
                :precision="2"
                :step="0.1"
                :min="0"
                clearable
                style="width: 100%"
                @change="reverseMathNum('taxExclusiveTotalPrice')"
              />
            </el-form-item>
@@ -540,251 +806,58 @@
              </el-select>
            </el-form-item>
          </el-col>
               <el-col :span="12">
                  <el-form-item label="库存预警数量:" prop="warnNum">
                     <el-input-number
                        v-model="productForm.warnNum"
                        :precision="2"
                        :step="0.1"
                        clearable
                        style="width: 100%"
                     />
                  </el-form-item>
               </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitProduct">确认</el-button>
          <el-button @click="closeProductDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 二维码显示对话框 -->
    <el-dialog
      v-model="qrCodeDialogVisible"
      title="采购合同号二维码"
      width="400px"
      center
    >
      <div style="text-align: center;">
        <img :src="qrCodeUrl" alt="二维码" style="width:200px;height:200px;" />
        <div style="margin: 20px;">
          <el-button type="primary" @click="downloadQRCode">下载二维码图片</el-button>
        </div>
      </div>
    </el-dialog>
    <!-- 扫码新增对话框 -->
    <el-dialog
      v-model="scanAddDialogVisible"
      title="扫码新增采购台账"
      width="70%"
      @close="closeScanAddDialog"
    >
      <el-form
        :model="scanAddForm"
        label-width="140px"
        label-position="top"
        :rules="scanAddRules"
        ref="scanAddFormRef"
      >
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码内容:">
              <el-input
                v-model="scanAddForm.scanContent"
                type="textarea"
                :rows="3"
                placeholder="请扫描二维码或手动输入采购合同信息"
                @input="parseScanContent"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="采购合同号:" prop="purchaseContractNumber">
              <el-input
                v-model="scanAddForm.purchaseContractNumber"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                v-model="scanAddForm.supplierName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="项目名称:" prop="projectName">
              <el-input
                v-model="scanAddForm.projectName"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="合同金额(元):" prop="contractAmount">
            <el-form-item label="库存预警数量:" prop="warnNum">
              <el-input-number
                v-model="scanAddForm.contractAmount"
                v-model="productForm.warnNum"
                :precision="2"
                :step="0.1"
                :min="0"
                clearable
                style="width: 100%"
                placeholder="请输入"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="付款方式:">
              <el-input
                v-model="scanAddForm.paymentMethod"
                placeholder="请输入"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入人:">
              <el-input v-model="scanAddForm.recorderName" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注:">
              <el-input
                v-model="scanAddForm.remark"
                type="textarea"
                :rows="2"
                placeholder="请输入备注信息"
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <!--        <el-row :gutter="30">-->
        <!--          <el-col :span="12">-->
        <!--            <el-form-item label="是否质检:"-->
        <!--                          prop="isChecked">-->
        <!--              <el-radio-group v-model="productForm.isChecked">-->
        <!--                <el-radio label="是"-->
        <!--                          :value="true" />-->
        <!--                <el-radio label="否"-->
        <!--                          :value="false" />-->
        <!--              </el-radio-group>-->
        <!--            </el-form-item>-->
        <!--          </el-col>-->
        <!--        </el-row>-->
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitScanAdd">确认新增</el-button>
          <el-button @click="closeScanAddDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 扫码登记对话框 -->
    <el-dialog
      v-model="scanDialogVisible"
      title="扫码登记"
      width="60%"
      @close="closeScanDialog"
    >
      <el-form
        :model="scanForm"
        label-width="120px"
        label-position="left"
        :rules="scanRules"
        ref="scanFormRef"
      >
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="采购合同号:">
              <el-input v-model="scanForm.purchaseContractNumber" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="供应商名称:">
              <el-input v-model="scanForm.supplierName" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="项目名称:">
              <el-input v-model="scanForm.projectName" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="扫码时间:">
              <el-input v-model="scanForm.scanTime" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="扫码人:">
              <el-input v-model="scanForm.scannerName" disabled />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="扫码状态:">
              <el-tag :type="scanForm.scanStatus === '已扫码' ? 'success' : 'warning'">
                {{ scanForm.scanStatus }}
              </el-tag>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码备注:">
              <el-input
                v-model="scanForm.scanRemark"
                type="textarea"
                :rows="3"
                placeholder="请输入扫码备注信息"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="扫码记录:">
              <el-table :data="scanRecords" border style="width: 100%">
                <el-table-column label="序号" type="index" width="60" align="center" />
                <el-table-column label="扫码时间" prop="scanTime" width="180" />
                <el-table-column label="扫码人" prop="scannerName" width="120" />
                <el-table-column label="扫码状态" prop="scanStatus" width="100">
                  <template #default="scope">
                    <el-tag :type="scope.row.scanStatus === '已扫码' ? 'success' : 'warning'">
                      {{ scope.row.scanStatus }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="备注" prop="scanRemark" />
              </el-table>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitScan">确认扫码</el-button>
          <el-button @click="closeScanDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
    </FormDialog>
    <FileListDialog
      ref="fileListRef"
      v-model="fileListDialogVisible"
      title="附件列表"
    />
  </div>
</template>
<script setup>
import { getToken } from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import {
  ref,
  onMounted,
  reactive,
  toRefs,
  getCurrentInstance,
  nextTick,
} from "vue";
import { Search, Delete } from "@element-plus/icons-vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import FileListDialog from "@/components/Dialog/FileListDialog.vue";
import {
  getSalesLedgerWithProducts,
  addOrUpdateSalesLedgerProduct,
@@ -794,16 +867,20 @@
} from "@/api/salesManagement/salesLedger.js";
import {
  addOrEditPurchase,
  addPurchaseTemplate,
  updatePurchaseTemplate,
  createPurchaseNo,
  delPurchase,
  getSalesNo,
  purchaseListPage,
  productList,
  getPurchaseById,
  getOptions,
  createPurchaseNo,
  getPurchaseTemplateList,
  delPurchaseTemplate,
} from "@/api/procurementManagement/procurementLedger.js";
import useFormData from "@/hooks/useFormData.js";
import QRCode from "qrcode";
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const productData = ref([]);
@@ -827,9 +904,136 @@
const userStore = useUserStore();
// 二维码相关变量
const qrCodeDialogVisible = ref(false);
const qrCodeUrl = ref("");
// 审批人节点(仿销售台账发货审批人)
const approverNodes = ref([{ id: 1, userId: null }]);
let nextApproverId = 2;
const addApproverNode = () => {
  approverNodes.value.push({ id: nextApproverId++, userId: null });
};
const removeApproverNode = (index) => {
  approverNodes.value.splice(index, 1);
};
// 订单审批状态显示文本
const approvalStatusText = {
  1: "待审核",
  2: "审批中",
  3: "审批通过",
  4: "审批失败",
};
// 获取审批状态标签类型
const getApprovalStatusType = (status) => {
  const typeMap = {
    1: "info", // 待审核 - 灰色
    2: "warning", // 审批中 - 橙色
    3: "success", // 审批通过 - 绿色
    4: "danger", // 审批失败 - 红色
  };
  return typeMap[status] || "";
};
const templateName = ref("");
const filterInputValue = ref("");
const templateList = ref([]);
const isTemplateNameDuplicate = ref(false); // 标记模板名称是否重复
// 当前选中的模板ID(用于区分新增模板还是更新模板)
const currentTemplateId = ref(null);
// 检查模板名称是否重复
const checkTemplateNameDuplicate = (name) => {
  if (!name || name.trim() === "") {
    isTemplateNameDuplicate.value = false;
    return false;
  }
  const isDuplicate = templateList.value.some(
    (item) => item.templateName === name.trim()
  );
  isTemplateNameDuplicate.value = isDuplicate;
  return isDuplicate;
};
// 防抖定时器
let duplicateCheckTimer = null;
const onTemplateFilterChange = (val) => {
  filterInputValue.value = val ?? "";
  // 清除之前的定时器
  if (duplicateCheckTimer) {
    clearTimeout(duplicateCheckTimer);
  }
  // 实时检查模板名称是否重复(防抖处理,避免频繁提示)
  if (val && val.trim()) {
    duplicateCheckTimer = setTimeout(() => {
      const isDuplicate = checkTemplateNameDuplicate(val);
      if (isDuplicate) {
        ElMessage({
          message: "模板名称已存在,请更换模板名称",
          type: "warning",
          duration: 2000,
        });
      }
    }, 300); // 300ms 防抖
  } else {
    isTemplateNameDuplicate.value = false;
  }
};
// allow-create 时,输入不存在的内容会作为 string 值返回;这里同步回输入框以确保文字不丢
const onTemplateChange = async (val) => {
  if (typeof val === "string") {
    filterInputValue.value = val;
    // 选择或输入时检查重复
    checkTemplateNameDuplicate(val);
  }
  // 过滤数据,查找匹配的模板
  const matchedTemplate = templateList.value.find(
    (item) => item.templateName === val
  );
  if (matchedTemplate?.id) {
    // 记录当前选中的模板ID,后续保存时进行更新操作
    currentTemplateId.value = matchedTemplate.id;
    // 选中已有模板时,不应视为“模板名称重复导致不可保存”
    isTemplateNameDuplicate.value = false;
    // 如果找到模板,只赋值供应商、项目名称、付款方式和产品信息
    if (matchedTemplate.supplierId) {
      form.value.supplierId = matchedTemplate.supplierId;
    }
    if (matchedTemplate.supplierName) {
      form.value.supplierName = matchedTemplate.supplierName;
    }
    if (matchedTemplate.projectName) {
      form.value.projectName = matchedTemplate.projectName;
    }
    if (matchedTemplate.paymentMethod) {
      form.value.paymentMethod = matchedTemplate.paymentMethod;
    }
    // 模板数据中的产品字段是 productList,需要转换为 productData
    productData.value =
      matchedTemplate.productList || matchedTemplate.productData || [];
  } else {
    // 未匹配到已有模板,视为新模板
    currentTemplateId.value = null;
    // 如果没有找到模板,重置表单(保持当前表单状态)
    const currentFormData = { ...form.value };
    const currentProductData = [...productData.value];
    // 如果对话框未打开,先打开
    if (!dialogFormVisible.value) {
      operationType.value = "add";
      dialogFormVisible.value = true;
    }
    // 等待下一个 tick 后恢复数据
    await nextTick();
    form.value = {
      ...form.value,
      ...currentFormData,
    };
    productData.value = currentProductData;
  }
};
// 用户信息表单弹框数据
const operationType = ref("");
@@ -854,17 +1058,35 @@
    supplierName: "",
    supplierId: "",
    paymentMethod: "",
    executionDate: "",
    isChecked: true,
  },
  rules: {
    purchaseContractNumber: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    projectName: [{ required: true, message: "请输入", trigger: "blur" }],
    approverId: [
      { required: true, message: "请选择审批人", trigger: "change" },
    ],
    projectName: [
      { required: true, message: "请输入项目名称", trigger: "blur" },
    ],
    supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
    entryDate: [{ required: true, message: "请选择", trigger: "change" }],
    executionDate: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const {  form, rules } = toRefs(data);
const { form: searchForm } = useFormData(data.searchForm);
const { form, rules } = toRefs(data);
const { form: searchForm } = useFormData({
  ...data.searchForm,
  // 设置录入日期范围为当天
  entryDate: [
    dayjs().startOf("day").format("YYYY-MM-DD"),
    dayjs().endOf("day").format("YYYY-MM-DD"),
  ],
  entryDateStart: dayjs().startOf("day").format("YYYY-MM-DD"),
  entryDateEnd: dayjs().endOf("day").format("YYYY-MM-DD"),
});
// 产品表单弹框数据
const productFormVisible = ref(false);
@@ -877,6 +1099,8 @@
    productCategory: "",
    productModelId: "",
    specificationModel: "",
    materialCode: "",
    batchNo: "",
    unit: "",
    quantity: "",
    taxInclusiveUnitPrice: "",
@@ -884,7 +1108,8 @@
    taxInclusiveTotalPrice: "",
    taxExclusiveTotalPrice: "",
    invoiceType: "",
      warnNum: "",
    warnNum: "",
    isChecked: true,
  },
  productRules: {
    productId: [{ required: true, message: "请选择", trigger: "change" }],
@@ -895,7 +1120,7 @@
      { required: true, message: "请输入", trigger: "blur" },
    ],
    taxRate: [{ required: true, message: "请选择", trigger: "change" }],
      warnNum: [{ required: true, message: "请选择", trigger: "change" }],
    warnNum: [{ required: true, message: "请选择", trigger: "change" }],
    taxInclusiveTotalPrice: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
@@ -903,6 +1128,7 @@
      { required: true, message: "请输入", trigger: "blur" },
    ],
    invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
    isChecked: [{ required: true, message: "请选择", trigger: "change" }],
  },
});
const { productForm, productRules } = toRefs(productFormData);
@@ -912,6 +1138,75 @@
  // 设置上传的请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
// 导入相关
const importUploadRef = ref(null);
const importUpload = reactive({
  title: "导入采购台账",
  open: false,
  url: import.meta.env.VITE_APP_BASE_API + "/purchase/ledger/import",
  headers: { Authorization: "Bearer " + getToken() },
  isUploading: false,
  beforeUpload: (file) => {
    const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
    const isLt10M = file.size / 1024 / 1024 < 10;
    if (!isExcel) {
      proxy.$modal.msgError("上传文件只能是 xlsx/xls 格式!");
      return false;
    }
    if (!isLt10M) {
      proxy.$modal.msgError("上传文件大小不能超过 10MB!");
      return false;
    }
    return true;
  },
  onChange: (file, fileList) => {
    // noop
  },
  onProgress: (event, file, fileList) => {
    // noop
  },
  onSuccess: (response, file, fileList) => {
    importUpload.isUploading = false;
    if (response?.code === 200) {
      proxy.$modal.msgSuccess("导入成功");
      importUpload.open = false;
      if (importUploadRef.value) {
        importUploadRef.value.clearFiles?.();
      }
      getList();
    } else {
      proxy.$modal.msgError(response?.msg || "导入失败");
    }
  },
  onError: () => {
    importUpload.isUploading = false;
    proxy.$modal.msgError("导入失败,请重试");
  },
});
const handleImport = () => {
  importUpload.title = "导入采购台账";
  importUpload.open = true;
  importUpload.isUploading = false;
  if (importUploadRef.value) {
    importUploadRef.value.clearFiles?.();
  }
};
// 下载导入模板(如后端路径不同,可在此处调整)
const downloadTemplate = () => {
  proxy.download(
    "/purchase/ledger/exportTemplate",
    {},
    "采购台账导入模板.xlsx"
  );
};
const submitImportFile = () => {
  importUpload.isUploading = true;
  proxy.$refs["importUploadRef"]?.submit?.();
};
const changeDaterange = (value) => {
  if (value) {
@@ -932,6 +1227,111 @@
const handleQuery = () => {
  page.current = 1;
  getList();
};
// 保存模板
const handleButtonClick = async () => {
  // 检查模板名称是否为空
  if (!templateName.value || templateName.value.trim() === "") {
    ElMessage({
      message: "请输入模板名称",
      type: "warning",
    });
    return;
  }
  // 如果是“新增模板”(没有选中已有模板),才需要做重名校验;
  // 若是选中已有模板后修改,则允许使用原名称(视为更新)
  if (!currentTemplateId.value) {
    const isDuplicate = checkTemplateNameDuplicate(templateName.value);
    if (isDuplicate) {
      ElMessage({
        message: "模板名称已存在,请更换模板名称",
        type: "warning",
      });
      return;
    }
  }
  // 检查供应商是否选择
  if (!form.value.supplierId) {
    ElMessage({
      message: "请先选择供应商",
      type: "warning",
    });
    return;
  }
  // 检查是否有产品数据
  if (!productData.value || productData.value.length === 0) {
    ElMessage({
      message: "请先添加产品信息",
      type: "warning",
    });
    return;
  }
  try {
    // 获取审批人ID字符串
    const approveUserIds = approverNodes.value
      .filter((node) => node.userId)
      .map((node) => node.userId)
      .join(",");
    let params = {
      productData: proxy.HaveJson(productData.value),
      supplierId: form.value.supplierId,
      paymentMethod: form.value.paymentMethod,
      recorderId: form.value.recorderId,
      projectName: form.value.projectName,
      approveUserIds: approveUserIds,
      templateName: templateName.value.trim(),
    };
    console.log(
      "template params ===>",
      params,
      "currentTemplateId:",
      currentTemplateId.value
    );
    // 如果 currentTemplateId 有值,说明当前是“编辑已有模板” → 调用更新接口
    // 否则为“新建模板” → 调用新增接口
    let res;
    if (currentTemplateId.value) {
      res = await updatePurchaseTemplate({
        id: currentTemplateId.value,
        ...params,
      });
    } else {
      res = await addPurchaseTemplate(params);
    }
    if (res && res.code === 200) {
      ElMessage({
        message: currentTemplateId.value ? "模板更新成功" : "模板保存成功",
        type: "success",
      });
      // 保存成功后重新获取模板列表
      await getTemplateList();
      // 清空模板名称输入
      templateName.value = "";
      filterInputValue.value = "";
      isTemplateNameDuplicate.value = false;
      // 保存/更新完成后,重置当前模板ID
      currentTemplateId.value = null;
    } else {
      ElMessage({
        message: res?.msg || "模板保存失败",
        type: "error",
      });
    }
  } catch (error) {
    console.error("保存模板失败:", error);
    ElMessage({
      message: "模板保存失败,请稍后重试",
      type: "error",
    });
  }
};
// 子表合计方法
const summarizeChildrenTable = (param) => {
@@ -963,8 +1363,12 @@
  purchaseListPage({ ...rest, ...page })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = res.data.records;
      tableData.value.map((item) => {
      // tableData.value = res.data.records;
      tableData.value = res.data.records.map((record) => ({
        ...record,
      }));
      // 初始化子数据数组
      tableData.value.forEach((item) => {
        item.children = [];
      });
      total.value = res.data.total;
@@ -983,19 +1387,24 @@
};
const expandedRowKeys = ref([]);
// 展开行
const expandChange = (row, expandedRows) => {
const expandChange = async (row, expandedRows) => {
  if (expandedRows.length > 0) {
    expandedRowKeys.value = [];
    try {
      productList({ salesLedgerId: row.id, type: 2 }).then((res) => {
        const index = tableData.value.findIndex((item) => item.id === row.id);
        if (index > -1) {
          tableData.value[index].children = res.data;
        }
      const res = await productList({ salesLedgerId: row.id, type: 2 });
      const index = tableData.value.findIndex((item) => item.id === row.id);
      if (index > -1) {
        tableData.value[index].children = res.data || [];
        expandedRowKeys.value.push(row.id);
      });
      }
    } catch (error) {
      console.log(error);
      console.error("加载产品列表失败:", error);
      proxy.$modal.msgError("加载产品列表失败");
      // 展开失败时,移除展开状态
      const index = expandedRows.findIndex((item) => item.id === row.id);
      if (index > -1) {
        expandedRows.splice(index, 1);
      }
    }
  } else {
    expandedRowKeys.value = [];
@@ -1014,40 +1423,89 @@
  ]);
};
// 打开弹框
const openForm = (type, row) => {
const openForm = async (type, row) => {
  // 编辑时检查审核状态,只有待审核(1)和审批失败(4)才能编辑
  if (type === "edit" && row) {
    if (row.approvalStatus !== 1 && row.approvalStatus !== 4) {
      proxy.$modal.msgWarning("只有待审核和审批失败状态的记录才能编辑");
      return;
    }
  }
  await getTemplateList();
  operationType.value = type;
  form.value = {};
  productData.value = [];
  fileList.value = [];
  if (operationType.value == "add") {
    createPurchaseNo().then((res) => {
      form.value.purchaseContractNumber = res.data;
    });
  }
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
  getSalesNo().then((res) => {
    salesContractList.value = res;
  });
  getOptions().then((res) => {
    supplierList.value = res.data;
  });
  form.value.recorderId = userStore.id;
  form.value.entryDate = getCurrentDate();
  if (type === "edit") {
    currentId.value = row.id;
    getPurchaseById({ id: row.id, type: 2 }).then((res) => {
      form.value = { ...res };
      productData.value = form.value.productData;
      if (form.value.salesLedgerFiles) {
        fileList.value = form.value.salesLedgerFiles;
      } else {
        fileList.value = [];
  templateName.value = "";
  filterInputValue.value = "";
  isTemplateNameDuplicate.value = false;
  // 重置审批人节点(默认一个空节点)
  approverNodes.value = [{ id: 1, userId: null }];
  nextApproverId = 2;
  try {
    // 并行加载基础数据
    const [userRes, salesRes, supplierRes] = await Promise.all([
      userListNoPage(),
      getSalesNo(),
      getOptions(),
    ]);
    userList.value = userRes.data || [];
    salesContractList.value = salesRes || [];
    // 供应商过滤出isWhite=0 的数据
    supplierList.value = (supplierRes.data || []).filter(
      (item) => item.isWhite === 0
    );
    // 设置默认值
    form.value.recorderId = userStore.id;
    form.value.entryDate = getCurrentDate();
    if (type === "add") {
      // 新增时生成采购合同号
      try {
        const purchaseNoRes = await createPurchaseNo();
        if (purchaseNoRes?.data) {
          form.value.purchaseContractNumber = purchaseNoRes.data;
        }
      } catch (error) {
        console.error("生成采购合同号失败:", error);
        proxy.$modal.msgWarning("生成采购合同号失败");
      }
    });
    } else if (type === "edit" && row?.id) {
      // 编辑时加载数据
      currentId.value = row.id;
      try {
        const purchaseRes = await getPurchaseById({ id: row.id, type: 2 });
        form.value = { ...purchaseRes };
        productData.value = purchaseRes.productData || [];
        fileList.value = purchaseRes.salesLedgerFiles || [];
        // 如果编辑时有审批人,解析审批人ID字符串并设置到节点中
        if (purchaseRes.approveUserIds) {
          const approverIds = purchaseRes.approveUserIds.split(",");
          approverNodes.value = approverIds.map((id, index) => ({
            id: index + 1,
            userId: Number(id),
          }));
          nextApproverId = approverIds.length + 1;
        }
      } catch (error) {
        console.error("加载采购台账数据失败:", error);
        proxy.$modal.msgError("加载数据失败");
        return;
      }
    }
    if (form.value.salesLedgerId == -1) {
      form.value.salesLedgerId = null;
    }
    console.log(form.value, "form.value===========");
    dialogFormVisible.value = true;
  } catch (error) {
    console.error("打开表单失败:", error);
    proxy.$modal.msgError("加载基础数据失败");
  }
  dialogFormVisible.value = true;
};
// 上传前校检
function handleBeforeUpload(file) {
@@ -1076,26 +1534,50 @@
  }
}
// 移除文件
function handleRemove(file) {
  console.log("handleRemove", file.id);
  if (file.size > 1024 * 1024 * 10) {
    // 仅前端清理,不调用删除接口和提示
    return;
async function handleRemove(file) {
  if (!file?.id) {
    return;
  }
  if (operationType.value === "edit") {
    let ids = [];
    ids.push(file.id);
    delLedgerFile(ids).then((res) => {
  console.log("handleRemove", file.id);
  if (file.size > 1024 * 1024 * 10) {
    // 仅前端清理,不调用删除接口和提示
    return;
  }
  if (operationType.value === "edit" && file.id) {
    try {
      await delLedgerFile([file.id]);
      proxy.$modal.msgSuccess("删除成功");
    });
    } catch (error) {
      console.error("删除文件失败:", error);
      proxy.$modal.msgError("删除文件失败");
    }
  }
}
// 提交表单
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      // 审批人必填校验(所有节点都要选人)
      const hasEmptyApprover = approverNodes.value.some((node) => !node.userId);
      if (hasEmptyApprover) {
        proxy.$modal.msgError("请为所有审批节点选择审批人!");
        return;
      }
      const approveUserIds = approverNodes.value
        .map((node) => node.userId)
        .join(",");
      if (productData.value.length > 0) {
        form.value.productData = proxy.HaveJson(productData.value);
        // 新增时,需要从每个产品对象中删除 id 字段
        let processedProductData = productData.value;
        if (operationType.value === "add") {
          processedProductData = productData.value.map((product) => {
            const { id, ...rest } = product;
            return rest;
          });
        }
        form.value.productData = proxy.HaveJson(processedProductData);
      } else {
        proxy.$modal.msgWarning("请添加产品信息");
        return;
@@ -1106,7 +1588,20 @@
      }
      form.value.tempFileIds = tempFileIds;
      form.value.type = 2;
      addOrEditPurchase(form.value).then((res) => {
      form.value.approveUserIds = approveUserIds;
      // 如果salesLedgerId为空,则不传递salesContractNo
      if (!form.value.salesLedgerId) {
        form.value.salesContractNo = "";
      }
      // 新增时不传递id
      const submitData = { ...form.value };
      if (operationType.value === "add") {
        delete submitData.id;
      }
      addOrEditPurchase(submitData).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeDia();
        getList();
@@ -1117,50 +1612,166 @@
// 关闭弹框
const closeDia = () => {
  proxy.resetForm("formRef");
  // 重置审批人节点(默认一个空节点)
  approverNodes.value = [{ id: 1, userId: null }];
  nextApproverId = 2;
  dialogFormVisible.value = false;
};
// 打开产品弹框
const openProductForm = (type, row, index) => {
const openProductForm = async (type, row, index) => {
  productOperationType.value = type;
  productOperationIndex.value = index;
  productForm.value = {};
  proxy.resetForm("productFormRef");
  if (type === "edit") {
    productForm.value = { ...row };
  }
  productFormVisible.value = true;
  getProductOptions();
  // 先获取产品选项,确保数据加载完成
  await getProductOptions();
  // 等待 DOM 更新
  await nextTick();
  if (type === "edit") {
    // 复制行数据
    productForm.value = { ...row };
    // el-radio-group 的 value 是布尔 true/false
    // 后端/表格数据可能是 0/1 或字符串,需做一次归一化,避免不回显/提交默认“否”
    const normalizeIsChecked = (val) => {
      if (val === true) return true;
      if (val === false) return false;
      if (val === 1 || val === "1") return true;
      if (val === 0 || val === "0") return false;
      if (typeof val === "string") {
        const s = val.trim().toLowerCase();
        if (["是", "yes", "true", "y"].includes(s)) return true;
        if (["否", "no", "false", "n"].includes(s)) return false;
      }
      return !!val;
    };
    productForm.value.isChecked = normalizeIsChecked(row?.isChecked);
    // 如果是从模板加载的数据,可能没有 productId 和 productModelId
    // 需要根据 productCategory 和 specificationModel 来查找对应的 ID
    if (!productForm.value.productId && productForm.value.productCategory) {
      // 根据 productCategory 查找 productId
      const findProductIdByCategory = (nodes, categoryName) => {
        for (let i = 0; i < nodes.length; i++) {
          if (nodes[i].label === categoryName) {
            return nodes[i].value;
          }
          if (nodes[i].children && nodes[i].children.length > 0) {
            const found = findProductIdByCategory(
              nodes[i].children,
              categoryName
            );
            if (found) return found;
          }
        }
        return null;
      };
      const productId = findProductIdByCategory(
        productOptions.value,
        productForm.value.productCategory
      );
      if (productId) {
        productForm.value.productId = productId;
        // 获取型号列表并等待完成
        const modelRes = await modelList({ id: productId });
        modelOptions.value = modelRes;
        // 等待 DOM 更新
        await nextTick();
        // 根据 specificationModel 查找 productModelId
        if (
          productForm.value.specificationModel &&
          modelOptions.value.length > 0
        ) {
          const modelItem = modelOptions.value.find(
            (item) => item.model === productForm.value.specificationModel
          );
          if (modelItem) {
            productForm.value.productModelId = modelItem.id;
            // 设置规格型号和单位
            getProductModel(modelItem.id);
          }
        }
      }
    } else if (productForm.value.productId) {
      // 如果有 productId,正常加载型号列表
      await getModels(productForm.value.productId);
      // 等待 DOM 更新
      await nextTick();
      if (productForm.value.productModelId) {
        getProductModel(productForm.value.productModelId);
      }
    }
    // 最后再等待一次 DOM 更新,确保所有数据都已设置
    await nextTick();
  }
};
const getProductOptions = () => {
  productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res);
  return productTreeList().then((res) => {
    const tree = convertIdToValue(res);
    productOptions.value = filterOutSemiFinished(tree);
    return res;
  });
};
const filterOutSemiFinished = (nodes = []) => {
  return (nodes || [])
    .filter((node) => {
      const label = String(node?.label ?? "");
      return !label.includes("半成品");
    })
    .map((node) => {
      const next = { ...node };
      if (next.children && next.children.length > 0) {
        next.children = filterOutSemiFinished(next.children);
      }
      return next;
    });
};
const getModels = (value) => {
  productForm.value.productCategory = findNodeById(productOptions.value, value);
  modelList({ id: value }).then((res) => {
    modelOptions.value = res;
  });
  if (value) {
    productForm.value.productCategory =
      findNodeById(productOptions.value, value) || "";
    return modelList({ id: value }).then((res) => {
      modelOptions.value = res;
      return res;
    });
  } else {
    productForm.value.productCategory = "";
    modelOptions.value = [];
    return Promise.resolve([]);
  }
};
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;
  } else {
    productForm.value.specificationModel = null;
    productForm.value.unit = null;
    productForm.value.materialCode = null;
  }
};
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
      return nodes[i].label; // 找到节点,返回该节点
      return nodes[i].label; // 找到节点,返回该节点的label
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const foundNode = findNodeById(nodes[i].children, productId);
      if (foundNode) {
        return foundNode.label; // 在子节点中找到,返回该节点
        return foundNode; // 在子节点中找到,直接返回(已经是label字符串)
      }
    }
  }
@@ -1180,10 +1791,30 @@
    return newItem;
  });
}
/** 与库存新增一致:未填批号时前端生成(PH + 时间戳 + 随机数) */
const generateProductBatchNo = () => {
  const d = new Date();
  const pad = (n) => String(n).padStart(2, "0");
  const ts = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(
    d.getDate()
  )}${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
  const r = Math.floor(Math.random() * 10000)
    .toString()
    .padStart(4, "0");
  return `PH${ts}${r}`;
};
const ensureProductBatchNo = () => {
  const v = (productForm.value.batchNo ?? "").toString().trim();
  productForm.value.batchNo = v || generateProductBatchNo();
};
// 提交产品表单
const submitProduct = () => {
  proxy.$refs["productFormRef"].validate((valid) => {
    if (valid) {
      ensureProductBatchNo();
      if (operationType.value === "edit") {
        submitProductEdit();
      } else {
@@ -1240,11 +1871,9 @@
        delProduct(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          closeProductDia();
          getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then(
            (res) => {
              productData.value = res.productData;
            }
          );
          getPurchaseById({ id: currentId.value, type: 2 }).then((res) => {
            productData.value = res.productData;
          });
        });
      })
      .catch(() => {
@@ -1275,12 +1904,6 @@
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
      // 检查是否有他人维护的数据
      const unauthorizedData = selectedRows.value.filter(item => item.recorderName !== userStore.nickName);
      if (unauthorizedData.length > 0) {
         proxy.$modal.msgWarning("不可删除他人维护的数据");
         return;
      }
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
@@ -1310,10 +1933,10 @@
  return `${year}-${month}-${day}`;
}
const mathNum = () => {
   if (!productForm.value.taxRate) {
      proxy.$modal.msgWarning("请先选择税率");
      return;
   }
  if (!productForm.value.taxRate) {
    proxy.$modal.msgWarning("请先选择税率");
    return;
  }
  if (!productForm.value.taxInclusiveUnitPrice) {
    return;
  }
@@ -1336,39 +1959,89 @@
  }
};
const reverseMathNum = (field) => {
   if (!productForm.value.taxRate) {
      proxy.$modal.msgWarning("请先选择税率");
      return;
   }
  if (!productForm.value.taxRate) {
    proxy.$modal.msgWarning("请先选择税率");
    return;
  }
  const taxRate = Number(productForm.value.taxRate);
  if (!taxRate) return;
  if (field === 'taxInclusiveTotalPrice') {
  // 确保输入值不为负数
  if (
    field === "taxInclusiveTotalPrice" ||
    field === "taxExclusiveTotalPrice"
  ) {
    const value = Number(productForm.value[field]);
    if (value < 0) {
      productForm.value[field] = "0";
      proxy.$modal.msgWarning("值不能小于0");
      return;
    }
  }
  if (field === "taxInclusiveTotalPrice") {
    // 已知含税总价和数量,反算含税单价
    if (productForm.value.quantity) {
      productForm.value.taxInclusiveUnitPrice =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
      productForm.value.taxInclusiveUnitPrice = (
        Number(productForm.value.taxInclusiveTotalPrice) /
        Number(productForm.value.quantity)
      ).toFixed(2);
      // 确保结果不为负数
      if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
        productForm.value.taxInclusiveUnitPrice = "0";
      }
    }
    // 已知含税总价和含税单价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
      productForm.value.quantity = (
        Number(productForm.value.taxInclusiveTotalPrice) /
        Number(productForm.value.taxInclusiveUnitPrice)
      ).toFixed(2);
      // 确保结果不为负数
      if (Number(productForm.value.quantity) < 0) {
        productForm.value.quantity = "0";
      }
    }
    // 反算不含税总价
    productForm.value.taxExclusiveTotalPrice =
      (Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)).toFixed(2);
  } else if (field === 'taxExclusiveTotalPrice') {
    productForm.value.taxExclusiveTotalPrice = (
      Number(productForm.value.taxInclusiveTotalPrice) /
      (1 + taxRate / 100)
    ).toFixed(2);
    // 确保结果不为负数
    if (Number(productForm.value.taxExclusiveTotalPrice) < 0) {
      productForm.value.taxExclusiveTotalPrice = "0";
    }
  } else if (field === "taxExclusiveTotalPrice") {
    // 反算含税总价
    productForm.value.taxInclusiveTotalPrice =
      (Number(productForm.value.taxExclusiveTotalPrice) * (1 + taxRate / 100)).toFixed(2);
    productForm.value.taxInclusiveTotalPrice = (
      Number(productForm.value.taxExclusiveTotalPrice) *
      (1 + taxRate / 100)
    ).toFixed(2);
    // 确保结果不为负数
    if (Number(productForm.value.taxInclusiveTotalPrice) < 0) {
      productForm.value.taxInclusiveTotalPrice = "0";
    }
    // 已知数量,反算含税单价
    if (productForm.value.quantity) {
      productForm.value.taxInclusiveUnitPrice =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)).toFixed(2);
      productForm.value.taxInclusiveUnitPrice = (
        Number(productForm.value.taxInclusiveTotalPrice) /
        Number(productForm.value.quantity)
      ).toFixed(2);
      // 确保结果不为负数
      if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
        productForm.value.taxInclusiveUnitPrice = "0";
      }
    }
    // 已知含税单价,反算数量
    else if (productForm.value.taxInclusiveUnitPrice) {
      productForm.value.quantity =
        (Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)).toFixed(2);
      productForm.value.quantity = (
        Number(productForm.value.taxInclusiveTotalPrice) /
        Number(productForm.value.taxInclusiveUnitPrice)
      ).toFixed(2);
      // 确保结果不为负数
      if (Number(productForm.value.quantity) < 0) {
        productForm.value.quantity = "0";
      }
    }
  }
};
@@ -1378,7 +2051,6 @@
  var index = salesContractList.value.findIndex((item) => item.id == row);
  console.log("index", index);
  if (index > -1) {
    form.value.projectName = salesContractList.value[index].projectName;
    await querygProductInfoByContractNo();
  }
};
@@ -1392,197 +2064,150 @@
  }
};
// 显示二维码
const showQRCode = async (row) => {
  try {
    // 构建二维码内容,只包含采购合同号(纯文本)
    const qrContent = row.purchaseContractNumber || '';
    // 检查内容是否为空
    if (!qrContent || qrContent.trim() === '') {
      proxy.$modal.msgWarning("该行没有采购合同号,无法生成二维码");
      return;
    }
    qrCodeUrl.value = await QRCode.toDataURL(qrContent, {
      width: 200,
      margin: 2,
      color: {
        dark: '#000000',
        light: '#FFFFFF'
      }
    });
    qrCodeDialogVisible.value = true;
  } catch (error) {
    console.error('生成二维码失败:', error);
    proxy.$modal.msgError("生成二维码失败:" + error.message);
const fileListRef = ref(null);
const fileListDialogVisible = ref(false);
const downLoadFile = (row) => {
  if (fileListRef.value) {
    fileListRef.value.open(row.salesLedgerFiles);
  }
};
// 下载二维码
const downloadQRCode = () => {
  if (!qrCodeUrl.value) {
    proxy.$modal.msgWarning("二维码未生成");
// 获取模板信息
const getTemplateList = async () => {
  let res = await getPurchaseTemplateList();
  if (res && res.code === 200 && Array.isArray(res.data)) {
    templateList.value = res.data;
  }
};
// 删除模板
const handleDeleteTemplate = async (item) => {
  if (!item.id) {
    proxy.$modal.msgWarning("无法删除该模板");
    return;
  }
  const a = document.createElement('a');
  a.href = qrCodeUrl.value;
  a.download = `采购合同号二维码_${new Date().getTime()}.png`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  proxy.$modal.msgSuccess("下载成功");
};
// 扫码新增对话框相关变量
const scanAddDialogVisible = ref(false);
const scanAddForm = reactive({
  scanContent: "",
  purchaseContractNumber: "",
  supplierName: "",
  projectName: "",
  contractAmount: "",
  paymentMethod: "",
  recorderName: "",
  scanRemark: "",
});
const scanAddRules = {
  purchaseContractNumber: [{ required: true, message: "请输入采购合同号", trigger: "blur" }],
  supplierName: [{ required: true, message: "请输入供应商名称", trigger: "blur" }],
  projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
};
  try {
    await ElMessageBox.confirm(
      `确定要删除模板"${item.templateName}"吗?`,
      "删除确认",
      {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }
    );
// 扫码登记对话框相关变量
const scanDialogVisible = ref(false);
const scanForm = reactive({
  purchaseContractNumber: "",
  supplierName: "",
  projectName: "",
  scanTime: "",
  scannerName: "",
  scanStatus: "未扫码",
  scanRemark: "",
});
const scanRules = {
  scanRemark: [{ required: true, message: "请输入扫码备注", trigger: "blur" }],
};
const scanRecords = ref([]);
// 打开扫码新增对话框
const openScanAddDialog = () => {
  scanAddForm.scanContent = "";
  scanAddForm.purchaseContractNumber = "";
  scanAddForm.supplierName = "";
  scanAddForm.projectName = "";
  scanAddForm.contractAmount = "";
  scanAddForm.paymentMethod = "";
  scanAddForm.recorderName = userStore.nickName;
  scanAddForm.scanRemark = "";
  scanAddDialogVisible.value = true;
};
// 解析扫码内容(模拟解析二维码数据)
const parseScanContent = (content) => {
  if (!content) return;
  // 模拟解析二维码内容,这里可以根据实际需求调整解析逻辑
  // 假设扫码内容格式为:合同号|供应商|项目|金额|付款方式
  const parts = content.split('|');
  if (parts.length >= 3) {
    scanAddForm.purchaseContractNumber = parts[0] || "";
    scanAddForm.supplierName = parts[1] || "";
    scanAddForm.projectName = parts[2] || "";
    scanAddForm.contractAmount = parts[3] || "";
    scanAddForm.paymentMethod = parts[4] || "";
    const res = await delPurchaseTemplate([item.id]);
    if (res && res.code === 200) {
      ElMessage({
        message: "删除成功",
        type: "success",
      });
      // 如果删除的是当前选中的模板,清空选择
      if (templateName.value === item.templateName) {
        templateName.value = "";
        filterInputValue.value = "";
      }
      // 重新获取模板列表
      await getTemplateList();
    } else {
      ElMessage({
        message: res?.msg || "删除失败",
        type: "error",
      });
    }
  } catch (error) {
    if (error !== "cancel") {
      console.error("删除模板失败:", error);
      ElMessage({
        message: "删除失败,请稍后重试",
        type: "error",
      });
    }
  }
};
// 关闭扫码新增对话框
const closeScanAddDialog = () => {
  scanAddDialogVisible.value = false;
  proxy.resetForm("scanAddFormRef");
};
// 提交扫码新增
const submitScanAdd = () => {
  proxy.$refs["scanAddFormRef"].validate((valid) => {
    if (valid) {
      // 构建新增数据
      const newData = {
        purchaseContractNumber: scanAddForm.purchaseContractNumber,
        supplierName: scanAddForm.supplierName,
        projectName: scanAddForm.projectName,
        contractAmount: scanAddForm.contractAmount,
        paymentMethod: scanAddForm.paymentMethod,
        recorderName: scanAddForm.recorderName,
        entryDate: getCurrentDate(),
        remark: scanAddForm.scanRemark,
        type: 2
      };
      // 模拟新增成功
      proxy.$modal.msgSuccess("扫码新增成功!");
      closeScanAddDialog();
      // 可以选择是否刷新列表
      // getList();
    }
  });
};
// 打开扫码登记对话框
const openScanDialog = (row) => {
  scanForm.purchaseContractNumber = row.purchaseContractNumber;
  scanForm.supplierName = row.supplierName;
  scanForm.projectName = row.projectName;
  scanForm.scanTime = getCurrentDateTime();
  scanForm.scannerName = userStore.nickName;
  scanForm.scanStatus = "未扫码";
  scanForm.scanRemark = "";
  scanRecords.value = [];
  scanDialogVisible.value = true;
};
// 关闭扫码登记对话框
const closeScanDialog = () => {
  scanDialogVisible.value = false;
  proxy.resetForm("scanFormRef");
};
// 提交扫码登记
const submitScan = () => {
  proxy.$refs["scanFormRef"].validate((valid) => {
    if (valid) {
      // 添加扫码记录
      scanRecords.value.push({
        ...scanForm,
        id: Date.now(), // 模拟ID
        scanTime: getCurrentDateTime(),
      });
      scanForm.scanStatus = "已扫码";
      scanForm.scanRemark = scanForm.scanRemark || "无";
      proxy.$modal.msgSuccess("扫码登记成功!");
      closeScanDialog();
    }
  });
};
// 获取当前日期时间
function getCurrentDateTime() {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, "0");
  const day = String(now.getDate()).padStart(2, "0");
  const hours = String(now.getHours()).padStart(2, "0");
  const minutes = String(now.getMinutes()).padStart(2, "0");
  const seconds = String(now.getSeconds()).padStart(2, "0");
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
onMounted(() => {
  getList();
  getTemplateList();
});
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.invalid-row {
  opacity: 0.6;
  background-color: #f5f7fa;
}
.el-row {
  justify-content: space-between;
  align-items: center;
}
.no-arrow-select {
  --el-select-suffix-icon-color: transparent; /* 隐藏默认下拉箭头 */
}
.select-button-group {
  display: flex;
  align-items: center;
}
// 审批人节点容器样式
.approver-nodes-container {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  padding: 16px;
  background-color: #f8f9fa;
  border-radius: 4px;
  border: 1px solid #e4e7ed;
}
.approver-node-item {
  flex: 0 0 calc(33.333% - 12px);
  min-width: 200px;
  padding: 12px;
  background-color: #fff;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  transition: all 0.3s;
  &:hover {
    border-color: #409eff;
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
  }
}
.approver-node-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.approver-node-label {
  font-size: 13px;
  font-weight: 500;
  color: #606266;
}
@media (max-width: 1200px) {
  .approver-node-item {
    flex: 0 0 calc(50% - 8px);
  }
}
@media (max-width: 768px) {
  .approver-node-item {
    flex: 0 0 100%;
  }
}
// 删除图标样式
.delete-icon {
  transition: all 0.3s;
  &:hover {
    color: #f56c6c !important;
    transform: scale(1.2);
  }
}
</style>