4、反应釜压力监控系统 - /自动化与控制组件/reaction-vessel-monitor
76个工业组件库示例汇总
反应釜压力监控组件
这是一个用于反应釜压力监控的自定义组件,专为化工厂反应釜压力监控设计。采用苹果工业风格界面,简洁优雅,功能实用,易于使用。
功能特点
- 实时压力可视化:直观展示反应釜压力数据变化趋势
- 多反应釜监控:支持同时监控多个反应釜设备
- 压力预警系统:提供高/低压力阈值监控和报警
- 设备状态指示:清晰展示各反应釜工作状态
- 简洁数据面板:展示关键数据指标和统计信息
- 苹果工业风格:优雅简洁的界面设计,符合现代工业审美
组件区域说明
组件主要包含以下功能区域:
- 总览视图:顶部显示反应釜系统整体状态和关键指标
- 压力趋势图:中部展示历史压力数据和实时变化趋势
- 设备状态列表:右侧显示各反应釜的详细状态和当前压力
- 告警信息区:底部显示最近的告警信息和处理状态
自定义选项
可以根据实际需求调整以下内容:
- 压力阈值:在脚本中设置高/低压力预警阈值
- 监控反应釜数量:通过修改配置调整监控的设备数量
- 图表显示时长:调整历史数据显示的时间范围
- 刷新频率:根据需要调整数据更新频率
- 颜色主题:修改CSS变量来调整整体配色方案
连接实际数据源
组件默认使用模拟数据。如需连接真实数据源:
- 替换
fetchVesselData()
函数中的数据模拟逻辑 - 实现与实际数据API的连接
- 根据实际数据格式调整数据处理逻辑
项目结构
效果展示
源码
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">×</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) {// 提取反应釜IDconst 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';}
}