Files
KCGL/inventory-web/src/views/outbound/Selection.vue
dxc 6e5df70ee6 fix: add typeLabel for display in outbound manual selection dialog
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
2026-02-12 10:05:16 +08:00

255 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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,
typeLabel: i.type === 'material' ? '采购件' : i.type === 'semi' ? '半成品' : i.type === 'product' ? '成品' : '未知'
}))
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>