食品包装线控制系统
这是一个用于食品包装线控制系统的自定义组件,提供了食品包装生产线的可视化监控与控制界面。组件采用工业风格设计,包含生产流程控制、实时数据监控和逻辑编程三个主要功能区域。
功能特点
- 工业风格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 PackagingControl
VAR
StartButton 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) {
// 获取当前点击的Tab
const clickedTab = event.currentTarget;
// 获取目标Tab的ID
const 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');
// 创建元素DOM
const 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);