Compare commits
5 Commits
a5a35777b5
...
01ce9c1432
| Author | SHA1 | Date | |
|---|---|---|---|
| 01ce9c1432 | |||
| 0ab7050e03 | |||
| cd714d0c16 | |||
| a6409ac091 | |||
| eba558c9d9 |
@ -8,12 +8,31 @@
|
|||||||
<span class="subtitle">(请添加需要出库的物品)</span>
|
<span class="subtitle">(请添加需要出库的物品)</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- 批量模式 -->
|
||||||
|
<template v-if="isBulkMode">
|
||||||
|
<el-button @click="cancelBulkMode">
|
||||||
|
取消
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" :disabled="selectedRows.length === 0" @click="batchRemove">
|
||||||
|
移除选中 ({{ selectedRows.length }})
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<!-- 普通模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="warning" plain :disabled="selectedItems.length === 0" @click="isBulkMode = true">
|
||||||
|
批量操作
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="danger" :disabled="selectedItems.length === 0" @click="clearAll">
|
||||||
|
清空货车
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="primary" :icon="Plus" @click="openManualSelect">
|
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="primary" :icon="Plus" @click="openManualSelect">
|
||||||
手动添加库存
|
手动添加库存
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="warning" :icon="List" @click="openBomSelect">
|
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="warning" :icon="List" @click="openBomSelect">
|
||||||
按 BOM 套餐添加
|
按 BOM 套餐添加
|
||||||
</el-button>
|
</el-button>
|
||||||
|
</template>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="success" :icon="Printer" :disabled="selectedItems.length === 0" @click="handlePreview">
|
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="success" :icon="Printer" :disabled="selectedItems.length === 0" @click="handlePreview">
|
||||||
生成预览 & 打印
|
生成预览 & 打印
|
||||||
@ -33,12 +52,17 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
|
ref="tableRef"
|
||||||
v-else
|
v-else
|
||||||
:data="sortedSelectedItems"
|
:data="sortedSelectedItems"
|
||||||
border
|
border
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
row-key="uniqueKey"
|
row-key="uniqueKey"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@row-click="handleBulkRowClick"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
>
|
>
|
||||||
|
<el-table-column v-if="isBulkMode" type="selection" width="55" align="center" />
|
||||||
<el-table-column type="index" label="序号" width="50" align="center" />
|
<el-table-column type="index" label="序号" width="50" align="center" />
|
||||||
|
|
||||||
<el-table-column label="类型" width="100" align="center">
|
<el-table-column label="类型" width="100" align="center">
|
||||||
@ -79,7 +103,7 @@
|
|||||||
|
|
||||||
<el-table-column label="操作" width="80" align="center" fixed="right">
|
<el-table-column label="操作" width="80" align="center" fixed="right">
|
||||||
<template #default="{ $index }">
|
<template #default="{ $index }">
|
||||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="danger" link @click="removeRow($index)">移除</el-button>
|
<el-button v-if="!isBulkMode && userStore.hasPermission('outbound_selection:operation')" type="danger" link @click="removeRow($index)">移除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -217,9 +241,8 @@
|
|||||||
v-if="userStore.hasPermission('outbound_selection:operation')"
|
v-if="userStore.hasPermission('outbound_selection:operation')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="confirmBomAdd"
|
@click="confirmBomAdd"
|
||||||
:disabled="hasShortage"
|
|
||||||
>
|
>
|
||||||
{{ hasShortage ? '库存不足,无法添加' : '一键计算并添加' }}
|
{{ hasShortage ? '仅添加现有库存' : '确认添加' }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@ -340,6 +363,8 @@ const userStore = useUserStore()
|
|||||||
|
|
||||||
// --- 状态变量 ---
|
// --- 状态变量 ---
|
||||||
const selectedItems = ref<any[]>([])
|
const selectedItems = ref<any[]>([])
|
||||||
|
const selectedRows = ref<any[]>([])
|
||||||
|
const isBulkMode = ref(false)
|
||||||
|
|
||||||
// 按库位路径自然升序排序(优化拣货路径)
|
// 按库位路径自然升序排序(优化拣货路径)
|
||||||
const sortedSelectedItems = computed(() => {
|
const sortedSelectedItems = computed(() => {
|
||||||
@ -368,6 +393,7 @@ let stockSearchTimer: ReturnType<typeof setTimeout> | null = null
|
|||||||
|
|
||||||
// 表格引用
|
// 表格引用
|
||||||
const manualTableRef = ref<InstanceType<typeof ElTable>>()
|
const manualTableRef = ref<InstanceType<typeof ElTable>>()
|
||||||
|
const tableRef = ref<InstanceType<typeof ElTable>>()
|
||||||
|
|
||||||
// BOM 相关
|
// BOM 相关
|
||||||
const bomOptions = ref<any[]>([])
|
const bomOptions = ref<any[]>([])
|
||||||
@ -632,55 +658,128 @@ watch(selectedBomNo, async (newBomNo) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const confirmBomAdd = async () => {
|
const confirmBomAdd = async () => {
|
||||||
if(!selectedBomNo.value) return ElMessage.warning('请选择 BOM');
|
if (!selectedBomNo.value) return ElMessage.warning('请选择 BOM')
|
||||||
|
|
||||||
if (allStockData.value.length === 0) {
|
if (allStockData.value.length === 0) {
|
||||||
await openManualSelect()
|
await ensureAllStockLoaded()
|
||||||
manualDialogVisible.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentBomDetail.value.length === 0) {
|
||||||
try {
|
try {
|
||||||
const detailRes = await getBomDetail(selectedBomNo.value)
|
const detailRes = await getBomDetail(selectedBomNo.value)
|
||||||
const bomRows = detailRes.data?.children || []
|
currentBomDetail.value = detailRes.data?.children || []
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('获取 BOM 详情失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bomRows = currentBomDetail.value
|
||||||
|
let addedCount = 0
|
||||||
|
let skippedCount = 0
|
||||||
|
|
||||||
let addedCount = 0;
|
|
||||||
bomRows.forEach((bomItem: any) => {
|
bomRows.forEach((bomItem: any) => {
|
||||||
const needQty = (parseFloat(bomItem.dosage) || 0) * bomSets.value
|
const dosage = parseFloat(bomItem.dosage) || 0
|
||||||
// ★ BOM 添加时,匹配本地库存数据,带入库位信息
|
const needQty = dosage * bomSets.value
|
||||||
|
|
||||||
const stockCandidate = allStockData.value.find(s =>
|
const stockCandidate = allStockData.value.find(s =>
|
||||||
(s.base_id && s.base_id == bomItem.child_id)
|
(s.base_id && s.base_id == bomItem.child_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (stockCandidate) {
|
if (stockCandidate) {
|
||||||
|
const availableQty = stockCandidate.availableCount || 0
|
||||||
|
const actualAddQty = Math.min(needQty, availableQty)
|
||||||
|
|
||||||
|
if (actualAddQty > 0) {
|
||||||
const existing = selectedItems.value.find(e => e.uniqueKey === stockCandidate.uniqueKey)
|
const existing = selectedItems.value.find(e => e.uniqueKey === stockCandidate.uniqueKey)
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.export_quantity += needQty
|
existing.export_quantity += actualAddQty
|
||||||
} else {
|
} else {
|
||||||
const newItem = JSON.parse(JSON.stringify(stockCandidate))
|
const newItem = JSON.parse(JSON.stringify(stockCandidate))
|
||||||
// 如果后端 BomService 也返回了 warehouse_location (聚合的),这里优先使用
|
|
||||||
if (bomItem.warehouse_location) {
|
if (bomItem.warehouse_location) {
|
||||||
newItem.warehouse_location = bomItem.warehouse_location
|
newItem.warehouse_location = bomItem.warehouse_location
|
||||||
}
|
}
|
||||||
newItem.export_quantity = needQty
|
newItem.export_quantity = actualAddQty
|
||||||
selectedItems.value.push(newItem)
|
selectedItems.value.push(newItem)
|
||||||
}
|
}
|
||||||
addedCount++
|
addedCount++
|
||||||
|
} else {
|
||||||
|
skippedCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
skippedCount++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if(addedCount > 0) {
|
if (addedCount > 0) {
|
||||||
ElMessage.success(`成功添加 BOM 相关物料,共 ${addedCount} 类`)
|
const tip = skippedCount > 0 ? `(跳过 ${skippedCount} 种缺货物料)` : ''
|
||||||
|
ElMessage.success(`成功添加 ${addedCount} 类物料${tip}`)
|
||||||
bomSelectVisible.value = false
|
bomSelectVisible.value = false
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning('库存中未找到该 BOM 所需的任何原料')
|
ElMessage.warning('该 BOM 所有物料库存均为 0')
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
ElMessage.error('获取 BOM 详情失败')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 通用逻辑 ---
|
// --- 通用逻辑 ---
|
||||||
|
|
||||||
|
const handleSelectionChange = (val: any[]) => {
|
||||||
|
selectedRows.value = val
|
||||||
|
if (val.length === 0 && isBulkMode.value) {
|
||||||
|
isBulkMode.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBulkRowClick = (row: any) => {
|
||||||
|
if (isBulkMode.value && tableRef.value) {
|
||||||
|
tableRef.value.toggleRowSelection(row, undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRowClassName = () => {
|
||||||
|
return isBulkMode.value ? 'bulk-clickable-row' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelBulkMode = () => {
|
||||||
|
isBulkMode.value = false
|
||||||
|
selectedRows.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAll = () => {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'确定要清空当前拣货车中的所有物品吗?',
|
||||||
|
'清空确认',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定清空',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
|
selectedItems.value = []
|
||||||
|
selectedRows.value = []
|
||||||
|
isBulkMode.value = false
|
||||||
|
ElMessage.success('已清空拣货车')
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchRemove = () => {
|
||||||
|
if (selectedRows.value.length === 0) return
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确定要移除选中的 ${selectedRows.value.length} 项物品吗?`,
|
||||||
|
'批量移除确认',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定移除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
|
const keysToRemove = new Set(selectedRows.value.map(row => row.uniqueKey))
|
||||||
|
selectedItems.value = selectedItems.value.filter(item => !keysToRemove.has(item.uniqueKey))
|
||||||
|
selectedRows.value = []
|
||||||
|
isBulkMode.value = false
|
||||||
|
ElMessage.success(`已移除 ${keysToRemove.size} 项物品`)
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
const removeRow = (index: number) => {
|
const removeRow = (index: number) => {
|
||||||
selectedItems.value.splice(index, 1)
|
selectedItems.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
@ -772,5 +871,34 @@ const confirmExport = () => {
|
|||||||
.signature-item { display: flex; flex-direction: column; align-items: center; width: 30%; }
|
.signature-item { display: flex; flex-direction: column; align-items: center; width: 30%; }
|
||||||
.sig-label { font-size: 14px; margin-bottom: 40px; text-align: left; width: 100%; }
|
.sig-label { font-size: 14px; margin-bottom: 40px; text-align: left; width: 100%; }
|
||||||
.sig-line { border-bottom: 1px solid #000; width: 100%; height: 1px; display: block; }
|
.sig-line { border-bottom: 1px solid #000; width: 100%; height: 1px; display: block; }
|
||||||
|
|
||||||
|
/* ★★★ 修复预览弹窗中 el-table 打印分页截断问题 ★★★ */
|
||||||
|
.print-preview-content {
|
||||||
|
height: auto !important;
|
||||||
|
max-height: none !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
.print-preview-content .el-table,
|
||||||
|
.print-preview-content .el-table__inner-wrapper,
|
||||||
|
.print-preview-content .el-table__body-wrapper,
|
||||||
|
.print-preview-content .el-table__body {
|
||||||
|
height: auto !important;
|
||||||
|
max-height: none !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
.print-preview-content .el-scrollbar__wrap {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
.print-preview-content .el-table tr {
|
||||||
|
page-break-inside: avoid !important;
|
||||||
|
break-inside: avoid !important;
|
||||||
|
}
|
||||||
|
.print-preview-content .el-table__body-wrapper is-scrollable-none {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.bulk-clickable-row) {
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user