feat(仓库管理): 新增钢卷库区操作日志功能

添加钢卷库区操作日志记录功能,包括:
1. 在入库操作时自动记录日志
2. 新增日志查询API接口
3. 实现日志查看页面和表格组件
4. 处理拒签和删除操作时的日志清理
This commit is contained in:
砂糖
2026-03-05 17:19:48 +08:00
parent 428e94d4f9
commit 9e24368d4c
5 changed files with 418 additions and 2 deletions

View File

@@ -0,0 +1,63 @@
import request from '@/utils/request'
// 查询钢卷库区操作记录列表
export function listCoilWarehouseOperationLog(query) {
return request({
url: '/wms/coilWarehouseOperationLog/list',
method: 'get',
params: query
})
}
// 查询钢卷库区操作记录详细
export function getCoilWarehouseOperationLog(logId) {
return request({
url: '/wms/coilWarehouseOperationLog/' + logId,
method: 'get'
})
}
// 新增钢卷库区操作记录
export function addCoilWarehouseOperationLog(data) {
return request({
url: '/wms/coilWarehouseOperationLog',
method: 'post',
data: data
})
}
// 修改钢卷库区操作记录
export function updateCoilWarehouseOperationLog(data) {
return request({
url: '/wms/coilWarehouseOperationLog',
method: 'put',
data: data
})
}
// 删除钢卷库区操作记录
export function delCoilWarehouseOperationLog(logId) {
return request({
url: '/wms/coilWarehouseOperationLog/' + logId,
method: 'delete'
})
}
// 根据钢卷 ID、操作类型及出入库类型删除钢卷库区操作记录
export function delCoilWarehouseOperationLogByCoilId(params) {
return request({
url: '/wms/coilWarehouseOperationLog/byCoilId',
method: 'delete',
params
})
}
// 根据二级库区ID和时间范围查询操作记录及钢卷信息
export function getCoilWarehouseOperationLogByWarehouseId(params) {
return request({
timeout: 100000,
url: '/wms/coilWarehouseOperationLog/byWarehouseAndTime',
method: 'get',
params
})
}

View File

