当前位置: 首页 > article >正文

5、开放式PLC梯形图编程组件 - /自动化与控制组件/open-plc-programming

76个工业组件库示例汇总

开放式PLC编程环境

这是一个开放式PLC编程环境的自定义组件,提供了一个面向智能仓储堆垛机控制的开放式PLC编程环境。该组件采用苹果科技风格设计,支持多厂商PLC硬件,具有直观的界面和丰富的功能。

功能特点

  1. 多语言编程支持:梯形图(LD)、结构化文本(ST)和功能块图(FBD)三种PLC编程语言
  2. 多厂商硬件兼容:支持西门子、AB、三菱、欧姆龙和施耐德等主流PLC厂商
  3. 苹果科技风格界面:简洁美观的UI设计,符合现代工业审美
  4. 专业编程工具:集成Monaco编辑器,提供代码高亮、自动完成和错误检查等功能
  5. 实时变量监控:在线查看和跟踪PLC变量状态变化
  6. 堆垛机可视化:直观展示堆垛机运行状态和位置信息
  7. 告警管理系统:实时显示系统告警信息和处理状态
  8. 自适应布局:响应式设计,适应不同屏幕尺寸
  9. 动态交互效果:流畅的动画效果,提升用户体验

界面区域说明

组件包含以下主要功能区域:

  1. 顶部工具栏:包含编程语言选择、PLC厂商选择和文件操作选项
  2. 编程区域
    • 梯形图编辑器:可视化梯形图编程环境
    • 文本编辑器:用于ST语言和FBD编程
    • 标签页管理:多程序文件的标签页切换
  3. 变量监控区
    • 变量列表:显示和筛选当前PLC变量
    • 实时值更新:动态显示变量的当前值
  4. 堆垛机状态区
    • 可视化动画:显示堆垛机位置和动作
    • 实时指标:当前速度、位置和载重等关键参数
  5. 告警信息区
    • 告警显示:实时系统告警和错误信息
    • 告警处理:告警确认和处理功能

连接实际硬件

要将组件连接到实际的PLC硬件,请按照以下步骤操作:

  1. 点击顶部菜单按钮,打开硬件配置对话框
  2. 选择相应的PLC型号和通信参数
  3. 配置I/O模块和通信地址
  4. 保存配置后重新连接

组件默认使用模拟数据。要连接实际硬件,需要修改script.js中的initVariableSimulation函数,实现与实际PLC的通信。

编程示例

组件内置了几个堆垛机控制的编程示例:

  1. 主程序:主控制循环和基本功能
  2. 子程序1:堆垛机位置控制逻辑
  3. 配置:系统参数和硬件配置

这些示例可以作为开发实际应用程序的起点。

自定义选项

您可以通过修改组件代码来自定义以下内容:

  • 界面主题:在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">&times;</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编程环境的自定义组件&#xff0c;提供了一个面向智能仓储堆垛机控制的开放式PLC编程环境。该组件采用苹果科技风格设计&#xff0c;支持多厂商PLC硬件&#xff0c;具有直观的界面和丰富的功能。 功能特点 多语…...

数据指标和数据标签

数据指标和数据标签是数据管理与分析中的两个重要概念&#xff0c;它们在用途、形式和应用场景上有显著区别。以下是两者的详细对比&#xff1a; 1. 核心定义 维度数据指标&#xff08;Data Metrics&#xff09;数据标签&#xff08;Data Tags/Labels&#xff09;定义量化衡量…...

linux中常用的命令(三)

目录 1- ls(查看当前目录下的内容) 2- pwd (查看当前所在的文件夹) 3- cd [目录名]&#xff08;切换文件夹&#xff09; 4- touch [文件名] &#xff08;如果文件不存在&#xff0c;新建文件&#xff09; 5- mkdir[目录名] &#xff08;创建目录&#xff09; 6-rm[文件名]&…...

Java 中 AQS 的实现原理

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

『Python学习笔记』ubuntu解决matplotlit中文乱码的问题!

