台灣地區主要水庫蓄水量報告表 (Node-Red)
https://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx
[{"id":"357295f693e9312c","type":"inject","z":"fc9e6d27522970cd","name":"手動/定時觸發 (每小時)","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"3600","crontab":"","once":true,"onceDelay":"0.1","topic":"","payload":"","payloadType":"date","x":140,"y":100,"wires":[["18457e53ea60c02c"]]},{"id":"18457e53ea60c02c","type":"http request","z":"fc9e6d27522970cd","name":"擷取水庫網頁 (文字模式)","method":"GET","ret":"txt","paytoqs":"ignore","url":"https://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"x":250,"y":140,"wires":[["8d80d24007d7ec6a"]]},{"id":"8d80d24007d7ec6a","type":"function","z":"fc9e6d27522970cd","name":"自定義字串解析 (所有水庫)","func":"// 由於 html 節點可能因複雜的 HTML/Selector 失敗,此處直接使用 RegExp/字串解析。\n\nlet htmlString = msg.payload;\nlet outputMsgs = [];\n\n// *** 包含您要求的所有 21 個水庫名稱 ***\nconst targetReservoirs = [\n '石門水庫', \n '新山水庫', \n '翡翠水庫', \n '寶山第二水庫', \n '永和山水庫', \n '明德水庫', \n '鯉魚潭水庫', \n '德基水庫', \n '石岡壩', \n '霧社水庫', \n '日月潭水庫', \n '集集攔河堰', \n '湖山水庫', \n '仁義潭水庫', \n '白河水庫', \n '烏山頭水庫', \n '曾文水庫', \n '南化水庫', \n '阿公店水庫', \n '高屏溪攔河堰', \n '牡丹水庫'\n];\n\n\n// 步驟 1: 找到表格的內容 (<tr>...</tr>)\nconst tableContentRegex = /<table[^>]*id=\"ctl00_cphMain_gvList\"[^>]*>([\\s\\S]*?)<\\/table>/;\nconst match = htmlString.match(tableContentRegex);\n\nif (!match || match.length < 2) {\n node.warn(\"未找到水庫表格內容。可能網站結構已更改。\");\n return null;\n}\n\nconst tableHTML = match[1];\n\n// 步驟 2: 提取每一行的數據\n// 正則表達式: 尋找 <tr>...</tr> 內部的 <td> 值\nconst rowRegex = /<tr[^>]*>([\\s\\S]*?)<\\/tr>/g;\nlet rowMatch;\n\nwhile ((rowMatch = rowRegex.exec(tableHTML)) !== null) {\n const rowHTML = rowMatch[1];\n \n // 提取所有的 <td> 值\n const tdRegex = /<td[^>]*>([\\s\\S]*?)<\\/td>/g;\n const tdMatches = [...rowHTML.matchAll(tdRegex)].map(m => m[1]);\n \n // 確保有足夠的欄位(至少11個 <td>, 從 0 開始編號)\n if (tdMatches.length < 11) {\n continue; \n }\n \n // 欄位索引 (從 0 開始): 0: 水庫名稱, 1: 有效容量, 10: 蓄水量百分比\n const name = tdMatches[0] ? tdMatches[0].trim() : '';\n const capacityStr = tdMatches[1] ? tdMatches[1].trim() : 'N/A';\n const percentageStr = tdMatches[10] ? tdMatches[10].trim() : '';\n \n // 過濾非目標水庫和附註行 (如'附註')\n if (name && targetReservoirs.includes(name)) {\n \n // 處理百分比 (移除 % 和空格,轉為數字)\n const percentage = parseFloat(percentageStr.replace('%', '').trim());\n \n if (name === '高屏溪攔河堰' || isNaN(percentage)) {\n // 處理無法取得百分比的水庫(例如攔河堰)\n outputMsgs.push({\n topic: name,\n payload: 0, // 儀表板繪圖用,值不重要\n capacity: capacityStr, \n special_status: '無百分比數據' \n });\n }\n else {\n // 正常水庫數據\n outputMsgs.push({\n topic: name,\n payload: percentage, // 百分比數字\n capacity: capacityStr, // 容量文字\n });\n }\n }\n}\n\n// 檢查是否有提取到數據\nif (outputMsgs.length === 0) {\n node.warn(\"自定義解析後未找到目標水庫數據。\");\n}\n\n// 將結果包裝在一個訊息中,輸出到 ui_template\nreturn { payload: outputMsgs };\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":200,"wires":[["3df62c3d986ea509","8f42813483ec6d8c"]]},{"id":"3df62c3d986ea509","type":"ui_template","z":"fc9e6d27522970cd","group":"4d5c1a2b.3e4f5g","name":"儀表板圓形顯示 (21水庫適用)","order":1,"width":24,"height":14,"format":"<style>\n /* 調整整體佈局為彈性佈局 */\n #reservoir_dashboard {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start; \n width: 100%;\n }\n /* 圓形卡片樣式 */\n .reservoir-card {\n display: flex; \n flex-direction: column;\n align-items: center;\n justify-content: space-between;\n margin: 10px; \n width: 150px; /* 寬度略減以容納更多卡片 */\n height: 190px;\n padding: 5px; \n background-color: #fff;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1); \n border-radius: 8px; \n }\n /* 水庫名稱標題 */\n .reservoir-card h3 {\n font-size: 1em; \n font-weight: bold;\n margin: 5px 0;\n text-align: center;\n overflow: hidden; \n white-space: nowrap;\n text-overflow: ellipsis;\n max-width: 100%;\n height: 20px;\n }\n /* 圓形進度條容器 */\n .circular-progress {\n position: relative;\n height: 120px;\n width: 120px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n margin: 5px auto;\n background: conic-gradient(#f0f0f0 360deg, #f0f0f0 360deg);\n }\n /* 圓心白色遮罩 */\n .value-container {\n position: absolute;\n height: 100px;\n width: 100px;\n border-radius: 50%;\n background-color: #fff;\n display: grid;\n place-items: center;\n font-size: 1.2em;\n font-weight: bold;\n box-shadow: inset 0 0 5px rgba(0,0,0,0.05);\n }\n /* 容量資訊文字 */\n .info-text {\n font-size: 0.75em;\n color: #666;\n margin-bottom: 5px;\n height: 15px;\n }\n</style>\n\n<div id=\"reservoir_dashboard\"></div>\n\n<script>\n(function(scope) {\n // 監聽來自 Node-RED 的資料\n scope.$watch('msg', function(msg) {\n if (!msg || !msg.payload || !Array.isArray(msg.payload)) {\n return;\n }\n \n const container = document.getElementById('reservoir_dashboard');\n container.innerHTML = ''; // 清空舊內容\n \n msg.payload.forEach(function(data) {\n const name = data.topic;\n const percentage = data.payload;\n const capacity = data.capacity || 'N/A';\n const specialStatus = data.special_status || null; \n\n let color = '#28a745'; // 預設綠色\n let displayPercentage = percentage.toFixed(1) + '%';\n let gradient = `conic-gradient(${color} ${percentage * 3.6}deg, #e9ecef ${percentage * 3.6}deg)`;\n\n if (specialStatus === '無百分比數據') {\n color = '#6c757d'; // 灰色\n displayPercentage = 'N/A'; \n gradient = `conic-gradient(#6c757d 10deg, #e9ecef 10deg)`; // 顯示一個灰色標記,代表數據不適用\n } else if (percentage < 60) {\n color = '#ffc107'; // 黃色\n } else if (percentage < 30) {\n color = '#dc3545'; // 紅色\n }\n\n const card = document.createElement('div');\n card.className = 'reservoir-card';\n card.innerHTML = `\n <h3>${name}</h3>\n <div class=\"circular-progress\" style=\"background: ${gradient};\">\n <div class=\"value-container\" style=\"color:${color};\">${displayPercentage}</div>\n </div>\n <div class=\"info-text\">${capacity} 萬方</div>\n `;\n \n container.appendChild(card);\n });\n });\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"x":700,"y":200,"wires":[[]]},{"id":"8f42813483ec6d8c","type":"debug","z":"fc9e6d27522970cd","name":"自定義解析輸出檢查","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":680,"y":280,"wires":[]},{"id":"4d5c1a2b.3e4f5g","type":"ui_group","name":"水庫水情儀表板","tab":"1a2b3c4d.5e6f7g","order":1,"disp":true,"width":24,"collapse":false},{"id":"1a2b3c4d.5e6f7g","type":"ui_tab","name":"台灣水庫水情","icon":"water-meter","order":1,"disabled":false,"hidden":false},{"id":"686bc41bd76eb607","type":"global-config","env":[],"modules":{"node-red-dashboard":"3.6.6"}}]







沒有留言:
張貼留言