| | |
| | | </el-row> |
| | | <el-row> |
| | | <el-form-item label="产品信息:" prop="entryDate"> |
| | | <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">添加</el-button> |
| | | <el-button v-if="operationType !== 'view'" type="primary" @click="addProductInline">添加</el-button> |
| | | <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >删除</el-button> |
| | | </el-form-item> |
| | | </el-row> |
| | |
| | | <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="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)" |
| | | /> |
| | | <span v-else>{{ scope.row.productCategory ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="规格型号" prop="specificationModel" min-width="140"> |
| | | <template #default="scope"> |
| | | <el-select |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.productModelId" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | style="width: 140px" |
| | | @change="(val) => handleInlineProductModelChange(scope.row, val)" |
| | | > |
| | | <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="厚度" prop="thickness" min-width="90"> |
| | | <template #default="scope"> |
| | | {{ scope.row.thickness ?? "" }} |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.thickness" |
| | | :min="0" |
| | | :step="0.000000000000001" |
| | | :precision="15" |
| | | style="width: 110px" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | <span v-else>{{ scope.row.thickness ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="宽(mm)" prop="width" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.width ?? "" }} |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.width" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 110px" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSizeChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ scope.row.width ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="高(mm)" prop="height" min-width="80"> |
| | | <template #default="scope"> |
| | | {{ scope.row.height ?? "" }} |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.height" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 110px" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSizeChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ scope.row.height ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="面积(m²)" prop="actualTotalArea" min-width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.actualTotalArea ?? "" }} |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.actualTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 120px" |
| | | placeholder="自动计算" |
| | | :disabled="true" |
| | | /> |
| | | <span v-else>{{ scope.row.actualTotalArea ?? "" }}</span> |
| | | </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="quantity" min-width="90"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.quantity" |
| | | :step="0.1" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 110px" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineQuantityChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ scope.row.quantity ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" min-width="140"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | :step="0.01" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 120px" |
| | | v-model="scope.row.taxInclusiveUnitPrice" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineUnitPriceChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice ?? 0) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="税率(%)" prop="taxRate" min-width="90"> |
| | | <template #default="scope"> |
| | | <el-select |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.taxRate" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 90px" |
| | | @change="() => handleInlineTaxRateChange(scope.row)" |
| | | > |
| | | <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" /> |
| | | <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> |
| | | <el-table-column fixed="right" label="操作" min-width="60" align="center" v-if="operationType !== 'view'"> |
| | | <el-table-column label="发票类型" prop="invoiceType" min-width="120"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="openProductForm('edit', scope.row,scope.$index)">编辑</el-button> |
| | | <el-select |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.invoiceType" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 120px" |
| | | > |
| | | <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="settlePieceArea" min-width="140"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.settlePieceArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 140px" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSettleAreaChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ scope.row.settlePieceArea ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <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: 160px" |
| | | /> |
| | | <span v-else>{{ scope.row.processRequirement ?? "" }}</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: 140px" |
| | | /> |
| | | <span v-else>{{ scope.row.remark ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="楼层编号" prop="floorCode" min-width="140" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-input |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.floorCode" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 140px" |
| | | /> |
| | | <span v-else>{{ scope.row.floorCode ?? "" }}</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: 110px" |
| | | /> |
| | | <span v-else>{{ scope.row.heavyBox ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="操作" min-width="220" align="center" v-if="operationType !== 'view'"> |
| | | <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="420" |
| | | trigger="click" |
| | | :hide-after="0" |
| | | v-model:visible="scope.row.__otherAmountPopoverVisible" |
| | | > |
| | | <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" @click="startAddOtherAmountForRow(scope.row)"> |
| | | 新增 |
| | | </el-button> |
| | | </div> |
| | | |
| | | <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0" |
| | | style="display:flex; flex-direction:column; gap: 8px;" |
| | | > |
| | | <div |
| | | v-for="(item, idx) in scope.row.salesProductProcessList" |
| | | :key="String(item.id) + '_' + idx" |
| | | style="display:flex; align-items:center; gap: 8px;" |
| | | > |
| | | <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="operationType === 'view'" |
| | | @change="handleOtherAmountQuantityChange(scope.row)" |
| | | /> |
| | | <el-button type="danger" link size="small" @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 |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="editProductInline(scope.row, scope.$index)" |
| | | > |
| | | 编辑 |
| | | </el-button> |
| | | <el-popover |
| | | :width="420" |
| | | trigger="click" |
| | | :hide-after="0" |
| | | v-model:visible="scope.row.__otherAmountPopoverVisible" |
| | | > |
| | | <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)" |
| | | @click="startAddOtherAmountForRow(scope.row)" |
| | | > |
| | | 新增 |
| | | </el-button> |
| | | </div> |
| | | |
| | | <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0" |
| | | style="display:flex; flex-direction:column; gap: 8px;" |
| | | > |
| | | <div |
| | | v-for="(item, idx) in scope.row.salesProductProcessList" |
| | | :key="String(item.id) + '_' + idx" |
| | | style="display:flex; align-items:center; gap: 8px;" |
| | | > |
| | | <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="operationType === 'view' || isProductShipped(scope.row)" |
| | | @change="handleOtherAmountQuantityChange(scope.row)" |
| | | /> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @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="8"> |
| | | <el-form-item label="税率(%):" prop="taxRate"> |
| | | <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%"> |
| | | <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> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice"> |
| | | <el-input-number |
| | | :step="0.01" |
| | |
| | | clearable |
| | | @change="calculateFromUnitPrice" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="税率(%):" prop="taxRate"> |
| | | <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%"> |
| | | <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> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | |
| | | const { productForm, productRules } = toRefs(productFormData); |
| | | // 防止循环计算的标志 |
| | | const isCalculating = ref(false); |
| | | |
| | | // 产品行内编辑:只允许同时编辑一行 |
| | | const editingProductRow = ref(null); |
| | | |
| | | const ensureProductRowDefaults = (row) => { |
| | | if (!row || typeof row !== "object") return; |
| | | if (!Array.isArray(row.salesProductProcessList)) row.salesProductProcessList = []; |
| | | if (row.__otherAmountPopoverVisible === undefined || row.__otherAmountPopoverVisible === null) row.__otherAmountPopoverVisible = false; |
| | | if (row.width === undefined || row.width === null) row.width = 0; |
| | | if (row.height === undefined || row.height === null) row.height = 0; |
| | | if (row.perimeter === undefined || row.perimeter === null) row.perimeter = 0; |
| | | if (row.actualPieceArea === undefined || row.actualPieceArea === null) row.actualPieceArea = 0; |
| | | if (row.actualTotalArea === undefined || row.actualTotalArea === null) row.actualTotalArea = 0; |
| | | if (row.settlePieceArea === undefined || row.settlePieceArea === null) row.settlePieceArea = 0; |
| | | if (row.settleTotalArea === undefined || row.settleTotalArea === null) row.settleTotalArea = 0; |
| | | if (row.processRequirement === undefined || row.processRequirement === null) row.processRequirement = ""; |
| | | if (row.remark === undefined || row.remark === null) row.remark = ""; |
| | | if (row.floorCode === undefined || row.floorCode === null) row.floorCode = ""; |
| | | if (row.invoiceType === undefined || row.invoiceType === null) row.invoiceType = ""; |
| | | if (row.taxRate === undefined || row.taxRate === null) row.taxRate = ""; |
| | | if (row.quantity === undefined || row.quantity === null) row.quantity = 0; |
| | | if (row.taxInclusiveUnitPrice === undefined || row.taxInclusiveUnitPrice === null) row.taxInclusiveUnitPrice = 0; |
| | | if (row.taxInclusiveTotalPrice === undefined || row.taxInclusiveTotalPrice === null) row.taxInclusiveTotalPrice = 0; |
| | | if (row.taxExclusiveTotalPrice === undefined || row.taxExclusiveTotalPrice === null) row.taxExclusiveTotalPrice = 0; |
| | | }; |
| | | |
| | | const stopOtherEditingRows = () => { |
| | | (productData.value || []).forEach((r) => { |
| | | if (r && r.__editing) r.__editing = false; |
| | | }); |
| | | editingProductRow.value = null; |
| | | }; |
| | | |
| | | const addProductInline = async () => { |
| | | if (operationType.value === "view") return; |
| | | // 已有行在编辑时,先取消其编辑状态,避免混乱 |
| | | stopOtherEditingRows(); |
| | | await getProductOptions(); |
| | | await fetchOtherAmountSelectOptions(true); |
| | | const row = { |
| | | id: null, |
| | | __tempKey: `__temp_${Date.now()}_${Math.random().toString(16).slice(2)}`, |
| | | __editing: true, |
| | | __isNew: true, |
| | | __productCategoryId: null, |
| | | productCategory: "", |
| | | productModelId: null, |
| | | specificationModel: "", |
| | | thickness: null, |
| | | quantity: 0, |
| | | taxInclusiveUnitPrice: 0, |
| | | taxRate: "", |
| | | taxInclusiveTotalPrice: 0, |
| | | taxExclusiveTotalPrice: 0, |
| | | invoiceType: "", |
| | | width: 0, |
| | | height: 0, |
| | | perimeter: 0, |
| | | actualPieceArea: 0, |
| | | actualTotalArea: 0, |
| | | settlePieceArea: 0, |
| | | settleTotalArea: 0, |
| | | processRequirement: "", |
| | | remark: "", |
| | | salesProductProcessList: [], |
| | | processFlowConfigId: null, |
| | | floorCode: "", |
| | | heavyBox: "", |
| | | }; |
| | | productData.value.push(row); |
| | | editingProductRow.value = row; |
| | | // 让现有的计算/其他金额逻辑复用当前行 |
| | | productForm.value = row; |
| | | }; |
| | | |
| | | const editProductInline = async (row, index) => { |
| | | if (operationType.value === "view") return; |
| | | if (!row) return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | stopOtherEditingRows(); |
| | | await getProductOptions(); |
| | | await fetchOtherAmountSelectOptions(true); |
| | | ensureProductRowDefaults(row); |
| | | // 产品大类 tree-select 回显:名称 -> id |
| | | row.__productCategoryId = findNodeIdByLabel(productOptions.value, row.productCategory); |
| | | |
| | | // 兼容后端字段命名(保持原逻辑) |
| | | row.actualPieceArea = row?.actualPieceArea ?? row?.actual_piece_area ?? 0; |
| | | row.actualTotalArea = row?.actualTotalArea ?? row?.actual_total_area ?? 0; |
| | | row.settlePieceArea = row?.settlePieceArea ?? row?.settle_piece_area ?? 0; |
| | | row.settleTotalArea = row?.settleTotalArea ?? row?.settle_total_area ?? 0; |
| | | row.processRequirement = row?.processRequirement ?? row?.process_requirement ?? ""; |
| | | row.remark = row?.remark ?? row?.remarks ?? ""; |
| | | row.floorCode = row?.floorCode ?? row?.floor_code ?? ""; |
| | | row.processFlowConfigId = row?.processFlowConfigId ?? row?.process_flow_config_id ?? null; |
| | | row.perimeter = row?.perimeter ?? row?.heavyBoxPerimeter ?? row?.heavyboxPerimeter ?? 0; |
| | | row.thickness = row?.thickness; |
| | | |
| | | row.salesProductProcessList = normalizeOtherAmountsFromRow(row); |
| | | mergeOtherAmountOptionsBySelection(row.salesProductProcessList); |
| | | row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); |
| | | |
| | | // 备份用于取消 |
| | | row.__backup = JSON.parse(JSON.stringify(row)); |
| | | row.__editing = true; |
| | | editingProductRow.value = row; |
| | | productForm.value = row; |
| | | |
| | | // 根据产品大类名称反查 tree 节点 id,并加载规格型号列表 |
| | | try { |
| | | const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); |
| | | const categoryId = findNodeIdByLabel(options, row.productCategory); |
| | | if (categoryId) { |
| | | const models = await modelList({ id: categoryId }); |
| | | modelOptions.value = models || []; |
| | | const currentModel = (modelOptions.value || []).find((m) => m.model === row.specificationModel); |
| | | if (currentModel) row.productModelId = currentModel.id; |
| | | } |
| | | } catch (e) { |
| | | console.error("加载产品规格型号失败", e); |
| | | } |
| | | |
| | | // 同步计算一次 |
| | | recalcPerimeterFromWidthHeight(); |
| | | recalcAreaFromWidthHeight(); |
| | | }; |
| | | |
| | | const validateInlineProductRow = (row) => { |
| | | if (!row) return false; |
| | | if (!row.productCategory) { |
| | | proxy.$modal.msgWarning("请选择产品大类"); |
| | | return false; |
| | | } |
| | | if (!row.productModelId) { |
| | | proxy.$modal.msgWarning("请选择规格型号"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | const saveProductInline = async (row, index) => { |
| | | if (operationType.value === "view") return; |
| | | if (!row) return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | // 确保 productForm 指向当前行,以复用计算逻辑 |
| | | productForm.value = row; |
| | | ensureProductRowDefaults(row); |
| | | |
| | | if (!validateInlineProductRow(row)) return; |
| | | |
| | | // 厚度精度处理 |
| | | if (row.thickness !== null && row.thickness !== undefined && row.thickness !== "") { |
| | | row.thickness = Number(Number(row.thickness).toFixed(15)); |
| | | } |
| | | |
| | | // 提交前兜底计算一次(沿用原逻辑) |
| | | recalcAreaTotals(); |
| | | |
| | | // 规范化其他金额提交结构 |
| | | row.salesProductProcessList = (Array.isArray(row.salesProductProcessList) ? row.salesProductProcessList : []) |
| | | .map((it) => ({ |
| | | id: it?.id, |
| | | processName: it?.processName ?? "", |
| | | unitPrice: Number(it?.unitPrice ?? 0) || 0, |
| | | quantity: Number(it?.quantity ?? 0) || 0, |
| | | })) |
| | | .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); |
| | | |
| | | // 规格型号:根据 productModelId 回填名称 |
| | | const model = (modelOptions.value || []).find((m) => String(m.id) === String(row.productModelId)); |
| | | if (model?.model) row.specificationModel = model.model; |
| | | |
| | | if (operationType.value === "edit") { |
| | | // 台账已存在:走原接口保存到后端,再回拉刷新 |
| | | const payload = { ...row, salesLedgerId: currentId.value, type: 1 }; |
| | | delete payload.__backup; |
| | | delete payload.__editing; |
| | | delete payload.__isNew; |
| | | delete payload.__productCategoryId; |
| | | delete payload.__tempKey; |
| | | await addOrUpdateSalesLedgerProduct(payload); |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | await getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => { |
| | | productData.value = res.productData; |
| | | }); |
| | | } else { |
| | | // 新增台账:仅在本地 productData 生效,最终随台账一起提交 |
| | | row.__isNew = false; |
| | | row.__editing = false; |
| | | delete row.__backup; |
| | | } |
| | | |
| | | stopOtherEditingRows(); |
| | | }; |
| | | |
| | | const cancelProductInline = (row, index) => { |
| | | if (!row) return; |
| | | if (row.__isNew) { |
| | | productData.value.splice(index, 1); |
| | | } else if (row.__backup) { |
| | | const restored = JSON.parse(JSON.stringify(row.__backup)); |
| | | // 保留 id 与状态字段 |
| | | const keepId = row.id; |
| | | Object.keys(row).forEach((k) => delete row[k]); |
| | | Object.assign(row, restored); |
| | | row.id = keepId; |
| | | row.__editing = false; |
| | | delete row.__backup; |
| | | } |
| | | stopOtherEditingRows(); |
| | | }; |
| | | |
| | | const openOtherAmountInline = async (row) => { |
| | | if (!row) return; |
| | | if (operationType.value === "view") return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | ensureProductRowDefaults(row); |
| | | productForm.value = row; |
| | | await fetchOtherAmountSelectOptions(true); |
| | | mergeOtherAmountOptionsBySelection(row.salesProductProcessList); |
| | | row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); |
| | | // 只做数据准备与打开浮层(新增由浮层内按钮触发) |
| | | row.__otherAmountPopoverVisible = true; |
| | | }; |
| | | |
| | | const startAddOtherAmountForRow = async (row) => { |
| | | if (!row) return; |
| | | if (operationType.value === "view") return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | ensureProductRowDefaults(row); |
| | | productForm.value = row; |
| | | await fetchOtherAmountSelectOptions(true); |
| | | mergeOtherAmountOptionsBySelection(row.salesProductProcessList); |
| | | row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); |
| | | startAddOtherAmount(); |
| | | }; |
| | | |
| | | const removeOtherAmountAtForRow = (row, index) => { |
| | | if (!row) return; |
| | | if (operationType.value === "view") return; |
| | | if (isProductShipped(row)) return; |
| | | productForm.value = row; |
| | | removeOtherAmountAt(index); |
| | | }; |
| | | |
| | | const handleOtherAmountQuantityChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | calculateFromUnitPrice(true); |
| | | }; |
| | | |
| | | const handleInlineProductCategoryChange = async (row, val) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | // 复用原有逻辑:会写入 productCategory(名称)、重置规格/厚度并拉取型号 |
| | | await getModels(val); |
| | | // 行内编辑时把选中的 id 记录下来,便于回显 |
| | | row.__productCategoryId = val; |
| | | }; |
| | | |
| | | const handleInlineProductModelChange = (row, val) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | // 复用原有逻辑:会写入 specificationModel、厚度 |
| | | getProductModel(val); |
| | | }; |
| | | |
| | | const handleInlineSizeChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | recalcPerimeterFromWidthHeight(); |
| | | recalcAreaFromWidthHeight(); |
| | | recalcAreaTotals(); |
| | | }; |
| | | |
| | | const handleInlineUnitPriceChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | calculateFromUnitPrice(); |
| | | recalcAreaTotals(); |
| | | }; |
| | | |
| | | const handleInlineQuantityChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | calculateFromQuantity(); |
| | | recalcAreaTotals(); |
| | | }; |
| | | |
| | | const handleInlineTaxRateChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | calculateFromTaxRate(); |
| | | }; |
| | | |
| | | const handleInlineSettleAreaChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | recalcAreaTotals(); |
| | | calculateFromUnitPrice(true); |
| | | }; |
| | | const upload = reactive({ |
| | | // 上传的地址 |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | if (valid) { |
| | | console.log('productData.value--', productData.value) |
| | | // 行内编辑未保存时不允许提交,避免脏数据/临时字段进入后端 |
| | | const hasEditingRow = (productData.value || []).some((r) => r && r.__editing); |
| | | if (hasEditingRow) { |
| | | proxy.$modal.msgWarning("产品信息存在未保存的编辑行,请先保存或取消"); |
| | | return; |
| | | } |
| | | if (productData.value !== null && productData.value.length > 0) { |
| | | form.value.productData = proxy.HaveJson(productData.value); |
| | | const cleanedProducts = (productData.value || []).map((p) => { |
| | | if (!p || typeof p !== "object") return p; |
| | | const { __editing, __isNew, __backup, __productCategoryId, __tempKey, __otherAmountPopoverVisible, ...rest } = p; |
| | | return rest; |
| | | }); |
| | | form.value.productData = proxy.HaveJson(cleanedProducts); |
| | | } else { |
| | | proxy.$modal.msgWarning("请添加产品信息"); |
| | | return; |
| | |
| | | |
| | | if (operationType.value === "add") { |
| | | productSelectedRows.value.forEach((selectedRow) => { |
| | | const index = productData.value.findIndex( |
| | | (product) => product.id === selectedRow.id |
| | | ); |
| | | const index = productData.value.findIndex((product) => { |
| | | if (!product || !selectedRow) return false; |
| | | // 新增行 id 为空时,用临时 key 定位 |
| | | if (product.id != null && selectedRow.id != null) { |
| | | return String(product.id) === String(selectedRow.id); |
| | | } |
| | | return ( |
| | | product.__tempKey && |
| | | selectedRow.__tempKey && |
| | | String(product.__tempKey) === String(selectedRow.__tempKey) |
| | | ); |
| | | }); |
| | | if (index !== -1) { |
| | | productData.value.splice(index, 1); |
| | | } |