liulijun
2026-02-06 d49aedf1a71838f3dcb6c762895c601174937c0b
添加库存平面图

添加库存平面图
已修改5个文件
642 ■■■■■ 文件已修改
项目代码/WMS/WMSClient/public/webconfig.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSClient/src/views/Home.vue 463 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSServices/WIDESEA_BasicService/Base/LocationInfoService.cs 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSServices/WIDESEA_IBasicService/ILocationInfoService.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS/WMSServices/WIDESEA_WMSServer/Controllers/Basic/LocationInfoController.cs 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WMS/WMSClient/public/webconfig.js
@@ -1,5 +1,5 @@
window.webConfig = {
    // "webApiBaseUrl": "http://192.168.35.3:9283/",
    //"webApiBaseUrl": "http://192.168.35.3:9283/",
    "webApiBaseUrl": "http://127.0.0.1:9293/",
    "webApiProduction":"http://192.168.35.3:9283/"
}
ÏîÄ¿´úÂë/WMS/WMSClient/src/views/Home.vue
@@ -1,24 +1,463 @@
<template>
  <div class="title"></div>
</template>
    <template>
      <div class="container">
        <div class="content-wrapper">
          <!-- æŽ§åˆ¶é¢æ¿åŒºåŸŸ -->
          <!-- <div class="form-group">
            <label>区域:</label>
            <el-select
              size="mini"
              filterable
              v-model="Area.shelf_code"
              placeholder="请选择"
              class="full-width"
            >
              <el-option
                v-for="item in slectData"
                :value="item.shelf_code"
                :label="item.house_name"
                :key="item.house_name"
              ></el-option>
            </el-select>
          </div> -->
          <div class="form-group">
            <div style="display:flex;flex-direction: column;">
              <h4>仓库</h4>
              <el-select size="mini" @change="onWarehouseChange" v-model="Area.warehouse" placeholder="请选择仓库"
                class="full-width">
                <el-option v-for="item in warehouseList" :value="item" :label="item" :key="item"></el-option>
              </el-select>
              <h4>货位排</h4>
              <el-select size="mini" @change="SCChange" v-model="Area.row" placeholder="请选择排" class="full-width">
                <el-option v-for="item in scList" :value="item" :label="'第' + item + '排'" :key="item"></el-option>
              </el-select>
              <el-button type="success" class="refresh-btn" @click="GetViewData">
                åˆ·æ–°
              </el-button>
            </div>
            <div class="legend-section" style="margin-left: 20px;">
              <h4>说明</h4>
              <div class="legend-grid">
                <div class="legend-item" v-for="item in infoMsg" :key="item.bgcolor">
                  <span class="color-box" :style="{ 'background-color': item.bgcolor }"></span>
                  <span class="legend-label">{{ item.msg }}</span>
                </div>
              </div>
            </div>
          </div>
        </div>
        <!-- è´§ä½å±•示区域 -->
        <div v-if="loading" class="loading-container">
          <el-icon class="is-loading">
            <Loading />
          </el-icon>
          <span>加载中...</span>
        </div>
        <div v-else-if="locationData.length === 0" class="empty-container">
          <el-empty description="暂无数据" />
        </div>
        <div v-else>
          <div class="location-view">
            <div class="layer-container" v-for="(item, index) in locationData" :key="index">
              <h3 class="layer-title">第{{ item.layer }}层</h3>
              <div class="row">
                <div class="location-cell" :style="{ 'background-color': GetBgColor(column) }"
                  v-for="(column, index) in item.locationObj" :key="index" @mouseenter="showTooltip(column, $event)"
                  @mouseleave="hideTooltip">
                  {{ column.row }}-{{ column.column }}-{{ column.layer }}
                </div>
              </div>
            </div>
          </div>
          <!-- æ‚¬æµ®æç¤ºæ¡† -->
          <div v-if="showTooltipFlag" class="location-tooltip" :style="{
            left: tooltipPosition.x + 'px',
            top: tooltipPosition.y + 'px',
          }">
            <div v-if="currentLocation">
              <p><strong>仓库:</strong>{{ Area.warehouse || "未选择" }}</p>
              <p><strong>货位号:</strong>{{ currentLocation.locationCode }}</p>
              <!-- <p><strong>料箱号:</strong>{{ currentLocation.barCode ? currentLocation.barCode : "无料箱" }}</p> -->
              <!-- æ ¹æ®ä»“库类型显示不同的标签和内容 -->
              <p v-if="Area.warehouse === '原料库'">
                <strong>RFID:</strong>{{ this.rfidData[currentLocation.locationCode] || "无" }}
              </p>
              <p v-else-if="Area.warehouse === '成品库'">
                <strong>托盘号:</strong>{{ this.rfidData[currentLocation.locationCode] || "无" }}
              </p>
              <p v-else>
                <strong>标识:</strong>{{ this.rfidData[currentLocation.locationCode] || "无" }}
              </p>
              <p>
                <strong>排列层:</strong> {{ currentLocation.row }}排{{
                  currentLocation.column
                }}列{{ currentLocation.layer }}层
              </p>
              <p><strong>状态:</strong> {{ getStatusText(currentLocation) }}</p>
              <!-- <p>
                <strong>禁用:</strong>
                {{ currentLocation.location_lock == 3 ? "是" : "否" }}
              </p> -->
              <!-- <p v-if="currentLocation.location_state > 0">
                <strong>物料编码:</strong>
                {{ currentLocation.material_code || "无" }}
              </p>
              <p v-if="currentLocation.location_state > 0">
                <strong>数量:</strong> {{ currentLocation.quantity || "无" }}
              </p> -->
            </div>
          </div>
        </div>
      </div>
    </template>
