feat(wms-warehouse): 添加钢卷右键领料功能

新增仓库视图右键菜单触发钢卷选择弹窗,支持平面视图和3D视图右键选中钢卷,实现快速领料流程,包含领料弹窗、工序选择和接口调用逻辑
This commit is contained in:
2026-06-30 10:02:27 +08:00
parent dbcc28fb80
commit 08dec15614
6 changed files with 209 additions and 17 deletions

View File

@@ -91,20 +91,14 @@ export default {
return this.findFirstLeaf(this.item, this.basePath) return this.findFirstLeaf(this.item, this.basePath)
}, },
menuStyle() { menuStyle() {
console.log('[SidebarItem] 完整 item 对象:', JSON.parse(JSON.stringify(this.item)))
console.log('[SidebarItem] item.meta:', this.item.meta)
console.log('[SidebarItem] item.meta?.style 原始值:', this.item.meta && this.item.meta.style)
if (this.item.meta && this.item.meta.style) { if (this.item.meta && this.item.meta.style) {
try { try {
const parsed = JSON.parse(this.item.meta.style) const parsed = JSON.parse(this.item.meta.style)
console.log('[SidebarItem] ✅ menuStyle 解析成功:', parsed)
return parsed return parsed
} catch (e) { } catch (e) {
console.warn('[SidebarItem] ❌ JSON.parse 失败:', e)
return {} return {}
} }
} }
console.log('[SidebarItem] ⚠️ item.meta.style 为空,返回 {}')
return {} return {}
} }
}, },

View File

@@ -49,13 +49,10 @@ const permission = {
arr.forEach(item => { arr.forEach(item => {
const title = (item.meta && item.meta.title) || item.name || '' const title = (item.meta && item.meta.title) || item.name || ''
const styleVal = item.meta && item.meta.style const styleVal = item.meta && item.meta.style
console.log('[permission] depth=' + depth, title, 'meta.style=', styleVal)
if (item.children) logMetaStyle(item.children, depth + 1) if (item.children) logMetaStyle(item.children, depth + 1)
}) })
} }
console.log('[permission] === getRouters 原始数据 ===')
logMetaStyle(res.data) logMetaStyle(res.data)
console.log('[permission] === getRouters 数据结束 ===')
const sdata = JSON.parse(JSON.stringify(res.data)) const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data)) const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata) const sidebarRoutes = filterAsyncRouter(sdata)

View File

