| | |
| | | <!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> |
| | | |
| | | <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 id="mapDataId" style="position: relative;margin-top: 50px;" :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"> |
| | | <div class="shelf-loc-info" v-if="getShelfLocInfo(index, idx)">{{getShelfLocInfo(index, idx)}}</div> |
| | | </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 track-crn" v-if="getTrackSiteNo(col.value) == -1"></div> |
| | | <div class="track-item track-crn" v-else :id="'crnTrackSiteNo-' + getTrackSiteNo(col.value)"></div> |
| | | </div> |
| | | </td> |
| | | |
| | | </tr> |
| | | </table> |
| | | </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: { |
| | |
| | | crnParam: { |
| | | crnNo: 0 |
| | | }, |
| | | dualCrnParam: { |
| | | crnNo: 0 |
| | | }, |
| | | mapSettingParam: { |
| | | zoom: 70 |
| | | }, |
| | |
| | | rgvParam: { |
| | | rgvNo: 0 |
| | | }, |
| | | locMastData: []//库位数据 |
| | | 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.getLevList() //获取地图层级列表 |
| | | this.getLocMastData() //获取库位数据 |
| | | |
| | | this.consoleInterval = setInterval(() => { |
| | | this.getCrnInfo() //获取堆垛机数据 |
| | | this.getSiteInfo() //获取输送站点数据 |
| | | this.getRgvInfo() //获取RGV数据 |
| | | }, 1000) |
| | | }, |
| | | 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; |
| | |
| | | } |
| | | }) |
| | | }, |
| | | //获取地图数据 |
| | | getMap() { |
| | | let that = this |
| | | let rowPx = 35; |
| | | let colPx = 35; |
| | | |
| | | $.ajax({ |
| | | url: baseUrl + "/basMap/lev/" + this.currentLev + "/auth", |
| | | 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"; |
| | | }) |
| | | }) |
| | | that.map = mapData; |
| | | } |
| | | }) |
| | | }, |
| | | switchLev(lev) { |
| | | this.currentLev = lev; |
| | | this.getMap() |
| | | this.getLocMastData() //切换层级时重新获取库位数据 |
| | | 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; |
| | |
| | | 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; |
| | | |
| | | 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].taskNo != null && crns[i].taskNo > 0) { |
| | | crnEl.html(crns[i].crnId + "[" + crns[i].taskNo + "]"); |
| | | } else { |
| | | crnEl.html(crns[i].crnId); |
| | | } |
| | | |
| | | let bay = parseInt(crns[i].bay, 10); |
| | | if (isNaN(bay) || bay < 1 || bay === -2) { |
| | | bay = 1; |
| | | } |
| | | |
| | | // 根据列值定位到当前行的目标单元格,避免随机偏移导致的误差 |
| | | let targetCell = that.getCrnTargetCell(crnEl, bay); |
| | | if (!targetCell || targetCell.length === 0) { |
| | | continue; |
| | | } |
| | | if (targetCell.offset() == undefined) { |
| | | continue; |
| | | } |
| | | if (crnEl.offsetParent().offset() == undefined) { |
| | | continue; |
| | | } |
| | | |
| | | let parentLeft = crnEl.offsetParent().offset().left; |
| | | let cellLeft = targetCell.offset().left - parentLeft; |
| | | let offsetWithinCell = (targetCell.outerWidth() - crnEl.outerWidth()) / 2; |
| | | let zoomFactor = that.mapSettingParam.zoom ? (that.mapSettingParam.zoom / 100) : 1; |
| | | if (zoomFactor <= 0) { zoomFactor = 1; } |
| | | |
| | | let finalOffset = (cellLeft + offsetWithinCell) / zoomFactor; |
| | | if(finalOffset < 0) { |
| | | finalOffset = 0; |
| | | } |
| | | |
| | | crnEl.animate({left: finalOffset + 'px'}, 500); |
| | | |
| | | crnList.push({ |
| | | crnNo: crns[i].crnId, |
| | | crnStatus: crns[i].crnStatus |
| | | }) |
| | | } |
| | | } 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); |
| | | if(rgvEl.offset() == undefined) { |
| | | continue; |
| | | } |
| | | 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"); |
| | | }else if (rgvs[i].rgvStatus == 'waiting') { |
| | | rgvEl.attr("class", "rgv-item machine-working"); |
| | | }else { |
| | | rgvEl.attr("class", "rgv-item machine-un-auto"); |
| | | } |
| | | |
| | | let trackSiteNo = rgvs[i].trackSiteNo; |
| | | let trackSiteEl = $("#rgvTrackSiteNo-" + trackSiteNo); |
| | | |
| | | if(rgvEl.offsetParent().offset() == undefined) { |
| | | continue; |
| | | } |
| | | |
| | | 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() { |
| | | // 系统开关 |
| | |
| | | getLocMastData() { |
| | | let that = this; |
| | | $.ajax({ |
| | | url: baseUrl + "/locMast/list/auth", |
| | | url: baseUrl + "/console/map/locList", |
| | | headers: { |
| | | 'token': localStorage.getItem('token') |
| | | }, |
| | | method: "get", |
| | | data: { |
| | | curr: 1, |
| | | limit: 10000, |
| | | lev1: this.currentLev |
| | | }, |
| | | data: {}, |
| | | success: (res) => { |
| | | if (res.code === 200) { |
| | | that.locMastData = res.data.records; |
| | | that.locMastData = res.data; |
| | | } |
| | | } |
| | | }) |
| | |
| | | } |
| | | return false; |
| | | }); |
| | | |
| | | |
| | | if (locInfo && locInfo.row1 && locInfo.bay1) { |
| | | return locInfo.row1 + '-' + locInfo.bay1; |
| | | } |
| | | return ''; |
| | | } |
| | | }, |
| | | } |
| | | }) |
| | | </script> |
| | | </body> |
| | | </html> |
| | | </script> |
| | | </body> |
| | | </html> |