4、反应釜压力监控系统 - /自动化与控制组件/reaction-vessel-monitor

发布于:2025-05-12 ⋅ 阅读:(17) ⋅ 点赞:(0)

76个工业组件库示例汇总

反应釜压力监控组件

这是一个用于反应釜压力监控的自定义组件,专为化工厂反应釜压力监控设计。采用苹果工业风格界面,简洁优雅,功能实用,易于使用。

功能特点

  1. 实时压力可视化:直观展示反应釜压力数据变化趋势
  2. 多反应釜监控:支持同时监控多个反应釜设备
  3. 压力预警系统:提供高/低压力阈值监控和报警
  4. 设备状态指示:清晰展示各反应釜工作状态
  5. 简洁数据面板:展示关键数据指标和统计信息
  6. 苹果工业风格:优雅简洁的界面设计,符合现代工业审美

组件区域说明

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

  1. 总览视图:顶部显示反应釜系统整体状态和关键指标
  2. 压力趋势图:中部展示历史压力数据和实时变化趋势
  3. 设备状态列表:右侧显示各反应釜的详细状态和当前压力
  4. 告警信息区:底部显示最近的告警信息和处理状态

自定义选项

可以根据实际需求调整以下内容:

  • 压力阈值:在脚本中设置高/低压力预警阈值
  • 监控反应釜数量:通过修改配置调整监控的设备数量
  • 图表显示时长:调整历史数据显示的时间范围
  • 刷新频率:根据需要调整数据更新频率
  • 颜色主题:修改CSS变量来调整整体配色方案

连接实际数据源

组件默认使用模拟数据。如需连接真实数据源:

  1. 替换fetchVesselData()函数中的数据模拟逻辑
  2. 实现与实际数据API的连接
  3. 根据实际数据格式调整数据处理逻辑

项目结构

在这里插入图片描述

效果展示

在这里插入图片描述

源码

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>反应釜监控系统</title>
  <link rel="stylesheet" href="styles.css">
  <!-- 添加Chart.js库 -->
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
  <div id="reaction-vessel-monitor">
    <header class="rv-header">
      <div class="rv-logo">
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
          <path d="M9 17.25V9.75M15 17.25V9.75M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
        <span>反应釜监控系统</span>
      </div>
      <div class="rv-actions">
        <button class="rv-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M9.59322 18C9.59322 18.5523 9.14551 19 8.59322 19C8.04093 19 7.59322 18.5523 7.59322 18C7.59322 17.4477 8.04093 17 8.59322 17C9.14551 17 9.59322 17.4477 9.59322 18Z" fill="currentColor"/>
            <path d="M16.5932 18C16.5932 18.5523 16.1455 19 15.5932 19C15.0409 19 14.5932 18.5523 14.5932 18C14.5932 17.4477 15.0409 17 15.5932 17C16.1455 17 16.5932 17.4477 16.5932 18Z" fill="currentColor"/>
            <path d="M9.59322 12C9.59322 12.5523 9.14551 13 8.59322 13C8.04093 13 7.59322 12.5523 7.59322 12C7.59322 11.4477 8.04093 11 8.59322 11C9.14551 11 9.59322 11.4477 9.59322 12Z" fill="currentColor"/>
            <path d="M16.5932 12C16.5932 12.5523 16.1455 13 15.5932 13C15.0409 13 14.5932 12.5523 14.5932 12C14.5932 11.4477 15.0409 11 15.5932 11C16.1455 11 16.5932 11.4477 16.5932 12Z" fill="currentColor"/>
            <path d="M9.59322 6C9.59322 6.55228 9.14551 7 8.59322 7C8.04093 7 7.59322 6.55228 7.59322 6C7.59322 5.44772 8.04093 5 8.59322 5C9.14551 5 9.59322 5.44772 9.59322 6Z" fill="currentColor"/>
            <path d="M16.5932 6C16.5932 6.55228 16.1455 7 15.5932 7C15.0409 7 14.5932 6.55228 14.5932 6C14.5932 5.44772 15.0409 5 15.5932 5C16.1455 5 16.5932 5.44772 16.5932 6Z" fill="currentColor"/>
          </svg>
          菜单
        </button>
      </div>
    </header>
    
    <div class="rv-content">
      <!-- 系统概览面板 -->
      <div class="rv-panel rv-overview">
        <div class="rv-panel-header">
          <h2>系统概览</h2>
          <span class="rv-status-badge status-normal">系统正常</span>
        </div>
        <div class="rv-panel-body">
          <div class="rv-overview-stats">
            <div class="rv-stat">
              <div class="rv-stat-value">5</div>
              <div class="rv-stat-label">反应釜总数</div>
            </div>
            <div class="rv-stat">
              <div class="rv-stat-value">4</div>
              <div class="rv-stat-label">正常运行</div>
            </div>
            <div class="rv-stat">
              <div class="rv-stat-value">1</div>
              <div class="rv-stat-label">需要注意</div>
            </div>
            <div class="rv-stat">
              <div class="rv-stat-value">0</div>
              <div class="rv-stat-label">严重警告</div>
            </div>
          </div>
        </div>
      </div>
      
      <!-- 压力趋势图面板 -->
      <div class="rv-panel rv-trend">
        <div class="rv-panel-header">
          <h2>压力趋势图</h2>
          <div class="rv-panel-actions">
            <select id="time-range">
              <option value="1h">1小时</option>
              <option value="8h" selected>8小时</option>
              <option value="24h">24小时</option>
            </select>
          </div>
        </div>
        <div class="rv-panel-body">
          <div class="chart-container">
            <canvas id="pressure-chart"></canvas>
            <div class="chart-loading">
              <div class="loading-spinner"></div>
              <div class="loading-text">加载中...</div>
            </div>
          </div>
        </div>
      </div>
      
      <!-- 设备状态列表面板 -->
      <div class="rv-panel rv-vessels">
        <div class="rv-panel-header">
          <h2>设备状态</h2>
          <div class="rv-panel-actions">
            <button class="rv-btn rv-btn-sm">刷新</button>
          </div>
        </div>
        <div class="rv-panel-body">
          <div id="vessel-list" class="rv-vessel-list">
            <!-- 设备列表将通过JavaScript动态生成 -->
            <div class="rv-vessel-loading">加载设备数据中...</div>
          </div>
        </div>
      </div>
      
      <!-- 告警信息面板 -->
      <div class="rv-panel rv-alerts">
        <div class="rv-panel-header">
          <h2>告警信息</h2>
          <div class="rv-badge">3</div>
        </div>
        <div class="rv-panel-body">
          <div id="alert-list" class="rv-alert-list">
            <!-- 告警列表将通过JavaScript动态生成 -->
          </div>
        </div>
      </div>
    </div>
    
    <!-- 设备详情模态框 -->
    <div id="vessel-detail-modal" class="rv-modal">
      <div class="rv-modal-content">
        <div class="rv-modal-header">
          <h3 id="vessel-detail-title">设备详情</h3>
          <button class="rv-modal-close">&times;</button>
        </div>
        <div class="rv-modal-body">
          <div id="vessel-detail-content"></div>
        </div>
        <div class="rv-modal-footer">
          <button class="rv-btn rv-btn-secondary modal-close-btn">关闭</button>
          <button class="rv-btn">查看历史数据</button>
        </div>
      </div>
    </div>
  </div>

  <!-- 先加载脚本 -->
  <script src="script.js"></script>
  <!-- 脚本加载完成后再初始化 -->
  <script>
    // 等待DOM完全加载后再初始化系统
    document.addEventListener('DOMContentLoaded', function() {
      // 确保函数存在
      if (typeof initReactionVesselMonitor === 'function') {
        // 调用初始化函数
        initReactionVesselMonitor();
      } else if (typeof initializeMonitor === 'function') {
        // 兼容性调用
        initializeMonitor();
      } else {
        console.error('初始化函数未找到');
      }
    });
  </script>
</body>
</html> 

styles.css

/* 反应釜压力监控系统 - 苹果工业风格 */

:root {
  /* 颜色变量 - 苹果风格 */
  --background: #F2F2F7;
  --card-bg: #FFFFFF;
  --primary-text: #1C1C1E;
  --secondary-text: #8E8E93;
  --accent-blue: #007AFF;
  --accent-green: #34C759;
  --accent-orange: #FF9500;
  --accent-red: #FF3B30;
  --accent-purple: #5856D6;
  --border-color: #E5E5EA;
  --chart-grid: #D1D1D6;
  
  /* 阴影 */
  --card-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  --header-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
  
  /* 尺寸变量 */
  --header-height: 60px;
  --footer-height: 120px;
  --panel-gap: 16px;
  --border-radius: 10px;
}

/* 基础样式 */
#reaction-vessel-monitor {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', '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;
}

#reaction-vessel-monitor * {
  box-sizing: border-box;
}

/* 顶部导航栏 */
.rv-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;
}

.rv-logo {
  display: flex;
  align-items: center;
  gap: 10px;
}

.rv-logo-icon {
  font-size: 24px;
}

.rv-logo-text {
  font-size: 18px;
  font-weight: 600;
}

.rv-system-status {
  display: flex;
  align-items: center;
  gap: 8px;
}

.status-indicator {
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

.status-indicator.online {
  background-color: var(--accent-green);
  box-shadow: 0 0 5px var(--accent-green);
}

.status-indicator.offline {
  background-color: var(--accent-red);
  box-shadow: 0 0 5px var(--accent-red);
}

.status-indicator.warning {
  background-color: var(--accent-orange);
  box-shadow: 0 0 5px var(--accent-orange);
}

.status-text {
  font-size: 14px;
  color: var(--secondary-text);
}

/* 主内容区域 */
.rv-content {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto 1fr auto;
  grid-gap: var(--panel-gap);
  padding: 20px;
  overflow: hidden;
  height: calc(100% - var(--header-height));
  width: 100%;
}

/* 面板布局 */
.rv-overview {
  grid-column: 1;
  grid-row: 1;
}

.rv-trend {
  grid-column: 1 / 3;
  grid-row: 2;
}

.rv-vessels {
  grid-column: 2;
  grid-row: 1;
}

.rv-alerts {
  grid-column: 1 / 3;
  grid-row: 3;
}

/* 面板样式 */
.rv-panel {
  background-color: var(--card-bg);
  border-radius: var(--border-radius);
  box-shadow: var(--card-shadow);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.rv-panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px;
  border-bottom: 1px solid var(--border-color);
}

.rv-panel-header h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.rv-panel-actions {
  display: flex;
  gap: 10px;
}

.rv-panel-body {
  flex: 1;
  padding: 15px;
  overflow: auto;
}

/* 按钮样式 */
.rv-btn {
  display: flex;
  align-items: center;
  gap: 5px;
  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;
}

.rv-btn:hover {
  background-color: var(--background);
}

.rv-btn-sm {
  padding: 5px 10px;
  font-size: 12px;
}

.rv-btn-secondary {
  background-color: var(--background);
}

/* 状态徽章 */
.rv-status-badge {
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
}

.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);
}

/* 统计数据 */
.rv-overview-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 15px;
}

.rv-stat {
  background-color: var(--background);
  border-radius: 8px;
  padding: 15px;
  text-align: center;
}

.rv-stat-value {
  font-size: 24px;
  font-weight: 700;
  margin-bottom: 5px;
}

.rv-stat-label {
  font-size: 12px;
  color: var(--secondary-text);
}

/* 设备列表 */
.rv-vessel-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}

.rv-vessel-item {
  background-color: var(--background);
  border-radius: 8px;
  padding: 15px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  cursor: pointer;
  transition: transform 0.2s, box-shadow 0.2s;
}

.rv-vessel-item:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}

.rv-vessel-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.rv-vessel-name {
  font-weight: 600;
  font-size: 14px;
}

.rv-vessel-status {
  font-size: 12px;
  padding: 3px 8px;
  border-radius: 10px;
}

.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);
}

.rv-vessel-metrics {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}

.rv-vessel-metric {
  text-align: center;
  flex: 1;
}

.rv-metric-value {
  font-size: 18px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

.rv-metric-label {
  font-size: 11px;
  color: var(--secondary-text);
  margin-top: 2px;
}

.rv-vessel-actions {
  display: flex;
  justify-content: flex-end;
}

.rv-trend-indicator {
  font-size: 12px;
}

.rising {
  color: var(--accent-red);
}

.falling {
  color: var(--accent-green);
}

.stable {
  color: var(--secondary-text);
}

/* 告警列表 */
.rv-alert-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rv-alert-item {
  display: flex;
  align-items: center;
  gap: 10px;
  background-color: var(--background);
  border-radius: 8px;
  padding: 12px 15px;
}

.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);
}

.rv-alert-icon {
  display: flex;
  color: var(--accent-orange);
}

.alert-error .rv-alert-icon {
  color: var(--accent-red);
}

.alert-info .rv-alert-icon {
  color: var(--accent-blue);
}

.rv-alert-content {
  flex: 1;
}

.rv-alert-message {
  font-size: 13px;
}

.rv-alert-time {
  font-size: 11px;
  color: var(--secondary-text);
  margin-top: 2px;
}

.rv-alert-actions {
  flex-shrink: 0;
}

.rv-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;
}

.rv-alert-btn:hover {
  background-color: var(--accent-blue);
  color: white;
}

/* 徽章 */
.rv-badge {
  background-color: var(--accent-red);
  color: white;
  font-size: 12px;
  font-weight: 500;
  padding: 2px 6px;
  border-radius: 10px;
  min-width: 20px;
  text-align: center;
}

/* 设备加载状态 */
.rv-vessel-loading {
  text-align: center;
  padding: 20px;
  color: var(--secondary-text);
}

/* 模态框 */
.rv-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;
}

.rv-modal-content {
  background-color: var(--card-bg);
  border-radius: var(--border-radius);
  width: 90%;
  max-width: 500px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}

.rv-modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px 20px;
  border-bottom: 1px solid var(--border-color);
}

.rv-modal-header h3 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.rv-modal-close {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: var(--secondary-text);
}

.rv-modal-body {
  padding: 20px;
  overflow-y: auto;
  flex: 1;
}

.rv-modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 15px 20px;
  border-top: 1px solid var(--border-color);
}

/* 设备详情样式 */
.rv-detail-status {
  display: inline-block;
  padding: 5px 10px;
  border-radius: 6px;
  margin-bottom: 15px;
}

.rv-detail-info {
  margin-bottom: 20px;
}

.rv-detail-info p {
  margin: 5px 0;
  line-height: 1.5;
}

.rv-detail-metrics {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 15px;
  margin-bottom: 20px;
}

.rv-detail-metric {
  background-color: var(--background);
  border-radius: 8px;
  padding: 15px;
  text-align: center;
}

.rv-detail-metric-value {
  font-size: 24px;
  font-weight: 700;
  margin-bottom: 5px;
}

.rv-detail-metric-label {
  font-size: 12px;
  color: var(--secondary-text);
}

.rv-detail-params {
  background-color: var(--background);
  border-radius: 8px;
  padding: 15px;
}

.rv-detail-params h4 {
  margin-top: 0;
  margin-bottom: 10px;
  font-size: 14px;
  font-weight: 600;
}

.rv-detail-params table {
  width: 100%;
  border-collapse: collapse;
}

.rv-detail-params td {
  padding: 6px 0;
  border-bottom: 1px solid var(--border-color);
  font-size: 13px;
}

.rv-detail-params tr:last-child td {
  border-bottom: none;
}

/* 图表容器 */
.chart-container {
  position: relative;
  width: 100%;
  height: 300px;
}

.chart-loading {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: rgba(255, 255, 255, 0.8);
  gap: 12px;
}

.loading-spinner {
  width: 30px;
  height: 30px;
  border: 3px solid rgba(0, 0, 0, 0.1);
  border-radius: 50%;
  border-top-color: var(--accent-blue);
  animation: spin 1s linear infinite;
}

.loading-text {
  font-size: 14px;
  color: var(--secondary-text);
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* 响应式布局调整 */
@media (max-width: 992px) {
  .rv-content {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto auto;
  }
  
  .rv-overview {
    grid-column: 1;
    grid-row: 1;
  }
  
  .rv-vessels {
    grid-column: 1;
    grid-row: 2;
  }
  
  .rv-trend {
    grid-column: 1;
    grid-row: 3;
  }
  
  .rv-alerts {
    grid-column: 1;
    grid-row: 4;
  }
}

/* 告警列表中的"更多信息"样式 */
.rv-alert-more-info {
  text-align: center;
  padding: 10px;
  margin-top: 8px;
  background-color: var(--background);
  border-radius: 8px;
  color: var(--secondary-text);
  font-size: 13px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.rv-alert-more-info:hover {
  background-color: rgba(0, 122, 255, 0.05);
  color: var(--accent-blue);
} 

script.js

// 反应釜压力监控系统 JavaScript

// 全局变量
let vessels = []; // 存储反应釜数据
let pressureChart = null;
let vesselData = [];
let alertHistory = [];
let systemStatus = 'online'; // 系统状态
const pressureThresholds = {
  low: 0.2, // 低压警告阈值 (MPa)
  high: 0.8, // 高压警告阈值 (MPa)
  critical: 1.0 // 临界压力阈值 (MPa)
};

// 初始化函数 - 与HTML中调用的函数名保持一致
function initReactionVesselMonitor() {
  console.log('初始化反应釜压力监控系统');
  
  // 初始化系统状态
  updateSystemStatus(systemStatus);
  
  // 初始化模拟数据
  generateVesselData();
  
  // 将生成的数据复制到vessels变量
  vessels = [...vesselData];
  
  // 初始化图表
  initializePressureChart();
  
  // 渲染反应釜列表
  renderVesselList();
  
  // 设置事件监听器
  setupEventListeners();
  
  // 渲染告警列表
  renderAlertList();
  
  // 添加示例告警
  addAlert('警告', '反应釜 #2 压力接近高阈值');
  addAlert('信息', '系统自检完成,所有传感器正常');
  
  // 设置数据刷新定时器
  setInterval(() => {
    updateVesselData();
    updatePressureChart();
    renderVesselList();
    checkForAlerts();
  }, 5000);
  
  // 隐藏加载提示
  document.querySelector('.chart-loading').style.display = 'none';
  document.querySelector('.rv-vessel-loading').style.display = 'none';
  
  // 初始化完成
  console.log('反应釜压力监控系统初始化完成');
}

// 原始的初始化函数,保留以保证向后兼容
function initializeMonitor() {
  initReactionVesselMonitor();
}

// 生成模拟船只数据
function generateVesselData() {
  const data = [];
  const statusOptions = ['normal', 'warning', 'error', 'maintenance'];
  const namePrefix = ['A', 'B', 'C', 'D', 'E'];
  
  for (let i = 0; i < 5; i++) {
    // 生成过去8小时的压力历史数据点 (48个点,每10分钟一个)
    const pressureHistory = [];
    const now = new Date();
    
    for (let j = 47; j >= 0; j--) {
      const timestamp = new Date(now.getTime() - j * 10 * 60 * 1000); // 每10分钟一个数据点
      const basePressure = 5 + Math.random() * 2; // 基础压力在5-7之间
      let pressure;
      
      // 为了使图表更有变化,随机添加一些波动
      if (j % 12 === 0) { // 每2小时出现一次较大波动
        pressure = basePressure + (Math.random() * 1.5 - 0.5);
      } else {
        pressure = basePressure + (Math.random() * 0.4 - 0.2);
      }
      
      pressureHistory.push({
        timestamp: timestamp,
        value: pressure.toFixed(2)
      });
    }
    
    // 创建反应釜对象
    data.push({
      id: i + 1,
      name: `反应釜${namePrefix[i]}`,
      status: i === 2 ? 'warning' : 'normal', // 让第3个反应釜处于警告状态
      pressureHistory: pressureHistory,
      currentPressure: pressureHistory[pressureHistory.length - 1].value,
      temperature: (75 + Math.random() * 10).toFixed(1),
      fillLevel: Math.floor(60 + Math.random() * 30),
      lastUpdated: new Date(),
      description: `${namePrefix[i]}系列化学反应容器,用于${['聚合', '中和', '催化', '分解', '合成'][i]}反应`
    });
  }
  
  vesselData = data;
}

// 更新船只数据
function updateVesselData() {
  const now = new Date();
  
  vesselData.forEach(vessel => {
    // 更新压力
    const lastPressure = parseFloat(vessel.currentPressure);
    const pressureChange = (Math.random() * 0.6 - 0.3); // -0.3到0.3的变化
    const newPressure = Math.max(4.5, Math.min(8.5, lastPressure + pressureChange)).toFixed(2);
    
    // 添加新的压力历史记录
    vessel.pressureHistory.push({
      timestamp: now,
      value: newPressure
    });
    
    // 只保留最近48个数据点
    if (vessel.pressureHistory.length > 48) {
      vessel.pressureHistory.shift();
    }
    
    // 更新当前压力
    vessel.currentPressure = newPressure;
    
    // 更新温度
    const lastTemp = parseFloat(vessel.temperature);
    const tempChange = (Math.random() * 1.0 - 0.5); // -0.5到0.5的变化
    vessel.temperature = Math.max(70, Math.min(90, lastTemp + tempChange)).toFixed(1);
    
    // 更新液位
    const fillChange = Math.floor(Math.random() * 5 - 2); // -2到2的变化
    vessel.fillLevel = Math.max(30, Math.min(95, vessel.fillLevel + fillChange));
    
    // 偶尔改变状态
    if (Math.random() < 0.05) { // 5%的概率改变状态
      const currentStatus = vessel.status;
      if (currentStatus === 'normal') {
        vessel.status = Math.random() < 0.7 ? 'warning' : 'normal';
      } else if (currentStatus === 'warning') {
        vessel.status = Math.random() < 0.3 ? 'error' : (Math.random() < 0.6 ? 'normal' : 'warning');
      } else if (currentStatus === 'error') {
        vessel.status = Math.random() < 0.5 ? 'warning' : 'error';
      }
    }
    
    // 更新最后更新时间
    vessel.lastUpdated = now;
  });
}

// 初始化压力趋势图表
function initializePressureChart() {
  const ctx = document.getElementById('pressure-chart').getContext('2d');
  
  // 获取所有反应釜的最近数据
  const labels = [];
  const datasets = [];
  
  // 生成时间标签
  const now = new Date();
  const timeRange = document.getElementById('time-range').value;
  const pointCount = timeRange === '1h' ? 6 : (timeRange === '8h' ? 24 : 48);
  const interval = timeRange === '1h' ? 10 : (timeRange === '8h' ? 20 : 30);
  
  for (let i = pointCount - 1; i >= 0; i--) {
    const time = new Date(now.getTime() - i * interval * 60 * 1000);
    const timeStr = time.getHours().toString().padStart(2, '0') + ':' + 
                    time.getMinutes().toString().padStart(2, '0');
    labels.push(timeStr);
  }
  
  // 为每个反应釜创建一个数据集
  const colors = ['rgba(54, 162, 235, 1)', 'rgba(255, 99, 132, 1)', 'rgba(75, 192, 192, 1)', 
                 'rgba(255, 159, 64, 1)', 'rgba(153, 102, 255, 1)'];
                 
  vesselData.forEach((vessel, index) => {
    // 获取最近的压力数据
    const pressureData = vessel.pressureHistory.slice(-pointCount).map(p => p.value);
    
    datasets.push({
      label: vessel.name,
      data: pressureData,
      borderColor: colors[index % colors.length],
      backgroundColor: colors[index % colors.length].replace('1)', '0.1)'),
      borderWidth: 2,
      pointRadius: 2,
      pointHoverRadius: 5,
      tension: 0.3
    });
  });
  
  // 创建图表
  pressureChart = new Chart(ctx, {
    type: 'line',
    data: {
      labels: labels,
      datasets: datasets
    },
    options: {
      responsive: true,
      maintainAspectRatio: false,
      plugins: {
        legend: {
          position: 'top',
          labels: {
            boxWidth: 12,
            usePointStyle: true,
            pointStyle: 'circle'
          }
        },
        tooltip: {
          mode: 'index',
          intersect: false
        }
      },
      scales: {
        x: {
          grid: {
            color: 'rgba(200, 200, 200, 0.1)'
          }
        },
        y: {
          beginAtZero: false,
          min: 4,
          max: 9,
          ticks: {
            stepSize: 1
          },
          grid: {
            color: 'rgba(200, 200, 200, 0.1)'
          },
          title: {
            display: true,
            text: '压力 (MPa)'
          }
        }
      }
    }
  });
}

// 更新压力趋势图
function updatePressureChart() {
  if (!pressureChart) {
    console.error('图表尚未初始化');
    return;
  }
  
  // 获取时间范围
  const timeRange = document.getElementById('time-range').value;
  const pointCount = timeRange === '1h' ? 6 : (timeRange === '8h' ? 24 : 48);
  
  // 更新标签
  const now = new Date();
  const labels = [];
  const interval = timeRange === '1h' ? 10 : (timeRange === '8h' ? 20 : 30);
  
  for (let i = pointCount - 1; i >= 0; i--) {
    const time = new Date(now.getTime() - i * interval * 60 * 1000);
    const timeStr = time.getHours().toString().padStart(2, '0') + ':' + 
                    time.getMinutes().toString().padStart(2, '0');
    labels.push(timeStr);
  }
  
  pressureChart.data.labels = labels;
  
  // 更新数据集
  vesselData.forEach((vessel, index) => {
    // 获取最近的压力数据
    const pressureData = vessel.pressureHistory.slice(-pointCount).map(p => p.value);
    
    // 确保数据集存在
    if (pressureChart.data.datasets[index]) {
      pressureChart.data.datasets[index].data = pressureData;
    }
  });
  
  // 更新图表
  pressureChart.update();
}

// 渲染反应釜列表
function renderVesselList() {
  const container = document.getElementById('vessel-list');
  
  if (!container) {
    console.error('找不到反应釜列表容器元素');
    return;
  }
  
  // 清空容器
  container.innerHTML = '';
  
  // 遍历反应釜并创建列表项
  vesselData.forEach(vessel => {
    const item = document.createElement('div');
    item.className = `rv-vessel-item status-${vessel.status}`;
    item.dataset.id = vessel.id;
    
    // 设置趋势图标
    let trendIcon = '→'; // 稳定
    let trendClass = 'stable';
    
    // 计算趋势
    if (vessel.pressureHistory && vessel.pressureHistory.length >= 2) {
      const last = vessel.pressureHistory[vessel.pressureHistory.length - 1].value;
      const prev = vessel.pressureHistory[vessel.pressureHistory.length - 2].value;
      if (parseFloat(last) > parseFloat(prev)) {
        trendIcon = '↑';
        trendClass = 'rising';
      } else if (parseFloat(last) < parseFloat(prev)) {
        trendIcon = '↓';
        trendClass = 'falling';
      }
    }
    
    item.innerHTML = `
      <div class="rv-vessel-info">
        <div class="rv-vessel-name">${vessel.name}</div>
        <div class="rv-vessel-status status-${vessel.status}">
          ${vessel.status === 'normal' ? '正常' : 
            vessel.status === 'warning' ? '警告' : '错误'}
        </div>
      </div>
      <div class="rv-vessel-metrics">
        <div class="rv-vessel-metric">
          <div class="rv-metric-value">
            ${vessel.currentPressure} 
            <span class="rv-trend-indicator ${trendClass}">${trendIcon}</span>
          </div>
          <div class="rv-metric-label">压力 (MPa)</div>
        </div>
        <div class="rv-vessel-metric">
          <div class="rv-metric-value">${vessel.temperature}°C</div>
          <div class="rv-metric-label">温度</div>
        </div>
        <div class="rv-vessel-metric">
          <div class="rv-metric-value">${vessel.fillLevel}%</div>
          <div class="rv-metric-label">液位</div>
        </div>
      </div>
      <div class="rv-vessel-actions">
        <button class="rv-btn rv-btn-sm view-details" data-id="${vessel.id}">查看详情</button>
      </div>
    `;
    
    // 点击查看详情
    item.querySelector('.view-details').addEventListener('click', (e) => {
      e.stopPropagation(); // 防止事件冒泡
      showVesselDetails(vessel.id);
    });
    
    container.appendChild(item);
  });
  
  // 更新统计信息
  updateStatistics();
}

// 更新统计信息
function updateStatistics() {
  // 使用正确的选择器或跳过不存在的元素
  // 反应釜总数由系统概览面板中的数据显示,无需更新
  
  if (vessels.length === 0) return; // 防止除零错误
  
  try {
    // 统计正常/警告/错误状态的反应釜数量
    const normalCount = vessels.filter(v => v.status === 'normal').length;
    const warningCount = vessels.filter(v => v.status === 'warning').length;
    const errorCount = vessels.filter(v => v.status === 'error').length;
    
    // 查找并更新统计值的显示元素
    const statElements = document.querySelectorAll('.rv-stat-value');
    if (statElements.length >= 4) {
      statElements[0].textContent = vessels.length; // 总数
      statElements[1].textContent = normalCount; // 正常
      statElements[2].textContent = warningCount; // 警告
      statElements[3].textContent = errorCount; // 错误
    }
    
    // 更新告警数量显示
    const alertBadge = document.querySelector('.rv-badge');
    if (alertBadge) {
      alertBadge.textContent = alertHistory.length;
    }
  } catch (error) {
    console.error('更新统计信息时出错:', error);
  }
}

// 检查告警
function checkForAlerts() {
  vesselData.forEach(vessel => {
    const pressure = parseFloat(vessel.currentPressure);
    
    // 检查是否超过阈值
    if (pressure >= 8.0) {
      addAlert('危险', `${vessel.name} 压力过高: ${vessel.currentPressure} MPa`);
    } else if (pressure >= 7.5) {
      addAlert('警告', `${vessel.name} 压力偏高: ${vessel.currentPressure} MPa`);
    } else if (pressure <= 5.0) {
      addAlert('警告', `${vessel.name} 压力偏低: ${vessel.currentPressure} MPa`);
    }
  });
  
  // 更新告警列表显示
  renderAlertList();
}

// 刷新数据
function refreshData() {
  console.log('刷新数据...');
  updateVesselData();
  renderVesselList();
  updatePressureChart();
}

// 添加告警
function addAlert(type, message) {
  const alert = {
    id: Date.now(),
    type: type,
    message: message,
    time: new Date(),
    processed: false
  };
  
  // 添加到列表开头
  alertHistory.unshift(alert);
  
  // 最多保留20条告警
  if (alertHistory.length > 20) {
    alertHistory.pop();
  }
  
  // 更新告警列表显示
  renderAlertList();
  
  // 更新统计信息
  updateStatistics();
}

// 渲染告警列表
function renderAlertList() {
  const container = document.getElementById('alert-list');
  
  if (!container) {
    console.error('找不到告警列表容器元素');
    return;
  }
  
  // 清空容器
  container.innerHTML = '';
  
  // 如果没有告警,显示空消息
  if (alertHistory.length === 0) {
    container.innerHTML = '<div class="rv-alert-empty">暂无告警信息</div>';
    return;
  }
  
  // 只显示最新的5条告警信息
  const recentAlerts = alertHistory.slice(0, 5);
  
  // 遍历告警并创建列表项
  recentAlerts.forEach(alert => {
    let alertClass = 'alert-info';
    if (alert.type === '警告') alertClass = 'alert-warning';
    if (alert.type === '危险') alertClass = 'alert-error';
    
    const item = document.createElement('div');
    item.className = `rv-alert-item ${alertClass}`;
    
    const timeFormatted = formatTime(alert.time);
    
    item.innerHTML = `
      <div class="rv-alert-icon">
        ${alert.type === '危险' ? 
          '<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>' : 
          '<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="rv-alert-content">
        <div class="rv-alert-message">${alert.message}</div>
        <div class="rv-alert-time">${timeFormatted}</div>
      </div>
      <div class="rv-alert-actions">
        <button class="rv-alert-btn view-vessel" data-id="${alert.id}">查看</button>
      </div>
    `;
    
    container.appendChild(item);
  });
  
  // 如果有更多告警,显示提示信息
  if (alertHistory.length > 5) {
    const moreInfoElement = document.createElement('div');
    moreInfoElement.className = 'rv-alert-more-info';
    moreInfoElement.textContent = `还有 ${alertHistory.length - 5} 条更多告警信息`;
    container.appendChild(moreInfoElement);
  }
  
  // 添加告警列表中的"查看"按钮点击事件
  document.querySelectorAll('.view-vessel').forEach(button => {
    button.addEventListener('click', function(e) {
      e.stopPropagation();
      const alertId = parseInt(this.getAttribute('data-id'));
      const alert = alertHistory.find(a => a.id === alertId);
      if (alert && alert.message) {
        // 提取反应釜ID
        const match = alert.message.match(/反应釜(\w+)/);
        if (match && match[1]) {
          // 查找对应的反应釜并显示详情
          const vessel = vesselData.find(v => v.name.includes(match[1]));
          if (vessel) {
            showVesselDetails(vessel.id);
          }
        }
      }
    });
  });
}

// 处理告警
function processAlert(alertId) {
  const alert = alertHistory.find(a => a.id === alertId);
  
  if (alert) {
    alert.processed = true;
    renderAlertList();
    updateStatistics();
  }
}

// 清除已处理的告警
function clearProcessedAlerts() {
  alertHistory = alertHistory.filter(alert => !alert.processed);
  renderAlertList();
  updateStatistics();
}

// 显示反应釜详情
function showVesselDetails(vesselId) {
  const vessel = vesselData.find(v => v.id == vesselId);
  if (!vessel) return;
  
  const modal = document.getElementById('vessel-detail-modal');
  const title = document.getElementById('vessel-detail-title');
  const content = document.getElementById('vessel-detail-content');
  
  title.textContent = vessel.name + ' 详情';
  
  // 格式化最后更新时间
  const lastUpdated = vessel.lastUpdated;
  const timeString = `${lastUpdated.getHours().toString().padStart(2, '0')}:${lastUpdated.getMinutes().toString().padStart(2, '0')}:${lastUpdated.getSeconds().toString().padStart(2, '0')}`;
  
  content.innerHTML = `
    <div class="rv-detail-status status-${vessel.status}">
      <strong>状态:</strong> ${getStatusText(vessel.status)}
    </div>
    
    <div class="rv-detail-info">
      <p><strong>描述:</strong> ${vessel.description}</p>
      <p><strong>最后更新:</strong> ${timeString}</p>
    </div>
    
    <div class="rv-detail-metrics">
      <div class="rv-detail-metric">
        <div class="rv-detail-metric-value">${vessel.currentPressure}</div>
        <div class="rv-detail-metric-label">压力 (MPa)</div>
      </div>
      <div class="rv-detail-metric">
        <div class="rv-detail-metric-value">${vessel.temperature}°C</div>
        <div class="rv-detail-metric-label">温度</div>
      </div>
      <div class="rv-detail-metric">
        <div class="rv-detail-metric-value">${vessel.fillLevel}%</div>
        <div class="rv-detail-metric-label">液位</div>
      </div>
    </div>
    
    <div class="rv-detail-params">
      <h4>运行参数</h4>
      <table>
        <tr>
          <td>安全压力范围:</td>
          <td>5.0 - 7.5 MPa</td>
        </tr>
        <tr>
          <td>最佳温度范围:</td>
          <td>75 - 85 °C</td>
        </tr>
        <tr>
          <td>建议液位范围:</td>
          <td>40% - 80%</td>
        </tr>
        <tr>
          <td>搅拌速度:</td>
          <td>60 rpm</td>
        </tr>
        <tr>
          <td>反应类型:</td>
          <td>${['聚合', '中和', '催化', '分解', '合成'][vessel.id - 1]}反应</td>
        </tr>
      </table>
    </div>
  `;
  
  // 显示模态框
  modal.style.display = 'flex';
  
  // 添加关闭模态框的事件
  document.querySelector('.rv-modal-close').onclick = function() {
    modal.style.display = 'none';
  };
  
  document.querySelector('.modal-close-btn').onclick = function() {
    modal.style.display = 'none';
  };
  
  // 点击模态框外部关闭
  window.onclick = function(event) {
    if (event.target == modal) {
      modal.style.display = 'none';
    }
  };
}

// 辅助函数 - 获取状态文本
function getStatusText(status) {
  switch (status) {
    case 'normal': return '正常';
    case 'warning': return '警告';
    case 'error': return '错误';
    case 'maintenance': return '维护中';
    default: return '未知';
  }
}

// 设置事件监听器
function setupEventListeners() {
  // 监听时间范围选择
  document.getElementById('time-range').addEventListener('change', function() {
    updatePressureChart();
  });
  
  // 监听刷新按钮
  const refreshBtn = document.querySelector('.rv-vessels .rv-btn');
  if (refreshBtn) {
    refreshBtn.addEventListener('click', function() {
      refreshData();
    });
  }
  
  // 监听模态框关闭按钮
  const modalCloseBtn = document.querySelector('.rv-modal-close');
  if (modalCloseBtn) {
    modalCloseBtn.addEventListener('click', function() {
      document.getElementById('vessel-detail-modal').style.display = 'none';
    });
  }
  
  const secondaryCloseBtn = document.querySelector('.modal-close-btn');
  if (secondaryCloseBtn) {
    secondaryCloseBtn.addEventListener('click', function() {
      document.getElementById('vessel-detail-modal').style.display = 'none';
    });
  }
}

// 检查所有设备
function checkAllVessels() {
  // 模拟设备检查
  const checkTime = document.getElementById('check-time');
  checkTime.textContent = formatTime(new Date());
  
  // 显示检查成功消息
  const message = `设备检查完成,发现 ${vessels.filter(v => v.status !== 'normal').length} 个异常设备`;
  addAlert('信息', message);
  
  // 更新系统状态
  const hasIssues = vessels.some(v => v.status === 'critical');
  updateSystemStatus(hasIssues ? 'warning' : 'online');
  
  // 视觉反馈
  const btn = document.getElementById('check-all-btn');
  btn.textContent = '检查完成';
  btn.disabled = true;
  
  setTimeout(() => {
    btn.textContent = '检查所有设备';
    btn.disabled = false;
  }, 2000);
}

// 更新系统状态
function updateSystemStatus(status) {
  systemStatus = status;
  
  // 添加空检查,防止报错
  const indicator = document.querySelector('.status-indicator');
  const statusText = document.querySelector('.status-text');
  
  // 如果元素不存在,则更新状态栏
  const statusBadge = document.querySelector('.rv-status-badge');
  if (statusBadge) {
    statusBadge.className = 'rv-status-badge';
    if (status === 'online') {
      statusBadge.classList.add('status-normal');
      statusBadge.textContent = '系统正常';
    } else if (status === 'warning') {
      statusBadge.classList.add('status-warning');
      statusBadge.textContent = '系统警告';
    } else if (status === 'offline') {
      statusBadge.classList.add('status-error');
      statusBadge.textContent = '系统离线';
    }
    return;
  }
  
  // 兼容旧版本接口
  if (indicator) {
    indicator.className = 'status-indicator';
    indicator.classList.add(status);
  }
  
  if (statusText) {
    if (status === 'online') {
      statusText.textContent = '系统运行正常';
    } else if (status === 'warning') {
      statusText.textContent = '系统警告';
    } else if (status === 'offline') {
      statusText.textContent = '系统离线';
    }
  }
}

// 格式化时间
function formatTime(date) {
  return new Intl.DateTimeFormat('zh-CN', {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  }).format(date);
}

// 自动增加模拟设备函数
function addSimulatedVessel() {
  const newId = vessels.length + 1;
  const pressure = Math.random() * 0.8 + 0.1;
  
  const newVessel = {
    id: newId,
    name: `反应釜 #${newId}`,
    currentPressure: pressure.toFixed(2),
    status: getStatusFromPressure(pressure),
    temperature: (Math.random() * 30 + 70).toFixed(1),
    pressureHistory: [],
    fillLevel: Math.round(Math.random() * 100),
    lastUpdated: new Date(),
    description: `新增反应釜 #${newId} 已连接`
  };
  
  vessels.push(newVessel);
  addAlert('信息', newVessel.description);
  renderVesselList();
  updatePressureChart();
}

// 初始化监控系统
window.onload = function() {
  // 确认Chart.js是否已加载
  if (typeof Chart === 'undefined') {
    console.error('Chart.js库未加载,尝试动态加载');
    loadChartJs();
  } else {
    try {
      initializeMonitor();
    } catch (error) {
      console.error('初始化监控系统失败:', error);
      updateSystemStatus('offline');
      
      // 不再尝试添加告警,防止错误循环
      if (document.querySelector('.rv-badge')) {
        document.querySelector('.rv-badge').textContent = '错误';
      }
      
      // 显示错误信息
      const chartContainer = document.querySelector('.chart-container');
      if (chartContainer) {
        const loadingDiv = chartContainer.querySelector('.chart-loading');
        if (loadingDiv) {
          loadingDiv.innerHTML = `
            <div class="loading-text" style="color: var(--accent-red)">
              加载失败: ${error.message}
            </div>
            <button class="rv-btn rv-btn-sm" onclick="location.reload()">
              重试
            </button>
          `;
        }
      }
    }
  }
};

// 动态加载Chart.js
function loadChartJs() {
  const script = document.createElement('script');
  script.src = 'https://cdn.jsdelivr.net/npm/chart.js';
  script.onload = function() {
    console.log('Chart.js加载成功');
    initializeMonitor();
  };
  script.onerror = function() {
    console.error('Chart.js加载失败');
    const chartContainer = document.querySelector('.chart-container');
    if (chartContainer) {
      const loadingDiv = chartContainer.querySelector('.chart-loading');
      if (loadingDiv) {
        loadingDiv.innerHTML = `
          <div class="loading-text" style="color: var(--accent-red)">
            Chart.js加载失败,请检查网络连接
          </div>
          <button class="rv-btn rv-btn-sm" onclick="location.reload()">
            重试
          </button>
        `;
      }
    }
  };
  document.head.appendChild(script);
}

// 导出函数供 Appsmith 使用
window.reactionVesselMonitor = {
  // 初始化函数
  initialize: initializeMonitor,
  
  // 功能函数
  refreshData,
  addSimulatedVessel,
  checkAllVessels,
  clearProcessedAlerts,
  
  // 获取数据函数
  getVessels: () => vessels,
  getAlerts: () => alertHistory,
  getSystemStatus: () => systemStatus,
  
  // 设置函数
  setWarningThresholds: (low, high, critical) => {
    pressureThresholds.low = parseFloat(low) || 0.2;
    pressureThresholds.high = parseFloat(high) || 0.8;
    pressureThresholds.critical = parseFloat(critical) || 1.0;
    
    // 更新状态
    vessels.forEach(vessel => {
      vessel.status = getStatusFromPressure(parseFloat(vessel.currentPressure));
    });
    
    renderVesselList();
  }
};

// 根据压力获取状态
function getStatusFromPressure(pressure) {
  if (pressure >= pressureThresholds.critical) {
    return 'critical';
  } else if (pressure >= pressureThresholds.high || pressure <= pressureThresholds.low) {
    return 'warning';
  } else {
    return 'normal';
  }
} 

网站公告

今日签到

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