From 0005d58f6888dd3e4524784d1b6f103f9b1c588e Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期一, 30 三月 2026 18:33:22 +0800
Subject: [PATCH] 合并
---
Code/WMS/WIDESEA_WMSClient/src/views/stock/stockChat.vue | 684 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 684 insertions(+), 0 deletions(-)
diff --git a/Code/WMS/WIDESEA_WMSClient/src/views/stock/stockChat.vue b/Code/WMS/WIDESEA_WMSClient/src/views/stock/stockChat.vue
new file mode 100644
index 0000000..a463460
--- /dev/null
+++ b/Code/WMS/WIDESEA_WMSClient/src/views/stock/stockChat.vue
@@ -0,0 +1,684 @@
+<template>
+ <div class="stock-chat-container">
+ <!-- 浠撳簱 Tabs -->
+ <el-tabs v-model="activeWarehouse" @tab-change="onWarehouseChange">
+ <el-tab-pane
+ v-for="wh in warehouseList"
+ :key="wh.warehouseId || wh.id"
+ :label="wh.warehouseName"
+ :name="wh.warehouseId || wh.id"
+ />
+ </el-tabs>
+
+ <!-- 宸ュ叿鏍� -->
+ <div class="toolbar">
+ <el-select v-model="filterStockStatus" placeholder="搴撳瓨鐘舵�佺瓫閫�" style="width: 160px" clearable>
+ <el-option label="缁勭洏鏆傚瓨(1)" :value="1" />
+ <el-option label="鍏ュ簱纭(3)" :value="3" />
+ <el-option label="鍏ュ簱瀹屾垚(6)" :value="6" />
+ <el-option label="鍑哄簱閿佸畾(7)" :value="7" />
+ <el-option label="鍑哄簱瀹屾垚(8)" :value="8" />
+ <el-option label="绌烘墭鐩�(22)" :value="22" />
+ </el-select>
+ <el-select v-model="filterMaterielCode" placeholder="鐗╂枡绛涢��" style="width: 140px" clearable>
+ <el-option v-for="code in materielCodeList" :key="code" :label="code" :value="code" />
+ </el-select>
+ <el-select v-model="filterBatchNo" placeholder="鎵规绛涢��" style="width: 140px" clearable>
+ <el-option v-for="batch in batchNoList" :key="batch" :label="batch" :value="batch" />
+ </el-select>
+ <el-button @click="resetCamera">閲嶇疆瑙嗚</el-button>
+ <el-button type="primary" @click="refreshData" :loading="refreshing">鍒锋柊鏁版嵁</el-button>
+ </div>
+
+ <!-- 3D Canvas -->
+ <div ref="canvasContainer" class="canvas-container" />
+
+ <!-- 鐘舵�佸浘渚� -->
+ <div class="legend">
+ <div class="legend-title">璐т綅鐘舵��</div>
+ <div v-for="item in legendItems" :key="item.status" class="legend-item">
+ <span class="color-box" :style="{ background: item.color }" />
+ <span>{{ item.label }}</span>
+ </div>
+ </div>
+
+ <!-- 璇︽儏渚ц竟闈㈡澘 -->
+ <el-drawer v-model="detailDialogVisible" title="搴撳瓨璇︽儏" direction="rtl" size="500px">
+ <div v-if="selectedLocation" class="detail-content">
+ <el-descriptions :column="2" border>
+ <el-descriptions-item label="璐т綅缂栧彿">{{ selectedLocation.locationCode }}</el-descriptions-item>
+ <el-descriptions-item label="璐т綅鐘舵��">{{ getLocationStatusText(selectedLocation.locationStatus) }}</el-descriptions-item>
+ <el-descriptions-item label="鎵樼洏缂栧彿">{{ selectedLocation.palletCode || '鏃�' }}</el-descriptions-item>
+ <el-descriptions-item label="搴撳瓨鐘舵��">{{ getStockStatusText(selectedLocation.stockStatus) }}</el-descriptions-item>
+ <el-descriptions-item label="鎬诲簱瀛�">{{ selectedLocation.stockQuantity }}{{ selectedLocation.unit || '' }}</el-descriptions-item>
+ </el-descriptions>
+
+ <!-- 搴撳瓨鏄庣粏琛ㄦ牸 -->
+ <div v-if="selectedLocation.details && selectedLocation.details.length > 0" class="detail-table">
+ <h4>搴撳瓨鏄庣粏</h4>
+ <el-table :data="selectedLocation.details" border size="small" max-height="400">
+ <el-table-column prop="materielCode" label="鐗╂枡缂栫爜" width="100" />
+ <el-table-column prop="materielName" label="鐗╂枡鍚嶇О" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="batchNo" label="鎵规鍙�" width="100" show-overflow-tooltip />
+ <el-table-column prop="stockQuantity" label="鏁伴噺" width="70" align="right" />
+ <el-table-column prop="unit" label="鍗曚綅" width="50" align="center" />
+ <el-table-column prop="effectiveDate" label="鏈夋晥鏈�" width="100" />
+ </el-table>
+ </div>
+ <div v-else class="no-detail">
+ <el-empty description="鏆傛棤搴撳瓨鏄庣粏" />
+ </div>
+ </div>
+ </el-drawer>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onUnmounted, watch, nextTick, 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
+
+// 棰滆壊甯搁噺 - 鍩轰簬瀹為檯鏋氫妇鍊�
+// 璐т綅鐘舵��(locationStatus): 0=绌洪棽, 1=閿佸畾, 10=鏈夎揣閿佸畾, 20=绌洪棽閿佸畾, 99=澶ф墭鐩橀攣瀹�, 100=鏈夎揣
+// 搴撳瓨鐘舵��(stockStatus): 1=缁勭洏鏆傚瓨, 3=鍏ュ簱纭, 6=鍏ュ簱瀹屾垚, 7=鍑哄簱閿佸畾, 8=鍑哄簱瀹屾垚, 9=绉诲簱閿佸畾绛�
+const COLOR_MAP = {
+ // 璐т綅鐘舵�侀鑹�
+ LOC_FREE: 0x90EE90, // 0=绌洪棽 - 娴呯豢鑹�
+ LOC_LOCK: 0xFF6B6B, // 1=閿佸畾 - 绾㈣壊
+ LOC_INSTOCK_LOCK: 0xFFA500, // 10=鏈夎揣閿佸畾 - 姗欒壊
+ LOC_FREE_LOCK: 0xFFD700, // 20=绌洪棽閿佸畾 - 閲戣壊
+ LOC_PALLET_LOCK: 0x9370DB, // 99=澶ф墭鐩橀攣瀹� - 绱壊
+ LOC_INSTOCK: 0x409EFF, // 100=鏈夎揣 - 钃濊壊
+
+ // 搴撳瓨鐘舵�侀鑹�
+ STOCK_PENDING: 0x00CED1, // 1=缁勭洏鏆傚瓨 - 娣遍潚鑹�
+ STOCK_CONFIRMED: 0x87CEEB, // 3=鍏ュ簱纭 - 澶╄摑鑹�
+ STOCK_COMPLETED: 0x32CD32, // 6=鍏ュ簱瀹屾垚 - 浜豢鑹�
+ STOCK_OUT_LOCK: 0xFF6347, // 7=鍑哄簱閿佸畾 - 鐣寗绾�
+ STOCK_OUT_COMPLETED: 0x228B22, // 8=鍑哄簱瀹屾垚 - 妫灄缁�
+ STOCK_TRANSFER_LOCK: 0xFF8C00, // 9=绉诲簱閿佸畾 - 娣辨鑹�
+ STOCK_COMPLETED_NO_ORDER: 0x20B2AA, // 10=鍏ュ簱瀹屾垚鏈缓鍑哄簱鍗�
+ STOCK_RETURN: 0xFF4500, // 11=閫�搴�
+ STOCK_MANUAL_PENDING: 0x48D1CC, // 12=鎵嬪姩缁勭洏鏆傚瓨
+ STOCK_MANUAL_CONFIRMED: 0x7FFFD4, // 13=鎵嬪姩缁勭洏鍏ュ簱纭
+ STOCK_PICK_COMPLETED: 0x6B8E23, // 14=鎷i�夊畬鎴�
+ STOCK_MES_RETURN: 0xDC143C, // 21=MES閫�搴�
+ STOCK_EMPTY_TRAY: 0xDDA0DD, // 22=绌烘墭鐩樺簱瀛� - 姊呯孩鑹�
+ STOCK_GROUP_CANCEL: 0xDEB887, // 99=缁勭洏鎾ら攢 - 鏆楅噾鑹�
+ STOCK_IN_CANCEL: 0xA0522D, // 199=鍏ュ簱鎾ら攢 - 璧壊
+}
+
+// 鍥句緥椤� - 璐т綅鐘舵��
+const legendItems = [
+ { status: 'loc_free', label: '绌洪棽(0)', color: '#90EE90' },
+ { status: 'loc_lock', label: '閿佸畾(1)', color: '#FF6B6B' },
+ { status: 'loc_instock_lock', label: '鏈夎揣閿佸畾(10)', color: '#FFA500' },
+ { status: 'loc_free_lock', label: '绌洪棽閿佸畾(20)', color: '#FFD700' },
+ { status: 'loc_pallet_lock', label: '澶ф墭鐩橀攣瀹�(99)', color: '#9370DB' },
+ { status: 'loc_instock', label: '鏈夎揣(100)', color: '#409EFF' },
+]
+
+// Refs
+const canvasContainer = ref(null)
+
+// 鐘舵��
+const activeWarehouse = ref(null)
+const warehouseList = ref([])
+const filterStockStatus = ref(null)
+const filterMaterielCode = ref(null)
+const filterBatchNo = ref(null)
+const materielCodeList = ref([])
+const batchNoList = ref([])
+const detailDialogVisible = ref(false)
+const selectedLocation = ref(null)
+const refreshing = ref(false)
+
+// Three.js 鐩稿叧
+let scene, camera, renderer, controls, raycaster, mouse
+let locationMesh = null
+let locationData = []
+let originalLocationData = [] // 淇濆瓨鍘熷瀹屾暣鏁版嵁锛岀敤浜庣瓫閫夋仮澶�
+let animationId = null
+let locationIdToInstanceId = new Map() // locationId -> instanceId 鏄犲皠
+
+// 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) => {
+ console.log('鏀跺埌搴撳瓨鏇存柊:', update)
+ // 鏇存柊瀵瑰簲璐т綅鐨勬暟鎹�
+ const idx = locationData.findIndex(x => x.locationId === update.locationId);
+ if (idx !== -1) {
+ locationData[idx].stockQuantity = update.stockQuantity;
+ locationData[idx].stockStatus = update.stockStatus;
+ locationData[idx].palletCode = update.palletCode;
+ locationData[idx].locationStatus = update.locationStatus;
+ // 鏇存柊搴撳瓨鏄庣粏
+ if (update.details && update.details.length > 0) {
+ locationData[idx].details = update.details;
+ }
+ // 閫氳繃鏄犲皠鎵惧埌瀹炰緥ID锛屾洿鏂伴鑹�
+ const instanceId = locationIdToInstanceId.get(update.locationId);
+ if (instanceId !== undefined) {
+ updateInstanceColor(instanceId, update.locationStatus);
+ }
+ }
+ });
+ });
+}
+
+// 鏇存柊鍗曚釜璐т綅棰滆壊
+function updateInstanceColor(instanceId, locationStatus) {
+ if (!locationMesh) return;
+ // 鏍规嵁璐т綅鐘舵�佽幏鍙栭鑹�
+ const color = getLocationColorByStatus(locationStatus);
+ locationMesh.setColorAt(instanceId, new THREE.Color(color));
+ locationMesh.instanceColor.needsUpdate = true;
+}
+
+// 鏍规嵁璐т綅鐘舵�佽幏鍙栭鑹�
+function getLocationColorByStatus(locStatus) {
+ switch (locStatus) {
+ case 0: return COLOR_MAP.LOC_FREE // 绌洪棽
+ case 1: return COLOR_MAP.LOC_LOCK // 閿佸畾
+ case 10: return COLOR_MAP.LOC_INSTOCK_LOCK // 鏈夎揣閿佸畾
+ case 20: return COLOR_MAP.LOC_FREE_LOCK // 绌洪棽閿佸畾
+ case 99: return COLOR_MAP.LOC_PALLET_LOCK // 澶ф墭鐩橀攣瀹�
+ case 100: return COLOR_MAP.LOC_INSTOCK // 鏈夎揣
+ default: return COLOR_MAP.LOC_FREE // 榛樿绌洪棽鑹�
+ }
+}
+
+// 鑾峰彇璐т綅棰滆壊 - 鍙牴鎹揣浣嶇姸鎬�
+function getLocationColor(location) {
+ const locStatus = location.locationStatus
+
+ // 鏍规嵁璐т綅鐘舵�佸垽鏂鑹�
+ switch (locStatus) {
+ case 0: return COLOR_MAP.LOC_FREE // 绌洪棽
+ case 1: return COLOR_MAP.LOC_LOCK // 閿佸畾
+ case 10: return COLOR_MAP.LOC_INSTOCK_LOCK // 鏈夎揣閿佸畾
+ case 20: return COLOR_MAP.LOC_FREE_LOCK // 绌洪棽閿佸畾
+ case 99: return COLOR_MAP.LOC_PALLET_LOCK // 澶ф墭鐩橀攣瀹�
+ case 100: return COLOR_MAP.LOC_INSTOCK // 鏈夎揣
+ default: return COLOR_MAP.LOC_FREE // 榛樿绌洪棽鑹�
+ }
+}
+
+// 鑾峰彇璐т綅鐘舵�佹枃鏈�
+function getLocationStatusText(status) {
+ const map = {
+ 0: '绌洪棽',
+ 1: '閿佸畾',
+ 10: '鏈夎揣閿佸畾',
+ 20: '绌洪棽閿佸畾',
+ 99: '澶ф墭鐩橀攣瀹�',
+ 100: '鏈夎揣'
+ }
+ return map[status] || '鏈煡(' + status + ')'
+}
+
+// 鑾峰彇搴撳瓨鐘舵�佹枃鏈�
+function getStockStatusText(status) {
+ const map = {
+ 0: '鏃犲簱瀛�',
+ 1: '缁勭洏鏆傚瓨',
+ 3: '鍏ュ簱纭',
+ 6: '鍏ュ簱瀹屾垚',
+ 7: '鍑哄簱閿佸畾',
+ 8: '鍑哄簱瀹屾垚',
+ 9: '绉诲簱閿佸畾',
+ 10: '鍏ュ簱瀹屾垚鏈缓鍑哄簱鍗�',
+ 11: '閫�搴�',
+ 12: '鎵嬪姩缁勭洏鏆傚瓨',
+ 13: '鎵嬪姩缁勭洏鍏ュ簱纭',
+ 14: '鎷i�夊畬鎴�',
+ 21: 'MES閫�搴�',
+ 22: '绌烘墭鐩樺簱瀛�',
+ 99: '缁勭洏鎾ら攢',
+ 199: '鍏ュ簱鎾ら攢'
+ }
+ return map[status] || '鏈煡(' + status + ')'
+}
+
+// 鍔犺浇浠撳簱鍒楄〃
+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
+ await loadWarehouseData(activeWarehouse.value)
+ }
+ }
+ } catch (e) {
+ console.error('鍔犺浇浠撳簱鍒楄〃澶辫触', e)
+ }
+}
+
+// 鍔犺浇浠撳簱璐т綅鏁版嵁
+async function loadWarehouseData(warehouseId) {
+ try {
+ const res = await proxy.http.get(`/api/StockInfo/Get3DLayout?warehouseId=${warehouseId}`)
+ if (res.status && res.data) {
+ const data = res.data
+ originalLocationData = data.locations || [] // 淇濆瓨鍘熷瀹屾暣鏁版嵁
+ locationData = [...originalLocationData] // 褰撳墠鏄剧ず鏁版嵁
+ // 浣跨敤鍚庣杩斿洖鐨勭瓫閫夊垪琛�
+ materielCodeList.value = data.materielCodeList || []
+ batchNoList.value = data.batchNoList || []
+ // 娓叉煋璐т綅
+ renderLocations()
+ }
+ } catch (e) {
+ console.error('鍔犺浇璐т綅鏁版嵁澶辫触', e)
+ }
+}
+
+// 鍒濆鍖� Three.js 鍦烘櫙
+function initThreeJS() {
+ if (!canvasContainer.value) return
+
+ const width = canvasContainer.value.clientWidth
+ const height = canvasContainer.value.clientHeight
+
+ // 鍒涘缓鍦烘櫙
+ scene = new THREE.Scene()
+ scene.background = new THREE.Color(0x1a1a2e)
+
+ // 鍒涘缓鐩告満
+ camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
+ camera.position.set(20, 20, 20)
+ camera.lookAt(0, 0, 0)
+
+ // 鍒涘缓娓叉煋鍣�
+ renderer = new THREE.WebGLRenderer({ antialias: true })
+ renderer.setPixelRatio(window.devicePixelRatio)
+ renderer.setSize(width, height)
+ canvasContainer.value.appendChild(renderer.domElement)
+
+ // 鍒涘缓杞ㄩ亾鎺у埗鍣�
+ controls = new OrbitControls(camera, renderer.domElement)
+ controls.enableDamping = true
+ controls.dampingFactor = 0.05
+
+ // 鍒涘缓鐜鍏�
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
+ scene.add(ambientLight)
+
+ // 鍒涘缓涓诲畾鍚戝厜
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0)
+ directionalLight.position.set(50, 100, 50)
+ scene.add(directionalLight)
+
+ // 鍒涘缓琛ュ厜
+ const fillLight = new THREE.DirectionalLight(0xffffff, 0.3)
+ fillLight.position.set(-50, 50, -50)
+ scene.add(fillLight)
+
+ // 鍒涘缓鍦伴潰
+ const groundGeometry = new THREE.PlaneGeometry(100, 100)
+ const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x2d2d2d })
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial)
+ ground.rotation.x = -Math.PI / 2
+ ground.position.y = -0.5
+ scene.add(ground)
+
+ // 鍒涘缓缃戞牸
+ const gridHelper = new THREE.GridHelper(100, 50)
+ scene.add(gridHelper)
+
+ // 鍒涘缓灏勭嚎妫�娴嬪櫒
+ raycaster = new THREE.Raycaster()
+ mouse = new THREE.Vector2()
+
+ // 娣诲姞鐐瑰嚮浜嬩欢
+ canvasContainer.value.addEventListener('mousedown', onCanvasClick)
+
+ // 寮�濮嬫覆鏌撳惊鐜�
+ animate()
+}
+
+// 娓叉煋璐т綅
+function renderLocations() {
+ if (!scene) return
+ console.log('娓叉煋璐т綅锛屽師濮嬫暟鎹�绘暟:', originalLocationData.length)
+ // 濡傛灉鍘熷鏁版嵁涓虹┖锛屽皾璇曢噸鏂板姞杞�
+ if (originalLocationData.length === 0) {
+ console.warn('鍘熷鏁版嵁涓虹┖锛岄噸鏂板姞杞�...')
+ if (activeWarehouse.value) {
+ loadWarehouseData(activeWarehouse.value)
+ }
+ return
+ }
+
+ // 绉婚櫎鏃х殑璐т綅缃戞牸
+ console.log("馃殌 ~ renderLocations ~ locationMesh:", locationMesh)
+ if (locationMesh) {
+ scene.remove(locationMesh)
+ locationMesh.geometry.dispose()
+ if (Array.isArray(locationMesh.material)) {
+ locationMesh.material.forEach(m => m.dispose())
+ } else {
+ locationMesh.material.dispose()
+ }
+ locationMesh = null
+ }
+
+ // 杩囨护鏁版嵁 - 濮嬬粓浠庡師濮嬪畬鏁存暟鎹繃婊�
+ let filteredData = [...originalLocationData]
+ if (filterStockStatus.value !== null) {
+ filteredData = filteredData.filter(loc => loc.stockStatus === filterStockStatus.value)
+ }
+ if (filterMaterielCode.value) {
+ filteredData = filteredData.filter(loc => loc.materielCode === filterMaterielCode.value)
+ }
+ if (filterBatchNo.value) {
+ filteredData = filteredData.filter(loc => loc.batchNo === filterBatchNo.value)
+ }
+
+ console.log('杩囨护鍚庢暟鎹暟閲�:', filteredData.length, '绛涢�夋潯浠�:', filterStockStatus.value, filterMaterielCode.value, filterBatchNo.value)
+
+ // 鍒涘缓 InstancedMesh
+ const geometry = new THREE.BoxGeometry(1.5, 1, 1.5)
+ const material = new THREE.MeshStandardMaterial({
+ color: 0xffffff,
+ roughness: 0.5,
+ metalness: 0.1
+ })
+
+ // 濡傛灉杩囨护鍚庢棤鏁版嵁锛屽垱寤虹┖鐨� InstancedMesh
+ if (filteredData.length === 0) {
+ locationMesh = new THREE.InstancedMesh(geometry, material, 0)
+ scene.add(locationMesh)
+ return
+ }
+
+ locationMesh = new THREE.InstancedMesh(geometry, material, filteredData.length)
+
+ // 娓呯┖骞堕噸寤烘槧灏�
+ locationIdToInstanceId.clear()
+
+ const dummy = new THREE.Object3D()
+ const color = new THREE.Color()
+
+ filteredData.forEach((location, i) => {
+ const x = (location.column - 1) * 2
+ const y = location.layer * 1.5
+ const z = (location.row - 1) * 2
+
+ dummy.position.set(x, y, z)
+ dummy.updateMatrix()
+ locationMesh.setMatrixAt(i, dummy.matrix)
+
+ // 璁剧疆棰滆壊
+ color.setHex(getLocationColor(location))
+ locationMesh.setColorAt(i, color)
+
+ // 寤虹珛鏄犲皠: locationId -> instanceId (i)
+ locationIdToInstanceId.set(location.locationId, i)
+
+ 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)
+}
+
+// 鍔ㄧ敾寰幆
+function animate() {
+ animationId = requestAnimationFrame(animate)
+ controls.update()
+ renderer.render(scene, camera)
+}
+
+// 鐐瑰嚮鐢诲竷
+function onCanvasClick(event) {
+ if (!canvasContainer.value || !locationMesh) return
+
+ const rect = canvasContainer.value.getBoundingClientRect()
+ mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
+ mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
+
+ raycaster.setFromCamera(mouse, camera)
+ const intersects = raycaster.intersectObject(locationMesh)
+
+ if (intersects.length > 0) {
+ const instanceId = intersects[0].instanceId
+ // 鑾峰彇鍘熷鏁版嵁绱㈠紩锛堣�冭檻杩囨护鍚庣殑鏁版嵁锛�
+ let filteredData = [...originalLocationData]
+ if (filterStockStatus.value !== null) {
+ filteredData = filteredData.filter(loc => loc.stockStatus === filterStockStatus.value)
+ }
+ if (filterMaterielCode.value) {
+ filteredData = filteredData.filter(loc => loc.materielCode === filterMaterielCode.value)
+ }
+ if (filterBatchNo.value) {
+ filteredData = filteredData.filter(loc => loc.batchNo === filterBatchNo.value)
+ }
+
+ if (instanceId < filteredData.length) {
+ selectedLocation.value = filteredData[instanceId]
+ detailDialogVisible.value = true
+ // 鑱氱劍鐩告満
+ focusCamera(selectedLocation.value)
+ }
+ }
+}
+
+// 鑱氱劍鐩告満鍒拌揣浣�
+function focusCamera(location) {
+ if (!camera || !controls) return
+
+ const targetX = (location.column - 1) * 2
+ const targetY = location.layer * 1.5
+ const targetZ = (location.row - 1) * 2
+
+ const offsetX = 5
+ const offsetY = 5
+ const offsetZ = 5
+
+ const targetPosition = new THREE.Vector3(targetX + offsetX, targetY + offsetY, targetZ + offsetZ)
+
+ // 浣跨敤 lerp 骞虫粦绉诲姩
+ const startPosition = camera.position.clone()
+ const duration = 500
+ const startTime = Date.now()
+
+ function lerpMove() {
+ const elapsed = Date.now() - startTime
+ const t = Math.min(elapsed / duration, 1)
+ const easeT = 1 - Math.pow(1 - t, 3) // easeOutCubic
+
+ camera.position.lerpVectors(startPosition, targetPosition, easeT)
+ controls.target.set(targetX, targetY, targetZ)
+
+ if (t < 1) {
+ requestAnimationFrame(lerpMove)
+ }
+ }
+
+ lerpMove()
+}
+
+// 閲嶇疆鐩告満
+function resetCamera() {
+ if (!camera || !controls) return
+ camera.position.set(20, 20, 20)
+ controls.target.set(0, 0, 0)
+}
+
+// 鍒锋柊鏁版嵁
+async function refreshData() {
+ refreshing.value = true
+ try {
+ // 閲嶇疆绛涢�夋潯浠�
+ filterStockStatus.value = null
+ filterMaterielCode.value = null
+ filterBatchNo.value = null
+ // 閲嶆柊鍔犺浇褰撳墠浠撳簱鏁版嵁
+ if (activeWarehouse.value) {
+ await loadWarehouseData(activeWarehouse.value)
+ }
+ } finally {
+ refreshing.value = false
+ }
+}
+
+// 浠撳簱鍒囨崲
+async function onWarehouseChange(warehouseId) {
+ await loadWarehouseData(warehouseId)
+}
+
+// 鐩戝惉绛涢�夊彉鍖�
+watch(filterStockStatus, async () => {
+ await nextTick()
+ if (originalLocationData.length > 0) {
+ renderLocations()
+ }
+})
+watch(filterMaterielCode, async () => {
+ await nextTick()
+ if (originalLocationData.length > 0) {
+ renderLocations()
+ }
+})
+watch(filterBatchNo, async () => {
+ await nextTick()
+ if (originalLocationData.length > 0) {
+ renderLocations()
+ }
+})
+
+// 绐楀彛澶у皬鍙樺寲
+function onWindowResize() {
+ if (!canvasContainer.value || !camera || !renderer) return
+ const width = canvasContainer.value.clientWidth
+ const height = canvasContainer.value.clientHeight
+ camera.aspect = width / height
+ camera.updateProjectionMatrix()
+ renderer.setSize(width, height)
+}
+
+// 缁勪欢鎸傝浇
+onMounted(() => {
+ initThreeJS()
+ loadWarehouseList()
+ initSignalR()
+ window.addEventListener('resize', onWindowResize)
+})
+
+// 缁勪欢鍗歌浇
+onUnmounted(() => {
+ if (animationId) {
+ cancelAnimationFrame(animationId)
+ }
+ if (canvasContainer.value) {
+ canvasContainer.value.removeEventListener('mousedown', onCanvasClick)
+ }
+ window.removeEventListener('resize', onWindowResize)
+ if (renderer) {
+ renderer.dispose()
+ }
+ if (connection) {
+ connection.stop()
+ }
+})
+</script>
+
+<style scoped>
+.stock-chat-container {
+ width: 100%;
+ height: calc(100vh - 120px);
+ position: relative;
+ overflow: visible;
+}
+.toolbar {
+ position: absolute;
+ top: 60px;
+ left: 20px;
+ z-index: 10;
+ display: flex;
+ gap: 10px;
+ background: rgba(255, 255, 255, 0.9);
+ padding: 10px;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
+}
+.canvas-container {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+.legend {
+ position: absolute;
+ bottom: 20px;
+ right: 20px;
+ background: rgba(0, 0, 0, 0.7);
+ padding: 10px;
+ border-radius: 4px;
+ color: white;
+ z-index: 10;
+ max-height: 400px;
+ overflow-y: auto;
+}
+.legend-title {
+ font-weight: bold;
+ font-size: 13px;
+ margin-bottom: 6px;
+ color: #fff;
+}
+.legend-divider {
+ height: 1px;
+ background: rgba(255, 255, 255, 0.3);
+ margin: 8px 0;
+}
+.legend-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 4px;
+ font-size: 12px;
+}
+.color-box {
+ width: 16px;
+ height: 16px;
+ border-radius: 2px;
+ flex-shrink: 0;
+}
+.detail-content {
+ padding: 20px;
+}
+.detail-table {
+ margin-top: 20px;
+}
+.detail-table h4 {
+ margin-bottom: 10px;
+ color: #303133;
+}
+.no-detail {
+ margin-top: 20px;
+}
+
+:deep(.el-drawer__body) {
+ padding: 20px;
+}
+</style>
--
Gitblit v1.9.3