WIDESEA_WMSClient/src/views/stock/stockChat.vue| 技术 | 选型 | 说明 |
|---|---|---|
| 3D引擎 | Three.js | WebGL 主流库,Vue 3 友好 |
| 渲染策略 | InstancedMesh | 批量渲染大量货位,单次 drawcall |
| 状态管理 | Vue 3 Composition API | ref/reactive |
| UI组件 | Element Plus | 与项目现有技术栈一致 |
| 实时通信 | SignalR | 与后端 WebSocket 配合实现库存动态更新 |
颜色判定规则(按优先级顺序):
locationStatus = 3(禁用) → 深灰 #2d2d2d(最高优先级)locationStatus = 2(锁定) → 红色 #F56C6ClocationStatus = 1(占用)且 stockStatus = 0(无货) → 暗灰 #4a4a4alocationStatus = 1(占用)且 stockStatus = 1(有货) → 蓝色 #409EFFlocationStatus = 1(占用)且 stockStatus = 2(库存紧张 <10%) → 橙色 #E6A23ClocationStatus = 1(占用)且 stockStatus = 3(已满 ≥90%) → 绿色 #67C23AlocationStatus = 0(空) → 暗灰 #4a4a4a阈值定义:
- 库存紧张:stockQuantity / maxCapacity < 10%
- 已满:stockQuantity / maxCapacity ≥ 90%
GET /api/StockInfo/Get3DLayout?warehouseId={id}
响应结构:json { "status": true, "data": { "warehouseId": 1, "warehouseName": "主仓库", "maxRow": 10, "maxColumn": 20, "maxLayer": 5, "materielCodeList": ["M001", "M002", "M003"], "batchNoList": ["B20260301", "B20260302"], "locations": [ { "locationId": 1, "locationCode": "A-01-02-03", "row": 1, "column": 2, "layer": 3, "locationStatus": 0, "stockStatus": 2, "stockQuantity": 50, "maxCapacity": 100, "palletCode": "PLT-001", "materielCode": "M001", "materielName": "物料A", "batchNo": "B20260301" } ] } }
说明:
- locationStatus: 0=空, 1=占用, 2=锁定, 3=禁用
- stockStatus: 0=无货, 1=有货, 2=库存紧张, 3=已满
- maxCapacity: 货位最大容量(用于计算填充率)
- materielCodeList: 当前仓库所有物料编号列表(用于筛选下拉)
- batchNoList: 当前仓库所有批次号列表(用于筛选下拉)
- 颜色判定在前端实现:后端返回 stockQuantity 和 maxCapacity,前端按 3.2 规则计算颜色
Hub 路径:/stockHub
推送事件:javascript // 库存变化事件 stockUpdated: { locationId, warehouseId, stockQuantity, stockStatus }
WIDESEA_WMSClient/src/
├── views/stock/
│ └── stockChat.vue # 主页面组件
├── extension/stock/
│ └── stockChat.js # ViewGrid 扩展配置
└── api/
└── http.js # 复用现有 http 封装
<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"
:label="wh.warehouseName"
:name="wh.warehouseId"
/>
</el-tabs>
<!-- 工具栏 -->
<div class="toolbar">
<el-select v-model="filterStockStatus" placeholder="库存状态筛选" clearable>
<el-option label="有货" :value="1" />
<el-option label="库存紧张" :value="2" />
<el-option label="已满" :value="3" />
</el-select>
<el-select v-model="filterMaterielCode" placeholder="物料筛选" clearable>
<el-option v-for="code in materielCodeList" :key="code" :label="code" :value="code" />
</el-select>
<el-select v-model="filterBatchNo" placeholder="批次筛选" clearable>
<el-option v-for="batch in batchNoList" :key="batch" :label="batch" :value="batch" />
</el-select>
<el-button @click="resetCamera">重置视角</el-button>
</div>
<!-- 3D Canvas -->
<div ref="canvasContainer" class="canvas-container" />
<!-- 状态图例 -->
<div class="legend">
<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-dialog v-model="detailDialogVisible" title="库存详情" fullscreen>
<!-- 详情内容 -->
</el-dialog>
</div>
</template>
WebGLRenderer,挂载到 canvasContainerPerspectiveCamera(透视相机)Scene 场景PlaneGeometry + 网格材质)InstancedMesh)OrbitControls 控制器x = (column - maxColumn/2) * CELL_SIZE_X
y = layer * CELL_SIZE_Y
z = (row - maxRow/2) * CELL_SIZE_Z
Raycaster 进行射线检测instanceId 识别被点击的货位实例camera.position.lerp(target, 0.05) 每帧执行| 策略 | 说明 |
|---|---|
| InstancedMesh | 单次 drawcall 渲染所有货位 |
| 视锥体剔除 | 相机外的货位不渲染 |
| 颜色缓存 | 材质复用,避免频繁创建 |
| requestAnimationFrame | 渲染循环使用 RAF |
| 数据分页 | 大仓库可考虑按区域分片加载 |
在 viewGird.js 中注册路由:javascript { path: '/stockChat', name: 'stockChat', component: () => import('@/views/stock/stockChat.vue') }
Get3DLayout API 实现,返回结构见 4.1/stockHub),推送 stockUpdated 事件filterStockStatus、filterMaterielCode、filterBatchNo 三者联动