@@ -92,7 +92,8 @@
<tbody> <tbody>
<tr v-for="c in tableRows" :key="c.id" <tr v-for="c in tableRows" :key="c.id"
:class="{ sel: detail && detail.id === c.id }" :class="{ sel: detail && detail.id === c.id }"
@click="showCoilDetail(c.id)"> @click="showCoilDetail(c.id)"
@contextmenu.prevent="handleRowRightClick(c)">
<td>{{ c.posKey }} L{{ c.layer }}</td> <td>{{ c.posKey }} L{{ c.layer }}</td>
<td>{{ c.coilNo }}</td> <td>{{ c.coilNo }}</td>
<td>{{ c.specification }}</td> <td>{{ c.specification }}</td>
@@ -306,6 +307,7 @@ export default {
const dom = this.renderer.domElement; const dom = this.renderer.domElement;
dom.addEventListener('click', this.onCanvasClick); dom.addEventListener('click', this.onCanvasClick);
dom.addEventListener('contextmenu', this.onContextMenu);
dom.addEventListener('mousedown', this.onMouseDown); dom.addEventListener('mousedown', this.onMouseDown);
dom.addEventListener('mousemove', this.onMouseMove); dom.addEventListener('mousemove', this.onMouseMove);
dom.addEventListener('mouseup', this.onMouseUp); dom.addEventListener('mouseup', this.onMouseUp);
@@ -608,6 +610,32 @@ export default {
this.closeDetail(); this.closeDetail();
}, },
onContextMenu(e) {
e.preventDefault();
const rect = this.renderer.domElement.getBoundingClientRect();
this.mouseVec.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
this.mouseVec.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
this.raycaster.setFromCamera(this.mouseVec, this.camera);
const targets = [];
this.scene.traverse((o) => { if (o.userData && o.userData.coilId) targets.push(o); });
const hits = this.raycaster.intersectObjects(targets, true);
if (hits.length > 0) {
let obj = hits[0].object;
while (obj.parent && !(obj.userData && obj.userData.coilId)) obj = obj.parent;
if (obj.userData && obj.userData.coilId) {
const coil = this.coilList.find(c => c.id === obj.userData.coilId);
if (coil) {
this.$emit('coil-selected', {
coilId: coil.id,
currentCoilNo: coil.coilNo,
warehouseId: coil.warehouseId,
warehouseName: coil.warehouseName,
});
}
}
}
},
async showCoilDetail(cid) { async showCoilDetail(cid) {
const coil = this.coilList.find((c) => c.id === cid); const coil = this.coilList.find((c) => c.id === cid);
if (!coil) return; if (!coil) return;
@@ -638,6 +666,15 @@ export default {
} }
}, },
handleRowRightClick(coil) {
this.$emit('coil-selected', {
coilId: coil.id,
currentCoilNo: coil.coilNo,
warehouseId: coil.warehouseId,
warehouseName: coil.warehouseName,
});
},
closeDetail() { closeDetail() {
this.detail = null; this.detail = null;
this.applyHighlight(); this.applyHighlight();

View File

@@ -80,7 +80,7 @@
</div> </div>
<warehouse-interlaced v-else="warehouseList.length" :id="id" :columns="columns" :canToggle="canToggle" <warehouse-interlaced v-else="warehouseList.length" :id="id" :columns="columns" :canToggle="canToggle"
:canRelease="canRelease" @split-warehouse="handleSplitWarehouse" @merge-warehouse="handleMergeWarehouse" :canRelease="canRelease" @split-warehouse="handleSplitWarehouse" @merge-warehouse="handleMergeWarehouse"
@release-warehouse="handleReleaseWarehouse" /> @release-warehouse="handleReleaseWarehouse" @coil-selected="handleCoilSelected" />
</div> </div>
</div> </div>
</template> </template>
@@ -179,6 +179,10 @@ export default {
handleReleaseWarehouse(warehouse) { handleReleaseWarehouse(warehouse) {
this.$emit('release-warehouse', warehouse); this.$emit('release-warehouse', warehouse);
}, },
handleCoilSelected(coil) {
this.$emit('coil-selected', coil);
},
/** /**
* 解析第三级库位编码 * 解析第三级库位编码
* 新规则: * 新规则:

View File

@@ -41,7 +41,8 @@
}"> }">
<div v-for="warehouse in columnWarehouseData[column].layer1" <div v-for="warehouse in columnWarehouseData[column].layer1"
:key="`warehouse-1-${warehouse.actualWarehouseId}`" class="warehouse-cell layer-1" :key="`warehouse-1-${warehouse.actualWarehouseId}`" class="warehouse-cell layer-1"
:class="{ disabled: warehouse.isEnabled === 0, error: warehouse.isEnabled === 0 && warehouse.currentCoilNo == null }" @click.stop="handleCellClick(warehouse)"> :class="{ disabled: warehouse.isEnabled === 0, error: warehouse.isEnabled === 0 && warehouse.currentCoilNo == null }" @click.stop="handleCellClick(warehouse)"
@contextmenu.prevent.stop="handleCellContextMenu(warehouse)">
<div class="cell-name"> <div class="cell-name">
<div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div> <div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div>
<div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div> <div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div>
@@ -61,7 +62,8 @@
:class="{ disabled: warehouse.isEnabled === 0, error: warehouse.isEnabled === 0 && warehouse.currentCoilNo == null }" :style="{ :class="{ disabled: warehouse.isEnabled === 0, error: warehouse.isEnabled === 0 && warehouse.currentCoilNo == null }" :style="{
transform: `translateY(var(--offset-value))`, transform: `translateY(var(--offset-value))`,
position: 'relative' position: 'relative'
}" @click.stop="handleCellClick(warehouse)"> }" @click.stop="handleCellClick(warehouse)"
@contextmenu.prevent.stop="handleCellContextMenu(warehouse)">
<div class="cell-name"> <div class="cell-name">
<div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div> <div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div>
<div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div> <div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div>
@@ -433,6 +435,16 @@ export default {
this.currentWarehouse = { ...warehouse }; this.currentWarehouse = { ...warehouse };
this.dialogOpen = true; this.dialogOpen = true;
}, },
handleCellContextMenu(warehouse) {
if (!warehouse.coilId) return;
this.$emit('coil-selected', {
coilId: warehouse.coilId,
currentCoilNo: warehouse.currentCoilNo,
warehouseId: warehouse.actualWarehouseId,
warehouseName: warehouse.actualWarehouseName || warehouse.actualWarehouseCode,
});
},
handleSplitWarehouse(payload) { handleSplitWarehouse(payload) {
this.$emit('split-warehouse', payload); this.$emit('split-warehouse', payload);
}, },

View File

@@ -13,7 +13,7 @@
<!-- 右侧仓库信息区域 - 2D / 3D 双视图 tab --> <!-- 右侧仓库信息区域 - 2D / 3D 双视图 tab -->
<div class="warehouse-container" v-if="selectedNodeId" v-loading="rightLoading" element-loading-text="加载中..." <div class="warehouse-container" v-if="selectedNodeId" v-loading="rightLoading" element-loading-text="加载中..."
element-loading-spinner="el-icon-loading"> element-loading-spinner="el-icon-loading">
<el-tabs v-model="activeTab" class="overview-tabs"> <el-tabs v-model="activeTab" class="overview-tabs" @tab-click="handleTabClick">
<el-tab-pane label="平面视图" name="bird"> <el-tab-pane label="平面视图" name="bird">
<!-- <button buttonLoading type="primary" @click="exportAllQrcodes">导出二维码</button> --> <!-- <button buttonLoading type="primary" @click="exportAllQrcodes">导出二维码</button> -->
<WarehouseBird <WarehouseBird
@@ -22,10 +22,12 @@
@open-init-dialog="openInitDialog" @open-init-dialog="openInitDialog"
@split-warehouse="handleSplitWarehouse" @split-warehouse="handleSplitWarehouse"
@merge-warehouse="handleMergeWarehouse" @merge-warehouse="handleMergeWarehouse"
@coil-selected="handleCoilSelected"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="数字孪生 3D" name="three"> <el-tab-pane label="数字孪生 3D" name="three">
<Warehouse3D v-if="activeTab === 'three'" :warehouse-list="warehouseList" :parent-id="selectedNodeId" /> <Warehouse3D v-if="activeTab === 'three'" :warehouse-list="warehouseList" :parent-id="selectedNodeId"
@coil-selected="handleCoilSelected" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
@@ -36,6 +38,29 @@
</div> </div>
</div> </div>
<!-- 领料弹窗 -->
<el-dialog title="领料" :visible.sync="pickMaterialDialogVisible" width="900px" destroy-on-close append-to-body>
<div v-loading="coilFullInfoLoading" element-loading-text="正在加载钢卷信息...">
<CoilInfoRender v-if="selectedCoilFullInfo" :coilInfo="selectedCoilFullInfo" :column="4" border
:showOnlyValue="true" title="钢卷信息" />
<div v-else-if="!coilFullInfoLoading" class="no-coil-data">暂无钢卷数据</div>
</div>
<el-form v-if="selectedCoilFullInfo" style="margin-top: 16px">
<el-form-item label="选择工序" required>
<el-select v-model="selectedProcess" placeholder="请选择工序" style="width: 300px">
<el-option v-for="p in processList" :key="p.actionType" :label="p.name" :value="p.actionType" />
</el-select>
</el-form-item>
</el-form>
<template slot="footer">
<el-button @click="pickMaterialDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmPickMaterial" :loading="pickLoading"
:disabled="!selectedProcess">确认领料</el-button>
</template>
</el-dialog>
<!-- 库位初始化弹窗 --> <!-- 库位初始化弹窗 -->
<el-dialog title="库位初始化" :visible.sync="initDialogVisible" width="700px" destroy-on-close append-to-body center> <el-dialog title="库位初始化" :visible.sync="initDialogVisible" width="700px" destroy-on-close append-to-body center>
<el-form ref="initFormRef" :model="initForm" :rules="initFormRules" label-width="100px" size="mini"> <el-form ref="initFormRef" :model="initForm" :rules="initFormRules" label-width="100px" size="mini">
@@ -71,6 +96,9 @@
<script> <script>
import { listActualWarehouse, treeActualWarehouseTwoLevel, getActualWarehouse, generateLocations, splitActualWarehouse, mergeActualWarehouse } from "@/api/wms/actualWarehouse"; import { listActualWarehouse, treeActualWarehouseTwoLevel, getActualWarehouse, generateLocations, splitActualWarehouse, mergeActualWarehouse } from "@/api/wms/actualWarehouse";
import { getMaterialCoil, startSpecialSplit } from "@/api/wms/coil";
import { addPendingAction } from "@/api/wms/pendingAction";
import { PROCESSES } from "./processes";
import WarehouseBird from './components/WarehouseBird.vue'; import WarehouseBird from './components/WarehouseBird.vue';
import Warehouse3D from './components/Warehouse3D.vue'; import Warehouse3D from './components/Warehouse3D.vue';
import jsPDF from 'jspdf'; import jsPDF from 'jspdf';
@@ -125,6 +153,14 @@ export default {
isSwitching: false, isSwitching: false,
nodeClickTimer: null, nodeClickTimer: null,
buttonLoading: false, buttonLoading: false,
// 领料相关
pickMaterialDialogVisible: false,
selectedCoilInfo: null,
selectedCoilFullInfo: null,
selectedProcess: '',
pickLoading: false,
coilFullInfoLoading: false,
processList: PROCESSES,
}; };
}, },
created() { created() {
@@ -134,6 +170,110 @@ export default {
if (this.nodeClickTimer) clearTimeout(this.nodeClickTimer); if (this.nodeClickTimer) clearTimeout(this.nodeClickTimer);
}, },
methods: { methods: {
handleCoilSelected(coil) {
this.selectedCoilInfo = { ...coil };
this.openPickMaterialDialog();
},
clearCoilSelection() {
this.selectedCoilInfo = null;
this.selectedCoilFullInfo = null;
this.selectedProcess = '';
},
handleTabClick() {
this.clearCoilSelection();
},
async openPickMaterialDialog() {
if (!this.selectedCoilInfo || !this.selectedCoilInfo.coilId) {
this.$message.warning('请先选中一个钢卷');
return;
}
this.pickMaterialDialogVisible = true;
this.selectedProcess = '';
this.coilFullInfoLoading = true;
try {
const res = await getMaterialCoil(this.selectedCoilInfo.coilId);
this.selectedCoilFullInfo = res.data || {};
} catch (err) {
this.$message.error('获取钢卷信息失败:' + err.message);
this.selectedCoilFullInfo = null;
} finally {
this.coilFullInfoLoading = false;
}
},
async confirmPickMaterial() {
if (!this.selectedProcess) {
this.$message.warning('请选择工序');
return;
}
const process = this.processList.find(p => p.actionType == this.selectedProcess);
if (!process) {
this.$message.error('未知的工序');
return;
}
const coil = this.selectedCoilInfo;
try {
await this.$confirm(
`确认对钢卷 ${coil.currentCoilNo || coil.coilId} 进行【${process.name}】领料操作?`,
'二次确认',
{ confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }
);
} catch {
return;
}
this.pickLoading = true;
try {
let actionId = null;
if (process.api === 'pendingAction') {
const pendingData = {
coilId: coil.coilId,
currentCoilNo: coil.currentCoilNo,
actionType: process.actionType,
actionStatus: 0,
sourceType: 'manual',
warehouseId: coil.warehouseId,
priority: 0,
lockValue: process.lockValue,
};
await addPendingAction(pendingData);
} else {
const res = await startSpecialSplit(coil.coilId, process.actionType);
actionId = res.data;
}
this.$message.success('领料成功,已创建待操作任务');
this.pickMaterialDialogVisible = false;
this.clearCoilSelection();
// 跳转到工序操作界面
// const path = this.getOperationRoutePath(process.actionType);
// this.$router.push({
// path,
// query: {
// coilId: coil.coilId,
// actionId: actionId || '',
// actionTypeCode: process.actionType,
// actionType: process.name,
// }
// });
} catch (err) {
this.$message.error('领料失败:' + (err.message || '未知错误'));
} finally {
this.pickLoading = false;
}
},
getOperationRoutePath(actionType) {
if (actionType >= 100 && actionType <= 199) return '/wms/split';
if (actionType >= 200 && actionType <= 299) return '/wms/merge';
return '/wms/typing';
},
/** /**
* 处理分割库位事件 * 处理分割库位事件
*/ */
@@ -222,7 +362,6 @@ export default {
let currentX = margin; // 当前X坐标 let currentX = margin; // 当前X坐标
let currentY = margin; // 当前Y坐标 let currentY = margin; // 当前Y坐标
let currentIndex = 0; // 当前处理的库位索引 let currentIndex = 0; // 当前处理的库位索引
// const list = this.warehouseList.filter(item => item.actualWarehouseCode.includes('X'));
const list = this.warehouseList; const list = this.warehouseList;
const totalCount = list.length; const totalCount = list.length;
@@ -307,6 +446,7 @@ export default {
this.rightLoading = true; this.rightLoading = true;
this.selectedNodeId = node.actualWarehouseId; this.selectedNodeId = node.actualWarehouseId;
this.selectedNode = node; this.selectedNode = node;
this.clearCoilSelection();
this.getWarehouseList(node.actualWarehouseId) this.getWarehouseList(node.actualWarehouseId)
.finally(() => { .finally(() => {
@@ -317,6 +457,7 @@ export default {
this.selectedNodeId = ""; this.selectedNodeId = "";
this.selectedNode = null; this.selectedNode = null;
this.warehouseList = []; this.warehouseList = [];
this.clearCoilSelection();
} }
}, 300); }, 300);
}, },
@@ -505,6 +646,13 @@ export default {
} }
} }
.no-coil-data {
text-align: center;
padding: 32px;
color: #909399;
font-size: 14px;
}
// 初始化网格选择器样式 // 初始化网格选择器样式
.grid-selector-container { .grid-selector-container {
width: 100%; width: 100%;