ubuntu解决matplotlit中文乱码的问题&#xff01; 文章目录 simhei.ttf字体下载链接&#xff1a;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&#xff08;可选&#xff09;&#xff1a;指定要查看状态…...

redis数据结构-04 (HINCRBY、HDEL、HKEYS、HVALS)

哈希操作&#xff1a;HINCRBY、HDEL、HKEYS、HVALS Redis 中的哈希功能极其丰富&#xff0c;让您能够以类似于编程语言中对象的方式存储和检索数据。本课将深入探讨具体的哈希操作&#xff0c;这些操作为操作以下结构中的数据提供了强大的工具&#xff1a; HINCRBY 、 HDEL 、…...

鸿蒙知识总结

判断题 1、 在http模块中&#xff0c;多个请求可以使用同一个httpRequest对象&#xff0c;httpRequest对象可以复用。&#xff08;错误&#xff09; 2、订阅dataReceiverProgress响应事件是用来接收HTTP流式响应数据。&#xff08;错误&#xff09; 3、ArkTS中变量声明时不需要…...

Ubuntu 22虚拟机【网络故障】快速解决指南

Ubuntu22虚拟机突然无法连接网络了&#xff0c;以下是故障排除步骤记录。 Ubuntu 22虚拟机网络故障快速解决指南 当在虚拟机中安装的 Ubuntu 22 系统出现 ping: connect: 网络不可达 和 ping: www.baidu.com: 域名解析出现暂时性错误的报错时&#xff0c;通常意味着虚拟机无法…...

C++23 新特性:深入解析 std::views::join_with(P2441R2)

文章目录 std::views::join_with 基本用法处理字符串集合std::views::join_with 与其他视图的结合使用总结 随着C23标准的逐步推进&#xff0c;我们迎来了许多令人兴奋的新特性&#xff0c;其中之一就是 std::views::join_with。这个新特性是C23中引入的视图适配器&#xff0c…...

购物车构件示例

通用购物车构件设计 注:代码仅用于演示原理,不可用于生产环境。 一、设计目标 设计一个高度可复用的购物车构件,具备以下特点: 与具体业务系统解耦支持多种应用场景(商城、积分系统等)提供标准化接口易于集成和扩展二、核心架构设计 1. 分层架构 ┌─────────…...

数据可视化大屏——智慧社区内网比对平台

综述分析&#xff1a; 智慧社区内网数据比对信息系统 这段代码实现了一个智慧社区内网数据比对信息系统的前端界面&#xff0c;采用三栏式布局展示各类社区安全相关数据。界面主要由左侧数据统计、中间地图展示和右侧数据分析三部分组成&#xff0c;使用了多种图表可视化技术…...

详解SLAM中的李群和李代数(中)

1 概述 在上一篇文章《详解SLAM中的李群和李代数&#xff08;上&#xff09;》中&#xff0c;我们已经通过对李群求导引出了李代数。在这篇文章中&#xff0c;我们就系统总结一下李代数的相关知识。 2 李代数 2.1 定义 李代数是一个向量空间 g \mathfrak{g} g与一个二元运算…...

Jenkins企业级实战

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

uniapp-商城-52-后台 商家信息(商家信息数据,云对象使用)

1、概述 已经通过好几个篇幅来说明商家信息&#xff0c;包括logo、商家名称&#xff0c;地址&#xff0c;电话以及商家简介。通过表单组件和标签&#xff0c;以及我们的文件上传标签&#xff0c;都做了说明。&#xff08;logo上传&#xff0c;用的文件上传组件是上传到公共的数…...

MySQL 索引设计宝典:原理、原则与实战案例深度解析

目录 前言第一章&#xff1a;索引设计的基础原则 (知其然&#xff0c;更要知其所以然)第二章&#xff1a;实战案例&#xff1a;电商订单系统的索引设计第三章&#xff1a;索引设计的实践流程总结结语 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜…...

C#上传文件到腾讯云的COS

