5、开放式PLC梯形图编程组件 - /自动化与控制组件/open-plc-programming
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
};
相关文章:

5、开放式PLC梯形图编程组件 - /自动化与控制组件/open-plc-programming
76个工业组件库示例汇总 开放式PLC编程环境 这是一个开放式PLC编程环境的自定义组件,提供了一个面向智能仓储堆垛机控制的开放式PLC编程环境。该组件采用苹果科技风格设计,支持多厂商PLC硬件,具有直观的界面和丰富的功能。 功能特点 多语…...
数据指标和数据标签
数据指标和数据标签是数据管理与分析中的两个重要概念,它们在用途、形式和应用场景上有显著区别。以下是两者的详细对比: 1. 核心定义 维度数据指标(Data Metrics)数据标签(Data Tags/Labels)定义量化衡量…...

linux中常用的命令(三)
目录 1- ls(查看当前目录下的内容) 2- pwd (查看当前所在的文件夹) 3- cd [目录名](切换文件夹) 4- touch [文件名] (如果文件不存在,新建文件) 5- mkdir[目录名] (创建目录) 6-rm[文件名]&…...

Java 中 AQS 的实现原理
AQS 简介 AQS(全称AbstractQueuedSynchronizer)即抽象同步队列,它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。 由类图可以看到,AQS是一个FIFO的双向队列,其内部通过节点head和tail记录队首和队尾元素࿰…...

『Python学习笔记』ubuntu解决matplotlit中文乱码的问题!
ubuntu解决matplotlit中文乱码的问题! 文章目录 simhei.ttf字体下载链接:http://xiazaiziti.com/210356.html将字体放到合适的地方 sudo cp SimHei.ttf /usr/share/fonts/(base) zkfzkf:~$ fc-list | grep -i "SimHei" /usr/local/share/font…...
docker compose ps 命令
docker compose ps 命令用于列出与 Docker Compose 项目相关的容器及其状态。 docker compose ps 能显示当前项目中所有服务容器的运行状态、端口映射等信息。 语法 docker compose ps [OPTIONS] [SERVICE…] SERVICE(可选):指定要查看状态…...
redis数据结构-04 (HINCRBY、HDEL、HKEYS、HVALS)
哈希操作:HINCRBY、HDEL、HKEYS、HVALS Redis 中的哈希功能极其丰富,让您能够以类似于编程语言中对象的方式存储和检索数据。本课将深入探讨具体的哈希操作,这些操作为操作以下结构中的数据提供了强大的工具: HINCRBY 、 HDEL 、…...

鸿蒙知识总结
判断题 1、 在http模块中,多个请求可以使用同一个httpRequest对象,httpRequest对象可以复用。(错误) 2、订阅dataReceiverProgress响应事件是用来接收HTTP流式响应数据。(错误) 3、ArkTS中变量声明时不需要…...
Ubuntu 22虚拟机【网络故障】快速解决指南
Ubuntu22虚拟机突然无法连接网络了,以下是故障排除步骤记录。 Ubuntu 22虚拟机网络故障快速解决指南 当在虚拟机中安装的 Ubuntu 22 系统出现 ping: connect: 网络不可达 和 ping: www.baidu.com: 域名解析出现暂时性错误的报错时,通常意味着虚拟机无法…...

C++23 新特性:深入解析 std::views::join_with(P2441R2)
文章目录 std::views::join_with 基本用法处理字符串集合std::views::join_with 与其他视图的结合使用总结 随着C23标准的逐步推进,我们迎来了许多令人兴奋的新特性,其中之一就是 std::views::join_with。这个新特性是C23中引入的视图适配器,…...
购物车构件示例
通用购物车构件设计 注:代码仅用于演示原理,不可用于生产环境。 一、设计目标 设计一个高度可复用的购物车构件,具备以下特点: 与具体业务系统解耦支持多种应用场景(商城、积分系统等)提供标准化接口易于集成和扩展二、核心架构设计 1. 分层架构 ┌─────────…...

数据可视化大屏——智慧社区内网比对平台
综述分析: 智慧社区内网数据比对信息系统 这段代码实现了一个智慧社区内网数据比对信息系统的前端界面,采用三栏式布局展示各类社区安全相关数据。界面主要由左侧数据统计、中间地图展示和右侧数据分析三部分组成,使用了多种图表可视化技术…...
详解SLAM中的李群和李代数(中)
1 概述 在上一篇文章《详解SLAM中的李群和李代数(上)》中,我们已经通过对李群求导引出了李代数。在这篇文章中,我们就系统总结一下李代数的相关知识。 2 李代数 2.1 定义 李代数是一个向量空间 g \mathfrak{g} g与一个二元运算…...

Jenkins企业级实战
目标 在Windows操作系统上使用Jenkins完成代码的自动拉取、编译、打包、发布工作。 实施 1.安装Java开发工具包(JDK) Jenkins是基于Java的应用程序,因此需要先安装JDK。可以从Oracle官网或OpenJDK下载适合的JDK版本。推荐java17版本&#x…...

