yangpeixing
2026-03-13 ca7571548578c32bf0a72b269655f9baec1f6849
WMS/WIDESEA_WMSClient/src/views/Home.vue
@@ -1,5 +1,5 @@
<template>
  <div class="container">
  <div class="container" id="vol-main"> <!-- 新增id,匹配JS中的获取逻辑 -->
    <div class="header">
      <h2 class="title">货位排图</h2>
    </div>
@@ -9,108 +9,73 @@
      <div class="control-panel">
        <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 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.shelf_code"></el-option> <!-- 修复key值,避免重复 -->
          </el-select>
        </div>
        <div class="form-group">
          <label>排:</label>
          <el-select
            size="mini"
            clearable
            filterable
            @change="SCChange"
            v-model="Area.tunnel"
            placeholder="请选择"
            class="full-width"
          >
            <el-option
              v-for="item in scList"
              :value="item"
              :label="item"
              :key="item"
            ></el-option>
          <el-select size="mini" clearable filterable @change="SCChange" v-model="Area.tunnel" placeholder="请选择"
            class="full-width">
            <el-option v-for="item in scList" :value="item" :label="item" :key="item"></el-option>
          </el-select>
        </div>
        <el-button type="success" class="refresh-btn" @click="GetViewData">
          刷新
        </el-button>
        <el-button plain class="notify-trigger-btn" @click="open2">
          警告
        </el-button>
        <div class="legend-section">
          <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 class="legend-item" v-for="item in infoMsg" :key="item.state"> <!-- 修复key值,用state更唯一 -->
              <span class="color-box" :style="{ 'background-color': item.bgcolor }"></span>
              <span class="legend-label">{{ item.msg }} {{ item.quantity }}</span>
            </div>
          </div>
        </div>
        <!-- 饼图容器:修改样式使其居中 -->
        <div class="echarts-container">
          <div ref="myChart" class="chart-inner"></div>
        </div>
      </div>
      <!-- 货位展示区域 -->
      <div class="location-view">
        <div
          class="layer-container"
          v-for="layer in locationData"
          :key="layer.index"
        >
        <!-- 增加无数据提示 -->
        <div v-if="locationData.length === 0" class="empty-tip">暂无货位数据,请选择区域并点击刷新</div>
        <div class="layer-container" v-for="layer in locationData" :key="layer.index">
          <h3 class="layer-title">第{{ layer.index }}层</h3>
          <div class="row" v-for="row in layer.rows" :key="row.index">
            <div
              class="location-cell"
              :style="{ 'background-color': GetBgColor(col) }"
              v-for="col in row.cols"
              :key="col.index"
              @mouseenter="showTooltip(col, $event)"
              @mouseleave="hideTooltip"
            >
            <div class="location-cell" :style="{ 'background-color': GetBgColor(col) }" v-for="col in row.cols"
              :key="col.index" @mouseenter="showTooltip(col, $event)" @mouseleave="hideTooltip">
              {{ row.index }}-{{ col.index }}-{{ layer.index }}
            </div>
          </div>
        </div>
      </div>
      <!-- 悬浮提示框 -->
      <div
        v-if="showTooltipFlag"
        class="location-tooltip"
        :style="{
          left: tooltipPosition.x + 'px',
          top: tooltipPosition.y + 'px',
        }"
      >
      <div v-if="showTooltipFlag" class="location-tooltip" :style="{
        left: tooltipPosition.x + 'px',
        top: tooltipPosition.y + 'px',
      }">
        <div v-if="currentLocation">
          <p><strong>货位号:</strong>{{ currentLocation.locationCode }}</p>
          <p><strong>货位号:</strong>{{ currentLocation.locationCode || '无' }}</p> <!-- 增加默认值 -->
          <p>
            <strong>货位排列层:</strong> {{ currentLocation.row }}排{{
              currentLocation.index
            }}列{{ currentLocation.layer }}层
            <strong>货位排列层:</strong> {{ currentLocation.row || 0 }}排{{
              currentLocation.index || 0
            }}列{{ currentLocation.layer || 0 }}层
          </p>
          <p><strong>状态:</strong> {{ getStatusText(currentLocation) }}</p>
          <p>
            <strong>禁用:</strong>
            {{ currentLocation.location_lock == 3 ? "是" : "否" }}
          </p>
          <!-- 恢复物料信息显示,适配有货状态 -->
          <p v-if="currentLocation.location_state === 2">
            <strong>物料编码:</strong>
            {{ currentLocation.material_code || "无" }}
