| <!DOCTYPE html> | 
| <html lang="zh-CN"> | 
| <head> | 
|     <meta charset="UTF-8"> | 
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 
|     <title>提升机监控系统</title> | 
|     <link rel="stylesheet" href="../static/vue/element/element.css"> | 
|     <link rel="stylesheet" href="../static/css/shuttle_page.min.css"> | 
|     <script src="../static/js/shuttle_page.js"></script> | 
|     <script type="text/javascript" src="../static/js/jquery/jquery-3.3.1.min.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> | 
|     <style> | 
|         body { | 
|             font-family: 'Noto Sans SC', sans-serif; | 
|             background-color: #0f172a; | 
|             color: #e2e8f0; | 
|         } | 
|         .card { | 
|             background-color: #1e293b; | 
|             border-radius: 0.75rem; | 
|             box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | 
|             transition: all 0.3s ease; | 
|         } | 
|         /*.card:hover {*/ | 
|         /*    transform: translateY(-2px);*/ | 
|         /*    box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2);*/ | 
|         /*}*/ | 
|         .btn { | 
|             background-color: #3b82f6; | 
|             color: white; | 
|             border-radius: 0.5rem; | 
|             padding: 0.5rem 1rem; | 
|             transition: all 0.3s ease; | 
|         } | 
|         .btn:hover { | 
|             background-color: #2563eb; | 
|             transform: translateY(-1px); | 
|         } | 
|         .status-active { | 
|             color: #4ade80; | 
|         } | 
|         .status-inactive { | 
|             color: #f87171; | 
|         } | 
|         .progress-bar { | 
|             height: 0.75rem; | 
|             border-radius: 0.375rem; | 
|             background-color: #334155; | 
|         } | 
|         .progress-fill { | 
|             height: 100%; | 
|             border-radius: 0.375rem; | 
|             background-color: #4ade80; | 
|             transition: width 0.5s ease; | 
|         } | 
|         .table-striped tbody tr:nth-child(odd) { | 
|             background-color: #1e293b; | 
|         } | 
|         .table-striped tbody tr:nth-child(even) { | 
|             background-color: #1a2537; | 
|         } | 
|     </style> | 
| </head> | 
| <body class="min-h-screen"> | 
|     <div id="app"> | 
|         <div class="container mx-auto px-4 py-8"> | 
|             <!-- 顶部标题和状态栏 --> | 
|             <div class="flex flex-col md:flex-row justify-between items-center mb-8"> | 
|                 <h1 class="text-3xl font-bold text-blue-400 mb-4 md:mb-0"> | 
|                     <i class="fas fa-robot mr-2"></i>提升机监控系统 | 
|                 </h1> | 
|                 <div class="flex items-center space-x-4"> | 
|                     <div class="flex items-center"> | 
|                         <div class="w-3 h-3 rounded-full bg-green-500 mr-2"></div> | 
|                         <span>在线: {{ deviceStatusCountMap.online }}</span> | 
|                     </div> | 
|                     <div class="flex items-center"> | 
|                         <div class="w-3 h-3 rounded-full bg-yellow-500 mr-2"></div> | 
|                         <span>故障: {{ deviceStatusCountMap.error }}</span> | 
|                     </div> | 
|                     <div class="flex items-center"> | 
|                         <div class="w-3 h-3 rounded-full bg-red-500 mr-2"></div> | 
|                         <span>离线: {{ deviceStatusCountMap.offline }}</span> | 
|                     </div> | 
|                 </div> | 
|             </div> | 
|   | 
|             <!-- 主要监控区域 --> | 
|             <div class="card p-6 mb-8"> | 
|                 <!-- 提升机状态表格 --> | 
|                 <div class="card p-6"> | 
|                     <div class="flex justify-between items-center mb-4"> | 
|                         <h2 class="text-xl font-semibold text-blue-300"> | 
|                             <i class="fas fa-list-alt mr-2"></i>提升机状态 | 
|                         </h2> | 
|                         <div class="relative"> | 
|                             <select v-model="selectListStatus" class="bg-slate-700 text-white rounded px-3 py-1 pr-8"> | 
|                                 <option value="all">全部状态</option> | 
|                                 <option value="online">在线</option> | 
|                                 <option value="idle">空闲</option> | 
|                                 <option value="running">运行中</option> | 
|                                 <option value="offline">离线</option> | 
|                                 <option value="error">故障</option> | 
|                             </select> | 
|                         </div> | 
|                     </div> | 
|                     <div class="overflow-auto" style="height: 500px;"> | 
|                         <table class="w-full table-striped"> | 
|                             <thead> | 
|                             <tr class="text-left text-slate-300 border-b border-slate-600 bg-slate-800 sticky top-0 z-10"> | 
|                                 <th class="py-3 px-4">编号</th> | 
|                                 <th class="py-3 px-4">工作号</th> | 
|                                 <th class="py-3 px-4">PLC工作号</th> | 
|                                 <th class="py-3 px-4">设备模式</th> | 
|                                 <th class="py-3 px-4">设备状态</th> | 
|                                 <th class="py-3 px-4">任务状态</th> | 
|                                 <th class="py-3 px-4">任务模式</th> | 
|                                 <th class="py-3 px-4">取货数据</th> | 
|                                 <th class="py-3 px-4">放货数据</th> | 
|                                 <th class="py-3 px-4">出入库模式</th> | 
|                                 <th class="py-3 px-4">托盘</th> | 
|                                 <th class="py-3 px-4">小车</th> | 
|                                 <th class="py-3 px-4">故障码</th> | 
|                                 <th class="py-3 px-4">层</th> | 
|                                 <th class="py-3 px-4">站点信息</th> | 
|                             </tr> | 
|                             </thead> | 
|                             <tbody> | 
|                                 <tr v-for="(item,idx) in liftList" :key="idx" class="hover:bg-slate-700"> | 
|                                     <td class="py-3 px-4">{{ item.liftNo }}</td> | 
|                                     <td class="py-3 px-4">{{ item.taskNo }}</td> | 
|                                     <td class="py-3 px-4">{{ item.plcTaskNo }}</td> | 
|                                     <td class="py-3 px-4">{{ item.model$ }}</td> | 
|                                     <td class="py-3 px-4">{{ item.deviceStatus$ }}</td> | 
|                                     <td class="py-3 px-4">{{ item.protocolStatus$ }}</td> | 
|                                     <td class="py-3 px-4">{{ item.taskMode$ }}</td> | 
|                                     <td class="py-3 px-4">{{ item.pick }}</td> | 
|                                     <td class="py-3 px-4">{{ item.put }}</td> | 
|                                     <td class="py-3 px-4">{{ item.iOMode$ }}</td> | 
|                                     <td class="py-3 px-4">{{ item.hasTray ? '有':'无' }}</td> | 
|                                     <td class="py-3 px-4">{{ item.hasCar ? '有':'无' }}</td> | 
|                                     <td class="py-3 px-4">{{ item.errorCode }}</td> | 
|                                     <td class="py-3 px-4">{{ item.lev }}</td> | 
|                                     <td class="py-3 px-4"> | 
|                                         <div v-for="(sta,id) in item.liftStaProtocols" :key="id"> | 
|                                             {{ sta.lev }}层,托盘:{{ sta.hasTray ? 'Y':'N' }},小车:{{ sta.hasCar ? 'Y':'N' }} | 
|                                         </div> | 
|                                     </td> | 
|                                 </tr> | 
|                             </tbody> | 
|                         </table> | 
|                     </div> | 
|                 </div> | 
|             </div> | 
|   | 
|             <!-- 操作控制区域 --> | 
|             <div class="card p-6 mb-8"> | 
|                 <h2 class="text-xl font-semibold text-blue-300 mb-4"> | 
|                     <i class="fas fa-sliders-h mr-2"></i>控制面板 | 
|                 </h2> | 
|                 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-2"> | 
|                     <div class="bg-slate-700 p-4 rounded-lg"> | 
|                         <h3 class="text-lg font-medium mb-2">设备控制</h3> | 
|                         <div class="flex flex-wrap gap-2" style="display: flex;justify-content: center;"> | 
|                             <div> | 
|                                 <el-input @change="changeControlLiftNo" v-model="controlData.liftNo" style="width: 150px;" placeholder="编号"></el-input> | 
|                             </div> | 
|                             <div> | 
|                                 <el-input v-model="controlData.sourceStaNo" style="width: 200px;" placeholder="源站"></el-input> | 
|                             </div> | 
|                             <div> | 
|                                 <el-input v-model="controlData.staNo" style="width: 200px;" placeholder="目标站"></el-input> | 
|                             </div> | 
|                         </div> | 
|                     </div> | 
|                     <div class="bg-slate-700 p-4 rounded-lg"> | 
|                         <h3 class="text-lg font-medium mb-2">设备指令</h3> | 
|                         <div class="flex flex-wrap gap-2" style="display: flex;justify-content: center;"> | 
|                             <button class="btn bg-slate-600 hover:bg-slate-500" @click="liftOperator('movePallet')"> | 
|                                 <i class="fas fa-level-up mr-1"></i>移动托盘 | 
|                             </button> | 
|                             <button class="btn bg-slate-600 hover:bg-slate-500" @click="liftOperator('switchShuttle')"> | 
|                                 <i class="fas fa-level-down mr-1"></i>小车换层 | 
|                             </button> | 
|                             <button class="btn bg-slate-600 hover:bg-slate-500" @click="liftOperator('move')"> | 
|                                 <i class="fas fa-battery-three-quarters mr-1"></i>移动 | 
|                             </button> | 
|                             <button class="btn bg-slate-600 hover:bg-slate-500" @click="liftOperator('reset')"> | 
|                                 <i class="fas fa-home mr-1"></i>复位 | 
|                             </button> | 
|                         </div> | 
|                     </div> | 
|                 </div> | 
|             </div> | 
|   | 
|         </div> | 
|     </div> | 
| </body> | 
|   | 
| <script> | 
|     var app = new Vue({ | 
|         el: '#app', | 
|         data: { | 
|             ws: null, | 
|             liftList: [], //提升机集合 | 
|             liftAllList: [], | 
|             selectListStatus: "all", | 
|             deviceStatusCountMap: {}, | 
|             controlData: { | 
|                 liftNo: "", | 
|                 taskNo: "", | 
|                 sourceLocNo: "", | 
|                 targetLocNo: "" | 
|             }, | 
|             controlLiftInfo: null, | 
|         }, | 
|         created() { | 
|             this.init() | 
|         }, | 
|         watch: { | 
|   | 
|         }, | 
|         methods: { | 
|             init() { | 
|                 this.consoleInterval = setInterval(() => { | 
|                     this.websocketConnect(); | 
|   | 
|                     this.getLiftStateInfo() //获取提升机信息 | 
|                 }, 1000) | 
|             }, | 
|             getLiftStateInfo() { | 
|                 this.sendWs(JSON.stringify({ | 
|                     "url": "/lift/table/lift/state", | 
|                     "data": {} | 
|                 })) | 
|             }, | 
|             setLiftStateInfo(res) { | 
|                 // 提升机信息表获取 | 
|                 if (res.code == 200) { | 
|                     let list = res.data; | 
|   | 
|                     let allList = [] | 
|                     let runningList = [] | 
|                     let idleList = [] | 
|                     let errorList = [] | 
|                     let offlineList = [] | 
|                     let onlineList = [] | 
|   | 
|                     list.forEach((item) => { | 
|                         allList.push(item) | 
|   | 
|                         if(item.protocolStatusType == "WORKING") { | 
|                             runningList.push(item) | 
|                         } | 
|   | 
|                         if(item.protocolStatusType == "IDLE") { | 
|                             idleList.push(item) | 
|                         } | 
|   | 
|                         if(item.errorCode > 0) { | 
|                             errorList.push(item) | 
|                         } | 
|   | 
|                         if (item.protocolStatusType == "NONE") { | 
|                             offlineList.push(item) | 
|                         }else { | 
|                             onlineList.push(item) | 
|                         } | 
|                     }) | 
|   | 
|                     if (this.selectListStatus == "all") { | 
|                         this.liftList = allList; | 
|                     }else if (this.selectListStatus == "running") { | 
|                         this.liftList = runningList; | 
|                     }else if (this.selectListStatus == "idle") { | 
|                         this.liftList = idleList; | 
|                     }else if (this.selectListStatus == "error") { | 
|                         this.liftList = errorList; | 
|                     }else if (this.selectListStatus == "offline") { | 
|                         this.liftList = offlineList; | 
|                     }else if (this.selectListStatus == "online") { | 
|                         this.liftList = onlineList; | 
|                     } | 
|   | 
|                     let tmpMap = { | 
|                         idle: idleList.length, | 
|                         running: runningList.length, | 
|                         error: errorList.length, | 
|                         offline: offlineList.length, | 
|                         online: idleList.length + runningList.length, | 
|                     } | 
|                     this.deviceStatusCountMap = tmpMap; | 
|   | 
|                     this.liftAllList = allList; | 
|                     if (this.controlLiftInfo != null) { | 
|                         allList.forEach((item) => { | 
|                             if(item.liftNo == this.controlLiftInfo.liftNo) { | 
|                                 this.controlLiftInfo = item; | 
|                             } | 
|                         }); | 
|                     } | 
|                 } | 
|             }, | 
|             liftOperator(type) { | 
|                 let that = this; | 
|                 if (this.controlData.liftNo == null || this.controlData.liftNo == "") { | 
|                     this.$message({ | 
|                         message: '请输入设备编号', | 
|                         type: 'warning' | 
|                     }); | 
|                     return; | 
|                 } | 
|   | 
|                 let requestParam = { | 
|                     liftNo: this.controlData.liftNo | 
|                 }; | 
|   | 
|                 if (type == 'movePallet') { | 
|                     if (this.controlData.sourceStaNo == null || this.controlData.sourceStaNo == "") { | 
|                         this.$message({ | 
|                             message: '请输入源站', | 
|                             type: 'warning' | 
|                         }); | 
|                         return; | 
|                     } | 
|   | 
|                     if (this.controlData.staNo == null || this.controlData.staNo == "") { | 
|                         this.$message({ | 
|                             message: '请输入目标站', | 
|                             type: 'warning' | 
|                         }); | 
|                         return; | 
|                     } | 
|   | 
|                     requestParam.liftTaskMode = 1; | 
|                     requestParam.sourceStaNo = this.controlData.sourceStaNo; | 
|                     requestParam.staNo = this.controlData.staNo; | 
|                 }else if (type == 'switchShuttle') { | 
|                     if (this.controlData.sourceStaNo == null || this.controlData.sourceStaNo == "") { | 
|                         this.$message({ | 
|                             message: '请输入源站', | 
|                             type: 'warning' | 
|                         }); | 
|                         return; | 
|                     } | 
|   | 
|                     if (this.controlData.staNo == null || this.controlData.staNo == "") { | 
|                         this.$message({ | 
|                             message: '请输入目标站', | 
|                             type: 'warning' | 
|                         }); | 
|                         return; | 
|                     } | 
|   | 
|                     requestParam.liftTaskMode = 2; | 
|                     requestParam.sourceStaNo = this.controlData.sourceStaNo; | 
|                     requestParam.staNo = this.controlData.staNo; | 
|                 }else if (type == 'move') { | 
|                     if (this.controlData.sourceStaNo == null || this.controlData.sourceStaNo == "") { | 
|                         this.$message({ | 
|                             message: '请输入源站', | 
|                             type: 'warning' | 
|                         }); | 
|                         return; | 
|                     } | 
|   | 
|                     if (this.controlData.staNo == null || this.controlData.staNo == "") { | 
|                         this.$message({ | 
|                             message: '请输入目标站', | 
|                             type: 'warning' | 
|                         }); | 
|                         return; | 
|                     } | 
|   | 
|                     requestParam.liftTaskMode = 3; | 
|                     requestParam.sourceStaNo = this.controlData.sourceStaNo; | 
|                     requestParam.staNo = this.controlData.staNo; | 
|                 }else if (type == 'reset') { | 
|                     requestParam.liftTaskMode = 9996; | 
|                 } | 
|   | 
|                 $.ajax({ | 
|                     url: baseUrl + "/lift/operator/lift", | 
|                     headers: {'token': localStorage.getItem('token')}, | 
|                     method: 'POST', | 
|                     data: requestParam, | 
|                     success: function (res) { | 
|                         if (res.code === 200) { | 
|                             that.$message({ | 
|                                 message: res.msg, | 
|                                 type: 'success' | 
|                             }); | 
|                         } else if (res.code === 403) { | 
|                             window.location.href = baseUrl + "/login"; | 
|                         } else { | 
|                             that.$message({ | 
|                                 message: res.msg, | 
|                                 type: 'warning' | 
|                             }); | 
|                         } | 
|                     } | 
|                 }); | 
|             }, | 
|             changeControlLiftNo() { | 
|                 let liftNo = this.controlData.liftNo; | 
|                 if (liftNo == null || liftNo == "") { | 
|                     this.controlLiftInfo = null | 
|                 } | 
|   | 
|                 this.liftAllList.forEach((item) => { | 
|                     if (item.liftNo == liftNo) { | 
|                         this.controlLiftInfo = item; | 
|                     } | 
|                 }); | 
|             }, | 
|             websocketConnect() { | 
|                 if (this.ws == null) { | 
|                     this.ws = new WebSocket("ws://" + window.location.host + baseUrl + "/console/websocket"); | 
|                     this.ws.onopen = this.webSocketOnOpen | 
|                     this.ws.onerror = this.webSocketOnError | 
|                     this.ws.onmessage = this.webSocketOnMessage | 
|                     this.ws.onclose = this.webSocketClose | 
|                 } | 
|             }, | 
|             webSocketOnOpen(e) { | 
|                 console.log("open"); | 
|             }, | 
|             webSocketOnError(e) { | 
|                 this.ws = null; | 
|                 console.log(e); | 
|             }, | 
|             webSocketOnMessage(e) { | 
|                 const result = JSON.parse(e.data); | 
|                 if (result.url == "/lift/table/lift/state") { | 
|                     this.setLiftStateInfo(JSON.parse(result.data)) | 
|                 } | 
|             }, | 
|             webSocketClose(e) { | 
|                 this.ws = null; | 
|                 console.log("close"); | 
|             }, | 
|             sendWs(message) { | 
|                 if (this.ws == null) { | 
|                     return; | 
|                 } | 
|   | 
|                 if (this.ws.readyState == WebSocket.OPEN) { | 
|                     this.ws.send(message); | 
|                 } | 
|             } | 
|         } | 
|     }) | 
| </script> | 
| </html> |