| | |
| | | import { ref, reactive, onMounted, onUnmounted, watch, getCurrentInstance } from 'vue' |
| | | import * as THREE from 'three' |
| | | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' |
| | | import * as signalR from '@microsoft/signalr' |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | // SignalR 连接 |
| | | let connection = null |
| | | |
| | | // 颜色常量 |
| | | const COLOR_MAP = { |
| | |
| | | let locationData = [] |
| | | let animationId = null |
| | | |
| | | // SignalR 初始化 |
| | | function initSignalR() { |
| | | proxy.http.post('api/User/GetCurrentUserInfo').then((result) => { |
| | | connection = new signalR.HubConnectionBuilder() |
| | | .withAutomaticReconnect() |
| | | .withUrl(`${proxy.http.ipAddress}stockHub?userName=${result.data.userName}`) |
| | | .build(); |
| | | |
| | | connection.start().catch((err) => console.log('SignalR连接失败:', err)); |
| | | |
| | | connection.on('StockUpdated', (update) => { |
| | | // 更新对应货位的数据 |
| | | const idx = locationData.findIndex(x => x.locationId === update.locationId); |
| | | if (idx !== -1) { |
| | | locationData[idx].stockQuantity = update.stockQuantity; |
| | | locationData[idx].stockStatus = update.stockStatus; |
| | | // 重新渲染单个货位颜色 |
| | | updateInstanceColor(idx, update.stockStatus); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | // 更新单个货位颜色 |
| | | function updateInstanceColor(instanceId, stockStatus) { |
| | | if (!locationMesh) return; |
| | | const loc = locationData[instanceId]; |
| | | if (!loc) return; |
| | | const color = getLocationColor(loc); |
| | | locationMesh.setColorAt(instanceId, new THREE.Color(color)); |
| | | locationMesh.instanceColor.needsUpdate = true; |
| | | } |
| | | |
| | | // 获取货位颜色 |
| | | function getLocationColor(location) { |
| | | if (location.locationStatus === 3) return COLOR_MAP.DISABLED // 禁用 |
| | |
| | | async function loadWarehouseList() { |
| | | try { |
| | | const res = await proxy.http.get('/api/Warehouse/GetAll') |
| | | if (res.Status && res.Data) { |
| | | warehouseList.value = res.Data |
| | | if (res.Data.length > 0) { |
| | | activeWarehouse.value = res.Data[0].warehouseId || res.Data[0].id |
| | | if (res.status && res.data) { |
| | | warehouseList.value = res.data |
| | | if (res.data.length > 0) { |
| | | activeWarehouse.value = res.data[0].warehouseId || res.data[0].id |
| | | await loadWarehouseData(activeWarehouse.value) |
| | | } |
| | | } |
| | |
| | | async function loadWarehouseData(warehouseId) { |
| | | try { |
| | | const res = await proxy.http.get(`/api/StockInfo/Get3DLayout?warehouseId=${warehouseId}`) |
| | | if (res.Status && res.Data) { |
| | | locationData = res.Data |
| | | // 提取物料编号和批次列表 |
| | | const codes = new Set() |
| | | const batches = new Set() |
| | | res.Data.forEach(loc => { |
| | | if (loc.materielCode) codes.add(loc.materielCode) |
| | | if (loc.batchNo) batches.add(loc.batchNo) |
| | | }) |
| | | materielCodeList.value = Array.from(codes) |
| | | batchNoList.value = Array.from(batches) |
| | | console.log('Get3DLayout response:', res) |
| | | if (res.status && res.data) { |
| | | const data = res.data |
| | | console.log('data.locations:', data.locations) |
| | | locationData = data.locations || [] |
| | | // 使用后端返回的筛选列表 |
| | | materielCodeList.value = data.materielCodeList || [] |
| | | batchNoList.value = data.batchNoList || [] |
| | | console.log('locationData set:', locationData.length, 'items') |
| | | // 渲染货位 |
| | | renderLocations() |
| | | } |
| | |
| | | |
| | | // 渲染货位 |
| | | function renderLocations() { |
| | | console.log('renderLocations called', { scene: !!scene, locationDataLength: locationData.length }) |
| | | if (!scene) return |
| | | |
| | | // 移除旧的货位网格 |
| | |
| | | } |
| | | |
| | | // 过滤数据 |
| | | let filteredData = locationData |
| | | let filteredData = [...locationData] |
| | | if (filterStockStatus.value !== null) { |
| | | filteredData = filteredData.filter(loc => loc.stockStatus === filterStockStatus.value) |
| | | } |
| | |
| | | filteredData = filteredData.filter(loc => loc.batchNo === filterBatchNo.value) |
| | | } |
| | | |
| | | console.log('filteredData length:', filteredData.length) |
| | | if (filteredData.length === 0) return |
| | | |
| | | // 创建 InstancedMesh |
| | |
| | | // 设置颜色 |
| | | color.setHex(getLocationColor(location)) |
| | | locationMesh.setColorAt(i, color) |
| | | |
| | | if (i === 0) { |
| | | console.log('First location:', location, { x, y, z }) |
| | | } |
| | | }) |
| | | |
| | | locationMesh.instanceMatrix.needsUpdate = true |
| | | if (locationMesh.instanceColor) locationMesh.instanceColor.needsUpdate = true |
| | | |
| | | scene.add(locationMesh) |
| | | console.log('locationMesh added to scene') |
| | | } |
| | | |
| | | // 动画循环 |
| | |
| | | onMounted(() => { |
| | | initThreeJS() |
| | | loadWarehouseList() |
| | | initSignalR() |
| | | window.addEventListener('resize', onWindowResize) |
| | | }) |
| | | |
| | |
| | | if (renderer) { |
| | | renderer.dispose() |
| | | } |
| | | if (connection) { |
| | | connection.stop() |
| | | } |
| | | }) |
| | | </script> |
| | | |