| | |
| | | <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="客户合同号:"> |
| | | <el-input v-model="searchForm.customerContractNo" placeholder="请输入" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="销售合同号:"> |
| | | <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | |
| | | <el-button type="primary" @click="openForm('add')"> |
| | | 新增台账 |
| | | </el-button> |
| | | <el-button type="primary" plain @click="openOtherAmountDialog"> |
| | | 其他金额维护 |
| | | </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> |
| | | <el-button type="primary" plain @click="handlePrint">打印</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" show-summary style="width: 100%" |
| | | :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" :row-class-name="tableRowClassName" show-summary style="width: 100%" |
| | | :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" type="selection" width="55" fixed="left"/> |
| | | <el-table-column type="expand" width="60" fixed="left"> |
| | |
| | | width="100px" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.approveStatus === 1" |
| | | |
| | | <el-tag v-if="scope.row.approveStatus === 1 && (!scope.row.shippingDate || !scope.row.shippingCarNumber)" |
| | | type="success">充足</el-tag> |
| | | <el-tag v-else |
| | | type="danger">不足</el-tag> |
| | | <el-tag v-else-if="scope.row.approveStatus === 0 && (scope.row.shippingDate || scope.row.shippingCarNumber)" |
| | | type="success">已出库</el-tag> |
| | | <el-tag v-else type="danger">不足</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="发货状态" prop="shippingStatus" width="140" align="center" show-overflow-tooltip /> |
| | | <el-table-column label="发货状态" width="140" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getShippingStatusType(scope.row)" size="small"> |
| | | {{ getShippingStatusText(scope.row) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="快递公司" prop="expressCompany" show-overflow-tooltip /> |
| | | <el-table-column label="快递单号" prop="expressNumber" show-overflow-tooltip /> |
| | | <el-table-column label="发货车牌" minWidth="100px" align="center"> |
| | | <template #default="scope"> |
| | | <div> |
| | | <el-tag type="success" v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag> |
| | | <el-tag v-else type="info">未发货</el-tag> |
| | | <el-tag v-else type="info">-</el-tag> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="scope.row.approveStatus !== 1 || !!scope.row.shippingDate || !!scope.row.shippingCarNumber" |
| | | type="primary" |
| | | :disabled="!canShip(scope.row)" |
| | | @click="openDeliveryForm(scope.row)"> |
| | | 发货 |
| | | </el-button> |
| | |
| | | </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="customerContractNo" 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="projectName" width="180" 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 fixed="right" label="操作" min-width="100" align="center"> |
| | | <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 fixed="right" label="操作" width="130" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">编辑</el-button> |
| | | <!-- <el-button link type="primary" size="small" @click="openForm('view', scope.row)">详情</el-button>--> |
| | | <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">附件</el-button> |
| | | <!-- <el-button link type="primary" size="small" @click="openDeliveryForm(scope.row)">发货</el-button>--> |
| | | <el-button link type="primary" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit">编辑</el-button> |
| | | <el-button link type="primary" @click="downLoadFile(scope.row)">附件</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <FormDialog v-model="dialogFormVisible" :title="operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'" :width="'70%'" |
| | | :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="closeDia"> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <!-- 报价单导入入口:放在表单顶部,选择后反显客户/业务员等 --> |
| | | <el-row v-if="operationType === 'add'" style="margin-bottom: 10px;"> |
| | | <el-col :span="24" style="text-align: right;"> |
| | | <el-button type="primary" plain @click="openQuotationDialog"> |
| | | 从销售报价导入 |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="销售合同号:" prop="salesContractNo"> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户合同号:" prop="customerContractNo"> |
| | | <el-input v-model="form.customerContractNo" placeholder="请输入" clearable :disabled="operationType === 'view'"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户名称:" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="请选择" clearable :disabled="operationType === 'view'"> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项目名称:" prop="projectName"> |
| | | <el-input v-model="form.projectName" placeholder="请输入" clearable :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项目名称:" prop="projectName"> |
| | | <el-input v-model="form.projectName" placeholder="请输入" clearable :disabled="operationType === 'view'" /> |
| | | </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 :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="付款方式"> |
| | | <el-input v-model="form.paymentMethod" placeholder="请输入" clearable :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="交货日期:" prop="entryDate"> |
| | | <el-date-picker style="width: 100%" v-model="form.deliveryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" |
| | | type="date" placeholder="请选择" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-form-item label="产品信息:" prop="entryDate"> |
| | | <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">添加</el-button> |
| | |
| | | </el-row> |
| | | <el-table :data="productData" border @selection-change="productSelected" show-summary |
| | | :summary-method="summarizeMainTable"> |
| | | <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" /> |
| | | <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" |
| | | :selectable="(row) => !isProductShipped(row)" /> |
| | | <el-table-column align="center" label="序号" type="index" width="60" /> |
| | | <el-table-column label="产品大类" prop="productCategory" /> |
| | | <el-table-column label="规格型号" prop="specificationModel" /> |
| | |
| | | <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> |
| | | <el-table-column fixed="right" label="操作" min-width="60" align="center" v-if="operationType !== 'view'"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row,scope.$index)">编辑</el-button> |
| | | <el-button link type="primary" size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="openProductForm('edit', scope.row,scope.$index)">编辑</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="备注·:" prop="remark"> |
| | | <el-input v-model="form.remark" placeholder="请输入" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> |
| | | <el-form-item label="备注:" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请输入" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="附件材料:" prop="remark"> |
| | | <el-form-item label="附件材料:" prop="salesLedgerFiles"> |
| | | <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload |
| | | :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" :on-remove="handleRemove"> |
| | |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <pagination |
| | | v-show="quotationPage.total > 0" |
| | | :total="quotationPage.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="quotationPage.current" |
| | | :limit="quotationPage.size" |
| | | @pagination="quotationPaginationChange" |
| | | /> |
| | | |
| | | <template #footer> |
| | | <el-button @click="quotationDialogVisible = false">关闭</el-button> |
| | | </template> |
| | |
| | | <FormDialog |
| | | v-model="productFormVisible" |
| | | :title="productOperationType === 'add' ? '新增产品' : '编辑产品'" |
| | | :width="'40%'" |
| | | :width="'60%'" |
| | | :operation-type="productOperationType" |
| | | @close="closeProductDia" |
| | | @confirm="submitProduct" |
| | | @cancel="closeProductDia"> |
| | | <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef"> |
| | | <!-- 每行三个:产品大类/规格型号/单位 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="产品大类:" prop="productCategory"> |
| | | <!-- <el-select v-model="productForm.productCategory" placeholder="请选择" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> |
| | | </el-select> --> |
| | | <el-tree-select v-model="productForm.productCategory" placeholder="请选择" clearable check-strictly |
| | | @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> |
| | | <el-tree-select |
| | | v-model="productForm.productCategory" |
| | | placeholder="请选择" |
| | | clearable |
| | | check-strictly |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="规格型号:" prop="productModelId"> |
| | | <el-select v-model="productForm.productModelId" placeholder="请选择" clearable @change="getProductModel" filterable> |
| | | <el-select |
| | | v-model="productForm.productModelId" |
| | | placeholder="请选择" |
| | | clearable |
| | | @change="getProductModel" |
| | | filterable |
| | | style="width: 100%" |
| | | > |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="单位:" prop="unit"> |
| | | <el-input v-model="productForm.unit" placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | </el-row> |
| | | |
| | | <!-- 每行三个:税率/含税单价/数量 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="税率(%):" prop="taxRate"> |
| | | <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate"> |
| | | <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%"> |
| | | <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"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice"> |
| | | <el-input-number :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%" |
| | | :precision="2" |
| | | placeholder="请输入" clearable @change="calculateFromUnitPrice" /> |
| | | <el-input-number |
| | | :step="0.01" |
| | | :min="0" |
| | | v-model="productForm.taxInclusiveUnitPrice" |
| | | style="width: 100%" |
| | | :precision="2" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="calculateFromUnitPrice" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="数量:" prop="quantity"> |
| | | <el-input-number :step="0.1" :min="0" v-model="productForm.quantity" placeholder="请输入" clearable |
| | | :precision="2" |
| | | @change="calculateFromQuantity" style="width: 100%" /> |
| | | <el-input-number |
| | | :step="0.1" |
| | | :min="0" |
| | | v-model="productForm.quantity" |
| | | placeholder="请输入" |
| | | clearable |
| | | :precision="2" |
| | | @change="() => { calculateFromQuantity(); recalcAreaTotals(); }" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 每行三个:含税总价/不含税总价/发票类型 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="含税总价(元):" prop="taxInclusiveTotalPrice"> |
| | | <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="请输入" clearable @change="calculateFromTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="不含税总价(元):" prop="taxExclusiveTotalPrice"> |
| | | <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="请输入" clearable @change="calculateFromExclusiveTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="发票类型:" prop="invoiceType"> |
| | | <el-select v-model="productForm.invoiceType" placeholder="请选择" clearable> |
| | | <el-select v-model="productForm.invoiceType" placeholder="请选择" clearable style="width: 100%"> |
| | | <el-option label="增普票" value="增普票" /> |
| | | <el-option label="增专票" value="增专票" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 每行三个:宽/高/实际单片面积 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="宽(mm):" prop="width"> |
| | | <el-input-number |
| | | v-model="productForm.width" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="请输入宽(mm)" |
| | | clearable |
| | | @change="recalcAreaFromWidthHeight" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="高(mm):" prop="height"> |
| | | <el-input-number |
| | | v-model="productForm.height" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="请输入高(mm)" |
| | | clearable |
| | | @change="recalcAreaFromWidthHeight" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="周长(cm):" prop="perimeter"> |
| | | <el-input-number |
| | | v-model="productForm.perimeter" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | :disabled="true" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 每行三个:实际单片面积/实际总面积/结算单片面积 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="实际单片面积(㎡):" prop="actualPieceArea"> |
| | | <el-input-number |
| | | v-model="productForm.actualPieceArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="recalcAreaTotals" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="实际总面积(㎡):" prop="actualTotalArea"> |
| | | <el-input-number |
| | | v-model="productForm.actualTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="结算单片面积(㎡):" prop="settlePieceArea"> |
| | | <el-input-number |
| | | v-model="productForm.settlePieceArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="recalcAreaTotals" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="结算总面积(㎡):" prop="settleTotalArea"> |
| | | <el-input-number |
| | | v-model="productForm.settleTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="结算总面积(㎡):" prop="settleTotalArea"> |
| | | <el-input-number |
| | | v-model="productForm.settleTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="重箱:" prop="heavyBox"> |
| | | <el-input v-model="productForm.heavyBox" placeholder="请输入" clearable @change="calculateFromExclusiveTotalPrice" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- 其他金额(占满一行:等同于 3 列网格的整行) --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="加工要求:" prop="processRequirement"> |
| | | <el-input v-model="productForm.processRequirement" placeholder="请输入加工要求" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="备注:" prop="remark"> |
| | | <el-input v-model="productForm.remark" 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; gap: 12px; width: 100%;"> |
| | | <div style="font-weight: 600; color: #303133;">其他金额:</div> |
| | | <div style="color:#909399; font-size: 13px; flex: 1;"> |
| | | 已选择 {{ productForm?.salesProductProcessList?.length || 0 }} 项 |
| | | </div> |
| | | <el-button |
| | | v-if="operationType !== 'view'" |
| | | type="primary" |
| | | plain |
| | | size="small" |
| | | @click="startAddOtherAmount" |
| | | > |
| | | 新增 |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | |
| | | <div style="display:flex; flex-direction:column; gap: 12px;"> |
| | | <div v-if="Array.isArray(productForm?.salesProductProcessList) && productForm.salesProductProcessList.length > 0" |
| | | style="display:flex; flex-wrap:wrap; gap: 12px; align-items:flex-start;" |
| | | > |
| | | <div |
| | | v-for="(item, index) in productForm.salesProductProcessList" |
| | | :key="String(item.id) + '_' + index" |
| | | style="display:flex; gap: 10px; align-items:center; padding: 10px 12px; border: 1px solid #ebeef5; border-radius: 8px; box-sizing:border-box; min-width: 0;" |
| | | :style="getOtherAmountCardFlexStyle()" |
| | | > |
| | | <div style="flex: 1; min-width: 0;"> |
| | | <el-tag type="info" style="width: 100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> |
| | | {{ item.processName }} |
| | | </el-tag> |
| | | </div> |
| | | <div style="flex: 1;"> |
| | | <el-input-number |
| | | v-model="item.quantity" |
| | | :min="0" |
| | | :step="0.1" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | placeholder="请输入数量" |
| | | :disabled="operationType === 'view'" |
| | | /> |
| | | </div> |
| | | <el-button |
| | | v-if="operationType !== 'view'" |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | @click="removeOtherAmountAt(index)" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else style="color:#909399; font-size: 13px;"> |
| | | 暂无其他金额 |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </FormDialog> |
| | | <!-- 其他金额:新增弹框 --> |
| | | <el-dialog |
| | | v-model="otherAmountAddDialogVisible" |
| | | title="新增其他金额" |
| | | width="520px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div style="padding: 4px 0 10px;"> |
| | | <div style="font-size: 14px; color: #606266; margin-bottom: 10px;"> |
| | | 请选择要新增的其他金额项目 |
| | | </div> |
| | | <el-select |
| | | v-model="otherAmountAddId" |
| | | filterable |
| | | clearable |
| | | placeholder="请选择其他金额项目" |
| | | style="width: 100%;" |
| | | :disabled="operationType === 'view'" |
| | | > |
| | | <el-option |
| | | v-for="item in otherAmountSelectOptions" |
| | | :key="item.id" |
| | | :label="item.processName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </div> |
| | | <template #footer> |
| | | <el-button @click="cancelAddOtherAmount">取消</el-button> |
| | | <el-button |
| | | type="primary" |
| | | @click="confirmAddOtherAmount" |
| | | :disabled="operationType === 'view' || otherAmountAddId === null || otherAmountAddId === undefined || otherAmountAddId === ''" |
| | | > |
| | | 确认添加 |
| | | </el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 导入弹窗 --> |
| | | <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> |
| | | <!-- 附件列表弹窗 --> |
| | | <FileListDialog |
| | |
| | | <div v-for="(item, index) in printData" :key="index" class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="company-name">鼎诚瑞实业有限责任公司</div> |
| | | <div class="document-title">零售发货单</div> |
| | | </div> |
| | | |
| | |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">客户名称:</span> |
| | | <span class="value">{{ item.customerName || '张爱有' }}</span> |
| | | <span class="value">{{ item.customerName }}</span> |
| | | </div> |
| | | <span class="label">单号:</span> |
| | | <span class="value">{{ item.salesContractNo }}</span> |
| | |
| | | <div> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | @click="removeApproverNode(index)" |
| | | v-if="approverNodes.length > 1" |
| | | >删除</el-button> |
| | |
| | | <el-button @click="closeDeliveryDia">取消</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 其他金额维护(新增/编辑/删除) --> |
| | | <el-dialog |
| | | v-model="otherAmountDialogVisible" |
| | | title="其他金额维护" |
| | | width="80%" |
| | | :close-on-click-modal="false" |
| | | @close="closeOtherAmountDialog" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="14"> |
| | | <el-table |
| | | :data="otherAmountRecords" |
| | | border |
| | | v-loading="otherAmountLoading" |
| | | height="55vh" |
| | | > |
| | | <el-table-column label="编码" prop="code" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="项目" prop="processName" min-width="180" show-overflow-tooltip /> |
| | | <el-table-column label="数量" prop="quantity" min-width="110" :formatter="formattedNumber" /> |
| | | <el-table-column label="单价(元)" prop="unitPrice" min-width="130" :formatter="formattedNumber" /> |
| | | <el-table-column label="金额(元)" prop="amount" min-width="160" :formatter="formattedNumber" /> |
| | | <el-table-column fixed="right" label="操作" width="160" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="handleOtherEdit(scope.row)">编辑</el-button> |
| | | <el-button link type="danger" size="small" @click="handleOtherDelete(scope.row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <pagination |
| | | v-show="otherAmountTotal > 0" |
| | | :total="otherAmountTotal" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="otherAmountPage.current" |
| | | :limit="otherAmountPage.size" |
| | | @pagination="otherAmountPaginationChange" |
| | | /> |
| | | </el-col> |
| | | |
| | | <el-col :span="10"> |
| | | <div style="padding: 8px 0;"> |
| | | <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 10px;"> |
| | | <div style="font-weight:600;"> |
| | | {{ otherAmountOperationType === 'add' ? '新增其他金额' : '编辑其他金额' }} |
| | | </div> |
| | | <el-button |
| | | type="primary" |
| | | plain |
| | | size="small" |
| | | @click="handleOtherAdd" |
| | | :disabled="otherAmountOperationType === 'add'" |
| | | > |
| | | 新增 |
| | | </el-button> |
| | | </div> |
| | | |
| | | <el-form |
| | | :model="otherAmountForm" |
| | | label-width="120px" |
| | | label-position="top" |
| | | :rules="otherAmountRules" |
| | | ref="otherAmountFormRef" |
| | | > |
| | | <el-form-item label="编码"> |
| | | <el-input v-model="otherAmountForm.code" placeholder="请输入编码(可选)" clearable /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="项目" prop="processName"> |
| | | <el-input v-model="otherAmountForm.processName" placeholder="请输入项目名称" clearable /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="数量" prop="quantity"> |
| | | <el-input-number |
| | | v-model="otherAmountForm.quantity" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width:100%" |
| | | placeholder="请输入数量" |
| | | clearable |
| | | @change="recalcOtherAmount" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="单价(元)" prop="unitPrice"> |
| | | <el-input-number |
| | | v-model="otherAmountForm.unitPrice" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width:100%" |
| | | placeholder="请输入单价" |
| | | clearable |
| | | @change="recalcOtherAmount" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="金额(元)"> |
| | | <el-input v-model="otherAmountForm.amount" disabled /> |
| | | </el-form-item> |
| | | |
| | | <div style="display:flex; justify-content:flex-end; gap: 10px; margin-top: 8px;"> |
| | | <el-button @click="closeOtherAmountDialog">取消</el-button> |
| | | <el-button type="primary" @click="submitOtherAmountForm">保存</el-button> |
| | | </div> |
| | | </el-form> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | |
| | | delLedger, |
| | | addOrUpdateSalesLedgerProduct, |
| | | delProduct, |
| | | delLedgerFile, getProductInventory, |
| | | delLedgerFile, |
| | | getProductInventory, |
| | | salesLedgerProductProcessList, |
| | | salesLedgerProductProcessAdd, |
| | | salesLedgerProductProcessUpdate, |
| | | salesLedgerProductProcessDelete, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | |
| | | customerId: "", |
| | | entryPerson: "", |
| | | entryDate: "", |
| | | deliveryDate: "", |
| | | maintenanceTime: "", |
| | | productData: [], |
| | | executionDate: "", |
| | |
| | | customerId: [{ required: true, message: "请选择", trigger: "change" }], |
| | | entryPerson: [{ required: true, message: "请选择", trigger: "change" }], |
| | | entryDate: [{ required: true, message: "请选择", trigger: "change" }], |
| | | deliveryDate: [{ required: true, message: "请选择", trigger: "change" }], |
| | | executionDate: [{ required: true, message: "请选择", trigger: "change" }], |
| | | }, |
| | | }); |
| | |
| | | taxInclusiveTotalPrice: "", |
| | | taxExclusiveTotalPrice: "", |
| | | invoiceType: "", |
| | | // 新增:尺寸/面积/加工与其他金额 |
| | | width: 0, // 宽(mm) |
| | | height: 0, // 高(mm) |
| | | perimeter: 0, // 周长(cm) = (宽+高)*2,宽高为 mm |
| | | // 面积字段(㎡) |
| | | actualPieceArea: 0, // 实际单片面积(㎡) |
| | | actualTotalArea: 0, // 实际总面积(㎡) |
| | | settlePieceArea: 0, // 结算单片面积(㎡) |
| | | settleTotalArea: 0, // 结算总面积(㎡) |
| | | processRequirement: "", // 加工要求 |
| | | remark: "", // 备注 |
| | | salesProductProcessList: [], // 其他金额:[{id, processName, quantity}] |
| | | }, |
| | | productRules: { |
| | | productCategory: [{ required: true, message: "请选择", trigger: "change" }], |
| | |
| | | quotationNo: "", |
| | | customer: "", |
| | | }); |
| | | // 报价单弹框分页 |
| | | const quotationPage = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | const selectedQuotation = ref(null); |
| | | |
| | | // 发货相关 |
| | |
| | | }, |
| | | }); |
| | | const { deliveryForm, deliveryRules } = toRefs(deliveryFormData); |
| | | |
| | | // 其他金额维护(工序/流程金额维护) |
| | | const otherAmountDialogVisible = ref(false); |
| | | const otherAmountLoading = ref(false); |
| | | const otherAmountRecords = ref([]); |
| | | const otherAmountTotal = ref(0); |
| | | const otherAmountPage = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }); |
| | | |
| | | const otherAmountOperationType = ref("add"); // add/edit |
| | | const otherAmountFormRef = ref(null); |
| | | const otherAmountForm = reactive({ |
| | | id: null, |
| | | code: "", // 前端字段名:code;后端接口列表返回 remark,此处进行映射 |
| | | processName: "", |
| | | quantity: 0, |
| | | unitPrice: 0, |
| | | amount: "0.00", |
| | | }); |
| | | const otherAmountRules = reactive({ |
| | | processName: [{ required: true, message: "请输入项目名称", trigger: "change" }], |
| | | quantity: [{ required: true, message: "请输入数量", trigger: "blur" }], |
| | | unitPrice: [{ required: true, message: "请输入单价", trigger: "blur" }], |
| | | }); |
| | | |
| | | const recalcOtherAmount = () => { |
| | | const quantity = Number(otherAmountForm.quantity ?? 0) || 0; |
| | | const unitPrice = Number(otherAmountForm.unitPrice ?? 0) || 0; |
| | | otherAmountForm.amount = (quantity * unitPrice).toFixed(2); |
| | | }; |
| | | |
| | | const resetOtherAmountForm = (type = "add") => { |
| | | otherAmountOperationType.value = type; |
| | | otherAmountForm.id = null; |
| | | otherAmountForm.code = ""; |
| | | otherAmountForm.processName = ""; |
| | | otherAmountForm.quantity = 0; |
| | | otherAmountForm.unitPrice = 0; |
| | | otherAmountForm.amount = "0.00"; |
| | | }; |
| | | |
| | | const openOtherAmountDialog = () => { |
| | | otherAmountDialogVisible.value = true; |
| | | resetOtherAmountForm("add"); |
| | | // 打开弹框时刷新数据,避免长时间停留导致数据过期 |
| | | otherAmountPage.current = otherAmountPage.current || 1; |
| | | fetchOtherAmountList(); |
| | | }; |
| | | |
| | | const closeOtherAmountDialog = () => { |
| | | otherAmountDialogVisible.value = false; |
| | | resetOtherAmountForm("add"); |
| | | }; |
| | | |
| | | const fetchOtherAmountList = async () => { |
| | | otherAmountLoading.value = true; |
| | | try { |
| | | const params = { |
| | | current: otherAmountPage.current, |
| | | size: otherAmountPage.size, |
| | | }; |
| | | const res = await salesLedgerProductProcessList(params); |
| | | |
| | | // 兼容不同接口响应结构:可能是 res.records / res.total 或 res.data.records / res.data.total |
| | | const records = res?.records ?? res?.data?.records ?? []; |
| | | const total = res?.total ?? res?.data?.total ?? 0; |
| | | |
| | | otherAmountRecords.value = records.map((item) => { |
| | | const quantity = Number(item.quantity ?? 0) || 0; |
| | | const unitPrice = Number(item.unitPrice ?? 0) || 0; |
| | | const amount = Number(item.amount ?? quantity * unitPrice) || 0; |
| | | return { |
| | | id: item.id, |
| | | code: item.code ?? item.remark ?? "", |
| | | processName: item.processName ?? "", |
| | | quantity, |
| | | unitPrice, |
| | | amount: amount.toFixed(2), |
| | | }; |
| | | }); |
| | | |
| | | otherAmountTotal.value = total; |
| | | } finally { |
| | | otherAmountLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const otherAmountPaginationChange = (obj) => { |
| | | otherAmountPage.current = obj.page; |
| | | otherAmountPage.size = obj.limit; |
| | | fetchOtherAmountList(); |
| | | }; |
| | | |
| | | const handleOtherAdd = () => { |
| | | resetOtherAmountForm("add"); |
| | | }; |
| | | |
| | | const handleOtherEdit = (row) => { |
| | | if (!row) return; |
| | | otherAmountOperationType.value = "edit"; |
| | | otherAmountForm.id = row.id ?? null; |
| | | otherAmountForm.code = row.code ?? ""; |
| | | otherAmountForm.processName = row.processName ?? ""; |
| | | otherAmountForm.quantity = Number(row.quantity ?? 0) || 0; |
| | | otherAmountForm.unitPrice = Number(row.unitPrice ?? 0) || 0; |
| | | recalcOtherAmount(); |
| | | }; |
| | | |
| | | const submitOtherAmountForm = () => { |
| | | otherAmountFormRef.value?.validate((valid) => { |
| | | if (!valid) return; |
| | | |
| | | const payload = { |
| | | processName: otherAmountForm.processName, |
| | | quantity: Number(otherAmountForm.quantity) || 0, |
| | | unitPrice: Number(otherAmountForm.unitPrice) || 0, |
| | | amount: Number(otherAmountForm.amount) || 0, |
| | | // 列表返回字段是 remark,这里按“code=remark”做映射 |
| | | remark: otherAmountForm.code, |
| | | // 兼容后端可能直接使用 code 字段 |
| | | code: otherAmountForm.code, |
| | | }; |
| | | |
| | | if (otherAmountOperationType.value === "edit") { |
| | | payload.id = otherAmountForm.id; |
| | | salesLedgerProductProcessUpdate(payload).then(() => { |
| | | proxy.$modal.msgSuccess("保存成功"); |
| | | fetchOtherAmountList(); |
| | | resetOtherAmountForm("add"); |
| | | }); |
| | | } else { |
| | | salesLedgerProductProcessAdd(payload).then(() => { |
| | | proxy.$modal.msgSuccess("保存成功"); |
| | | fetchOtherAmountList(); |
| | | resetOtherAmountForm("add"); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleOtherDelete = (row) => { |
| | | if (!row?.id) return; |
| | | ElMessageBox.confirm("确认删除该记录?", "删除", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | return salesLedgerProductProcessDelete(row.id).then(() => { |
| | | proxy.$modal.msgSuccess("删除成功"); |
| | | fetchOtherAmountList(); |
| | | |
| | | if (otherAmountOperationType.value === "edit" && otherAmountForm.id === row.id) { |
| | | resetOtherAmountForm("add"); |
| | | } |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | |
| | | // 产品弹框:其他金额多选下拉(基于“其他金额维护”查询接口) |
| | | const otherAmountSelectOptions = ref([]); // [{id, processName}] |
| | | const otherAmountSelectOptionsLoading = ref(false); |
| | | |
| | | const fetchOtherAmountSelectOptions = async (force = false) => { |
| | | if (!force && otherAmountSelectOptions.value.length > 0) return; |
| | | otherAmountSelectOptionsLoading.value = true; |
| | | try { |
| | | const params = { |
| | | current: 1, |
| | | // 下拉框尽量一次性拉全,避免多次分页影响选择体验 |
| | | size: 1000, |
| | | }; |
| | | const res = await salesLedgerProductProcessList(params); |
| | | const records = res?.records ?? res?.data?.records ?? []; |
| | | |
| | | otherAmountSelectOptions.value = records.map((item) => ({ |
| | | id: item.id, |
| | | processName: item.processName ?? "", |
| | | })); |
| | | } finally { |
| | | otherAmountSelectOptionsLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const normalizeOtherAmountsFromRow = (row) => { |
| | | if (!row) return []; |
| | | const raw = |
| | | row.other_amounts ?? |
| | | row.otherAmounts ?? |
| | | row.otherAmountProjects ?? |
| | | row.otherAmountList ?? |
| | | row.salesProductProcessList ?? |
| | | []; |
| | | |
| | | if (!Array.isArray(raw)) return []; |
| | | if (raw.length === 0) return []; |
| | | |
| | | // 情况1:后端直接返回 [{id, processName}...] |
| | | if (typeof raw[0] === "object") { |
| | | return raw |
| | | .map((it) => { |
| | | const id = it?.id ?? it?.processId ?? it?.otherAmountId ?? null; |
| | | const quantity = |
| | | Number( |
| | | it?.quantity ?? |
| | | it?.processQuantity ?? |
| | | it?.otherQuantity ?? |
| | | it?.process_quantity ?? |
| | | it?.other_amount_quantity |
| | | ) || 0; |
| | | return { |
| | | id, |
| | | processName: it?.processName ?? "", |
| | | quantity, |
| | | }; |
| | | }) |
| | | .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); |
| | | } |
| | | |
| | | // 情况2:后端只返回 ids: [1,2,3] |
| | | return raw |
| | | .map((id) => ({ |
| | | id, |
| | | processName: "", |
| | | quantity: 0, |
| | | })) |
| | | .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); |
| | | }; |
| | | |
| | | const mergeOtherAmountOptionsBySelection = (selected) => { |
| | | const list = Array.isArray(selected) ? selected : []; |
| | | for (const s of list) { |
| | | const exists = otherAmountSelectOptions.value.some((o) => String(o.id) === String(s.id)); |
| | | if (!exists) { |
| | | otherAmountSelectOptions.value.push({ |
| | | id: s.id, |
| | | processName: s.processName ?? "", |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const fillOtherAmountProcessName = (selected) => { |
| | | const list = Array.isArray(selected) ? selected : []; |
| | | return list.map((s) => { |
| | | const opt = otherAmountSelectOptions.value.find((o) => String(o.id) === String(s.id)); |
| | | return { |
| | | id: s.id, |
| | | processName: opt?.processName ?? s.processName ?? "", |
| | | quantity: Number(s.quantity ?? 0) || 0, |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | // “其他金额”卡片布局:只有一条时占满整行;>=2时两列排布 |
| | | const getOtherAmountCardFlexStyle = () => { |
| | | const list = productForm.value?.salesProductProcessList; |
| | | const len = Array.isArray(list) ? list.length : 0; |
| | | if (len === 1) { |
| | | return { flex: "0 0 100%", maxWidth: "100%", width: "100%" }; |
| | | } |
| | | return { |
| | | flex: "0 0 calc(50% - 6px)", |
| | | maxWidth: "calc(50% - 6px)", |
| | | width: "calc(50% - 6px)", |
| | | }; |
| | | }; |
| | | |
| | | // 其他金额:点击“新增”后在弹窗里选择一个项目 |
| | | const otherAmountAddDialogVisible = ref(false); |
| | | const otherAmountAddId = ref(null); |
| | | |
| | | const startAddOtherAmount = () => { |
| | | if (operationType.value === "view") return; |
| | | otherAmountAddDialogVisible.value = true; |
| | | otherAmountAddId.value = null; |
| | | // 通常 openProductForm 已经拉过 options,这里兜底 |
| | | if (otherAmountSelectOptions.value.length === 0) { |
| | | fetchOtherAmountSelectOptions(true); |
| | | } |
| | | }; |
| | | |
| | | const cancelAddOtherAmount = () => { |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | }; |
| | | |
| | | const handleOtherAmountSelected = (id) => { |
| | | const selectedId = id ?? otherAmountAddId.value; |
| | | if (selectedId === null || selectedId === undefined || selectedId === "") return; |
| | | const opt = otherAmountSelectOptions.value.find((o) => String(o.id) === String(selectedId)); |
| | | if (!opt) return; |
| | | |
| | | const exists = (productForm.value?.salesProductProcessList ?? []).some( |
| | | (it) => String(it?.id) === String(opt.id) |
| | | ); |
| | | if (exists) { |
| | | proxy.$modal.msgWarning("该其他金额项目已添加"); |
| | | return; |
| | | } |
| | | |
| | | productForm.value.salesProductProcessList.push({ |
| | | id: opt.id, |
| | | processName: opt.processName, |
| | | quantity: 0, |
| | | }); |
| | | |
| | | // 选择完成后关闭弹窗,下一次可再次点击“新增”继续添加 |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | }; |
| | | |
| | | const confirmAddOtherAmount = () => { |
| | | handleOtherAmountSelected(otherAmountAddId.value); |
| | | }; |
| | | |
| | | const removeOtherAmountAt = (index) => { |
| | | if (operationType.value === "view") return; |
| | | if (!Array.isArray(productForm.value?.salesProductProcessList)) return; |
| | | productForm.value.salesProductProcessList.splice(index, 1); |
| | | }; |
| | | |
| | | // 发货审批人节点(仿协同审批 infoFormDia.vue) |
| | | const approverNodes = ref([{ id: 1, userId: null }]); |
| | |
| | | expandedRowKeys.value = []; |
| | | } |
| | | }; |
| | | |
| | | // 添加表行类名方法 |
| | | const tableRowClassName = ({ row }) => { |
| | | if (!row.deliveryDate) return ''; |
| | | if (row.isFh) return ''; |
| | | |
| | | const diff = row.deliveryDaysDiff; |
| | | if (diff === 15) { |
| | | return 'yellow'; |
| | | } else if (diff === 10) { |
| | | return 'pink'; |
| | | } else if (diff === 2) { |
| | | return 'purple'; |
| | | } else if (diff < 2) { |
| | | return 'red'; |
| | | } |
| | | }; |
| | | // 主表合计方法 |
| | | const summarizeMainTable = (param) => { |
| | | return proxy.summarizeTable(param, [ |
| | |
| | | const openQuotationDialog = async () => { |
| | | if (operationType.value === "view") return; |
| | | quotationDialogVisible.value = true; |
| | | // 打开弹窗时重置分页到第一页 |
| | | quotationPage.current = 1; |
| | | // 先确保客户列表已加载,便于后续回填 customerId |
| | | if (!customerOption.value || customerOption.value.length === 0) { |
| | | try { |
| | |
| | | quotationLoading.value = true; |
| | | try { |
| | | const params = { |
| | | // 兼容后端分页字段:这里沿用报价页面已有可用的字段命名 |
| | | currentPage: 1, |
| | | pageSize: 100, |
| | | // 后端分页字段:current / size |
| | | current: quotationPage.current, |
| | | size: quotationPage.size, |
| | | ...quotationSearchForm, |
| | | status: "通过", |
| | | }; |
| | | const res = await getQuotationList(params); |
| | | quotationList.value = res?.data?.records || []; |
| | | quotationPage.total = res?.data?.total || 0; |
| | | } finally { |
| | | quotationLoading.value = false; |
| | | } |
| | |
| | | const resetQuotationSearch = async () => { |
| | | quotationSearchForm.quotationNo = ""; |
| | | quotationSearchForm.customer = ""; |
| | | quotationPage.current = 1; |
| | | await fetchQuotationList(); |
| | | }; |
| | | |
| | | // 报价单弹框分页切换 |
| | | const quotationPaginationChange = (obj) => { |
| | | quotationPage.current = obj.page; |
| | | quotationPage.size = obj.limit; |
| | | fetchQuotationList(); |
| | | }; |
| | | |
| | | // 选中报价单后回填到台账表单 |
| | |
| | | selectedQuotation.value = row; |
| | | |
| | | // 业务员 |
| | | form.value.salesman = row.salesperson || ""; |
| | | form.value.salesman = (row.salesperson || "").trim(); |
| | | |
| | | // 客户名称 -> customerId |
| | | const customer = (customerOption.value || []).find((c) => c.customerName === row.customer); |
| | | const qCustomerName = String(row.customer || "").trim(); |
| | | const customer = (customerOption.value || []).find((c) => { |
| | | const name = String(c.customerName || "").trim(); |
| | | return name === qCustomerName || name.includes(qCustomerName) || qCustomerName.includes(name); |
| | | }); |
| | | if (customer?.id) { |
| | | form.value.customerId = customer.id; |
| | | } else { |
| | |
| | | taxInclusiveTotalPrice: taxInclusiveTotalPrice, |
| | | taxExclusiveTotalPrice: taxExclusiveTotalPrice, |
| | | invoiceType: "增普票", |
| | | // 新增:默认值(避免后续提交时字段缺失) |
| | | width: 0, |
| | | height: 0, |
| | | actualPieceArea: 0, |
| | | actualTotalArea: 0, |
| | | settlePieceArea: 0, |
| | | settleTotalArea: 0, |
| | | processRequirement: "", |
| | | remark: "", |
| | | salesProductProcessList: [], |
| | | }; |
| | | }); |
| | | |
| | |
| | | const productIndex = ref(0); |
| | | // 打开产品弹框 |
| | | const openProductForm = async (type, row, index) => { |
| | | // 编辑时检查产品是否已发货或审核通过 |
| | | if (type === "edit" && isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | |
| | | productOperationType.value = type; |
| | | productForm.value = {}; |
| | | proxy.resetForm("productFormRef"); |
| | | // 确保多选项默认是数组,避免 el-select multiple 报错 |
| | | productForm.value.salesProductProcessList = []; |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | if (type === "edit") { |
| | | productForm.value = { ...row }; |
| | | |
| | | // 字段命名兼容:优先驼峰(如 actualPieceArea),兼容后端可能返回下划线(如 actual_piece_area) |
| | | productForm.value.actualPieceArea = row?.actualPieceArea ?? row?.actual_piece_area ?? 0; |
| | | productForm.value.actualTotalArea = row?.actualTotalArea ?? row?.actual_total_area ?? 0; |
| | | productForm.value.settlePieceArea = row?.settlePieceArea ?? row?.settle_piece_area ?? 0; |
| | | productForm.value.settleTotalArea = row?.settleTotalArea ?? row?.settle_total_area ?? 0; |
| | | |
| | | // 加工要求/备注兼容:后端可能使用其它命名 |
| | | productForm.value.processRequirement = |
| | | row?.processRequirement ?? row?.process_requirement ?? ""; |
| | | productForm.value.remark = row?.remark ?? row?.remarks ?? ""; |
| | | |
| | | // 周长回显(如后端返回;最终仍以公式计算为准) |
| | | productForm.value.perimeter = |
| | | row?.perimeter ?? row?.heavyBoxPerimeter ?? row?.heavyboxPerimeter ?? 0; |
| | | |
| | | productForm.value.salesProductProcessList = normalizeOtherAmountsFromRow(row); |
| | | productIndex.value = index; |
| | | // 编辑时根据产品大类名称反查 tree 节点 id,并加载规格型号列表 |
| | | try { |
| | |
| | | // 加载失败时保持可编辑,不中断弹窗 |
| | | console.error("加载产品规格型号失败", e); |
| | | } |
| | | |
| | | // 根据当前宽高重新计算周长与面积 |
| | | recalcPerimeterFromWidthHeight(); |
| | | recalcAreaFromWidthHeight(); |
| | | |
| | | // 回显“其他金额”多选:先拉取下拉选项,再补齐 processName |
| | | await fetchOtherAmountSelectOptions(true); |
| | | mergeOtherAmountOptionsBySelection(productForm.value.salesProductProcessList); |
| | | productForm.value.salesProductProcessList = fillOtherAmountProcessName(productForm.value.salesProductProcessList); |
| | | } else { |
| | | getProductOptions() |
| | | // 新增时下拉选项加载一次即可 |
| | | fetchOtherAmountSelectOptions(true); |
| | | } |
| | | productFormVisible.value = true; |
| | | }; |
| | |
| | | const submitProduct = () => { |
| | | proxy.$refs["productFormRef"].validate((valid) => { |
| | | if (valid) { |
| | | // 面积/总计字段在提交前兜底计算一次 |
| | | recalcAreaTotals(); |
| | | // 其他金额只提交 {id, processName, quantity}(后端字段:salesProductProcessList) |
| | | productForm.value.salesProductProcessList = (Array.isArray(productForm.value.salesProductProcessList) |
| | | ? productForm.value.salesProductProcessList |
| | | : [] |
| | | ) |
| | | .map((it) => ({ |
| | | id: it?.id, |
| | | processName: it?.processName ?? "", |
| | | quantity: Number(it?.quantity ?? 0) || 0, |
| | | })) |
| | | .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); |
| | | |
| | | if (operationType.value === "edit") { |
| | | submitProductEdit(); |
| | | } else { |
| | |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | | return; |
| | | } |
| | | |
| | | // 检查是否有已发货或审核通过的产品 |
| | | const shippedProducts = productSelectedRows.value.filter(row => isProductShipped(row)); |
| | | if (shippedProducts.length > 0) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能删除"); |
| | | return; |
| | | } |
| | | |
| | | if (operationType.value === "add") { |
| | | productSelectedRows.value.forEach((selectedRow) => { |
| | | const index = productData.value.findIndex( |
| | |
| | | const closeProductDia = () => { |
| | | proxy.resetForm("productFormRef"); |
| | | productFormVisible.value = false; |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | }; |
| | | // 导入 |
| | | const handleImport = () => { |
| | |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | /** 判断单个产品是否已发货(根据shippingStatus判断,已发货或审核通过不可编辑和删除) */ |
| | | const isProductShipped = (product) => { |
| | | if (!product) return false; |
| | | const status = String(product.shippingStatus || "").trim(); |
| | | // 如果发货状态是"已发货"或"审核通过",则不可编辑和删除 |
| | | return status === "已发货" || status === "审核通过"; |
| | | }; |
| | | |
| | | /** 判断销售订单下是否存在已发货/发货完成的产品(不可删除) */ |
| | | const hasShippedProducts = (products) => { |
| | | if (!products || !products.length) return false; |
| | | return products.some((p) => { |
| | | const status = String(p.shippingStatus || "").trim(); |
| | | // 有发货日期或车牌号视为已发货 |
| | | if (p.shippingDate || p.shippingCarNumber) return true; |
| | | // 已进行发货、发货完成、已发货 均不可删除 |
| | | return status === "已进行发货" || status === "发货完成" || status === "已发货"; |
| | | }); |
| | | }; |
| | | |
| | | // 删除 |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | const handleDelete = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map((item) => item.id); |
| | | |
| | | // 检查是否有已进行发货或发货完成的销售订单,若有则不允许删除 |
| | | const cannotDeleteNames = []; |
| | | for (const row of selectedRows.value) { |
| | | let products = row.children && row.children.length > 0 ? row.children : null; |
| | | if (!products) { |
| | | try { |
| | | const res = await productList({ salesLedgerId: row.id, type: 1 }); |
| | | products = res.data || []; |
| | | } catch { |
| | | products = []; |
| | | } |
| | | } |
| | | if (hasShippedProducts(products)) { |
| | | cannotDeleteNames.push(row.salesContractNo || `ID:${row.id}`); |
| | | } |
| | | } |
| | | if (cannotDeleteNames.length > 0) { |
| | | proxy.$modal.msgWarning("已进行发货或发货完成的销售订单不能删除:" + cannotDeleteNames.join("、")); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | |
| | | <div class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="company-name">鼎诚瑞实业有限责任公司</div> |
| | | <div class="document-title">零售发货单</div> |
| | | </div> |
| | | |
| | |
| | | </div> |
| | | <div> |
| | | <span class="label">客户名称:</span> |
| | | <span class="value">${item.customerName || '张爱有'}</span> |
| | | <span class="value">${item.customerName}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | |
| | | } |
| | | }; |
| | | |
| | | // 新增:尺寸(宽高)与面积(单片/总计)联动 |
| | | const recalcAreaTotals = () => { |
| | | const qty = Number(productForm.value.quantity ?? 0) || 0; |
| | | const actualPiece = Number(productForm.value.actualPieceArea ?? 0) || 0; |
| | | const settlePiece = Number(productForm.value.settlePieceArea ?? 0) || 0; |
| | | |
| | | productForm.value.actualTotalArea = Number((actualPiece * qty).toFixed(5)); |
| | | productForm.value.settleTotalArea = Number((settlePiece * qty).toFixed(5)); |
| | | }; |
| | | |
| | | // 新增:周长(cm)(重箱heavyBox周长) |
| | | // width/height 单位为 mm,因此周长(cm)需要除以 10 |
| | | const recalcPerimeterFromWidthHeight = () => { |
| | | const width = Number(productForm.value.width ?? 0) || 0; |
| | | const height = Number(productForm.value.height ?? 0) || 0; |
| | | |
| | | if (width <= 0 || height <= 0) { |
| | | productForm.value.perimeter = 0; |
| | | return; |
| | | } |
| | | |
| | | // 周长 = (宽 + 高) * 2,单位从 mm 转为 cm:/10 |
| | | productForm.value.perimeter = Number((((width + height) * 2) / 10).toFixed(2)); |
| | | }; |
| | | |
| | | const recalcAreaFromWidthHeight = () => { |
| | | const width = Number(productForm.value.width ?? 0) || 0; |
| | | const height = Number(productForm.value.height ?? 0) || 0; |
| | | |
| | | if (width <= 0 || height <= 0) { |
| | | // 宽高为空/为0时,把单片面积与总面积置为0 |
| | | productForm.value.actualPieceArea = 0; |
| | | productForm.value.actualTotalArea = 0; |
| | | productForm.value.perimeter = 0; |
| | | |
| | | // 只有在结算单片面积也为空/为0时,才同步置0,避免覆盖用户手动填写 |
| | | const settlePiece = Number(productForm.value.settlePieceArea ?? 0) || 0; |
| | | if (!settlePiece) { |
| | | productForm.value.settlePieceArea = 0; |
| | | } |
| | | productForm.value.settleTotalArea = Number( |
| | | ((Number(productForm.value.settlePieceArea ?? 0) || 0) * (Number(productForm.value.quantity ?? 0) || 0)).toFixed(5) |
| | | ); |
| | | return; |
| | | } |
| | | |
| | | const computedPieceArea = (width * height) / 1e6; // mm*mm -> ㎡ |
| | | const computed = Number(computedPieceArea.toFixed(5)); |
| | | |
| | | productForm.value.actualPieceArea = computed; |
| | | |
| | | // settlePieceArea:若用户未填写/为0,则默认使用宽高计算值 |
| | | const settlePieceRaw = Number(productForm.value.settlePieceArea ?? 0) || 0; |
| | | if (!settlePieceRaw) { |
| | | productForm.value.settlePieceArea = computed; |
| | | } |
| | | |
| | | recalcPerimeterFromWidthHeight(); |
| | | recalcAreaTotals(); |
| | | }; |
| | | |
| | | // 根据含税总价计算含税单价和数量 |
| | | const calculateFromTotalPrice = () => { |
| | | if (isCalculating.value) return; |
| | |
| | | isCalculating.value = false; |
| | | }; |
| | | /** |
| | | * 获取发货状态文本 |
| | | * @param row 行数据 |
| | | */ |
| | | const getShippingStatusText = (row) => { |
| | | // 如果已发货(有发货日期或车牌号),显示"已发货" |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | return '已发货'; |
| | | } |
| | | |
| | | // 获取发货状态字段 |
| | | const status = row.shippingStatus; |
| | | |
| | | // 如果状态为空或未定义,默认为"待发货" |
| | | if (status === null || status === undefined || status === '') { |
| | | return '待发货'; |
| | | } |
| | | |
| | | // 状态是字符串 |
| | | const statusStr = String(status).trim(); |
| | | const statusTextMap = { |
| | | '待发货': '待发货', |
| | | '待审核': '待审核', |
| | | '审核中': '审核中', |
| | | '审核拒绝': '审核拒绝', |
| | | '审核通过': '审核通过', |
| | | '已发货': '已发货' |
| | | }; |
| | | return statusTextMap[statusStr] || '待发货'; |
| | | }; |
| | | |
| | | /** |
| | | * 获取发货状态标签类型(颜色) |
| | | * @param row 行数据 |
| | | */ |
| | | const getShippingStatusType = (row) => { |
| | | // 如果已发货(有发货日期或车牌号),显示绿色 |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | return 'success'; |
| | | } |
| | | |
| | | // 获取发货状态字段 |
| | | const status = row.shippingStatus; |
| | | |
| | | // 如果状态为空或未定义,默认为灰色(待发货) |
| | | if (status === null || status === undefined || status === '') { |
| | | return 'info'; |
| | | } |
| | | |
| | | // 状态是字符串 |
| | | const statusStr = String(status).trim(); |
| | | const typeTextMap = { |
| | | '待发货': 'info', |
| | | '待审核': 'info', |
| | | '审核中': 'warning', |
| | | '审核拒绝': 'danger', |
| | | '审核通过': 'success', |
| | | '已发货': 'success' |
| | | }; |
| | | return typeTextMap[statusStr] || 'info'; |
| | | }; |
| | | |
| | | /** |
| | | * 判断是否可以发货 |
| | | * 只有在产品状态是充足,发货状态是待发货和审核拒绝的时候才可以发货 |
| | | * @param row 行数据 |
| | | */ |
| | | const canShip = (row) => { |
| | | // 产品状态必须是充足(approveStatus === 1) |
| | | if (row.approveStatus !== 1) { |
| | | return false; |
| | | } |
| | | |
| | | // 获取发货状态 |
| | | const shippingStatus = row.shippingStatus; |
| | | |
| | | // 如果已发货(有发货日期或车牌号),不能再次发货 |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | return false; |
| | | } |
| | | |
| | | // 发货状态必须是"待发货"或"审核拒绝" |
| | | const statusStr = shippingStatus ? String(shippingStatus).trim() : ''; |
| | | return statusStr === '待发货' || statusStr === '审核拒绝'; |
| | | }; |
| | | |
| | | /** |
| | | * 下载文件 |
| | | * |
| | | * @param row 下载文件的相关信息对象 |
| | |
| | | |
| | | // 打开发货弹框 |
| | | const openDeliveryForm = (row) => { |
| | | // 校验:只有产品状态为充足且未发货时才能发货 |
| | | if (row.approveStatus !== 1) { |
| | | proxy.$modal.msgWarning("产品状态不足,无法发货"); |
| | | // 检查是否可以发货 |
| | | if (!canShip(row)) { |
| | | proxy.$modal.msgWarning("只有在产品状态是充足,发货状态是待发货或审核拒绝的时候才可以发货"); |
| | | return; |
| | | } |
| | | if (row.shippingDate || row.shippingCarNumber) { |
| | | proxy.$modal.msgWarning("该产品已发货,无法重复发货"); |
| | | return; |
| | | } |
| | | |
| | | currentDeliveryRow.value = row; |
| | | deliveryForm.value = { |
| | | type: "货车", |
| | |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | ::v-deep .yellow { |
| | | background-color: #FAF0DE; |
| | | } |
| | | |
| | | ::v-deep .pink { |
| | | background-color: #FAE1DE; |
| | | } |
| | | |
| | | ::v-deep .red { |
| | | background-color: #FAE1DE; |
| | | } |
| | | |
| | | ::v-deep .purple{ |
| | | background-color: #F4DEFA; |
| | | } |
| | | |
| | | .other-amount-select { |
| | | /* 多选标签区域强制单行,不让输入框随标签换行而拉高高度 */ |
| | | :deep .el-select__tags { |
| | | display: flex; |
| | | flex-wrap: nowrap !important; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | max-height: 32px; |
| | | } |
| | | } |
| | | |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |