#
Junjie
4 天以前 05bd24a1c6753ef482cd6253eb2b2be462fbdb5b
src/main/webapp/views/watch/console.html
@@ -1,95 +1,76 @@
<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8">
      <title>WCS控制中心</title>
      <link rel="stylesheet" href="../../static/css/animate.min.css">
      <link rel="stylesheet" href="../../static/vue/element/element.css">
      <link rel="stylesheet" href="../../static/css/watch/console_vue.css">
      <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
      <script type="text/javascript" src="../../static/layui/layui.js"></script>
      <script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.js"></script>
      <script type="text/javascript" src="../../static/js/common.js"></script>
      <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
      <script type="text/javascript" src="../../static/vue/element/element.js"></script>
   </head>
      <head>
         <meta charset="UTF-8">
         <title>WCS控制中心</title>
         <link rel="stylesheet" href="../../static/css/animate.min.css">
         <link rel="stylesheet" href="../../static/vue/element/element.css">
         <link rel="stylesheet" href="../../static/css/watch/console_vue.css">
         <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
         <script type="text/javascript" src="../../static/layui/layui.js"></script>
         <script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.js"></script>
         <script type="text/javascript" src="../../static/js/common.js"></script>
         <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
         <script type="text/javascript" src="../../static/vue/element/element.js"></script>
         <script src="../../static/js/gsap.min.js"></script>
         <script src="../../static/js/pixi-legacy.min.js"></script>
      </head>
   <body>
      <div id="app">
         <div style="display: flex;margin-top: 30px;margin-left: 20px;">
            <div style="width: 20%;height: 60vh;margin-right: 20px;">
         <div style="display: flex;margin-left: 20px;">
            <div style="width: 20%;height: 60vh;margin-right: 20px;margin-top: 30px;">
               <el-tabs type="border-card" v-model="activateCard" @tab-click="handleCardClick">
                  <el-tab-pane label="堆垛机" name="crn">
                     <watch-crn-card :param="crnParam"></watch-crn-card>
                     <watch-crn-card ref="watchCrnCard" :param="crnParam"></watch-crn-card>
                  </el-tab-pane>
                  <el-tab-pane label="双工位堆垛机" name="dualCrn">
                     <watch-dual-crn-card ref="watchDualCrnCard" :param="dualCrnParam"></watch-dual-crn-card>
                  </el-tab-pane>
                  <el-tab-pane label="输送站" name="devp">
                     <devp-card :param="devpParam"></devp-card>
                     <devp-card ref="devpCard" :param="devpParam"></devp-card>
                  </el-tab-pane>
                  <el-tab-pane label="RGV" name="rgv">
                     <watch-rgv-card :param="rgvParam"></watch-rgv-card>
                     <watch-rgv-card ref="watchRgvCard" :param="rgvParam"></watch-rgv-card>
                  </el-tab-pane>
                  <el-tab-pane label="地图配置" name="mapSetting">
                  <!-- <el-tab-pane label="地图配置" name="mapSetting">
                     <map-setting-card :param="mapSettingParam"></map-setting-card>
                  </el-tab-pane>
                  </el-tab-pane> -->
               </el-tabs>
            </div>
            <div id="mapDataId" style="position: relative;" :style="{zoom: mapSettingParam.zoom / 100}">
               <table class="excel-table">
                  <tr v-for="(row,index) in map" :key="index">
                     <td
                           v-for="(col,idx) in row" :key="idx"
                           :rowspan="col.rowSpan"
                           :colspan="col.colSpan"
                           :style="{width: col.width}"
                           v-if="col.type != 'merge'"
                     >
                        <div v-if="col.type == 'none'">
                           <div class="item" style="visibility: hidden">{{idx}}</div>
                        </div>
                        <div v-else-if="col.type == 'shelf'">
                           <div class="shelf">{{col.shelfIdx}}</div>
                        </div>
                        <div v-else-if="col.type == 'devp'">
                           <div class="site" :style="{height: col.rowPx}" :id="'site-' + getStationId(col.value)" @click="openSite(getStationId(col.value))">{{getStationId(col.value)}}</div>
                        </div>
                        <div v-else-if="col.type == 'rgv'" style="position: relative;">
                           <div class="rgv-item" v-if="getDeviceNo(col.value) != -1" :style="{width: col.width}" :id="'rgv-' + getDeviceNo(col.value)" @click="openRgv(getDeviceNo(col.value))">{{getDeviceNo(col.value)}}</div>
                           <div class="track-item" v-if="getTrackSiteNo(col.value) == -1"></div>
                           <div class="track-item" v-else :id="'rgvTrackSiteNo-' + getTrackSiteNo(col.value)"></div>
                        </div>
                        <div v-else-if="col.type == 'crn'">
                           <div class="crn-item" v-if="getDeviceNo(col.value) != -1" :style="{width: col.width}" :id="'crn-' + getDeviceNo(col.value)" @click="openCrn(getDeviceNo(col.value))">{{getDeviceNo(col.value)}}</div>
                           <div class="track-item" v-if="getTrackSiteNo(col.value) == -1"></div>
                           <div class="track-item" v-else :id="'crnTrackSiteNo-' + getTrackSiteNo(col.value)"></div>
                        </div>
                     </td>
<!--                     <td>-->
<!--                        &lt;!&ndash; 显示行号 &ndash;&gt;-->
<!--                        <div class="item" style="background: none;color: #000;">#{{index+1}}</div>-->
<!--                     </td>-->
                  </tr>
               </table>
            <map-canvas :lev="currentLev" :crn-param="crnParam" :rgv-param="rgvParam" :devp-param="devpParam" @crn-click="openCrn" @dual-crn-click="openDualCrn" @station-click="openSite" style="width: 80%; height: 100vh;"></map-canvas>
            <div style="position: absolute;top: 15px;left: 50%;display: flex;">
               <div v-if="levList.length > 1" v-for="(lev,index) in levList" :key="index" style="margin-right: 10px;">
                  <el-button :type="currentLev == lev ? 'primary' : ''" @click="switchLev(lev)" size="mini">{{ lev }}F</el-button>
               </div>
            </div>
         </div>
      </div>
      <script src="../../components/WatchCrnCard.js"></script>
      <script src="../../components/WatchDualCrnCard.js"></script>
      <script src="../../components/DevpCard.js"></script>
      <script src="../../components/MapSettingCard.js"></script>
      <script src="../../components/WatchRgvCard.js"></script>
      <script src="../../components/MapCanvas.js"></script>
      <script>
         let ws;
         var app = new Vue({
            el: '#app',
            data: {
               map: [],//地图数据
               crnList: [], //堆垛机集合
               levList: [],
               currentLev: 1,
               systemStatus: true,//系统运行状态
               consoleInterval: null,//定时器存储变量
               crnInitPosition: [],
               rgvPosition: [],
               activateCard: 'crn',
               crnParam: {
                  crnNo: 0
               },
               dualCrnParam: {
                  crnNo: 0
               },
               mapSettingParam: {
@@ -100,60 +81,132 @@
               },
               rgvParam: {
                  rgvNo: 0
               }
               },
               locMastData: [],//库位数据
               wsReconnectTimer: null,
               wsReconnectAttempts: 0,
               wsReconnectBaseDelay: 1000,
               wsReconnectMaxDelay: 15000
            },
            created() {
               this.init()
            },
            mounted() {
            },
            beforeDestroy() {
               if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
               if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { try { ws.close(); } catch (e) {} }
            },
            watch: {
            },
            methods: {
                    sendWs(data) {
                        if (ws && ws.readyState === WebSocket.OPEN) {
                            ws.send(data);
                        }
                    },
                    webSocketOnOpen() {
                        console.log("WebSocket连接成功");
                        if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
                        this.wsReconnectAttempts = 0;
                        this.getMap();
                    },
                    webSocketOnError() {
                        console.log("WebSocket连接发生错误");
                        this.scheduleWsReconnect();
                    },
                    webSocketClose() {
                        console.log("WebSocket连接关闭");
                        this.scheduleWsReconnect();
                    },
                    webSocketOnMessage(e) {
                        const result = JSON.parse(e.data);
                        if (result.url == "/crn/table/crn/state") {
                             if(this.$refs.watchCrnCard) {
                                 this.$refs.watchCrnCard.setCrnList(JSON.parse(result.data));
                             }
                        } else if (result.url == "/dualcrn/table/crn/state") {
                             if(this.$refs.watchDualCrnCard) {
                                 this.$refs.watchDualCrnCard.setDualCrnList(JSON.parse(result.data));
                             }
                        } else if (result.url == "/console/latest/data/station") {
                             if(this.$refs.devpCard) {
                                 this.$refs.devpCard.setStationList(JSON.parse(result.data));
                             }
                        } else if (result.url == "/rgv/table/rgv/state") {
                             if(this.$refs.watchRgvCard) {
                                 this.$refs.watchRgvCard.setRgvList(JSON.parse(result.data));
                             }
                        } else if (result.url == "/basMap/lev/" + this.currentLev + "/auth") {
                            // 地图数据
                             let res = JSON.parse(result.data);
                             if (res.code === 200) {
                                 this.map = res.data;
                             }
                        }
                    },
                    getMap() {
                        this.sendWs(JSON.stringify({
                            "url": "/basMap/lev/" + this.currentLev + "/auth",
                            "data": {}
                        }))
                    },
               init() {
                  this.getMap()
                  this.connectWs();
                  this.getSystemRunningStatus() //获取系统运行状态
                  this.consoleInterval = setInterval(() => {
                     this.getCrnInfo() //获取堆垛机数据
                     this.getSiteInfo() //获取输送站点数据
                     this.getRgvInfo() //获取RGV数据
                  }, 1000)
                  this.getLevList() //获取地图层级列表
                  this.getLocMastData() //获取库位数据
               },
               //获取地图数据
               getMap() {
                  let rowPx = 35;
                  let colPx = 35;
               connectWs() {
                  if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { return; }
                  ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket");
                  ws.onopen = this.webSocketOnOpen;
                  ws.onerror = this.webSocketOnError;
                  ws.onmessage = this.webSocketOnMessage;
                  ws.onclose = this.webSocketClose;
               },
               scheduleWsReconnect() {
                  if (this.wsReconnectTimer) { return; }
                  const attempt = this.wsReconnectAttempts + 1;
                  const jitter = Math.floor(Math.random() * 300);
                  const delay = Math.min(this.wsReconnectMaxDelay, this.wsReconnectBaseDelay * Math.pow(2, this.wsReconnectAttempts)) + jitter;
                  this.wsReconnectTimer = setTimeout(() => {
                     this.wsReconnectTimer = null;
                     this.wsReconnectAttempts = attempt;
                     this.connectWs();
                  }, delay);
               },
               getLevList() {
                  let that = this;
                  $.ajax({
                     url: baseUrl + "/basMap/lev/1/auth",
                     url: baseUrl + "/basMap/getLevList",
                     headers: {
                        'token': localStorage.getItem('token')
                     },
                     method: "get",
                     success: (res) => {
                        let data = res.data;
                        let mapData = JSON.parse(data)
                        mapData.forEach((row) => {
                           let shelfIdx = 1;
                           row.forEach((col) => {
                              if (col.type == 'shelf') {
                                 col.shelfIdx = shelfIdx;
                                 shelfIdx++;
                              }
                              col.rowPx = (col.rowSpan * rowPx) + "px";
                              col.colPx = (col.colSpan * colPx) + "px";
                              col.width = (col.cellWidth / 30) + "px";
                           })
                        })
                        this.map = mapData;
                        that.levList = data;
                     }
                  })
               },
               switchLev(lev) {
                  this.currentLev = lev;
                  this.getMap()
                  this.getLocMastData()
               },
               openCrn(id) {
                  this.crnParam.crnNo = id;
                  this.activateCard = 'crn';
                  console.log(id);
               },
               openDualCrn(id) {
                  this.dualCrnParam.crnNo = id;
                  this.activateCard = 'dualCrn';
                  console.log(id);
               },
               openRgv(id) {
                  this.rgvParam.rgvNo = id;
@@ -162,146 +215,6 @@
               openSite(id) {
                  this.devpParam.stationId = id;
                  this.activateCard = 'devp';
               },
               getSiteInfo() {
                  //获取输送站点数据
                  $.ajax({
                     url: baseUrl + "/console/latest/data/station",
                     headers: {'token': localStorage.getItem('token')},
                     method: 'POST',
                     success: function (res) {
                        if (res.code === 200) {
                           var sites = res.data;
                           for (var i = 0; i < sites.length; i++){
                              var siteEl = $("#site-"+sites[i].stationId);
                              siteEl.attr("class", "site " + sites[i].stationStatus);
                              if (sites[i].taskNo != null && sites[i].taskNo>0) {
                                 siteEl.html(sites[i].stationId + "[" + sites[i].taskNo + "]");
                              } else {
                                 siteEl.html(sites[i].stationId);
                              }
                           }
                        } else if (res.code === 403) {
                           parent.location.href = baseUrl + "/login";
                        } else {
                           console.log(res.msg);
                        }
                     }
                  });
               },
               getCrnInfo() {
                  let that = this
                  //获取堆垛机数据
                  $.ajax({
                     url: baseUrl + "/console/latest/data/crn",
                     headers: {'token': localStorage.getItem('token')},
                     method: 'POST',
                     success: function (res) {
                        if (res.code === 200) {
                           var crns = res.data;
                           if (that.crnInitPosition.length == 0) {
                              let position = []
                              for (var i = 0; i < crns.length; i++) {
                                 var crnEl = $("#crn-" + crns[i].crnId);
                                 position.push({
                                    id: crns[i].crnId,
                                    left: crnEl.offset().left + crnEl.width()
                                 })
                              }
                              that.crnInitPosition = position
                              return;
                           }
                           let crnList = []
                           for (var i = 0; i < crns.length; i++) {
                              var crnEl = $("#crn-" + crns[i].crnId);
                              crnEl.attr("class", "crn-item " + crns[i].crnStatus);
                              if (crns[i].bay < 0 || crns[i].bay === -2) {
                                 crns[i].bay = 1
                              }
                              let basePosition = 0;
                              that.crnInitPosition.forEach((item) => {
                                 if (item.id == crns[i].crnId) {
                                    basePosition = item.left
                                 }
                              })
                              var offSet = crns[i].offset;
                              let finalOffset = basePosition + offSet;
                              if (finalOffset < basePosition) {
                                 finalOffset = basePosition;
                              }
                              crnEl.animate({left: finalOffset + 'px'}, 1000);
                              crnList.push({
                                 crnNo: crns[i].crnId,
                                 crnStatus: crns[i].crnStatus
                              })
                           }
                           that.crnList = crnList;
                        } else if (res.code === 403) {
                           parent.location.href = baseUrl + "/login";
                        } else {
                           console.log(res.msg);
                        }
                     }
                  });
               },
               getRgvInfo() {
                  let that = this
                  //获取RGV数据
                  $.ajax({
                     url: baseUrl + "/console/latest/data/rgv",
                     headers: {'token': localStorage.getItem('token')},
                     method: 'POST',
                     success: function (res) {
                        if (res.code === 200) {
                           var rgvs = res.data;
                           if (that.rgvPosition.length == 0) {
                              let position = []
                              for (var i = 0; i < rgvs.length; i++) {
                                 var rgvEl = $("#rgv-" + rgvs[i].rgvNo);
                                 position.push({
                                    id: rgvs[i].rgvNo,
                                    trackSiteNo: rgvs[i].trackSiteNo,
                                    initLeft: rgvEl.offset().left
                                 })
                              }
                              that.rgvPosition = position
                              return;
                           }
                           for (var i = 0; i < rgvs.length; i++) {
                              var rgvEl = $("#rgv-" + rgvs[i].rgvNo);
                              if (rgvs[i].rgvStatus == 'IDLE') {
                                 rgvEl.attr("class", "rgv-item");
                              }else if (rgvs[i].rgvStatus == 'WORKING') {
                                 rgvEl.attr("class", "rgv-item machine-working");
                              }
                              let trackSiteNo = rgvs[i].trackSiteNo;
                              let trackSiteEl = $("#rgvTrackSiteNo-" + trackSiteNo);
                              let parentLeft = rgvEl.offsetParent().offset().left;
                              let targetPosition = trackSiteEl.parent().parent().offset().left - parentLeft;
                              let rgvPosition = rgvEl.position().left;
                              let zoomFactor = that.mapSettingParam.zoom ? (that.mapSettingParam.zoom / 100) : 1;
                              if (zoomFactor <= 0) { zoomFactor = 1; }
                              let finalOffset = targetPosition / zoomFactor;
                              rgvEl.animate({left: finalOffset + "px"}, 500);
                           }
                        } else if (res.code === 403) {
                           parent.location.href = baseUrl + "/login";
                        } else {
                           console.log(res.msg);
                        }
                     }
                  });
               },
               systemSwitch() {
                  // 系统开关
@@ -390,6 +303,38 @@
                     }
                  });
               },
               getCrnTargetCell(crnEl, bay) {
                  if (!crnEl || bay == null) {
                     return null;
                  }
                  let rowEl = crnEl.closest('tr');
                  if (!rowEl || rowEl.length === 0) {
                     return null;
                  }
                  let targetCell = null;
                  let colCounter = 0;
                  let startCount = false;
                  rowEl.find('td').each(function () {
                     // 以当前行中首次出现的track-item为起点进行列计数
                     const isTrackCrn = $(this).find('.track-crn').length > 0;
                     if (!startCount && !isTrackCrn) {
                        return;
                     }
                     let span = parseInt($(this).attr('colspan'), 10);
                     if (isNaN(span) || span < 1) {
                        span = 1;
                     }
                     if (!startCount && isTrackCrn) {
                        startCount = true;
                     }
                     colCounter += span;
                     if (targetCell == null && colCounter >= bay) {
                        targetCell = $(this);
                        return false;
                     }
                  });
                  return targetCell;
               },
               getDeviceNo(obj) {
                  if (this.isJson(obj)) {
                     let data = JSON.parse(obj)
@@ -433,9 +378,48 @@
               },
               handleCardClick(tab, event) {
               }
               },
               //获取库位数据
               getLocMastData() {
                  let that = this;
                  $.ajax({
                     url: baseUrl + "/console/map/locList",
                     headers: {
                        'token': localStorage.getItem('token')
                     },
                     method: "get",
                     data: {},
                     success: (res) => {
                        if (res.code === 200) {
                           that.locMastData = res.data;
                        }
                     }
                  })
               },
               //根据地图坐标获取库位的排列信息
               getShelfLocInfo(rowIdx, colIdx) {
                  if (!this.locMastData || this.locMastData.length === 0) {
                     return '';
                  }
                  // 在locMastData中查找匹配的库位
                  // locType字段存储的是地图坐标信息
                  let locInfo = this.locMastData.find(loc => {
                     if (!loc.locType) return false;
                     // locType格式类似 "0-1-1" (mapX-mapY-lev)
                     let parts = loc.locType.split('-');
                     if (parts.length >= 2) {
                        return parseInt(parts[0]) === rowIdx && parseInt(parts[1]) === colIdx;
                     }
                     return false;
                  });
                  if (locInfo && locInfo.row1 && locInfo.bay1) {
                     return locInfo.row1 + '-' + locInfo.bay1;
                  }
                  return '';
               },
            }
         })
      </script>
   </body>
</html>
         </script>
      </body>
      </html>