wanshenmean
2026-03-30 715cf4c8b9e097aae6c4bcaf00bdd67a763529c4
feat(stockChat): 集成 SignalR 实现实时库存更新

- 添加 SignalR Hub 连接管理
- 实现 initSignalR() 初始化库存更新监听
- 实现 updateInstanceColor() 更新单个货位颜色
- 在 onMounted 中调用 initSignalR()
- 在 onUnmounted 中断开 SignalR 连接

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
已修改4个文件
63 ■■■■■ 文件已修改
Code/WMS/WIDESEA_WMSClient/src/views/stock/stockChat.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITask_HtyService.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/Task_HtyService.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/stock/stockChat.vue
@@ -59,8 +59,12 @@
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 = {
@@ -101,6 +105,39 @@
let locationMesh = null
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) {
@@ -386,6 +423,7 @@
onMounted(() => {
  initThreeJS()
  loadWarehouseList()
  initSignalR()
  window.addEventListener('resize', onWindowResize)
})
@@ -401,6 +439,9 @@
  if (renderer) {
    renderer.dispose()
  }
  if (connection) {
    connection.stop()
  }
})
</script>
Code/WMS/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITask_HtyService.cs
@@ -6,6 +6,7 @@
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_DTO.Stock;
@@ -15,4 +16,8 @@
public interface ITask_HtyService : IService<Dt_Task_Hty>
{
    /// <summary>
    /// 获取任务历史仓储接口
    /// </summary>
    IRepository<Dt_Task_Hty> Repository { get; }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -254,7 +254,7 @@
                    {
                        return content.Error($"任务完成失败:MES进站失败: {inboundResult?.Data?.Msg ?? inboundResult?.ErrorMessage ?? "未知错误"}");
                    }
                    return await CompleteTaskAsync(task, "Inbound");
                    return await CompleteTaskAsync(task);
                });
            }
            catch (Exception ex)
@@ -308,7 +308,7 @@
                        return content.Error($"任务完成失败:MES出站失败: {outboundResult?.Data?.Msg ?? outboundResult?.ErrorMessage ?? "未知错误"}");
                    }
                    return await CompleteTaskAsync(task, "Outbound");
                    return await CompleteTaskAsync(task);
                });
            }
            catch (Exception ex)
@@ -354,7 +354,7 @@
                    if (!updateSourceResult || !updateTargetResult || !updateStockResult)
                        return WebResponseContent.Instance.Error("移库任务完成失败");
                    return await CompleteTaskAsync(task, "Relocation");
                    return await CompleteTaskAsync(task);
                });
            }
            catch (Exception ex)
@@ -565,7 +565,7 @@
        /// <summary>
        /// 完成任务后统一处理(删除任务数据)
        /// </summary>
        private async Task<WebResponseContent> CompleteTaskAsync(Dt_Task task, string operateType)
        private async Task<WebResponseContent> CompleteTaskAsync(Dt_Task task, string operateType = "")
        {
            var deleteTaskResult = await BaseDal.DeleteDataAsync(task);
            if (!deleteTaskResult) return WebResponseContent.Instance.Error("任务完成失败");
@@ -573,8 +573,8 @@
            var historyTask = _mapper.Map<Dt_Task_Hty>(task);
            historyTask.InsertTime = DateTime.Now;
            historyTask.OperateType = operateType;
            var saveResult = _task_HtyService.AddData(historyTask);
            if (!saveResult.Status) return WebResponseContent.Instance.Error("任务历史保存失败");
            var saveResult = await _task_HtyService.Repository.AddDataAsync(historyTask) > 0;
            if (!saveResult) return WebResponseContent.Instance.Error("任务历史保存失败");
            return WebResponseContent.Instance.OK("任务完成");
        }
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/Task_HtyService.cs
@@ -11,6 +11,11 @@
    public class Task_HtyService : ServiceBase<Dt_Task_Hty, IRepository<Dt_Task_Hty>>, ITask_HtyService
    {
        /// <summary>
        /// 获取任务历史仓储接口
        /// </summary>
        public IRepository<Dt_Task_Hty> Repository => BaseDal;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="baseDal">基础数据访问对象</param>