| <!DOCTYPE html> | 
| <html lang="zh"> | 
| <head> | 
|     <meta charset="UTF-8"> | 
|     <title>设备消息监控</title> | 
|     <style> | 
|         html, body { | 
|             height: 100%; | 
|             margin: 0; | 
|             background-color: #3C3F41; | 
|         } | 
|   | 
|         table { | 
|             width: 100%; | 
|             table-layout: fixed; | 
|             border-collapse: collapse; | 
|         } | 
|   | 
|         td { | 
|             height: inherit; | 
|             border: 1px solid #555; | 
|         } | 
|   | 
|         select { | 
|             width: 210px; | 
|             height: 30px; | 
|             margin: 0 auto 5px auto; | 
|             color: #BABABA; | 
|             font-size: 16px; | 
|             padding: 2px; | 
|             border: 1px solid #646464; | 
|             background-color: #414141; | 
|         } | 
|   | 
|         input { | 
|             width: 100px; | 
|             height: 23px; | 
|             color: #BABABA; | 
|             padding: 2px; | 
|             border: 1px solid #646464; | 
|             background-color: #414141; | 
|         } | 
|   | 
|         button { | 
|             width: 90px; | 
|             color: #BABABA; | 
|             font-size: 18px; | 
|             border: 1px solid #4C708C; | 
|             background-color: #365880; | 
|         } | 
|   | 
|         button:active { | 
|             color: #EEE; | 
|             border: 1px solid #6B96B4; | 
|         } | 
|   | 
|         p { | 
|             color: #BABABA; | 
|             text-align: left; | 
|             white-space: pre-wrap; | 
|             word-wrap: break-word; | 
|             word-break: break-all; | 
|         } | 
|   | 
|         a { | 
|             color: #FFF; | 
|         } | 
|   | 
|         .cell { | 
|             height: inherit; | 
|             text-align: center; | 
|             display: flex; | 
|             flex-direction: column; | 
|         } | 
|   | 
|         .textarea { | 
|             height: inherit; | 
|             overflow: auto; | 
|             background-color: #2B2B2B; | 
|         } | 
|   | 
|         .tool { | 
|             padding: 10px 0 10px 0; | 
|         } | 
|   | 
|         ::-webkit-scrollbar { | 
|             width: 10px; | 
|         } | 
|   | 
|         ::-webkit-scrollbar-thumb { | 
|             background-color: #666; | 
|         } | 
|   | 
|         ::-webkit-scrollbar-thumb:hover { | 
|             background-color: #777; | 
|         } | 
|     </style> | 
| </head> | 
|   | 
| <body> | 
| <table id="dev_box"> | 
|     <tr> | 
|         <td> | 
|             <div class="cell"> | 
|                 <div class="textarea" id="dev_text_[idx]"></div> | 
|                 <div class="tool"> | 
|                     <select id="dev_sel_[idx]">[option]</select> | 
|                     <input type="text" onkeyup="onSearch(this)"> | 
|                     <button type="button" onclick="onSub([idx])">订阅</button> | 
|                     <button type="button" onclick="onUnsub([idx])">取消</button> | 
|                     <button type="button" onclick="$('dev_text_[idx]').innerHTML=''">清空</button> | 
|                     <button type="button" onclick="onExport([idx])">导出</button> | 
|                 </div> | 
|             </div> | 
|         </td> | 
|     </tr> | 
| </table> | 
| <a href="?s=1,2" target="_blank">1*2</a> | 
| <a href="?s=1,4" target="_blank">1*4</a> | 
| <a href="?s=2,6" target="_blank">2*6</a> | 
|   | 
| <script type="text/javascript"> | 
|     const reg = new RegExp("\\[([^\\[\\]]*?)]", 'igm'); //igm是指分别用于指定区分大小写的匹配、全局匹配和多行匹配。 | 
|   | 
|     const host = window.location.host; | 
|     const domain = '//' + host; | 
|   | 
|     let eventSource = null; | 
|     const subscriptions = {}; | 
|   | 
|     const $ = function (id) { | 
|         return document.getElementById(id); | 
|     }; | 
|   | 
|     function getQueryVariable(key) { | 
|         const query = window.location.search.substring(1); | 
|         const paramsArr = query.split("&"); | 
|         for (let i = 0; i < paramsArr.length; i++) { | 
|             const pair = paramsArr[i].split("="); | 
|             if (pair[0] === key) { | 
|                 return pair[1]; | 
|             } | 
|         } | 
|         return null; | 
|     } | 
|   | 
|     const renderForm = function (data) { | 
|         let options = '<option value="">没有可订阅的设备</option>\n'; | 
|         if (data && data.length) { | 
|             options = '<option value="">---请选择订阅设备---</option>\n'; | 
|             options += data.map(function (device) { | 
|                 return `<option value="${device.mobileNo}">${device.mobileNo} - ${device.plateNo}</option>` | 
|             }).join('\n'); | 
|         } | 
|   | 
|         let size = [1, 1]; | 
|         const s = getQueryVariable('s'); | 
|         if (s) | 
|             size = s.split(','); | 
|         const row = size[0] > 3 ? 3 : size[0]; | 
|         const column = size[1] > 6 ? 6 : size[1]; | 
|   | 
|         const height = Math.floor((document.documentElement.clientHeight) / row) - 1; | 
|   | 
|         const table = $('dev_box'); | 
|         const td = table.children[0].children[0].innerHTML; | 
|         let html = ''; | 
|         let idx = 0; | 
|   | 
|         for (let i = 0; i < row; i++) { | 
|             html += `<tr style="height: ${height}px">`; | 
|             for (let j = 0; j < column; j++) { | 
|                 html += td.replace(reg, function (node, key) { | 
|                     return ({idx: idx, option: options})[key] | 
|                 }); | 
|                 ++idx; | 
|             } | 
|             html += '</tr>'; | 
|         } | 
|         table.innerHTML = html; | 
|     }; | 
|   | 
|     function onSearch(e) { | 
|         let value = e.value; | 
|         let select = e.previousElementSibling; | 
|         if (!select.disabled) { | 
|             const options = select.querySelectorAll('option'); | 
|             options[0].selected = true; | 
|             if (value) { | 
|                 options.forEach(option => { | 
|                     if (option.innerHTML.search(value) >= 0) { | 
|                         option.selected = true; | 
|                     } | 
|                 }); | 
|             } | 
|         } | 
|     } | 
|   | 
|     function onExport(idx) { | 
|         const data = $('dev_text_' + idx).innerText; | 
|         if (data) { | 
|             const a = document.createElement('a'); | 
|             a.download = $('dev_sel_' + idx).value + '_' + new Date().toLocaleString().replaceAll(/[^0-9]/g, '_') + '.txt'; | 
|             a.href = 'data:text/plain;base64,' + btoa(unescape(encodeURIComponent(data))); | 
|             a.click(); | 
|         } | 
|     } | 
|   | 
|     function onSub(idx) { | 
|         const select = $('dev_sel_' + idx); | 
|         if (select.disabled) { | 
|             return; | 
|         } | 
|         const clientId = select.value; | 
|         if (!clientId) { | 
|             return; | 
|         } | 
|         if (subscriptions[clientId]) { | 
|             alert(`同一设备[${clientId}]不能重复订阅`); | 
|             return; | 
|         } | 
|   | 
|         select.disabled = true; | 
|         const text = $('dev_text_' + idx); | 
|         text.println = function (data) { | 
|             const c = this.scrollTop + this.offsetHeight + 4; | 
|             const h = this.scrollHeight; | 
|   | 
|             const p = document.createElement('p'); | 
|             p.append(new Date().toLocaleString().replaceAll('/', '-') + ' '); | 
|             p.append(data); | 
|             this.append(p); | 
|   | 
|             if (c >= h) | 
|                 this.scrollTop = this.scrollHeight; | 
|         } | 
|   | 
|         eventSource.addEventListener(clientId, subscriptions[clientId] = function (event) { | 
|             text.println(event.data); | 
|         }); | 
|   | 
|         fetch(`${domain}/device/sse?sub=1&clientId=${clientId}`, {method: 'post'}) | 
|             .then(res => res.json()) | 
|             .then(function (res) { | 
|                 if (res) { | 
|                     text.println('开始订阅'); | 
|                 } else { | 
|                     text.println('订阅失败,请刷新页面'); | 
|                 } | 
|             }).catch(err => text.println('连接服务器失败')); | 
|     } | 
|   | 
|     function onUnsub(idx) { | 
|         const select = $('dev_sel_' + idx); | 
|         const clientId = select.value; | 
|         if (!clientId) { | 
|             return; | 
|         } | 
|   | 
|         fetch(`${domain}/device/sse?sub=0&clientId=${clientId}`, {method: 'post'}) | 
|             .then(res => res.json()) | 
|             .then(function (res) { | 
|                 if (res) { | 
|                     console.log(`取消订阅设备[${clientId}]....`); | 
|                     eventSource.removeEventListener(clientId, subscriptions[clientId]); | 
|                     delete subscriptions[clientId]; | 
|                     select.disabled = false; | 
|                     $('dev_text_' + idx).println('结束订阅'); | 
|                 } | 
|             }).catch(err => console.error(err)); | 
|     } | 
|   | 
|     window.onload = function () { | 
|         fetch(`${domain}/device/option`) | 
|             .then(res => res.json()) | 
|             .then(function (res) { | 
|                 renderForm(res.data); | 
|                 eventSource = new EventSource(`${domain}/device/sse`); | 
|   | 
|                 eventSource.addEventListener('open', function (e) { | 
|                     console.log(`建立连接`); | 
|                 }); | 
|   | 
|                 eventSource.addEventListener('error', function (e) { | 
|                     console.log(`断开连接`); | 
|                 }); | 
|             }).catch(err => console.error(err)); | 
|     } | 
| </script> | 
| </body> | 
| </html> |