@@ -353,6 +353,7 @@ import { listDeliveryPlan } from '@/api/wms/deliveryPlan'
import handleCoil, { COIL_ACTIONS } from '../js/coilActions'
import LabelRender from '../panels/LabelRender/index.vue'
import CoilNo from "@/components/KLPService/Renderer/CoilNo.vue";
import { delCoilWarehouseOperationLogByCoilId } from '@/api/wms/coilWarehouseOperationLog'
// 键值为[400, 500), 不包含在字典中但后续可以加入的特殊操作这类操作录入后会立刻完成例如入库401和发货402
@@ -676,7 +677,7 @@ export default {
this.buttonLoading = true;
updatePendingAction({
...row,
actionStatus: 3, // 3表示拒签
actionStatus: 3, // 3表示取消操作,也就是拒签
}).then(response => {
this.$modal.msgSuccess("拒签成功");
this.getList();
@@ -723,6 +724,11 @@ export default {
}
delPendingAction(row.actionId).then(response => {
this.$modal.msgSuccess("删除成功");
delCoilWarehouseOperationLogByCoilId({
coilId: row.coilId,
operationType: 1,
inOutType: 1
})
this.getList();
}).finally(() => {
this.buttonLoading = false;

View File

@@ -1,5 +1,6 @@
import { addMaterialCoil, updateMaterialCoil, updateMaterialCoilSimple } from "@/api/wms/coil"
import { addPendingAction, updatePendingAction } from '@/api/wms/pendingAction';
import { addCoilWarehouseOperationLog } from '@/api/wms/coilWarehouseOperationLog';
export const COIL_ACTIONS = {
RECEIVE: 'RECEIVE', // 收货与入库的区别在于,收货相当于计划入库,不管是否已到货都执行这个操作,计划收货也是收获
@@ -57,12 +58,26 @@ export const actionStrategies = {
updateMaterialCoilSimple({
...coil,
dataType: 1,
}),
addCoilWarehouseOperationLog({
coilId: coil.coilId,
actualWarehouseId: coil.actualWarehouseId,
operationType: 1,
inOutType: 1,
})
])
// 如果填写了实际库区,还需要新增一条库区出入记录
// if (coil.actualWarehouseId) {
// await addCoilWarehouseOperationLog({
// coilId: coil.coilId,
// actualWarehouseId: coil.actualWarehouseId,
// operationType: 1,
// inOutType: 1,
// })
// }
return true
}
},
}
const handleCoil = (action, ...args) => {

View File

@@ -0,0 +1,153 @@
<template>
<div class="log-table-container">
<!-- 筛选条件 -->
<el-form :inline="true" :model="logParams" class="demo-form-inline mb20">
<el-form-item label="开始时间">
<el-date-picker
v-model="logParams.startTime"
type="datetime"
placeholder="选择开始时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="logParams.endTime"
type="datetime"
placeholder="选择结束时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getLogList">查询</el-button>
<el-button @click="resetParams">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="logLoading"
:data="logList"
style="width: 100%"
border
>
<el-table-column prop="createTime" label="操作时间" width="180">
<template slot-scope="scope">
{{ scope.row.createTime }}
</template>
</el-table-column>
<el-table-column prop="operationType" label="操作类型" width="100">
<template slot-scope="scope">
{{ scope.row.operationType === 1 ? '入库' : '出库' }}
</template>
</el-table-column>
<el-table-column prop="inOutType" label="出入库类型" width="100">
<template slot-scope="scope">
{{ scope.row.inOutType === 1 ? '入库' : '出库' }}
</template>
</el-table-column>
<el-table-column prop="coil.enterCoilNo" label="卷号" width="150">
<template slot-scope="scope">
{{ scope.row.coil && scope.row.coil.enterCoilNo ? scope.row.coil.enterCoilNo : '-' }}
</template>
</el-table-column>
<el-table-column prop="coil.itemName" label="物料名称" width="120">
<template slot-scope="scope">
{{ scope.row.coil && scope.row.coil.itemName ? scope.row.coil.itemName : '-' }}
</template>
</el-table-column>
<el-table-column prop="coil.specification" label="规格" width="150">
<template slot-scope="scope">
{{ scope.row.coil && scope.row.coil.specification ? scope.row.coil.specification : '-' }}
</template>
</el-table-column>
<el-table-column prop="warehouse.actualWarehouseName" label="库位" width="150">
<template slot-scope="scope">
{{ scope.row.warehouse && scope.row.warehouse.actualWarehouseName ? scope.row.warehouse.actualWarehouseName : '-' }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="150">
<template slot-scope="scope">
{{ scope.row.remark || '-' }}
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" :page.sync="logParams.pageNum"
:limit.sync="logParams.pageSize" @pagination="getLogList" />
</div>
</template>
<script>
import { getCoilWarehouseOperationLogByWarehouseId } from "@/api/wms/coilWarehouseOperationLog";
export default {
name: "LogTable",
props: {
warehouseId: {
type: String,
default: ""
}
},
data() {
return {
logList: [],
total: 0,
logParams: {
startTime: "",
endTime: "",
operationType: null,
inOutType: null,
pageNum: 1,
pageSize: 10,
},
logLoading: false,
};
},
watch: {
warehouseId: {
handler(newVal, oldVal) {
if (newVal !== oldVal && newVal !== "") {
this.logParams.secondWarehouseId = newVal;
this.getLogList();
}
},
immediate: true
}
},
methods: {
// 获取日志列表
getLogList() {
this.logLoading = true;
getCoilWarehouseOperationLogByWarehouseId(this.logParams)
.then((res) => {
this.logList = res.data || [];
this.total = res.total || 0;
})
.catch((err) => { this.$message.error("获取日志列表失败:" + err.message); })
.finally(() => { this.logLoading = false; });
},
// 重置筛选条件
resetParams() {
this.logParams = {
startTime: "",
endTime: "",
operationType: null,
inOutType: null,
pageNum: 1,
pageSize: 10,
secondWarehouseId: this.warehouseId
};
this.getLogList();
},
},
};
</script>
<style scoped>
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<div class="app-container">
<!-- 整体布局 -->
<div class="layout-container">
<!-- 左侧树形结构 -->
<div class="tree-container" v-loading="treeLoading">
<el-tree ref="warehouseTreeRef" :data="warehouseTree" :props="treeProps" node-key="actualWarehouseId"
@node-click="handleNodeClick" :expand-on-click-node="false" highlight-current class="warehouse-tree"
:disabled="isSwitching">
</el-tree>
</div>
<!-- 右侧仓库信息区域 - 替换为 Bird 组件 -->
<div class="warehouse-container" v-if="selectedNodeId" v-loading="rightLoading" element-loading-text="加载中..."
element-loading-spinner="el-icon-loading">
<!-- 日志表格 -->
<LogTable :warehouseId="selectedNodeId" />
</div>
<!-- 未选中节点提示 -->
<div class="empty-select-tip" v-if="!selectedNodeId">
请选择左侧仓库查看吞吐日志
</div>
</div>
</div>
</template>
<script>
import { treeActualWarehouseTwoLevel } from "@/api/wms/actualWarehouse";
import LogTable from "@/views/wms/warehouse/components/LogTable.vue";
export default {
name: "Overview",
components: {
LogTable,
},
data() {
return {
warehouseTree: [],
treeProps: { label: "actualWarehouseName", children: "children" },
selectedNodeId: "",
selectedNode: null,
// 加载状态
treeLoading: false,
rightLoading: false,
isSwitching: false,
nodeClickTimer: null,
buttonLoading: false,
};
},
created() {
this.getWarehouseTree();
},
beforeDestroy() {
if (this.nodeClickTimer) clearTimeout(this.nodeClickTimer);
},
methods: {
// 获取树形数据
getWarehouseTree() {
this.treeLoading = true;
treeActualWarehouseTwoLevel()
.then((res) => { this.warehouseTree = res.data || []; })
.catch((err) => { this.$message.error("获取仓库树形数据失败:" + err.message); })
.finally(() => { this.treeLoading = false; });
},
// 树节点点击
handleNodeClick(node) {
// if (this.isSwitching) return;
// if (this.nodeClickTimer) clearTimeout(this.nodeClickTimer);
// this.nodeClickTimer = setTimeout(() => {
if (!node.children || node.children.length === 0) {
// this.isSwitching = true;
// this.rightLoading = true;
this.selectedNodeId = node.actualWarehouseId;
this.selectedNode = node;
} else {
this.selectedNodeId = '';
this.selectedNode = null;
}
// }, 300);
},
},
};
</script>
<style scoped lang="scss">
.app-container {
width: 100%;
height: calc(100vh - 90px);
padding: 16px;
box-sizing: border-box;
background: #f5f7fa;
}
.layout-container {
display: flex;
width: 100%;
height: 100%;
gap: 16px;
.tree-container {
width: 160px;
height: 100%;
background: #fff;
border-radius: 8px;
padding: 16px;
box-sizing: border-box;
overflow-y: auto;
}
.warehouse-container {
flex: 1;
height: 100%;
overflow-y: auto;
}
.empty-select-tip {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
color: #909399;
font-size: 16px;
background: #fff;
border-radius: 8px;
}
}
// 初始化网格选择器样式
.grid-selector-container {
width: 100%;
padding: 10px;
box-sizing: border-box;
.selector-tip {
margin-bottom: 10px;
font-size: 12px;
color: #606266;
}
.grid-selector {
width: 100%;
max-width: 500px;
max-height: 300px;
overflow: auto;
border: 1px solid #e6e6e6;
background: #fafafa;
.grid-selector-row {
display: flex;
.grid-selector-cell {
width: 20px;
height: 20px;
border: 1px solid #e0e0e0;
box-sizing: border-box;
transition: all 0.1s;
&.hovered {
background: #e8f4ff;
border-color: #409eff;
}
&.selected {
background: #409eff;
border-color: #1e88e5;
}
&:hover {
background: #c6e2ff;
border-color: #409eff;
}
}
}
}
}
</style>