| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <template v-if="isFormPageMode"> |
| | | <div class="sales-ledger-page-header"> |
| | | <div> |
| | | <el-button class="sales-ledger-page-back" |
| | | @click="exitFormPage()">返回台账</el-button> |
| | | <div class="sales-ledger-page-title">{{ pageFormTitle }}</div> |
| | | <div class="sales-ledger-page-subtitle">{{ pageFormSubtitle }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="sales-ledger-page-form"> |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | @keydown.capture="handleTabScrollFollow" |
| | | 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-input v-model="form.salesContractNo" |
| | | placeholder="自动生成" |
| | | clearable |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="业务员:" |
| | | prop="salesman"> |
| | | <el-select v-model="form.salesman" |
| | | placeholder="请选择" |
| | | clearable |
| | | :disabled="isReviewedEdit"> |
| | | <el-option v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.nickName" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户名称:" |
| | | prop="customerId"> |
| | | <el-select v-model="form.customerId" |
| | | filterable |
| | | placeholder="请选择" |
| | | clearable |
| | | :disabled="isReviewedEdit"> |
| | | <el-option v-for="item in customerOption" |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.id"> |
| | | {{ item.customerName + (item.taxpayerIdentificationNumber ? "——" + item.taxpayerIdentificationNumber : "") }} |
| | | </el-option> |
| | | </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="isReviewedEdit" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <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="isReviewedEdit" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="交货日期:" |
| | | prop="deliveryDate"> |
| | | <el-date-picker style="width: 100%" |
| | | v-model="form.deliveryDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="请选择" |
| | | clearable |
| | | :disabled="isReviewedEdit" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="录入人:" |
| | | prop="entryPerson"> |
| | | <el-select v-model="form.entryPerson" |
| | | filterable |
| | | default-first-option |
| | | :reserve-keyword="false" |
| | | placeholder="请选择" |
| | | clearable |
| | | @change="changs" |
| | | :disabled="isReviewedEdit"> |
| | | <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 |
| | | :disabled="isReviewedEdit" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-form-item label="产品信息:" |
| | | prop="entryDate"> |
| | | <el-button type="primary" |
| | | :disabled="hasEditingProductRow() || isReviewedEdit" |
| | | @click="addProductInline"> |
| | | 添加 |
| | | </el-button> |
| | | <el-button plain |
| | | type="danger" |
| | | :disabled="isReviewedEdit" |
| | | @click="deleteProduct">删除</el-button> |
| | | </el-form-item> |
| | | </el-row> |
| | | <el-table :data="productData" |
| | | border |
| | | @selection-change="productSelected" |
| | | show-summary |
| | | :summary-method="summarizeProductTable"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" |
| | | :selectable="(row) => !isProductShipped(row)" /> |
| | | <el-table-column align="center" |
| | | label="序号" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="产品大类" |
| | | prop="productCategory" |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-tree-select v-if="scope.row.__editing" |
| | | v-model="scope.row.__productCategoryId" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | check-strictly |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | :filter-node-method="filterProductCategoryNode" |
| | | @change="(val) => handleInlineProductCategoryChange(scope.row, val)" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.productCategory ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="规格型号" |
| | | prop="specificationModel" |
| | | min-width="200"> |
| | | <template #default="scope"> |
| | | <el-select v-if="scope.row.__editing" |
| | | v-model="scope.row.productModelId" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | @change="(val) => handleInlineProductModelChange(scope.row, val)" |
| | | :disabled="isReviewedEdit"> |
| | | <el-option v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | <span v-else>{{ scope.row.specificationModel ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="厚度(mm)" |
| | | prop="thickness" |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.thickness" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.thickness ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="楼层编号" |
| | | prop="floorCode" |
| | | min-width="250" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-input v-if="scope.row.__editing" |
| | | v-model="scope.row.floorCode" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.floorCode ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | :step="0.01" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | v-model="scope.row.taxInclusiveUnitPrice" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineUnitPriceChange(scope.row)" |
| | | @input="() => handleInlineUnitPriceChange(scope.row)" /> |
| | | <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="宽(mm)" |
| | | prop="width" |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.width" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width:100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSizeChange(scope.row)" |
| | | @input="() => handleInlineSizeChange(scope.row)" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.width ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="高(mm)" |
| | | prop="height" |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.height" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSizeChange(scope.row)" |
| | | @input="() => handleInlineSizeChange(scope.row)" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.height ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="数量" |
| | | prop="quantity" |
| | | min-width="150"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.quantity" |
| | | :step="1" |
| | | :min="0" |
| | | :precision="0" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineQuantityChange(scope.row)" |
| | | @input="() => handleInlineQuantityChange(scope.row)" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.quantity ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="结算单片面积(㎡)" |
| | | prop="settlePieceArea" |
| | | min-width="200"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.settlePieceArea" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSettleAreaChange(scope.row)" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.settlePieceArea ? Number(scope.row.settlePieceArea).toFixed(4) : "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="面积(m²)" |
| | | prop="actualTotalArea" |
| | | min-width="200"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.actualTotalArea" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="自动计算" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.actualTotalArea ? Number(scope.row.actualTotalArea).toFixed(4) : "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="税率(%)" |
| | | prop="taxRate" |
| | | min-width="120"> |
| | | <template #default="scope"> |
| | | <el-select v-if="scope.row.__editing" |
| | | v-model="scope.row.taxRate" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 100%" |
| | | @change="() => handleInlineTaxRateChange(scope.row)" |
| | | :disabled="isReviewedEdit"> |
| | | <el-option label="1" |
| | | value="1" /> |
| | | <el-option label="3" |
| | | value="3" /> |
| | | <el-option label="6" |
| | | value="6" /> |
| | | <el-option label="9" |
| | | value="9" /> |
| | | <el-option label="13" |
| | | value="13" /> |
| | | </el-select> |
| | | <span v-else>{{ scope.row.taxRate ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="含税总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | min-width="120" /> |
| | | <el-table-column label="不含税总价(元)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | min-width="120" /> |
| | | <el-table-column label="加工要求" |
| | | prop="processRequirement" |
| | | min-width="160" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-input v-if="scope.row.__editing" |
| | | v-model="scope.row.processRequirement" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.processRequirement ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="发票类型" |
| | | prop="invoiceType" |
| | | min-width="120"> |
| | | <template #default="scope"> |
| | | <el-select v-if="scope.row.__editing" |
| | | v-model="scope.row.invoiceType" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isReviewedEdit"> |
| | | <el-option label="增普票" |
| | | value="增普票" /> |
| | | <el-option label="增专票" |
| | | value="增专票" /> |
| | | </el-select> |
| | | <span v-else>{{ scope.row.invoiceType ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="备注" |
| | | prop="remark" |
| | | min-width="140" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-input v-if="scope.row.__editing" |
| | | v-model="scope.row.remark" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.remark ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="重箱" |
| | | prop="heavyBox" |
| | | min-width="100"> |
| | | <template #default="scope"> |
| | | <el-input v-if="scope.row.__editing" |
| | | v-model="scope.row.heavyBox" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isReviewedEdit" /> |
| | | <span v-else>{{ scope.row.heavyBox ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" |
| | | label="操作" |
| | | min-width="220" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <template v-if="scope.row.__editing"> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="saveProductInline(scope.row, scope.$index)">保存</el-button> |
| | | <el-button link |
| | | type="danger" |
| | | size="small" |
| | | @click="cancelProductInline(scope.row, scope.$index)">取消</el-button> |
| | | <el-popover :width="560" |
| | | trigger="click" |
| | | :hide-after="0" |
| | | :visible="scope.row.__otherAmountPopoverVisible" |
| | | @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)"> |
| | | <template #reference> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="openOtherAmountInline(scope.row)"> |
| | | 额外加工({{ (scope.row.salesProductProcessList || []).length || 0 }}) |
| | | </el-button> |
| | | </template> |
| | | <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;"> |
| | | <div style="font-weight: 600; color:#303133;"> |
| | | 额外加工 |
| | | </div> |
| | | <el-button type="primary" |
| | | plain |
| | | size="small" |
| | | :disabled="isReviewedEdit" |
| | | @click="startAddOtherAmountForRow(scope.row)"> |
| | | 新增 |
| | | </el-button> |
| | | </div> |
| | | <div v-if="scope.row.__inlineOtherAmountAdding" |
| | | style="display:flex; flex-direction:column; gap: 8px; margin-bottom: 10px;" |
| | | @click.stop> |
| | | <el-select v-model="scope.row.__inlineOtherAmountAddId" |
| | | filterable |
| | | clearable |
| | | placeholder="请选择额外加工项目" |
| | | style="width: 100%;"> |
| | | <el-option v-for="item in otherAmountSelectOptions" |
| | | :key="item.id" |
| | | :label="item.processName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | <div style="display:flex; justify-content:flex-end; gap: 8px;"> |
| | | <el-button size="small" |
| | | @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null"> |
| | | 取消 |
| | | </el-button> |
| | | <el-button type="primary" |
| | | size="small" |
| | | :disabled="isReviewedEdit || scope.row.__inlineOtherAmountAddId === null || scope.row.__inlineOtherAmountAddId === undefined || scope.row.__inlineOtherAmountAddId === ''" |
| | | @click="confirmAddOtherAmountForRow(scope.row)"> |
| | | 确认添加 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0" |
| | | style="display:flex; flex-wrap:wrap; gap: 8px;"> |
| | | <div v-for="(item, idx) in scope.row.salesProductProcessList" |
| | | :key="String(item.id) + '_' + idx" |
| | | style="display:flex; align-items:center; gap: 8px; flex: 0 0 calc(50% - 4px); max-width: calc(50% - 4px);"> |
| | | <el-tag type="info" |
| | | style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> |
| | | {{ item.processName }} |
| | | </el-tag> |
| | | <el-input-number v-model="item.quantity" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="0" |
| | | style="width: 120px;" |
| | | placeholder="数量" |
| | | :disabled="isReviewedEdit" |
| | | @change="handleOtherAmountQuantityChange(scope.row)" /> |
| | | <el-button type="danger" |
| | | link |
| | | size="small" |
| | | :disabled="isReviewedEdit" |
| | | @click="removeOtherAmountAtForRow(scope.row, idx)"> |
| | | 删除 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <div v-else |
| | | style="color:#909399; font-size: 13px;"> |
| | | 暂无额外加工 |
| | | </div> |
| | | </el-popover> |
| | | </template> |
| | | <template v-else> |
| | | <el-button v-if="isReviewedEdit" |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="editPriceOnly(scope.row)"> |
| | | 修改单价 |
| | | </el-button> |
| | | <template v-if="!isReviewedEdit"> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="editProductInline(scope.row, scope.$index)"> |
| | | 编辑 |
| | | </el-button> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="copyProductInline(scope.row, scope.$index)"> |
| | | 复制新建 |
| | | </el-button> |
| | | </template> |
| | | <el-popover :width="560" |
| | | trigger="click" |
| | | :hide-after="0" |
| | | :visible="scope.row.__otherAmountPopoverVisible" |
| | | @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)"> |
| | | <template #reference> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="openOtherAmountInline(scope.row)"> |
| | | 额外加工({{ (scope.row.salesProductProcessList || []).length || 0 }}) |
| | | </el-button> |
| | | </template> |
| | | <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;"> |
| | | <div style="font-weight: 600; color:#303133;"> |
| | | 额外加工 |
| | | </div> |
| | | <el-button type="primary" |
| | | plain |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row) || isReviewedEdit" |
| | | @click="startAddOtherAmountForRow(scope.row)"> |
| | | 新增 |
| | | </el-button> |
| | | </div> |
| | | <div v-if="scope.row.__inlineOtherAmountAdding" |
| | | style="display:flex; flex-direction:column; gap: 8px; margin-bottom: 10px;" |
| | | @click.stop> |
| | | <el-select v-model="scope.row.__inlineOtherAmountAddId" |
| | | filterable |
| | | clearable |
| | | placeholder="请选择额外加工项目" |
| | | style="width: 100%;" |
| | | :disabled="isProductShipped(scope.row) || isReviewedEdit"> |
| | | <el-option v-for="item in otherAmountSelectOptions" |
| | | :key="item.id" |
| | | :label="item.processName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | <div style="display:flex; justify-content:flex-end; gap: 8px;"> |
| | | <el-button size="small" |
| | | :disabled="isProductShipped(scope.row) || isReviewedEdit" |
| | | @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null"> |
| | | 取消 |
| | | </el-button> |
| | | <el-button type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row) || isReviewedEdit || scope.row.__inlineOtherAmountAddId === null || scope.row.__inlineOtherAmountAddId === undefined || scope.row.__inlineOtherAmountAddId === ''" |
| | | @click="confirmAddOtherAmountForRow(scope.row)"> |
| | | 确认添加 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0" |
| | | style="display:flex; flex-wrap:wrap; gap: 8px;"> |
| | | <div v-for="(item, idx) in scope.row.salesProductProcessList" |
| | | :key="String(item.id) + '_' + idx" |
| | | style="display:flex; align-items:center; gap: 8px; flex: 0 0 calc(50% - 4px); max-width: calc(50% - 4px);"> |
| | | <el-tag type="info" |
| | | style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"> |
| | | {{ item.processName }} |
| | | </el-tag> |
| | | <el-input-number v-model="item.quantity" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="0" |
| | | style="width: 120px;" |
| | | placeholder="数量" |
| | | :disabled="isProductShipped(scope.row) || isReviewedEdit" |
| | | @change="handleOtherAmountQuantityChange(scope.row)" /> |
| | | <el-button type="danger" |
| | | link |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row) || isReviewedEdit" |
| | | @click="removeOtherAmountAtForRow(scope.row, idx)"> |
| | | 删除 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <div v-else |
| | | style="color:#909399; font-size: 13px;"> |
| | | 暂无额外加工 |
| | | </div> |
| | | </el-popover> |
| | | </template> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="备注:" |
| | | prop="remarks"> |
| | | <el-input v-model="form.remarks" |
| | | placeholder="请输入" |
| | | clearable |
| | | type="textarea" |
| | | :rows="2" |
| | | :disabled="isReviewedEdit" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="客户备注:" |
| | | prop="customerRemarks"> |
| | | <el-input v-model="form.customerRemarks" |
| | | placeholder="请输入" |
| | | clearable |
| | | type="textarea" |
| | | :rows="2" |
| | | :disabled="isReviewedEdit" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <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-button type="primary">上传</el-button> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | | 文件格式支持 |
| | | doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <div class="sales-ledger-page-actions"> |
| | | <el-button type="primary" @click="submitForm">提交</el-button> |
| | | </div> |
| | | </el-form> |
| | | </div> |
| | | </template> |
| | | <template v-else> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" |
| | | :inline="true"> |
| | |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :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 - 22em)"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" |
| | | fixed="left" /> |
| | | <el-table-column type="expand" |
| | | width="60" |
| | | fixed="left"> |
| | | <template #default="props"> |
| | | <el-table :data="props.row.children" |
| | | border |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable"> |
| | | <el-table-column align="center" |
| | | label="序号" |
| | | type="index" /> |
| | | <el-table-column label="楼层编号" |
| | | prop="floorCode" |
| | | min-width="100" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="产品大类" |
| | | prop="productCategory" /> |
| | | <el-table-column label="规格型号" |
| | | prop="specificationModel" /> |
| | | <el-table-column label="厚度" |
| | | prop="thickness" |
| | | min-width="90"> |
| | | <template #default="scope"> |
| | | {{ scope.row.thickness ?? "" }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="宽(mm)" |
| | | prop="width" |
| | | min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.width ?? "" }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="高(mm)" |
| | | prop="height" |
| | | min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.height ?? "" }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="周长(cm)" |
| | | prop="perimeter" |
| | | min-width="90"> |
| | | <template #default="scope"> |
| | | {{ scope.row.perimeter ?? "" }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="总面积(m²)" |
| | | prop="actualTotalArea" |
| | | min-width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.actualTotalArea ?? "" }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="加工要求" |
| | | prop="processRequirement" |
| | | min-width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="备注" |
| | | prop="remark" |
| | | min-width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="重箱" |
| | | prop="heavyBox" |
| | | min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.heavyBox ?? "" }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="产品状态" |
| | | width="100px" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.approveStatus === 1 && (!scope.row.shippingDate || !scope.row.shippingCarNumber)" |
| | | type="success">充足</el-tag> |
| | | <el-tag v-else-if="scope.row.approveStatus === 1 && scope.row.shippingDate && scope.row.shippingCarNumber" |
| | | type="success">已出库</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="入库状态" |
| | | width="100px" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.productStockStatus == 1" |
| | | type="warning">部分入库</el-tag> |
| | | <el-tag v-else-if="scope.row.productStockStatus == 2" |
| | | type="success">已入库</el-tag> |
| | | <el-tag v-else-if="scope.row.productStockStatus == 0" |
| | | type="info">未入库</el-tag> |
| | | <el-tag v-else |
| | | type="danger">未入库</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <!-- <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> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="发货日期" |
| | | minWidth="100px" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <div> |
| | | <div v-if="scope.row.shippingDate">{{ scope.row.shippingDate }}</div> |
| | | <el-tag v-else |
| | | type="info">-</el-tag> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="数量" |
| | | prop="quantity" /> |
| | | <el-table-column label="税率(%)" |
| | | prop="taxRate" /> |
| | | <el-table-column label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="含税总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="不含税总价(元)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" /> |
| | | <!--操作--> |
| | | <!-- <el-table-column Width="60px" label="操作" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | :disabled="!canShip(scope.row)" |
| | | @click="openDeliveryForm(scope.row)"> |
| | | 发货 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> --> |
| | | </el-table> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column align="center" |
| | | label="序号" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="销售合同号" |
| | | <el-table-column label="订单号" |
| | | prop="salesContractNo" |
| | | width="180" |
| | | show-overflow-tooltip /> |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openForm('view', scope.row)">{{ scope.row.salesContractNo || "-" }}</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="客户名称" |
| | | prop="customerName" |
| | | width="300" |
| | |
| | | <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="contractAmount" |
| | |
| | | prop="entryDate" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="签订日期" |
| | | prop="executionDate" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="交付日期" |
| | | prop="deliveryDate" |
| | | width="120" |
| | |
| | | :limit="page.size" |
| | | @pagination="paginationChange" /> |
| | | </div> |
| | | <FormDialog v-model="dialogFormVisible" |
| | | :title="isCompletedOrder ? '查看销售台账页面(已完成)' : operationType === 'add' ? '新增销售台账页面' : '编辑销售台账页面'" |
| | | </template> |
| | | <FormDialog v-if="!isFormPageMode" |
| | | v-model="dialogFormVisible" |
| | | :title="isCompletedOrder ? '查看销售台账页面(已完成)' : operationType === 'add' ? '新增销售台账页面' : operationType === 'view' ? '查看销售台账页面' : '编辑销售台账页面'" |
| | | :width="'70%'" |
| | | :operation-type="isCompletedOrder ? 'detail' : operationType" |
| | | :operation-type="isCompletedOrder || operationType === 'view' ? 'detail' : operationType" |
| | | @close="closeDia" |
| | | @confirm="submitForm" |
| | | @cancel="closeDia"> |
| | |
| | | border |
| | | @selection-change="productSelected" |
| | | show-summary |
| | | :summary-method="summarizeMainTable"> |
| | | :summary-method="summarizeProductTable"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" |
| | |
| | | <script setup> |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, watch, nextTick } from "vue"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, watch, nextTick, computed } from "vue"; |
| | | import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import { ArrowDown, WarningFilled } from "@element-plus/icons-vue"; |
| | |
| | | import { printSalesDeliveryNote } from "./components/salesDeliveryPrint.js"; |
| | | import { printSalesLabel } from "./components/salesLabelPrint.js"; |
| | | import QRCode from "qrcode"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | // import { salesLedgerProductSetProcessFlowConfig } from "@/api/salesManagement/salesProcessFlowConfig.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const { proxy } = getCurrentInstance(); |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const tableData = ref([]); |
| | | const productData = ref([]); |
| | | const selectedRows = ref([]); |
| | |
| | | const ledgerQrDialogVisible = ref(false); |
| | | const ledgerQrCompositeUrl = ref(""); |
| | | const ledgerQrDownloadBaseName = ref(""); |
| | | const pendingAddPrefillDetail = ref(null); |
| | | const pageMode = computed(() => String(route.query.mode || "")); |
| | | const isFormPageMode = computed(() => |
| | | pageMode.value === "add" || pageMode.value === "edit" |
| | | ); |
| | | const pageFormTitle = computed(() => |
| | | pageMode.value === "edit" ? "编辑销售台账" : "新增销售台账" |
| | | ); |
| | | const pageFormSubtitle = computed(() => |
| | | pageMode.value === "edit" |
| | | ? "编辑完成后将自动返回销售台账列表" |
| | | : "新增完成后将自动返回销售台账列表" |
| | | ); |
| | | |
| | | const sanitizeLedgerQrFilename = s => |
| | | String(s) |
| | |
| | | if (res.newLedgerIds.length === 1) { |
| | | const newId = res.newLedgerIds[0]; |
| | | getSalesLedgerWithProducts({ id: newId, type: 1 }).then(detail => { |
| | | openFormWithPreFill(detail); |
| | | enterAddPage(detail); |
| | | }); |
| | | } |
| | | } |
| | |
| | | }; |
| | | |
| | | /** 用预填数据打开新增台账页面(重新生成场景) */ |
| | | const openFormWithPreFill = async (detail) => { |
| | | const openFormWithPreFill = async (detail, keepPageMode = false) => { |
| | | operationType.value = "add"; |
| | | form.value = {}; |
| | | productData.value = []; |
| | | selectedQuotation.value = null; |
| | | fileList.value = []; |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | customerList().then(res => { |
| | |
| | | form.value.customerRemarks = detail.customerRemarks ?? detail.customer_remarks ?? ""; |
| | | productData.value = detail.productData || []; |
| | | form.value.deliveryDate = dayjs(form.value.entryDate).add(7, "day").format("YYYY-MM-DD"); |
| | | dialogFormVisible.value = true; |
| | | dialogFormVisible.value = !keepPageMode; |
| | | }; |
| | | |
| | | const paginationChange = obj => { |
| | |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | }; |
| | | const summarizeProductTable = param => { |
| | | return proxy.summarizeTable(param, [ |
| | | "quantity", |
| | | "settlePieceArea", |
| | | "actualTotalArea", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | }; |
| | | |
| | | const initAddFormState = async () => { |
| | | operationType.value = "add"; |
| | | isCompletedOrder.value = false; |
| | | isReviewedEdit.value = false; |
| | | form.value = {}; |
| | | productData.value = []; |
| | | fileList.value = []; |
| | | selectedQuotation.value = null; |
| | | |
| | | const userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | customerList().then(res => { |
| | | customerOption.value = res; |
| | | }); |
| | | |
| | | form.value.entryPerson = userStore.id; |
| | | form.value.entryDate = getCurrentDate(); |
| | | form.value.executionDate = getCurrentDate(); |
| | | form.value.customerRemarks = ""; |
| | | form.value.deliveryDate = dayjs(form.value.entryDate) |
| | | .add(7, "day") |
| | | .format("YYYY-MM-DD"); |
| | | }; |
| | | |
| | | const initEditFormState = async rowId => { |
| | | operationType.value = "edit"; |
| | | isCompletedOrder.value = false; |
| | | form.value = {}; |
| | | productData.value = []; |
| | | fileList.value = []; |
| | | selectedQuotation.value = null; |
| | | |
| | | const userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | customerList().then(res => { |
| | | customerOption.value = res; |
| | | }); |
| | | |
| | | currentId.value = rowId; |
| | | const res = await getSalesLedgerWithProducts({ id: rowId, type: 1 }); |
| | | form.value = { ...res }; |
| | | form.value.entryPerson = Number(res.entryPerson); |
| | | form.value.customerRemarks = |
| | | res?.customerRemarks ?? res?.customer_remarks ?? ""; |
| | | productData.value = form.value.productData || []; |
| | | fileList.value = form.value.salesLedgerFiles || []; |
| | | isReviewedEdit.value = Number(res?.reviewStatus) === 1; |
| | | }; |
| | | |
| | | const enterAddPage = async detail => { |
| | | pendingAddPrefillDetail.value = detail || null; |
| | | const query = { ...route.query, mode: "add" }; |
| | | await router.push({ path: route.path, query }); |
| | | }; |
| | | |
| | | const enterEditPage = async row => { |
| | | const query = { ...route.query, mode: "edit", id: row?.id }; |
| | | await router.push({ path: route.path, query }); |
| | | }; |
| | | |
| | | const exitFormPage = async (shouldRefresh = false) => { |
| | | const query = { ...route.query }; |
| | | delete query.mode; |
| | | delete query.id; |
| | | pendingAddPrefillDetail.value = null; |
| | | await router.push({ path: route.path, query }); |
| | | closeDia(false); |
| | | if (shouldRefresh) { |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | watch( |
| | | pageMode, |
| | | async enabled => { |
| | | if (enabled === "add") { |
| | | await initAddFormState(); |
| | | if (pendingAddPrefillDetail.value) { |
| | | await openFormWithPreFill(pendingAddPrefillDetail.value, true); |
| | | pendingAddPrefillDetail.value = null; |
| | | } |
| | | return; |
| | | } |
| | | if (enabled === "edit" && route.query.id) { |
| | | await initEditFormState(route.query.id); |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | |
| | | // 打开弹框 |
| | | const openForm = async (type, row) => { |
| | | if (type === "add") { |
| | | await enterAddPage(); |
| | | return; |
| | | } |
| | | if (type === "edit") { |
| | | await enterEditPage(row); |
| | | return; |
| | | } |
| | | // 已完成订单强制为只读模式,但附件上传仍可用 |
| | | const isCompleted = Number(row?.orderStatus) === 1; |
| | | const effectiveType = isCompleted ? 'view' : type; |
| | |
| | | delete submitPayload.paymentMethod; |
| | | addOrUpdateSalesLedger(submitPayload).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | if (isFormPageMode.value) { |
| | | exitFormPage(true); |
| | | return; |
| | | } |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | |
| | | }); |
| | | }; |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | const closeDia = (resetFormRef = true) => { |
| | | if (resetFormRef) { |
| | | proxy.resetForm("formRef"); |
| | | } |
| | | dialogFormVisible.value = false; |
| | | isCompletedOrder.value = false; |
| | | isReviewedEdit.value = false; |
| | | if (!isFormPageMode.value) { |
| | | fileList.value = []; |
| | | } |
| | | }; |
| | | |
| | | const productIndex = ref(0); |
| | |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .sales-ledger-page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | gap: 16px; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .sales-ledger-page-title { |
| | | font-size: 22px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | line-height: 1.3; |
| | | } |
| | | |
| | | .sales-ledger-page-back { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .sales-ledger-page-subtitle { |
| | | margin-top: 6px; |
| | | font-size: 13px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .sales-ledger-page-form { |
| | | padding: 20px; |
| | | background: #fff; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .sales-ledger-page-actions { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 16px; |
| | | margin-top: 24px; |
| | | } |
| | | |
| | | .ledger-qr-dialog { |
| | | text-align: center; |
| | | padding-bottom: 8px; |
| | |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .sales-ledger-page-header { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .approver-node-item { |
| | | flex: 0 0 100%; |
| | | } |