uniapp-商城-52-后台 商家信息(商家信息数据,云对象使用)
1、概述 已经通过好几个篇幅来说明商家信息,包括logo、商家名称,地址,电话以及商家简介。通过表单组件和标签,以及我们的文件上传标签,都做了说明。(logo上传,用的文件上传组件是上传到公共的数…...

MySQL 索引设计宝典:原理、原则与实战案例深度解析
目录 前言第一章:索引设计的基础原则 (知其然,更要知其所以然)第二章:实战案例:电商订单系统的索引设计第三章:索引设计的实践流程总结结语 🌟我的其他文章也讲解的比较有趣😁,如果喜…...

C#上传文件到腾讯云的COS
测试环境: vs2022 .net 6控制台应用程序 测试步骤如下: 1 添加子用户,目前是为了拿到secretId和secretKey,打开添加子用户界面链接:https://console.cloud.tencent.com/cam 并为子用户添加API 密钥 2 通过链接htt…...
java的Stream流处理
Java Stream 流处理详解 Stream 是 Java 8 引入的一个强大的数据处理抽象,它允许你以声明式方式处理数据集合(类似于 SQL 语句),支持并行操作,提高了代码的可读性和处理效率。 一、Stream 的核心概念 1. 什么是 Str…...

C PRIMER PLUS——第9节:动态内存分配、存储类别、链接和内存管理
目录 1.动态内存分配 1.1 malloc 函数 1.2 calloc 函数 1.3 realloc 函数 1.4 free 函数 1.5常见错误 1.6综合例题 2.C语言的内存结构 3.存储类别 3.1作用域(Scope) 3.2链接(Linkage) 3.3存储期(Storage Du…...

作业...
基础配置 RI R2 R3 R4 R5 例如R1 BGP配置 1,R1和R2之间使用直连接口IP地址来建立EBGP对等体关系 2、R2、R3、R4之间配置OSPF协议,保证各设备之间的网络互通,且通过重发布的方式发布路由 查看R2、R3、R4的OSPF路由表: \ R2、R3、R4使用环…...

IC ATE集成电路测试学习——电流测试的原理和方法
电流测试 我们可以通过电流来判断芯片的工作状态时,首先先了解下芯片的电流是如何产生的。 静态电流 理论上,CMOS结构的芯片静态时几乎不耗电 CMOS基本结构:Pmos Nmos 串联当逻辑电平稳定时: ➜ 要么Pmos导通,Nmo…...
redis数据结构-03 (HMSET、HGET、HGETALL)
Redis 哈希介绍:HMSET、HGET、HGETALL Redis 哈希是一种强大的数据结构,允许您在单个键内存储字段值对的集合。它们对于表示对象、配置或任何可以自然分组到字段中的数据非常有用。本课将向您介绍使用 Redis 哈希的基本命令: HMSET 、 HGET …...
2025年01月09日德美医疗前端面试
目录 vue2 的双向绑定的原理vue3 的双向绑定原理vue 的生命周期vue 子组件为何不能修改父组件的值js delete 删除数组的某一个值会怎么样vue 和 react 的 diff 算法什么是闭包原型链this指向 vue2 的双向绑定的原理 以下是 Vue 2 双向绑定的原理: 1. 核心概念 …...

快速理解动态代理
什么是动态代理(Java核心技术卷1的解释) 动态代理是一种运行时生成代理对象的技术,其本质是通过字节码增强在不修改原始类代码的前提下,动态拦截并扩展目标对象的行为。它通过代理对象对原始方法的调用进行拦截,并在方法执行前后注入自定义逻…...
实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API
实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API 理论千遍,不如动手一遍!在前面几篇文章中,我们了解了 Serverless 的概念、FaaS 的核心原理以及 BaaS 的重要作用。现在,是时候把这些知识运用起来,亲手构建一个简单但完整的 Serverless 应用了。 …...
spark算子介绍
目录 1. 转换算子(Transformation)1.1 常用转换算子 2. 行动算子(Action)2.1 常用行动算子 3. 转换算子与行动算子的区别4. 示例代码5. 总结 在Spark中,算子(Operator)是对数据集(RD…...

AugmentCode 非常昂贵的新定价
AugmentCode 现在的价格比 Cursor 和 Windsurf 的总和还要贵。 AugmentCode 曾是我开发工作流程的常用工具。出乎意料的是,他们改变了定价结构,让开发者们震惊不已。 原来的30 美元月费已经增长为50 美元月费,这是一个67%的增长。 改变我看法的不仅仅是价格上涨,还有他…...

前端面试2
1. 面试准备 1. 建立自己的知识体系 思维导图ProcessOn框架Vue elementUI自查 https://zh.javascript.info/ 借鉴 https://juejin.cn/post/6844904103504527374http://conardli.top/blog/article/https://github.com/mqyqingfeng/Bloghttp://47.98.159.95/my_blog/#html 2.技能…...

大疆卓驭嵌入式面经及参考答案
FreeRTOS 有哪 5 种内存管理方式? heap_1.c:这种方式简单地在编译时分配一块固定大小的内存,在整个运行期间不会进行内存的动态分配和释放。它适用于那些对内存使用需求非常明确且固定,不需要动态分配内存的场景,优点是…...
RAID磁盘阵列的概念(自用留档)
概念 RAID磁盘阵列是由若干个磁盘组成的磁盘组。 磁盘组可以恢复意外丢失的数据,保证了数据的安全性。 种类 根据实际情况的不同,RAID有若干种,以一个具有三块硬盘的硬盘组为例: RAID 0:将文件拆分成三份分别放到三…...