76個工業組件庫示例匯總
開放式PLC編程環境
這是一個開放式PLC編程環境的自定義組件,提供了一個面向智能倉儲堆垛機控制的開放式PLC編程環境。該組件采用蘋果科技風格設計,支持多廠商PLC硬件,具有直觀的界面和豐富的功能。
功能特點
- 多語言編程支持:梯形圖(LD)、結構化文本(ST)和功能塊圖(FBD)三種PLC編程語言
- 多廠商硬件兼容:支持西門子、AB、三菱、歐姆龍和施耐德等主流PLC廠商
- 蘋果科技風格界面:簡潔美觀的UI設計,符合現代工業審美
- 專業編程工具:集成Monaco編輯器,提供代碼高亮、自動完成和錯誤檢查等功能
- 實時變量監控:在線查看和跟蹤PLC變量狀態變化
- 堆垛機可視化:直觀展示堆垛機運行狀態和位置信息
- 告警管理系統:實時顯示系統告警信息和處理狀態
- 自適應布局:響應式設計,適應不同屏幕尺寸
- 動態交互效果:流暢的動畫效果,提升用戶體驗
界面區域說明
組件包含以下主要功能區域:
- 頂部工具欄:包含編程語言選擇、PLC廠商選擇和文件操作選項
- 編程區域:
- 梯形圖編輯器:可視化梯形圖編程環境
- 文本編輯器:用于ST語言和FBD編程
- 標簽頁管理:多程序文件的標簽頁切換
- 變量監控區:
- 變量列表:顯示和篩選當前PLC變量
- 實時值更新:動態顯示變量的當前值
- 堆垛機狀態區:
- 可視化動畫:顯示堆垛機位置和動作
- 實時指標:當前速度、位置和載重等關鍵參數
- 告警信息區:
- 告警顯示:實時系統告警和錯誤信息
- 告警處理:告警確認和處理功能
連接實際硬件
要將組件連接到實際的PLC硬件,請按照以下步驟操作:
- 點擊頂部菜單按鈕,打開硬件配置對話框
- 選擇相應的PLC型號和通信參數
- 配置I/O模塊和通信地址
- 保存配置后重新連接
組件默認使用模擬數據。要連接實際硬件,需要修改script.js
中的initVariableSimulation
函數,實現與實際PLC的通信。
編程示例
組件內置了幾個堆垛機控制的編程示例:
- 主程序:主控制循環和基本功能
- 子程序1:堆垛機位置控制邏輯
- 配置:系統參數和硬件配置
這些示例可以作為開發實際應用程序的起點。
自定義選項
您可以通過修改組件代碼來自定義以下內容:
- 界面主題:在CSS中修改顏色變量,調整組件的視覺風格
- 編程功能:添加新的編程工具或語言支持
- 變量監控:調整變量的顯示方式和更新頻率
- 堆垛機動畫:根據實際設備特性調整可視化效果
- 告警規則:定制告警觸發條件和處理流程
瀏覽器兼容性
該組件使用了現代Web技術,建議在以下瀏覽器版本中使用:
- Chrome 60+
- Firefox 55+
- Safari 11+
- Edge 16+
注意事項
- 組件默認使用模擬數據,實際應用時需要連接到真實的PLC數據源
- 為保證最佳性能,請在實際部署環境中優化數據刷新頻率
- 使用前請確認所選PLC廠商的通信驅動是否可用
項目結構
效果展示
源碼
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>開放式PLC編程環境</title><link rel="stylesheet" href="styles.css"><!-- 添加Monaco編輯器 --><script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/loader.js"></script>
</head>
<body><div id="plc-programming-environment"><header class="ppe-header"><div class="ppe-logo"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M21 7V17C21 19.2091 19.2091 21 17 21H7C4.79086 21 3 19.2091 3 17V7C3 4.79086 4.79086 3 7 3H17C19.2091 3 21 4.79086 21 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M7 9H10M7 12H13M7 15H10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M15 9L17 9M15 12L17 12M15 15L17 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg><span>智能倉儲堆垛機控制</span></div><div class="ppe-actions"><button class="ppe-btn ppe-btn-primary" id="deploy-btn"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg>部署程序</button><button class="ppe-btn" id="menu-btn"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 18H21M3 12H21M3 6H21" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>菜單</button></div></header><div class="ppe-content"><!-- 工具欄 --><div class="ppe-toolbar"><div class="ppe-toolbar-group"><button class="ppe-tool-btn active" data-mode="ladder"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 5v14M20 5v14M4 12h16M8 5v7M16 12v7"></path></svg>梯形圖</button><button class="ppe-tool-btn" data-mode="st"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"></path></svg>ST語言</button><button class="ppe-tool-btn" data-mode="fbd"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="8" width="6" height="8" rx="2"></rect><rect x="15" y="8" width="6" height="8" rx="2"></rect><path d="M9 12h6"></path></svg>功能塊</button></div><div class="ppe-toolbar-group"><select id="plc-vendor" class="ppe-select"><option value="siemens">西門子S7</option><option value="ab">AB CompactLogix</option><option value="mitsubishi">三菱FX5U</option><option value="omron">歐姆龍NX</option><option value="schneider">施耐德M340</option></select></div><div class="ppe-toolbar-group"><button class="ppe-tool-btn" id="save-btn"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>保存</button><button class="ppe-tool-btn" id="download-btn"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>下載</button></div></div><!-- 主面板區域 --><div class="ppe-main-panels"><!-- 編程區域 --><div class="ppe-panel ppe-editor-panel"><div class="ppe-panel-header"><h2>程序編輯區</h2><div class="ppe-panel-actions"><button class="ppe-btn ppe-btn-sm" id="verify-btn">驗證</button><button class="ppe-btn ppe-btn-sm" id="format-btn">格式化</button></div></div><div class="ppe-panel-body"><div class="ppe-tabs"><div class="ppe-tab active" data-tab="main">主程序</div><div class="ppe-tab" data-tab="subr1">子程序1</div><div class="ppe-tab" data-tab="config">配置</div><div class="ppe-tab-add">+</div></div><div class="ppe-editor-container"><div id="monaco-editor" class="ppe-editor-area"></div><div class="ppe-ladder-editor" style="display: none;"><div class="ppe-ladder-toolbar"><button class="ppe-ladder-btn" data-element="contact-no">常開觸點</button><button class="ppe-ladder-btn" data-element="contact-nc">常閉觸點</button><button class="ppe-ladder-btn" data-element="coil">線圈</button><button class="ppe-ladder-btn" data-element="timer">定時器</button><button class="ppe-ladder-btn" data-element="counter">計數器</button></div><div class="ppe-ladder-grid" id="ladder-grid"><!-- 梯形圖網格區域 --></div></div></div></div></div><!-- 右側面板 --><div class="ppe-side-panels"><!-- 變量監控面板 --><div class="ppe-panel ppe-variables-panel"><div class="ppe-panel-header"><h2>變量監控</h2><div class="ppe-panel-actions"><button class="ppe-btn ppe-btn-sm" id="refresh-vars-btn">刷新</button></div></div><div class="ppe-panel-body"><div class="ppe-search-bar"><input type="text" placeholder="搜索變量..." class="ppe-search-input"></div><div class="ppe-variables-list"><div class="ppe-variable-item"><div class="ppe-variable-name">S_StartButton</div><div class="ppe-variable-type">BOOL</div><div class="ppe-variable-value">TRUE</div></div><div class="ppe-variable-item"><div class="ppe-variable-name">S_StopButton</div><div class="ppe-variable-type">BOOL</div><div class="ppe-variable-value">FALSE</div></div><div class="ppe-variable-item"><div class="ppe-variable-name">Position_X</div><div class="ppe-variable-type">REAL</div><div class="ppe-variable-value">145.32</div></div><div class="ppe-variable-item"><div class="ppe-variable-name">Position_Y</div><div class="ppe-variable-type">REAL</div><div class="ppe-variable-value">87.65</div></div><div class="ppe-variable-item"><div class="ppe-variable-name">Position_Z</div><div class="ppe-variable-type">REAL</div><div class="ppe-variable-value">22.41</div></div><div class="ppe-variable-item"><div class="ppe-variable-name">Speed_X</div><div class="ppe-variable-type">REAL</div><div class="ppe-variable-value">0.75</div></div><div class="ppe-variable-item"><div class="ppe-variable-name">CurrentShelf</div><div class="ppe-variable-type">INT</div><div class="ppe-variable-value">12</div></div><div class="ppe-variable-item"><div class="ppe-variable-name">TargetShelf</div><div class="ppe-variable-type">INT</div><div class="ppe-variable-value">18</div></div></div></div></div><!-- 堆垛機監控面板 --><div class="ppe-panel ppe-stacker-panel"><div class="ppe-panel-header"><h2>堆垛機狀態</h2><span class="ppe-status-badge status-normal">運行中</span></div><div class="ppe-panel-body"><div class="ppe-stacker-animation"><div class="ppe-storage-rack"><!-- 儲物架動畫區域 --></div><div class="ppe-stacker" id="stacker-animation"><!-- 堆垛機動畫 --></div></div><div class="ppe-stacker-metrics"><div class="ppe-metric"><div class="ppe-metric-value">5.4 m/s</div><div class="ppe-metric-label">當前速度</div></div><div class="ppe-metric"><div class="ppe-metric-value">B12-34</div><div class="ppe-metric-label">當前位置</div></div><div class="ppe-metric"><div class="ppe-metric-value">45 kg</div><div class="ppe-metric-label">載重</div></div></div></div></div></div></div><!-- 底部告警區域 --><div class="ppe-panel ppe-alerts-panel"><div class="ppe-panel-header"><h2>系統告警</h2><div class="ppe-badge">2</div></div><div class="ppe-panel-body"><div id="alerts-list" class="ppe-alerts-list"><div class="ppe-alert-item alert-warning"><div class="ppe-alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg></div><div class="ppe-alert-content"><div class="ppe-alert-message">堆垛機X軸接近極限位置,請檢查程序邏輯</div><div class="ppe-alert-time">10:45:21</div></div><div class="ppe-alert-actions"><button class="ppe-alert-btn">查看</button></div></div><div class="ppe-alert-item alert-error"><div class="ppe-alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg></div><div class="ppe-alert-content"><div class="ppe-alert-message">PLC通信中斷,請檢查網絡連接</div><div class="ppe-alert-time">10:42:53</div></div><div class="ppe-alert-actions"><button class="ppe-alert-btn">查看</button></div></div></div></div></div></div><!-- 模態框 - 硬件配置 --><div id="hardware-config-modal" class="ppe-modal"><div class="ppe-modal-content"><div class="ppe-modal-header"><h3>PLC硬件配置</h3><button class="ppe-modal-close">×</button></div><div class="ppe-modal-body"><div class="ppe-form-group"><label>PLC型號</label><select class="ppe-select"><option>西門子 S7-1200</option><option>西門子 S7-1500</option><option>AB CompactLogix 5380</option><option>AB ControlLogix 5580</option><option>三菱 FX5U-32M</option></select></div><div class="ppe-form-group"><label>IP地址</label><input type="text" class="ppe-input" value="192.168.1.100"></div><div class="ppe-form-group"><label>通信端口</label><input type="number" class="ppe-input" value="102"></div><div class="ppe-form-group"><label>刷新率 (ms)</label><input type="number" class="ppe-input" value="100"></div><div class="ppe-hardware-modules"><h4>I/O模塊</h4><div class="ppe-module-list"><div class="ppe-module-item"><div class="ppe-module-header"><span>數字量輸入模塊 DI16</span><span>Slot 1</span></div><div class="ppe-module-body">16通道,24V DC</div></div><div class="ppe-module-item"><div class="ppe-module-header"><span>數字量輸出模塊 DO16</span><span>Slot 2</span></div><div class="ppe-module-body">16通道,繼電器輸出</div></div><div class="ppe-module-item"><div class="ppe-module-header"><span>模擬量輸入模塊 AI8</span><span>Slot 3</span></div><div class="ppe-module-body">8通道,±10V/4-20mA</div></div><div class="ppe-module-item"><div class="ppe-module-header"><span>模擬量輸出模塊 AO4</span><span>Slot 4</span></div><div class="ppe-module-body">4通道,±10V/4-20mA</div></div></div><button class="ppe-btn ppe-btn-sm ppe-btn-add">添加模塊</button></div></div><div class="ppe-modal-footer"><button class="ppe-btn ppe-btn-secondary modal-close-btn">取消</button><button class="ppe-btn ppe-btn-primary">保存配置</button></div></div></div></div><script src="script.js"></script>
</body>
</html>
styles.css
/* 開放式PLC編程環境 - 蘋果科技風格 */:root {/* 顏色變量 - 蘋果風格 */--background: #F5F5F7;--card-bg: #FFFFFF;--primary-text: #1D1D1F;--secondary-text: #86868B;--accent-blue: #0066CC;--accent-green: #34C759;--accent-orange: #FF9500;--accent-red: #FF3B30;--accent-purple: #5E5CE6;--border-color: #D2D2D7;--grid-color: #E8E8ED;/* 陰影 */--card-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);--header-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);/* 尺寸變量 */--header-height: 60px;--toolbar-height: 48px;--panels-gap: 16px;--border-radius: 10px;--input-height: 32px;
}/* 基礎樣式 */
#plc-programming-environment {font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', sans-serif;color: var(--primary-text);background-color: var(--background);display: flex;flex-direction: column;height: 100%;width: 100%;position: relative;box-sizing: border-box;margin: 0;padding: 0;overflow: hidden;
}#plc-programming-environment * {box-sizing: border-box;
}/* 頂部導航欄 */
.ppe-header {height: var(--header-height);background-color: var(--card-bg);border-bottom: 1px solid var(--border-color);display: flex;justify-content: space-between;align-items: center;padding: 0 20px;box-shadow: var(--header-shadow);z-index: 10;
}.ppe-logo {display: flex;align-items: center;gap: 10px;font-weight: 500;
}.ppe-logo svg {color: var(--accent-blue);
}.ppe-actions {display: flex;gap: 12px;
}/* 主內容區域 */
.ppe-content {display: flex;flex-direction: column;flex: 1;height: calc(100% - var(--header-height));overflow: hidden;
}/* 工具欄 */
.ppe-toolbar {height: var(--toolbar-height);background-color: var(--card-bg);border-bottom: 1px solid var(--border-color);display: flex;align-items: center;padding: 0 16px;gap: 20px;
}.ppe-toolbar-group {display: flex;align-items: center;gap: 8px;
}.ppe-toolbar-group:not(:last-child) {padding-right: 20px;border-right: 1px solid var(--border-color);
}.ppe-tool-btn {display: flex;align-items: center;gap: 6px;padding: 5px 10px;border-radius: 6px;border: none;background-color: transparent;color: var(--primary-text);font-size: 13px;cursor: pointer;transition: all 0.2s;
}.ppe-tool-btn:hover {background-color: rgba(0, 0, 0, 0.05);
}.ppe-tool-btn.active {background-color: rgba(0, 102, 204, 0.1);color: var(--accent-blue);
}.ppe-tool-btn svg {color: var(--secondary-text);
}.ppe-tool-btn.active svg {color: var(--accent-blue);
}/* 選擇器樣式 */
.ppe-select {height: var(--input-height);padding: 0 12px;border-radius: 6px;border: 1px solid var(--border-color);background-color: var(--card-bg);color: var(--primary-text);font-size: 13px;outline: none;appearance: none;background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2386868B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");background-repeat: no-repeat;background-position: right 12px center;padding-right: 32px;
}.ppe-select:hover {border-color: var(--secondary-text);
}.ppe-select:focus {border-color: var(--accent-blue);box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2);
}/* 主面板區域 - 使用Grid布局 */
.ppe-main-panels {display: grid;grid-template-columns: 1fr 320px;grid-gap: var(--panels-gap);padding: var(--panels-gap);flex: 1;overflow: hidden;
}/* 面板樣式 */
.ppe-panel {background-color: var(--card-bg);border-radius: var(--border-radius);box-shadow: var(--card-shadow);display: flex;flex-direction: column;overflow: hidden;
}.ppe-panel-header {display: flex;justify-content: space-between;align-items: center;padding: 12px 16px;border-bottom: 1px solid var(--border-color);
}.ppe-panel-header h2 {margin: 0;font-size: 14px;font-weight: 600;
}.ppe-panel-actions {display: flex;gap: 8px;
}.ppe-panel-body {flex: 1;overflow: hidden;display: flex;flex-direction: column;
}/* 編輯器面板 */
.ppe-editor-panel {height: 100%;
}/* 標簽頁樣式 */
.ppe-tabs {display: flex;border-bottom: 1px solid var(--border-color);background-color: #FAFAFA;
}.ppe-tab {padding: 8px 16px;font-size: 13px;cursor: pointer;border-right: 1px solid var(--border-color);transition: background-color 0.2s;
}.ppe-tab:hover {background-color: rgba(0, 0, 0, 0.02);
}.ppe-tab.active {background-color: var(--card-bg);border-bottom: 2px solid var(--accent-blue);
}.ppe-tab-add {padding: 8px 12px;font-size: 14px;cursor: pointer;color: var(--secondary-text);display: flex;align-items: center;justify-content: center;
}.ppe-tab-add:hover {color: var(--accent-blue);background-color: rgba(0, 0, 0, 0.02);
}/* 編輯器容器 */
.ppe-editor-container {flex: 1;overflow: hidden;position: relative;
}.ppe-editor-area {height: 100%;width: 100%;
}/* 梯形圖編輯器 */
.ppe-ladder-editor {display: flex;flex-direction: column;height: 100%;max-height: 100%;
}.ppe-ladder-toolbar {display: flex;padding: 8px;border-bottom: 1px solid var(--border-color);gap: 8px;background-color: var(--card-bg);flex-shrink: 0;
}.ppe-ladder-btn {padding: 4px 8px;font-size: 12px;border: 1px solid var(--border-color);border-radius: 4px;background-color: var(--card-bg);cursor: pointer;transition: all 0.2s;
}.ppe-ladder-btn:hover {background-color: var(--background);border-color: var(--secondary-text);
}.ppe-ladder-grid {flex: 1;overflow-y: auto;overflow-x: auto;background-color: var(--card-bg);background-image: linear-gradient(var(--grid-color) 1px, transparent 1px),linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);background-size: 20px 20px;padding: 8px;max-height: 400px; /* 設置最大高度為400px */position: relative;
}/* 梯形圖元素樣式 */
.ladder-element {position: absolute;width: 100px;height: 40px;display: flex;align-items: center;justify-content: center;
}.ladder-function-block {padding: 5px 10px;border: 1px solid var(--primary-text);border-radius: 4px;background-color: var(--background);font-size: 12px;min-width: 80px;text-align: center;
}.ladder-label {position: absolute;font-size: 10px;top: -15px;width: 100%;text-align: center;color: var(--secondary-text);
}/* 右側面板 */
.ppe-side-panels {display: grid;grid-template-rows: 1fr 1fr;grid-gap: var(--panels-gap);height: 100%;max-height: 100%;
}/* 變量監控面板 */
.ppe-variables-panel {max-height: 100%;
}.ppe-search-bar {padding: 12px 16px;border-bottom: 1px solid var(--border-color);
}.ppe-search-input {width: 100%;height: var(--input-height);padding: 0 12px;border-radius: 6px;border: 1px solid var(--border-color);background-color: var(--background);font-size: 13px;outline: none;
}.ppe-search-input:focus {border-color: var(--accent-blue);box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2);
}.ppe-variables-list {overflow-y: auto;padding: 8px;max-height: calc(100% - 57px);
}.ppe-variable-item {display: flex;padding: 8px 12px;border-radius: 6px;background-color: var(--background);margin-bottom: 6px;transition: all 0.2s;
}.ppe-variable-item:hover {background-color: rgba(0, 102, 204, 0.05);
}.ppe-variable-name {flex: 3;font-size: 13px;font-weight: 500;
}.ppe-variable-type {flex: 1;font-size: 12px;color: var(--secondary-text);text-align: center;
}.ppe-variable-value {flex: 1;font-size: 13px;text-align: right;font-weight: 500;color: var(--accent-blue);
}/* 堆垛機狀態面板 */
.ppe-stacker-panel {display: flex;flex-direction: column;
}.ppe-stacker-animation {flex: 1;position: relative;background-color: #F0F2F5;overflow: hidden;min-height: 140px;
}.ppe-storage-rack {position: absolute;top: 10px;left: 10px;right: 10px;bottom: 10px;background-image: repeating-linear-gradient(to right,rgba(0, 0, 0, 0.1) 0px,rgba(0, 0, 0, 0.1) 1px,transparent 1px,transparent 40px),repeating-linear-gradient(to bottom,rgba(0, 0, 0, 0.1) 0px,rgba(0, 0, 0, 0.1) 1px,transparent 1px,transparent 40px);border: 1px solid rgba(0, 0, 0, 0.2);
}.ppe-stacker {position: absolute;width: 30px;height: 40px;background-color: var(--accent-blue);border-radius: 4px;left: 150px;top: 60px;transition: all 0.5s cubic-bezier(0.22, 1, 0.36, 1);box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}.ppe-stacker:before {content: '';position: absolute;width: 10px;height: 5px;background-color: #FFD700;bottom: 0;left: 10px;border-radius: 2px 2px 0 0;transform-origin: bottom center;animation: move-fork 2s ease-in-out infinite;
}@keyframes move-fork {0%, 100% { transform: scaleY(1); }50% { transform: scaleY(2); }
}.ppe-stacker-metrics {display: flex;justify-content: space-between;padding: 12px 16px;background-color: var(--card-bg);border-top: 1px solid var(--border-color);
}.ppe-metric {text-align: center;flex: 1;
}.ppe-metric-value {font-size: 16px;font-weight: 600;
}.ppe-metric-label {font-size: 12px;color: var(--secondary-text);margin-top: 2px;
}/* 按鈕樣式 */
.ppe-btn {display: flex;align-items: center;gap: 6px;padding: 8px 12px;border-radius: 6px;border: 1px solid var(--border-color);background-color: var(--card-bg);color: var(--primary-text);font-size: 14px;cursor: pointer;transition: all 0.2s;
}.ppe-btn:hover {background-color: var(--background);
}.ppe-btn-sm {padding: 4px 10px;font-size: 13px;
}.ppe-btn-primary {background-color: var(--accent-blue);border-color: var(--accent-blue);color: white;
}.ppe-btn-primary:hover {background-color: #0055B3;border-color: #0055B3;
}.ppe-btn-secondary {background-color: var(--background);
}.ppe-btn-add {background-color: transparent;color: var(--accent-blue);border-style: dashed;
}.ppe-btn-add:hover {background-color: rgba(0, 102, 204, 0.05);
}/* 告警面板 */
.ppe-alerts-panel {margin: 0 var(--panels-gap) var(--panels-gap);height: 120px;flex-shrink: 0;
}.ppe-alerts-list {display: flex;flex-direction: column;gap: 8px;padding: 8px;overflow-y: auto;max-height: 100%;
}/* 告警項 */
.ppe-alert-item {display: flex;align-items: center;gap: 10px;background-color: var(--background);border-radius: 6px;padding: 10px 12px;
}.alert-warning {border-left: 3px solid var(--accent-orange);
}.alert-error {border-left: 3px solid var(--accent-red);
}.alert-info {border-left: 3px solid var(--accent-blue);
}.ppe-alert-icon {color: var(--accent-orange);
}.alert-error .ppe-alert-icon {color: var(--accent-red);
}.alert-info .ppe-alert-icon {color: var(--accent-blue);
}.ppe-alert-content {flex: 1;
}.ppe-alert-message {font-size: 13px;
}.ppe-alert-time {font-size: 12px;color: var(--secondary-text);margin-top: 2px;
}.ppe-alert-actions {flex-shrink: 0;
}.ppe-alert-btn {padding: 3px 8px;border-radius: 4px;font-size: 12px;border: 1px solid var(--border-color);background-color: transparent;color: var(--accent-blue);cursor: pointer;
}.ppe-alert-btn:hover {background-color: var(--accent-blue);color: white;
}/* 狀態徽章 */
.ppe-status-badge, .ppe-badge {font-size: 12px;font-weight: 500;padding: 2px 8px;border-radius: 10px;
}.ppe-badge {background-color: var(--accent-red);color: white;min-width: 24px;text-align: center;
}.status-normal {background-color: rgba(52, 199, 89, 0.1);color: var(--accent-green);
}.status-warning {background-color: rgba(255, 149, 0, 0.1);color: var(--accent-orange);
}.status-error {background-color: rgba(255, 59, 48, 0.1);color: var(--accent-red);
}/* 輸入和表單樣式 */
.ppe-input {height: var(--input-height);padding: 0 12px;border-radius: 6px;border: 1px solid var(--border-color);width: 100%;font-size: 13px;outline: none;
}.ppe-input:focus {border-color: var(--accent-blue);box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2);
}.ppe-form-group {margin-bottom: 16px;
}.ppe-form-group label {display: block;font-size: 13px;font-weight: 500;margin-bottom: 6px;
}/* 模態框 */
.ppe-modal {display: none;position: fixed;top: 0;left: 0;width: 100%;height: 100%;background-color: rgba(0, 0, 0, 0.5);z-index: 1000;justify-content: center;align-items: center;
}.ppe-modal.active {display: flex;
}.ppe-modal-content {background-color: var(--card-bg);border-radius: var(--border-radius);width: 90%;max-width: 600px;max-height: 90vh;display: flex;flex-direction: column;overflow: hidden;box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}.ppe-modal-header {display: flex;justify-content: space-between;align-items: center;padding: 15px 20px;border-bottom: 1px solid var(--border-color);
}.ppe-modal-header h3 {margin: 0;font-size: 16px;font-weight: 600;
}.ppe-modal-close {background: none;border: none;font-size: 20px;cursor: pointer;color: var(--secondary-text);
}.ppe-modal-body {padding: 20px;overflow-y: auto;flex: 1;
}.ppe-modal-footer {display: flex;justify-content: flex-end;gap: 10px;padding: 15px 20px;border-top: 1px solid var(--border-color);
}/* 硬件模塊列表 */
.ppe-hardware-modules {margin-top: 20px;
}.ppe-hardware-modules h4 {font-size: 14px;margin-bottom: 10px;
}.ppe-module-list {display: flex;flex-direction: column;gap: 8px;margin-bottom: 12px;
}.ppe-module-item {border: 1px solid var(--border-color);border-radius: 6px;overflow: hidden;
}.ppe-module-header {padding: 8px 12px;background-color: var(--background);display: flex;justify-content: space-between;font-size: 13px;font-weight: 500;
}.ppe-module-body {padding: 8px 12px;font-size: 12px;color: var(--secondary-text);
}/* 動畫效果 */
@keyframes pulse {0% { transform: scale(1); }50% { transform: scale(1.05); }100% { transform: scale(1); }
}@keyframes blink {0%, 100% { opacity: 1; }50% { opacity: 0.5; }
}.animate-pulse {animation: pulse 2s infinite;
}.animate-blink {animation: blink 1.5s infinite;
}/* 響應式布局調整 */
@media (max-width: 992px) {.ppe-main-panels {grid-template-columns: 1fr;grid-template-rows: 1fr auto;}.ppe-side-panels {grid-template-rows: auto auto;}.ppe-stacker-panel {min-height: 250px;}
}@media (max-width: 768px) {.ppe-toolbar {overflow-x: auto;justify-content: flex-start;}.ppe-toolbar-group {flex-shrink: 0;}
}
script.js
// 開放式PLC編程環境 - JavaScript// 全局變量
let editor = null; // Monaco編輯器實例
let currentMode = 'ladder'; // 當前編程模式:ladder(梯形圖), st(結構化文本), fbd(功能塊)
let currentPLCVendor = 'siemens'; // 當前選擇的PLC廠商
let simulationRunning = true; // 模擬運行狀態
let stackerPosition = { x: 150, y: 60 }; // 堆垛機位置
let alertHistory = []; // 告警歷史記錄
let variableValues = {}; // 變量值記錄
let plcConnected = true; // PLC連接狀態// 初始示例代碼 - ST語言
const sampleSTCode = `// 堆垛機控制程序 - 結構化文本
PROGRAM Main
VARS_StartButton AT %I0.0 : BOOL; // 啟動按鈕S_StopButton AT %I0.1 : BOOL; // 停止按鈕S_ResetButton AT %I0.2 : BOOL; // 復位按鈕S_EmergencyStop AT %I0.3 : BOOL; // 緊急停止按鈕Position_X AT %MW10 : REAL; // X軸位置Position_Y AT %MW14 : REAL; // Y軸位置Position_Z AT %MW18 : REAL; // Z軸位置Speed_X AT %MW22 : REAL; // X軸速度Speed_Y AT %MW26 : REAL; // Y軸速度Speed_Z AT %MW30 : REAL; // Z軸速度CurrentShelf AT %MW34 : INT; // 當前貨架位置TargetShelf AT %MW36 : INT; // 目標貨架位置RunStatus AT %Q0.0 : BOOL; // 運行狀態FaultStatus AT %Q0.1 : BOOL; // 故障狀態PLC_Cycle : TIME := T#10MS; // PLC周期時間
END_VAR// 主程序循環
RunStatus := S_StartButton AND NOT S_StopButton AND NOT S_EmergencyStop;
FaultStatus := S_EmergencyStop OR (Position_X > 2000.0);// 如果系統處于運行狀態,執行堆垛機控制
IF RunStatus THEN// 簡單移動邏輯 - 向目標位置移動IF CurrentShelf <> TargetShelf THEN// 計算目標位置坐標Position_X := Position_X + Speed_X * PLC_Cycle;Position_Y := Position_Y + Speed_Y * PLC_Cycle;// 檢查是否到達目標位置IF ABS(Position_X - TargetShelf * 100.0) < 1.0 THENCurrentShelf := TargetShelf;Speed_X := 0.0;Speed_Y := 0.0;END_IF;END_IF;
END_IF;// 復位操作
IF S_ResetButton THENPosition_X := 0.0;Position_Y := 0.0;Position_Z := 0.0;Speed_X := 0.0;Speed_Y := 0.0;Speed_Z := 0.0;CurrentShelf := 0;
END_IF;END_PROGRAM`;// 梯形圖示例數據
const ladderData = [{ type: 'rung_start', x: 0, y: 0 },{ type: 'contact_no', x: 1, y: 0, label: 'S_StartButton' },{ type: 'contact_nc', x: 2, y: 0, label: 'S_StopButton' },{ type: 'contact_nc', x: 3, y: 0, label: 'S_EmergencyStop' },{ type: 'coil', x: 4, y: 0, label: 'RunStatus' },{ type: 'rung_end', x: 5, y: 0 },{ type: 'rung_start', x: 0, y: 1 },{ type: 'contact_no', x: 1, y: 1, label: 'S_EmergencyStop' },{ type: 'coil', x: 4, y: 1, label: 'FaultStatus' },{ type: 'rung_end', x: 5, y: 1 },{ type: 'rung_start', x: 0, y: 2 },{ type: 'contact_no', x: 1, y: 2, label: 'RunStatus' },{ type: 'contact_no', x: 2, y: 2, label: 'TargetNotReached' },{ type: 'function_block', x: 3, y: 2, label: 'MOVE', inputs: ['Speed_X', 'PLC_Cycle'], outputs: ['Position_X'] },{ type: 'rung_end', x: 5, y: 2 },{ type: 'rung_start', x: 0, y: 3 },{ type: 'contact_no', x: 1, y: 3, label: 'S_ResetButton' },{ type: 'function_block', x: 3, y: 3, label: 'RESET', inputs: [], outputs: ['Position_X', 'Position_Y', 'Position_Z'] },{ type: 'rung_end', x: 5, y: 3 },
];// 初始化函數
function initPLCProgrammingEnvironment() {console.log('初始化PLC編程環境...');// 初始化Monaco編輯器initMonacoEditor();// 初始化梯形圖編輯器initLadderEditor();// 初始化事件監聽器setupEventListeners();// 初始化堆垛機動畫initStackerAnimation();// 初始化變量模擬initVariableSimulation();// 更新界面狀態updateUIState();console.log('PLC編程環境初始化完成');
}// 初始化Monaco編輯器
function initMonacoEditor() {require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs' }});require(['vs/editor/editor.main'], function() {// 注冊PLC編程語言monaco.languages.register({ id: 'structuredtext' });// 配置語法高亮monaco.languages.setMonarchTokensProvider('structuredtext', {defaultToken: '',ignoreCase: true,tokenizer: {root: [[/\/\/.*$/, 'comment'],[/\b(PROGRAM|END_PROGRAM|VAR|END_VAR|IF|THEN|ELSE|ELSIF|END_IF|FOR|TO|BY|DO|END_FOR|WHILE|END_WHILE|REPEAT|UNTIL|END_REPEAT|CASE|OF|END_CASE|RETURN|EXIT)\b/, 'keyword'],[/\b(BOOL|INT|DINT|REAL|TIME|STRING|ARRAY|STRUCT|END_STRUCT)\b/, 'type'],[/\b(TRUE|FALSE|NULL)\b/, 'constant'],[/\b(AT|ANY|FROM)\b/, 'modifier'],[/\b(ABS|SQRT|LOG|EXP|SIN|COS|TAN|ASIN|ACOS|ATAN|ADD|SUB|MUL|DIV|MOD)\b/, 'function'],[/\b(AND|OR|NOT|XOR|GT|GE|LT|LE|EQ|NE)\b/, 'operator'],[/\b\d+\.\d*([eE][-+]?\d+)?\b/, 'number.float'],[/\b\d+\b/, 'number'],[/T#[0-9]+[smhd]/, 'timevalue'],[/\%[IQM][XBWDL]?[0-9\.]+/, 'address'],[/"([^"\\]|\\.)*$/, 'string.invalid'],[/"/, { token: 'string.quote', bracket: '@open', next: '@string' }],],string: [[/[^\\"]+/, 'string'],[/\\./, 'string.escape'],[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }]]}});// 創建編輯器editor = monaco.editor.create(document.getElementById('monaco-editor'), {value: sampleSTCode,language: 'structuredtext',theme: 'vs-light',automaticLayout: true,minimap: {enabled: false}});// 隱藏編輯器,顯示梯形圖編輯器document.getElementById('monaco-editor').style.display = 'none';document.querySelector('.ppe-ladder-editor').style.display = 'flex';});
}// 初始化梯形圖編輯器
function initLadderEditor() {const ladderGrid = document.getElementById('ladder-grid');// 清空現有內容ladderGrid.innerHTML = '';// 創建網格背景const gridSize = 20;const cols = 10;const rows = 15;// 繪制梯形圖ladderData.forEach(element => {const elementDiv = document.createElement('div');elementDiv.className = `ladder-element ladder-${element.type}`;elementDiv.style.left = `${element.x * gridSize * 5}px`;elementDiv.style.top = `${element.y * gridSize * 3}px`;if (element.label) {const labelDiv = document.createElement('div');labelDiv.className = 'ladder-label';labelDiv.textContent = element.label;elementDiv.appendChild(labelDiv);}// 根據類型設置具體內容if (element.type === 'contact_no') {elementDiv.innerHTML += `<svg viewBox="0 0 50 20" xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="10" x2="15" y2="10" stroke="black" /><line x1="35" y1="10" x2="50" y2="10" stroke="black" /><line x1="15" y1="0" x2="15" y2="20" stroke="black" /><line x1="35" y1="0" x2="35" y2="20" stroke="black" /></svg>`;} else if (element.type === 'contact_nc') {elementDiv.innerHTML += `<svg viewBox="0 0 50 20" xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="10" x2="15" y2="10" stroke="black" /><line x1="35" y1="10" x2="50" y2="10" stroke="black" /><line x1="15" y1="0" x2="15" y2="20" stroke="black" /><line x1="35" y1="0" x2="35" y2="20" stroke="black" /><line x1="15" y1="5" x2="35" y2="5" stroke="black" /></svg>`;} else if (element.type === 'coil') {elementDiv.innerHTML += `<svg viewBox="0 0 50 20" xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="10" x2="10" y2="10" stroke="black" /><line x1="40" y1="10" x2="50" y2="10" stroke="black" /><circle cx="25" cy="10" r="15" fill="none" stroke="black" /></svg>`;} else if (element.type === 'function_block') {const blockDiv = document.createElement('div');blockDiv.className = 'ladder-function-block';blockDiv.textContent = element.label || 'FUNC';elementDiv.appendChild(blockDiv);} else if (element.type === 'rung_start') {elementDiv.innerHTML += `<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="10" x2="20" y2="10" stroke="black" stroke-width="2" /></svg>`;} else if (element.type === 'rung_end') {elementDiv.innerHTML += `<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="10" x2="20" y2="10" stroke="black" stroke-width="2" /></svg>`;}ladderGrid.appendChild(elementDiv);});
}// 初始化事件監聽器
function setupEventListeners() {// 編程模式切換document.querySelectorAll('.ppe-tool-btn[data-mode]').forEach(btn => {btn.addEventListener('click', function() {const mode = this.getAttribute('data-mode');switchProgrammingMode(mode);});});// PLC廠商選擇document.getElementById('plc-vendor').addEventListener('change', function() {currentPLCVendor = this.value;console.log(`切換到PLC廠商: ${currentPLCVendor}`);});// 保存按鈕document.getElementById('save-btn').addEventListener('click', function() {saveProgram();});// 下載按鈕document.getElementById('download-btn').addEventListener('click', function() {downloadProgram();});// 驗證按鈕document.getElementById('verify-btn').addEventListener('click', function() {verifyProgram();});// 格式化按鈕document.getElementById('format-btn').addEventListener('click', function() {formatProgram();});// 部署按鈕document.getElementById('deploy-btn').addEventListener('click', function() {deployProgram();});// 標簽頁切換document.querySelectorAll('.ppe-tab').forEach(tab => {tab.addEventListener('click', function() {const tabId = this.getAttribute('data-tab');switchTab(tabId);});});// 刷新變量按鈕document.getElementById('refresh-vars-btn').addEventListener('click', function() {refreshVariables();});// 變量搜索功能document.querySelector('.ppe-search-input').addEventListener('input', function() {filterVariables(this.value);});// 硬件配置模態框document.getElementById('menu-btn').addEventListener('click', function() {openHardwareConfig();});// 關閉模態框document.querySelector('.ppe-modal-close').addEventListener('click', function() {closeModal('hardware-config-modal');});document.querySelector('.modal-close-btn').addEventListener('click', function() {closeModal('hardware-config-modal');});
}// 切換編程模式
function switchProgrammingMode(mode) {// 更新全局變量currentMode = mode;// 更新按鈕狀態document.querySelectorAll('.ppe-tool-btn[data-mode]').forEach(btn => {btn.classList.remove('active');});document.querySelector(`.ppe-tool-btn[data-mode="${mode}"]`).classList.add('active');// 顯示相應的編輯器if (mode === 'ladder') {document.getElementById('monaco-editor').style.display = 'none';document.querySelector('.ppe-ladder-editor').style.display = 'flex';} else {document.getElementById('monaco-editor').style.display = 'block';document.querySelector('.ppe-ladder-editor').style.display = 'none';// 更新Monaco編輯器語言if (editor) {if (mode === 'st') {monaco.editor.setModelLanguage(editor.getModel(), 'structuredtext');} else {monaco.editor.setModelLanguage(editor.getModel(), 'plaintext');}}}console.log(`切換到${mode}編程模式`);
}// 初始化堆垛機動畫
function initStackerAnimation() {const stacker = document.getElementById('stacker-animation');if (!stacker) return;// 設置初始位置updateStackerPosition();// 設置動畫setInterval(() => {if (simulationRunning) {// 隨機移動堆垛機const newX = Math.max(10, Math.min(280, stackerPosition.x + (Math.random() * 40 - 20)));const newY = Math.max(10, Math.min(120, stackerPosition.y + (Math.random() * 20 - 10)));// 緩慢更新位置stackerPosition.x = stackerPosition.x + (newX - stackerPosition.x) * 0.1;stackerPosition.y = stackerPosition.y + (newY - stackerPosition.y) * 0.1;updateStackerPosition();updateStackerMetrics();}}, 500);
}// 更新堆垛機位置
function updateStackerPosition() {const stacker = document.getElementById('stacker-animation');if (!stacker) return;stacker.style.left = `${stackerPosition.x}px`;stacker.style.top = `${stackerPosition.y}px`;
}// 更新堆垛機指標
function updateStackerMetrics() {// 更新速度指標const speedElement = document.querySelector('.ppe-stacker-metrics .ppe-metric:nth-child(1) .ppe-metric-value');if (speedElement) {const speed = (Math.random() * 2 + 4).toFixed(1);speedElement.textContent = `${speed} m/s`;}// 更新位置指標const positionElement = document.querySelector('.ppe-stacker-metrics .ppe-metric:nth-child(2) .ppe-metric-value');if (positionElement) {const shelfX = String.fromCharCode(65 + Math.floor(stackerPosition.x / 40));const shelfY = Math.floor(stackerPosition.y / 30) + 10;positionElement.textContent = `${shelfX}${shelfY}`;}// 更新載重指標const loadElement = document.querySelector('.ppe-stacker-metrics .ppe-metric:nth-child(3) .ppe-metric-value');if (loadElement) {const load = Math.floor(Math.random() * 30 + 30);loadElement.textContent = `${load} kg`;}
}// 初始化變量模擬
function initVariableSimulation() {// 初始化變量值variableValues = {'S_StartButton': true,'S_StopButton': false,'S_ResetButton': false,'S_EmergencyStop': false,'Position_X': 145.32,'Position_Y': 87.65,'Position_Z': 22.41,'Speed_X': 0.75,'Speed_Y': 0.5,'Speed_Z': 0.3,'CurrentShelf': 12,'TargetShelf': 18,'RunStatus': true,'FaultStatus': false};// 定期更新變量值setInterval(() => {if (simulationRunning) {// 隨機更新一些變量variableValues.Position_X += (Math.random() * 2 - 1) * 0.5;variableValues.Position_Y += (Math.random() * 2 - 1) * 0.3;variableValues.Position_Z += (Math.random() * 2 - 1) * 0.1;variableValues.Speed_X = Math.max(0, Math.min(2, variableValues.Speed_X + (Math.random() * 0.2 - 0.1)));// 檢測異常if (variableValues.Position_X > 200 && Math.random() < 0.05) {addAlert('warning', '堆垛機X軸接近極限位置,請檢查程序邏輯');}// 隨機PLC連接狀態if (Math.random() < 0.01) {plcConnected = !plcConnected;if (!plcConnected) {addAlert('error', 'PLC通信中斷,請檢查網絡連接');} else {addAlert('info', 'PLC通信已恢復');}updateUIState();}// 更新變量顯示updateVariableDisplay();}}, 2000);
}// 更新變量顯示
function updateVariableDisplay() {document.querySelectorAll('.ppe-variable-item').forEach(item => {const name = item.querySelector('.ppe-variable-name').textContent;const valueElement = item.querySelector('.ppe-variable-value');if (name in variableValues) {const value = variableValues[name];if (typeof value === 'boolean') {valueElement.textContent = value ? 'TRUE' : 'FALSE';} else if (typeof value === 'number') {if (Number.isInteger(value)) {valueElement.textContent = value.toString();} else {valueElement.textContent = value.toFixed(2);}} else {valueElement.textContent = value.toString();}}});
}// 過濾變量
function filterVariables(query) {if (!query) {// 顯示所有變量document.querySelectorAll('.ppe-variable-item').forEach(item => {item.style.display = 'flex';});return;}query = query.toLowerCase();document.querySelectorAll('.ppe-variable-item').forEach(item => {const name = item.querySelector('.ppe-variable-name').textContent.toLowerCase();if (name.includes(query)) {item.style.display = 'flex';} else {item.style.display = 'none';}});
}// 刷新變量
function refreshVariables() {console.log('刷新變量...');// 添加閃爍動畫效果document.querySelectorAll('.ppe-variable-item').forEach(item => {item.classList.add('animate-blink');setTimeout(() => {item.classList.remove('animate-blink');}, 1000);});updateVariableDisplay();
}// 添加告警
function addAlert(type, message) {const alert = {id: Date.now(),type: type,message: message,time: new Date()};// 添加到告警歷史alertHistory.unshift(alert);// 限制最大歷史記錄數if (alertHistory.length > 5) {alertHistory.pop();}// 更新告警顯示updateAlertDisplay();// 更新告警徽章const badge = document.querySelector('.ppe-badge');if (badge) {badge.textContent = alertHistory.length;}
}// 更新告警顯示
function updateAlertDisplay() {const alertsList = document.getElementById('alerts-list');if (!alertsList) return;// 清空當前告警alertsList.innerHTML = '';// 添加告警alertHistory.forEach(alert => {let alertClass = 'alert-info';let iconSvg = '';if (alert.type === 'warning') {alertClass = 'alert-warning';iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>';} else if (alert.type === 'error') {alertClass = 'alert-error';iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>';} else {iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>';}// 格式化時間const time = new Intl.DateTimeFormat('zh-CN', {hour: '2-digit',minute: '2-digit',second: '2-digit'}).format(alert.time);const alertHtml = `<div class="ppe-alert-item ${alertClass}"><div class="ppe-alert-icon">${iconSvg}</div><div class="ppe-alert-content"><div class="ppe-alert-message">${alert.message}</div><div class="ppe-alert-time">${time}</div></div><div class="ppe-alert-actions"><button class="ppe-alert-btn">查看</button></div></div>`;alertsList.innerHTML += alertHtml;});// 添加查看按鈕事件document.querySelectorAll('.ppe-alert-btn').forEach((btn, index) => {btn.addEventListener('click', function() {console.log(`查看告警: ${alertHistory[index].message}`);// 這里可以添加彈出詳情的邏輯});});
}// 保存程序
function saveProgram() {console.log('保存程序...');addAlert('info', '程序已保存');
}// 下載程序
function downloadProgram() {console.log('下載程序...');let content = '';let filename = '';if (currentMode === 'ladder') {content = JSON.stringify(ladderData, null, 2);filename = 'ladder_program.json';} else {if (editor) {content = editor.getValue();filename = currentMode === 'st' ? 'program.st' : 'program.txt';}}if (content) {const blob = new Blob([content], { type: 'text/plain' });const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = filename;a.click();URL.revokeObjectURL(url);addAlert('info', `程序已下載為 ${filename}`);}
}// 驗證程序
function verifyProgram() {console.log('驗證程序...');// 模擬驗證過程setTimeout(() => {if (Math.random() < 0.7) {addAlert('info', '程序驗證通過');} else {addAlert('warning', '程序驗證發現潛在問題');}}, 500);
}// 格式化程序
function formatProgram() {console.log('格式化程序...');if (currentMode !== 'ladder' && editor) {// 模擬格式化過程editor.getAction('editor.action.formatDocument').run();addAlert('info', '程序已格式化');} else {addAlert('info', '梯形圖程序無需格式化');}
}// 部署程序
function deployProgram() {console.log('部署程序...');// 檢查PLC連接狀態if (!plcConnected) {addAlert('error', '無法部署程序,PLC未連接');return;}// 模擬部署過程const deployBtn = document.getElementById('deploy-btn');if (deployBtn) {deployBtn.disabled = true;deployBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" class="animate-spin"></svg> 部署中...';}setTimeout(() => {if (deployBtn) {deployBtn.disabled = false;deployBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg> 部署程序';}if (Math.random() < 0.9) {addAlert('info', '程序已成功部署到PLC');} else {addAlert('error', '程序部署失敗,請檢查PLC連接');}}, 2000);
}// 切換標簽頁
function switchTab(tabId) {document.querySelectorAll('.ppe-tab').forEach(tab => {tab.classList.remove('active');});document.querySelector(`.ppe-tab[data-tab="${tabId}"]`).classList.add('active');console.log(`切換到標簽頁: ${tabId}`);// 這里可以添加加載不同標簽頁內容的邏輯
}// 打開硬件配置對話框
function openHardwareConfig() {document.getElementById('hardware-config-modal').style.display = 'flex';
}// 關閉模態框
function closeModal(modalId) {document.getElementById(modalId).style.display = 'none';
}// 更新UI狀態
function updateUIState() {// 更新PLC連接狀態const statusBadge = document.querySelector('.ppe-status-badge');if (statusBadge) {if (plcConnected) {statusBadge.className = 'ppe-status-badge status-normal';statusBadge.textContent = '運行中';} else {statusBadge.className = 'ppe-status-badge status-error';statusBadge.textContent = '已斷開';}}
}// 頁面加載完成后初始化
document.addEventListener('DOMContentLoaded', function() {initPLCProgrammingEnvironment();
});// 導出全局對象
window.plcProgramming = {switchMode: switchProgrammingMode,deployProgram: deployProgram,refreshVariables: refreshVariables,addAlert: addAlert
};