| | |
| | | <el-dialog |
| | | v-model="visible" |
| | | title="领料" |
| | | width="1000px" |
| | | width="1400px" |
| | | top="3vh" |
| | | :close-on-click-modal="false" |
| | | destroy-on-close |
| | | class="material-requisition-dialog" |
| | | > |
| | | <div class="material-requisition-form"> |
| | | <!-- 原材料列表 --> |
| | | <el-table :data="materialList" border style="width: 100%" height="65vh"> |
| | | <el-table-column type="index" label="序号" width="60" align="center" /> |
| | | <el-table-column prop="productName" label="产品名称" min-width="150" /> |
| | | <el-table-column prop="model" label="型号" min-width="150" /> |
| | | <!-- <el-table-column prop="batchNo" label="批号" min-width="150"> |
| | | <template #default="{ row }"> |
| | | <el-select |
| | | v-model="row.batchNo" |
| | | placeholder="请选择批号" |
| | | clearable |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in row.batchOptions || []" |
| | | :key="item.batchNo" |
| | | :label="item.batchNo" |
| | | :value="item.batchNo" |
| | | /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> --> |
| | | <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="requisitionQty" label="领用数量" width="120" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-input-number |
| | | v-model="row.requisitionQty" |
| | | :min="0" |
| | | :precision="2" |
| | | :controls="false" |
| | | :disabled="!row.qualitity || hasDrawMaterials" |
| | | style="width: 100%" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="备注" min-width="150"> |
| | | </el-table-column> |
| | | </el-table> |
| | | <!-- 原材料 Tab --> |
| | | |
| | | <div class="operation-bar"> |
| | | <el-button type="primary" @click="handleAdd">新增</el-button> |
| | | </div> |
| | | <el-table :data="materialList" border style="width: 100%" height="50vh"> |
| | | <el-table-column type="index" label="序号" width="60" align="center" /> |
| | | <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="requisitionQty" label="领用数量" width="120" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-input-number |
| | | v-model="row.requisitionQty" |
| | | :min="0" |
| | | :max="row.qualitity || 0" |
| | | :precision="2" |
| | | :controls="false" |
| | | :disabled="!row.qualitity" |
| | | style="width: 100%" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="备注" min-width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.remark" placeholder="请输入备注" clearable /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="80" align="center"> |
| | | <template #default="{ $index }"> |
| | | <el-button type="danger" link @click="handleDelete($index)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <template #footer> |
| | |
| | | <el-button @click="handleCancel">取 消</el-button> |
| | | </span> |
| | | </template> |
| | | |
| | | <!-- 新增原材料弹窗 --> |
| | | <el-dialog |
| | | v-model="addDialogVisible" |
| | | title="选择原材料" |
| | | 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="filteredMaterials" |
| | | border |
| | | style="width: 100%" |
| | | height="50vh" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column type="index" label="序号" width="60" align="center" /> |
| | | <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 }} |
| | | </template> |
| | | </el-table-column> --> |
| | | </el-table> |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" @click="handleAddConfirm">确 定</el-button> |
| | | <el-button @click="addDialogVisible = false">取 消</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, watch } from 'vue'; |
| | | import { ref, computed, watch } from 'vue'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import { getByBomId, drawMaterials } from '@/api/productionManagement/productionOrder.js'; |
| | | import { drawMaterials } from '@/api/productionManagement/productionOrder.js'; |
| | | import { getMaterials } from '@/api/inventoryManagement/stockInventory.js'; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | |
| | | |
| | | const loading = ref(false); |
| | | const saving = ref(false); |
| | | const activeTab = ref('material'); |
| | | const materialList = ref([]); |
| | | const hasDrawMaterials = ref(false); |
| | | |
| | | // 新增弹窗相关 |
| | | const addDialogVisible = ref(false); |
| | | 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) => { |
| | |
| | | const loadMaterialList = async () => { |
| | | const order = props.orderData; |
| | | const drawMaterialsData = order.drawMaterials; |
| | | const bomId = order?.bomId; |
| | | |
| | | // 如果已有领料数据,直接使用 |
| | | // 先获取getMaterials的最新数据 |
| | | let materialsFromApi = []; |
| | | if (bomId) { |
| | | try { |
| | | const res = await getMaterials({ bomId }); |
| | | materialsFromApi = (res.data || []).map(normalizeMaterial); |
| | | } catch (error) { |
| | | console.error('查询原材料列表失败:', error); |
| | | } |
| | | } |
| | | |
| | | // 加载已保存的领料数据 |
| | | if (drawMaterialsData) { |
| | | hasDrawMaterials.value = true; |
| | | try { |
| | | const list = typeof drawMaterialsData === 'string' |
| | | ? JSON.parse(drawMaterialsData) |
| | | : drawMaterialsData; |
| | | materialList.value = list.map(item => ({ |
| | | ...item, |
| | | requisitionQty: item.requisitionQty || 0 |
| | | })); |
| | | return; |
| | | // 合并数据:使用API的qualitity,使用保存的requisitionQty和remark |
| | | materialList.value = list.map(savedItem => { |
| | | const apiItem = materialsFromApi.find(m => m.id === savedItem.id || m.productModelId === savedItem.productModelId); |
| | | return { |
| | | ...savedItem, |
| | | qualitity: apiItem?.qualitity ?? savedItem.qualitity ?? 0, |
| | | requisitionQty: savedItem.requisitionQty || 0, |
| | | customer: savedItem.customer ?? savedItem.supplierName ?? apiItem?.customer ?? '', |
| | | batchNo: savedItem.batchNo ?? savedItem.batchNumber ?? apiItem?.batchNo ?? '', |
| | | }; |
| | | }); |
| | | } catch (e) { |
| | | console.error('解析领料数据失败:', e); |
| | | materialList.value = []; |
| | | } |
| | | } else { |
| | | materialList.value = []; |
| | | } |
| | | }; |
| | | |
| | | // 没有领料数据,调用接口查询 |
| | | hasDrawMaterials.value = false; |
| | | // 打开新增弹窗 |
| | | const handleAdd = async () => { |
| | | const order = props.orderData; |
| | | const bomId = order?.bomId; |
| | | if (!bomId) { |
| | | ElMessage.warning('当前订单缺少BOM信息'); |
| | |
| | | |
| | | loading.value = true; |
| | | try { |
| | | const res = await getByBomId({ bomId }); |
| | | const data = res.data || []; |
| | | // 处理数据,添加领用数量字段和批号选项 |
| | | materialList.value = data.map(item => ({ |
| | | ...item, |
| | | requisitionQty: item.qualitity ? 0 : 0, |
| | | batchNo: item.batchNo || '', |
| | | remark: item.remark || '', |
| | | // 批号选项,从库存原材料信息中获取 |
| | | batchOptions: item.inventoryList || [] |
| | | })); |
| | | const res = await getMaterials({ bomId }); |
| | | console.log('getMaterials返回数据:', res.data); |
| | | // 直接展示所有数据,不过滤 |
| | | availableMaterials.value = (res.data || []).map(normalizeMaterial); |
| | | filterSupplier.value = ''; |
| | | filterBatchNo.value = ''; |
| | | selectedMaterials.value = []; |
| | | addDialogVisible.value = true; |
| | | } catch (error) { |
| | | console.error('查询原材料列表失败:', error); |
| | | ElMessage.error('查询原材料列表失败'); |
| | | materialList.value = []; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 选择变化 |
| | | const handleSelectionChange = (selection) => { |
| | | selectedMaterials.value = selection; |
| | | }; |
| | | |
| | | // 确认添加 |
| | | const handleAddConfirm = () => { |
| | | if (selectedMaterials.value.length === 0) { |
| | | ElMessage.warning('请选择至少一条记录'); |
| | | return; |
| | | } |
| | | |
| | | // 过滤掉已存在的(通过id和productModelId判断) |
| | | const existingIds = materialList.value.map(item => item.id); |
| | | const existingProductModelIds = materialList.value.map(item => item.productModelId); |
| | | |
| | | const newItems = selectedMaterials.value |
| | | .filter(item => !existingIds.includes(item.id) && !existingProductModelIds.includes(item.productModelId)) |
| | | .map(item => ({ |
| | | ...item, |
| | | requisitionQty: 0, |
| | | remark: '', |
| | | })); |
| | | |
| | | if (newItems.length === 0) { |
| | | ElMessage.warning('所选数据已存在,无需重复添加'); |
| | | return; |
| | | } |
| | | |
| | | materialList.value = [...materialList.value, ...newItems]; |
| | | addDialogVisible.value = false; |
| | | ElMessage.success(`成功添加 ${newItems.length} 条记录`); |
| | | }; |
| | | |
| | | // 删除 |
| | | const handleDelete = (index) => { |
| | | materialList.value.splice(index, 1); |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | visible.value = false; |
| | | materialList.value = []; |
| | | hasDrawMaterials.value = false; |
| | | activeTab.value = 'material'; |
| | | }; |
| | | |
| | | const handleConfirm = async () => { |
| | |
| | | ElMessage.success('领料保存成功'); |
| | | visible.value = false; |
| | | materialList.value = []; |
| | | hasDrawMaterials.value = false; |
| | | activeTab.value = 'material'; |
| | | emit('confirm'); |
| | | } catch (error) { |
| | | console.error('保存领料失败:', error); |
| | | ElMessage.error('保存领料失败'); |
| | |
| | | |
| | | <style scoped lang="scss"> |
| | | .material-requisition-form { |
| | | .operation-bar { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .el-tabs { |
| | | height: 100%; |
| | | } |
| | | |
| | | .el-table { |
| | | margin-top: 10px; |
| | | } |
| | |
| | | padding: 15px 20px; |
| | | border-top: 1px solid #e4e7ed; |
| | | } |
| | | |
| | | :deep(.el-tabs__content) { |
| | | height: calc(100% - 55px); |
| | | } |
| | | |
| | | :deep(.el-tab-pane) { |
| | | height: 100%; |
| | | } |
| | | </style> |