<script>
import { ref, reactive } from 'vue'
import { ElButton, Loading } from "element-plus";
export default {
  setup() {
    return {
  data() {
      return {
        slectData: [],
        scList: [],
        warehouseList: ["原料库", "成品库"],
        warehouseMap: {
          "原料库": 1,
          "成品库": 2
        },
        Area: {
          warehouse: "原料库",
          row: "",
          shelf_code: ""
        },
        mian_height: "",
        loading: false,
        infoMsg: [
          { bgcolor: "lightgreen", msg: "空闲", state: 0 },
          { bgcolor: "orange", msg: "有货", state: 100 },
          { bgcolor: "#2BB3D5", msg: "锁定", state: 1 },
        ],
        locationData: [],
        showTooltipFlag: false,
        currentLocation: null,
        tooltipPosition: { x: 0, y: 0 },
        rfidData: {}, // å­˜å‚¨è´§ä½ç¼–号与RFID的映射关系
      };
    },
  computed: {
    GetBgColor() {
      return (col) => {
        var bgColor = "";
        //优先显示禁用状态
        if (col.location_lock > 0) {
          this.infoMsg.forEach((el) => {
            if (el.state === col.location_lock) {
              bgColor = el.bgcolor;
            }
          });
        }
        else {
          return "lightgreen";
        }
        return bgColor;
      };
    },
  },
  methods: {
    // èŽ·å–RFID信息
    getRfidInfo(locationCodes, warehouseId) {
      if (!locationCodes || locationCodes.length === 0) {
        console.log("没有货位编号,跳过RFID查询");
        return;
      }
      console.log("调用RFID查询API:", {
        locationCodes: locationCodes,
        warehouseId: warehouseId
      });
      this.http
        .post(`/api/LocationInfo/GetRfid`, { locationCodes, warehouseId }, "查询RFID中")
        .then((response) => {
          console.log("RFID查询API返回结果:", response);
          if (response.status && response.data) {
            console.log(`成功获取${response.data.length}条RFID记录`);
            // æ›´æ–°rfidData映射
            response.data.forEach((item, index) => {
              try {
                // æ£€æŸ¥å­—段名,支持大小写两种格式
                const locationCode = item.LocationCode || item.locationCode;
                const rfidCode = item.RfidCode || item.rfidCode;
                if (item && locationCode !== undefined) {
                  console.log(`更新RFID数据: ${locationCode} -> ${rfidCode}`);
                  this.rfidData[locationCode] = rfidCode;
                } else {
                  console.warn(`跳过无效的RFID数据项(${index}):`, item);
                }
              } catch (error) {
                console.error(`处理RFID数据项(${index})时出错:`, error, item);
              }
            });
            console.log("更新后的rfidData:", this.rfidData);
          } else {
            console.log("RFID查询API返回状态失败或数据为空");
          }
        })
        .catch((error) => {
          console.error("获取RFID信息失败:", error);
        });
    },
    GetViewData() {
      var _this = this;
      this.loading = true;
      this.rfidData = {}; // æ¸…空之前的RFID数据
      let warehouseId = 0;
      if (this.Area.warehouse) {
        warehouseId = this.warehouseMap[this.Area.warehouse] || 1;
        console.log(`当前选择的仓库: ${this.Area.warehouse}, å¯¹åº”çš„warehouseId: ${warehouseId}`);
      }
      console.log(`调用GetLocationStatus API: row=${_this.Area.row}, warehouseId=${warehouseId}`);
      this.http
        .post(`/api/LocationInfo/GetLocationStatus?row=${_this.Area.row}&warehouseId=${warehouseId}`, {}, "查询中")
        .then((x) => {
          console.log("GetLocationStatus API返回结果:", x);
          this.locationData = x.data || [];
          // æå–所有货位编号
          let locationCodes = [];
          this.locationData.forEach(layer => {
            layer.locationObj.forEach(location => {
              locationCodes.push(location.locationCode);
            });
          });
          console.log(`从货位数据中提取到${locationCodes.length}个货位编号`);
          console.log("提取的货位编号:", locationCodes);
          // è°ƒç”¨API获取RFID信息
          this.getRfidInfo(locationCodes, warehouseId);
        })
        .catch((error) => {
          this.$message.error("获取数据失败,请重试");
          console.error("获取货位状态失败:", error);
        })
        .finally(() => {
          this.loading = false;
        });
    },
    // åˆ‡æ¢æŽ’
    SCChange() {
      this.GetViewData();
    },
    // åˆ‡æ¢ä»“库
    onWarehouseChange() {
      // åˆ‡æ¢ä»“库时重置排选择
      this.GetViewData();
    },
    showTooltip(location, event) {
      this.currentLocation = location;
      this.showTooltipFlag = true;
      // è®¾ç½®æç¤ºæ¡†ä½ç½®ï¼Œç¨å¾®åç§»é¿å…é®æŒ¡é¼ æ ‡
      this.tooltipPosition = {
        x: event.clientX + 10,
        y: event.clientY + 10,
      };
    },
    hideTooltip() {
      this.showTooltipFlag = false;
      this.currentLocation = null;
    },
    getStatusText(location) {
      // if (location.location_lock === 3) return "禁用";
      if (location.location_lock === 0) return "空闲";
      if (location.location_lock === 1) return "锁定";
      if (location.location_lock === 100) return "有货";
      // if (location.location_state > 0 && location.location_state < 100)
      //   return "锁定";
      return "其他";
    },
    getWarehouseName(location) {
      if (location.warehouseId === 1) return "原料库";
      if (location.warehouseId === 2) return "成品库";
      return "未知仓库";
    },
  },
  mounted() {
    var mainHeight = document.getElementById("vol-main");
    if (mainHeight) {
      this.mian_height = mainHeight.offsetHeight - 40 + "px";
    }
  }
}
    var _this = this;
    //加载下拉选项
    this.http.post("/api/LocationInfo/GetRow", {}, "查询中").then((x) => {
      //加载第一个区域,第一排
      // _this.Area.shelf_code = _this.slectData[0].shelf_code;
      _this.scList = x.data;
      if (_this.scList.length > 0) {
        _this.Area.row = _this.scList[0];
      }
      _this.GetViewData();
    });
  },
  components: { ElButton, Loading },
};
</script>
<style scoped>
.title {
  line-height: 70vh;
.container {
      display: flex;
      flex-direction: column;
      height: 100%;
      width: 100%;
      padding: 10px;
    }
.header {
  text-align: center;
  font-size: 28px;
  color: orange;
  margin-bottom: 20px;
}
.title {
  font-size: 20px;
  font-weight: bold;
  margin: 0;
}
.content-wrapper {
  display: flex;
  flex: 1;
  min-height: 0;
  padding: 10px;
}
.full-width {
  width: 100%;
}
.refresh-btn {
  margin-top: 10px;
  width: 35%;
}
.legend-section h4 {
  margin-bottom: 10px;
}
.legend-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.legend-item {
  display: flex;
  align-items: center;
}
.color-box {
  display: inline-block;
  width: 20px;
  height: 20px;
  margin-right: 8px;
  border-radius: 3px;
}
.legend-label {
  font-size: 13px;
}
.location-view {
      flex: 1;
      width: 100%;
      max-width: 100%;
      overflow: auto;
      padding: 10px;
      background-color: white;
      border-radius: 4px;
    }
.layer-container {
  margin-bottom: 25px;
}
.layer-title {
  margin: 0 0 10px 0;
  font-size: 16px;
  color: #333;
}
.row {
  display: flex;
  flex-wrap: wrap;
  margin-bottom: 8px;
  cursor: pointer;
}
.location-cell {
  width: 80px;
  height: 38px;
  margin: 3px;
  text-align: center;
  font-size: 14px;
  border-radius: 3px;
  line-height: 38px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.location-tooltip {
  position: fixed;
  z-index: 9999;
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 10px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  pointer-events: none;
  max-width: 250px;
}
.location-tooltip p {
  margin: 5px 0;
  font-size: 13px;
  line-height: 1.4;
}
.location-tooltip strong {
  display: inline-block;
  width: 70px;
  color: #666;
}
.form-group {
  display: flex;
}
.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 200px;
  font-size: 16px;
}
.empty-container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 200px;
}
</style>
ÏîÄ¿´úÂë/WMS/WMSServices/WIDESEA_BasicService/Base/LocationInfoService.cs
@@ -32,13 +32,65 @@
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        private readonly IBasicRepository _basicRepository;
        private readonly IStockInfoRepository _stockInfoRepository;
        private readonly IProStockInfoRepository _proStockInfoRepository;
        public ILocationInfoRepository Repository => BaseDal;
        public LocationInfoService(ILocationInfoRepository BaseDal, IUnitOfWorkManage unitOfWorkManage, IBasicRepository basicRepository, IStockInfoRepository stockInfoRepository) : base(BaseDal)
        public LocationInfoService(ILocationInfoRepository BaseDal, IUnitOfWorkManage unitOfWorkManage, IBasicRepository basicRepository, IStockInfoRepository stockInfoRepository, IProStockInfoRepository proStockInfoRepository) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _basicRepository = basicRepository;
            _stockInfoRepository = stockInfoRepository;
            _proStockInfoRepository = proStockInfoRepository;
        }
        /// <summary>
        /// æŸ¥è¯¢è´§ä½å¯¹åº”çš„RFID
        /// </summary>
        /// <param name="locationCodes"></param>
        /// <returns></returns>
        public WebResponseContent GetRfid(string[] locationCodes, int warehouseId = 0)
        {
            try
            {
                // å‚数验证
                if (locationCodes == null || locationCodes.Length == 0)
                {
                    return WebResponseContent.Instance.Error("货位编号不能为空");
                }
                if (warehouseId == 1)
                {
                    // æŸ¥è¯¢åŽŸæ–™åº“Dt_StockInfo表,获取RFID信息
                    var rawMaterialRfidList = _stockInfoRepository.QueryData()
                        .Where(x => locationCodes.Contains(x.LocationCode))
                        .Select(x => new { x.LocationCode, x.RfidCode })
                        .ToList();
                    var rfidList = rawMaterialRfidList.ToList();
                    // è¿”回结果
                    return WebResponseContent.Instance.OK(data: rfidList);
                }
                if (warehouseId == 2)
                {
                    // æŸ¥è¯¢æˆå“åº“Dt_ProStockInfo表,获取PalletCode信息
                    var finishedProductRfidList =_proStockInfoRepository.QueryData()
                        .Where(x => locationCodes.Contains(x.LocationCode))
                        .Select(x => new { x.LocationCode, RfidCode = x.PalletCode })
                        .ToList();
                    var rfidList = finishedProductRfidList.ToList();
                    // è¿”回结果
                    return WebResponseContent.Instance.OK(data: rfidList);
                }
                // å¦‚果没有匹配的仓库ID,返回空列表
                return WebResponseContent.Instance.OK(data: new List<object>());
            }
            catch (Exception ex)
            {
                Console.WriteLine($"GetRfid å¼‚常: {ex.Message}");
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        /// <summary>
ÏîÄ¿´úÂë/WMS/WMSServices/WIDESEA_IBasicService/ILocationInfoService.cs
@@ -16,6 +16,13 @@
    public interface ILocationInfoService : IService<Dt_LocationInfo>
    {
        ILocationInfoRepository Repository { get; }
        /// <summary>
        /// æŸ¥è¯¢è´§ä½RFID
        /// </summary>
        /// <param name="locationCode"></param>
        /// <param name="warehouseId"></param>
        /// <returns></returns>
        WebResponseContent GetRfid(string[] locationCodes, int warehouseId = 0);
        /// <summary>
        /// æ‰¹é‡å¯ç”¨è´§ä½
ÏîÄ¿´úÂë/WMS/WMSServices/WIDESEA_WMSServer/Controllers/Basic/LocationInfoController.cs
@@ -86,5 +86,121 @@
                return WebResponseContent.Instance.Error(e.Message);
            }
        }
        /// <summary>
        /// æŸ¥è¯¢æ¡ä»¶è´§ä½
        /// </summary>
        /// <param name="row"></param>
        /// <param name="warehouseId"></param>
        /// <returns></returns>
        [HttpPost, HttpGet, Route("GetLocationStatus")]
        public WebResponseContent GetLocationStatus(int row, int warehouseId = 0)
        {
            List<int> layers;
            if (warehouseId == 0)
            {
                layers = _repository.QueryData(x => x.Row == row).Select(x => x.Layer).Distinct().ToList();
            }
            else
            {
                layers = _repository.QueryData(x => x.Row == row && x.WarehouseId == warehouseId).Select(x => x.Layer).Distinct().ToList();
            }
            List<object> listObj = new List<object>();
            foreach (var item in layers)
            {
                object locationObj;
                if (warehouseId == 0)
                {
                    locationObj = _repository.QueryData(x => x.Row == row && x.Layer == item)
                        .OrderBy(x => x.Columns)
                        .Select(x => new
                        {
                            layer = x.Layer.ToString().PadLeft(2, '0'),
                            row = x.Row.ToString().PadLeft(2, '0'),
                            column = x.Columns.ToString().PadLeft(2, '0'),
                            locationCode = x.LocationCode,
                            location_lock = x.LocationStatus
                        }).ToList();
                }
                else
                {
                    locationObj = _repository.QueryData(x => x.Row == row && x.Layer == item && x.WarehouseId == warehouseId)
                        .OrderBy(x => x.Columns)
                        .Select(x => new
                        {
                            layer = x.Layer.ToString().PadLeft(2, '0'),
                            row = x.Row.ToString().PadLeft(2, '0'),
                            column = x.Columns.ToString().PadLeft(2, '0'),
                            locationCode = x.LocationCode,
                            location_lock = x.LocationStatus
                        }).ToList();
                }
                object obj = new { layer = item, locationObj };
                listObj.Add(obj);
            }
            return WebResponseContent.Instance.OK("成功", listObj);
        }
        /// <summary>
        /// æŸ¥è¯¢å…¨éƒ¨è´§ä½
        /// </summary>
        /// <returns></returns>
        [HttpPost, HttpGet, Route("GetRow")]
        public WebResponseContent GetRow()
        {
            List<int> listRow = _repository.QueryData().Select(x => x.Row).Distinct().ToList();
            return WebResponseContent.Instance.OK("成功", listRow);
        }
        /// <summary>
        /// æŸ¥è¯¢è´§ä½RFID
        /// </summary>
        /// <param name="requestData"></param>
        /// <returns></returns>
        [HttpPost, Route("GetRfid")]
        public WebResponseContent GetRfid([FromBody] dynamic requestData)
        {
            try
            {
                if (requestData == null)
                {
                    return WebResponseContent.Instance.Error("请求数据为空");
                }
                // æ£€æŸ¥locationCodes字段是否存在且不为null(兼容locationCode)
                if (requestData.locationCodes == null && requestData.locationCode == null)
                {
                    return WebResponseContent.Instance.Error("货位编号数组不能为空");
                }
                // è½¬æ¢ä¸ºstring[],优先使用locationCodes
                string[] locationCode = null;
                var locationCodeField = requestData.locationCodes ?? requestData.locationCode;
                if (locationCodeField is Newtonsoft.Json.Linq.JArray)
                {
                    locationCode = ((Newtonsoft.Json.Linq.JArray)locationCodeField).ToObject<string[]>();
                }
                else
                {
                    return WebResponseContent.Instance.Error("货位编号必须是数组格式");
                }
                // æ£€æŸ¥warehouseId字段是否存在
                int warehouseId = 0;
                if (requestData.warehouseId != null)
                {
                    warehouseId = Convert.ToInt32(requestData.warehouseId);
                }
                return Service.GetRfid(locationCode, warehouseId);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"参数解析失败: {ex.Message}");
            }
        }
    }
}