76個工業組件庫示例匯總
食品包裝線控制系統
這是一個用于食品包裝線控制系統的自定義組件,提供了食品包裝生產線的可視化監控與控制界面。組件采用工業風格設計,包含生產流程控制、實時數據監控和邏輯編程三個主要功能區域。
功能特點
- 工業風格UI設計:深色主題,高對比度,符合工業控制系統的視覺風格
- 生產流程可視化:直觀展示從上料到碼垛的完整包裝生產線工藝流程
- 實時數據監控:支持產量統計、質量數據和效率分析等多種數據可視化
- 邏輯控制編程:提供可視化編程、代碼編輯和時序圖三種邏輯開發方式
- 靈活的配置選項:可調整生產速度、包裝規格等關鍵參數
- 告警系統:實時顯示系統告警信息,支持告警確認和處理
- 系統狀態監控:展示CPU負載、內存使用和通信狀態等系統指標
- 響應式設計:適應不同屏幕尺寸,確保在各種設備上正常顯示
主要區域說明
組件包含以下主要功能區域:
- 頂部控制欄:顯示系統名稱、狀態和基本控制按鈕(啟動、停止、緊急停止)
- 生產流程控制區:
- 流程可視化:展示6個工站(上料、稱重、包裝、貼標、檢測、碼垛)的狀態和連接
- 生產參數控制:提供速度、包裝規格和批次號等參數調整
- 實時數據監控區:
- 數據圖表:多種圖表類型,展示產量、質量和效率數據
- 關鍵指標卡片:總產量、合格率、設備效率和運行時間等核心指標
- 邏輯編程控制區:
- 可視化編程:拖拽式邏輯流程創建
- 代碼編輯:支持梯形圖、順序功能圖和結構化文本三種PLC編程語言
- 時序圖:工站操作的時序關系可視化
- 底部狀態欄:顯示告警信息、系統資源使用情況和當前時間
自定義選項
可以通過修改代碼自定義以下內容:
- 顏色主題:在CSS中修改
:root
中的顏色變量 - 工站配置:在HTML的
process-line
區域添加或修改工站 - 圖表類型:在JavaScript的
updateChart
函數中修改圖表樣式和數據 - 告警閾值:在JavaScript的
checkAlarms
函數中調整告警觸發條件 - 邏輯編程界面:修改工具箱中的邏輯元件和操作類型
連接實際設備
組件目前使用模擬數據進行演示。要連接實際的生產線設備,需要:
- 替換JavaScript中的數據模擬函數,連接到實際的數據源
- 調整控制按鈕的事件處理函數,使其發送實際的控制命令
- 實現數據持久化存儲,保存歷史數據和生產記錄
項目結構
效果展示
源碼
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"></head>
<body><div id="food-packaging-control"><!-- 頂部控制欄 --><div class="control-header"><div class="logo-section"><div class="system-logo">食品包裝線控制系統</div><div class="system-version">v1.0</div></div><div class="control-status"><div class="status-item"><span class="status-label">系統狀態:</span><span class="status-value" id="system-status">運行中</span><div class="status-indicator running"></div></div><div class="status-item"><span class="status-label">當前產量:</span><span class="status-value" id="current-output">1,245 件/小時</span></div><div class="control-actions"><button class="control-btn start-btn" id="start-system">啟動系統</button><button class="control-btn stop-btn" id="stop-system">停止系統</button><button class="control-btn emergency-btn" id="emergency-stop">緊急停止</button></div></div></div><!-- 主內容區域 --><div class="main-content"><!-- 左側:流程控制區 --><div class="panel process-panel"><div class="panel-header"><h3>生產流程控制</h3><div class="panel-actions"><button class="panel-btn" id="process-settings" title="流程設置"><i class="btn-icon">?</i></button><button class="panel-btn" id="process-expand" title="展開視圖"><i class="btn-icon">?</i></button></div></div><div class="panel-body"><div class="process-visualization" id="process-visual"><!-- 包裝流程可視化區域 --><div class="process-line"><div class="process-station" id="station-1"><div class="station-icon feeding"></div><div class="station-label">上料站</div><div class="station-status active" id="station-1-status"></div></div><div class="process-connection"><div class="process-flow" id="flow-1-2"></div></div><div class="process-station" id="station-2"><div class="station-icon weighing"></div><div class="station-label">稱重站</div><div class="station-status active" id="station-2-status"></div></div><div class="process-connection"><div class="process-flow" id="flow-2-3"></div></div><div class="process-station" id="station-3"><div class="station-icon packaging"></div><div class="station-label">包裝站</div><div class="station-status active" id="station-3-status"></div></div><div class="process-connection"><div class="process-flow" id="flow-3-4"></div></div><div class="process-station" id="station-4"><div class="station-icon labeling"></div><div class="station-label">貼標站</div><div class="station-status active" id="station-4-status"></div></div><div class="process-connection"><div class="process-flow" id="flow-4-5"></div></div><div class="process-station" id="station-5"><div class="station-icon inspection"></div><div class="station-label">檢測站</div><div class="station-status active" id="station-5-status"></div></div><div class="process-connection"><div class="process-flow" id="flow-5-6"></div></div><div class="process-station" id="station-6"><div class="station-icon palletizing"></div><div class="station-label">碼垛站</div><div class="station-status active" id="station-6-status"></div></div></div></div><div class="process-controls"><div class="control-group"><div class="control-label">生產速度:</div><div class="control-input"><input type="range" id="speed-control" min="50" max="150" value="100"><span id="speed-value">100%</span></div></div><div class="control-group"><div class="control-label">包裝規格:</div><div class="control-input"><select id="package-type"><option value="small">小包裝 (100g)</option><option value="medium" selected>中包裝 (250g)</option><option value="large">大包裝 (500g)</option><option value="bulk">散裝 (1kg)</option></select></div></div><div class="control-group"><div class="control-label">批次號:</div><div class="control-input"><input type="text" id="batch-number" value="B202504091"></div></div></div></div></div><!-- 中間:實時數據與監控 --><div class="panel monitoring-panel"><div class="panel-header"><h3>實時數據監控</h3><div class="panel-actions"><select id="chart-selector"><option value="production">產量統計</option><option value="quality">質量數據</option><option value="efficiency">效率分析</option></select><button class="panel-btn" id="export-data" title="導出數據"><i class="btn-icon">↓</i></button></div></div><div class="panel-body"><div class="chart-container"><div class="chart-header"><span id="chart-title">當日產量數據</span><div class="chart-legend"><div class="legend-item"><span class="legend-color" style="background-color: #2196F3;"></span><span class="legend-text">實際產量</span></div><div class="legend-item"><span class="legend-color" style="background-color: #4CAF50;"></span><span class="legend-text">目標產量</span></div><div class="legend-item"><span class="legend-color" style="background-color: #FF5722;"></span><span class="legend-text">不良品率</span></div></div></div><div class="chart-wrapper" id="chart-area"><!-- 圖表將由JavaScript渲染 --></div></div><div class="monitor-grid"><div class="monitor-card"><div class="card-label">總產量</div><div class="card-value" id="total-output">24,589</div><div class="card-unit">包/日</div></div><div class="monitor-card"><div class="card-label">合格率</div><div class="card-value" id="quality-rate">99.7%</div><div class="card-trend positive">↑0.2%</div></div><div class="monitor-card"><div class="card-label">設備效率</div><div class="card-value" id="equipment-efficiency">94.3%</div><div class="card-trend positive">↑1.5%</div></div><div class="monitor-card"><div class="card-label">運行時間</div><div class="card-value" id="running-time">06:42:15</div><div class="card-unit">時:分:秒</div></div></div></div></div><!-- 右側:邏輯編程區 --><div class="panel logic-panel"><div class="panel-header"><h3>邏輯控制編程</h3><div class="panel-actions"><button class="panel-btn" id="new-logic" title="新建"><i class="btn-icon">+</i></button><button class="panel-btn" id="save-logic" title="保存"><i class="btn-icon">?</i></button><button class="panel-btn" id="deploy-logic" title="部署"><i class="btn-icon">↗</i></button></div></div><div class="panel-body"><div class="tab-header"><div class="tab active" data-tab="visual-programming">可視化編程</div><div class="tab" data-tab="code-editor">代碼編輯</div><div class="tab" data-tab="sequence">時序圖</div></div><div class="tab-content"><div class="tab-pane active" id="visual-programming-pane"><div class="logic-toolbox"><div class="toolbox-section"><div class="toolbox-title">控制元件</div><div class="toolbox-items"><div class="logic-item" draggable="true" data-type="start">開始</div><div class="logic-item" draggable="true" data-type="decision">判斷</div><div class="logic-item" draggable="true" data-type="action">動作</div><div class="logic-item" draggable="true" data-type="delay">延時</div><div class="logic-item" draggable="true" data-type="parallel">并行</div><div class="logic-item" draggable="true" data-type="end">結束</div></div></div><div class="toolbox-section"><div class="toolbox-title">工站操作</div><div class="toolbox-items"><div class="logic-item" draggable="true" data-type="feeding">上料</div><div class="logic-item" draggable="true" data-type="weighing">稱重</div><div class="logic-item" draggable="true" data-type="packaging">包裝</div><div class="logic-item" draggable="true" data-type="labeling">貼標</div><div class="logic-item" draggable="true" data-type="inspection">檢測</div><div class="logic-item" draggable="true" data-type="palletizing">碼垛</div></div></div></div><div class="logic-canvas" id="logic-canvas"><!-- 邏輯流程圖將由JavaScript渲染 --><div class="placeholder-text">拖放控制元件到此區域創建邏輯流程</div><div class="workflow-container" id="workflow-container"><!-- 工作流將在這里動態創建 --></div></div></div><div class="tab-pane" id="code-editor-pane"><div class="editor-toolbar"><select id="language-selector"><option value="ladder">梯形圖</option><option value="sfc">順序功能圖</option><option value="st">結構化文本</option></select><button class="editor-btn" id="check-syntax">檢查語法</button><button class="editor-btn" id="format-code">格式化</button></div><div class="code-editor" id="code-editor"><pre class="code-content" id="code-content">// 食品包裝線控制邏輯// 以下為示例梯形圖代碼PROGRAM PackagingControlVARStartButton AT %I0.0: BOOL;StopButton AT %I0.1: BOOL;EmergencyStop AT %I0.2: BOOL;ConveyorRunning AT %Q0.0: BOOL;FeedingSystem AT %Q0.1: BOOL;WeighingSystem AT %Q0.2: BOOL;PackagingSystem AT %Q0.3: BOOL;LabelingSystem AT %Q0.4: BOOL;InspectionSystem AT %Q0.5: BOOL;PalletizingSystem AT %Q0.6: BOOL;SystemRunning: BOOL;ProductDetected AT %I0.3: BOOL;WeightOK AT %I0.4: BOOL;PackageSealed AT %I0.5: BOOL;LabelApplied AT %I0.6: BOOL;QualityCheck AT %I0.7: BOOL;END_VAR// 主控制邏輯SystemRunning := StartButton AND NOT StopButton AND NOT EmergencyStop;ConveyorRunning := SystemRunning;// 各工站控制邏輯FeedingSystem := SystemRunning AND (FeedingSystem OR NOT WeighingSystem);WeighingSystem := SystemRunning AND ProductDetected AND WeightOK;PackagingSystem := SystemRunning AND WeighingSystem AND WeightOK;LabelingSystem := SystemRunning AND PackageSealed;InspectionSystem := SystemRunning AND LabelApplied;PalletizingSystem := SystemRunning AND QualityCheck;END_PROGRAM</pre></div></div><div class="tab-pane" id="sequence-pane"><div class="sequence-diagram" id="sequence-diagram"><!-- 時序圖將由JavaScript渲染 --><div class="placeholder-text">選擇工站查看詳細時序圖</div></div><div class="sequence-controls"><div class="sequence-station-selector"><label for="station-selector">選擇工站:</label><select id="station-selector"><option value="all">整體流程</option><option value="station-1">上料站</option><option value="station-2">稱重站</option><option value="station-3">包裝站</option><option value="station-4">貼標站</option><option value="station-5">檢測站</option><option value="station-6">碼垛站</option></select></div><div class="sequence-time-scale"><label for="time-scale">時間尺度:</label><select id="time-scale"><option value="1x">1x (實時)</option><option value="2x">2x (加速)</option><option value="0.5x">0.5x (減速)</option></select></div></div></div></div></div></div></div><!-- 底部狀態欄 --><div class="footer-bar"><div class="alarm-section"><div class="alarm-icon" id="alarm-icon">?</div><div class="alarm-count" id="alarm-count">0</div><div class="alarm-message" id="current-alarm">無告警信息</div></div><div class="system-metrics"><div class="metric"><span class="metric-label">CPU負載:</span><span class="metric-value" id="cpu-load">32%</span></div><div class="metric"><span class="metric-label">內存使用:</span><span class="metric-value" id="memory-usage">1.2GB/4GB</span></div><div class="metric"><span class="metric-label">通信狀態:</span><span class="metric-value connected" id="comm-status">已連接</span></div></div><div class="system-time" id="system-time">2025-04-09 08:51:22</div></div><!-- 告警彈窗 --><div class="alarm-modal" id="alarm-modal"><div class="alarm-modal-header"><div class="alarm-modal-title">系統告警</div><div class="alarm-modal-close" id="close-alarm-modal">×</div></div><div class="alarm-modal-body"><div class="alarm-list" id="alarm-list"><!-- 告警列表將由JavaScript動態生成 --></div></div><div class="alarm-modal-footer"><button class="alarm-btn" id="acknowledge-all">確認所有</button><button class="alarm-btn" id="close-modal">關閉</button></div></div></div> <script src="script.js"></script>
</body>
</html>
styles.css
/* 食品包裝線控制系統 - 樣式表 */
:root {/* 顏色變量 */--primary-dark: #1a2b42; /* 主要深色背景 */--primary-medium: #253952; /* 次要深色背景 */--primary-light: #2d4263; /* 淺色背景 */--accent-blue: #0288d1; /* 藍色強調 */--accent-green: #2e7d32; /* 綠色強調 */--accent-red: #d32f2f; /* 紅色強調/告警 */--accent-orange: #f57c00; /* 橙色強調/警告 */--text-primary: #ffffff; /* 主要文本 */--text-secondary: #b0bec5; /* 次要文本 */--border-color: #37474f; /* 邊框顏色 */--hover-color: rgba(255, 255, 255, 0.08); /* 懸停效果 *//* 尺寸變量 */--header-height: 60px;--footer-height: 40px;--panel-gap: 12px;--border-radius: 4px;
}/* 基礎樣式 */
#food-packaging-control {font-family: 'Roboto', 'Arial', sans-serif;color: var(--text-primary);background-color: var(--primary-dark);display: flex;flex-direction: column;height: 100vh;overflow: hidden;box-sizing: border-box;margin: 0;padding: 0;user-select: none;
}#food-packaging-control * {box-sizing: border-box;
}/* 頂部控制欄 */
.control-header {height: var(--header-height);background-color: var(--primary-medium);border-bottom: 1px solid var(--border-color);display: flex;justify-content: space-between;align-items: center;padding: 0 16px;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);z-index: 10;
}.logo-section {display: flex;flex-direction: column;align-items: flex-start;
}.system-logo {font-size: 1.2rem;font-weight: bold;letter-spacing: 0.5px;
}.system-version {font-size: 0.7rem;color: var(--text-secondary);
}.control-status {display: flex;align-items: center;gap: 24px;
}.status-item {display: flex;align-items: center;gap: 8px;
}.status-label {color: var(--text-secondary);font-size: 0.85rem;
}.status-value {font-weight: bold;font-size: 0.9rem;
}.status-indicator {width: 10px;height: 10px;border-radius: 50%;
}.status-indicator.running {background-color: var(--accent-green);box-shadow: 0 0 8px var(--accent-green);
}.status-indicator.warning {background-color: var(--accent-orange);box-shadow: 0 0 8px var(--accent-orange);
}.status-indicator.error {background-color: var(--accent-red);box-shadow: 0 0 8px var(--accent-red);
}.status-indicator.idle {background-color: var(--text-secondary);
}.control-actions {display: flex;gap: 8px;
}.control-btn {padding: 8px 12px;border: none;border-radius: var(--border-radius);font-weight: bold;font-size: 0.85rem;cursor: pointer;transition: all 0.2s;
}.start-btn {background-color: var(--accent-green);color: white;
}.start-btn:hover {background-color: #388e3c;
}.stop-btn {background-color: #455a64;color: white;
}.stop-btn:hover {background-color: #546e7a;
}.emergency-btn {background-color: var(--accent-red);color: white;
}.emergency-btn:hover {background-color: #e53935;
}/* 主內容區域 */
.main-content {flex: 1;display: flex;gap: var(--panel-gap);padding: var(--panel-gap);height: calc(100vh - var(--header-height) - var(--footer-height));overflow: hidden;
}/* 面板通用樣式 */
.panel {background-color: var(--primary-medium);border-radius: var(--border-radius);box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);overflow: hidden;display: flex;flex-direction: column;
}.process-panel {flex: 1;
}.monitoring-panel {flex: 1.5;
}.logic-panel {flex: 1.5;
}.panel-header {display: flex;justify-content: space-between;align-items: center;padding: 12px 16px;background-color: var(--primary-light);border-bottom: 1px solid var(--border-color);
}.panel-header h3 {margin: 0;font-size: 1rem;font-weight: 500;letter-spacing: 0.5px;
}.panel-actions {display: flex;gap: 8px;align-items: center;
}.panel-btn {width: 30px;height: 30px;border: none;border-radius: 4px;background-color: var(--primary-medium);color: var(--text-primary);cursor: pointer;display: flex;align-items: center;justify-content: center;transition: background-color 0.2s;
}.panel-btn:hover {background-color: var(--hover-color);
}.btn-icon {font-style: normal;
}.panel-body {flex: 1;overflow: auto;padding: 16px;display: flex;flex-direction: column;gap: 16px;
}/* 流程控制面板 */
.process-visualization {background-color: var(--primary-dark);border-radius: var(--border-radius);padding: 16px;min-height: 180px;overflow: auto;
}.process-line {display: flex;align-items: center;justify-content: space-between;padding: 8px 0;
}.process-station {position: relative;width: 80px;display: flex;flex-direction: column;align-items: center;gap: 8px;z-index: 2;
}.station-icon {width: 50px;height: 50px;border-radius: 8px;background-color: var(--primary-light);border: 2px solid var(--border-color);display: flex;align-items: center;justify-content: center;position: relative;transition: all 0.3s;
}.station-icon::before {font-family: Arial, sans-serif;font-size: 24px;font-weight: bold;
}.station-icon.feeding::before { content: "F"; color: #64b5f6; }
.station-icon.weighing::before { content: "W"; color: #81c784; }
.station-icon.packaging::before { content: "P"; color: #ffb74d; }
.station-icon.labeling::before { content: "L"; color: #ba68c8; }
.station-icon.inspection::before { content: "I"; color: #4fc3f7; }
.station-icon.palletizing::before { content: "S"; color: #f06292; }.station-label {font-size: 0.8rem;text-align: center;
}.station-status {position: absolute;width: 10px;height: 10px;border-radius: 50%;top: -5px;right: 12px;
}.station-status.active {background-color: var(--accent-green);box-shadow: 0 0 5px var(--accent-green);
}.station-status.warning {background-color: var(--accent-orange);box-shadow: 0 0 5px var(--accent-orange);
}.station-status.error {background-color: var(--accent-red);box-shadow: 0 0 5px var(--accent-red);
}.station-status.idle {background-color: var(--text-secondary);
}.process-connection {flex: 1;display: flex;align-items: center;justify-content: center;padding: 0 4px;z-index: 1;
}.process-flow {height: 4px;background-color: var(--accent-blue);width: 100%;position: relative;overflow: hidden;
}.process-flow.active::after {content: '';position: absolute;top: 0;left: -20%;height: 100%;width: 20%;background-color: rgba(255, 255, 255, 0.7);animation: flow 1.5s linear infinite;
}@keyframes flow {0% { left: -20%; }100% { left: 100%; }
}.process-controls {display: flex;flex-direction: column;gap: 12px;margin-top: 16px;
}.control-group {display: flex;justify-content: space-between;align-items: center;
}.control-label {font-size: 0.85rem;color: var(--text-secondary);
}.control-input {flex: 1;max-width: 200px;display: flex;align-items: center;gap: 8px;
}input[type="range"] {flex: 1;cursor: pointer;
}input[type="text"], select {background-color: var(--primary-dark);border: 1px solid var(--border-color);border-radius: 4px;color: var(--text-primary);padding: 6px 10px;width: 100%;font-size: 0.85rem;
}select {appearance: none;background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23b0bec5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat: no-repeat;background-position: right 8px center;padding-right: 30px;
}/* 監控面板 */
.chart-container {background-color: var(--primary-dark);border-radius: var(--border-radius);padding: 16px;min-height: 240px;display: flex;flex-direction: column;gap: 12px;
}.chart-header {display: flex;justify-content: space-between;align-items: center;flex-wrap: wrap;gap: 8px;
}#chart-title {font-weight: 500;font-size: 0.9rem;
}.chart-legend {display: flex;gap: 12px;
}.legend-item {display: flex;align-items: center;gap: 4px;font-size: 0.8rem;
}.legend-color {width: 12px;height: 12px;border-radius: 2px;
}.chart-wrapper {flex: 1;min-height: 180px;position: relative;display: flex;align-items: center;justify-content: center;
}.monitor-grid {display: grid;grid-template-columns: repeat(4, 1fr);gap: 12px;
}.monitor-card {background-color: var(--primary-dark);border-radius: var(--border-radius);padding: 12px;display: flex;flex-direction: column;align-items: center;text-align: center;
}.card-label {font-size: 0.8rem;color: var(--text-secondary);margin-bottom: 4px;
}.card-value {font-size: 1.5rem;font-weight: bold;line-height: 1;margin-bottom: 4px;
}.card-unit {font-size: 0.75rem;color: var(--text-secondary);
}.card-trend {font-size: 0.75rem;font-weight: 500;
}.card-trend.positive {color: var(--accent-green);
}.card-trend.negative {color: var(--accent-red);
}/* 邏輯編程面板 */
.tab-header {display: flex;border-bottom: 1px solid var(--border-color);margin-bottom: 16px;margin-top: -8px;
}.tab {padding: 8px 16px;font-size: 0.85rem;cursor: pointer;border-bottom: 2px solid transparent;transition: all 0.2s;
}.tab:hover {background-color: var(--hover-color);
}.tab.active {border-bottom: 2px solid var(--accent-blue);color: var(--accent-blue);
}.tab-content {flex: 1;overflow: hidden;display: flex;
}.tab-pane {flex: 1;display: none;flex-direction: column;height: 100%;overflow: hidden;
}.tab-pane.active {display: flex;
}/* 可視化編程區域 */
.logic-toolbox {background-color: var(--primary-dark);border-radius: var(--border-radius);padding: 12px;display: flex;flex-wrap: wrap;gap: 12px;margin-bottom: 12px;
}.toolbox-section {flex: 1;min-width: 150px;
}.toolbox-title {font-size: 0.8rem;color: var(--text-secondary);margin-bottom: 8px;
}.toolbox-items {display: flex;flex-wrap: wrap;gap: 8px;
}.logic-item {background-color: var(--primary-light);border: 1px solid var(--border-color);border-radius: var(--border-radius);padding: 6px 10px;font-size: 0.85rem;cursor: grab;transition: all 0.2s;
}.logic-item:hover {background-color: var(--hover-color);border-color: var(--accent-blue);
}.logic-canvas {flex: 1;background-color: var(--primary-dark);border-radius: var(--border-radius);overflow: auto;position: relative;min-height: 250px;
}.placeholder-text {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);color: var(--text-secondary);font-size: 0.9rem;text-align: center;opacity: 0.7;
}.workflow-container {padding: 20px;min-height: 100%;position: relative;
}/* 代碼編輯區 */
.editor-toolbar {display: flex;gap: 8px;margin-bottom: 12px;
}.editor-btn {padding: 6px 12px;font-size: 0.85rem;border: none;background-color: var(--primary-dark);color: var(--text-primary);border-radius: var(--border-radius);cursor: pointer;transition: all 0.2s;
}.editor-btn:hover {background-color: var(--hover-color);
}.code-editor {flex: 1;background-color: var(--primary-dark);border-radius: var(--border-radius);overflow: auto;
}.code-content {font-family: 'Consolas', 'Monaco', monospace;font-size: 0.9rem;line-height: 1.5;padding: 12px;margin: 0;color: var(--text-primary);overflow: auto;tab-size: 4;
}/* 時序圖區域 */
.sequence-diagram {flex: 1;background-color: var(--primary-dark);border-radius: var(--border-radius);overflow: auto;position: relative;min-height: 250px;
}.sequence-controls {display: flex;justify-content: space-between;margin-top: 12px;padding: 8px 12px;background-color: var(--primary-dark);border-radius: var(--border-radius);
}.sequence-station-selector,
.sequence-time-scale {display: flex;align-items: center;gap: 8px;font-size: 0.85rem;
}.sequence-station-selector label,
.sequence-time-scale label {color: var(--text-secondary);
}.sequence-station-selector select,
.sequence-time-scale select {width: auto;
}/* 底部狀態欄 */
.footer-bar {height: var(--footer-height);background-color: var(--primary-medium);border-top: 1px solid var(--border-color);display: flex;justify-content: space-between;align-items: center;padding: 0 16px;font-size: 0.8rem;
}.alarm-section {display: flex;align-items: center;gap: 8px;
}.alarm-icon {color: var(--accent-orange);font-size: 1rem;
}.alarm-icon.active {animation: blink 1s infinite;
}@keyframes blink {0%, 100% { opacity: 1; }50% { opacity: 0.5; }
}.alarm-count {background-color: var(--accent-red);color: white;border-radius: 10px;padding: 0 6px;min-width: 18px;font-size: 0.7rem;text-align: center;display: inline-block;
}.alarm-message {color: var(--text-secondary);
}.system-metrics {display: flex;gap: 16px;
}.metric {display: flex;align-items: center;gap: 4px;
}.metric-label {color: var(--text-secondary);
}.metric-value {font-weight: 500;
}.connected {color: var(--accent-green);
}.disconnected {color: var(--accent-red);
}.system-time {color: var(--text-secondary);
}/* 告警彈窗 */
.alarm-modal {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);background-color: var(--primary-medium);border-radius: var(--border-radius);box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);z-index: 1000;width: 500px;max-width: 90vw;max-height: 80vh;display: none;flex-direction: column;
}.alarm-modal.show {display: flex;
}.alarm-modal-header {padding: 12px 16px;background-color: var(--primary-light);border-bottom: 1px solid var(--border-color);display: flex;justify-content: space-between;align-items: center;
}.alarm-modal-title {font-weight: 500;font-size: 1rem;
}.alarm-modal-close {font-size: 1.2rem;cursor: pointer;width: 24px;height: 24px;display: flex;align-items: center;justify-content: center;border-radius: 50%;
}.alarm-modal-close:hover {background-color: var(--hover-color);
}.alarm-modal-body {padding: 16px;overflow-y: auto;flex: 1;
}.alarm-list {display: flex;flex-direction: column;gap: 8px;
}.alarm-item {display: flex;align-items: flex-start;gap: 12px;padding: 8px;border-radius: var(--border-radius);background-color: var(--primary-dark);
}.alarm-item-icon {font-size: 1.2rem;margin-top: 2px;
}.alarm-item-icon.critical {color: var(--accent-red);
}.alarm-item-icon.warning {color: var(--accent-orange);
}.alarm-item-icon.info {color: var(--accent-blue);
}.alarm-item-content {flex: 1;
}.alarm-item-title {font-weight: 500;font-size: 0.9rem;
}.alarm-item-desc {font-size: 0.8rem;color: var(--text-secondary);margin-top: 4px;
}.alarm-item-time {font-size: 0.75rem;color: var(--text-secondary);margin-top: 4px;
}.alarm-modal-footer {padding: 12px 16px;border-top: 1px solid var(--border-color);display: flex;justify-content: flex-end;gap: 8px;
}.alarm-btn {padding: 6px 12px;border: none;border-radius: var(--border-radius);font-size: 0.85rem;cursor: pointer;background-color: var(--primary-dark);color: var(--text-primary);transition: all 0.2s;
}.alarm-btn:hover {background-color: var(--hover-color);
}/* 響應式布局 */
@media (max-width: 1200px) {.main-content {flex-direction: column;overflow-y: auto;height: calc(100vh - var(--header-height) - var(--footer-height));}.process-panel, .monitoring-panel, .logic-panel {width: 100%;flex: none;}.panel {max-height: 600px;}.monitor-grid {grid-template-columns: repeat(2, 1fr);}.control-status {flex-wrap: wrap;}
}@media (max-width: 768px) {.control-header {flex-direction: column;height: auto;padding: 12px;gap: 12px;}.logo-section {align-items: center;}.control-status {width: 100%;justify-content: center;}.footer-bar {flex-direction: column;height: auto;padding: 8px;gap: 8px;text-align: center;}.system-metrics {flex-wrap: wrap;justify-content: center;}.monitor-grid {grid-template-columns: 1fr;}
}
script.js
/*** 食品包裝線控制系統 - JavaScript控制邏輯* 本文件實現了食品包裝線控制系統的各項功能,包括:* - 系統狀態管理* - 工站控制和狀態監控* - 實時數據可視化* - 邏輯編程界面交互* - 告警管理*/// 全局變量
let systemRunning = true; // 系統運行狀態
let productionSpeed = 100; // 生產速度(%)
let currentPackageType = 'medium'; // 當前包裝類型
let batchNumber = 'B202504091'; // 當前批次號
let runningTime = 0; // 運行時間(秒)
let totalProduction = 24589; // 總產量
let alarmCount = 0; // 告警數量
let alarms = []; // 告警列表
let stationStatus = { // 各工站狀態'station-1': 'active','station-2': 'active','station-3': 'active','station-4': 'active','station-5': 'active','station-6': 'active'
};
let flowStatus = { // 物料流狀態'flow-1-2': true,'flow-2-3': true,'flow-3-4': true,'flow-4-5': true,'flow-5-6': true
};// 計時器
let runningTimeInterval;
let dataUpdateInterval;
let chartUpdateInterval;
let flowAnimationInterval;
let systemMetricsInterval;// 圖表數據
let productionData = {labels: [...Array(12).keys()].map(i => `${8 + Math.floor(i/2)}:${i % 2 ? '30' : '00'}`),actual: [920, 1050, 1150, 1230, 1320, 1260, 1200, 1310, 1400, 1380, 1420, 1245],target: [1000, 1000, 1200, 1200, 1300, 1300, 1300, 1300, 1400, 1400, 1400, 1400],defects: [2.1, 1.8, 1.9, 0.8, 0.5, 0.7, 0.9, 0.6, 0.4, 0.5, 0.3, 0.3]
};let qualityData = {labels: ['100g', '250g', '500g', '1kg'],pass: [99.2, 99.7, 99.5, 99.1],weight: [99.5, 99.8, 99.6, 99.4],seal: [99.7, 99.9, 99.8, 99.5],label: [99.3, 99.6, 99.4, 99.2]
};let efficiencyData = {labels: [...Array(8).keys()].map(i => `${i + 8}:00`),oee: [91.2, 92.5, 93.8, 94.2, 94.5, 94.7, 94.3, 93.6],uptime: [97.5, 98.2, 98.5, 98.7, 98.8, 98.5, 98.1, 97.8],performance: [94.5, 95.2, 96.1, 96.3, 96.5, 96.7, 96.3, 95.8]
};// 工作流元素數據
let workflowElements = [];/*** 初始化函數 - 頁面加載完成后執行*/
function initializeSystem() {// 綁定事件監聽器bindEventListeners();// 啟動系統模擬startSystemSimulation();// 初始化圖表updateChart('production');// 初始化邏輯編程界面initializeLogicProgramming();// 更新系統時間updateSystemTime();console.log('食品包裝線控制系統初始化完成');
}/*** 綁定所有UI元素的事件監聽器*/
function bindEventListeners() {// 系統控制按鈕document.getElementById('start-system').addEventListener('click', startSystem);document.getElementById('stop-system').addEventListener('click', stopSystem);document.getElementById('emergency-stop').addEventListener('click', emergencyStop);// 流程控制面板document.getElementById('process-settings').addEventListener('click', showProcessSettings);document.getElementById('process-expand').addEventListener('click', expandProcessView);document.getElementById('speed-control').addEventListener('input', updateSpeed);document.getElementById('package-type').addEventListener('change', updatePackageType);document.getElementById('batch-number').addEventListener('change', updateBatchNumber);// 監控面板document.getElementById('chart-selector').addEventListener('change', (e) => updateChart(e.target.value));document.getElementById('export-data').addEventListener('click', exportMonitoringData);// 邏輯編程面板document.querySelectorAll('.tab').forEach(tab => {tab.addEventListener('click', switchProgrammingTab);});document.getElementById('new-logic').addEventListener('click', createNewLogic);document.getElementById('save-logic').addEventListener('click', saveLogic);document.getElementById('deploy-logic').addEventListener('click', deployLogic);document.querySelectorAll('.logic-item').forEach(item => {item.addEventListener('dragstart', handleDragStart);});document.getElementById('logic-canvas').addEventListener('dragover', handleDragOver);document.getElementById('logic-canvas').addEventListener('drop', handleDrop);document.getElementById('check-syntax').addEventListener('click', checkCodeSyntax);document.getElementById('format-code').addEventListener('click', formatCode);document.getElementById('station-selector').addEventListener('change', updateSequenceDiagram);// 告警相關document.getElementById('alarm-icon').addEventListener('click', showAlarmModal);document.getElementById('close-alarm-modal').addEventListener('click', hideAlarmModal);document.getElementById('close-modal').addEventListener('click', hideAlarmModal);document.getElementById('acknowledge-all').addEventListener('click', acknowledgeAllAlarms);
}/*** 啟動系統模擬*/
function startSystemSimulation() {// 設置運行時間計時器runningTimeInterval = setInterval(updateRunningTime, 1000);// 設置數據更新計時器dataUpdateInterval = setInterval(updateRealTimeData, 2000);// 設置圖表更新計時器chartUpdateInterval = setInterval(() => {if (document.getElementById('chart-selector').value === 'production') {updateProductionData();updateChart('production');}}, 10000);// 設置流動動畫flowAnimationInterval = setInterval(updateFlowAnimation, 5000);// 設置系統指標更新systemMetricsInterval = setInterval(updateSystemMetrics, 3000);
}/*** 停止系統模擬*/
function stopSystemSimulation() {clearInterval(runningTimeInterval);clearInterval(dataUpdateInterval);clearInterval(chartUpdateInterval);clearInterval(flowAnimationInterval);clearInterval(systemMetricsInterval);
}/*** 啟動系統*/
function startSystem() {if (!systemRunning) {systemRunning = true;document.getElementById('system-status').textContent = '運行中';document.querySelector('.status-indicator').className = 'status-indicator running';startSystemSimulation();// 更新各工站狀態Object.keys(stationStatus).forEach(station => {stationStatus[station] = 'active';document.getElementById(station + '-status').className = 'station-status active';});// 更新流動狀態Object.keys(flowStatus).forEach(flow => {flowStatus[flow] = true;document.getElementById(flow).className = 'process-flow active';});addAlarm('info', '系統已啟動', '操作員啟動了系統');}
}/*** 停止系統*/
function stopSystem() {if (systemRunning) {systemRunning = false;document.getElementById('system-status').textContent = '已停止';document.querySelector('.status-indicator').className = 'status-indicator idle';stopSystemSimulation();// 更新各工站狀態Object.keys(stationStatus).forEach(station => {stationStatus[station] = 'idle';document.getElementById(station + '-status').className = 'station-status idle';});// 更新流動狀態Object.keys(flowStatus).forEach(flow => {flowStatus[flow] = false;document.getElementById(flow).className = 'process-flow';});addAlarm('info', '系統已停止', '操作員停止了系統');}
}/*** 緊急停止*/
function emergencyStop() {systemRunning = false;document.getElementById('system-status').textContent = '緊急停止';document.querySelector('.status-indicator').className = 'status-indicator error';stopSystemSimulation();// 更新各工站狀態Object.keys(stationStatus).forEach(station => {stationStatus[station] = 'error';document.getElementById(station + '-status').className = 'station-status error';});// 更新流動狀態Object.keys(flowStatus).forEach(flow => {flowStatus[flow] = false;document.getElementById(flow).className = 'process-flow';});addAlarm('critical', '系統緊急停止', '觸發緊急停止按鈕');
}/*** 顯示流程設置*/
function showProcessSettings() {// 此處可實現流程設置彈窗console.log('顯示流程設置');
}/*** 展開流程視圖*/
function expandProcessView() {// 此處可實現流程圖的全屏展示console.log('展開流程視圖');
}/*** 更新速度設置*/
function updateSpeed(event) {productionSpeed = event.target.value;document.getElementById('speed-value').textContent = productionSpeed + '%';console.log('生產速度已更新:', productionSpeed + '%');
}/*** 更新包裝類型*/
function updatePackageType(event) {currentPackageType = event.target.value;console.log('包裝規格已更新:', currentPackageType);
}/*** 更新批次號*/
function updateBatchNumber(event) {batchNumber = event.target.value;console.log('批次號已更新:', batchNumber);
}/*** 導出監控數據*/
function exportMonitoringData() {console.log('導出監控數據');// 此處可實現數據導出功能
}/*** 更新圖表顯示*/
function updateChart(chartType) {const chartArea = document.getElementById('chart-area');let chartTitle = '';// 清空圖表區域chartArea.innerHTML = '';// 根據圖表類型顯示不同的圖表switch(chartType) {case 'production':chartTitle = '當日產量數據';renderProductionChart(chartArea);break;case 'quality':chartTitle = '產品質量數據';renderQualityChart(chartArea);break;case 'efficiency':chartTitle = '生產效率分析';renderEfficiencyChart(chartArea);break;}document.getElementById('chart-title').textContent = chartTitle;
}/*** 渲染產量圖表*/
function renderProductionChart(container) {try {// 嘗試使用Chart.js繪制圖表if (typeof Chart !== 'undefined') {const canvas = document.createElement('canvas');container.appendChild(canvas);new Chart(canvas, {type: 'line',data: {labels: productionData.labels,datasets: [{label: '實際產量',data: productionData.actual,borderColor: '#2196F3',backgroundColor: 'rgba(33, 150, 243, 0.1)',tension: 0.3,fill: true},{label: '目標產量',data: productionData.target,borderColor: '#4CAF50',backgroundColor: 'transparent',borderDash: [5, 5],tension: 0.1},{label: '不良品率(%)',data: productionData.defects,borderColor: '#FF5722',backgroundColor: 'transparent',yAxisID: 'y1'}]},options: {scales: {y: {beginAtZero: false,title: {display: true,text: '產量 (件/小時)'}},y1: {position: 'right',beginAtZero: true,max: 5,title: {display: true,text: '不良品率 (%)'}}}}});} else {// 使用簡單SVG繪制圖表renderSimpleProductionChart(container);}} catch (error) {console.error('圖表渲染失敗:', error);renderSimpleProductionChart(container);}
}/*** 渲染簡單SVG產量圖表(當Chart.js不可用時)*/
function renderSimpleProductionChart(container) {const svgNS = "http://www.w3.org/2000/svg";const width = container.clientWidth;const height = 250;const svg = document.createElementNS(svgNS, "svg");svg.setAttribute("width", width);svg.setAttribute("height", height);svg.style.backgroundColor = "var(--primary-dark)";const maxValue = Math.max(...productionData.actual, ...productionData.target);const xStep = width / (productionData.labels.length - 1);const yScale = (height - 40) / maxValue;// 繪制坐標軸const axis = document.createElementNS(svgNS, "path");axis.setAttribute("d", `M 30 10 V ${height - 30} H ${width - 10}`);axis.setAttribute("stroke", "var(--border-color)");axis.setAttribute("fill", "none");svg.appendChild(axis);// 繪制實際產量線const actualLine = document.createElementNS(svgNS, "path");let path = `M ${30} ${height - 30 - productionData.actual[0] * yScale}`;for (let i = 1; i < productionData.actual.length; i++) {path += ` L ${30 + i * xStep} ${height - 30 - productionData.actual[i] * yScale}`;}actualLine.setAttribute("d", path);actualLine.setAttribute("stroke", "#2196F3");actualLine.setAttribute("stroke-width", "2");actualLine.setAttribute("fill", "none");svg.appendChild(actualLine);// 繪制目標產量線(虛線)const targetLine = document.createElementNS(svgNS, "path");path = `M ${30} ${height - 30 - productionData.target[0] * yScale}`;for (let i = 1; i < productionData.target.length; i++) {path += ` L ${30 + i * xStep} ${height - 30 - productionData.target[i] * yScale}`;}targetLine.setAttribute("d", path);targetLine.setAttribute("stroke", "#4CAF50");targetLine.setAttribute("stroke-width", "2");targetLine.setAttribute("stroke-dasharray", "5,5");targetLine.setAttribute("fill", "none");svg.appendChild(targetLine);container.appendChild(svg);
}/*** 渲染質量圖表*/
function renderQualityChart(container) {try {// 嘗試使用Chart.js繪制圖表if (typeof Chart !== 'undefined') {const canvas = document.createElement('canvas');container.appendChild(canvas);new Chart(canvas, {type: 'bar',data: {labels: qualityData.labels,datasets: [{label: '合格率',data: qualityData.pass,backgroundColor: '#4CAF50'},{label: '重量合格',data: qualityData.weight,backgroundColor: '#2196F3'},{label: '密封合格',data: qualityData.seal,backgroundColor: '#FF9800'},{label: '標簽合格',data: qualityData.label,backgroundColor: '#9C27B0'}]},options: {scales: {y: {min: 95,max: 100,title: {display: true,text: '合格率 (%)'}}}}});} else {// 使用簡單SVG繪制圖表renderSimpleQualityChart(container);}} catch (error) {console.error('圖表渲染失敗:', error);renderSimpleQualityChart(container);}
}/*** 渲染簡單SVG質量圖表(當Chart.js不可用時)*/
function renderSimpleQualityChart(container) {const svgNS = "http://www.w3.org/2000/svg";const width = container.clientWidth;const height = 250;const svg = document.createElementNS(svgNS, "svg");svg.setAttribute("width", width);svg.setAttribute("height", height);svg.style.backgroundColor = "var(--primary-dark)";const barWidth = 15;const groupWidth = barWidth * 4 + 20;const xStep = width / (qualityData.labels.length + 1);const yScale = (height - 40) / 5; // 5% scale (95-100%)// 繪制坐標軸const axis = document.createElementNS(svgNS, "path");axis.setAttribute("d", `M 30 10 V ${height - 30} H ${width - 10}`);axis.setAttribute("stroke", "var(--border-color)");axis.setAttribute("fill", "none");svg.appendChild(axis);// 繪制柱狀圖const colors = ['#4CAF50', '#2196F3', '#FF9800', '#9C27B0'];for (let i = 0; i < qualityData.labels.length; i++) {const x = 50 + i * xStep;// 繪制4種不同質量數據的柱狀圖for (let j = 0; j < 4; j++) {const dataKey = ['pass', 'weight', 'seal', 'label'][j];const value = qualityData[dataKey][i];const barHeight = (value - 95) * yScale;const bar = document.createElementNS(svgNS, "rect");bar.setAttribute("x", x + j * (barWidth + 5));bar.setAttribute("y", height - 30 - barHeight);bar.setAttribute("width", barWidth);bar.setAttribute("height", barHeight);bar.setAttribute("fill", colors[j]);svg.appendChild(bar);}// 繪制標簽const text = document.createElementNS(svgNS, "text");text.setAttribute("x", x + groupWidth / 2 - 15);text.setAttribute("y", height - 10);text.setAttribute("fill", "var(--text-secondary)");text.setAttribute("font-size", "12");text.textContent = qualityData.labels[i];svg.appendChild(text);}container.appendChild(svg);
}/*** 渲染效率圖表*/
function renderEfficiencyChart(container) {try {// 嘗試使用Chart.js繪制圖表if (typeof Chart !== 'undefined') {const canvas = document.createElement('canvas');container.appendChild(canvas);new Chart(canvas, {type: 'line',data: {labels: efficiencyData.labels,datasets: [{label: 'OEE',data: efficiencyData.oee,borderColor: '#2196F3',backgroundColor: 'rgba(33, 150, 243, 0.1)',tension: 0.3,fill: true},{label: '運行時間',data: efficiencyData.uptime,borderColor: '#4CAF50',backgroundColor: 'transparent',tension: 0.3},{label: '性能效率',data: efficiencyData.performance,borderColor: '#FF9800',backgroundColor: 'transparent',tension: 0.3}]},options: {scales: {y: {min: 85,max: 100,title: {display: true,text: '效率 (%)'}}}}});} else {// 使用簡單SVG繪制圖表renderSimpleEfficiencyChart(container);}} catch (error) {console.error('圖表渲染失敗:', error);renderSimpleEfficiencyChart(container);}
}/*** 渲染簡單SVG效率圖表(當Chart.js不可用時)*/
function renderSimpleEfficiencyChart(container) {const svgNS = "http://www.w3.org/2000/svg";const width = container.clientWidth;const height = 250;const svg = document.createElementNS(svgNS, "svg");svg.setAttribute("width", width);svg.setAttribute("height", height);svg.style.backgroundColor = "var(--primary-dark)";const xStep = width / (efficiencyData.labels.length + 1);const yScale = (height - 40) / 15; // 15% scale (85-100%)// 繪制坐標軸const axis = document.createElementNS(svgNS, "path");axis.setAttribute("d", `M 30 10 V ${height - 30} H ${width - 10}`);axis.setAttribute("stroke", "var(--border-color)");axis.setAttribute("fill", "none");svg.appendChild(axis);// 繪制OEE線const oeeLine = document.createElementNS(svgNS, "path");let path = `M ${50} ${height - 30 - (efficiencyData.oee[0] - 85) * yScale}`;for (let i = 1; i < efficiencyData.oee.length; i++) {path += ` L ${50 + i * xStep} ${height - 30 - (efficiencyData.oee[i] - 85) * yScale}`;}oeeLine.setAttribute("d", path);oeeLine.setAttribute("stroke", "#2196F3");oeeLine.setAttribute("stroke-width", "2");oeeLine.setAttribute("fill", "none");svg.appendChild(oeeLine);// 繪制運行時間線const uptimeLine = document.createElementNS(svgNS, "path");path = `M ${50} ${height - 30 - (efficiencyData.uptime[0] - 85) * yScale}`;for (let i = 1; i < efficiencyData.uptime.length; i++) {path += ` L ${50 + i * xStep} ${height - 30 - (efficiencyData.uptime[i] - 85) * yScale}`;}uptimeLine.setAttribute("d", path);uptimeLine.setAttribute("stroke", "#4CAF50");uptimeLine.setAttribute("stroke-width", "2");uptimeLine.setAttribute("fill", "none");svg.appendChild(uptimeLine);// 繪制性能效率線const perfLine = document.createElementNS(svgNS, "path");path = `M ${50} ${height - 30 - (efficiencyData.performance[0] - 85) * yScale}`;for (let i = 1; i < efficiencyData.performance.length; i++) {path += ` L ${50 + i * xStep} ${height - 30 - (efficiencyData.performance[i] - 85) * yScale}`;}perfLine.setAttribute("d", path);perfLine.setAttribute("stroke", "#FF9800");perfLine.setAttribute("stroke-width", "2");perfLine.setAttribute("fill", "none");svg.appendChild(perfLine);container.appendChild(svg);
}/*** 更新實時數據*/
function updateRealTimeData() {if (!systemRunning) return;// 模擬產量數據const hourlyRate = Math.floor(1200 * (productionSpeed / 100) * (Math.random() * 0.2 + 0.9));document.getElementById('current-output').textContent = hourlyRate.toLocaleString() + ' 件/小時';// 更新各工站狀態updateStationStatus();// 模擬產品質量數據const qualityRate = (99.5 + Math.random() * 0.5).toFixed(1);document.getElementById('quality-rate').textContent = qualityRate + '%';const qualityTrend = Math.random() > 0.7 ? -0.1 : 0.2;const trendEl = document.getElementById('quality-rate').nextElementSibling;trendEl.textContent = (qualityTrend >= 0 ? '↑' : '↓') + Math.abs(qualityTrend).toFixed(1) + '%';trendEl.className = 'card-trend ' + (qualityTrend >= 0 ? 'positive' : 'negative');// 模擬設備效率數據const efficiency = (94.0 + Math.random() * 1.0).toFixed(1);document.getElementById('equipment-efficiency').textContent = efficiency + '%';const efficiencyTrend = Math.random() > 0.3 ? 1.5 : -0.8;const effTrendEl = document.getElementById('equipment-efficiency').nextElementSibling;effTrendEl.textContent = (efficiencyTrend >= 0 ? '↑' : '↓') + Math.abs(efficiencyTrend).toFixed(1) + '%';effTrendEl.className = 'card-trend ' + (efficiencyTrend >= 0 ? 'positive' : 'negative');// 更新總產量if (Math.random() > 0.7) {const increment = Math.floor(Math.random() * 20 + 10);totalProduction += increment;document.getElementById('total-output').textContent = totalProduction.toLocaleString();}// 檢查是否需要產生告警checkAlarms();
}/*** 更新各工站狀態*/
function updateStationStatus() {if (!systemRunning) return;// 隨機選擇一個工站可能出現臨時警告if (Math.random() < 0.05) { // 5%概率出現警告const stationId = 'station-' + Math.ceil(Math.random() * 6);if (stationStatus[stationId] === 'active') {stationStatus[stationId] = 'warning';document.getElementById(stationId + '-status').className = 'station-status warning';// 添加警告信息const stationName = document.querySelector('#' + stationId + ' .station-label').textContent;addAlarm('warning', stationName + '狀態異常', '檢測到性能波動,請注意監控');// 2-5秒后自動恢復setTimeout(() => {if (systemRunning && stationStatus[stationId] === 'warning') {stationStatus[stationId] = 'active';document.getElementById(stationId + '-status').className = 'station-status active';}}, 2000 + Math.random() * 3000);}}
}/*** 更新流動動畫*/
function updateFlowAnimation() {if (!systemRunning) return;// 更新流動狀態動畫Object.keys(flowStatus).forEach(flow => {if (flowStatus[flow]) {const el = document.getElementById(flow);// 切換動畫狀態來重啟動畫el.classList.remove('active');setTimeout(() => {if (systemRunning && flowStatus[flow]) {el.classList.add('active');}}, 50);}});
}/*** 更新系統時間*/
function updateSystemTime() {const now = new Date();const timeStr = now.toLocaleString('zh-CN', {year: 'numeric',month: '2-digit',day: '2-digit',hour: '2-digit',minute: '2-digit',second: '2-digit',hour12: false});document.getElementById('system-time').textContent = timeStr;setTimeout(updateSystemTime, 1000);
}/*** 更新運行時間*/
function updateRunningTime() {if (!systemRunning) return;runningTime++;const hours = Math.floor(runningTime / 3600).toString().padStart(2, '0');const minutes = Math.floor((runningTime % 3600) / 60).toString().padStart(2, '0');const seconds = (runningTime % 60).toString().padStart(2, '0');document.getElementById('running-time').textContent = `${hours}:${minutes}:${seconds}`;
}/*** 更新系統指標數據*/
function updateSystemMetrics() {if (!systemRunning) return;// 更新CPU負載const cpuLoad = Math.floor(20 + Math.random() * 25);document.getElementById('cpu-load').textContent = cpuLoad + '%';// 更新內存使用const memoryUsage = (1.0 + Math.random() * 0.5).toFixed(1);document.getElementById('memory-usage').textContent = memoryUsage + 'GB/4GB';// 更新通信狀態if (Math.random() < 0.02) { // 2%概率通信狀態變化const commStatus = document.getElementById('comm-status');if (commStatus.textContent === '已連接') {commStatus.textContent = '重連中...';commStatus.className = 'metric-value disconnected';// 添加通信中斷告警addAlarm('warning', '通信狀態異常', '系統正在嘗試重新建立連接');// 1-3秒后恢復setTimeout(() => {if (systemRunning) {commStatus.textContent = '已連接';commStatus.className = 'metric-value connected';addAlarm('info', '通信已恢復', '系統通信連接已重新建立');}}, 1000 + Math.random() * 2000);}}
}/*** 更新產量數據(模擬數據變化)*/
function updateProductionData() {if (!systemRunning) return;// 移除第一個時間點數據productionData.labels.shift();productionData.actual.shift();productionData.target.shift();productionData.defects.shift();// 添加新的時間點數據const lastTime = productionData.labels[productionData.labels.length - 1];const [hour, minute] = lastTime.split(':').map(Number);let newHour = hour;let newMinute = minute + 30;if (newMinute >= 60) {newMinute = 0;newHour += 1;}const newTimeStr = `${newHour}:${newMinute === 0 ? '00' : '30'}`;productionData.labels.push(newTimeStr);// 添加新的產量數據const lastTarget = productionData.target[productionData.target.length - 1];const newTarget = lastTarget + (Math.random() > 0.7 ? 100 : 0);const baseProduction = newTarget * (productionSpeed / 100);const newProduction = Math.floor(baseProduction * (0.9 + Math.random() * 0.2));const newDefect = Math.max(0.1, Math.min(3.0, productionData.defects[productionData.defects.length - 1] + (Math.random() - 0.5))).toFixed(1);productionData.target.push(newTarget);productionData.actual.push(newProduction);productionData.defects.push(parseFloat(newDefect));
}/*** 檢查是否產生告警*/
function checkAlarms() {if (!systemRunning) return;// 隨機觸發告警(低概率)if (Math.random() < 0.04) { // 4%概率產生新告警const alarmTypes = [{ level: 'info', title: '批次變更通知', desc: '系統已自動更新至新批次:' + batchNumber },{ level: 'info', title: '日常維護提醒', desc: '設備1號傳送帶已運行720小時,建議進行日常維護檢查' },{ level: 'warning', title: '質量波動', desc: '最近30分鐘內包裝質量合格率出現輕微下降' },{ level: 'warning', title: '能耗異常', desc: '3號包裝工位能耗高于正常值15%,建議檢查' },{ level: 'warning', title: '備料不足', desc: '備料區原材料庫存低于預警閾值,請及時補充' },{ level: 'critical', title: '設備故障', desc: '標簽打印機5分鐘內多次出現卡紙現象,需要檢修' },{ level: 'critical', title: '溫度超限', desc: '熱封區溫度超出工藝范圍,當前值: 185°C (標準:160±10°C)' }];const alarm = alarmTypes[Math.floor(Math.random() * alarmTypes.length)];addAlarm(alarm.level, alarm.title, alarm.desc);}
}/*** 添加新告警*/
function addAlarm(level, title, description) {// 創建新告警const alarm = {id: Date.now(),level: level,title: title,description: description,time: new Date(),acknowledged: false};// 添加到告警列表alarms.unshift(alarm);// 限制告警列表最大長度if (alarms.length > 50) {alarms.pop();}// 更新告警計數updateAlarmCount();// 更新當前告警信息document.getElementById('current-alarm').textContent = title;// 更新告警圖標狀態const alarmIcon = document.getElementById('alarm-icon');alarmIcon.classList.add('active');// 如果是嚴重告警,彈出告警窗口if (level === 'critical') {showAlarmModal();}// 更新告警列表(如果彈窗已顯示)if (document.querySelector('.alarm-modal.show')) {updateAlarmList();}
}/*** 更新告警計數*/
function updateAlarmCount() {// 統計未確認告警數量const unacknowledgedCount = alarms.filter(alarm => !alarm.acknowledged).length;alarmCount = unacknowledgedCount;// 更新告警計數顯示document.getElementById('alarm-count').textContent = alarmCount;// 更新告警圖標狀態const alarmIcon = document.getElementById('alarm-icon');if (alarmCount > 0) {alarmIcon.classList.add('active');} else {alarmIcon.classList.remove('active');}
}/*** 顯示告警彈窗*/
function showAlarmModal() {// 更新告警列表updateAlarmList();// 顯示彈窗const alarmModal = document.getElementById('alarm-modal');alarmModal.classList.add('show');
}/*** 隱藏告警彈窗*/
function hideAlarmModal() {const alarmModal = document.getElementById('alarm-modal');alarmModal.classList.remove('show');
}/*** 更新告警列表*/
function updateAlarmList() {const alarmList = document.getElementById('alarm-list');alarmList.innerHTML = '';if (alarms.length === 0) {const emptyMessage = document.createElement('div');emptyMessage.className = 'empty-alarm';emptyMessage.textContent = '無告警信息';alarmList.appendChild(emptyMessage);return;}// 創建告警列表項alarms.forEach(alarm => {const alarmItem = document.createElement('div');alarmItem.className = `alarm-item ${alarm.level} ${alarm.acknowledged ? 'acknowledged' : ''}`;alarmItem.dataset.id = alarm.id;// 告警時間const time = document.createElement('div');time.className = 'alarm-time';time.textContent = formatTime(alarm.time);// 告警圖標const icon = document.createElement('div');icon.className = 'alarm-icon';icon.innerHTML = alarm.level === 'critical' ? '?' : (alarm.level === 'warning' ? '?' : '?');// 告警內容const content = document.createElement('div');content.className = 'alarm-content';const title = document.createElement('div');title.className = 'alarm-title';title.textContent = alarm.title;const description = document.createElement('div');description.className = 'alarm-description';description.textContent = alarm.description;content.appendChild(title);content.appendChild(description);// 確認按鈕const ackBtn = document.createElement('button');ackBtn.className = 'alarm-ack-btn';ackBtn.textContent = '確認';ackBtn.addEventListener('click', () => acknowledgeAlarm(alarm.id));if (!alarm.acknowledged) {alarmItem.appendChild(icon);alarmItem.appendChild(time);alarmItem.appendChild(content);alarmItem.appendChild(ackBtn);} else {alarmItem.appendChild(icon);alarmItem.appendChild(time);alarmItem.appendChild(content);const ackMark = document.createElement('div');ackMark.className = 'alarm-ack-mark';ackMark.textContent = '已確認';alarmItem.appendChild(ackMark);}alarmList.appendChild(alarmItem);});
}/*** 確認單個告警*/
function acknowledgeAlarm(id) {const alarm = alarms.find(a => a.id === id);if (alarm) {alarm.acknowledged = true;// 更新告警計數updateAlarmCount();// 更新告警列表updateAlarmList();}
}/*** 確認所有告警*/
function acknowledgeAllAlarms() {alarms.forEach(alarm => {alarm.acknowledged = true;});// 更新告警計數updateAlarmCount();// 更新告警列表updateAlarmList();// 如果沒有未確認的告警,自動關閉彈窗setTimeout(hideAlarmModal, 500);
}/*** 格式化時間顯示*/
function formatTime(date) {const hours = date.getHours().toString().padStart(2, '0');const minutes = date.getMinutes().toString().padStart(2, '0');const seconds = date.getSeconds().toString().padStart(2, '0');return `${hours}:${minutes}:${seconds}`;
}/*** 切換邏輯編程區域的Tab*/
function switchProgrammingTab(event) {// 獲取當前點擊的Tabconst clickedTab = event.currentTarget;// 獲取目標Tab的IDconst targetTabId = clickedTab.dataset.tab;// 移除所有Tab的active類document.querySelectorAll('.tab').forEach(tab => {tab.classList.remove('active');});// 隱藏所有Tab內容document.querySelectorAll('.tab-pane').forEach(pane => {pane.classList.remove('active');});// 添加當前Tab的active類clickedTab.classList.add('active');// 顯示對應的Tab內容document.getElementById(targetTabId + '-pane').classList.add('active');
}/*** 初始化邏輯編程界面*/
function initializeLogicProgramming() {// 設置Canvas的拖放區域setupDragAndDrop();// 初始化代碼編輯器initializeCodeEditor();// 初始化時序圖updateSequenceDiagram();
}/*** 設置拖放功能*/
function setupDragAndDrop() {const logicCanvas = document.getElementById('logic-canvas');// 添加提示文本const placeholderText = document.createElement('div');placeholderText.className = 'placeholder-text';placeholderText.textContent = '拖放控制元件到此區域創建邏輯流程';// 初始化工作流容器const workflowContainer = document.getElementById('workflow-container');if (!workflowContainer) {const container = document.createElement('div');container.id = 'workflow-container';container.className = 'workflow-container';logicCanvas.appendChild(container);}
}/*** 處理拖動開始事件*/
function handleDragStart(event) {// 存儲被拖動元素的類型event.dataTransfer.setData('type', event.target.dataset.type);event.dataTransfer.effectAllowed = 'copy';
}/*** 處理拖動經過事件*/
function handleDragOver(event) {event.preventDefault();event.dataTransfer.dropEffect = 'copy';// 添加拖動經過的視覺效果event.currentTarget.classList.add('drag-over');
}/*** 處理拖放事件*/
function handleDrop(event) {event.preventDefault();// 移除拖動經過的視覺效果event.currentTarget.classList.remove('drag-over');// 獲取被拖動元素的類型const type = event.dataTransfer.getData('type');// 獲取放置位置const canvasRect = event.currentTarget.getBoundingClientRect();const x = event.clientX - canvasRect.left;const y = event.clientY - canvasRect.top;// 創建新的邏輯元素createLogicElement(type, x, y);
}/*** 創建新的邏輯元素*/
function createLogicElement(type, x, y) {// 元素配置const elementConfig = {start: { shape: 'circle', label: '開始', color: '#4CAF50' },decision: { shape: 'diamond', label: '判斷', color: '#FF9800' },action: { shape: 'rect', label: '動作', color: '#2196F3' },delay: { shape: 'rect', label: '延時', color: '#9C27B0' },parallel: { shape: 'rect', label: '并行', color: '#00BCD4' },end: { shape: 'circle', label: '結束', color: '#F44336' },feeding: { shape: 'rect', label: '上料', color: '#2196F3' },weighing: { shape: 'rect', label: '稱重', color: '#4CAF50' },packaging: { shape: 'rect', label: '包裝', color: '#FF9800' },labeling: { shape: 'rect', label: '貼標', color: '#9C27B0' },inspection: { shape: 'rect', label: '檢測', color: '#00BCD4' },palletizing: { shape: 'rect', label: '碼垛', color: '#795548' }};const config = elementConfig[type] || { shape: 'rect', label: type, color: '#607D8B' };// 創建新元素const id = 'element-' + Date.now();const element = {id: id,type: type,label: config.label,shape: config.shape,color: config.color,x: x,y: y,connections: []};// 添加到元素列表workflowElements.push(element);// 渲染新元素renderLogicElement(element);
}/*** 渲染邏輯元素*/
function renderLogicElement(element) {const container = document.getElementById('workflow-container');// 創建元素DOMconst el = document.createElement('div');el.id = element.id;el.className = `workflow-element ${element.shape} ${element.type}`;el.style.left = element.x + 'px';el.style.top = element.y + 'px';el.style.backgroundColor = element.color;// 添加標簽const label = document.createElement('div');label.className = 'element-label';label.textContent = element.label;el.appendChild(label);// 添加連接點const connectionPoints = document.createElement('div');connectionPoints.className = 'connection-points';['top', 'right', 'bottom', 'left'].forEach(position => {const point = document.createElement('div');point.className = `connection-point ${position}`;point.dataset.position = position;point.dataset.elementId = element.id;// 添加連接點的事件處理point.addEventListener('mousedown', startConnection);connectionPoints.appendChild(point);});el.appendChild(connectionPoints);// 添加拖動功能el.draggable = true;el.addEventListener('dragstart', handleElementDragStart);el.addEventListener('dragend', handleElementDragEnd);// 添加雙擊編輯功能el.addEventListener('dblclick', editElement);// 添加到容器container.appendChild(el);
}/*** 開始創建連接*/
function startConnection(event) {// 這里實現連接創建邏輯console.log('開始創建連接點,從元素:', event.target.dataset.elementId);
}/*** 處理元素拖動開始*/
function handleElementDragStart(event) {// 保存當前位置信息const elementId = event.target.id;const element = workflowElements.find(el => el.id === elementId);if (element) {event.dataTransfer.setData('elementId', elementId);// 設置拖動時的視覺效果event.target.classList.add('dragging');}
}/*** 處理元素拖動結束*/
function handleElementDragEnd(event) {// 移除拖動時的視覺效果event.target.classList.remove('dragging');
}/*** 編輯元素*/
function editElement(event) {const elementId = event.currentTarget.id;const element = workflowElements.find(el => el.id === elementId);if (element) {// 這里可以實現彈出編輯對話框等功能console.log('編輯元素:', element);}
}/*** 創建新的邏輯流程*/
function createNewLogic() {// 清空工作區const container = document.getElementById('workflow-container');container.innerHTML = '';// 清空元素列表workflowElements = [];console.log('創建新的邏輯流程');
}/*** 保存當前邏輯流程*/
function saveLogic() {// 這里可以實現保存邏輯,如導出JSON等console.log('保存邏輯流程:', workflowElements);
}/*** 部署邏輯流程*/
function deployLogic() {// 這里可以實現部署邏輯console.log('部署邏輯流程');// 顯示部署成功消息addAlarm('info', '邏輯流程已部署', '新的控制邏輯已成功部署到系統');
}/*** 初始化代碼編輯器*/
function initializeCodeEditor() {// 這里可以添加代碼編輯器的高亮和自動完成等功能console.log('初始化代碼編輯器');
}/*** 檢查代碼語法*/
function checkCodeSyntax() {const code = document.getElementById('code-content').textContent;// 這里可以實現語法檢查邏輯console.log('檢查代碼語法');// 模擬語法檢查結果const hasErrors = Math.random() < 0.3;if (hasErrors) {addAlarm('warning', '語法檢查發現問題', '代碼第25行可能存在語法錯誤,請檢查');} else {addAlarm('info', '語法檢查通過', '代碼語法檢查未發現問題');}
}/*** 格式化代碼*/
function formatCode() {// 這里可以實現代碼格式化邏輯console.log('格式化代碼');// 添加操作提示addAlarm('info', '代碼已格式化', '代碼格式化操作已完成');
}/*** 更新時序圖*/
function updateSequenceDiagram() {const selectedStation = document.getElementById('station-selector').value;const diagram = document.getElementById('sequence-diagram');// 清空時序圖區域diagram.innerHTML = '';if (selectedStation === 'all') {renderFullSequenceDiagram(diagram);} else {renderStationSequenceDiagram(diagram, selectedStation);}
}/*** 渲染完整流程時序圖*/
function renderFullSequenceDiagram(container) {// 這里可以實現完整流程時序圖的渲染console.log('渲染完整流程時序圖');// 示例簡單時序圖const svgNS = "http://www.w3.org/2000/svg";const width = container.clientWidth;const height = 300;const svg = document.createElementNS(svgNS, "svg");svg.setAttribute("width", width);svg.setAttribute("height", height);svg.style.backgroundColor = "var(--primary-dark)";// 繪制簡單的時序線const stations = ['控制系統', '上料站', '稱重站', '包裝站', '貼標站', '檢測站', '碼垛站'];const xStep = width / (stations.length + 1);// 繪制垂直生命線stations.forEach((station, i) => {const x = (i + 1) * xStep;// 繪制站點標簽const text = document.createElementNS(svgNS, "text");text.setAttribute("x", x);text.setAttribute("y", 20);text.setAttribute("text-anchor", "middle");text.setAttribute("fill", "var(--text-primary)");text.setAttribute("font-size", "12");text.textContent = station;svg.appendChild(text);// 繪制垂直線const line = document.createElementNS(svgNS, "line");line.setAttribute("x1", x);line.setAttribute("y1", 30);line.setAttribute("x2", x);line.setAttribute("y2", height - 10);line.setAttribute("stroke", "var(--border-color)");line.setAttribute("stroke-dasharray", "5,5");svg.appendChild(line);});// 繪制消息箭頭const messages = [{ from: 0, to: 1, label: '啟動指令', y: 60 },{ from: 1, to: 2, label: '物料傳送', y: 90 },{ from: 2, to: 3, label: '重量數據', y: 120 },{ from: 3, to: 4, label: '包裝完成', y: 150 },{ from: 4, to: 5, label: '貼標完成', y: 180 },{ from: 5, to: 6, label: '檢測結果', y: 210 },{ from: 6, to: 0, label: '流程完成', y: 240 }];messages.forEach(msg => {const x1 = (msg.from + 1) * xStep;const x2 = (msg.to + 1) * xStep;const y = msg.y;// 繪制箭頭線const arrow = document.createElementNS(svgNS, "line");arrow.setAttribute("x1", x1);arrow.setAttribute("y1", y);arrow.setAttribute("x2", x2);arrow.setAttribute("y2", y);arrow.setAttribute("stroke", "#4CAF50");arrow.setAttribute("stroke-width", "1.5");arrow.setAttribute("marker-end", "url(#arrow)");svg.appendChild(arrow);// 繪制消息標簽const text = document.createElementNS(svgNS, "text");text.setAttribute("x", (x1 + x2) / 2);text.setAttribute("y", y - 5);text.setAttribute("text-anchor", "middle");text.setAttribute("fill", "var(--text-secondary)");text.setAttribute("font-size", "10");text.textContent = msg.label;svg.appendChild(text);});// 添加箭頭標記定義const defs = document.createElementNS(svgNS, "defs");const marker = document.createElementNS(svgNS, "marker");marker.setAttribute("id", "arrow");marker.setAttribute("viewBox", "0 0 10 10");marker.setAttribute("refX", "9");marker.setAttribute("refY", "5");marker.setAttribute("markerWidth", "6");marker.setAttribute("markerHeight", "6");marker.setAttribute("orient", "auto");const path = document.createElementNS(svgNS, "path");path.setAttribute("d", "M 0 0 L 10 5 L 0 10 z");path.setAttribute("fill", "#4CAF50");marker.appendChild(path);defs.appendChild(marker);svg.appendChild(defs);container.appendChild(svg);
}/*** 渲染單個工站的時序圖*/
function renderStationSequenceDiagram(container, stationId) {// 這里可以實現單個工站的時序圖渲染console.log('渲染工站時序圖:', stationId);// 添加提示信息const info = document.createElement('div');info.className = 'station-sequence-info';info.textContent = '顯示 ' + document.querySelector('#' + stationId + ' .station-label').textContent + ' 的詳細時序操作';container.appendChild(info);
}// 頁面加載完成后初始化系統
document.addEventListener('DOMContentLoaded', initializeSystem);