测试环境&#xff1a; vs2022 .net 6控制台应用程序 测试步骤如下&#xff1a; 1 添加子用户&#xff0c;目前是为了拿到secretId和secretKey&#xff0c;打开添加子用户界面链接&#xff1a;https://console.cloud.tencent.com/cam 并为子用户添加API 密钥 2 通过链接htt…...

java的Stream流处理

Java Stream 流处理详解 Stream 是 Java 8 引入的一个强大的数据处理抽象&#xff0c;它允许你以声明式方式处理数据集合&#xff08;类似于 SQL 语句&#xff09;&#xff0c;支持并行操作&#xff0c;提高了代码的可读性和处理效率。 一、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作用域&#xff08;Scope&#xff09; 3.2链接&#xff08;Linkage&#xff09; 3.3存储期&#xff08;Storage Du…...

作业...

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

IC ATE集成电路测试学习——电流测试的原理和方法

电流测试 我们可以通过电流来判断芯片的工作状态时&#xff0c;首先先了解下芯片的电流是如何产生的。 静态电流 理论上&#xff0c;CMOS结构的芯片静态时几乎不耗电 CMOS基本结构&#xff1a;Pmos Nmos 串联当逻辑电平稳定时&#xff1a; ➜ 要么Pmos导通&#xff0c;Nmo…...

redis数据结构-03 (HMSET、HGET、HGETALL)

Redis 哈希介绍&#xff1a;HMSET、HGET、HGETALL Redis 哈希是一种强大的数据结构&#xff0c;允许您在单个键内存储字段值对的集合。它们对于表示对象、配置或任何可以自然分组到字段中的数据非常有用。本课将向您介绍使用 Redis 哈希的基本命令&#xff1a; HMSET 、 HGET …...

2025年01月09日德美医疗前端面试

目录 vue2 的双向绑定的原理vue3 的双向绑定原理vue 的生命周期vue 子组件为何不能修改父组件的值js delete 删除数组的某一个值会怎么样vue 和 react 的 diff 算法什么是闭包原型链this指向 vue2 的双向绑定的原理 以下是 Vue 2 双向绑定的原理&#xff1a; 1. 核心概念 …...

快速理解动态代理

什么是动态代理(Java核心技术卷1的解释) 动态代理是一种运行时生成代理对象的技术&#xff0c;其本质是通过字节码增强在不修改原始类代码的前提下&#xff0c;动态拦截并扩展目标对象的行为。它通过代理对象对原始方法的调用进行拦截&#xff0c;并在方法执行前后注入自定义逻…...

实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API

实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API 理论千遍,不如动手一遍!在前面几篇文章中,我们了解了 Serverless 的概念、FaaS 的核心原理以及 BaaS 的重要作用。现在,是时候把这些知识运用起来,亲手构建一个简单但完整的 Serverless 应用了。 …...

spark算子介绍

目录 1. 转换算子&#xff08;Transformation&#xff09;1.1 常用转换算子 2. 行动算子&#xff08;Action&#xff09;2.1 常用行动算子 3. 转换算子与行动算子的区别4. 示例代码5. 总结 在Spark中&#xff0c;算子&#xff08;Operator&#xff09;是对数据集&#xff08;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 种内存管理方式&#xff1f; heap_1.c&#xff1a;这种方式简单地在编译时分配一块固定大小的内存&#xff0c;在整个运行期间不会进行内存的动态分配和释放。它适用于那些对内存使用需求非常明确且固定&#xff0c;不需要动态分配内存的场景&#xff0c;优点是…...

RAID磁盘阵列的概念(自用留档)

概念 RAID磁盘阵列是由若干个磁盘组成的磁盘组。 磁盘组可以恢复意外丢失的数据&#xff0c;保证了数据的安全性。 种类 根据实际情况的不同&#xff0c;RAID有若干种&#xff0c;以一个具有三块硬盘的硬盘组为例&#xff1a; RAID 0&#xff1a;将文件拆分成三份分别放到三…...