Commit 26e22007 by 杨子

feat: 新增出库单管理功能及相关组件

refactor: 优化RFID标签显示为字典标签组件

style: 移除冗余ID列并调整表格列宽

fix: 修复入库单物资重复添加问题

perf: 优化货架和标签选择逻辑,增加重复检查

docs: 更新注释和字段标签描述

test: 添加出库单管理相关API测试

chore: 清理无用代码和配置
parent 46e23a8d
import request from '@/utils/request'
// 查询盘点任务明细列表
export function listWmsInventoryTaskDetail(query) {
return request({
url: '/ware/wmsInventoryTaskDetail/list',
method: 'get',
params: query
})
}
// 查询盘点任务明细详细
export function getWmsInventoryTaskDetail(detailId) {
return request({
url: '/ware/wmsInventoryTaskDetail/' + detailId,
method: 'get'
})
}
// 新增盘点任务明细
export function addWmsInventoryTaskDetail(data) {
return request({
url: '/ware/wmsInventoryTaskDetail',
method: 'post',
data: data
})
}
// 修改盘点任务明细
export function updateWmsInventoryTaskDetail(data) {
return request({
url: '/ware/wmsInventoryTaskDetail',
method: 'put',
data: data
})
}
// 删除盘点任务明细
export function delWmsInventoryTaskDetail(detailId) {
return request({
url: '/ware/wmsInventoryTaskDetail/' + detailId,
method: 'delete'
})
}
import request from '@/utils/request'
// 查询出库明细库存关联列表
export function listWmsOutboundItemInventory(query) {
return request({
url: '/ware/wmsOutboundItemInventory/list',
method: 'get',
params: query
})
}
// 查询出库明细库存关联详细
export function getWmsOutboundItemInventory(relationId) {
return request({
url: '/ware/wmsOutboundItemInventory/' + relationId,
method: 'get'
})
}
// 新增出库明细库存关联
export function addWmsOutboundItemInventory(data) {
return request({
url: '/ware/wmsOutboundItemInventory',
method: 'post',
data: data
})
}
// 修改出库明细库存关联
export function updateWmsOutboundItemInventory(data) {
return request({
url: '/ware/wmsOutboundItemInventory',
method: 'put',
data: data
})
}
// 删除出库明细库存关联
export function delWmsOutboundItemInventory(relationId) {
return request({
url: '/ware/wmsOutboundItemInventory/' + relationId,
method: 'delete'
})
}
......@@ -42,3 +42,44 @@ export function delWmsOutboundOrder(orderId) {
method: 'delete'
})
}
// 确认出库
export function confirmOutbound(data) {
return request({
url: '/ware/wmsOutboundOrder/rfidPicking',
method: 'post',
data: data
})
}
/**
* 按照出库明细撤销
* @param {*} data 出库单明细
* @param {*} operatorId 操作人ID
* @returns
*/
export function cancelByDetailId(data, operatorId) {
return request({
url: '/ware/wmsOutboundOrder/cancelItem/' + data.itemId,
method: 'post',
data: {
operatorId: operatorId
}
})
}
/**
* 按照出库记录撤销
* @param {*} data 出库记录
* @param {*} operatorId 操作人ID
* @returns
*/
export function cancelByRelationId(data, operatorId) {
return request({
url: '/ware/wmsOutboundOrder/cancelOutboundRelations/' + data.relationId,
method: 'post',
data: {
operatorId: operatorId
}
})
}
\ No newline at end of file
......@@ -6,10 +6,10 @@
append-to-body
>
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="操作类型" prop="operationType">
<el-form-item label="流水类型" prop="flowType">
<el-input
v-model="queryParams.operationType"
placeholder="请输入操作类型"
v-model="queryParams.flowType"
placeholder="请输入流水类型"
clearable
@keyup.enter="handleQuery"
/>
......@@ -29,19 +29,23 @@
</el-form>
<el-table v-loading="loading" :data="wmsInventoryFlowList">
<el-table-column label="流水ID" align="center" prop="flowId" />
<el-table-column label="流水ID" align="center" prop="flowId" />
<el-table-column label="库存ID" align="center" prop="inventoryId" />
<el-table-column label="物资ID" align="center" prop="materialId" />
<el-table-column label="仓库ID" align="center" prop="warehouseId" />
<el-table-column label="货架ID" align="center" prop="locationId" />
<el-table-column label="操作类型" align="center" prop="operationType" />
<el-table-column label="变动数量" align="center" prop="changeQuantity" />
<el-table-column label="变动前数量" align="center" prop="beforeQuantity" />
<el-table-column label="变动后数量" align="center" prop="afterQuantity" />
<el-table-column label="操作人" align="center" prop="operator" />
<el-table-column label="流水类型" align="center" prop="flowType" />
<el-table-column label="流水单号" align="center" prop="flowCode" />
<el-table-column label="批次号" align="center" prop="batchNo" />
<el-table-column label="变动前数量" align="center" prop="quantityBefore" />
<el-table-column label="变动数量" align="center" prop="quantityChange" />
<el-table-column label="变动后数量" align="center" prop="quantityAfter" />
<el-table-column label="单价" align="center" prop="unitPrice" />
<el-table-column label="总价" align="center" prop="totalPrice" />
<el-table-column label="操作人" align="center" prop="operatorId" />
<el-table-column label="操作时间" align="center" prop="operationTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.operationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
<span>{{ parseTime(scope.row.operationTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
......@@ -106,7 +110,7 @@ const data = reactive({
pageNum: 1,
pageSize: 10,
inventoryId: props.inventoryId,
operationType: null,
flowType: null,
operationTime: null
}
})
......@@ -144,16 +148,12 @@ function handleQuery() {
/** 重置按钮操作 */
function resetQuery() {
queryParams.value.operationType = null
queryParams.value.flowType = null
queryParams.value.operationTime = null
queryParams.value.pageNum = 1
getList()
}
// 暴露方法供外部调用
defineExpose({
handleQuery
})
</script>
<style scoped>
......
......@@ -56,7 +56,7 @@
<el-table-column label="标签类型" align="center" prop="tagType" />
<el-table-column label="标签状态" align="center" prop="tagStatus">
<template #default="scope">
<span>{{ sys_normal_disable[scope.row.tagStatus] }}</span>
<dict-tag :options="sys_normal_disable" :value="scope.row.tagStatus" />
</template>
</el-table-column>
<el-table-column label="最后读取时间" align="center" prop="lastReadTime">
......
......@@ -91,7 +91,6 @@
<el-table v-loading="loading" :data="wmsCustomerList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="客户ID" align="center" prop="customerId" />
<el-table-column label="客户编码" align="center" prop="customerCode" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="联系人" align="center" prop="contactPerson" />
......
......@@ -62,10 +62,10 @@
</template>
</el-table-column>
<!-- PLT标签 -->
<el-table-column prop="storageLocation" label="PLT标签" width="180">
<el-table-column prop="storageLocation" label="PTL标签" width="180">
<template #default="scope">
<div v-if="scope.row.isEditing">
<el-input v-model="scope.row.storageLocation" placeholder="请选择PLT标签" size="small" readonly />
<el-input v-model="scope.row.storageLocation" placeholder="请选择PTL标签" size="small" readonly />
<el-button slot="append" type="primary" size="small" @click="openPtlTagSelect(scope.$index)">选择</el-button>
</div>
<span v-else>{{ scope.row.storageLocation || '-' }}</span>
......@@ -75,10 +75,10 @@
<el-table-column prop="rfidTagIds" label="RFID标签" width="180">
<template #default="scope">
<div v-if="scope.row.isEditing">
<el-input v-model="scope.row.rfidTagIds" placeholder="请选择RFID标签" size="small" readonly />
<el-input v-model="scope.row.rfidTagCodes" placeholder="请选择RFID标签" size="small" readonly />
<el-button slot="append" type="primary" size="small" @click="openRfidTagSelect(scope.$index)">选择</el-button>
</div>
<span v-else>{{ scope.row.rfidTagIds || '-' }}</span>
<span v-else>{{ scope.row.rfidTagCodes || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" fixed="right">
......@@ -276,7 +276,8 @@ function openRfidTagSelect(index) {
// 处理RFID标签选择确认
function handleRfidTagConfirm(selectedTags) {
if (currentEditingRowIndex.value >= 0 && adjustInventoryDetailList.value[currentEditingRowIndex.value]) {
adjustInventoryDetailList.value[currentEditingRowIndex.value].rfidTagIds = selectedTags.map(tag => tag.rfidTagId).join(',')
adjustInventoryDetailList.value[currentEditingRowIndex.value].rfidTagIds = selectedTags.map(tag => tag.tagId).join(',')
adjustInventoryDetailList.value[currentEditingRowIndex.value].rfidTagCodes = selectedTags.map(tag => tag.tagCode).join(',')
}
currentEditingRowIndex.value = -1
}
......
......@@ -27,7 +27,28 @@
<span>{{ scope.row.amount }}</span>
</template>
</el-table-column>
<el-table-column prop="wmsLocation.locationName" label="货架" width="100" />
<el-table-column prop="locationId" label="货架" width="300">
<template #default="scope">
<LocationCascaderSelect
:model-value="{ warehouseId: inboundOrder?.warehouseId, areaId: scope.row.areaId, locationId: scope.row.locationId }"
@update:model-value="(value) => {
if (value.locationId) {
// 检查是否有其他行使用了相同的locationId
const duplicateRow = inventoryDetailList.find((item, index) =>
index !== scope.$index && item.locationId === value.locationId
);
if (duplicateRow) {
proxy.$message.warning('该货架已被其他明细行使用,请选择其他货架');
return;
}
}
scope.row.warehouseId = inboundOrder?.warehouseId;
scope.row.areaId = value.areaId;
scope.row.locationId = value.locationId;
}" />
</template>
</el-table-column>
<el-table-column prop="storageLocation" label="PTL标签" width="180">
<template #default="scope">
<el-input v-model="scope.row.storageLocation" placeholder="请选择PTL标签" size="small" readonly />
......@@ -36,10 +57,15 @@
</el-table-column>
<el-table-column prop="rfidTagIds" label="RFID标签" width="180">
<template #default="scope">
<el-input v-model="scope.row.rfidTagIds" placeholder="请选择RFID标签" size="small" readonly />
<el-input v-model="scope.row.rfidTagCodes" placeholder="请选择RFID标签" size="small" readonly />
<el-button slot="append" type="primary" size="small" @click="openRfidTagSelect(scope.$index)">选择</el-button>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注">
<template #default="scope">
{{ scope.row.remark }}
</template>
</el-table-column>
<el-table-column prop="wmsInboundOrderItemId" label="操作" width="100" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="handleExportQrcode(scope.row)">打印二维码</el-button>
......@@ -75,6 +101,7 @@ import { addToInventory } from "@/api/ware/wmsInboundOrder"
import RfidTagSelectDialog from "@/components/RfidTagSelectDialog.vue"
import PtlTagSelectDialog from "@/components/PtlTagSelectDialog.vue"
import QrcodePrintDialog from "@/components/QrcodePrintDialog.vue"
import LocationCascaderSelect from "@/components/LocationCascaderSelect.vue"
const props = defineProps({
visible: {
......@@ -170,7 +197,7 @@ function confirmInventory() {
proxy.$message.warning('请先添加入库明细')
return
}
// 确认前重新计算所有行的金额,确保数据准确性
inventoryDetailList.value.forEach(item => {
calculateAmount(item);
......@@ -204,7 +231,21 @@ function openRfidTagSelect(index) {
// 处理RFID标签选择确认
function handleRfidTagConfirm(selectedTags) {
if (currentEditingRowIndex.value >= 0 && inventoryDetailList.value[currentEditingRowIndex.value]) {
inventoryDetailList.value[currentEditingRowIndex.value].rfidTagIds = selectedTags.map(tag => tag.tagId).join(',')
const selectedTagIds = selectedTags.map(tag => tag.tagId).join(',')
const selectedTagCodes = selectedTags.map(tag => tag.tagCode).join(',')
// 检查是否有其他行使用了相同的RFID标签
const duplicateRow = inventoryDetailList.value.find((item, index) =>
index !== currentEditingRowIndex.value && item.rfidTagIds === selectedTagIds
);
if (duplicateRow) {
proxy.$message.warning('该RFID标签已被其他明细行使用,请选择其他标签');
return;
}
inventoryDetailList.value[currentEditingRowIndex.value].rfidTagIds = selectedTagCodes
inventoryDetailList.value[currentEditingRowIndex.value].rfidTagCodes = selectedTagCodes
}
currentEditingRowIndex.value = -1
}
......@@ -218,7 +259,19 @@ function openPtlTagSelect(index) {
// 处理PTL标签选择确认
function handlePtlTagConfirm(selectedTags) {
if (currentEditingRowIndex.value >= 0 && inventoryDetailList.value[currentEditingRowIndex.value]) {
inventoryDetailList.value[currentEditingRowIndex.value].storageLocation = selectedTags.map(tag => tag.antennaId).join(',')
const selectedTagIds = selectedTags.map(tag => tag.antennaId).join(',')
// 检查是否有其他行使用了相同的PTL标签
const duplicateRow = inventoryDetailList.value.find((item, index) =>
index !== currentEditingRowIndex.value && item.storageLocation === selectedTagIds
);
if (duplicateRow) {
proxy.$message.warning('该PTL标签已被其他明细行使用,请选择其他标签');
return;
}
inventoryDetailList.value[currentEditingRowIndex.value].storageLocation = selectedTagIds
}
currentEditingRowIndex.value = -1
}
......
......@@ -242,6 +242,12 @@
/>
</template>
</el-table-column>
<el-table-column prop="actualQuantity" label="实际数量" width="100">
<template #default="scope">
<el-input v-model.number="scope.row.actualQuantity" placeholder="请输入实际数量" size="small"
@change="calculateAmount(scope.row)" type="number" :precision="3" :step="0.001" />
</template>
</el-table-column>
<el-table-column prop="unit" label="单位" width="80">
<template #default="scope">
<el-input v-model="scope.row.unit" placeholder="请输入单位" size="small" disabled />
......@@ -311,21 +317,8 @@
<el-input v-model="scope.row.unqualifiedReason" placeholder="请输入不合格原因" size="small" />
</template>
</el-table-column> -->
<el-table-column prop="locationId" label="货架" width="300">
<template #default="scope">
<LocationCascaderSelect
:model-value="{warehouseId: form.warehouseId, areaId: scope.row.areaId, locationId: scope.row.locationId}"
@update:model-value="(value) => {
scope.row.warehouseId = value.warehouseId;
scope.row.areaId = value.areaId;
scope.row.locationId = value.locationId;
}"
/>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" width="150">
<el-table-column prop="remark" label="备注">
<template #default="scope">
<el-input type="textarea" v-model="scope.row.remark" placeholder="请输入备注" size="small" />
</template>
......@@ -719,6 +712,13 @@ function handleMaterialConfirm(selectedMaterials) {
// 将选中的物资添加到明细列表
selectedMaterials.forEach(material => {
// 检查物资ID是否已经存在于明细列表中
const existingItem = form.value.items.find(item => item.materialId === material.materialId)
if (existingItem) {
proxy.$modal.msgWarning(`物资"${material.materialName}"已存在于明细列表中,请勿重复添加`)
return
}
const newItem = {
materialId: material.materialId,
materialName: material.materialName,
......
......@@ -33,18 +33,18 @@
</el-row>
<el-table v-loading="loading" :data="wmsInventoryList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="物资ID" align="center" prop="materialId" />
<el-table-column type="selection" width="55" align="center" fixed="left" />
<el-table-column label="物资ID" align="center" prop="materialId" fixed="left" />
<!-- <el-table-column label="仓库ID" align="center" prop="warehouseId" /> -->
<!-- <el-table-column label="库区ID" align="center" prop="areaId" /> -->
<el-table-column label="货架ID" align="center" prop="locationId" />
<el-table-column label="货架ID" align="center" prop="locationId" fixed="left" />
<el-table-column label="批次号" align="center" prop="batchNo" width="180" />
<el-table-column label="生产日期" align="center" prop="productionDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.productionDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="效日期" align="center" prop="expirationDate" width="180">
<el-table-column label="效日期" align="center" prop="expirationDate" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.expirationDate, '{y}-{m}-{d}') }}</span>
</template>
......@@ -70,19 +70,13 @@
<span>{{ parseTime(scope.row.lastOutboundTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="RFID标签" align="center" prop="rfidTag" />
<el-table-column label="是否FIFO管理" align="center" prop="isFifo">
<el-table-column label="RFID标签" align="center" prop="rfidTagCodes">
<template #default="scope">
<dict-tag :options="sys_yes_no" :value="scope.row.isFifo" />
<span>{{ scope.row.rfidTag || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="RFID绑定数量" align="center" prop="rfidBindingCount" />
<!-- <el-table-column label="最后RFID检测时间" align="center" prop="lastRfidDetectTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.lastRfidDetectTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column> -->
<el-table-column label="RFID覆盖率" align="center" prop="rfidCoverageRate" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220" fixed="right">
<template #default="scope">
......
......@@ -28,12 +28,6 @@
<el-button slot="append" icon="Search" @click="handleSupplierSelect('query')">选择</el-button>
</div>
</el-form-item>
<el-form-item label="是否RFID" prop="isRfidManaged">
<el-select v-model="queryParams.isRfidManaged" placeholder="请选择是否RFID" clearable @keyup.enter="handleQuery">
<el-option v-for="item in custom_yes_no" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
......
<template>
<div class="outbound-manager-container">
<!-- 顶部导航和操作栏 -->
<div class="header-section">
<el-breadcrumb separator="/">
<el-breadcrumb-item>仓库管理</el-breadcrumb-item>
<el-breadcrumb-item>出库作业</el-breadcrumb-item>
<el-breadcrumb-item>批量出库</el-breadcrumb-item>
</el-breadcrumb>
<div class="header-actions">
<el-button type="primary" @click="createNewOrder">
<el-icon><Plus /></el-icon>新建出库单
</el-button>
<el-button @click="refreshData">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</div>
<!-- 标签页:出库单列表 和 出库执行 -->
<el-tabs v-model="activeTab" type="border-card" class="main-tabs">
<!-- 标签页1: 出库单列表 -->
<el-tab-pane label="出库单管理" name="orderList">
<OrderList
:orders="orders"
@select-order="handleSelectOrder"
@edit-order="handleEditOrder"
@delete-order="handleDeleteOrder"
@start-outbound="handleStartOutbound"
/>
</el-tab-pane>
<!-- 标签页2: 出库执行 (仅在选中订单后可用) -->
<el-tab-pane
label="出库执行"
name="outboundExecution"
:disabled="!selectedOrder"
>
<OutboundExecution
v-if="selectedOrder"
:order="selectedOrder"
:key="selectedOrder.id"
@order-updated="handleOrderUpdated"
@operation-log="handleOperationLog"
/>
</el-tab-pane>
<!-- 标签页3: 操作日志 -->
<el-tab-pane label="操作日志" name="operationLogs">
<OperationLogs :logs="operationLogs" />
</el-tab-pane>
</el-tabs>
<!-- 新建/编辑出库单对话框 -->
<OrderDialog
v-model="showOrderDialog"
:order="editingOrder"
mode="create"
@save="handleSaveOrder"
/>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh } from '@element-plus/icons-vue'
import OrderList from './components/OrderList.vue'
import OutboundExecution from './components/OutboundExecution.vue'
import OperationLogs from './components/OperationLogs.vue'
import OrderDialog from './components/OrderDialog.vue'
import {
fetchOrders,
deleteOrder,
createOrder,
fetchOperationLogs
} from './components/mockApi'
// 响应式数据
const activeTab = ref('orderList')
const orders = ref([])
const selectedOrder = ref(null)
const showOrderDialog = ref(false)
const editingOrder = ref(null)
const operationLogs = ref([])
// 生命周期
onMounted(() => {
loadOrders()
loadOperationLogs()
})
// 方法
const loadOrders = async () => {
try {
orders.value = await fetchOrders()
} catch (error) {
ElMessage.error('加载出库单失败')
}
}
const loadOperationLogs = async () => {
try {
operationLogs.value = await fetchOperationLogs()
} catch (error) {
console.error('加载操作日志失败:', error)
}
}
const refreshData = () => {
loadOrders()
loadOperationLogs()
ElMessage.success('数据已刷新')
}
const createNewOrder = () => {
editingOrder.value = null
showOrderDialog.value = true
}
const handleSelectOrder = (order) => {
selectedOrder.value = order
activeTab.value = 'outboundExecution'
}
const handleEditOrder = (order) => {
editingOrder.value = { ...order }
showOrderDialog.value = true
}
const handleDeleteOrder = async (order) => {
try {
await ElMessageBox.confirm(
`确定要删除出库单 ${order.orderNo} 吗?`,
'确认删除',
{ type: 'warning' }
)
await deleteOrder(order.id)
ElMessage.success('删除成功')
loadOrders()
} catch (error) {
// 用户取消
}
}
const handleStartOutbound = (order) => {
selectedOrder.value = order
activeTab.value = 'outboundExecution'
}
const handleSaveOrder = async (orderData) => {
try {
await createOrder(orderData)
ElMessage.success('出库单创建成功')
showOrderDialog.value = false
loadOrders()
} catch (error) {
ElMessage.error('保存失败')
}
}
const handleOrderUpdated = () => {
loadOrders()
}
const handleOperationLog = (log) => {
operationLogs.value.unshift(log)
}
</script>
<style scoped>
.outbound-manager-container {
height: 100vh;
display: flex;
flex-direction: column;
background: #f5f7fa;
padding: 20px;
}
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 10px;
}
.header-actions {
display: flex;
gap: 10px;
}
.main-tabs {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
:deep(.el-tabs__content) {
flex: 1;
overflow: auto;
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="1200px"
:close-on-click-modal="false"
>
<!-- 物资信息摘要 -->
<div v-if="detail" class="material-summary">
<el-descriptions :column="3" border size="small">
<el-descriptions-item label="物资编码">
{{ detail.materialCode }}
</el-descriptions-item>
<el-descriptions-item label="物资名称">
{{ detail.materialName }}
</el-descriptions-item>
<el-descriptions-item label="规格型号">
{{ detail.specification }}
</el-descriptions-item>
<el-descriptions-item label="计划数量">
<span class="planned-quantity">{{ detail.plannedQuantity }}</span>
</el-descriptions-item>
<el-descriptions-item label="实际出库">
<span :class="{
'completed': detail.actualQuantity >= detail.plannedQuantity,
'in-progress': detail.actualQuantity > 0 && detail.actualQuantity < detail.plannedQuantity
}">
{{ detail.actualQuantity }}
</span>
</el-descriptions-item>
<el-descriptions-item label="剩余需求">
<span class="remaining-need">
{{ Math.max(0, detail.plannedQuantity - detail.actualQuantity) }}
</span>
</el-descriptions-item>
</el-descriptions>
<el-progress
:percentage="completionPercentage"
:status="completionPercentage >= 100 ? 'success' : 'primary'"
:stroke-width="12"
style="margin-top: 15px;"
/>
</div>
<!-- 批次出库记录 -->
<div class="batch-transactions">
<div class="section-header">
<h4>批次出库记录</h4>
<span class="total-info">
{{ transactions.length }} 个批次
</span>
</div>
<el-table
:data="transactions"
stripe
border
size="small"
height="300px"
>
<el-table-column type="selection" width="55" align="center" fixed="left" />
<el-table-column type="index" label="序号" width="60" align="center" fixed="left" />
<el-table-column label="关联ID" align="center" prop="relationId" />
<el-table-column label="出库明细ID" align="center" prop="itemId" />
<el-table-column label="出库单ID" align="center" prop="orderId" />
<el-table-column label="库存ID" align="center" prop="inventoryId" />
<el-table-column label="RFID标签ID" align="center" prop="rfidTagId" />
<el-table-column label="RFID编码" align="center" prop="rfidCode" />
<el-table-column label="EPC编码" align="center" prop="epcCode" />
<el-table-column label="TID编码" align="center" prop="tidCode" />
<el-table-column label="物资ID" align="center" prop="materialId" />
<el-table-column label="批次号" align="center" prop="batchNo" width="120" />
<el-table-column label="仓库id" align="center" prop="warehouseId" />
<el-table-column label="库区id" align="center" prop="areaId" />
<el-table-column label="库位ID" align="center" prop="locationId" />
<el-table-column label="数量" align="center" prop="quantity" />
<el-table-column label="单位成本" align="center" prop="unitCost" />
<el-table-column label="出库时间" align="center" prop="outboundTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.outboundTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="是否已撤销" align="center" prop="isCanceled" />
<el-table-column label="撤销时间" align="center" prop="cancelTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.cancelTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="撤销人" align="center" prop="cancelBy" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
<template #default="scope">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ware:wmsOutboundItemInventory:remove']">撤销</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 汇总信息 -->
<div class="summary-section">
<el-descriptions :column="4" border size="small">
<el-descriptions-item label="批次总数">
<span class="summary-value">{{ batchCount }}</span>
</el-descriptions-item>
<el-descriptions-item label="正常出库">
<span class="summary-value success">
{{ normalTransactionCount }}
</span>
</el-descriptions-item>
<el-descriptions-item label="已撤销">
<span class="summary-value danger">
{{ reversedTransactionCount }}
</span>
</el-descriptions-item>
<el-descriptions-item label="合计数量">
<span class="summary-value highlight">
{{ totalAmount }}
</span>
</el-descriptions-item>
</el-descriptions>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">关闭</el-button>
<el-button
v-if="detail?.actualQuantity < detail?.plannedQuantity"
type="primary"
@click="handleContinueOutbound"
>
继续出库
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { Close } from '@element-plus/icons-vue'
import { listWmsOutboundItemInventory } from "@/api/ware/wmsOutboundItemInventory"
const props = defineProps({
modelValue: {
type: Boolean,
default: false
}
})
const detail = ref({})
const transactions = ref([])
const emit = defineEmits()
// 响应式数据
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 计算属性
const dialogTitle = computed(() => {
if (!detail.value) return '批次详情'
return `${detail.value.materialName} - 批次出库详情`
})
const completionPercentage = computed(() => {
if (!detail.value || detail.value.plannedQuantity === 0) return 0
return Math.min((detail.value.actualQuantity / detail.value.plannedQuantity) * 100, 100)
})
const batchCount = computed(() => {
const batchNos = new Set(transactions.value.map(t => t.batchNo))
return batchNos.size
})
const normalTransactionCount = computed(() => {
return transactions.value.filter(t => t.isCanceled === '0').length
})
const reversedTransactionCount = computed(() => {
return transactions.value.filter(t => t.status === 'REVERSED').length
})
const totalAmount = computed(() => {
return transactions.value
.filter(t => t.isCanceled === '0')
.reduce((sum, t) => sum + t.quantity, 0)
})
// 方法
const formatTime = (dateString) => {
const date = new Date(dateString)
return date.toLocaleString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
const formatAmount = (amount) => {
return amount.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
}
const handleContinueOutbound = () => {
emit('continue-outbound')
visible.value = false
}
const openDialog = (row) => {
detail.value = row
listWmsOutboundItemInventory({
orderId: row.orderId,
pageNum: 1,
pageSize: 1000
}).then(res => {
transactions.value = res.rows || []
})
}
defineExpose({
openDialog
})
</script>
<style scoped>
.material-summary {
margin-bottom: 20px;
padding: 15px;
background: #f8fafc;
border-radius: 6px;
}
.planned-quantity {
font-weight: 600;
color: #303133;
}
.completed {
color: #67c23a;
font-weight: 600;
}
.in-progress {
color: #e6a23c;
font-weight: 600;
}
.remaining-need {
color: #f56c6c;
font-weight: 600;
}
.batch-transactions {
margin-bottom: 20px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.section-header h4 {
margin: 0;
color: #303133;
font-weight: 600;
}
.total-info {
color: #606266;
font-size: 14px;
}
.batch-no-cell {
display: flex;
align-items: center;
gap: 8px;
}
.batch-no {
font-family: 'Monaco', 'Consolas', monospace;
font-size: 13px;
}
.quantity-cell {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 5px;
}
.quantity-cell.reversed {
text-decoration: line-through;
color: #f56c6c;
}
.amount-cell.reversed {
text-decoration: line-through;
color: #f56c6c;
}
.summary-section {
margin-top: 20px;
}
.summary-value {
font-weight: 500;
color: #303133;
}
.summary-value.success {
color: #67c23a;
}
.summary-value.danger {
color: #f56c6c;
}
.summary-value.highlight {
color: #f56c6c;
font-size: 16px;
font-weight: 600;
}
:deep(.el-descriptions__label) {
width: 80px;
font-weight: 500;
}
:deep(.el-descriptions__content) {
font-weight: 400;
}
</style>
\ No newline at end of file
......@@ -91,7 +91,6 @@
<el-table v-loading="loading" :data="wmsSupplierList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="供应商ID" align="center" prop="supplierId" />
<el-table-column label="供应商编码" align="center" prop="supplierCode" />
<el-table-column label="供应商名称" align="center" prop="supplierName" />
<el-table-column label="联系人" align="center" prop="contactPerson" />
......
......@@ -67,7 +67,6 @@
<el-table v-loading="loading" :data="wmsWarehouseList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="仓库ID" align="center" prop="warehouseId" />
<el-table-column label="仓库编码" align="center" prop="warehouseCode" />
<el-table-column label="仓库名称" align="center" prop="warehouseName" />
<el-table-column label="仓库类型" align="center" prop="warehouseType" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment