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

发布于:2025-05-09 ⋅ 阅读:(13) ⋅ 点赞:(0)

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
VAR
    S_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 THEN
            CurrentShelf := TargetShelf;
            Speed_X := 0.0;
            Speed_Y := 0.0;
        END_IF;
    END_IF;
END_IF;

// 复位操作
IF S_ResetButton THEN
    Position_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
}; 

网站公告

今日签到

点亮在社区的每一天
去签到