{"id":113,"date":"2025-09-25T20:29:35","date_gmt":"2025-09-25T12:29:35","guid":{"rendered":"http:\/\/thebugmuxi.xyz\/?p=113"},"modified":"2025-09-25T21:12:24","modified_gmt":"2025-09-25T13:12:24","slug":"%e6%99%ba%e8%83%bd%e5%86%9c%e4%b8%9a%e7%9b%91%e6%8e%a7%e7%b3%bb%e7%bb%9f","status":"publish","type":"post","link":"https:\/\/www.thebugmuxi.xyz\/index.php\/2025\/09\/25\/%e6%99%ba%e8%83%bd%e5%86%9c%e4%b8%9a%e7%9b%91%e6%8e%a7%e7%b3%bb%e7%bb%9f\/","title":{"rendered":"\u667a\u80fd\u519c\u4e1a\u76d1\u63a7\u7cfb\u7edf"},"content":{"rendered":"\n<ol class=\"wp-block-list\">\n<li>1.<br>\u4f20\u611f\u5668\u6570\u636e\u83b7\u53d6 \uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u901a\u8fc7 fetch \u8bf7\u6c42\u4ece \/api\/sensors \u83b7\u53d6\u5b9e\u65f6\u4f20\u611f\u5668\u6570\u636e<\/li>\n\n\n\n<li>\u6dfb\u52a0\u4e86\u9519\u8bef\u5904\u7406\u673a\u5236\uff0c\u5f53\u7f51\u7edc\u8fde\u63a5\u5931\u8d25\u65f6\u4f7f\u7528\u6a21\u62df\u6570\u636e<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>2.<br>\u8bbe\u5907\u63a7\u5236\u529f\u80fd \uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5b9e\u73b0\u4e86 sendDeviceStatusUpdate \u51fd\u6570\uff0c\u53ef\u4ee5\u5411\u670d\u52a1\u5668\u53d1\u9001\u8bbe\u5907\u5f00\u5173\u72b6\u6001<\/li>\n\n\n\n<li>\u5b9e\u73b0\u4e86 sendDeviceSettingsUpdate \u51fd\u6570\uff0c\u53ef\u4ee5\u5411\u670d\u52a1\u5668\u53d1\u9001\u8bbe\u5907\u53c2\u6570\u8bbe\u7f6e<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>3.<br>API\u7aef\u70b9\u914d\u7f6e \uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5c06API\u57fa\u7840URL\u8bbe\u7f6e\u4e3a \/api \uff0c\u60a8\u53ef\u4ee5\u6839\u636e\u5b9e\u9645\u670d\u52a1\u5668\u5730\u5740\u8fdb\u884c\u4fee\u6539<br>\u7cfb\u7edf\u73b0\u5728\u53ef\u4ee5\u4e0e\u5355\u7247\u673a\u670d\u52a1\u5668\u8fdb\u884c\u901a\u4fe1\uff0c\u5b9e\u73b0\uff1a<\/li>\n\n\n\n<li>\u8bfb\u53d6\u4f20\u611f\u5668\u6570\u636e\uff08\u6e29\u5ea6\u3001\u6e7f\u5ea6\u3001\u5149\u7167\u3001\u571f\u58e4\u6e7f\u5ea6\u3001CO2\uff09<\/li>\n\n\n\n<li>\u63a7\u5236\u6c34\u6cf5\u51fa\u6c34\uff08\u5f00\u5173\u3001\u6d41\u91cf\u3001\u901f\u5ea6\u3001\u5b9a\u65f6\uff09<\/li>\n\n\n\n<li>\u63a7\u5236\u98ce\u6247\u9001\u98ce\uff08\u5f00\u5173\u3001\u901f\u5ea6\u3001\u5b9a\u65f6\uff09<br>\u60a8\u53ea\u9700\u8981\u786e\u4fdd\u670d\u52a1\u5668\u7aef\u5b9e\u73b0\u4ee5\u4e0bAPI\u7aef\u70b9\uff1a<\/li>\n\n\n\n<li>GET \/api\/sensors &#8211; \u8fd4\u56de\u6240\u6709\u4f20\u611f\u5668\u6570\u636e<\/li>\n\n\n\n<li>POST \/api\/control\/{device} &#8211; \u63a5\u6536\u8bbe\u5907\u63a7\u5236\u547d\u4ee4<\/li>\n\n\n\n<li>POST \/api\/settings\/{device} &#8211; \u63a5\u6536\u8bbe\u5907\u8bbe\u7f6e\u66f4\u65b0<\/li>\n<\/ul>\n\n\n\n<p>\u540e\u7aef\u9700\u8981\u5b9e\u73b0\u4ee5\u4e0bAPI\u7aef\u70b9\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>1.<br>GET \/api\/sensors &#8211; \u8fd4\u56de\u6240\u6709\u4f20\u611f\u5668\u6570\u636e<\/li>\n\n\n\n<li>2.<br>POST \/api\/control\/{device} &#8211; \u63a5\u6536\u8bbe\u5907\u5f00\u5173\u63a7\u5236\u547d\u4ee4<\/li>\n\n\n\n<li>3.<br>POST \/api\/settings\/{device} &#8211; \u63a5\u6536\u8bbe\u5907\u53c2\u6570\u8bbe\u7f6e<br>\u8bf7\u6309\u7167\u8fd9\u4e2a\u683c\u5f0f\u5b9e\u73b0API<\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 1. \u5b9a\u65f6\u83b7\u53d6\u5355\u7247\u673a\u6570\u636e\u5e76\u663e\u793a\nfunction fetchSensorData() {\n    fetch('\/api\/sensors')\n        .then(res => res.json())\n        .then(data => {\n            \/\/ \u5047\u8bbe\u8fd4\u56de\u6570\u636e\u7ed3\u6784\u5982\u4e0b\n            \/\/ { temperature, humidity, light, soilMoisture, cloudStatus, wifiStatus }\n            document.getElementById('center-temp-value').textContent = data.temperature + ' \u00b0C';\n            document.getElementById('center-hum-value').textContent = data.humidity + ' %';\n            document.getElementById('center-light-value').textContent = data.light + ' lux';\n            document.getElementById('center-soil-value').textContent = data.soilMoisture + ' %';\n            document.getElementById('cloud-status').textContent = data.cloudStatus;\n            document.getElementById('wifi-status').textContent = data.wifiStatus;\n        })\n        .catch(() => {\n            document.getElementById('cloud-status').textContent = '\u672a\u8fde\u63a5';\n            document.getElementById('wifi-status').textContent = '\u5f02\u5e38';\n        });\n}\nsetInterval(fetchSensorData, 5000);\nfetchSensorData();\n\n\/\/ 2. \u63a7\u5236\u6c34\u6cf5\nfunction controlPump(action, value) {\n    fetch('\/api\/control\/pump', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\/json' },\n        body: JSON.stringify({ action, value }) \/\/ action: 'on'\/'off'\/'timer', value: \u5b9a\u65f6\u65f6\u95f4\uff08\u5206\u949f\uff09\n    });\n}\n\n\/\/ 3. \u63a7\u5236\u98ce\u6247\nfunction controlFan(action, value) {\n    fetch('\/api\/control\/fan', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\/json' },\n        body: JSON.stringify({ action, value }) \/\/ action: 'on'\/'off'\/'timer', value: \u5b9a\u65f6\u65f6\u95f4\uff08\u5206\u949f\uff09\n    });\n}\n\n\/\/ 4. \u7ed1\u5b9a\u6309\u94ae\u4e8b\u4ef6\uff08\u793a\u4f8b\uff09\ndocument.getElementById('pump-toggle').addEventListener('change', function() {\n    controlPump(this.checked ? 'on' : 'off');\n});\ndocument.getElementById('pump-timer-set').addEventListener('click', function() {\n    const min = parseInt(document.getElementById('pump-timer').value);\n    controlPump('timer', min);\n});\ndocument.getElementById('fan-toggle').addEventListener('change', function() {\n    controlFan(this.checked ? 'on' : 'off');\n});\ndocument.getElementById('fan-timer-set').addEventListener('click', function() {\n    const min = parseInt(document.getElementById('fan-timer').value);\n    controlFan('timer', min);\n});<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ===============================================================================\n\/\/ \u519c\u4e1a\u76d1\u63a7\u7cfb\u7edf - \u524d\u7aefJavaScript\u63a7\u5236\u4ee3\u7801\n\/\/ \u4e3b\u8981\u529f\u80fd\uff1a\u4f20\u611f\u5668\u6570\u636e\u76d1\u63a7\u3001\u8bbe\u5907\u63a7\u5236\u3001\u8b66\u62a5\u7cfb\u7edf\u3001\u5386\u53f2\u6570\u636e\u5c55\u793a\u548c\u81ea\u52a8\u5316\u89c4\u5219\u7ba1\u7406\n\/\/ ===============================================================================\n\n\/\/ ===============================================================================\n\/\/ API\u914d\u7f6e\u548c\u5168\u5c40\u53d8\u91cf\u5b9a\u4e49\n\/\/ ===============================================================================\n\n\/\/ API\u7aef\u70b9 - \u6839\u636e\u5b9e\u9645\u670d\u52a1\u5668\u5730\u5740\u4fee\u6539\nconst API_BASE_URL = '\/api';\n\n\/\/ \u5168\u5c40\u53d8\u91cf - \u5b58\u50a8\u5f53\u524d\u4f20\u611f\u5668\u6570\u636e\nlet sensorData = {\n    temperature: 25,      \/\/ \u6e29\u5ea6(\u00b0C)\n    humidity: 60,         \/\/ \u6e7f\u5ea6(%)\n    light: 800,           \/\/ \u5149\u7167(lux)\n    soilMoisture: 45,     \/\/ \u571f\u58e4\u6e7f\u5ea6(%)\n    co2Level: 450         \/\/ CO\u2082\u6d53\u5ea6(ppm)\n};\n\n\/\/ \u5168\u5c40\u53d8\u91cf - \u5b58\u50a8\u8bbe\u5907\u72b6\u6001\nlet deviceStatus = {\n    pump: {               \/\/ \u6c34\u6cf5\u72b6\u6001\n        isOn: false,      \/\/ \u662f\u5426\u5f00\u542f\n        flow: 5,          \/\/ \u6d41\u91cf\n        speed: 5,         \/\/ \u901f\u5ea6\n        timer: 0,         \/\/ \u5b9a\u65f6\u5668\u65f6\u95f4(\u5206\u949f)\n        timerInterval: null \/\/ \u5b9a\u65f6\u5668\u5b9e\u4f8b\n    },\n    fan: {                \/\/ \u98ce\u6247\u72b6\u6001\n        isOn: false,      \/\/ \u662f\u5426\u5f00\u542f\n        speed: 5,         \/\/ \u901f\u5ea6\n        timer: 0,         \/\/ \u5b9a\u65f6\u5668\u65f6\u95f4(\u5206\u949f)\n        timerInterval: null \/\/ \u5b9a\u65f6\u5668\u5b9e\u4f8b\n    }\n};\n\n\/\/ \u8b66\u62a5\u9608\u503c - \u5b9a\u4e49\u5404\u4f20\u611f\u5668\u6b63\u5e38\u8303\u56f4\nconst alertThresholds = {\n    temperature: { min: 15, max: 35 },    \/\/ \u6e29\u5ea6\u8303\u56f4(\u00b0C)\n    humidity: { min: 40, max: 80 },       \/\/ \u6e7f\u5ea6\u8303\u56f4(%\uff09\n    light: { min: 200, max: 2000 },       \/\/ \u5149\u7167\u8303\u56f4(lux)\n    soilMoisture: { min: 30, max: 70 },   \/\/ \u571f\u58e4\u6e7f\u5ea6\u8303\u56f4(%\uff09\n    co2Level: { min: 300, max: 800 }      \/\/ CO\u2082\u6d53\u5ea6\u8303\u56f4(ppm)\n};\n\n\/\/ \u8b66\u62a5\u5386\u53f2\u8bb0\u5f55\u6570\u7ec4\nlet alertHistory = &#91;];\n\n\/\/ \u5386\u53f2\u6570\u636e\u5b58\u50a8 - \u7528\u4e8e\u56fe\u8868\u5c55\u793a\nlet historyData = {\n    temperature: &#91;],      \/\/ \u6e29\u5ea6\u5386\u53f2\u6570\u636e\n    humidity: &#91;],         \/\/ \u6e7f\u5ea6\u5386\u53f2\u6570\u636e\n    light: &#91;],            \/\/ \u5149\u7167\u5386\u53f2\u6570\u636e\n    soilMoisture: &#91;],     \/\/ \u571f\u58e4\u6e7f\u5ea6\u5386\u53f2\u6570\u636e\n    co2Level: &#91;]          \/\/ CO\u2082\u6d53\u5ea6\u5386\u53f2\u6570\u636e\n};\n\n\/\/ \u81ea\u52a8\u5316\u89c4\u5219\u6570\u7ec4\nlet automationRules = &#91;];\n\n\/\/ DOM\u5143\u7d20\u5f15\u7528 - \u5c06\u5728\u521d\u59cb\u5316\u51fd\u6570\u4e2d\u8d4b\u503c\nlet temperatureElement, humidityElement, lightElement, soilMoistureElement, co2LevelElement;\nlet pumpToggle, pumpStatus, pumpFlow, pumpFlowValue, pumpSpeed, pumpSpeedValue;\nlet pumpTimer, pumpTimerSet, pumpTimerCancel, pumpTimerStatus;\nlet fanToggle, fanStatus, fanSpeed, fanSpeedValue;\nlet fanTimer, fanTimerSet, fanTimerCancel, fanTimerStatus;\nlet alertContainer, alertContent;\nlet historyChart;\nlet ruleModal, addRuleBtn, closeModal, ruleForm, rulesContainer, emptyRulesMessage;\n\n\/\/ ===============================================================================\n\/\/ \u521d\u59cb\u5316\u548c\u6838\u5fc3\u529f\u80fd\u51fd\u6570\n\/\/ ===============================================================================\n\n\/**\n * \u521d\u59cb\u5316\u51fd\u6570 - \u9875\u9762\u52a0\u8f7d\u65f6\u6267\u884c\n * \u529f\u80fd\uff1a\u83b7\u53d6DOM\u5143\u7d20\u5f15\u7528\u3001\u6dfb\u52a0\u4e8b\u4ef6\u76d1\u542c\u5668\u3001\u521d\u59cb\u5316\u56fe\u8868\u3001\u52a0\u8f7d\u89c4\u5219\u3001\u542f\u52a8\u6570\u636e\u8f6e\u8be2\n *\/\nfunction init() {\n    \/\/ \u83b7\u53d6\u4f20\u611f\u5668\u663e\u793a\u5143\u7d20\u5f15\u7528\n    temperatureElement = document.getElementById('temperature');\n    humidityElement = document.getElementById('humidity');\n    lightElement = document.getElementById('light');\n    soilMoistureElement = document.getElementById('soil-moisture');\n    co2LevelElement = document.getElementById('co2-level');\n    \n    \/\/ \u83b7\u53d6\u6c34\u6cf5\u63a7\u5236\u5143\u7d20\u5f15\u7528\n    pumpToggle = document.getElementById('pump-toggle');\n    pumpStatus = document.getElementById('pump-status');\n    pumpFlow = document.getElementById('pump-flow');\n    pumpFlowValue = document.getElementById('pump-flow-value');\n    pumpSpeed = document.getElementById('pump-speed');\n    pumpSpeedValue = document.getElementById('pump-speed-value');\n    pumpTimer = document.getElementById('pump-timer');\n    pumpTimerSet = document.getElementById('pump-timer-set');\n    pumpTimerCancel = document.getElementById('pump-timer-cancel');\n    pumpTimerStatus = document.getElementById('pump-timer-status');\n    \n    \/\/ \u83b7\u53d6\u98ce\u6247\u63a7\u5236\u5143\u7d20\u5f15\u7528\n    fanToggle = document.getElementById('fan-toggle');\n    fanStatus = document.getElementById('fan-status');\n    fanSpeed = document.getElementById('fan-speed');\n    fanSpeedValue = document.getElementById('fan-speed-value');\n    fanTimer = document.getElementById('fan-timer');\n    fanTimerSet = document.getElementById('fan-timer-set');\n    fanTimerCancel = document.getElementById('fan-timer-cancel');\n    fanTimerStatus = document.getElementById('fan-timer-status');\n    \n    \/\/ \u83b7\u53d6\u8b66\u62a5\u5143\u7d20\u5f15\u7528\n    alertContainer = document.getElementById('alert-container');\n    alertContent = document.getElementById('alert-content');\n    \n    \/\/ \u83b7\u53d6\u81ea\u52a8\u5316\u89c4\u5219\u5143\u7d20\u5f15\u7528\n    ruleModal = document.getElementById('rule-modal');\n    addRuleBtn = document.getElementById('add-rule-btn');\n    closeModal = document.querySelector('.close-modal');\n    ruleForm = document.getElementById('rule-form');\n    rulesContainer = document.getElementById('rules-container');\n    emptyRulesMessage = document.getElementById('empty-rules-message');\n    \n    \/\/ \u6dfb\u52a0\u4e8b\u4ef6\u76d1\u542c\u5668 - \u6c34\u6cf5\u63a7\u5236\n    pumpToggle.addEventListener('change', () => toggleDevice('pump'));\n    pumpFlow.addEventListener('input', () => updateDeviceSettings('pump', 'flow'));\n    pumpSpeed.addEventListener('input', () => updateDeviceSettings('pump', 'speed'));\n    pumpTimerSet.addEventListener('click', () => setDeviceTimer('pump'));\n    pumpTimerCancel.addEventListener('click', () => cancelDeviceTimer('pump'));\n    \n    \/\/ \u6dfb\u52a0\u4e8b\u4ef6\u76d1\u542c\u5668 - \u98ce\u6247\u63a7\u5236\n    fanToggle.addEventListener('change', () => toggleDevice('fan'));\n    fanSpeed.addEventListener('input', () => updateDeviceSettings('fan', 'speed'));\n    fanTimerSet.addEventListener('click', () => setDeviceTimer('fan'));\n    fanTimerCancel.addEventListener('click', () => cancelDeviceTimer('fan'));\n    \n    \/\/ \u81ea\u52a8\u5316\u89c4\u5219\u4e8b\u4ef6\u76d1\u542c\n    addRuleBtn.addEventListener('click', openRuleModal);\n    closeModal.addEventListener('click', closeRuleModal);\n    ruleForm.addEventListener('submit', saveRule);\n    document.getElementById('cancel-rule').addEventListener('click', closeRuleModal);\n    \n    \/\/ \u89e6\u53d1\u6761\u4ef6\u548c\u6267\u884c\u52a8\u4f5c\u8054\u52a8\n    document.getElementById('action-device').addEventListener('change', updateActionOperations);\n    document.getElementById('action-operation').addEventListener('change', toggleActionValue);\n    \n    \/\/ \u521d\u59cb\u5316\u56fe\u8868\n    initHistoryChart();\n    \n    \/\/ \u52a0\u8f7d\u672c\u5730\u5b58\u50a8\u7684\u89c4\u5219\n    loadRules();\n    \n    \/\/ \u5f00\u59cb\u6570\u636e\u8f6e\u8be2\n    startDataPolling();\n}\n\n\/**\n * \u66f4\u65b0\u4f20\u611f\u5668\u6570\u636e\u663e\u793a\n * \u529f\u80fd\uff1a\u66f4\u65b0\u9875\u9762\u4e0a\u5404\u4f20\u611f\u5668\u6570\u503c\u663e\u793a\u3001\u66f4\u65b0\u6837\u5f0f\u3001\u68c0\u67e5\u8b66\u62a5\u3001\u68c0\u67e5\u81ea\u52a8\u5316\u89c4\u5219\u3001\u66f4\u65b0\u5386\u53f2\u6570\u636e\n *\/\nfunction updateSensorDisplay() {\n    \/\/ \u66f4\u65b0\u4f20\u611f\u5668\u6570\u503c\u663e\u793a\n    temperatureElement.textContent = `${sensorData.temperature} \u00b0C`;\n    humidityElement.textContent = `${sensorData.humidity} %`;\n    lightElement.textContent = `${sensorData.light} lux`;\n    soilMoistureElement.textContent = `${sensorData.soilMoisture} %`;\n    co2LevelElement.textContent = `${sensorData.co2Level} ppm`;\n    \n    \/\/ \u66f4\u65b0\u4f20\u611f\u5668\u6837\u5f0f - \u8d85\u51fa\u9608\u503c\u65f6\u6dfb\u52a0\u8b66\u62a5\u6837\u5f0f\n    updateSensorStyle('temperature', sensorData.temperature, temperatureElement);\n    updateSensorStyle('humidity', sensorData.humidity, humidityElement);\n    updateSensorStyle('light', sensorData.light, lightElement);\n    updateSensorStyle('soilMoisture', sensorData.soilMoisture, soilMoistureElement);\n    updateSensorStyle('co2Level', sensorData.co2Level, co2LevelElement);\n    \n    \/\/ \u68c0\u67e5\u8b66\u62a5\n    checkAlerts();\n    \n    \/\/ \u68c0\u67e5\u81ea\u52a8\u5316\u89c4\u5219\n    checkAutomationRules();\n    \n    \/\/ \u66f4\u65b0\u5386\u53f2\u6570\u636e\n    updateHistoryData();\n}\n\n\/**\n * \u66f4\u65b0\u4f20\u611f\u5668\u6837\u5f0f\n * @param {string} sensorType - \u4f20\u611f\u5668\u7c7b\u578b\n * @param {number} value - \u4f20\u611f\u5668\u5f53\u524d\u503c\n * @param {HTMLElement} element - \u5bf9\u5e94\u7684DOM\u5143\u7d20\n * \u529f\u80fd\uff1a\u6839\u636e\u4f20\u611f\u5668\u6570\u503c\u662f\u5426\u5728\u9608\u503c\u8303\u56f4\u5185\u66f4\u65b0\u5143\u7d20\u6837\u5f0f\n *\/\nfunction updateSensorStyle(sensorType, value, element) {\n    const thresholds = alertThresholds&#91;sensorType];\n    \n    if (value &lt; thresholds.min || value > thresholds.max) {\n        element.classList.add('alert');\n    } else {\n        element.classList.remove('alert');\n    }\n}\n\n\/**\n * \u5207\u6362\u8bbe\u5907\u5f00\u5173\u72b6\u6001\n * @param {string} device - \u8bbe\u5907\u7c7b\u578b('pump'\u6216'fan')\n * \u529f\u80fd\uff1a\u5207\u6362\u8bbe\u5907\u5f00\u5173\u72b6\u6001\u5e76\u66f4\u65b0\u663e\u793a\uff0c\u540c\u65f6\u53d1\u9001\u66f4\u65b0\u5230\u670d\u52a1\u5668\n *\/\nfunction toggleDevice(device) {\n    \/\/ \u83b7\u53d6\u8bbe\u5907\u5f00\u5173\u72b6\u6001\n    const isOn = device === 'pump' ? pumpToggle.checked : fanToggle.checked;\n    deviceStatus&#91;device].isOn = isOn;\n    \n    \/\/ \u66f4\u65b0\u8bbe\u5907\u72b6\u6001\u663e\u793a\n    if (device === 'pump') {\n        pumpStatus.textContent = isOn ? '\u5f00\u542f' : '\u5173\u95ed';\n    } else {\n        fanStatus.textContent = isOn ? '\u5f00\u542f' : '\u5173\u95ed';\n    }\n    \n    \/\/ \u53d1\u9001\u8bbe\u5907\u72b6\u6001\u66f4\u65b0\u5230\u670d\u52a1\u5668\n    sendDeviceStatusUpdate(device);\n}\n\n\/**\n * \u66f4\u65b0\u8bbe\u5907\u8bbe\u7f6e\n * @param {string} device - \u8bbe\u5907\u7c7b\u578b('pump'\u6216'fan')\n * @param {string} setting - \u8bbe\u7f6e\u7c7b\u578b('flow'\u6216'speed')\n * \u529f\u80fd\uff1a\u66f4\u65b0\u8bbe\u5907\u7684\u6d41\u91cf\u6216\u901f\u5ea6\u8bbe\u7f6e\uff0c\u5e76\u53d1\u9001\u66f4\u65b0\u5230\u670d\u52a1\u5668\n *\/\nfunction updateDeviceSettings(device, setting) {\n    if (device === 'pump') {\n        if (setting === 'flow') {\n            deviceStatus.pump.flow = pumpFlow.value;\n            pumpFlowValue.textContent = pumpFlow.value;\n        } else if (setting === 'speed') {\n            deviceStatus.pump.speed = pumpSpeed.value;\n            pumpSpeedValue.textContent = pumpSpeed.value;\n        }\n    } else if (device === 'fan') {\n        deviceStatus.fan.speed = fanSpeed.value;\n        fanSpeedValue.textContent = fanSpeed.value;\n    }\n    \n    \/\/ \u53d1\u9001\u8bbe\u5907\u8bbe\u7f6e\u66f4\u65b0\u5230\u670d\u52a1\u5668\n    sendDeviceSettingsUpdate(device);\n}\n\n\/**\n * \u8bbe\u7f6e\u8bbe\u5907\u5b9a\u65f6\u5668\n * @param {string} device - \u8bbe\u5907\u7c7b\u578b('pump'\u6216'fan')\n * \u529f\u80fd\uff1a\u8bbe\u7f6e\u8bbe\u5907\u5728\u6307\u5b9a\u65f6\u95f4\u540e\u81ea\u52a8\u5173\u95ed\n *\/\nfunction setDeviceTimer(device) {\n    \/\/ \u83b7\u53d6\u5b9a\u65f6\u65f6\u95f4\u503c\n    const timerValue = device === 'pump' ? parseInt(pumpTimer.value) : parseInt(fanTimer.value);\n    \n    if (timerValue &lt;= 0) {\n        alert('\u8bf7\u8f93\u5165\u6709\u6548\u7684\u5b9a\u65f6\u65f6\u95f4');\n        return;\n    }\n    \n    \/\/ \u6e05\u9664\u73b0\u6709\u5b9a\u65f6\u5668\n    cancelDeviceTimer(device);\n    \n    \/\/ \u8bbe\u7f6e\u65b0\u5b9a\u65f6\u5668\n    deviceStatus&#91;device].timer = timerValue;\n    const timerStatus = device === 'pump' ? pumpTimerStatus : fanTimerStatus;\n    timerStatus.textContent = `\u5b9a\u65f6: ${timerValue} \u5206\u949f\u540e\u5173\u95ed`;\n    \n    \/\/ \u542f\u52a8\u5b9a\u65f6\u5668\n    deviceStatus&#91;device].timerInterval = setTimeout(() => {\n        \/\/ \u65f6\u95f4\u5230\u540e\u5173\u95ed\u8bbe\u5907\n        if (device === 'pump') {\n            pumpToggle.checked = false;\n        } else {\n            fanToggle.checked = false;\n        }\n        toggleDevice(device);\n        cancelDeviceTimer(device);\n    }, timerValue * 60 * 1000); \/\/ \u8f6c\u6362\u4e3a\u6beb\u79d2\n}\n\n\/**\n * \u53d6\u6d88\u8bbe\u5907\u5b9a\u65f6\u5668\n * @param {string} device - \u8bbe\u5907\u7c7b\u578b('pump'\u6216'fan')\n * \u529f\u80fd\uff1a\u53d6\u6d88\u8bbe\u5907\u7684\u5b9a\u65f6\u8bbe\u7f6e\n *\/\nfunction cancelDeviceTimer(device) {\n    if (deviceStatus&#91;device].timerInterval) {\n        clearTimeout(deviceStatus&#91;device].timerInterval);\n        deviceStatus&#91;device].timerInterval = null;\n        deviceStatus&#91;device].timer = 0;\n        \n        \/\/ \u66f4\u65b0\u5b9a\u65f6\u5668\u72b6\u6001\u663e\u793a\n        const timerStatus = device === 'pump' ? pumpTimerStatus : fanTimerStatus;\n        timerStatus.textContent = '\u672a\u8bbe\u7f6e\u5b9a\u65f6';\n        \n        \/\/ \u91cd\u7f6e\u5b9a\u65f6\u5668\u8f93\u5165\u503c\n        if (device === 'pump') {\n            pumpTimer.value = 0;\n        } else {\n            fanTimer.value = 0;\n        }\n    }\n}\n\n\/\/ ===============================================================================\n\/\/ \u8b66\u62a5\u7cfb\u7edf\u76f8\u5173\u51fd\u6570\n\/\/ ===============================================================================\n\n\/**\n * \u68c0\u67e5\u6240\u6709\u4f20\u611f\u5668\u8b66\u62a5\n * \u529f\u80fd\uff1a\u68c0\u67e5\u5404\u4f20\u611f\u5668\u6570\u503c\u662f\u5426\u8d85\u51fa\u9608\u503c\u5e76\u521b\u5efa\u76f8\u5e94\u8b66\u62a5\n *\/\nfunction checkAlerts() {\n    checkSensorAlert('temperature', sensorData.temperature, '\u6e29\u5ea6');\n    checkSensorAlert('humidity', sensorData.humidity, '\u6e7f\u5ea6');\n    checkSensorAlert('light', sensorData.light, '\u5149\u7167');\n    checkSensorAlert('soilMoisture', sensorData.soilMoisture, '\u571f\u58e4\u6e7f\u5ea6');\n    checkSensorAlert('co2Level', sensorData.co2Level, 'CO\u2082\u6d53\u5ea6');\n    \n    \/\/ \u66f4\u65b0\u8b66\u62a5\u663e\u793a\n    updateAlertDisplay();\n}\n\n\/**\n * \u68c0\u67e5\u5355\u4e2a\u4f20\u611f\u5668\u8b66\u62a5\n * @param {string} sensorType - \u4f20\u611f\u5668\u7c7b\u578b\n * @param {number} value - \u4f20\u611f\u5668\u5f53\u524d\u503c\n * @param {string} sensorName - \u4f20\u611f\u5668\u4e2d\u6587\u540d\u79f0\n * \u529f\u80fd\uff1a\u68c0\u67e5\u4f20\u611f\u5668\u6570\u503c\u662f\u5426\u8d85\u51fa\u9608\u503c\u5e76\u521b\u5efa\u76f8\u5e94\u8b66\u62a5\n *\/\nfunction checkSensorAlert(sensorType, value, sensorName) {\n    const thresholds = alertThresholds&#91;sensorType];\n    \n    if (value &lt; thresholds.min) {\n        createAlert('warning', `${sensorName}\u8fc7\u4f4e: ${value}`);\n    } else if (value > thresholds.max) {\n        createAlert('warning', `${sensorName}\u8fc7\u9ad8: ${value}`);\n    }\n}\n\n\/**\n * \u521b\u5efa\u8b66\u62a5\n * @param {string} type - \u8b66\u62a5\u7c7b\u578b('warning'\u3001'info'\u6216'success')\n * @param {string} message - \u8b66\u62a5\u6d88\u606f\u5185\u5bb9\n * \u529f\u80fd\uff1a\u521b\u5efa\u5e76\u5b58\u50a8\u8b66\u62a5\u4fe1\u606f\uff0c\u907f\u514d\u91cd\u590d\u8b66\u62a5\n *\/\nfunction createAlert(type, message) {\n    \/\/ \u68c0\u67e5\u662f\u5426\u5df2\u5b58\u5728\u76f8\u540c\u8b66\u62a5\n    const existingAlert = alertHistory.find(alert => alert.message === message);\n    \n    if (!existingAlert) {\n        const alert = {\n            type,\n            message,\n            timestamp: new Date()\n        };\n        \n        \/\/ \u6dfb\u52a0\u5230\u8b66\u62a5\u5386\u53f2\uff08\u6dfb\u52a0\u5230\u5f00\u5934\uff09\n        alertHistory.unshift(alert);\n        \n        \/\/ \u9650\u5236\u8b66\u62a5\u5386\u53f2\u6570\u91cf\uff0c\u6700\u591a\u4fdd\u755910\u6761\n        if (alertHistory.length > 10) {\n            alertHistory.pop();\n        }\n    }\n}\n\n\/**\n * \u66f4\u65b0\u8b66\u62a5\u663e\u793a\n * \u529f\u80fd\uff1a\u5c06\u8b66\u62a5\u5386\u53f2\u663e\u793a\u5728\u9875\u9762\u4e0a\n *\/\nfunction updateAlertDisplay() {\n    if (alertHistory.length === 0) {\n        alertContent.innerHTML = '&lt;p>\u5f53\u524d\u65e0\u5f02\u5e38\u8b66\u62a5&lt;\/p>';\n        return;\n    }\n    \n    let alertHTML = '';\n    \n    alertHistory.forEach(alert => {\n        \/\/ \u6839\u636e\u8b66\u62a5\u7c7b\u578b\u9009\u62e9\u56fe\u6807\n        const icon = alert.type === 'warning' ? 'exclamation-triangle' : 'info-circle';\n        const time = formatAlertTime(alert.timestamp);\n        \n        \/\/ \u6784\u5efa\u8b66\u62a5HTML\n        alertHTML += `\n            &lt;div class=\"alert-item ${alert.type}\">\n                &lt;i class=\"fas fa-${icon}\">&lt;\/i>\n                &lt;span>${alert.message}&lt;\/span>\n                &lt;span class=\"alert-timestamp\">${time}&lt;\/span>\n            &lt;\/div>\n        `;\n    });\n    \n    alertContent.innerHTML = alertHTML;\n}\n\n\/**\n * \u683c\u5f0f\u5316\u8b66\u62a5\u65f6\u95f4\n * @param {Date} timestamp - \u8b66\u62a5\u53d1\u751f\u65f6\u95f4\n * @returns {string} \u683c\u5f0f\u5316\u540e\u7684\u65f6\u95f4\u5b57\u7b26\u4e32\n * \u529f\u80fd\uff1a\u6839\u636e\u65f6\u95f4\u5dee\u8fd4\u56de\u53cb\u597d\u7684\u65f6\u95f4\u8868\u793a\n *\/\nfunction formatAlertTime(timestamp) {\n    const now = new Date();\n    const diff = Math.floor((now - timestamp) \/ 1000); \/\/ \u79d2\u6570\u5dee\n    \n    if (diff &lt; 60) {\n        return '\u521a\u521a';\n    } else if (diff &lt; 3600) {\n        return `${Math.floor(diff \/ 60)}\u5206\u949f\u524d`;\n    } else if (diff &lt; 86400) {\n        return `${Math.floor(diff \/ 3600)}\u5c0f\u65f6\u524d`;\n    } else {\n        return `${Math.floor(diff \/ 86400)}\u5929\u524d`;\n    }\n}\n\n\/\/ ===============================================================================\n\/\/ \u5386\u53f2\u6570\u636e\u56fe\u8868\u76f8\u5173\u51fd\u6570\n\/\/ ===============================================================================\n\n\/**\n * \u521d\u59cb\u5316\u5386\u53f2\u56fe\u8868\n * \u529f\u80fd\uff1a\u521b\u5efaChart.js\u56fe\u8868\u5b9e\u4f8b\u5e76\u6dfb\u52a0\u4e8b\u4ef6\u76d1\u542c\n *\/\nfunction initHistoryChart() {\n    const ctx = document.getElementById('history-chart').getContext('2d');\n    \n    historyChart = new Chart(ctx, {\n        type: 'line',\n        data: {\n            labels: &#91;],\n            datasets: &#91;{\n                label: '\u6e29\u5ea6',\n                data: &#91;],\n                borderColor: '#ff7675',\n                backgroundColor: 'rgba(255, 118, 117, 0.1)',\n                borderWidth: 2,\n                tension: 0.3,\n                fill: true\n            }]\n        },\n        options: {\n            responsive: true,\n            maintainAspectRatio: false,\n            scales: {\n                y: {\n                    beginAtZero: false\n                }\n            }\n        }\n    });\n    \n    \/\/ \u6dfb\u52a0\u4f20\u611f\u5668\u9009\u62e9\u4e8b\u4ef6\u76d1\u542c\n    document.getElementById('chart-sensor-select').addEventListener('change', updateChartData);\n    document.getElementById('chart-time-select').addEventListener('change', updateChartData);\n    \n    \/\/ \u521d\u59cb\u66f4\u65b0\u56fe\u8868\u6570\u636e\n    updateChartData();\n}\n\n\/**\n * \u66f4\u65b0\u56fe\u8868\u6570\u636e\n * \u529f\u80fd\uff1a\u6839\u636e\u9009\u62e9\u7684\u4f20\u611f\u5668\u548c\u65f6\u95f4\u8303\u56f4\u66f4\u65b0\u56fe\u8868\u6570\u636e\n *\/\nfunction updateChartData() {\n    const sensorType = document.getElementById('chart-sensor-select').value;\n    const timeRange = parseInt(document.getElementById('chart-time-select').value);\n    \n    \/\/ \u83b7\u53d6\u4f20\u611f\u5668\u540d\u79f0\n    const sensorName = getSensorName(sensorType);\n    \n    \/\/ \u51c6\u5907\u6570\u636e\n    const now = new Date();\n    const labels = &#91;];\n    const data = &#91;];\n    \n    \/\/ \u5982\u679c\u5386\u53f2\u6570\u636e\u4e0d\u8db3\uff0c\u751f\u6210\u6a21\u62df\u6570\u636e\n    if (historyData&#91;sensorType].length &lt; timeRange * 12) { \/\/ \u5047\u8bbe\u6bcf5\u5206\u949f\u4e00\u4e2a\u6570\u636e\u70b9\n        \/\/ \u751f\u6210\u8fc7\u53bbtimeRange\u5c0f\u65f6\u7684\u6a21\u62df\u6570\u636e\n        for (let i = timeRange * 12; i >= 0; i--) {\n            const time = new Date(now - i * 5 * 60 * 1000);\n            labels.push(time.toLocaleTimeString(&#91;], { hour: '2-digit', minute: '2-digit' }));\n            \n            \/\/ \u6839\u636e\u4f20\u611f\u5668\u7c7b\u578b\u751f\u6210\u5408\u7406\u8303\u56f4\u5185\u7684\u968f\u673a\u6570\u636e\n            let value;\n            switch (sensorType) {\n                case 'temperature':\n                    value = 20 + Math.random() * 10;\n                    break;\n                case 'humidity':\n                    value = 50 + Math.random() * 30;\n                    break;\n                case 'light':\n                    value = 500 + Math.random() * 1000;\n                    break;\n                case 'soilMoisture':\n                    value = 40 + Math.random() * 30;\n                    break;\n                case 'co2Level':\n                    value = 400 + Math.random() * 300;\n                    break;\n            }\n            \n            data.push(value);\n        }\n    } else {\n        \/\/ \u4f7f\u7528\u5b9e\u9645\u5386\u53f2\u6570\u636e\n        const relevantData = historyData&#91;sensorType].slice(0, timeRange * 12);\n        \n        relevantData.forEach(item => {\n            labels.push(new Date(item.timestamp).toLocaleTimeString(&#91;], { hour: '2-digit', minute: '2-digit' }));\n            data.push(item.value);\n        });\n    }\n    \n    \/\/ \u66f4\u65b0\u56fe\u8868\u6570\u636e\n    historyChart.data.labels = labels;\n    historyChart.data.datasets&#91;0].label = sensorName;\n    historyChart.data.datasets&#91;0].data = data;\n    \n    \/\/ \u6839\u636e\u4f20\u611f\u5668\u7c7b\u578b\u8bbe\u7f6e\u989c\u8272\n    switch (sensorType) {\n        case 'temperature':\n            historyChart.data.datasets&#91;0].borderColor = '#ff7675';\n            historyChart.data.datasets&#91;0].backgroundColor = 'rgba(255, 118, 117, 0.1)';\n            break;\n        case 'humidity':\n            historyChart.data.datasets&#91;0].borderColor = '#74b9ff';\n            historyChart.data.datasets&#91;0].backgroundColor = 'rgba(116, 185, 255, 0.1)';\n            break;\n        case 'light':\n            historyChart.data.datasets&#91;0].borderColor = '#ffeaa7';\n            historyChart.data.datasets&#91;0].backgroundColor = 'rgba(255, 234, 167, 0.1)';\n            break;\n        case 'soilMoisture':\n            historyChart.data.datasets&#91;0].borderColor = '#55efc4';\n            historyChart.data.datasets&#91;0].backgroundColor = 'rgba(85, 239, 196, 0.1)';\n            break;\n        case 'co2Level':\n            historyChart.data.datasets&#91;0].borderColor = '#a29bfe';\n            historyChart.data.datasets&#91;0].backgroundColor = 'rgba(162, 155, 254, 0.1)';\n            break;\n    }\n    \n    \/\/ \u66f4\u65b0\u56fe\u8868\u663e\u793a\n    historyChart.update();\n}\n\n\/**\n * \u83b7\u53d6\u4f20\u611f\u5668\u540d\u79f0\n * @param {string} sensorType - \u4f20\u611f\u5668\u7c7b\u578b\n * @returns {string} \u683c\u5f0f\u5316\u7684\u4f20\u611f\u5668\u540d\u79f0\uff08\u5305\u542b\u5355\u4f4d\uff09\n * \u529f\u80fd\uff1a\u6839\u636e\u4f20\u611f\u5668\u7c7b\u578b\u8fd4\u56de\u53cb\u597d\u7684\u4e2d\u6587\u540d\u79f0\u548c\u5355\u4f4d\n *\/\nfunction getSensorName(sensorType) {\n    switch (sensorType) {\n        case 'temperature': return '\u6e29\u5ea6 (\u00b0C)';\n        case 'humidity': return '\u6e7f\u5ea6 (%)';\n        case 'light': return '\u5149\u7167 (lux)';\n        case 'soilMoisture': return '\u571f\u58e4\u6e7f\u5ea6 (%)';\n        case 'co2Level': return 'CO\u2082\u6d53\u5ea6 (ppm)';\n        default: return '';\n    }\n}\n\n\/**\n * \u66f4\u65b0\u5386\u53f2\u6570\u636e\n * \u529f\u80fd\uff1a\u5c06\u5f53\u524d\u4f20\u611f\u5668\u6570\u636e\u6dfb\u52a0\u5230\u5386\u53f2\u6570\u636e\u4e2d\uff0c\u5e76\u9650\u5236\u5386\u53f2\u6570\u636e\u91cf\n *\/\nfunction updateHistoryData() {\n    const now = new Date();\n    \n    \/\/ \u6dfb\u52a0\u5f53\u524d\u6570\u636e\u5230\u5386\u53f2\u8bb0\u5f55\n    Object.keys(sensorData).forEach(key => {\n        historyData&#91;key].unshift({\n            value: sensorData&#91;key],\n            timestamp: now.getTime()\n        });\n        \n        \/\/ \u9650\u5236\u5386\u53f2\u6570\u636e\u91cf\uff08\u4fdd\u755924\u5c0f\u65f6\u7684\u6570\u636e\uff0c\u5047\u8bbe\u6bcf5\u5206\u949f\u4e00\u4e2a\u6570\u636e\u70b9\uff09\n        if (historyData&#91;key].length > 24 * 12) {\n            historyData&#91;key].pop();\n        }\n    });\n}\n\n\/\/ ===============================================================================\n\/\/ \u81ea\u52a8\u5316\u89c4\u5219\u76f8\u5173\u51fd\u6570\n\/\/ ===============================================================================\n\n\/**\n * \u6253\u5f00\u89c4\u5219\u6a21\u6001\u6846\n * \u529f\u80fd\uff1a\u663e\u793a\u6dfb\u52a0\u81ea\u52a8\u5316\u89c4\u5219\u7684\u6a21\u6001\u6846\n *\/\nfunction openRuleModal() {\n    ruleModal.style.display = 'flex';\n    document.getElementById('modal-title').textContent = '\u6dfb\u52a0\u81ea\u52a8\u5316\u89c4\u5219';\n    ruleForm.reset();\n    \n    \/\/ \u91cd\u7f6e\u8868\u5355ID\n    ruleForm.dataset.ruleId = '';\n}\n\n\/**\n * \u5173\u95ed\u89c4\u5219\u6a21\u6001\u6846\n * \u529f\u80fd\uff1a\u9690\u85cf\u89c4\u5219\u6a21\u6001\u6846\n *\/\nfunction closeRuleModal() {\n    ruleModal.style.display = 'none';\n}\n\n\/**\n * \u4fdd\u5b58\u89c4\u5219\n * @param {Event} e - \u8868\u5355\u63d0\u4ea4\u4e8b\u4ef6\n * \u529f\u80fd\uff1a\u4fdd\u5b58\u65b0\u89c4\u5219\u6216\u66f4\u65b0\u73b0\u6709\u89c4\u5219\n *\/\nfunction saveRule(e) {\n    e.preventDefault();\n    \n    \/\/ \u83b7\u53d6\u8868\u5355\u6570\u636e\n    const ruleName = document.getElementById('rule-name').value;\n    const triggerSensor = document.getElementById('trigger-sensor').value;\n    const triggerCondition = document.getElementById('trigger-condition').value;\n    const triggerValue = parseFloat(document.getElementById('trigger-value').value);\n    const actionDevice = document.getElementById('action-device').value;\n    const actionOperation = document.getElementById('action-operation').value;\n    const actionValue = document.getElementById('action-value').value ? parseInt(document.getElementById('action-value').value) : null;\n    \n    const ruleId = ruleForm.dataset.ruleId || Date.now().toString();\n    \n    \/\/ \u521b\u5efa\u89c4\u5219\u5bf9\u8c61\n    const rule = {\n        id: ruleId,\n        name: ruleName,\n        trigger: {\n            sensor: triggerSensor,\n            condition: triggerCondition,\n            value: triggerValue\n        },\n        action: {\n            device: actionDevice,\n            operation: actionOperation,\n            value: actionValue\n        }\n    };\n    \n    \/\/ \u68c0\u67e5\u662f\u5426\u662f\u7f16\u8f91\u73b0\u6709\u89c4\u5219\n    const existingRuleIndex = automationRules.findIndex(r => r.id === ruleId);\n    \n    if (existingRuleIndex !== -1) {\n        \/\/ \u66f4\u65b0\u73b0\u6709\u89c4\u5219\n        automationRules&#91;existingRuleIndex] = rule;\n    } else {\n        \/\/ \u6dfb\u52a0\u65b0\u89c4\u5219\n        automationRules.push(rule);\n    }\n    \n    \/\/ \u4fdd\u5b58\u5230\u672c\u5730\u5b58\u50a8\n    saveRulesToLocalStorage();\n    \n    \/\/ \u66f4\u65b0\u89c4\u5219\u663e\u793a\n    renderRules();\n    \n    \/\/ \u5173\u95ed\u6a21\u6001\u6846\n    closeRuleModal();\n}\n\n\/**\n * \u6e32\u67d3\u89c4\u5219\u5217\u8868\n * \u529f\u80fd\uff1a\u5c06\u6240\u6709\u81ea\u52a8\u5316\u89c4\u5219\u663e\u793a\u5728\u9875\u9762\u4e0a\n *\/\nfunction renderRules() {\n    if (automationRules.length === 0) {\n        emptyRulesMessage.style.display = 'block';\n        rulesContainer.innerHTML = '';\n        return;\n    }\n    \n    emptyRulesMessage.style.display = 'none';\n    \n    let rulesHTML = '';\n    \n    automationRules.forEach(rule => {\n        const triggerText = getTriggerText(rule.trigger);\n        const actionText = getActionText(rule.action);\n        \n        \/\/ \u6784\u5efa\u89c4\u5219\u5361\u7247HTML\n        rulesHTML += `\n            &lt;div class=\"rule-card\" data-rule-id=\"${rule.id}\">\n                &lt;div class=\"rule-info\">\n                    &lt;div class=\"rule-name\">${rule.name}&lt;\/div>\n                    &lt;div class=\"rule-condition\">\u5f53 ${triggerText}&lt;\/div>\n                    &lt;div class=\"rule-action\">\u6267\u884c ${actionText}&lt;\/div>\n                &lt;\/div>\n                &lt;div class=\"rule-controls\">\n                    &lt;button class=\"edit-rule\" onclick=\"editRule('${rule.id}')\">&lt;i class=\"fas fa-edit\">&lt;\/i>&lt;\/button>\n                    &lt;button class=\"delete-rule\" onclick=\"deleteRule('${rule.id}')\">&lt;i class=\"fas fa-trash\">&lt;\/i>&lt;\/button>\n                &lt;\/div>\n            &lt;\/div>\n        `;\n    });\n    \n    rulesContainer.innerHTML = rulesHTML;\n}\n\n\/**\n * \u83b7\u53d6\u89e6\u53d1\u6761\u4ef6\u6587\u672c\n * @param {Object} trigger - \u89e6\u53d1\u6761\u4ef6\u5bf9\u8c61\n * @returns {string} \u683c\u5f0f\u5316\u7684\u89e6\u53d1\u6761\u4ef6\u6587\u672c\n * \u529f\u80fd\uff1a\u5c06\u89e6\u53d1\u6761\u4ef6\u5bf9\u8c61\u8f6c\u6362\u4e3a\u53cb\u597d\u7684\u4e2d\u6587\u63cf\u8ff0\n *\/\nfunction getTriggerText(trigger) {\n    const sensorNames = {\n        temperature: '\u6e29\u5ea6',\n        humidity: '\u6e7f\u5ea6',\n        light: '\u5149\u7167',\n        soilMoisture: '\u571f\u58e4\u6e7f\u5ea6',\n        co2Level: 'CO\u2082\u6d53\u5ea6'\n    };\n    \n    const conditionSymbols = {\n        gt: '\u5927\u4e8e',\n        lt: '\u5c0f\u4e8e',\n        eq: '\u7b49\u4e8e'\n    };\n    \n    const units = {\n        temperature: '\u00b0C',\n        humidity: '%',\n        light: 'lux',\n        soilMoisture: '%',\n        co2Level: 'ppm'\n    };\n    \n    return `${sensorNames&#91;trigger.sensor]} ${conditionSymbols&#91;trigger.condition]} ${trigger.value}${units&#91;trigger.sensor]}`;\n}\n\n\/**\n * \u83b7\u53d6\u6267\u884c\u52a8\u4f5c\u6587\u672c\n * @param {Object} action - \u52a8\u4f5c\u5bf9\u8c61\n * @returns {string} \u683c\u5f0f\u5316\u7684\u52a8\u4f5c\u6587\u672c\n * \u529f\u80fd\uff1a\u5c06\u52a8\u4f5c\u5bf9\u8c61\u8f6c\u6362\u4e3a\u53cb\u597d\u7684\u4e2d\u6587\u63cf\u8ff0\n *\/\nfunction getActionText(action) {\n    const deviceNames = {\n        pump: '\u6c34\u6cf5',\n        fan: '\u98ce\u6247'\n    };\n    \n    const operationNames = {\n        turnOn: '\u6253\u5f00',\n        turnOff: '\u5173\u95ed',\n        setSpeed: '\u8bbe\u7f6e\u901f\u5ea6\u4e3a'\n    };\n    \n    let text = `${deviceNames&#91;action.device]} ${operationNames&#91;action.operation]}`;\n    \n    if (action.operation === 'setSpeed' &amp;&amp; action.value !== null) {\n        text += ` ${action.value}`;\n    }\n    \n    return text;\n}\n\n\/**\n * \u7f16\u8f91\u89c4\u5219\n * @param {string} ruleId - \u89c4\u5219ID\n * \u529f\u80fd\uff1a\u6253\u5f00\u7f16\u8f91\u89c4\u5219\u6a21\u6001\u6846\u5e76\u586b\u5145\u73b0\u6709\u89c4\u5219\u6570\u636e\n *\/\nfunction editRule(ruleId) {\n    const rule = automationRules.find(r => r.id === ruleId);\n    \n    if (!rule) return;\n    \n    \/\/ \u586b\u5145\u8868\u5355\u6570\u636e\n    document.getElementById('modal-title').textContent = '\u7f16\u8f91\u81ea\u52a8\u5316\u89c4\u5219';\n    document.getElementById('rule-name').value = rule.name;\n    document.getElementById('trigger-sensor').value = rule.trigger.sensor;\n    document.getElementById('trigger-condition').value = rule.trigger.condition;\n    document.getElementById('trigger-value').value = rule.trigger.value;\n    document.getElementById('action-device').value = rule.action.device;\n    document.getElementById('action-operation').value = rule.action.operation;\n    \n    \/\/ \u8bbe\u7f6e\u901f\u5ea6\u503c\u8f93\u5165\u6846\n    if (rule.action.value !== null) {\n        document.getElementById('action-value').value = rule.action.value;\n        document.getElementById('action-value').style.display = 'block';\n    } else {\n        document.getElementById('action-value').style.display = 'none';\n    }\n    \n    \/\/ \u8bbe\u7f6e\u8868\u5355ID\n    ruleForm.dataset.ruleId = ruleId;\n    \n    \/\/ \u6253\u5f00\u6a21\u6001\u6846\n    openRuleModal();\n}\n\n\/**\n * \u5220\u9664\u89c4\u5219\n * @param {string} ruleId - \u89c4\u5219ID\n * \u529f\u80fd\uff1a\u4ece\u5217\u8868\u4e2d\u5220\u9664\u6307\u5b9a\u89c4\u5219\n *\/\nfunction deleteRule(ruleId) {\n    if (confirm('\u786e\u5b9a\u8981\u5220\u9664\u6b64\u89c4\u5219\u5417\uff1f')) {\n        automationRules = automationRules.filter(rule => rule.id !== ruleId);\n        saveRulesToLocalStorage();\n        renderRules();\n    }\n}\n\n\/**\n * \u66f4\u65b0\u6267\u884c\u52a8\u4f5c\u64cd\u4f5c\u9009\u9879\n * \u529f\u80fd\uff1a\u6839\u636e\u9009\u62e9\u7684\u8bbe\u5907\u66f4\u65b0\u53ef\u7528\u7684\u64cd\u4f5c\u9009\u9879\n *\/\nfunction updateActionOperations() {\n    const actionDevice = document.getElementById('action-device').value;\n    const actionOperation = document.getElementById('action-operation');\n    \n    \/\/ \u6e05\u7a7a\u73b0\u6709\u9009\u9879\n    actionOperation.innerHTML = '';\n    \n    \/\/ \u6dfb\u52a0\u901a\u7528\u9009\u9879\n    actionOperation.appendChild(new Option('\u6253\u5f00', 'turnOn'));\n    actionOperation.appendChild(new Option('\u5173\u95ed', 'turnOff'));\n    \n    \/\/ \u6dfb\u52a0\u8bbe\u5907\u7279\u5b9a\u9009\u9879\n    if (actionDevice === 'pump' || actionDevice === 'fan') {\n        actionOperation.appendChild(new Option('\u8bbe\u7f6e\u901f\u5ea6', 'setSpeed'));\n    }\n    \n    \/\/ \u89e6\u53d1\u53d8\u66f4\u4e8b\u4ef6\n    toggleActionValue();\n}\n\n\/**\n * \u5207\u6362\u52a8\u4f5c\u503c\u8f93\u5165\u6846\u663e\u793a\n * \u529f\u80fd\uff1a\u6839\u636e\u9009\u62e9\u7684\u64cd\u4f5c\u663e\u793a\u6216\u9690\u85cf\u901f\u5ea6\u503c\u8f93\u5165\u6846\n *\/\nfunction toggleActionValue() {\n    const actionOperation = document.getElementById('action-operation').value;\n    const actionValue = document.getElementById('action-value');\n    \n    if (actionOperation === 'setSpeed') {\n        actionValue.style.display = 'block';\n        actionValue.required = true;\n    } else {\n        actionValue.style.display = 'none';\n        actionValue.required = false;\n    }\n}\n\n\/**\n * \u68c0\u67e5\u81ea\u52a8\u5316\u89c4\u5219\n * \u529f\u80fd\uff1a\u68c0\u67e5\u6240\u6709\u81ea\u52a8\u5316\u89c4\u5219\u662f\u5426\u6ee1\u8db3\u89e6\u53d1\u6761\u4ef6\uff0c\u5982\u679c\u6ee1\u8db3\u5219\u6267\u884c\u76f8\u5e94\u52a8\u4f5c\n *\/\nfunction checkAutomationRules() {\n    automationRules.forEach(rule => {\n        const sensorValue = sensorData&#91;rule.trigger.sensor];\n        let conditionMet = false;\n        \n        \/\/ \u68c0\u67e5\u89e6\u53d1\u6761\u4ef6\u662f\u5426\u6ee1\u8db3\n        switch (rule.trigger.condition) {\n            case 'gt':\n                conditionMet = sensorValue > rule.trigger.value;\n                break;\n            case 'lt':\n                conditionMet = sensorValue &lt; rule.trigger.value;\n                break;\n            case 'eq':\n                conditionMet = Math.abs(sensorValue - rule.trigger.value) &lt; 0.1; \/\/ \u8fd1\u4f3c\u76f8\u7b49\n                break;\n        }\n        \n        if (conditionMet) {\n            \/\/ \u6761\u4ef6\u6ee1\u8db3\uff0c\u6267\u884c\u89c4\u5219\u52a8\u4f5c\n            executeRuleAction(rule);\n        }\n    });\n}\n\n\/**\n * \u6267\u884c\u89c4\u5219\u52a8\u4f5c\n * @param {Object} rule - \u89c4\u5219\u5bf9\u8c61\n * \u529f\u80fd\uff1a\u6839\u636e\u89c4\u5219\u6267\u884c\u76f8\u5e94\u7684\u8bbe\u5907\u64cd\u4f5c\n *\/\nfunction executeRuleAction(rule) {\n    const { device, operation, value } = rule.action;\n    \n    switch (operation) {\n        case 'turnOn':\n            \/\/ \u6253\u5f00\u8bbe\u5907\n            if (!deviceStatus&#91;device].isOn) {\n                if (device === 'pump') {\n                    pumpToggle.checked = true;\n                } else {\n                    fanToggle.checked = true;\n                }\n                toggleDevice(device);\n                createAlert('info', `\u81ea\u52a8\u5316\u89c4\u5219 \"${rule.name}\" \u5df2\u6253\u5f00${device === 'pump' ? '\u6c34\u6cf5' : '\u98ce\u6247'}`);\n            }\n            break;\n        case 'turnOff':\n            \/\/ \u5173\u95ed\u8bbe\u5907\n            if (deviceStatus&#91;device].isOn) {\n                if (device === 'pump') {\n                    pumpToggle.checked = false;\n                } else {\n                    fanToggle.checked = false;\n                }\n                toggleDevice(device);\n                createAlert('info', `\u81ea\u52a8\u5316\u89c4\u5219 \"${rule.name}\" \u5df2\u5173\u95ed${device === 'pump' ? '\u6c34\u6cf5' : '\u98ce\u6247'}`);\n            }\n            break;\n        case 'setSpeed':\n            \/\/ \u8bbe\u7f6e\u8bbe\u5907\u901f\u5ea6\n            if (device === 'pump') {\n                pumpSpeed.value = value;\n                updateDeviceSettings('pump', 'speed');\n                createAlert('info', `\u81ea\u52a8\u5316\u89c4\u5219 \"${rule.name}\" \u5df2\u8bbe\u7f6e\u6c34\u6cf5\u901f\u5ea6\u4e3a ${value}`);\n            } else {\n                fanSpeed.value = value;\n                updateDeviceSettings('fan', 'speed');\n                createAlert('info', `\u81ea\u52a8\u5316\u89c4\u5219 \"${rule.name}\" \u5df2\u8bbe\u7f6e\u98ce\u6247\u901f\u5ea6\u4e3a ${value}`);\n            }\n            break;\n    }\n}\n\n\/**\n * \u4fdd\u5b58\u89c4\u5219\u5230\u672c\u5730\u5b58\u50a8\n * \u529f\u80fd\uff1a\u5c06\u81ea\u52a8\u5316\u89c4\u5219\u4fdd\u5b58\u5230\u6d4f\u89c8\u5668\u672c\u5730\u5b58\u50a8\u4e2d\n *\/\nfunction saveRulesToLocalStorage() {\n    localStorage.setItem('automationRules', JSON.stringify(automationRules));\n}\n\n\/**\n * \u4ece\u672c\u5730\u5b58\u50a8\u52a0\u8f7d\u89c4\u5219\n * \u529f\u80fd\uff1a\u4ece\u6d4f\u89c8\u5668\u672c\u5730\u5b58\u50a8\u4e2d\u52a0\u8f7d\u81ea\u52a8\u5316\u89c4\u5219\n *\/\nfunction loadRules() {\n    const savedRules = localStorage.getItem('automationRules');\n    \n    if (savedRules) {\n        automationRules = JSON.parse(savedRules);\n        renderRules();\n    }\n}\n\n\/\/ ===============================================================================\n\/\/ \u670d\u52a1\u5668\u901a\u4fe1\u76f8\u5173\u51fd\u6570\n\/\/ ===============================================================================\n\n\/**\n * \u53d1\u9001\u8bbe\u5907\u72b6\u6001\u66f4\u65b0\u5230\u670d\u52a1\u5668\n * @param {string} device - \u8bbe\u5907\u7c7b\u578b\n * \u529f\u80fd\uff1a\u5c06\u8bbe\u5907\u5f00\u5173\u72b6\u6001\u66f4\u65b0\u53d1\u9001\u5230\u670d\u52a1\u5668\n *\/\nfunction sendDeviceStatusUpdate(device) {\n    const deviceData = {\n        device: device,\n        status: deviceStatus&#91;device].isOn,\n        settings: {\n            speed: deviceStatus&#91;device].speed,\n            flow: device === 'pump' ? deviceStatus&#91;device].flow : undefined\n        }\n    };\n\n    fetch(`${API_BASE_URL}\/control\/${device}`, {\n        method: 'POST',\n        headers: {\n            'Content-Type': 'application\/json'\n        },\n        body: JSON.stringify(deviceData)\n    })\n    .then(response => {\n        if (!response.ok) {\n            throw new Error('\u7f51\u7edc\u54cd\u5e94\u5f02\u5e38');\n        }\n        return response.json();\n    })\n    .then(data => {\n        console.log(`${device}\u72b6\u6001\u66f4\u65b0\u6210\u529f:`, data);\n        createAlert('success', `${device === 'pump' ? '\u6c34\u6cf5' : '\u98ce\u6247'}\u72b6\u6001\u5df2\u66f4\u65b0`);\n    })\n    .catch(error => {\n        console.error(`${device}\u72b6\u6001\u66f4\u65b0\u5931\u8d25:`, error);\n        createAlert('error', `${device === 'pump' ? '\u6c34\u6cf5' : '\u98ce\u6247'}\u72b6\u6001\u66f4\u65b0\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u7f51\u7edc\u8fde\u63a5`);\n    });\n}\n\n\/**\n * \u53d1\u9001\u8bbe\u5907\u8bbe\u7f6e\u66f4\u65b0\u5230\u670d\u52a1\u5668\n * @param {string} device - \u8bbe\u5907\u7c7b\u578b\n * \u529f\u80fd\uff1a\u5c06\u8bbe\u5907\u8bbe\u7f6e\uff08\u5982\u901f\u5ea6\u3001\u6d41\u91cf\u7b49\uff09\u66f4\u65b0\u53d1\u9001\u5230\u670d\u52a1\u5668\n *\/\nfunction sendDeviceSettingsUpdate(device) {\n    const settingsData = {\n        device: device,\n        settings: {\n            speed: deviceStatus&#91;device].speed,\n            flow: device === 'pump' ? deviceStatus&#91;device].flow : undefined,\n            timer: deviceStatus&#91;device].timer\n        }\n    };\n\n    fetch(`${API_BASE_URL}\/settings\/${device}`, {\n        method: 'POST',\n        headers: {\n            'Content-Type': 'application\/json'\n        },\n        body: JSON.stringify(settingsData)\n    })\n    .then(response => {\n        if (!response.ok) {\n            throw new Error('\u7f51\u7edc\u54cd\u5e94\u5f02\u5e38');\n        }\n        return response.json();\n    })\n    .then(data => {\n        console.log(`${device}\u8bbe\u7f6e\u66f4\u65b0\u6210\u529f:`, data);\n        createAlert('success', `${device === 'pump' ? '\u6c34\u6cf5' : '\u98ce\u6247'}\u8bbe\u7f6e\u5df2\u66f4\u65b0`);\n    })\n    .catch(error => {\n        console.error(`${device}\u8bbe\u7f6e\u66f4\u65b0\u5931\u8d25:`, error);\n        createAlert('error', `${device === 'pump' ? '\u6c34\u6cf5' : '\u98ce\u6247'}\u8bbe\u7f6e\u66f4\u65b0\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u7f51\u7edc\u8fde\u63a5`);\n    });\n}\n\n\/**\n * \u83b7\u53d6\u4f20\u611f\u5668\u6570\u636e\n * \u529f\u80fd\uff1a\u4ece\u670d\u52a1\u5668\u83b7\u53d6\u6700\u65b0\u7684\u4f20\u611f\u5668\u6570\u636e\n *\/\nfunction fetchSensorData() {\n    fetch(`${API_BASE_URL}\/sensors`)\n        .then(response => {\n            if (!response.ok) {\n                throw new Error('\u7f51\u7edc\u54cd\u5e94\u5f02\u5e38');\n            }\n            return response.json();\n        })\n        .then(data => {\n            \/\/ \u66f4\u65b0\u4f20\u611f\u5668\u6570\u636e\n            sensorData = data;\n            \n            \/\/ \u66f4\u65b0\u663e\u793a\n            updateSensorDisplay();\n            \n            \/\/ \u66f4\u65b0\u5386\u53f2\u6570\u636e\n            updateHistoryData();\n            \n            \/\/ \u68c0\u67e5\u8b66\u62a5\n            checkAlerts();\n            \n            \/\/ \u68c0\u67e5\u81ea\u52a8\u5316\u89c4\u5219\n            checkAutomationRules();\n        })\n        .catch(error => {\n            console.error('\u83b7\u53d6\u4f20\u611f\u5668\u6570\u636e\u5931\u8d25:', error);\n            createAlert('error', '\u83b7\u53d6\u4f20\u611f\u5668\u6570\u636e\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u7f51\u7edc\u8fde\u63a5');\n            \n            \/\/ \u5982\u679c\u65e0\u6cd5\u8fde\u63a5\u670d\u52a1\u5668\uff0c\u4f7f\u7528\u6a21\u62df\u6570\u636e\n            simulateSensorData();\n        });\n}\n\n\/**\n * \u6a21\u62df\u4f20\u611f\u5668\u6570\u636e\n * \u529f\u80fd\uff1a\u5f53\u65e0\u6cd5\u8fde\u63a5\u670d\u52a1\u5668\u65f6\uff0c\u751f\u6210\u6a21\u62df\u7684\u4f20\u611f\u5668\u6570\u636e\u6ce2\u52a8\n *\/\nfunction simulateSensorData() {\n    \/\/ \u6a21\u62df\u6570\u636e\u6ce2\u52a8\n    sensorData.temperature = Math.round((sensorData.temperature + (Math.random() * 2 - 1)) * 10) \/ 10;\n    sensorData.humidity = Math.round((sensorData.humidity + (Math.random() * 4 - 2)) * 10) \/ 10;\n    sensorData.light = Math.round(sensorData.light + (Math.random() * 100 - 50));\n    sensorData.soilMoisture = Math.round((sensorData.soilMoisture + (Math.random() * 3 - 1.5)) * 10) \/ 10;\n    sensorData.co2Level = Math.round(sensorData.co2Level + (Math.random() * 20 - 10));\n    \n    \/\/ \u786e\u4fdd\u6570\u636e\u5728\u5408\u7406\u8303\u56f4\u5185\n    sensorData.temperature = Math.max(10, Math.min(40, sensorData.temperature));\n    sensorData.humidity = Math.max(20, Math.min(90, sensorData.humidity));\n    sensorData.light = Math.max(100, Math.min(2500, sensorData.light));\n    sensorData.soilMoisture = Math.max(20, Math.min(80, sensorData.soilMoisture));\n    sensorData.co2Level = Math.max(300, Math.min(1000, sensorData.co2Level));\n    \n    \/\/ \u66f4\u65b0\u663e\u793a\n    updateSensorDisplay();\n}\n\n\/**\n * \u5f00\u59cb\u6570\u636e\u8f6e\u8be2\n * \u529f\u80fd\uff1a\u542f\u52a8\u5b9a\u671f\u4ece\u670d\u52a1\u5668\u83b7\u53d6\u6570\u636e\u7684\u8f6e\u8be2\u673a\u5236\n *\/\nfunction startDataPolling() {\n    \/\/ \u521d\u59cb\u66f4\u65b0\n    fetchSensorData();\n    \n    \/\/ \u8bbe\u7f6e\u5b9a\u65f6\u66f4\u65b0\uff08\u6bcf5\u79d2\u66f4\u65b0\u4e00\u6b21\uff09\n    setInterval(fetchSensorData, 5000);\n}\n\n\/\/ ===============================================================================\n\/\/ \u9875\u9762\u52a0\u8f7d\u5b8c\u6210\u540e\u521d\u59cb\u5316\n\/\/ ===============================================================================\n\n\/\/ \u9875\u9762\u52a0\u8f7d\u5b8c\u6210\u540e\u6267\u884c\u521d\u59cb\u5316\u51fd\u6570\nwindow.addEventListener('DOMContentLoaded', init);\n\n\/\/ \u6dfb\u52a0\u5168\u5c40\u51fd\u6570\u4ee5\u4fbfHTML\u4e2d\u7684onclick\u8c03\u7528\nwindow.editRule = editRule;\nwindow.deleteRule = deleteRule;<\/code><\/pre>\n\n\n\n<p>\u8fd9\u4efd\u8be6\u7ec6\u6ce8\u91ca\u6db5\u76d6\u4e86\u6574\u4e2a\u519c\u4e1a\u76d1\u63a7\u7cfb\u7edfJavaScript\u4ee3\u7801\u7684\u6240\u6709\u529f\u80fd\u548c\u903b\u8f91\u3002\u4e3b\u8981\u5305\u62ec\u4ee5\u4e0b\u51e0\u4e2a\u90e8\u5206\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>API\u914d\u7f6e\u548c\u5168\u5c40\u53d8\u91cf\u5b9a\u4e49<\/strong>\uff1a\u5305\u62ec\u670d\u52a1\u5668\u5730\u5740\u3001\u4f20\u611f\u5668\u6570\u636e\u3001\u8bbe\u5907\u72b6\u6001\u3001\u8b66\u62a5\u9608\u503c\u7b49\u5168\u5c40\u53d8\u91cf<\/li>\n\n\n\n<li><strong>\u521d\u59cb\u5316\u548c\u6838\u5fc3\u529f\u80fd\u51fd\u6570<\/strong>\uff1a\u9875\u9762\u521d\u59cb\u5316\u3001\u4f20\u611f\u5668\u6570\u636e\u66f4\u65b0\u3001\u8bbe\u5907\u63a7\u5236\u7b49\u6838\u5fc3\u529f\u80fd<\/li>\n\n\n\n<li><strong>\u8b66\u62a5\u7cfb\u7edf\u76f8\u5173\u51fd\u6570<\/strong>\uff1a\u8b66\u62a5\u68c0\u6d4b\u3001\u521b\u5efa\u3001\u663e\u793a\u7b49\u529f\u80fd<\/li>\n\n\n\n<li><strong>\u5386\u53f2\u6570\u636e\u56fe\u8868\u76f8\u5173\u51fd\u6570<\/strong>\uff1aChart.js\u56fe\u8868\u521d\u59cb\u5316\u3001\u6570\u636e\u66f4\u65b0\u7b49\u529f\u80fd<\/li>\n\n\n\n<li><strong>\u81ea\u52a8\u5316\u89c4\u5219\u76f8\u5173\u51fd\u6570<\/strong>\uff1a\u89c4\u5219\u521b\u5efa\u3001\u7f16\u8f91\u3001\u5220\u9664\u3001\u6267\u884c\u7b49\u529f\u80fd<\/li>\n\n\n\n<li><strong>\u670d\u52a1\u5668\u901a\u4fe1\u76f8\u5173\u51fd\u6570<\/strong>\uff1a\u4e0e\u540e\u7aefAPI\u901a\u4fe1\uff0c\u53d1\u9001\u8bbe\u5907\u63a7\u5236\u6307\u4ee4\u548c\u83b7\u53d6\u4f20\u611f\u5668\u6570\u636e<\/li>\n\n\n\n<li><strong>\u9875\u9762\u52a0\u8f7d\u521d\u59cb\u5316<\/strong>\uff1a\u8bbe\u7f6eDOMContentLoaded\u4e8b\u4ef6\u76d1\u542c\u5668\uff0c\u786e\u4fdd\u9875\u9762\u52a0\u8f7d\u5b8c\u6210\u540e\u6267\u884c\u521d\u59cb\u5316<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>\u540e\u7aef\u9700\u8981\u5b9e\u73b0\u4ee5\u4e0bAPI\u7aef\u70b9\uff1a \u8fd9\u4efd\u8be6\u7ec6\u6ce8\u91ca\u6db5\u76d6\u4e86\u6574\u4e2a\u519c\u4e1a\u76d1\u63a7\u7cfb\u7edfJavaScript\u4ee3\u7801\u7684\u6240\u6709\u529f\u80fd\u548c\u903b\u8f91\u3002\u4e3b\u8981 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":17,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[27,25,14],"tags":[28,12,29],"class_list":["post-113","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ajax","category-25","category-14","tag-web","tag-12","tag-29"],"_links":{"self":[{"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/posts\/113","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/comments?post=113"}],"version-history":[{"count":2,"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/posts\/113\/revisions"}],"predecessor-version":[{"id":115,"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/posts\/113\/revisions\/115"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/media\/17"}],"wp:attachment":[{"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=113"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/categories?post=113"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.thebugmuxi.xyz\/index.php\/wp-json\/wp\/v2\/tags?post=113"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}