@@ -125,9 +90,11 @@
</template>
<script>
import { ElButton } from "element-plus";
import { ElButton, ElSelect, ElOption } from "element-plus"; // 完整引入需要的组件
import * as echarts from 'echarts';
export default {
  components: { ElButton, ElSelect, ElOption }, // 注册所有用到的组件
  data() {
    return {
      slectData: [],
@@ -135,123 +102,344 @@
      Area: { house_name: "", tunnel: "", shelf_code: "" },
      mian_height: "",
      infoMsg: [
        { bgcolor: "lightgreen", msg: "空货位", state: 0 },
        { bgcolor: "orange", msg: "有货", state: 2 }, // 关键改:state从100→2
        { bgcolor: "#2BB3D5", msg: "锁定", state: "InAssigned" },
        { bgcolor: "#ccc", msg: "禁用", state: 3 },
        { bgcolor: "#b7ba6b", msg: "其它", state: "else" },
        { bgcolor: "lightgreen", msg: "空货位", state: 0, quantity: 0 },
        { bgcolor: "orange", msg: "有货", state: 2, quantity: 0 },
        { bgcolor: "#2BB3D5", msg: "锁定", state: "InAssigned", quantity: 0 },
        { bgcolor: "#ccc", msg: "禁用", state: 3, quantity: 0 },
        { bgcolor: "#b7ba6b", msg: "其它", state: "else", quantity: 0 },
      ],
      locationData: [],
      showTooltipFlag: false,
      currentLocation: null,
      tooltipPosition: { x: 0, y: 0 },
      chart: null,
      notifyOffset: 0, // 新增:通知偏移量计数器
      notifyHeight: 80, // 通知组件高度(可根据实际调整)
      notifyGap: 15, // 通知之间的间距
    };
  },
  computed: {
    GetBgColor() {
      return (col) => {
        var bgColor = "#b7ba6b";
        //优先显示禁用状态
        // 简化逻辑,提高可读性
        if (col.location_lock == 3) {
          this.infoMsg.forEach((el) => {
            if (el.state == col.location_lock) {
              bgColor = el.bgcolor;
            }
          });
        } else {
          this.infoMsg.forEach((el) => {
            // 关键改:匹配有货状态(2),而非100
            if (col.location_state === 2) {
              bgColor = this.infoMsg.find(item => item.state === 2).bgcolor;
            }
            // 锁定状态(1/10/20/99)逻辑保持不变
            else if (col.location_state > 0 && col.location_state < 100) {
              if (el.state == "InAssigned") {
                bgColor = el.bgcolor;
              }
            }
            // 空货位(0)逻辑保持不变
            else if (el.state == col.location_state) {
              bgColor = el.bgcolor;
            }
          });
          return this.infoMsg.find(item => item.state === 3).bgcolor;
        }
        return bgColor;
        if (col.location_state === 2) {
          return this.infoMsg.find(item => item.state === 2).bgcolor;
        }
        if (col.location_state > 0 && col.location_state < 100) {
          return this.infoMsg.find(item => item.state === "InAssigned").bgcolor;
        }
        if (col.location_state === 0) {
          return this.infoMsg.find(item => item.state === 0).bgcolor;
        }
        return this.infoMsg.find(item => item.state === "else").bgcolor;
      };
    },
    // 新增:将infoMsg转换为饼图需要的数据格式
    chartData() {
      return this.infoMsg.map(item => ({
        name: item.msg,
        value: item.quantity,
        itemStyle: {
          color: item.bgcolor // 让饼图颜色和图例保持一致
        }
      })).filter(item => item.value > 0); // 过滤掉数量为0的项
    }
  },
  watch: {
    //切换库区
    "Area.shelf_code"(newValue, oldValue) {
      if (!newValue) return; // 空值时不执行
      this.scList = [];
      this.slectData.forEach((e) => {
        if (e.shelf_code == newValue) {
          this.Area.tunnel = e.tunnel[0];
          this.scList = e.tunnel;
        }
      });
      this.GetViewData();
      const target = this.slectData.find(e => e.shelf_code === newValue);
      if (target) {
        this.Area.tunnel = target.tunnel?.[0] || ""; // 可选链避免报错
        this.scList = target.tunnel || [];
        this.GetViewData(); // 数据加载完成后再调用
      }
    },
    // 监听chartData变化,更新饼图
    chartData: {
      deep: true,
      handler() {
        this.updateChart();
      }
    },
  },
  methods: {
    GetViewData() {
      var _this = this;
      this.http
        .post("/api/LocationInfoRow/GetLocationStatu", _this.Area, "查询中")
        .then((x) => {
          _this.locationData = x;
          console.log("后端返回:", x);
    async GetViewData() {
      this.warinngNotification();
      // 增加参数校验
      if (!this.Area.shelf_code || !this.Area.tunnel) {
        this.$message?.warning("请先选择区域和排!"); // Element Plus 提示
        return;
      }
      try {
        const res = await this.http.post(
          "/api/LocationInfoRow/GetLocationStatu",
          this.Area,
          "查询中"
        );
        this.locationData = res || [];
        console.log("后端返回:", this.locationData);
        // 重置统计数量
        this.infoMsg.forEach(item => item.quantity = 0);
        // 统计各状态数量
        this.locationData.forEach(layer => {
          (layer.rows || []).forEach(row => {
            (row.cols || []).forEach(col => {
              if (col.location_lock == 3) {
                const item = this.infoMsg.find(el => el.state === 3);
                if (item) item.quantity++;
              } else if (col.location_state === 2) {
                const item = this.infoMsg.find(el => el.state === 2);
                if (item) item.quantity++;
              } else if (col.location_state > 0 && col.location_state < 100) {
                const item = this.infoMsg.find(el => el.state === "InAssigned");
                if (item) item.quantity++;
              } else if (col.location_state === 0) {
                const item = this.infoMsg.find(el => el.state === 0);
                if (item) item.quantity++;
              } else {
                const item = this.infoMsg.find(el => el.state === "else");
                if (item) item.quantity++;
              }
            });
          });
        });
      } catch (error) {
        console.error("获取货位数据失败:", error);
        this.$message?.error("获取数据失败,请重试!");
        this.locationData = [];
      }
    },
    // 切换排
    SCChange() {
      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) {
      // 关键改:明确匹配有货状态(2)
      if (location.location_state === 2) return "有货";
      if (location.location_state === 0) return "空货位";
      if (location.location_state === 1) return "锁定";
      if (location.location_state === 10) return "有货锁定";
      if (location.location_state === 20) return "空闲锁定";
      if (location.location_state === 99) return "大托盘锁定";
      return "其他";
      const stateMap = {
        0: "空货位",
        1: "锁定",
        2: "有货",
        10: "有货锁定",
        20: "空闲锁定",
        99: "大托盘锁定"
      };
      return stateMap[location.location_state] || "其他";
    },
    // 初始化饼图
    initChart() {
      // 正确获取myChart元素
      const chartDom = this.$refs.myChart;
      if (!chartDom) return;
      this.chart = echarts.init(chartDom);
      // 设置饼图基础配置
      const option = {
        // 优化tooltip配置,防止被遮挡
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)', // 显示名称、数量、百分比
          // 关键配置:防止tooltip被遮挡
          zIndex: 99999, // 设置极高的层级,确保在最上层
          confine: true, // 将tooltip限制在图表容器内
          position: function (point, params, dom, rect, size) {
            // 自定义tooltip位置,避免超出容器
            const x = point[0];
            const y = point[1];
            // 计算tooltip的显示位置,优先显示在右侧,超出则显示在左侧
            const ret = {
              left: x + size.contentSize[0] > size.viewSize[0]
                ? (x - size.contentSize[0] - 10) + 'px'
                : (x + 10) + 'px',
              top: y + size.contentSize[1] > size.viewSize[1]
                ? (y - size.contentSize[1] - 10) + 'px'
                : (y + 10) + 'px'
            };
            return ret;
          },
          // 增加tooltip样式,增强视觉效果
          textStyle: {
            fontSize: 12
          },
          backgroundColor: 'rgba(255,255,255,0.95)',
          borderColor: '#ddd',
          borderWidth: 1,
          padding: 8,
          shadowBlur: 5,
          shadowColor: 'rgba(0,0,0,0.1)'
        },
        series: [
          {
            name: '',
            type: 'pie',
            radius: '64%',
            label: {
              show: true,
              position: 'outside', // 标签显示在外部
              formatter: '{b}:{d}%' // 显示名称和数量
            },
            data: this.chartData,
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
              }
            }
          }
        ]
      };
      this.chart.setOption(option);
      // 监听窗口大小变化,自适应图表
      window.addEventListener('resize', () => this.chart?.resize());
    },
    // 更新饼图数据
    updateChart() {
      if (!this.chart) return;
      this.chart.setOption({
        series: [
          {
            data: this.chartData
          }
        ]
      });
    },
    warinngNotification() {
      this.http.post("/api/LocationInfo/LocationWarning", {}, true).then((result) => {
        if (!result.status) {
          this.$Message.$error(x.message);
        } else {
          console.log(result.data)
          result.data.forEach(item => {
            this.open2(item);
          })
          console.log(this.messageList)
        }
      });
    },
    open2(locationName) {
      // 1. 计算当前通知的偏移量
      const currentOffset = this.notifyOffset;
      // 2. 显示通知
      const notifyInstance = this.$notify({
        title: '警告 [' + locationName + ']',
        message: "仓库占有率已达到80%或80%以上",
        type: 'warning',
        duration: 5000,
        offset: currentOffset, // 手动指定偏移量
        // 3. 通知关闭后,重置偏移量(避免后续通知位置错乱)
        onClose: () => {
          this.notifyOffset -= (this.notifyHeight + this.notifyGap);
          // 防止偏移量为负数
          if (this.notifyOffset < 0) this.notifyOffset = 0;
        }
      });
      // 4. 更新下一个通知的偏移量
      this.notifyOffset += (this.notifyHeight + this.notifyGap);
    },
  },
  mounted() {
    var mainHeight = document.getElementById("vol-main");
    this.mian_height = mainHeight.offsetHeight - 40 + "px";
    var _this = this;
    //加载下拉选项
    this.http.get("/api/LocationInfoRow/GetArea", {}, "查询中").then((x) => {
      _this.slectData = x;
      //加载第一个区域,第一排
      _this.Area.shelf_code = _this.slectData[0].shelf_code;
      _this.scList = _this.slectData[0].tunnel;
    // 确保DOM加载完成后获取元素
    this.$nextTick(() => {
      const mainHeight = document.getElementById("vol-main");
      if (mainHeight) {
        this.mian_height = mainHeight.offsetHeight - 40 + "px";
      }
      // this.warinngNotification();
      // 初始化下拉数据
      this.http.get("/api/LocationInfoRow/GetArea", {}, "查询中")
        .then((x) => {
          this.slectData = x || [];
          if (this.slectData.length > 0) {
            this.Area.shelf_code = this.slectData[0].shelf_code;
            this.scList = this.slectData[0].tunnel || [];
            this.Area.tunnel = this.scList[0] || "";
            // 初始化图表
            this.initChart();
          }
        })
        .catch(error => {
          console.error("获取区域数据失败:", error);
          this.$message?.error("加载区域数据失败!");
        });
    });
  },
  components: { ElButton },
  beforeUnmount() {
    // 销毁图表,避免内存泄漏
    if (this.chart) {
      this.chart.dispose();
      this.chart = null;
    }
  },
};
</script>
<style scoped>
/* 样式部分无修改,保持原逻辑 */
/* 原有样式保持不变,新增以下样式 */
#vol-main {
  height: 100vh;
  /* 确保容器有高度 */
  box-sizing: border-box;
}
.empty-tip {
  text-align: center;
  padding: 50px 0;
  color: #999;
  font-size: 14px;
}
/* 关键修改:饼图容器样式,实现在控制面板区域居中 */
.echarts-container {
  margin-top: 20px;
  width: 100%;
  flex: 1;
  /* 让饼图容器占据控制面板剩余空间 */
  display: flex;
  /* Flex布局实现水平+垂直居中 */
  justify-content: center;
  /* 水平居中 */
  align-items: center;
  /* 垂直居中 */
  position: relative;
  z-index: 1;
}
/* 饼图内部容器 */
.chart-inner {
  width: 400px;
  height: 400px;
  /* 饼图实际大小 */
}
/* 新增:给ECharts实例容器增加样式,确保tooltip不被遮挡 */
:deep(.echarts-tooltip) {
  z-index: 99999 !important;
  /* 强制最高层级 */
  pointer-events: none;
  /* 防止tooltip遮挡鼠标事件 */
}
/* 原有样式 */
.container {
  display: flex;
  flex-direction: column;
@@ -277,13 +465,16 @@
}
.control-panel {
  width: 220px;
  width: 320px;
  padding: 15px;
  background-color: #f5f7fa;
  border-radius: 4px;
  margin-right: 15px;
  display: flex;
  flex-direction: column;
  /* 确保控制面板内的元素层级正确 */
  position: relative;
  z-index: 10;
}
.form-group {
@@ -336,6 +527,8 @@
  padding: 10px;
  background-color: white;
  border-radius: 4px;
  /* 设置合理的层级,低于tooltip */
  z-index: 1;
}
.layer-container {
@@ -389,4 +582,8 @@
  width: 70px;
  color: #666;
}
.notify-trigger-btn {
  display: none !important;
}
</style>