| | |
| | | <el-option label="已发货" :value="4" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="入库状态:"> |
| | | <el-select v-model="searchForm.stockStatus" placeholder="请选择" clearable style="width: 140px"> |
| | | <el-option label="未入库" :value="0" /> |
| | | <el-option label="已入库" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery"> 搜索 </el-button> |
| | | </el-form-item> |
| | |
| | | @confirm="handleProcessFlowSelectConfirm" |
| | | /> |
| | | <el-space wrap> |
| | | <el-button type="primary" @click="handleSalesStock">入库</el-button> |
| | | <el-button type="primary" @click="openForm('add')">新增台账</el-button> |
| | | <el-button type="primary" @click="handleBulkDelivery">发货</el-button> |
| | | <el-button type="primary" plain @click="handleImport">导入</el-button> |
| | |
| | | <el-tag v-else type="info">-</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="入库状态" width="120" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="Number(scope.row.stockStatus) === 0" type="info">未入库</el-tag> |
| | | <el-tag v-else-if="Number(scope.row.stockStatus) === 1" type="success">已入库</el-tag> |
| | | <el-tag v-else type="info">-</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <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-row> |
| | | <el-row> |
| | | <el-form-item label="产品信息:" prop="entryDate"> |
| | | <el-button v-if="operationType !== 'view'" type="primary" @click="addProductInline">添加</el-button> |
| | | <el-button |
| | | v-if="operationType !== 'view'" |
| | | type="primary" |
| | | :disabled="hasEditingProductRow()" |
| | | @click="addProductInline" |
| | | > |
| | | 添加 |
| | | </el-button> |
| | | <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >删除</el-button> |
| | | </el-form-item> |
| | | </el-row> |
| | |
| | | <span v-else>{{ scope.row.productCategory ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="规格型号" prop="specificationModel" min-width="140"> |
| | | <el-table-column label="规格型号" prop="specificationModel" min-width="160"> |
| | | <template #default="scope"> |
| | | <el-select |
| | | v-if="scope.row.__editing" |
| | |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | style="width: 140px" |
| | | style="width: 100%" |
| | | @change="(val) => handleInlineProductModelChange(scope.row, val)" |
| | | > |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | |
| | | <span v-else>{{ scope.row.specificationModel ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="厚度" prop="thickness" min-width="90"> |
| | | <el-table-column label="厚度(mm)" prop="thickness" min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | |
| | | :min="0" |
| | | :step="0.000000000000001" |
| | | :precision="15" |
| | | style="width: 110px" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | <span v-else>{{ scope.row.thickness ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="宽(mm)" prop="width" min-width="80"> |
| | | <el-table-column label="宽(mm)" prop="width" min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 110px" |
| | | style="width:100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSizeChange(scope.row)" |
| | | @input="() => handleInlineSizeChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ scope.row.width ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="高(mm)" prop="height" min-width="80"> |
| | | <el-table-column label="高(mm)" prop="height" min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | |
| | | :min="0" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 110px" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSizeChange(scope.row)" |
| | | @input="() => handleInlineSizeChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ scope.row.height ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="面积(m²)" prop="actualTotalArea" min-width="100"> |
| | | <el-table-column label="结算单片面积(㎡)" prop="settlePieceArea" min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.actualTotalArea" |
| | | v-model="scope.row.settlePieceArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 120px" |
| | | placeholder="自动计算" |
| | | :disabled="true" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSettleAreaChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ scope.row.actualTotalArea ?? "" }}</span> |
| | | <span v-else>{{ scope.row.settlePieceArea ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="数量" prop="quantity" min-width="90"> |
| | | <el-table-column label="数量" prop="quantity" min-width="150"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | |
| | | :step="0.1" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 110px" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineQuantityChange(scope.row)" |
| | | @input="() => handleInlineQuantityChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ scope.row.quantity ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="面积(m²)" prop="actualTotalArea" min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.actualTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | style="width: 100%" |
| | | placeholder="自动计算" |
| | | /> |
| | | <span v-else>{{ scope.row.actualTotalArea ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" min-width="140"> |
| | |
| | | :step="0.01" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 120px" |
| | | 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 ?? 0) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="税率(%)" prop="taxRate" min-width="90"> |
| | | <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: 90px" |
| | | style="width: 100%" |
| | | @change="() => handleInlineTaxRateChange(scope.row)" |
| | | > |
| | | <el-option label="1" value="1" /> |
| | |
| | | <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 label="含税总价(元)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" min-width="120"/> |
| | | <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" min-width="120"/> |
| | | <el-table-column label="发票类型" prop="invoiceType" min-width="120"> |
| | | <template #default="scope"> |
| | | <el-select |
| | |
| | | v-model="scope.row.invoiceType" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 120px" |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="增普票" value="增普票" /> |
| | | <el-option label="增专票" value="增专票" /> |
| | |
| | | <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-model="scope.row.processRequirement" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 160px" |
| | | style="width: 100%" |
| | | /> |
| | | <span v-else>{{ scope.row.processRequirement ?? "" }}</span> |
| | | </template> |
| | |
| | | v-model="scope.row.remark" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 140px" |
| | | style="width: 100%" |
| | | /> |
| | | <span v-else>{{ scope.row.remark ?? "" }}</span> |
| | | </template> |
| | |
| | | v-model="scope.row.floorCode" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 140px" |
| | | style="width: 100%" |
| | | /> |
| | | <span v-else>{{ scope.row.floorCode ?? "" }}</span> |
| | | </template> |
| | |
| | | v-model="scope.row.heavyBox" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 110px" |
| | | style="width: 100%" |
| | | /> |
| | | <span v-else>{{ scope.row.heavyBox ?? "" }}</span> |
| | | </template> |
| | |
| | | <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" |
| | | :width="560" |
| | | trigger="click" |
| | | :hide-after="0" |
| | | v-model:visible="scope.row.__otherAmountPopoverVisible" |
| | | :visible="scope.row.__otherAmountPopoverVisible" |
| | | @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)" |
| | | > |
| | | <template #reference> |
| | | <el-button |
| | |
| | | 新增 |
| | | </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="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-direction:column; gap: 8px;" |
| | | 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;" |
| | | 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-button> |
| | | <el-popover |
| | | :width="420" |
| | | :width="560" |
| | | trigger="click" |
| | | :hide-after="0" |
| | | v-model:visible="scope.row.__otherAmountPopoverVisible" |
| | | :visible="scope.row.__otherAmountPopoverVisible" |
| | | @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)" |
| | | > |
| | | <template #reference> |
| | | <el-button |
| | |
| | | 新增 |
| | | </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)" |
| | | > |
| | | <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)" |
| | | @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null" |
| | | > |
| | | 取消 |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row) || 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-direction:column; gap: 8px;" |
| | | 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;" |
| | | 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 }} |
| | |
| | | <script setup> |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import {onMounted, ref, getCurrentInstance, watch} from "vue"; |
| | | import {onMounted, ref, getCurrentInstance, watch, nextTick} from "vue"; |
| | | import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import { ArrowDown } from "@element-plus/icons-vue"; |
| | |
| | | getSalesOrder, |
| | | getSalesInvoices, |
| | | getSalesLabel, |
| | | salesStock, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | deliveryStatus: undefined, // 发货状态:1未发货 2审批中 3审批失败 4已发货 |
| | | stockStatus: undefined, // 入库状态:0未入库 1已入库 |
| | | }, |
| | | form: { |
| | | salesContractNo: "", |
| | |
| | | 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.__inlineOtherAmountAdding === undefined || row.__inlineOtherAmountAdding === null) row.__inlineOtherAmountAdding = false; |
| | | if (row.__inlineOtherAmountAddId === undefined) row.__inlineOtherAmountAddId = null; |
| | | 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; |
| | |
| | | editingProductRow.value = null; |
| | | }; |
| | | |
| | | const hasEditingProductRow = () => { |
| | | return (productData.value || []).some((r) => r && r.__editing); |
| | | }; |
| | | |
| | | const addProductInline = async () => { |
| | | if (operationType.value === "view") return; |
| | | // 已有行在编辑时,先取消其编辑状态,避免混乱 |
| | | stopOtherEditingRows(); |
| | | if (hasEditingProductRow()) { |
| | | proxy.$modal.msgWarning("请先保存或取消当前编辑行"); |
| | | return; |
| | | } |
| | | await getProductOptions(); |
| | | await fetchOtherAmountSelectOptions(true); |
| | | const row = { |
| | |
| | | |
| | | // 提交前兜底计算一次(沿用原逻辑) |
| | | recalcAreaTotals(); |
| | | // 提交兜底:税率/数量未填时按数字 0 传递 |
| | | row.taxRate = Number(row.taxRate ?? 0) || 0; |
| | | row.quantity = Number(row.quantity ?? 0) || 0; |
| | | |
| | | // 规范化其他金额提交结构 |
| | | row.salesProductProcessList = (Array.isArray(row.salesProductProcessList) ? row.salesProductProcessList : []) |
| | |
| | | } |
| | | ensureProductRowDefaults(row); |
| | | productForm.value = row; |
| | | otherAmountAddTargetRow.value = row; |
| | | await fetchOtherAmountSelectOptions(true); |
| | | mergeOtherAmountOptionsBySelection(row.salesProductProcessList); |
| | | row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); |
| | | // 只做数据准备与打开浮层(新增由浮层内按钮触发) |
| | | row.__otherAmountPopoverVisible = true; |
| | | }; |
| | | |
| | | const keepOtherAmountPopoverOpenKey = ref(null); |
| | | const keepOtherAmountPopoverOpenUntil = ref(0); |
| | | |
| | | const getOtherAmountRowKey = (row) => String(row?.__tempKey ?? row?.id ?? ""); |
| | | |
| | | const lockOtherAmountPopoverOpen = (row, durationMs = 1200) => { |
| | | const key = getOtherAmountRowKey(row); |
| | | if (!key) return; |
| | | keepOtherAmountPopoverOpenKey.value = key; |
| | | keepOtherAmountPopoverOpenUntil.value = Date.now() + durationMs; |
| | | }; |
| | | |
| | | const handleOtherAmountPopoverVisibleChange = (row, visible) => { |
| | | if (!row) return; |
| | | if (visible) { |
| | | row.__otherAmountPopoverVisible = true; |
| | | return; |
| | | } |
| | | if (row.__inlineOtherAmountAdding) { |
| | | row.__otherAmountPopoverVisible = true; |
| | | return; |
| | | } |
| | | const key = getOtherAmountRowKey(row); |
| | | const shouldKeepOpen = Boolean( |
| | | key && |
| | | keepOtherAmountPopoverOpenKey.value === key && |
| | | Date.now() < keepOtherAmountPopoverOpenUntil.value |
| | | ); |
| | | row.__otherAmountPopoverVisible = shouldKeepOpen; |
| | | }; |
| | | |
| | | const startAddOtherAmountForRow = async (row) => { |
| | |
| | | await fetchOtherAmountSelectOptions(true); |
| | | mergeOtherAmountOptionsBySelection(row.salesProductProcessList); |
| | | row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); |
| | | startAddOtherAmount(); |
| | | row.__inlineOtherAmountAddId = null; |
| | | row.__inlineOtherAmountAdding = true; |
| | | row.__otherAmountPopoverVisible = true; |
| | | }; |
| | | |
| | | const confirmAddOtherAmountForRow = (row) => { |
| | | if (!row) return; |
| | | ensureProductRowDefaults(row); |
| | | productForm.value = row; |
| | | const selectedId = row.__inlineOtherAmountAddId; |
| | | if (selectedId === null || selectedId === undefined || selectedId === "") return; |
| | | const opt = otherAmountSelectOptions.value.find((o) => String(o.id) === String(selectedId)); |
| | | if (!opt) return; |
| | | const exists = (row.salesProductProcessList ?? []).some( |
| | | (it) => String(it?.id) === String(opt.id) |
| | | ); |
| | | if (exists) { |
| | | proxy.$modal.msgWarning("该其他金额项目已添加"); |
| | | return; |
| | | } |
| | | row.salesProductProcessList.push({ |
| | | id: opt.id, |
| | | processName: opt.processName, |
| | | unitPrice: opt.unitPrice ?? 0, |
| | | quantity: 0, |
| | | }); |
| | | row.__inlineOtherAmountAddId = null; |
| | | row.__inlineOtherAmountAdding = false; |
| | | row.__otherAmountPopoverVisible = true; |
| | | calculateFromUnitPrice(true); |
| | | }; |
| | | |
| | | const removeOtherAmountAtForRow = (row, index) => { |
| | |
| | | // 其他金额:点击“新增”后在弹窗里选择一个项目 |
| | | const otherAmountAddDialogVisible = ref(false); |
| | | const otherAmountAddId = ref(null); |
| | | const otherAmountAddTargetRow = ref(null); |
| | | const otherAmountAddTargetRowKey = ref(null); |
| | | |
| | | const startAddOtherAmount = () => { |
| | | if (operationType.value === "view") return; |
| | |
| | | const cancelAddOtherAmount = () => { |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | otherAmountAddTargetRow.value = null; |
| | | otherAmountAddTargetRowKey.value = null; |
| | | keepOtherAmountPopoverOpenKey.value = null; |
| | | keepOtherAmountPopoverOpenUntil.value = 0; |
| | | }; |
| | | |
| | | const handleOtherAmountSelected = (id) => { |
| | |
| | | }); |
| | | calculateFromUnitPrice(true); |
| | | |
| | | // 选择完成后关闭弹窗,下一次可再次点击“新增”继续添加 |
| | | // 选择完成后关闭“新增其他金额”弹窗,并保持行内“其他金额”弹层开启,便于直接填写数量 |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | const reopenOtherAmountPopover = () => { |
| | | let targetRow = otherAmountAddTargetRow.value; |
| | | const rowKey = otherAmountAddTargetRowKey.value; |
| | | if (rowKey) { |
| | | const matchedRow = (productData.value || []).find( |
| | | (it) => String(it?.__tempKey ?? it?.id ?? "") === rowKey |
| | | ); |
| | | if (matchedRow) targetRow = matchedRow; |
| | | } |
| | | if (targetRow && typeof targetRow === "object") { |
| | | lockOtherAmountPopoverOpen(targetRow, 1500); |
| | | targetRow.__otherAmountPopoverVisible = true; |
| | | } |
| | | }; |
| | | nextTick(() => { |
| | | reopenOtherAmountPopover(); |
| | | setTimeout(reopenOtherAmountPopover, 0); |
| | | setTimeout(reopenOtherAmountPopover, 80); |
| | | }); |
| | | otherAmountAddTargetRow.value = null; |
| | | otherAmountAddTargetRowKey.value = null; |
| | | }; |
| | | |
| | | const confirmAddOtherAmount = () => { |
| | |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // 入库(销售台账 -> 入库状态) |
| | | const handleSalesStock = async () => { |
| | | if (selectedRows.value.length !== 1) { |
| | | ElMessage.warning("请勾选一条台账数据进行入库"); |
| | | return; |
| | | } |
| | | const row = selectedRows.value[0] || {}; |
| | | const id = row?.id; |
| | | if (!id) { |
| | | ElMessage.warning("所选数据缺少id,无法入库"); |
| | | return; |
| | | } |
| | | if (Number(row.stockStatus) === 1) { |
| | | ElMessage.info("该台账已入库,无需重复操作"); |
| | | return; |
| | | } |
| | | try { |
| | | await ElMessageBox.confirm("确认对所选台账执行入库?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }); |
| | | } catch { |
| | | return; |
| | | } |
| | | proxy?.$modal?.loading?.("正在入库,请稍候..."); |
| | | try { |
| | | await salesStock({ id }); |
| | | proxy?.$modal?.msgSuccess?.("入库成功"); |
| | | await getList(); |
| | | } catch (e) { |
| | | proxy?.$modal?.msgError?.("入库失败,请稍后重试"); |
| | | } finally { |
| | | proxy?.$modal?.closeLoading?.(); |
| | | } |
| | | }; |
| | | |
| | | // 打开“工艺路线配置”选择弹窗(必须显式选择) |
| | |
| | | } |
| | | return null; // 没有找到节点,返回null |
| | | }; |
| | | function convertIdToValue(data) { |
| | | function convertIdToValue(data, level = 0) { |
| | | return data.map((item) => { |
| | | const { id, children, ...rest } = item; |
| | | const hasChildren = Array.isArray(children) && children.length > 0; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // 将 id 改为 value |
| | | // 仅允许叶子节点被选择(有子节点的分类节点统一禁用) |
| | | disabled: Boolean(rest?.disabled) || hasChildren, |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | if (hasChildren) { |
| | | newItem.children = convertIdToValue(children, level + 1); |
| | | } |
| | | |
| | | return newItem; |
| | |
| | | const cleanedProducts = (productData.value || []).map((p) => { |
| | | if (!p || typeof p !== "object") return p; |
| | | const { __editing, __isNew, __backup, __productCategoryId, __tempKey, __otherAmountPopoverVisible, ...rest } = p; |
| | | rest.taxRate = Number(rest.taxRate ?? 0) || 0; |
| | | rest.quantity = Number(rest.quantity ?? 0) || 0; |
| | | return rest; |
| | | }); |
| | | form.value.productData = proxy.HaveJson(cleanedProducts); |
| | |
| | | |
| | | // 面积/总计字段在提交前兜底计算一次 |
| | | recalcAreaTotals(); |
| | | // 提交兜底:税率/数量未填时按数字 0 传递 |
| | | productForm.value.taxRate = Number(productForm.value.taxRate ?? 0) || 0; |
| | | productForm.value.quantity = Number(productForm.value.quantity ?? 0) || 0; |
| | | // 其他金额只提交 {id, processName, quantity}(后端字段:salesProductProcessList) |
| | | productForm.value.salesProductProcessList = (Array.isArray(productForm.value.salesProductProcessList) |
| | | ? productForm.value.salesProductProcessList |
| | |
| | | |
| | | // 根据不含税总价计算含税单价和数量 |
| | | const calculateFromExclusiveTotalPrice = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请先选择税率"); |
| | | return; |
| | | } |
| | | // if (!productForm.value.taxRate) { |
| | | // proxy.$modal.msgWarning("请先选择税率"); |
| | | // return; |
| | | // } |
| | | if (isCalculating.value) return; |
| | | |
| | | const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice); |
| | |
| | | |
| | | // 根据数量变化计算总价 |
| | | const calculateFromQuantity = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请先选择税率"); |
| | | return; |
| | | } |
| | | // if (!productForm.value.taxRate) { |
| | | // proxy.$modal.msgWarning("请先选择税率"); |
| | | // return; |
| | | // } |
| | | if (isCalculating.value) return; |
| | | |
| | | const quantity = parseFloat(productForm.value.quantity); |
| | |
| | | |
| | | // 根据含税单价变化计算总价 |
| | | const calculateFromUnitPrice = (silent = false) => { |
| | | if (!productForm.value.taxRate) { |
| | | if (!silent) proxy.$modal.msgWarning("请先选择税率"); |
| | | return; |
| | | } |
| | | // if (!productForm.value.taxRate) { |
| | | // if (!silent) proxy.$modal.msgWarning("请先选择税率"); |
| | | // return; |
| | | // } |
| | | if (isCalculating.value) return; |
| | | |
| | | const quantity = parseFloat(productForm.value.quantity); |
| | |
| | | |
| | | // 根据税率变化计算不含税总价 |
| | | const calculateFromTaxRate = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请先选择税率"); |
| | | return; |
| | | } |
| | | // if (!productForm.value.taxRate) { |
| | | // proxy.$modal.msgWarning("请先选择税率"); |
| | | // return; |
| | | // } |
| | | if (isCalculating.value) return; |
| | | |
| | | const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice); |