| 2026-03-31 | liding | ![]() |
| 2026-03-31 | liding | ![]() |
| 2026-03-31 | spring | ![]() |
| 2026-03-31 | spring | ![]() |
| 2026-03-31 | huminmin | ![]() |
| 2026-03-31 | huminmin | ![]() |
| 2026-03-31 | spring | ![]() |
| 2026-03-31 | spring | ![]() |
| 2026-03-31 | huminmin | ![]() |
| 2026-03-31 | huminmin | ![]() |
| 2026-03-31 | spring | ![]() |
| 2026-03-30 | huminmin | ![]() |
src/api/inventoryManagement/stockInventory.js
@@ -65,4 +65,13 @@ url: "/stockInventory/getMaterials", method: "get", }); } } // 获取库存树(产品->规格/型号->批号->供应商) export const getStockInventoryAll = (params = {}) => { return request({ url: "/stockInventory/getStockInventoryAll", method: "get", params, }); }; src/views/inventoryManagement/dispatchLog/Record.vue
@@ -29,7 +29,7 @@ <div> <el-button @click="handleOut">导出</el-button> <el-button type="danger" plain @click="handleDelete">删除</el-button> <el-button type="primary" plain @click="handlePrint">打印</el-button> <!-- <el-button type="primary" plain @click="handlePrint">打印</el-button> --> </div> </div> <div class="table_list"> @@ -45,12 +45,30 @@ > <el-table-column align="center" type="selection" width="55" /> <el-table-column align="center" label="序号" type="index" width="60" /> <el-table-column <!-- <el-table-column label="出库批次" prop="outboundBatches" min-width="100" show-overflow-tooltip /> /> --> <el-table-column label="批号" min-width="120" show-overflow-tooltip > <template #default="scope"> {{ scope.row.batchNo || "" }} </template> </el-table-column> <el-table-column label="供应商" min-width="160" show-overflow-tooltip > <template #default="scope"> {{ scope.row.supplierName || scope.row.customer || "" }} </template> </el-table-column> <el-table-column label="出库日期" prop="createTime" @@ -424,8 +442,14 @@ </div> </div> <div class="info-row"> <span class="label">单号:</span> <span class="value">${item.code || ''}</span> <div> <span class="label">批号:</span> <span class="value">${item.batchNo || item.outboundBatches || ''}</span> </div> <div> <span class="label">单号:</span> <span class="value">${item.code || ''}</span> </div> </div> </div> src/views/inventoryManagement/receiptManagement/Record.vue
@@ -54,10 +54,24 @@ label="序号" type="index" width="60"/> <el-table-column label="入库批次" <!-- <el-table-column label="入库批次" prop="inboundBatches" width="280" show-overflow-tooltip/> show-overflow-tooltip/> --> <el-table-column label="批号" min-width="160" show-overflow-tooltip> <template #default="scope"> {{ scope.row.batchNo || '' }} </template> </el-table-column> <el-table-column label="供应商" min-width="200" show-overflow-tooltip> <template #default="scope"> {{ scope.row.supplierName || scope.row.customer || '' }} </template> </el-table-column> <el-table-column label="入库时间" prop="createTime" show-overflow-tooltip/> src/views/inventoryManagement/stockManagement/New.vue
@@ -37,6 +37,35 @@ <el-input v-model="formState.unit" disabled /> </el-form-item> <el-form-item label="批号" prop="batchNo" :rules="[{ required: true, message: '请输入批号', trigger: 'blur' }]"> <el-input v-model="formState.batchNo" placeholder="请输入批号" clearable /> </el-form-item> <el-form-item label="供应商" prop="customer" :rules="[{ required: true, message: '请选择供应商', trigger: 'change' }]" > <el-select v-model="formState.customer" placeholder="请选择供应商" filterable clearable allow-create :reserve-keyword="true" :default-first-option="false" > <el-option v-for="item in supplierList" :key="item.id" :label="item.supplierName" :value="item.supplierName" > {{ item.supplierName}} </el-option> </el-select> </el-form-item> <el-form-item label="库存数量" prop="qualitity" @@ -74,10 +103,11 @@ </template> <script setup> import {ref, computed, getCurrentInstance} from "vue"; import {ref, computed, getCurrentInstance, watch} from "vue"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import {createStockInventory} from "@/api/inventoryManagement/stockInventory.js"; import {createStockUnInventory} from "@/api/inventoryManagement/stockUninventory.js"; import {getOptions as getSupplierOptions} from "@/api/procurementManagement/procurementLedger.js"; const props = defineProps({ visible: { @@ -101,6 +131,8 @@ productName: "", productModelName: "", unit: "", batchNo: "", customer: "", qualitity: 0, warnNum: 0, remark: '', @@ -126,11 +158,39 @@ productModelId: undefined, productName: "", productModelName: "", description: '', unit: "", batchNo: "", customer: "", qualitity: 0, warnNum: 0, remark: '', }; isShow.value = false; }; const supplierList = ref([]); const loadSuppliers = async () => { try { const res = await getSupplierOptions(); // 复用采购台账筛选逻辑:isWhite=0 的供应商 supplierList.value = (res?.data || []).filter(item => item.isWhite === 0); } catch (e) { console.error("获取供应商列表失败:", e); supplierList.value = []; } }; watch( () => props.visible, (val) => { if (val) { loadSuppliers(); } }, { immediate: true } ); // 产品选择处理 const handleProductSelect = async (products) => { if (products && products.length > 0) { src/views/inventoryManagement/stockManagement/Qualified.vue
@@ -7,6 +7,21 @@ style="width: 240px" placeholder="请输入" clearable/> <span class="search_title ml10" style="margin-left: 20px">规格型号:</span> <el-input v-model="searchForm.model" style="width: 240px" placeholder="请输入" clearable/> <span class="search_title ml10" style="margin-left: 20px">UID码:</span> <el-input v-model="searchForm.uidNo" style="width: 200px" placeholder="请输入" clearable/> <span class="search_title ml10" style="margin-left: 20px">批次号:</span> <el-input v-model="searchForm.batchNo" style="width: 200px" placeholder="请输入" clearable/> <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button> </div> <div> @@ -26,6 +41,8 @@ <el-table-column label="产品大类" prop="productName" show-overflow-tooltip /> <el-table-column label="规格型号" prop="model" show-overflow-tooltip /> <el-table-column label="UID码" prop="uidNo" show-overflow-tooltip /> <el-table-column label="批号" prop="batchNo" show-overflow-tooltip /> <el-table-column label="供应商" prop="customer" show-overflow-tooltip /> <el-table-column label="单位" prop="unit" show-overflow-tooltip /> <el-table-column label="库存数量" prop="qualitity" show-overflow-tooltip /> <el-table-column label="冻结数量" prop="lockedQuantity" show-overflow-tooltip /> @@ -100,6 +117,9 @@ const data = reactive({ searchForm: { productName: '', model: '', uidNo: '', batchNo: '', } }) const { searchForm } = toRefs(data) src/views/inventoryManagement/stockManagement/Unqualified.vue
@@ -7,6 +7,21 @@ style="width: 240px" placeholder="请输入" clearable/> <span class="search_title ml10" style="margin-left: 20px">规格型号:</span> <el-input v-model="searchForm.model" style="width: 240px" placeholder="请输入" clearable/> <span class="search_title ml10" style="margin-left: 20px">UID码:</span> <el-input v-model="searchForm.uidNo" style="width: 200px" placeholder="请输入" clearable/> <span class="search_title ml10" style="margin-left: 20px">批次号:</span> <el-input v-model="searchForm.batchNo" style="width: 200px" placeholder="请输入" clearable/> <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button> </div> <div> @@ -23,6 +38,8 @@ <el-table-column label="产品大类" prop="productName" show-overflow-tooltip /> <el-table-column label="规格型号" prop="model" show-overflow-tooltip /> <el-table-column label="UID码" prop="uidNo" show-overflow-tooltip /> <el-table-column label="批号" prop="batchNo" show-overflow-tooltip /> <el-table-column label="供应商" prop="customer" show-overflow-tooltip /> <el-table-column label="单位" prop="unit" show-overflow-tooltip /> <el-table-column label="库存数量" prop="qualitity" show-overflow-tooltip /> <el-table-column label="冻结数量" prop="lockedQuantity" show-overflow-tooltip /> @@ -89,6 +106,9 @@ const data = reactive({ searchForm: { productName: '', model: '', uidNo: '', batchNo: '', } }) const { searchForm } = toRefs(data) src/views/inventoryManagement/stockReport/index.vue
@@ -168,13 +168,27 @@ show-overflow-tooltip v-if="searchForm.reportType !== 'inout'" /> <el-table-column label="入库批次" prop="inboundBatches" width="240" show-overflow-tooltip v-if="searchForm.reportType !== 'inout'" /> <el-table-column label="批号" width="240" show-overflow-tooltip v-if="searchForm.reportType !== 'inout'" > <template #default="scope"> {{ scope.row.batchNo || scope.row.inboundBatches || "" }} </template> </el-table-column> <el-table-column label="供应商" prop="supplierName" min-width="200" show-overflow-tooltip v-if="searchForm.reportType !== 'inout'" > <template #default="scope"> {{ scope.row.supplierName || scope.row.customer || "" }} </template> </el-table-column> <el-table-column label="产品大类" prop="productName" src/views/procurementManagement/procurementLedger/index.vue
@@ -86,6 +86,7 @@ prop="productCategory" /> <el-table-column label="规格型号" prop="specificationModel" /> <el-table-column label="UID码" prop="uidNo" /> <el-table-column label="单位" prop="unit" /> <el-table-column label="数量" @@ -409,6 +410,7 @@ prop="unit" width="70" /> <el-table-column label="UID码" prop="uidNo" /> <el-table-column label="批次号" prop="batchNo" /> <el-table-column label="数量" prop="quantity" width="70" /> @@ -579,6 +581,17 @@ </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="批次号:" prop="batchNo"> <el-input v-model="productForm.batchNo" placeholder="请输入" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="单位:" @@ -963,6 +976,7 @@ productCategory: "", productModelId: "", uidNo: "", batchNo: "", specificationModel: "", unit: "", quantity: "", src/views/productionManagement/productionOrder/MaterialRequisitionDialog.vue
@@ -2,7 +2,7 @@ <el-dialog v-model="visible" title="领料" width="1000px" width="1400px" top="3vh" :close-on-click-modal="false" destroy-on-close @@ -19,11 +19,8 @@ <el-table-column prop="productName" label="产品名称" min-width="150" /> <el-table-column prop="model" label="型号" min-width="150" /> <el-table-column prop="unit" label="单位" width="80" align="center" /> <!-- <el-table-column prop="qualitity" label="可领用数量" width="100" align="center"> <template #default="{ row }"> {{ row.qualitity || 0 }} </template> </el-table-column> --> <el-table-column prop="customer" label="供应商" min-width="160" show-overflow-tooltip /> <el-table-column prop="batchNo" label="批号" min-width="180" show-overflow-tooltip /> <el-table-column prop="requisitionQty" label="领用数量" width="120" align="center"> <template #default="{ row }"> <el-input-number @@ -61,13 +58,43 @@ <el-dialog v-model="addDialogVisible" title="选择原材料" width="800px" width="1000px" top="5vh" :close-on-click-modal="false" append-to-body > <div class="material-filter" style="margin-bottom: 20px;"> <el-select v-model="filterSupplier" placeholder="供应商" clearable filterable style="width: 220px" > <el-option v-for="opt in supplierFilterOptions" :key="opt" :label="opt" :value="opt" /> </el-select> <el-select v-model="filterBatchNo" placeholder="批号" clearable filterable style="width: 220px; margin-left: 12px" > <el-option v-for="opt in batchFilterOptions" :key="opt" :label="opt" :value="opt" /> </el-select> </div> <el-table :data="availableMaterials" :data="filteredMaterials" border style="width: 100%" height="50vh" @@ -78,6 +105,8 @@ <el-table-column prop="productName" label="产品名称" min-width="150" /> <el-table-column prop="model" label="型号" min-width="150" /> <el-table-column prop="unit" label="单位" width="80" align="center" /> <el-table-column prop="customer" label="供应商" min-width="160" show-overflow-tooltip /> <el-table-column prop="batchNo" label="批号" min-width="180" show-overflow-tooltip /> <!-- <el-table-column prop="qualitity" label="可领用数量" width="100" align="center"> <template #default="{ row }"> {{ row.qualitity || 0 }} @@ -129,6 +158,45 @@ const availableMaterials = ref([]); const selectedMaterials = ref([]); // 选择弹窗筛选条件(供应商/批号) const filterSupplier = ref(''); const filterBatchNo = ref(''); // 将后端可能返回的字段做一下归一化:供应商/批号字段名可能不一致 const normalizeMaterial = (m) => { return { ...m, customer: m.customer ?? m.supplierName ?? '', batchNo: m.batchNo ?? m.batchNumber ?? m.batch_number ?? m.lotNo ?? '', }; }; const supplierFilterOptions = computed(() => { return Array.from(new Set(availableMaterials.value.map((m) => m.customer).filter(Boolean))); }); const batchFilterOptions = computed(() => { const list = filterSupplier.value ? availableMaterials.value.filter((m) => m.customer === filterSupplier.value) : availableMaterials.value; return Array.from(new Set(list.map((m) => m.batchNo).filter(Boolean))); }); const filteredMaterials = computed(() => { return availableMaterials.value.filter((m) => { if (filterSupplier.value && m.customer !== filterSupplier.value) return false; if (filterBatchNo.value && m.batchNo !== filterBatchNo.value) return false; return true; }); }); watch(filterSupplier, () => { // 如果当前“批号”不属于所选供应商,则清空 if (filterBatchNo.value && !batchFilterOptions.value.includes(filterBatchNo.value)) { filterBatchNo.value = ''; } }); // 监听弹框打开,加载数据 watch(() => props.modelValue, (val) => { if (val && props.orderData) { @@ -146,7 +214,7 @@ if (bomId) { try { const res = await getMaterials({ bomId }); materialsFromApi = res.data || []; materialsFromApi = (res.data || []).map(normalizeMaterial); } catch (error) { console.error('查询原材料列表失败:', error); } @@ -164,7 +232,9 @@ return { ...savedItem, qualitity: apiItem?.qualitity ?? savedItem.qualitity ?? 0, requisitionQty: savedItem.requisitionQty || 0 requisitionQty: savedItem.requisitionQty || 0, customer: savedItem.customer ?? savedItem.supplierName ?? apiItem?.customer ?? '', batchNo: savedItem.batchNo ?? savedItem.batchNumber ?? apiItem?.batchNo ?? '', }; }); } catch (e) { @@ -190,7 +260,9 @@ const res = await getMaterials({ bomId }); console.log('getMaterials返回数据:', res.data); // 直接展示所有数据,不过滤 availableMaterials.value = res.data || []; availableMaterials.value = (res.data || []).map(normalizeMaterial); filterSupplier.value = ''; filterBatchNo.value = ''; selectedMaterials.value = []; addDialogVisible.value = true; } catch (error) { @@ -222,7 +294,7 @@ .map(item => ({ ...item, requisitionQty: 0, remark: '' remark: '', })); if (newItems.length === 0) { src/views/productionManagement/productionOrder/New.vue
@@ -166,9 +166,8 @@ const product = products[0]; formState.value.productId = product.productId; formState.value.productName = product.productName; const productNameArr = product.productName.split('-'); if (productNameArr.length === 3 && productNameArr[0] && productNameArr[1] !== '') { formState.value.manufacturingTeam = productNameArr[1].charAt(0) + '类车间'; if (product.parentName === '一类产品') { formState.value.manufacturingTeam = product.parentName.charAt(0) + '类车间'; } formState.value.productModelName = product.model; src/views/productionManagement/productionOrder/index.vue
@@ -159,6 +159,11 @@ width: '120px', }, { label: "批号", prop: "batchNo", width: '120px', }, { label: "工艺路线编号", prop: "processRouteCode", width: '200px', src/views/productionManagement/productionReporting/Input.vue
@@ -71,6 +71,14 @@ prop: 'uidNo', }, { label: '供应商', prop: 'customer', }, { label: '批号', prop: 'batchNo', }, { label: '投入数量', prop: 'quantity', }, src/views/productionManagement/workOrder/index.vue
@@ -173,6 +173,13 @@ label-width="120px"> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="产品名称"> <el-input v-model="reportForm.productName" readonly style="width: 300px" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="待生产数量"> <el-input v-model="reportForm.planQuantity" readonly @@ -212,18 +219,6 @@ @input="handleScrapQtyInput" /> </el-form-item></el-col> <el-col :span="12"> <el-form-item label="检品数量" prop="inspectedQuantity"> <el-input v-model.number="reportForm.inspectedQuantity" type="number" min="0" step="1" style="width: 300px" placeholder="请输入检品数量" @input="handleInspectedQuantity"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="班组信息"> <el-select v-model="reportForm.userId" style="width: 300px" @@ -262,6 +257,8 @@ <el-table-column prop="productName" label="产品名称" min-width="160" /> <el-table-column prop="model" label="型号" min-width="150" /> <el-table-column prop="unit" label="单位" width="90" align="center" /> <el-table-column prop="customer" label="供应商" min-width="160" show-overflow-tooltip /> <el-table-column prop="batchNo" label="批号" min-width="180" show-overflow-tooltip /> <el-table-column prop="reportQty" label="领用数量" width="160" align="center"> <template #default="{ row }"> <el-input-number @@ -300,7 +297,7 @@ <el-dialog v-model="addMaterialDialogVisible" title="选择原材料" width="1000px" width="1400px" top="5vh" :close-on-click-modal="false" append-to-body @@ -320,6 +317,8 @@ <el-table-column prop="productName" label="产品名称" min-width="160" /> <el-table-column prop="model" label="型号" min-width="150" /> <el-table-column prop="unit" label="单位" width="90" align="center" /> <el-table-column prop="customer" label="供应商" min-width="160" show-overflow-tooltip /> <el-table-column prop="batchNo" label="批号" min-width="180" show-overflow-tooltip /> <el-table-column prop="requisitionQty" label="可领用数量" width="140" align="center" /> </el-table> @@ -488,6 +487,8 @@ const userOptions = ref([]); const deviceOptions = ref([]); const reportForm = reactive({ // 报工弹框里“产品名称”只读回显 productName: "", planQuantity: 0, totalInvestment: 0, quantity: null, @@ -901,6 +902,8 @@ const showReportDialog = async row => { currentReportRowData.value = row; processParamList.value = await getProcessParamList(row) // 兼容后端/表格字段命名:优先 row.productName,其次 row.productCategory reportForm.productName = row.productName ?? row.productCategory ?? '' reportForm.planQuantity = row.planQuantity; reportForm.totalInvestment = row.totalInvestment; reportForm.quantity = src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -49,11 +49,6 @@ </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="UID码:" prop="uidNo"> <el-input v-model="form.uidNo" placeholder="请输入" disabled/> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> @@ -62,7 +57,17 @@ </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="数量:" prop="quantity"> <el-form-item label="UID码:" prop="uidNo"> <el-input v-model="form.uidNo" placeholder="请输入" disabled/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="检品数量:" prop="inspectedQuantity"> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.inspectedQuantity" placeholder="请输入" clearable :precision="2"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="总数量:" prop="quantity"> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2" :disabled="quantityDisabled"/> </el-form-item> </el-col> @@ -186,6 +191,7 @@ testStandardId: "", unit: "", uidNo: "", inspectedQuantity: "", quantity: "", checkCompany: "", checkResult: "", @@ -198,6 +204,7 @@ productModelId: [{ required: true, message: "请选择", trigger: "change" }], testStandardId: [{required: false, message: "请选择指标", trigger: "change"}], unit: [{ required: false, message: "请输入", trigger: "blur" }], inspectedQuantity: [{ required: true, message: "请输入", trigger: "blur" }], quantity: [{ required: true, message: "请输入", trigger: "blur" }], checkCompany: [{ required: false, message: "请输入", trigger: "blur" }], checkResult: [{ required: true, message: "请输入", trigger: "change" }], src/views/salesManagement/salesLedger/index.vue
@@ -47,6 +47,7 @@ <el-table-column label="产品大类" prop="productCategory" /> <el-table-column label="规格型号" prop="specificationModel" /> <el-table-column label="批号" prop="batchNo" /> <el-table-column label="UID码" prop="uidNo" /> <el-table-column label="单位" prop="unit" /> <el-table-column label="产品状态" width="100px" @@ -375,8 +376,25 @@ <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="批号:" prop="batchNo"> <el-select v-model="productForm.batchNo" placeholder="请选择" clearable filterable> <el-select v-model="productForm.batchNo" placeholder="请选择" clearable filterable @change="handleBatchNoChange"> <el-option v-for="item in batchNoOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="供应商:" prop="customer"> <el-select v-model="productForm.customer" placeholder="请选择" clearable filterable :disabled="!supplierOptions.length"> <el-option v-for="item in supplierOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> </el-col> @@ -533,6 +551,7 @@ <th>产品名称</th> <th>规格型号</th> <th>单位</th> <th>UID码</th> <th>单价</th> <th>零售数量</th> <th>零售金额</th> @@ -543,6 +562,7 @@ <td>{{ product.productCategory || '' }}</td> <td>{{ product.specificationModel || '' }}</td> <td>{{ product.unit || '' }}</td> <td>{{ product.uidNo || '' }}</td> <td>{{ product.taxInclusiveUnitPrice || '0' }}</td> <td>{{ product.quantity || '0' }}</td> <td>{{ product.taxInclusiveTotalPrice || '0' }}</td> @@ -696,11 +716,11 @@ delProduct, delLedgerFile, getProductInventory, saleOutboundExport, } from "@/api/salesManagement/salesLedger.js"; import { modelList, productTreeList } from "@/api/basicData/product.js"; import { getStockInventoryAll } from "@/api/inventoryManagement/stockInventory.js"; import useFormData from "@/hooks/useFormData.js"; import dayjs from "dayjs"; import { getCurrentDate } from "@/utils/index.js"; import {getProductOrderBatchNoOptions} from "@/api/productionManagement/productionOrder.js"; // 由 /stockInventory/getStockInventoryAll 驱动“批号/供应商”联动 import {safeTrainingExport} from "@/api/safeProduction/safetyTrainingAssessment.js"; const userStore = useUserStore(); @@ -713,6 +733,7 @@ const customerOption = ref([]); const productOptions = ref([]); const modelOptions = ref([]); const supplierOptions = ref([]); const tableLoading = ref(false); const page = reactive({ current: 1, @@ -761,6 +782,7 @@ const productFormData = reactive({ productForm: { productCategory: "", customer: "", specificationModel: "", uidNo: "", unit: "", @@ -776,6 +798,7 @@ productCategory: [{ required: true, message: "请选择", trigger: "change" }], productModelId: [{ required: true, message: "请选择", trigger: "change" }], batchNo: [{ required: true, message: "请选择", trigger: "change" }], customer: [{ required: true, message: "请选择", trigger: "change" }], specificationModel: [ { required: true, message: "请选择", trigger: "change" }, ], @@ -944,64 +967,203 @@ tableLoading.value = false; }); }; // 获取产品大类tree数据 const getProductOptions = () => { let stockInventoryAllTree = []; let batchNodeByBatchNo = new Map(); const normalizeStockInventoryTree = (nodes = []) => { const normalizeNodeValue = (node) => { // 后端有时会出现 id=null 的层级,这里给一个可用的 key if (node?.id !== null && node?.id !== undefined) return String(node.id); if (node?.nodeType === "batch") return String(node.batchNo ?? node.label ?? ""); if (node?.nodeType === "customer") return String(node.customer ?? node.label ?? ""); if (node?.nodeType === "model") return String(node.productModelId ?? node.model ?? node.label ?? ""); return String(node.productName ?? node.label ?? ""); }; const normalized = (list) => (list || []).map((n) => { const value = normalizeNodeValue(n); const label = n.label ?? n.productName ?? n.model ?? n.batchNo ?? n.customer ?? ""; return { ...n, value, label, children: normalized(n.children), }; }); return normalized(nodes); }; // 仅展示最多 3 个层级:第 1 层(product) -> 第 2 层(model) -> 第 3 层(batch),更深的节点不展示 const filterStockInventoryFirst3Levels = (nodes = []) => { const MAX_LEVEL = 3; const cloneAndFilterByLevel = (list = [], level = 1) => { return (list || []) .map((n) => { // 后续层级里如果还有 customer,直接剔除 if (n.nodeType === "customer") return null; // 到达展示深度后,不再向下挂子节点 if (level >= MAX_LEVEL) { return { ...n, children: [] }; } // 特例:batch 节点本身也不再展示 children(保持与接口节点语义一致) if (n.nodeType === "batch") { return { ...n, children: [] }; } return { ...n, children: cloneAndFilterByLevel(n.children, level + 1) }; }) .filter(Boolean); }; return cloneAndFilterByLevel(nodes, 1); }; const findNodeObjByValue = (nodes = [], value) => { for (let i = 0; i < (nodes || []).length; i++) { const node = nodes[i]; if (String(node?.value) === String(value)) return node; const children = node?.children || []; if (children.length) { const found = findNodeObjByValue(children, value); if (found) return found; } } return null; }; // 获取库存树(用于产品大类/规格型号联动) const getProductOptions = async () => { // 返回 Promise,便于在编辑产品时等待加载完成 return productTreeList().then((res) => { productOptions.value = convertIdToValue(res); return productOptions.value; }); const res = await getStockInventoryAll(); const data = res?.data || []; stockInventoryAllTree = normalizeStockInventoryTree(data); productOptions.value = filterStockInventoryFirst3Levels(stockInventoryAllTree); return productOptions.value; }; const formattedNumber = (row, column, cellValue) => { return parseFloat(cellValue).toFixed(2); }; // 获取tree子数据 // 获取tree子数据(先选产品,再选规格型号) const getModels = (value) => { productForm.value.productCategory = findNodeById(productOptions.value, value); modelList({ id: value }).then((res) => { modelOptions.value = res; }); const node = findNodeObjByValue(stockInventoryAllTree, value); if (!node) return; if (node.nodeType !== "product") return; // 选择产品后,重置下游字段 productForm.value.productCategory = node.label; modelOptions.value = (node.children || []) .filter((c) => c.nodeType === "model") .map((m) => ({ id: m.value, model: m.model ?? m.label ?? "", unit: m.unit ?? "", uidNo: m.uidNo ?? m.identifierCode ?? "", })); productForm.value.productModelId = null; productForm.value.specificationModel = ""; productForm.value.uidNo = ""; productForm.value.unit = ""; productForm.value.batchNo = ""; productForm.value.customer = ""; productForm.value.taxInclusiveUnitPrice = ""; productForm.value.taxInclusiveTotalPrice = ""; productForm.value.taxExclusiveTotalPrice = ""; modelOptions.value = modelOptions.value || []; batchNoOptions.value = []; supplierOptions.value = []; batchNodeByBatchNo = new Map(); }; // 规格型号选择后:回显 UID,并生成“批号下拉” const getProductModel = (value) => { const index = modelOptions.value.findIndex((item) => item.id === value); if (index !== -1) { productForm.value.specificationModel = modelOptions.value[index].model; productForm.value.unit = modelOptions.value[index].unit; productForm.value.uidNo = modelOptions.value[index].uidNo || ""; const modelNode = findNodeObjByValue(stockInventoryAllTree, value); if (!modelNode || modelNode.nodeType !== "model") return; const prevBatchNo = productForm.value.batchNo; const prevCustomer = productForm.value.customer; productForm.value.productModelId = modelNode.value; productForm.value.specificationModel = modelNode.model ?? modelNode.label ?? ""; // 有些接口/树数据里可能不包含 unit,这种情况下不要覆盖编辑时已回显的值 const nextUnit = modelNode.unit ?? ""; if (nextUnit !== null && nextUnit !== undefined && String(nextUnit).trim() !== "") { productForm.value.unit = nextUnit; } // 有些接口/树数据里可能不包含 uidNo,这种情况下不要覆盖编辑时已回显的值 const nextUidNo = modelNode.uidNo ?? modelNode.identifierCode ?? ""; if (nextUidNo !== null && nextUidNo !== undefined && String(nextUidNo).trim() !== "") { productForm.value.uidNo = nextUidNo; } const batchNodes = (modelNode.children || []).filter((b) => b.nodeType === "batch"); batchNodeByBatchNo = new Map( batchNodes.map((b) => { const key = String(b.batchNo ?? b.value ?? b.label ?? "").trim(); return [key, b]; }) ); batchNoOptions.value = batchNodes.map((b) => ({ label: String(b.batchNo ?? b.label ?? "").trim(), value: String(b.batchNo ?? b.value ?? b.label ?? "").trim(), })); // 批号不再属于新规格时,清空 const batchValues = new Set(batchNoOptions.value.map((x) => x.value)); if (!prevBatchNo || !batchValues.has(prevBatchNo)) { productForm.value.batchNo = ""; } // 需要供应商:批号回显后再生成 productForm.value.customer = ""; supplierOptions.value = []; if (productForm.value.batchNo) { handleBatchNoChange(productForm.value.batchNo, prevCustomer); } }; const handleBatchNoChange = (batchNo, prevCustomer) => { const safeBatchNo = String(batchNo ?? "").trim(); if (!safeBatchNo || !batchNodeByBatchNo.size) { productForm.value.customer = ""; supplierOptions.value = []; return; } const batchNode = batchNodeByBatchNo.get(String(safeBatchNo)); if (!batchNode) { productForm.value.customer = ""; supplierOptions.value = []; return; } // UID码可能来源于 batch 节点(不同接口字段名不一致时尽量兜底) const nextUidNo = batchNode.uidNo ?? batchNode.identifierCode ?? batchNode.uid ?? ""; if (nextUidNo !== null && nextUidNo !== undefined && String(nextUidNo).trim() !== "") { productForm.value.uidNo = nextUidNo; } const customers = (batchNode.children || []) .filter((c) => c.nodeType === "customer") .map((c) => c.customer ?? c.label ?? "") .filter(Boolean); const uniq = Array.from(new Set(customers)); supplierOptions.value = uniq.map((s) => ({ label: s, value: s })); // 编辑场景尽量回显;新增场景不回显 if (prevCustomer && uniq.includes(prevCustomer)) { productForm.value.customer = prevCustomer; } else { productForm.value.specificationModel = null; productForm.value.unit = null; productForm.value.uidNo = null; productForm.value.customer = ""; } }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].value === productId) { return nodes[i].label; // 找到节点,返回该节点 } if (nodes[i].children && nodes[i].children.length > 0) { const foundNode = findNodeById(nodes[i].children, productId); if (foundNode) { return foundNode; // 在子节点中找到,返回该节点 } } } return null; // 没有找到节点,返回null }; function convertIdToValue(data) { return data.map((item) => { const { id, children, ...rest } = item; const newItem = { ...rest, value: id, // 将 id 改为 value }; if (children && children.length > 0) { newItem.children = convertIdToValue(children); } return newItem; }); } // 根据名称反查产品大类 id,便于仅存名称时的反显 function findNodeIdByLabel(nodes, label) { if (!label) return null; @@ -1195,8 +1357,9 @@ return { // 台账字段 productCategory: p.product || p.productName || "", productModelId: p.productModelId || "", specificationModel: p.specification || "", uidNo: p.uidNo || "", uidNo: p.uidNo || "", unit: p.unit || "", quantity: quantity, taxRate: taxRate, @@ -1275,10 +1438,6 @@ }; const batchNoOptions = ref([]); const fetchBatchNoOptions = async () => { const res = await getProductOrderBatchNoOptions(); batchNoOptions.value = res.data; }; // 关闭弹框 const closeDia = () => { proxy.resetForm("formRef"); @@ -1302,25 +1461,44 @@ productIndex.value = index; // 编辑时根据产品大类名称反查 tree 节点 id,并加载规格型号列表 try { const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); const categoryId = findNodeIdByLabel(options, productForm.value.productCategory); if (categoryId) { const models = await modelList({ id: categoryId }); modelOptions.value = models || []; // 根据当前规格型号名称反查并设置 productModelId,便于下拉框显示已选值 const currentModel = (modelOptions.value || []).find( (m) => m.model === productForm.value.specificationModel ); if (!productOptions.value || productOptions.value.length === 0) { await getProductOptions(); } // 回显:根据“产品大类”反查产品节点 const categoryKey = findNodeIdByLabel(productOptions.value, productForm.value.productCategory); if (categoryKey) { const categoryNode = findNodeObjByValue(stockInventoryAllTree, categoryKey); const models = (categoryNode?.children || []) .filter((n) => n.nodeType === "model") .map((m) => ({ id: m.value, model: m.model ?? m.label ?? "", unit: m.unit ?? "", uidNo: m.uidNo ?? m.identifierCode ?? "", })); modelOptions.value = models; // 根据当前规格型号回显 const targetSpec = String(productForm.value.specificationModel ?? "").trim(); const currentModel = (models || []).find((m) => String(m.model ?? "").trim() === targetSpec) || (models || []).find((m) => String(m.model ?? "").trim().includes(targetSpec)) || (models || []).find((m) => targetSpec.includes(String(m.model ?? "").trim())); if (currentModel) { productForm.value.customer = productForm.value.customer || row.customer || row.supplierName || ""; productForm.value.productModelId = currentModel.id; getProductModel(currentModel.id); } } } catch (e) { // 加载失败时保持可编辑,不中断弹窗 console.error("加载产品规格型号失败", e); } // 最终兜底:如果中途被重置清空,至少回显行数据里的 UID productForm.value.uidNo = row.uidNo ?? productForm.value.uidNo ?? ""; // 最终兜底:同样保证单位不会因树数据缺失而被覆盖为空 productForm.value.unit = row.unit ?? productForm.value.unit ?? ""; } else { getProductOptions() } @@ -1732,6 +1910,7 @@ <th>产品名称</th> <th>规格型号</th> <th>单位</th> <th>UID码</th> <th>单价</th> <th>零售数量</th> <th>零售金额</th> @@ -1744,6 +1923,7 @@ <td>${product.productCategory || ''}</td> <td>${product.specificationModel || ''}</td> <td>${product.unit || ''}</td> <td>${product.uidNo || ''}</td> <td>${product.taxInclusiveUnitPrice || '0'}</td> <td>${product.quantity || '0'}</td> <td>${product.taxInclusiveTotalPrice || '0'}</td> @@ -2250,7 +2430,6 @@ onMounted(() => { getList(); fetchBatchNoOptions(); userListNoPage().then(res => { userList.value = res.data; })