254 lines
8.5 KiB
Vue
254 lines
8.5 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<el-card shadow="always" class="no-print-content">
|
||
<template #header>
|
||
<div class="card-header">
|
||
<div class="header-left">
|
||
<span class="title">出库拣货车</span>
|
||
<span class="subtitle">(请添加需要出库的物品)</span>
|
||
</div>
|
||
<div>
|
||
<el-button type="primary" :icon="Plus" @click="openManualSelect">
|
||
手动添加库存
|
||
</el-button>
|
||
<el-button type="warning" :icon="List" @click="openBomSelect">
|
||
按 BOM 套餐添加
|
||
</el-button>
|
||
<el-divider direction="vertical" />
|
||
<el-button type="success" :icon="Printer" :disabled="selectedItems.length === 0" @click="handlePreview">
|
||
生成预览 & 打印
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<el-alert
|
||
v-if="selectedItems.length === 0"
|
||
title="清单为空,请点击右上角按钮添加物品"
|
||
type="info"
|
||
center
|
||
show-icon
|
||
style="margin-bottom: 20px"
|
||
/>
|
||
|
||
<el-table
|
||
v-else
|
||
:data="selectedItems"
|
||
border
|
||
style="width: 100%"
|
||
row-key="uniqueKey"
|
||
>
|
||
<el-table-column type="index" label="序号" width="50" align="center" />
|
||
<el-table-column prop="typeLabel" label="类型" width="80" />
|
||
<el-table-column prop="name" label="名称" />
|
||
<el-table-column prop="standard" label="规格" />
|
||
<el-table-column prop="available_quantity" label="当前库存" width="100" />
|
||
|
||
<el-table-column label="本次出库" width="180" align="center">
|
||
<template #default="{ row }">
|
||
<el-input-number
|
||
v-model="row.export_quantity"
|
||
:min="1"
|
||
:max="row.available_quantity"
|
||
size="small"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column label="操作" width="80" align="center">
|
||
<template #default="{ $index }">
|
||
<el-button type="danger" link @click="removeRow($index)">移除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
|
||
<el-dialog v-model="manualDialogVisible" title="选择库存物品" width="80%" top="5vh">
|
||
<div class="filter-container">
|
||
<el-input v-model="searchKeyword" placeholder="搜索名称/规格" style="width: 300px" @input="filterStock" />
|
||
</div>
|
||
<el-table
|
||
:data="filteredStockData"
|
||
height="500"
|
||
border
|
||
@selection-change="handleStockSelection"
|
||
>
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column prop="name" label="名称" />
|
||
<el-table-column prop="standard" label="规格" />
|
||
<el-table-column prop="available_quantity" label="可用库存" />
|
||
<el-table-column label="本次出库" width="150">
|
||
<template #default="{ row }">
|
||
<el-input-number
|
||
v-model="row.export_quantity"
|
||
:min="1"
|
||
:max="row.available_quantity"
|
||
size="small"
|
||
style="width: 100%"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<template #footer>
|
||
<el-button @click="manualDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="confirmManualAdd">确认添加</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog v-model="bomSelectVisible" title="选择 BOM 套餐" width="600px">
|
||
<el-form label-width="80px">
|
||
<el-form-item label="选择产品">
|
||
<el-select v-model="selectedBomNo" filterable placeholder="选择要出库的产品BOM" style="width: 100%">
|
||
<el-option
|
||
v-for="b in bomOptions"
|
||
:key="b.bom_no"
|
||
:label="`${b.parent_name} (${b.version})`"
|
||
:value="b.bom_no"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="生产套数">
|
||
<el-input-number v-model="bomSets" :min="1" label="套" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="bomSelectVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="confirmBomAdd">一键添加</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog v-model="previewVisible" title="打印预览" width="800px">
|
||
<el-button @click="window.print()">确认打印</el-button>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { Plus, List, Printer } from '@element-plus/icons-vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { getAllStock } from '@/api/inbound/stock'
|
||
import { getBomList, getBomDetail } from '@/api/bom'
|
||
|
||
// 购物车数据
|
||
const selectedItems = ref<any[]>([])
|
||
|
||
// 手动选择相关
|
||
const manualDialogVisible = ref(false)
|
||
const allStockData = ref<any[]>([])
|
||
const filteredStockData = ref<any[]>([])
|
||
const searchKeyword = ref('')
|
||
const tempSelection = ref<any[]>([])
|
||
|
||
// BOM 相关
|
||
const bomSelectVisible = ref(false)
|
||
const bomOptions = ref<any[]>([])
|
||
const selectedBomNo = ref('')
|
||
const bomSets = ref(1)
|
||
|
||
const previewVisible = ref(false)
|
||
|
||
// --- 方法 ---
|
||
|
||
// 1. 打开手动选择
|
||
const openManualSelect = async () => {
|
||
manualDialogVisible.value = true
|
||
if (allStockData.value.length === 0) {
|
||
const res:any = await getAllStock()
|
||
// 简单处理数据,实际应复用之前的 normalizeItem 逻辑
|
||
const list = res.materials ? [...res.materials, ...res.products, ...res.semis] : []
|
||
allStockData.value = list.map((i:any) => ({
|
||
...i,
|
||
name: i.name || i.material_name || i.product_name,
|
||
standard: i.standard || i.spec_model,
|
||
uniqueKey: i.id + '_' + i.type,
|
||
export_quantity: 1
|
||
}))
|
||
filteredStockData.value = allStockData.value
|
||
}
|
||
}
|
||
|
||
const filterStock = () => {
|
||
const kw = searchKeyword.value.toLowerCase()
|
||
filteredStockData.value = allStockData.value.filter(i =>
|
||
(i.name && i.name.toLowerCase().includes(kw)) || (i.standard && i.standard.toLowerCase().includes(kw))
|
||
)
|
||
}
|
||
|
||
const handleStockSelection = (val: any[]) => { tempSelection.value = val }
|
||
|
||
const confirmManualAdd = () => {
|
||
const newItems = tempSelection.value.filter(item =>
|
||
!selectedItems.value.find(existing => existing.uniqueKey === item.uniqueKey)
|
||
)
|
||
selectedItems.value.push(...newItems)
|
||
manualDialogVisible.value = false
|
||
tempSelection.value = []
|
||
ElMessage.success(`添加了 ${newItems.length} 项`)
|
||
}
|
||
|
||
// 2. 打开 BOM 选择
|
||
const openBomSelect = async () => {
|
||
bomSelectVisible.value = true
|
||
const res = await getBomList()
|
||
bomOptions.value = res.data || []
|
||
}
|
||
|
||
const confirmBomAdd = async () => {
|
||
if(!selectedBomNo.value) return;
|
||
|
||
const detailRes = await getBomDetail(selectedBomNo.value)
|
||
const bomRows = detailRes.data || []
|
||
|
||
if (allStockData.value.length === 0) {
|
||
// 确保有库存数据用于匹配
|
||
await openManualSelect()
|
||
manualDialogVisible.value = false // 仅加载数据,不显示弹窗
|
||
}
|
||
|
||
let addedCount = 0;
|
||
bomRows.forEach((bomItem:any) => {
|
||
const needQty = bomItem.dosage * bomSets.value
|
||
// 简单匹配逻辑:通过名称或ID匹配
|
||
const stockCandidate = allStockData.value.find(s =>
|
||
(s.base_id == bomItem.child_id)
|
||
)
|
||
|
||
if (stockCandidate) {
|
||
const existing = selectedItems.value.find(e => e.uniqueKey === stockCandidate.uniqueKey)
|
||
if (existing) {
|
||
existing.export_quantity += needQty
|
||
} else {
|
||
const newItem = JSON.parse(JSON.stringify(stockCandidate))
|
||
newItem.export_quantity = needQty
|
||
selectedItems.value.push(newItem)
|
||
}
|
||
addedCount++
|
||
}
|
||
})
|
||
|
||
if(addedCount > 0) {
|
||
ElMessage.success(`已添加 BOM 相关物料`)
|
||
bomSelectVisible.value = false
|
||
} else {
|
||
ElMessage.warning('未在库存中找到匹配的物料')
|
||
}
|
||
}
|
||
|
||
const removeRow = (index: number) => {
|
||
selectedItems.value.splice(index, 1)
|
||
}
|
||
|
||
const handlePreview = () => {
|
||
previewVisible.value = true
|
||
setTimeout(() => window.print(), 500)
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.card-header { display: flex; justify-content: space-between; align-items: center; }
|
||
.header-left .title { font-size: 18px; font-weight: bold; margin-right: 10px; }
|
||
.header-left .subtitle { font-size: 12px; color: #909399; }
|
||
.filter-container { margin-bottom: 10px; }
|
||
</style>
|