Files
pickling-mes/push_pull_pickling_l2(1).html
wangyu 193da0018f feat: 移除PDI和订单号字段,新增设备巡检模块
- 从物料跟踪页面移除订单号列和表单字段
- 从导航菜单移除PDI管理,添加设备巡检
- 新增InspectionLocation和InspectionRecord后端模型和API
- 新增设备巡检前端页面(左侧点位列表,右侧设备和历史记录)
2026-05-27 16:38:40 +08:00

2263 lines
106 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>推拉酸洗线 L2 过程控制系统</title>
<style>
:root {
--bg-primary: #0d1117;
--bg-secondary: #161b22;
--bg-card: #1c2230;
--bg-panel: #212936;
--bg-input: #0d1117;
--border: #30363d;
--border-active: #1f6feb;
--text-primary: #e6edf3;
--text-secondary: #8b949e;
--text-muted: #6e7681;
--accent-blue: #1f6feb;
--accent-cyan: #00b4d8;
--accent-green: #28a745;
--accent-yellow: #f0a500;
--accent-orange: #e05a00;
--accent-red: #da3633;
--accent-purple: #8957e5;
--accent-teal: #2da44e;
--sms-blue: #0078d4;
--sms-highlight: #00c8ff;
--status-run: #28a745;
--status-stop: #6e7681;
--status-warn: #f0a500;
--status-fault: #da3633;
--font-main: 'Segoe UI', 'Microsoft YaHei', sans-serif;
--font-mono: 'Consolas', 'Courier New', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--font-main);
background: var(--bg-primary);
color: var(--text-primary);
font-size: 13px;
overflow: hidden;
height: 100vh;
display: flex;
flex-direction: column;
}
/* ─── TOP BAR ─── */
.top-bar {
height: 48px;
background: linear-gradient(90deg, #0d1b2e 0%, #0a1628 100%);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
padding: 0 16px;
gap: 16px;
flex-shrink: 0;
position: relative;
}
.top-bar .logo {
font-size: 15px;
font-weight: 700;
color: var(--sms-highlight);
letter-spacing: 1px;
white-space: nowrap;
}
.top-bar .logo span { color: var(--accent-yellow); }
.top-bar .sys-title {
font-size: 12px;
color: var(--text-secondary);
border-left: 1px solid var(--border);
padding-left: 16px;
white-space: nowrap;
}
.top-bar .spacer { flex: 1; }
.top-bar .status-pills {
display: flex;
gap: 10px;
align-items: center;
}
.pill {
padding: 2px 10px;
border-radius: 10px;
font-size: 11px;
font-weight: 600;
letter-spacing: .5px;
}
.pill.run { background: #1a3a1f; color: var(--status-run); border: 1px solid var(--status-run); }
.pill.stop { background: #222; color: var(--text-muted); border: 1px solid var(--border); }
.pill.warn { background: #3a2a00; color: var(--status-warn); border: 1px solid var(--status-warn); }
.top-bar .clock {
font-family: var(--font-mono);
font-size: 13px;
color: var(--sms-highlight);
min-width: 90px;
text-align: right;
}
/* ─── NAV ─── */
.nav-bar {
height: 38px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
display: flex;
align-items: stretch;
overflow-x: auto;
flex-shrink: 0;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
.nav-bar::-webkit-scrollbar { height: 3px; }
.nav-bar::-webkit-scrollbar-thumb { background: var(--border); }
.nav-item {
display: flex;
align-items: center;
gap: 5px;
padding: 0 14px;
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
white-space: nowrap;
border-bottom: 2px solid transparent;
transition: all .15s;
user-select: none;
}
.nav-item:hover { color: var(--text-primary); background: rgba(255,255,255,.03); }
.nav-item.active { color: var(--sms-highlight); border-bottom-color: var(--sms-highlight); background: rgba(0,200,255,.05); }
.nav-item .nav-icon { font-size: 14px; }
.nav-divider { width: 1px; background: var(--border); margin: 6px 2px; flex-shrink: 0; }
/* ─── MAIN ─── */
.main {
flex: 1;
overflow: hidden;
position: relative;
}
.screen {
display: none;
width: 100%;
height: 100%;
overflow-y: auto;
padding: 14px;
gap: 14px;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
.screen::-webkit-scrollbar { width: 6px; }
.screen::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
.screen.active { display: flex; flex-direction: column; }
/* ─── CARDS ─── */
.card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
background: var(--bg-panel);
border-bottom: 1px solid var(--border);
font-size: 12px;
font-weight: 600;
color: var(--sms-highlight);
letter-spacing: .4px;
}
.card-header .ch-icon { font-size: 14px; }
.card-header .ch-badge {
margin-left: auto;
font-size: 10px;
padding: 1px 8px;
border-radius: 8px;
background: rgba(0,200,255,.1);
color: var(--sms-highlight);
border: 1px solid rgba(0,200,255,.3);
}
.card-body { padding: 12px 14px; }
/* ─── GRID HELPERS ─── */
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
.grid-4 { display: grid; grid-template-columns: repeat(4,1fr); gap: 14px; }
.grid-5 { display: grid; grid-template-columns: repeat(5,1fr); gap: 14px; }
/* ─── KV PARAMS ─── */
.kv-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; align-items: center; }
.kv-label { color: var(--text-secondary); font-size: 12px; white-space: nowrap; }
.kv-value {
font-family: var(--font-mono);
font-size: 12px;
color: var(--sms-highlight);
font-weight: 600;
}
.kv-input {
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 3px;
color: var(--text-primary);
font-family: var(--font-mono);
font-size: 12px;
padding: 3px 7px;
width: 100%;
outline: none;
transition: border-color .15s;
}
.kv-input:focus { border-color: var(--accent-blue); }
.kv-unit { color: var(--text-muted); font-size: 11px; }
/* ─── METRIC BOXES ─── */
.metric-box {
background: var(--bg-panel);
border: 1px solid var(--border);
border-radius: 5px;
padding: 10px 14px;
display: flex;
flex-direction: column;
gap: 4px;
}
.metric-box .mb-label { font-size: 11px; color: var(--text-secondary); }
.metric-box .mb-value {
font-size: 22px;
font-family: var(--font-mono);
font-weight: 700;
color: var(--sms-highlight);
line-height: 1;
}
.metric-box .mb-unit { font-size: 11px; color: var(--text-muted); }
.metric-box .mb-delta { font-size: 11px; margin-top: 2px; }
.delta-up { color: var(--accent-green); }
.delta-down { color: var(--accent-red); }
/* ─── TABLE ─── */
.data-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.data-table th {
background: var(--bg-panel);
color: var(--text-secondary);
font-weight: 600;
padding: 7px 10px;
text-align: left;
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
.data-table td {
padding: 6px 10px;
border-bottom: 1px solid rgba(48,54,61,.5);
color: var(--text-primary);
font-family: var(--font-mono);
}
.data-table tr:hover td { background: rgba(255,255,255,.02); }
.data-table .td-num { color: var(--sms-highlight); }
.data-table .td-ok { color: var(--accent-green); }
.data-table .td-warn { color: var(--accent-yellow); }
.data-table .td-err { color: var(--accent-red); }
.data-table .td-muted { color: var(--text-muted); }
/* ─── BTN ─── */
.btn {
padding: 5px 14px;
border-radius: 4px;
border: 1px solid;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all .15s;
user-select: none;
font-family: var(--font-main);
}
.btn-primary { background: var(--sms-blue); border-color: var(--sms-blue); color: #fff; }
.btn-primary:hover { background: #1086e0; }
.btn-success { background: #1a3a1f; border-color: var(--accent-green); color: var(--accent-green); }
.btn-success:hover { background: var(--accent-green); color: #000; }
.btn-warn { background: #3a2a00; border-color: var(--accent-yellow); color: var(--accent-yellow); }
.btn-danger { background: #3a0a0a; border-color: var(--accent-red); color: var(--accent-red); }
.btn-outline { background: transparent; border-color: var(--border); color: var(--text-secondary); }
.btn-outline:hover { border-color: var(--sms-highlight); color: var(--sms-highlight); }
/* ─── BADGE ─── */
.badge {
display: inline-block;
padding: 1px 8px;
border-radius: 8px;
font-size: 11px;
font-weight: 600;
}
.badge-green { background: #1a3a1f; color: var(--accent-green); border: 1px solid var(--accent-green); }
.badge-yellow { background: #3a2a00; color: var(--accent-yellow); border: 1px solid var(--accent-yellow); }
.badge-red { background: #3a0a0a; color: var(--accent-red); border: 1px solid var(--accent-red); }
.badge-blue { background: rgba(0,120,212,.15); color: var(--sms-highlight); border: 1px solid rgba(0,200,255,.3); }
.badge-gray { background: #222; color: var(--text-muted); border: 1px solid var(--border); }
/* ─── PROGRESS BAR ─── */
.prog-bar-wrap { background: #111; border-radius: 3px; height: 6px; overflow: hidden; }
.prog-bar-fill { height: 100%; border-radius: 3px; transition: width .4s; }
/* ─── SECTION TITLE ─── */
.sec-title {
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1.2px;
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 1px solid var(--border);
}
/* ─── INLINE SVG MINI-CHART ─── */
.mini-chart { width: 100%; height: 60px; }
.mini-chart-lg { width: 100%; height: 120px; }
/* ─── WELD TRACKER SPECIFIC ─── */
.strip-track {
width: 100%;
height: 50px;
background: #0a0f18;
border: 1px solid var(--border);
border-radius: 4px;
position: relative;
overflow: hidden;
margin: 10px 0;
}
.strip-body {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 14px;
transform: translateY(-50%);
background: linear-gradient(90deg, #1a2a3a 0%, #1e3250 50%, #1a2a3a 100%);
border-top: 1px solid #2a4a6a;
border-bottom: 1px solid #2a4a6a;
}
.weld-mark {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 4px;
height: 26px;
background: var(--accent-yellow);
border-radius: 2px;
cursor: pointer;
}
.weld-mark.active-weld { background: var(--accent-red); box-shadow: 0 0 6px var(--accent-red); }
.process-zone {
position: absolute;
top: 6px;
bottom: 6px;
opacity: .25;
border-radius: 2px;
}
/* ─── TENSION DIAGRAM ─── */
.tension-diagram {
display: flex;
align-items: center;
gap: 0;
height: 80px;
padding: 0 10px;
overflow-x: auto;
scrollbar-width: none;
}
.td-roller {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
min-width: 52px;
}
.td-roller .r-body {
width: 28px; height: 28px; border-radius: 50%;
background: linear-gradient(135deg, #2a3a4a, #1a2a3a);
border: 2px solid #3a5a7a;
display: flex; align-items: center; justify-content: center;
font-size: 8px; color: var(--text-muted);
}
.td-roller .r-name { font-size: 9px; color: var(--text-muted); }
.td-roller .r-val { font-size: 10px; font-family: var(--font-mono); color: var(--sms-highlight); }
.td-line {
flex: 1;
height: 3px;
background: linear-gradient(90deg, #2a5a8a, #1a3a5a);
min-width: 20px;
border-radius: 2px;
}
.td-line.high { background: linear-gradient(90deg, var(--accent-yellow), var(--accent-orange)); }
.td-line.normal { background: linear-gradient(90deg, var(--accent-green), #1a8a3a); }
/* ─── GAUGE ─── */
.gauge-wrap { position: relative; width: 80px; height: 50px; margin: 0 auto; }
.gauge-wrap svg { width: 100%; height: 100%; }
/* ─── TABS ─── */
.tab-bar {
display: flex;
gap: 2px;
border-bottom: 1px solid var(--border);
padding: 0 4px;
background: var(--bg-secondary);
}
.tab-item {
padding: 6px 14px;
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all .15s;
user-select: none;
}
.tab-item:hover { color: var(--text-primary); }
.tab-item.active { color: var(--sms-highlight); border-bottom-color: var(--sms-highlight); }
.tab-content { display: none; padding: 12px 14px; }
.tab-content.active { display: block; }
/* ─── FOOTER ─── */
.footer {
height: 26px;
background: var(--bg-secondary);
border-top: 1px solid var(--border);
display: flex;
align-items: center;
padding: 0 14px;
gap: 16px;
font-size: 11px;
color: var(--text-muted);
flex-shrink: 0;
}
.footer .fp { display: flex; align-items: center; gap: 5px; }
.footer .fp .dot { width: 6px; height: 6px; border-radius: 50%; }
.footer .fp .dot.g { background: var(--accent-green); }
.footer .fp .dot.y { background: var(--accent-yellow); }
.footer .fp .dot.r { background: var(--accent-red); }
/* Responsive mini adjustments */
@media (max-width: 1200px) {
.grid-4 { grid-template-columns: repeat(2,1fr); }
.grid-5 { grid-template-columns: repeat(3,1fr); }
}
/* Scrollable wrapper for tables */
.table-scroll { overflow-x: auto; }
.table-scroll::-webkit-scrollbar { height: 4px; }
.table-scroll::-webkit-scrollbar-thumb { background: var(--border); }
/* Trend canvas */
canvas.trend-canvas { width: 100%; height: 160px; display: block; }
/* inline flex helpers */
.flex-row { display: flex; gap: 10px; align-items: center; }
.flex-col { display: flex; flex-direction: column; gap: 8px; }
.flex-between { display: flex; justify-content: space-between; align-items: center; }
.mt8 { margin-top: 8px; }
.mt12 { margin-top: 12px; }
.fw { width: 100%; }
/* star rating */
.stars { color: var(--accent-yellow); font-size: 13px; letter-spacing: 1px; }
/* Quality grade */
.q-grade {
display: inline-block;
width: 36px; height: 36px;
border-radius: 50%;
text-align: center;
line-height: 36px;
font-size: 16px;
font-weight: 800;
}
.q-a { background: rgba(40,167,69,.2); color: var(--accent-green); border: 2px solid var(--accent-green); }
.q-b { background: rgba(0,180,216,.15); color: var(--accent-cyan); border: 2px solid var(--accent-cyan); }
.q-c { background: rgba(240,165,0,.15); color: var(--accent-yellow); border: 2px solid var(--accent-yellow); }
.q-d { background: rgba(218,54,51,.15); color: var(--accent-red); border: 2px solid var(--accent-red); }
/* Sparkline placeholder */
.sparkline {
display: inline-block;
width: 60px;
height: 20px;
vertical-align: middle;
}
/* Section row */
.section-row { display: flex; gap: 14px; }
.section-row > .card { flex: 1; min-width: 0; }
/* scroll hint */
.scroll-fade { position: relative; }
.scroll-fade::after {
content: '';
position: absolute;
right: 0; top: 0; bottom: 0;
width: 30px;
background: linear-gradient(to right, transparent, var(--bg-card));
pointer-events: none;
}
</style>
</head>
<body>
<!-- ─── TOP BAR ─── -->
<div class="top-bar">
<div class="logo">SMS <span>X-Pact</span></div>
<div class="sys-title">推拉酸洗线 L2 过程控制系统 &nbsp;|&nbsp; PUSH-PULL PICKLING LINE</div>
<div class="spacer"></div>
<div class="status-pills">
<span class="pill run">● 机组运行</span>
<span class="pill run">● L2在线</span>
<span class="pill warn">⚠ L3待机</span>
</div>
<div class="clock" id="clock">--:--:--</div>
</div>
<!-- ─── NAV BAR ─── -->
<div class="nav-bar" id="navBar">
<div class="nav-item active" data-screen="s-weld" onclick="showScreen(this)"><span class="nav-icon">🎯</span>带头带尾跟踪</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-tension" onclick="showScreen(this)"><span class="nav-icon"></span>张力设定</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-process" onclick="showScreen(this)"><span class="nav-icon">🧪</span>工艺段模型</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-leveler" onclick="showScreen(this)"><span class="nav-icon">📐</span>五辊矫直机</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-trend" onclick="showScreen(this)"><span class="nav-icon">📈</span>趋势报表</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-pdi" onclick="showScreen(this)"><span class="nav-icon">📋</span>PDI管理</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-l3plan" onclick="showScreen(this)"><span class="nav-icon">📅</span>L3排产计划</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-capacity" onclick="showScreen(this)"><span class="nav-icon">🏭</span>产能分析</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-kpi" onclick="showScreen(this)"><span class="nav-icon">🎯</span>绩效KPI</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-quality" onclick="showScreen(this)"><span class="nav-icon"></span>质量报表</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-qgrade" onclick="showScreen(this)"><span class="nav-icon"></span>质量评级</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-energy" onclick="showScreen(this)"><span class="nav-icon"></span>能源消耗</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-material" onclick="showScreen(this)"><span class="nav-icon">🧴</span>原料消耗</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-spare" onclick="showScreen(this)"><span class="nav-icon">🔧</span>辅料易损件</div>
<div class="nav-divider"></div>
<div class="nav-item" data-screen="s-schedule" onclick="showScreen(this)"><span class="nav-icon">🗓</span>设备排产分析</div>
</div>
<!-- ─── MAIN AREA ─── -->
<div class="main">
<!-- ══════════ S1: 带头带尾跟踪 ══════════ -->
<div id="s-weld" class="screen active">
<!-- 状态栏 -->
<div class="grid-5" style="gap:8px;">
<div class="metric-box">
<div class="mb-label">穿带状态</div>
<div class="mb-value" id="ht-state-val" style="font-size:16px;color:var(--accent-green);">正常轧制</div>
<div class="mb-unit" id="ht-state-sub">机组速度模式</div>
</div>
<div class="metric-box">
<div class="mb-label">带头位置</div>
<div class="mb-value" id="ht-head-pos"></div>
<div class="mb-unit">m从入口夹送辊</div>
</div>
<div class="metric-box">
<div class="mb-label">带尾位置</div>
<div class="mb-value" id="ht-tail-pos"></div>
<div class="mb-unit">m从入口夹送辊</div>
</div>
<div class="metric-box">
<div class="mb-label">机组线速度</div>
<div class="mb-value" id="ht-linespd">85.3</div>
<div class="mb-unit">m/min</div>
</div>
<div class="metric-box">
<div class="mb-label">带钢总长</div>
<div class="mb-value" id="ht-strip-len"></div>
<div class="mb-unit">m</div>
</div>
</div>
<!-- 主跟踪图 -->
<div class="card">
<div class="card-header">
<span class="ch-icon">🎯</span>带头 / 带尾 实时位置跟踪
<span class="ch-badge" id="ht-badge">运行中</span>
<div style="margin-left:auto;display:flex;gap:8px;">
<button class="btn btn-outline" style="font-size:11px;padding:3px 10px;" onclick="htSimToggle(this)">⏸ 仿真暂停</button>
<button class="btn btn-outline" style="font-size:11px;padding:3px 10px;" onclick="htReset()">↺ 重置</button>
</div>
</div>
<div class="card-body">
<!-- 工艺线图 -->
<div style="position:relative;margin-bottom:6px;">
<canvas id="htCanvas" style="width:100%;height:90px;display:block;background:#0a0f18;border-radius:4px;"></canvas>
</div>
<!-- 图例 -->
<div class="flex-row" style="font-size:11px;gap:18px;flex-wrap:wrap;margin-top:4px;">
<span style="color:#3a7abf;">■ 入口夹送辊</span>
<span style="color:var(--accent-cyan);">■ 机械清洗</span>
<span style="color:var(--accent-yellow);">■ 酸洗槽(1/2/3)</span>
<span style="color:#2ecc71;">■ 漂洗/钝化</span>
<span style="color:#a0522d;">■ 矫直机</span>
<span style="color:#4a6a9a;">■ 出口夹送辊</span>
<span style="color:var(--accent-green);">▶ 带头</span>
<span style="color:var(--accent-red);">◀ 带尾</span>
</div>
</div>
</div>
<!-- 工艺段通过状态 + 速度设定 -->
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">📍 各工艺段通过状态</div>
<div class="card-body" style="padding:0;">
<table class="data-table" id="htZoneTable">
<thead>
<tr>
<th>工艺段</th>
<th>段长(m)</th>
<th>段起点(m)</th>
<th>带头状态</th>
<th>带尾状态</th>
<th>带头到达时间</th>
<th>带尾通过时间</th>
<th>限速(m/min)</th>
</tr>
</thead>
<tbody id="htZoneTbody">
<!-- 动态生成 -->
</tbody>
</table>
</div>
</div>
<div class="card" style="flex:1.1;">
<div class="card-header">⚡ 带头带尾速度设定模型</div>
<div class="card-body">
<div class="sec-title">穿带参数设定</div>
<div class="kv-grid" style="gap:7px 14px;grid-template-columns:auto 1fr auto;">
<span class="kv-label">穿带速度 V_thread</span>
<input class="kv-input" id="ht-vthread" value="20" oninput="htCalcSpeed()">
<span class="kv-unit">m/min</span>
<span class="kv-label">加速度 a</span>
<input class="kv-input" id="ht-accel" value="2.0" oninput="htCalcSpeed()">
<span class="kv-unit">m/min/s</span>
<span class="kv-label">正常线速 V_line</span>
<input class="kv-input" id="ht-vline" value="85" oninput="htCalcSpeed()">
<span class="kv-unit">m/min</span>
<span class="kv-label">带头加速完成位</span>
<input class="kv-input" id="ht-vup-pos" value="25.0" oninput="htCalcSpeed()">
<span class="kv-unit">m</span>
<span class="kv-label">带尾减速触发位</span>
<input class="kv-input" id="ht-vdown-pos" value="8.0" oninput="htCalcSpeed()">
<span class="kv-unit">m (尾到夹持辊)</span>
<span class="kv-label">带尾最低速</span>
<input class="kv-input" id="ht-vtail" value="15" oninput="htCalcSpeed()">
<span class="kv-unit">m/min</span>
</div>
<div class="mt12 sec-title">计算结果</div>
<div class="kv-grid" style="gap:6px 14px;">
<span class="kv-label">穿带加速距</span><span class="kv-value" id="ht-res-accdist"></span>
<span class="kv-label">穿带时间估算</span><span class="kv-value" id="ht-res-tthread"></span>
<span class="kv-label">带尾减速距</span><span class="kv-value" id="ht-res-decdist"></span>
<span class="kv-label">全程通过时间</span><span class="kv-value" id="ht-res-total"></span>
</div>
<div class="mt12">
<button class="btn btn-primary fw" onclick="htCalcSpeed()">重新计算</button>
</div>
</div>
</div>
</div>
<!-- 速度曲线 -->
<div class="card">
<div class="card-header">📈 带头/带尾速度曲线(位置 vs 速度)</div>
<div class="card-body">
<canvas id="htSpeedCurve" style="width:100%;height:130px;display:block;"></canvas>
</div>
</div>
<!-- 历史记录 -->
<div class="card">
<div class="card-header">📋 穿带历史记录</div>
<div class="card-body" style="padding:0;">
<table class="data-table">
<thead><tr><th>序号</th><th>钢卷号</th><th>厚(mm)</th><th>穿带开始</th><th>带头出口时间</th><th>带尾离夹时间</th><th>穿带速度</th><th>穿带时长(s)</th><th>状态</th></tr></thead>
<tbody>
<tr><td>1</td><td class="td-num">C2024115</td><td>2.50</td><td>10:05:12</td><td>10:07:48</td><td>10:08:02</td><td class="td-num">20 m/min</td><td class="td-num">170</td><td><span class="badge badge-green">成功</span></td></tr>
<tr><td>2</td><td class="td-num">C2024114</td><td>2.00</td><td>09:42:33</td><td>09:45:01</td><td>09:45:18</td><td class="td-num">22 m/min</td><td class="td-num">165</td><td><span class="badge badge-green">成功</span></td></tr>
<tr><td>3</td><td class="td-num">C2024113</td><td>3.00</td><td>09:18:05</td><td>09:21:02</td><td>09:21:25</td><td class="td-num">18 m/min</td><td class="td-num">200</td><td><span class="badge badge-yellow">慢速</span></td></tr>
<tr><td>4</td><td class="td-num">C2024112</td><td>2.50</td><td>08:55:14</td><td>08:58:06</td><td>08:58:20</td><td class="td-num">20 m/min</td><td class="td-num">186</td><td><span class="badge badge-green">成功</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ══════════ S2: 张力自动设定 ══════════ -->
<div id="s-tension" class="screen">
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">⚡ 张力自动设定模型 <span class="ch-badge">计算中</span></div>
<div class="card-body">
<div class="sec-title">带钢规格输入</div>
<div class="grid-4" style="margin-bottom:12px;">
<div class="flex-col">
<div class="kv-label">厚度 h</div>
<input class="kv-input" id="t-thick" value="2.50" oninput="calcTension()"> <span class="kv-unit">mm</span>
</div>
<div class="flex-col">
<div class="kv-label">宽度 w</div>
<input class="kv-input" id="t-width" value="1000" oninput="calcTension()"> <span class="kv-unit">mm</span>
</div>
<div class="flex-col">
<div class="kv-label">屈服强度 σ₀</div>
<input class="kv-input" id="t-yield" value="380" oninput="calcTension()"> <span class="kv-unit">MPa</span>
</div>
<div class="flex-col">
<div class="kv-label">张力系数 k</div>
<input class="kv-input" id="t-coef" value="0.25" oninput="calcTension()"> <span class="kv-unit"></span>
</div>
</div>
<div class="sec-title">张力图示</div>
<div class="tension-diagram" id="tensionDiagram">
<div class="td-roller"><div class="r-body">开卷</div><div class="r-name">开卷</div><div class="r-val" id="tr-uw"></div></div>
<div class="td-line normal" id="tl1"></div>
<div class="td-roller"><div class="r-body">S1</div><div class="r-name">S1辊</div><div class="r-val" id="tr-s1"></div></div>
<div class="td-line normal" id="tl2"></div>
<div class="td-roller"><div class="r-body"></div><div class="r-name">入口辊</div><div class="r-val" id="tr-in"></div></div>
<div class="td-line normal" id="tl3"></div>
<div class="td-roller"><div class="r-body">酸1</div><div class="r-name">酸槽1</div><div class="r-val" id="tr-a1"></div></div>
<div class="td-line normal" id="tl4"></div>
<div class="td-roller"><div class="r-body">酸2</div><div class="r-name">酸槽2</div><div class="r-val" id="tr-a2"></div></div>
<div class="td-line normal" id="tl5"></div>
<div class="td-roller"><div class="r-body">酸3</div><div class="r-name">酸槽3</div><div class="r-val" id="tr-a3"></div></div>
<div class="td-line normal" id="tl6"></div>
<div class="td-roller"><div class="r-body"></div><div class="r-name">漂洗</div><div class="r-val" id="tr-rn"></div></div>
<div class="td-line normal" id="tl7"></div>
<div class="td-roller"><div class="r-body"></div><div class="r-name">矫直机</div><div class="r-val" id="tr-lv"></div></div>
<div class="td-line normal" id="tl8"></div>
<div class="td-roller"><div class="r-body">S2</div><div class="r-name">S2辊</div><div class="r-val" id="tr-s2"></div></div>
<div class="td-line normal" id="tl9"></div>
<div class="td-roller"><div class="r-body">卷取</div><div class="r-name">卷取</div><div class="r-val" id="tr-rw"></div></div>
</div>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">⚡ 计算结果</div>
<div class="card-body">
<div class="kv-grid" style="gap:8px 16px;">
<span class="kv-label">截面积 A</span><span class="kv-value" id="res-area"></span>
<span class="kv-label">最大张力 T_max</span><span class="kv-value" id="res-tmax"></span>
<span class="kv-label">酸槽段张力</span><span class="kv-value" id="res-tacid"></span>
<span class="kv-label">出口段张力</span><span class="kv-value" id="res-tout"></span>
<span class="kv-label">比张力</span><span class="kv-value" id="res-spec"></span>
<span class="kv-label">单位张力</span><span class="kv-value" id="res-unit"></span>
</div>
<div class="mt12">
<button class="btn btn-primary fw" onclick="calcTension()">重新计算</button>
</div>
<div class="mt8">
<div class="sec-title">张力限制</div>
<div class="kv-grid" style="gap:6px 14px;">
<span class="kv-label">焊缝减速张力</span><span class="kv-value" id="res-weld"></span>
<span class="kv-label">加速段张力</span><span class="kv-value" id="res-acc"></span>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">⚡ 张力设定表(按钢卷批次)</div>
<div class="card-body" style="padding:0;">
<table class="data-table">
<thead><tr><th>钢卷号</th><th>厚(mm)</th><th>宽(mm)</th><th>σ₀(MPa)</th><th>T_入(kN)</th><th>T_酸(kN)</th><th>T_出(kN)</th><th>焊缝速限</th><th>状态</th></tr></thead>
<tbody>
<tr><td class="td-num">C2024115</td><td>2.50</td><td>1000</td><td>380</td><td class="td-num">23.8</td><td class="td-num">19.0</td><td class="td-num">21.4</td><td class="td-num">40 m/min</td><td><span class="badge badge-green">激活</span></td></tr>
<tr><td class="td-num">C2024116</td><td>2.00</td><td>1050</td><td>350</td><td class="td-num">18.4</td><td class="td-num">14.7</td><td class="td-num">16.5</td><td class="td-num">45 m/min</td><td><span class="badge badge-gray">等待</span></td></tr>
<tr><td class="td-num">C2024117</td><td>3.00</td><td>900</td><td>420</td><td class="td-num">28.4</td><td class="td-num">22.7</td><td class="td-num">25.5</td><td class="td-num">35 m/min</td><td><span class="badge badge-gray">等待</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ══════════ S3: 工艺段模型 ══════════ -->
<div id="s-process" class="screen">
<div class="grid-3">
<div class="card">
<div class="card-header">🧪 酸洗槽 #1</div>
<div class="card-body">
<div class="kv-grid" style="gap:7px 14px;">
<span class="kv-label">HCl 浓度</span><span class="kv-value" id="acid1-conc">178 g/L</span>
<span class="kv-label">酸液温度</span><span class="kv-value">72 °C</span>
<span class="kv-label">Fe²⁺ 含量</span><span class="kv-value">82 g/L</span>
<span class="kv-label">槽液液位</span><span class="kv-value">95 %</span>
<span class="kv-label">酸洗速度</span><span class="kv-value">85 m/min</span>
<span class="kv-label">停留时间</span><span class="kv-value" id="rt1">12.4 s</span>
</div>
<div class="mt8">
<div class="kv-label" style="margin-bottom:4px;">酸洗效果指数</div>
<div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:82%;background:var(--accent-cyan);"></div></div>
<div style="font-size:10px;color:var(--text-muted);margin-top:3px;">82% — 良好</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">🧪 酸洗槽 #2</div>
<div class="card-body">
<div class="kv-grid" style="gap:7px 14px;">
<span class="kv-label">HCl 浓度</span><span class="kv-value">165 g/L</span>
<span class="kv-label">酸液温度</span><span class="kv-value">74 °C</span>
<span class="kv-label">Fe²⁺ 含量</span><span class="kv-value">95 g/L</span>
<span class="kv-label">槽液液位</span><span class="kv-value">93 %</span>
<span class="kv-label">酸洗速度</span><span class="kv-value">85 m/min</span>
<span class="kv-label">停留时间</span><span class="kv-value">12.4 s</span>
</div>
<div class="mt8">
<div class="kv-label" style="margin-bottom:4px;">酸洗效果指数</div>
<div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:76%;background:var(--accent-yellow);"></div></div>
<div style="font-size:10px;color:var(--accent-yellow);margin-top:3px;">76% — 注意浓度</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">🧪 酸洗槽 #3 (出口段)</div>
<div class="card-body">
<div class="kv-grid" style="gap:7px 14px;">
<span class="kv-label">HCl 浓度</span><span class="kv-value">150 g/L</span>
<span class="kv-label">酸液温度</span><span class="kv-value">75 °C</span>
<span class="kv-label">Fe²⁺ 含量</span><span class="kv-value">110 g/L</span>
<span class="kv-label">槽液液位</span><span class="kv-value">91 %</span>
<span class="kv-label">酸洗速度</span><span class="kv-value">85 m/min</span>
<span class="kv-label">停留时间</span><span class="kv-value">12.4 s</span>
</div>
<div class="mt8">
<div class="kv-label" style="margin-bottom:4px;">酸洗效果指数</div>
<div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:68%;background:var(--accent-orange);"></div></div>
<div style="font-size:10px;color:var(--accent-orange);margin-top:3px;">68% — 需补酸</div>
</div>
</div>
</div>
</div>
<!-- Acid consumption model -->
<div class="card">
<div class="card-header">🧪 酸洗工艺计算模型</div>
<div class="card-body">
<div class="grid-4">
<div class="flex-col">
<div class="kv-label">带钢氧化皮重量 (g/m²)</div>
<input class="kv-input" id="scale-wt" value="8.5" oninput="calcProcess()">
</div>
<div class="flex-col">
<div class="kv-label">单槽有效长度 (m)</div>
<input class="kv-input" id="tank-len" value="18.0" oninput="calcProcess()">
</div>
<div class="flex-col">
<div class="kv-label">目标酸洗指数 (%)</div>
<input class="kv-input" id="target-pi" value="95">
</div>
<div class="flex-col">
<div class="kv-label">酸浓度目标 (g/L)</div>
<input class="kv-input" id="acid-target" value="180">
</div>
</div>
<div class="mt12 grid-4">
<div class="metric-box">
<div class="mb-label">理论停留时间</div>
<div class="mb-value" id="p-rt">12.7</div>
<div class="mb-unit">秒/槽</div>
</div>
<div class="metric-box">
<div class="mb-label">最高允许速度</div>
<div class="mb-value" id="p-maxspd">85</div>
<div class="mb-unit">m/min</div>
</div>
<div class="metric-box">
<div class="mb-label">补酸量</div>
<div class="mb-value" id="p-addacid">42</div>
<div class="mb-unit">L/t</div>
</div>
<div class="metric-box">
<div class="mb-label">废酸生成量</div>
<div class="mb-value" id="p-waste">18</div>
<div class="mb-unit">L/t</div>
</div>
</div>
</div>
</div>
<!-- Rinse section -->
<div class="section-row">
<div class="card">
<div class="card-header">🚿 漂洗段</div>
<div class="card-body">
<div class="kv-grid">
<span class="kv-label">漂洗段1 pH</span><span class="kv-value">2.8</span>
<span class="kv-label">漂洗段2 pH</span><span class="kv-value">4.1</span>
<span class="kv-label">漂洗段3 pH</span><span class="kv-value">6.2</span>
<span class="kv-label">漂洗水温度</span><span class="kv-value">55 °C</span>
<span class="kv-label">漂洗水流量</span><span class="kv-value">12.4 m³/h</span>
<span class="kv-label">出口残酸量</span><span class="kv-value">0.12 g/m²</span>
</div>
</div>
</div>
<div class="card">
<div class="card-header">🔧 机械清洗段</div>
<div class="card-body">
<div class="kv-grid">
<span class="kv-label">刷辊压力</span><span class="kv-value">18 kN</span>
<span class="kv-label">刷辊转速</span><span class="kv-value">420 rpm</span>
<span class="kv-label">清洗液浓度</span><span class="kv-value">3.2 %</span>
<span class="kv-label">清洗液温度</span><span class="kv-value">60 °C</span>
<span class="kv-label">清洗液流量</span><span class="kv-value">8.5 m³/h</span>
<span class="kv-label">清洗段运行</span><span class="kv-value td-ok">正常</span>
</div>
</div>
</div>
<div class="card">
<div class="card-header">🛡 钝化段</div>
<div class="card-body">
<div class="kv-grid">
<span class="kv-label">钝化液浓度</span><span class="kv-value">1.8 %</span>
<span class="kv-label">钝化液温度</span><span class="kv-value">40 °C</span>
<span class="kv-label">涂覆量</span><span class="kv-value">0.05 g/m²</span>
<span class="kv-label">干燥温度</span><span class="kv-value">120 °C</span>
<span class="kv-label">干燥时间</span><span class="kv-value">8 s</span>
<span class="kv-label">钝化段运行</span><span class="kv-value td-ok">正常</span>
</div>
</div>
</div>
</div>
</div>
<!-- ══════════ S4: 五辊矫直机 ══════════ -->
<div id="s-leveler" class="screen">
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">📐 五辊矫直机模型</div>
<div class="card-body">
<!-- 5-roll diagram -->
<div style="background:#0a0f18;border:1px solid var(--border);border-radius:4px;padding:16px 20px;margin-bottom:14px;">
<svg viewBox="0 0 620 160" style="width:100%;height:auto;">
<!-- strip path -->
<path d="M10 80 L100 80 C110 80 115 68 125 68 C135 68 140 92 150 92 C160 92 165 68 175 68 C185 68 190 92 200 92 C210 92 215 68 225 68 C235 68 240 92 250 92 L610 92" fill="none" stroke="#2a5a8a" stroke-width="3"/>
<!-- rollers: top -->
<circle cx="125" cy="50" r="22" fill="url(#rgrad)" stroke="#3a6a9a" stroke-width="1.5"/>
<text x="125" y="54" fill="#00c8ff" font-size="11" text-anchor="middle" font-weight="bold">R2</text>
<circle cx="175" cy="50" r="22" fill="url(#rgrad)" stroke="#3a6a9a" stroke-width="1.5"/>
<text x="175" y="54" fill="#00c8ff" font-size="11" text-anchor="middle" font-weight="bold">R4</text>
<!-- rollers: bottom -->
<circle cx="100" cy="110" r="22" fill="url(#rgrad2)" stroke="#5a4a2a" stroke-width="1.5"/>
<text x="100" y="114" fill="#f0a500" font-size="11" text-anchor="middle" font-weight="bold">R1</text>
<circle cx="150" cy="110" r="22" fill="url(#rgrad2)" stroke="#5a4a2a" stroke-width="1.5"/>
<text x="150" y="114" fill="#f0a500" font-size="11" text-anchor="middle" font-weight="bold">R3</text>
<circle cx="200" cy="110" r="22" fill="url(#rgrad2)" stroke="#5a4a2a" stroke-width="1.5"/>
<text x="200" y="114" fill="#f0a500" font-size="11" text-anchor="middle" font-weight="bold">R5</text>
<!-- entry/exit -->
<text x="30" y="77" fill="#8b949e" font-size="10">入口</text>
<text x="580" y="89" fill="#8b949e" font-size="10">出口</text>
<line x1="10" y1="80" x2="50" y2="80" stroke="#2a5a8a" stroke-width="3"/>
<line x1="260" y1="92" x2="610" y2="92" stroke="#2a5a8a" stroke-width="3"/>
<defs>
<radialGradient id="rgrad" cx="40%" cy="35%">
<stop offset="0%" stop-color="#3a5a7a"/>
<stop offset="100%" stop-color="#1a2a3a"/>
</radialGradient>
<radialGradient id="rgrad2" cx="40%" cy="35%">
<stop offset="0%" stop-color="#4a3a1a"/>
<stop offset="100%" stop-color="#2a1a0a"/>
</radialGradient>
</defs>
<!-- labels: indentation values -->
<text x="125" y="18" fill="#00c8ff" font-size="10" text-anchor="middle" id="svgR2">δ=1.2mm</text>
<text x="175" y="18" fill="#00c8ff" font-size="10" text-anchor="middle" id="svgR4">δ=0.8mm</text>
<text x="100" y="150" fill="#f0a500" font-size="10" text-anchor="middle">固定</text>
<text x="150" y="150" fill="#f0a500" font-size="10" text-anchor="middle">固定</text>
<text x="200" y="150" fill="#f0a500" font-size="10" text-anchor="middle">固定</text>
</svg>
</div>
<div class="grid-5" style="gap:8px;">
<div class="metric-box">
<div class="mb-label">入口延伸率</div>
<div class="mb-value" id="lv-ein">0.85</div>
<div class="mb-unit">%</div>
</div>
<div class="metric-box">
<div class="mb-label">总延伸率</div>
<div class="mb-value" id="lv-etot">1.20</div>
<div class="mb-unit">%</div>
</div>
<div class="metric-box">
<div class="mb-label">矫直力</div>
<div class="mb-value" id="lv-force">3.4</div>
<div class="mb-unit">MN</div>
</div>
<div class="metric-box">
<div class="mb-label">弯曲力矩</div>
<div class="mb-value" id="lv-moment">124</div>
<div class="mb-unit">kN·m</div>
</div>
<div class="metric-box">
<div class="mb-label">残余应力</div>
<div class="mb-value" id="lv-stress">42</div>
<div class="mb-unit">MPa</div>
</div>
</div>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">📐 矫直机参数输入</div>
<div class="card-body">
<div class="kv-grid" style="gap:8px 14px;">
<span class="kv-label">辊1(R1)压下量</span><input class="kv-input" value="1.2" id="lv-r1" oninput="calcLeveler()" style="width:70px;"> <span class="kv-unit">mm</span>
<span class="kv-label">辊2(R2)压下量</span><input class="kv-input" value="0.8" id="lv-r2" oninput="calcLeveler()" style="width:70px;"> <span class="kv-unit">mm</span>
<span class="kv-label">辊3(R3)压下量</span><input class="kv-input" value="0.6" id="lv-r3" oninput="calcLeveler()" style="width:70px;"> <span class="kv-unit">mm</span>
<span class="kv-label">辊4(R4)压下量</span><input class="kv-input" value="0.4" id="lv-r4" oninput="calcLeveler()" style="width:70px;"> <span class="kv-unit">mm</span>
<span class="kv-label">辊5(R5)压下量</span><input class="kv-input" value="0.2" id="lv-r5" oninput="calcLeveler()" style="width:70px;"> <span class="kv-unit">mm</span>
<span class="kv-label">带钢厚度</span><input class="kv-input" value="2.5" id="lv-h" oninput="calcLeveler()" style="width:70px;"> <span class="kv-unit">mm</span>
<span class="kv-label">辊径 D</span><input class="kv-input" value="110" id="lv-d" oninput="calcLeveler()" style="width:70px;"> <span class="kv-unit">mm</span>
<span class="kv-label">屈服强度</span><input class="kv-input" value="380" id="lv-sy" oninput="calcLeveler()" style="width:70px;"> <span class="kv-unit">MPa</span>
</div>
<div class="mt12 flex-row">
<button class="btn btn-primary" onclick="calcLeveler()">计算</button>
<button class="btn btn-outline" onclick="">默认值</button>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">📐 矫直效果历史趋势</div>
<div class="card-body">
<canvas id="levelerTrend" class="trend-canvas"></canvas>
</div>
</div>
</div>
<!-- ══════════ S5: 趋势报表 ══════════ -->
<div id="s-trend" class="screen">
<div class="card">
<div class="card-header">📈 实时趋势监控</div>
<div class="card-body">
<div class="flex-between" style="margin-bottom:10px;">
<div class="tab-bar" style="border:none;padding:0;background:none;">
<div class="tab-item active" onclick="setTrendTab(this,'trend-speed')">速度/张力</div>
<div class="tab-item" onclick="setTrendTab(this,'trend-acid')">酸洗参数</div>
<div class="tab-item" onclick="setTrendTab(this,'trend-quality')">质量参数</div>
</div>
<div class="flex-row">
<button class="btn btn-outline" onclick="toggleTrendPlay(this)">⏸ 暂停</button>
<select class="kv-input" style="width:90px;">
<option>1 min</option><option>5 min</option><option>15 min</option><option>1 hr</option>
</select>
</div>
</div>
<canvas id="trendCanvas" class="trend-canvas" style="height:200px;"></canvas>
</div>
</div>
<div class="grid-3">
<div class="card">
<div class="card-header">📈 速度趋势</div>
<div class="card-body" style="padding:8px 12px;">
<canvas id="tc-speed" class="trend-canvas" style="height:100px;"></canvas>
<div class="flex-between mt8">
<span class="kv-label">当前: <span class="kv-value">85.3 m/min</span></span>
<span class="kv-label">均值: <span class="kv-value">82.1 m/min</span></span>
</div>
</div>
</div>
<div class="card">
<div class="card-header">📈 张力趋势</div>
<div class="card-body" style="padding:8px 12px;">
<canvas id="tc-tension" class="trend-canvas" style="height:100px;"></canvas>
<div class="flex-between mt8">
<span class="kv-label">当前: <span class="kv-value">19.2 kN</span></span>
<span class="kv-label">均值: <span class="kv-value">18.8 kN</span></span>
</div>
</div>
</div>
<div class="card">
<div class="card-header">📈 酸洗浓度趋势</div>
<div class="card-body" style="padding:8px 12px;">
<canvas id="tc-acid" class="trend-canvas" style="height:100px;"></canvas>
<div class="flex-between mt8">
<span class="kv-label">当前: <span class="kv-value">178 g/L</span></span>
<span class="kv-label">均值: <span class="kv-value">174 g/L</span></span>
</div>
</div>
</div>
</div>
</div>
<!-- ══════════ S6: PDI ══════════ -->
<div id="s-pdi" class="screen">
<div class="section-row">
<div class="card" style="flex:1.5;">
<div class="card-header">📋 PDI 数据管理 <span class="ch-badge">当前卷批</span></div>
<div class="card-body" style="padding:0;">
<table class="data-table">
<thead><tr><th>钢卷号</th><th>钢种</th><th>厚(mm)</th><th>宽(mm)</th><th>重(t)</th><th>σ₀(MPa)</th><th>Rm(MPa)</th><th>A(%)</th><th>工艺路径</th><th>状态</th></tr></thead>
<tbody>
<tr><td class="td-num">C2024115</td><td>SPCC</td><td>2.50</td><td>1000</td><td>18.2</td><td>380</td><td>520</td><td>28</td><td>P1+P2+P3</td><td><span class="badge badge-green">在线</span></td></tr>
<tr><td class="td-num">C2024116</td><td>SPHC</td><td>2.00</td><td>1050</td><td>15.5</td><td>350</td><td>480</td><td>32</td><td>P1+P2+P3</td><td><span class="badge badge-gray">等待</span></td></tr>
<tr><td class="td-num">C2024117</td><td>SS400</td><td>3.00</td><td>900</td><td>21.0</td><td>420</td><td>560</td><td>22</td><td>P1+P2+P3+LV</td><td><span class="badge badge-gray">等待</span></td></tr>
<tr><td class="td-num">C2024118</td><td>SPCC</td><td>1.80</td><td>1200</td><td>14.8</td><td>320</td><td>440</td><td>36</td><td>P1+P2</td><td><span class="badge badge-gray">等待</span></td></tr>
</tbody>
</table>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">📋 PDI 编辑器</div>
<div class="card-body">
<div class="kv-grid" style="gap:8px 14px;">
<span class="kv-label">钢卷号</span><input class="kv-input" value="C2024119">
<span class="kv-label">钢种</span>
<select class="kv-input">
<option>SPCC</option><option>SPHC</option><option>SS400</option><option>Q235B</option>
</select>
<span class="kv-label">厚度 (mm)</span><input class="kv-input" value="2.00">
<span class="kv-label">宽度 (mm)</span><input class="kv-input" value="1000">
<span class="kv-label">重量 (t)</span><input class="kv-input" value="16.0">
<span class="kv-label">工艺路径</span>
<select class="kv-input">
<option>P1+P2+P3</option><option>P1+P2+P3+LV</option><option>P1+P2</option>
</select>
</div>
<div class="mt12 flex-row">
<button class="btn btn-primary">保存PDI</button>
<button class="btn btn-outline">清除</button>
<button class="btn btn-success">发送L2</button>
</div>
</div>
</div>
</div>
</div>
<!-- ══════════ S7: L3排产计划 ══════════ -->
<div id="s-l3plan" class="screen">
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">📅 L3 排产计划接收 <span class="ch-badge">今日计划</span></div>
<div class="card-body" style="padding:0;">
<table class="data-table">
<thead><tr><th>序号</th><th>钢卷号</th><th>客户</th><th>钢种</th><th>厚×宽(mm)</th><th>重(t)</th><th>计划时间</th><th>优先级</th><th>L3状态</th><th>L2确认</th></tr></thead>
<tbody>
<tr><td>1</td><td class="td-num">C2024115</td><td>客户A</td><td>SPCC</td><td>2.50×1000</td><td>18.2</td><td>08:00</td><td><span class="badge badge-red"></span></td><td><span class="badge badge-green">已发送</span></td><td><span class="badge badge-green">已确认</span></td></tr>
<tr><td>2</td><td class="td-num">C2024116</td><td>客户B</td><td>SPHC</td><td>2.00×1050</td><td>15.5</td><td>10:30</td><td><span class="badge badge-yellow"></span></td><td><span class="badge badge-green">已发送</span></td><td><span class="badge badge-yellow">待确认</span></td></tr>
<tr><td>3</td><td class="td-num">C2024117</td><td>客户A</td><td>SS400</td><td>3.00×900</td><td>21.0</td><td>13:00</td><td><span class="badge badge-yellow"></span></td><td><span class="badge badge-green">已发送</span></td><td><span class="badge badge-gray">未确认</span></td></tr>
<tr><td>4</td><td class="td-num">C2024118</td><td>客户C</td><td>SPCC</td><td>1.80×1200</td><td>14.8</td><td>15:30</td><td><span class="badge badge-gray"></span></td><td><span class="badge badge-yellow">待发送</span></td><td><span class="badge badge-gray"></span></td></tr>
<tr><td>5</td><td class="td-num">C2024119</td><td>客户D</td><td>Q235B</td><td>2.20×1100</td><td>17.0</td><td>17:00</td><td><span class="badge badge-gray"></span></td><td><span class="badge badge-gray">待发送</span></td><td><span class="badge badge-gray"></span></td></tr>
</tbody>
</table>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">📅 L3通讯状态</div>
<div class="card-body">
<div class="kv-grid" style="gap:8px 14px;">
<span class="kv-label">L3连接状态</span><span class="kv-value td-warn">待机</span>
<span class="kv-label">最后接收时间</span><span class="kv-value">07:58:22</span>
<span class="kv-label">今日计划总量</span><span class="kv-value">5 卷</span>
<span class="kv-label">已确认</span><span class="kv-value td-ok">1 卷</span>
<span class="kv-label">待确认</span><span class="kv-value td-warn">1 卷</span>
<span class="kv-label">未发送</span><span class="kv-value td-muted">2 卷</span>
<span class="kv-label">计划总重</span><span class="kv-value">86.5 t</span>
<span class="kv-label">计划完成率</span><span class="kv-value">20 %</span>
</div>
<div class="mt12 flex-row">
<button class="btn btn-primary">刷新计划</button>
<button class="btn btn-success">全部确认</button>
</div>
</div>
</div>
</div>
<!-- Gantt -->
<div class="card">
<div class="card-header">📅 今日排产甘特图</div>
<div class="card-body">
<svg viewBox="0 0 700 120" style="width:100%;height:auto;">
<rect width="700" height="120" fill="#0a0f18"/>
<!-- time axis -->
<line x1="80" y1="100" x2="680" y2="100" stroke="#30363d" stroke-width="1"/>
<text x="80" y="115" fill="#6e7681" font-size="9" text-anchor="middle">08:00</text>
<text x="205" y="115" fill="#6e7681" font-size="9" text-anchor="middle">11:00</text>
<text x="330" y="115" fill="#6e7681" font-size="9" text-anchor="middle">14:00</text>
<text x="455" y="115" fill="#6e7681" font-size="9" text-anchor="middle">17:00</text>
<text x="580" y="115" fill="#6e7681" font-size="9" text-anchor="middle">20:00</text>
<!-- grid verticals -->
<line x1="205" y1="10" x2="205" y2="100" stroke="#1a2a3a" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="330" y1="10" x2="330" y2="100" stroke="#1a2a3a" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="455" y1="10" x2="455" y2="100" stroke="#1a2a3a" stroke-width="1" stroke-dasharray="3,3"/>
<!-- bars -->
<rect x="80" y="12" width="120" height="14" rx="2" fill="#1a5a2a" stroke="#28a745" stroke-width="1"/>
<text x="84" y="23" fill="#e6edf3" font-size="9">C2024115 SPCC 2.5×1000 (18.2t)</text>
<rect x="205" y="12" width="95" height="14" rx="2" fill="#1a3a5a" stroke="#1f6feb" stroke-width="1"/>
<text x="209" y="23" fill="#e6edf3" font-size="9">C2024116 SPHC 2.0×1050</text>
<rect x="305" y="12" width="110" height="14" rx="2" fill="#1a3a5a" stroke="#1f6feb" stroke-width="1"/>
<text x="309" y="23" fill="#e6edf3" font-size="9">C2024117 SS400 3.0×900</text>
<rect x="420" y="12" width="80" height="14" rx="2" fill="#2a2a1a" stroke="#f0a500" stroke-width="1" stroke-dasharray="4,2"/>
<text x="424" y="23" fill="#f0a500" font-size="9">C2024118 (待确认)</text>
<rect x="505" y="12" width="90" height="14" rx="2" fill="#1a1a2a" stroke="#30363d" stroke-width="1" stroke-dasharray="4,2"/>
<text x="509" y="23" fill="#6e7681" font-size="9">C2024119 (待发)</text>
<!-- current time -->
<line x1="133" y1="5" x2="133" y2="100" stroke="#da3633" stroke-width="1.5"/>
<text x="135" y="10" fill="#da3633" font-size="9">现在</text>
</svg>
</div>
</div>
</div>
<!-- ══════════ S8: 产能分析 ══════════ -->
<div id="s-capacity" class="screen">
<div class="grid-4">
<div class="metric-box"><div class="mb-label">今日产量</div><div class="mb-value">342</div><div class="mb-unit"></div><div class="mb-delta delta-up">▲ +8.2% vs 昨日</div></div>
<div class="metric-box"><div class="mb-label">本月累计</div><div class="mb-value">8,420</div><div class="mb-unit"></div><div class="mb-delta delta-up">▲ +3.1%</div></div>
<div class="metric-box"><div class="mb-label">机组利用率</div><div class="mb-value">78.4</div><div class="mb-unit">%</div><div class="mb-delta delta-up">▲ +2.0%</div></div>
<div class="metric-box"><div class="mb-label">有效作业时间</div><div class="mb-value">18.8</div><div class="mb-unit">h / 24h</div><div class="mb-delta delta-down">▼ -0.5h</div></div>
</div>
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">🏭 日产能趋势 (近14天)</div>
<div class="card-body">
<canvas id="capChart" class="trend-canvas" style="height:160px;"></canvas>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">🏭 停机时间分析</div>
<div class="card-body">
<table class="data-table" style="font-size:11px;">
<thead><tr><th>停机原因</th><th>时长(min)</th><th>占比</th></tr></thead>
<tbody>
<tr><td>换卷对焊</td><td class="td-num">62</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:48%;background:var(--sms-blue);"></div></div></td></tr>
<tr><td>换辊作业</td><td class="td-num">35</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:27%;background:var(--accent-cyan);"></div></div></td></tr>
<tr><td>设备故障</td><td class="td-num">18</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:14%;background:var(--accent-red);"></div></div></td></tr>
<tr><td>换酸作业</td><td class="td-num">12</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:9%;background:var(--accent-yellow);"></div></div></td></tr>
<tr><td>其他</td><td class="td-num">3</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:2%;background:var(--text-muted);"></div></div></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- ══════════ S9: 绩效KPI ══════════ -->
<div id="s-kpi" class="screen">
<div class="grid-5">
<div class="metric-box">
<div class="mb-label">OEE 综合效率</div>
<div class="mb-value" style="color:var(--accent-green);">82.3</div>
<div class="mb-unit">%</div>
<div class="prog-bar-wrap mt8"><div class="prog-bar-fill" style="width:82.3%;background:var(--accent-green);"></div></div>
</div>
<div class="metric-box">
<div class="mb-label">可用率 A</div>
<div class="mb-value">91.2</div>
<div class="mb-unit">%</div>
<div class="prog-bar-wrap mt8"><div class="prog-bar-fill" style="width:91.2%;background:var(--accent-cyan);"></div></div>
</div>
<div class="metric-box">
<div class="mb-label">性能率 P</div>
<div class="mb-value">87.4</div>
<div class="mb-unit">%</div>
<div class="prog-bar-wrap mt8"><div class="prog-bar-fill" style="width:87.4%;background:var(--accent-blue);"></div></div>
</div>
<div class="metric-box">
<div class="mb-label">质量率 Q</div>
<div class="mb-value">98.6</div>
<div class="mb-unit">%</div>
<div class="prog-bar-wrap mt8"><div class="prog-bar-fill" style="width:98.6%;background:var(--accent-teal);"></div></div>
</div>
<div class="metric-box">
<div class="mb-label">计划完成率</div>
<div class="mb-value">94.8</div>
<div class="mb-unit">%</div>
<div class="prog-bar-wrap mt8"><div class="prog-bar-fill" style="width:94.8%;background:var(--accent-purple);"></div></div>
</div>
</div>
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">🎯 月度KPI趋势</div>
<div class="card-body">
<canvas id="kpiChart" class="trend-canvas" style="height:160px;"></canvas>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">🎯 KPI 明细</div>
<div class="card-body">
<table class="data-table" style="font-size:11px;">
<thead><tr><th>指标</th><th>本月</th><th>目标</th><th>达成</th></tr></thead>
<tbody>
<tr><td>日均产量</td><td class="td-num">380 t</td><td>400 t</td><td><span class="badge badge-yellow">95%</span></td></tr>
<tr><td>酸洗质量合格率</td><td class="td-num">99.1%</td><td>99%</td><td><span class="badge badge-green">达标</span></td></tr>
<tr><td>吨钢酸耗</td><td class="td-num">8.2 kg/t</td><td>8.0 kg/t</td><td><span class="badge badge-yellow">注意</span></td></tr>
<tr><td>设备故障率</td><td class="td-num">1.2%</td><td>2.0%</td><td><span class="badge badge-green">达标</span></td></tr>
<tr><td>换辊次数</td><td class="td-num">12 次</td><td>12 次</td><td><span class="badge badge-green">达标</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- ══════════ S10: 质量报表 ══════════ -->
<div id="s-quality" class="screen">
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">✅ 质量检测报表</div>
<div class="card-body" style="padding:0;">
<table class="data-table">
<thead><tr><th>钢卷号</th><th>钢种</th><th>表面等级</th><th>残酸(g/m²)</th><th>残氧(ppm)</th><th>平整度(IU)</th><th>镰刀弯(mm/m)</th><th>综合评级</th></tr></thead>
<tbody>
<tr><td class="td-num">C2024115</td><td>SPCC</td><td class="td-ok">SA 2.5</td><td class="td-num">0.08</td><td class="td-num">12</td><td class="td-ok">4.2</td><td class="td-ok">0.8</td><td><span class="badge badge-green">A级</span></td></tr>
<tr><td class="td-num">C2024114</td><td>SPCC</td><td class="td-ok">SA 2.5</td><td class="td-num">0.12</td><td class="td-num">15</td><td class="td-ok">5.1</td><td class="td-ok">1.0</td><td><span class="badge badge-green">A级</span></td></tr>
<tr><td class="td-num">C2024113</td><td>SPHC</td><td class="td-warn">SA 2.0</td><td class="td-num">0.22</td><td class="td-num">22</td><td class="td-warn">7.8</td><td class="td-warn">1.5</td><td><span class="badge badge-yellow">B级</span></td></tr>
<tr><td class="td-num">C2024112</td><td>SS400</td><td class="td-ok">SA 2.5</td><td class="td-num">0.09</td><td class="td-num">11</td><td class="td-ok">3.8</td><td class="td-ok">0.6</td><td><span class="badge badge-green">A级</span></td></tr>
<tr><td class="td-num">C2024111</td><td>Q235B</td><td class="td-err">SA 1.5</td><td class="td-num">0.35</td><td class="td-num">35</td><td class="td-err">12.1</td><td class="td-err">2.8</td><td><span class="badge badge-red">C级</span></td></tr>
</tbody>
</table>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">✅ 质量汇总</div>
<div class="card-body">
<div class="kv-grid" style="gap:8px 14px;">
<span class="kv-label">检测总卷数</span><span class="kv-value">5</span>
<span class="kv-label">A级品</span><span class="kv-value td-ok">3 卷</span>
<span class="kv-label">B级品</span><span class="kv-value td-warn">1 卷</span>
<span class="kv-label">C级品</span><span class="kv-value td-err">1 卷</span>
<span class="kv-label">合格率</span><span class="kv-value">80.0 %</span>
<span class="kv-label">优等品率</span><span class="kv-value td-ok">60.0 %</span>
</div>
<div class="mt12">
<button class="btn btn-primary fw">导出质量报表</button>
</div>
</div>
</div>
</div>
</div>
<!-- ══════════ S11: 质量评级 ══════════ -->
<div id="s-qgrade" class="screen">
<div class="grid-4">
<div class="card">
<div class="card-header">⭐ 评级分布</div>
<div class="card-body" style="text-align:center;">
<canvas id="gradeChart" style="max-height:160px;"></canvas>
</div>
</div>
<div class="card" style="grid-column:span 3;">
<div class="card-header">⭐ 质量评级规则</div>
<div class="card-body">
<table class="data-table" style="font-size:11px;">
<thead><tr><th>等级</th><th>残酸(g/m²)</th><th>平整度(IU)</th><th>镰刀弯(mm/m)</th><th>表面等级</th><th>综合条件</th><th>对应用途</th></tr></thead>
<tbody>
<tr><td><span class="q-grade q-a">A</span></td><td class="td-ok">≤ 0.15</td><td class="td-ok">≤ 6.0</td><td class="td-ok">≤ 1.0</td><td class="td-ok">SA 2.5</td><td class="td-ok">全部满足</td><td>冷轧/镀锌基料</td></tr>
<tr><td><span class="q-grade q-b">B</span></td><td class="td-num">0.150.25</td><td class="td-num">6.010</td><td class="td-num">1.02.0</td><td class="td-warn">SA 2.0+</td><td>3项满足</td><td>一般用途钢板</td></tr>
<tr><td><span class="q-grade q-c">C</span></td><td class="td-warn">0.250.40</td><td class="td-warn">1015</td><td class="td-warn">2.03.0</td><td class="td-warn">SA 1.5+</td><td>2项满足</td><td>结构用途</td></tr>
<tr><td><span class="q-grade q-d">D</span></td><td class="td-err"> 0.40</td><td class="td-err"> 15</td><td class="td-err"> 3.0</td><td class="td-err"> SA 1.5</td><td>不满足</td><td>降级处理</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card">
<div class="card-header">⭐ 月度质量评级统计</div>
<div class="card-body">
<canvas id="qGradeMonth" class="trend-canvas" style="height:140px;"></canvas>
</div>
</div>
</div>
<!-- ══════════ S12: 能源消耗 ══════════ -->
<div id="s-energy" class="screen">
<div class="grid-4">
<div class="metric-box"><div class="mb-label">今日电耗</div><div class="mb-value">4,820</div><div class="mb-unit">kWh</div><div class="mb-delta">折合 14.1 kWh/t</div></div>
<div class="metric-box"><div class="mb-label">今日蒸汽耗</div><div class="mb-value">28.4</div><div class="mb-unit">GJ</div><div class="mb-delta">折合 83 MJ/t</div></div>
<div class="metric-box"><div class="mb-label">今日水耗</div><div class="mb-value">186</div><div class="mb-unit"></div><div class="mb-delta">折合 0.54 m³/t</div></div>
<div class="metric-box"><div class="mb-label">今日压缩空气</div><div class="mb-value">1,240</div><div class="mb-unit">Nm³</div><div class="mb-delta">折合 3.6 Nm³/t</div></div>
</div>
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">⚡ 能源消耗趋势 (近14天)</div>
<div class="card-body">
<canvas id="energyChart" class="trend-canvas" style="height:160px;"></canvas>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">⚡ 能源分布</div>
<div class="card-body">
<table class="data-table" style="font-size:11px;">
<thead><tr><th>设备/用途</th><th>电耗(kWh)</th><th>占比</th></tr></thead>
<tbody>
<tr><td>主传动</td><td class="td-num">1,820</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:65%;background:var(--sms-blue);"></div></div></td></tr>
<tr><td>加热系统</td><td class="td-num">1,240</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:44%;background:var(--accent-orange);"></div></div></td></tr>
<tr><td>酸液循环泵</td><td class="td-num">640</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:23%;background:var(--accent-cyan);"></div></div></td></tr>
<tr><td>通风除酸雾</td><td class="td-num">480</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:17%;background:var(--accent-yellow);"></div></div></td></tr>
<tr><td>其他辅助</td><td class="td-num">640</td><td><div class="prog-bar-wrap"><div class="prog-bar-fill" style="width:23%;background:var(--text-muted);"></div></div></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- ══════════ S13: 原料消耗 ══════════ -->
<div id="s-material" class="screen">
<div class="grid-4">
<div class="metric-box"><div class="mb-label">今日盐酸消耗</div><div class="mb-value">2,840</div><div class="mb-unit">L</div><div class="mb-delta">折合 8.3 L/t</div></div>
<div class="metric-box"><div class="mb-label">漂洗水消耗</div><div class="mb-value">186</div><div class="mb-unit"></div><div class="mb-delta">折合 0.54 m³/t</div></div>
<div class="metric-box"><div class="mb-label">钝化液消耗</div><div class="mb-value">42</div><div class="mb-unit">L</div><div class="mb-delta">折合 0.12 L/t</div></div>
<div class="metric-box"><div class="mb-label">清洗液消耗</div><div class="mb-value">68</div><div class="mb-unit">L</div><div class="mb-delta">折合 0.20 L/t</div></div>
</div>
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">🧴 原料消耗趋势 (近14天)</div>
<div class="card-body">
<canvas id="matChart" class="trend-canvas" style="height:160px;"></canvas>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">🧴 补酸计划</div>
<div class="card-body">
<div class="kv-grid" style="gap:8px 14px;">
<span class="kv-label">槽1剩余量</span><span class="kv-value td-ok">1,200 L</span>
<span class="kv-label">槽2剩余量</span><span class="kv-value td-warn">680 L</span>
<span class="kv-label">槽3剩余量</span><span class="kv-value td-err">320 L</span>
<span class="kv-label">库存总量</span><span class="kv-value">12,000 L</span>
<span class="kv-label">预计补酸时间</span><span class="kv-value td-warn">槽3: 2.4h后</span>
<span class="kv-label">采购预警</span><span class="kv-value td-ok">充足</span>
</div>
<div class="mt12">
<button class="btn btn-warn fw">申请补酸 (槽3)</button>
</div>
</div>
</div>
</div>
</div>
<!-- ══════════ S14: 辅料易损件 ══════════ -->
<div id="s-spare" class="screen">
<div class="card">
<div class="card-header">🔧 辅料及易损件统计</div>
<div class="card-body" style="padding:0;">
<table class="data-table">
<thead><tr><th>物料名称</th><th>规格型号</th><th>当前库存</th><th>安全库存</th><th>消耗速率</th><th>预计用完</th><th>上次更换</th><th>更换周期</th><th>状态</th></tr></thead>
<tbody>
<tr><td>刷辊</td><td>ø220×1200mm</td><td class="td-num">4 只</td><td>2 只</td><td>2 只/月</td><td class="td-ok">60天</td><td>2026-04-15</td><td>30天</td><td><span class="badge badge-green">充足</span></td></tr>
<tr><td>夹送辊衬套</td><td>ø120 H8</td><td class="td-num">8 件</td><td>4 件</td><td>1件/2周</td><td class="td-ok">112天</td><td>2026-04-28</td><td>14天</td><td><span class="badge badge-green">充足</span></td></tr>
<tr><td>矫直机工作辊</td><td>ø110×1250mm</td><td class="td-num">2 只</td><td>2 只</td><td>1 只/季</td><td class="td-warn">45天</td><td>2026-03-01</td><td>90天</td><td><span class="badge badge-yellow">临界</span></td></tr>
<tr><td>密封圈组</td><td>各规格</td><td class="td-num">12 套</td><td>6 套</td><td>1套/月</td><td class="td-ok">360天</td><td>2026-04-10</td><td>30天</td><td><span class="badge badge-green">充足</span></td></tr>
<tr><td>盐酸耐腐泵叶轮</td><td>PP-200-150</td><td class="td-num">1 个</td><td>2 个</td><td>1个/季</td><td class="td-err">30天</td><td>2026-02-20</td><td>90天</td><td><span class="badge badge-red">需补货</span></td></tr>
<tr><td>吸酸雾滤芯</td><td>F8级 φ315</td><td class="td-num">6 片</td><td>3 片</td><td>2片/月</td><td class="td-ok">90天</td><td>2026-04-01</td><td>45天</td><td><span class="badge badge-green">充足</span></td></tr>
<tr><td>光电传感器</td><td>E3Z-D62 2M</td><td class="td-num">2 个</td><td>3 个</td><td>0.5个/月</td><td class="td-warn">120天</td><td>2026-03-15</td><td></td><td><span class="badge badge-yellow">临界</span></td></tr>
<tr><td>润滑油脂</td><td>LGWM2/18</td><td class="td-num">36 kg</td><td>20 kg</td><td>8 kg/月</td><td class="td-ok">60天</td><td></td><td></td><td><span class="badge badge-green">充足</span></td></tr>
</tbody>
</table>
</div>
</div>
<div class="section-row">
<div class="card" style="flex:2;">
<div class="card-header">🔧 易损件更换历史</div>
<div class="card-body">
<canvas id="spareChart" class="trend-canvas" style="height:130px;"></canvas>
</div>
</div>
<div class="card" style="flex:1;">
<div class="card-header">🔧 近期更换计划</div>
<div class="card-body">
<table class="data-table" style="font-size:11px;">
<thead><tr><th>物料</th><th>计划日期</th><th>优先级</th></tr></thead>
<tbody>
<tr><td>盐酸耐腐泵叶轮</td><td class="td-warn">2026-05-25</td><td><span class="badge badge-red">紧急</span></td></tr>
<tr><td>矫直机工作辊</td><td class="td-num">2026-06-01</td><td><span class="badge badge-yellow">计划</span></td></tr>
<tr><td>光电传感器</td><td class="td-num">2026-06-15</td><td><span class="badge badge-yellow">计划</span></td></tr>
<tr><td>刷辊</td><td class="td-num">2026-06-20</td><td><span class="badge badge-gray">常规</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- ══════════ S15: 设备排产分析 ══════════ -->
<div id="s-schedule" class="screen">
<div class="tab-bar" id="scheduleTabBar">
<div class="tab-item active" onclick="switchSchedTab(this,'sched-elec')">⚡ 电气设备</div>
<div class="tab-item" onclick="switchSchedTab(this,'sched-mech')">⚙ 机械设备</div>
</div>
<div id="sched-elec" class="tab-content active">
<div class="card">
<div class="card-header">⚡ 电气设备运行状态与排产分析</div>
<div class="card-body" style="padding:0;">
<table class="data-table">
<thead><tr><th>设备名称</th><th>型号/规格</th><th>运行状态</th><th>累计运行(h)</th><th>今日负荷(%)</th><th>计划维护日期</th><th>预警</th><th>排产建议</th></tr></thead>
<tbody>
<tr><td>主传动变频器1</td><td>ACS880 250kW</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">12,480</td><td class="td-num">72</td><td>2026-08-01</td><td class="td-ok">正常</td><td>可满负荷排产</td></tr>
<tr><td>主传动变频器2</td><td>ACS880 200kW</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">12,320</td><td class="td-num">68</td><td>2026-08-01</td><td class="td-ok">正常</td><td>可满负荷排产</td></tr>
<tr><td>焊机变压器</td><td>400kVA</td><td><span class="badge badge-gray">待机</span></td><td class="td-num">8,640</td><td class="td-num">0</td><td>2026-09-01</td><td class="td-ok">正常</td><td>按需启动</td></tr>
<tr><td>酸泵电机组</td><td>55kW×6台</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">18,200</td><td class="td-num">85</td><td>2026-06-15</td><td class="td-warn">临近维保</td><td>尽量安排夜班维修</td></tr>
<tr><td>矫直机电机</td><td>132kW</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">9,840</td><td class="td-num">61</td><td>2026-10-01</td><td class="td-ok">正常</td><td>可满负荷排产</td></tr>
<tr><td>除酸雾风机</td><td>22kW×4台</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">22,100</td><td class="td-num">90</td><td>2026-05-30</td><td class="td-err">需维保</td><td>本周末安排计划停机</td></tr>
<tr><td>高压配电柜</td><td>10kV</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">35,200</td><td class="td-num">78</td><td>2026-07-01</td><td class="td-ok">正常</td><td>按计划维保</td></tr>
</tbody>
</table>
</div>
</div>
<div class="card mt12">
<div class="card-header">⚡ 电气设备负荷分布</div>
<div class="card-body">
<canvas id="elecLoad" class="trend-canvas" style="height:130px;"></canvas>
</div>
</div>
</div>
<div id="sched-mech" class="tab-content">
<div class="card">
<div class="card-header">⚙ 机械设备运行状态与排产分析</div>
<div class="card-body" style="padding:0;">
<table class="data-table">
<thead><tr><th>设备名称</th><th>型号/规格</th><th>运行状态</th><th>累计运行(h)</th><th>润滑状态</th><th>计划维护日期</th><th>预警</th><th>排产建议</th></tr></thead>
<tbody>
<tr><td>开卷机</td><td>20t</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">14,200</td><td class="td-ok">正常</td><td>2026-09-01</td><td class="td-ok">正常</td><td>可满负荷排产</td></tr>
<tr><td>焊机压辊</td><td>ø200mm</td><td><span class="badge badge-gray">待机</span></td><td class="td-num">3,200</td><td class="td-ok">正常</td><td>2026-10-01</td><td class="td-ok">正常</td><td>按需启动</td></tr>
<tr><td>入口活套小车</td><td>行程100m</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">11,600</td><td class="td-warn">需加脂</td><td>2026-06-01</td><td class="td-warn">润滑异常</td><td>近期排班检查</td></tr>
<tr><td>张力辊S1</td><td>ø350mm</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">15,800</td><td class="td-ok">正常</td><td>2026-08-15</td><td class="td-ok">正常</td><td>可满负荷排产</td></tr>
<tr><td>五辊矫直机</td><td>5×ø110mm</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">9,200</td><td class="td-ok">正常</td><td>2026-06-01</td><td class="td-warn">辊面磨损</td><td>计划换辊6月</td></tr>
<tr><td>出口剪机</td><td>液压 400mm</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">8,400</td><td class="td-ok">正常</td><td>2026-09-01</td><td class="td-ok">正常</td><td>可满负荷排产</td></tr>
<tr><td>卷取机</td><td>20t</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">14,100</td><td class="td-ok">正常</td><td>2026-09-01</td><td class="td-ok">正常</td><td>可满负荷排产</td></tr>
<tr><td>刷辊组</td><td>4×ø220mm</td><td><span class="badge badge-green">运行中</span></td><td class="td-num">2,100</td><td class="td-ok">正常</td><td>2026-06-20</td><td class="td-ok">正常</td><td>常规更换</td></tr>
</tbody>
</table>
</div>
</div>
<div class="card mt12">
<div class="card-header">⚙ 机械设备健康度评估</div>
<div class="card-body">
<canvas id="mechHealth" class="trend-canvas" style="height:130px;"></canvas>
</div>
</div>
</div>
</div>
</div><!-- end .main -->
<!-- ─── FOOTER ─── -->
<div class="footer">
<div class="fp"><div class="dot g"></div>L2 系统正常</div>
<div class="fp"><div class="dot y"></div>L3 待机</div>
<div class="fp"><div class="dot g"></div>PLC 通讯正常</div>
<div class="fp"><div class="dot g"></div>HMI 实时</div>
<div style="flex:1;"></div>
<span>推拉酸洗线 PUSH-PULL PICKLING LINE</span>
<span style="margin-left:10px;color:var(--sms-highlight);">SMS X-Pact® L2</span>
<span style="margin-left:10px;" id="footer-date"></span>
</div>
<script>
// ─── CLOCK ───
function updateClock() {
const now = new Date();
document.getElementById('clock').textContent =
now.toTimeString().slice(0,8);
document.getElementById('footer-date').textContent =
now.toLocaleDateString('zh-CN');
}
updateClock();
setInterval(updateClock, 1000);
// ─── SCREEN SWITCH ───
function showScreen(el) {
document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
el.classList.add('active');
const id = el.getAttribute('data-screen');
const scr = document.getElementById(id);
if (scr) {
scr.classList.add('active');
if (id === 's-weld') { htInit(); }
if (id === 's-trend') initTrendCharts();
if (id === 's-capacity') initCapChart();
if (id === 's-kpi') initKpiCharts();
if (id === 's-qgrade') initQGradeCharts();
if (id === 's-energy') initEnergyChart();
if (id === 's-material') initMatChart();
if (id === 's-spare') initSpareChart();
if (id === 's-leveler') initLevelerTrend();
if (id === 's-schedule') initSchedCharts();
}
calcTension();
}
// ─── HEAD / TAIL TRACKING ───
// 机组工艺段定义(从入口夹送辊起始,单位 m
const HT_ZONES = [
{ name:'入口夹送辊', start:0, len:2, color:'#3a7abf', speedLimit:30 },
{ name:'机械清洗段', start:2, len:10, color:'#00b4d8', speedLimit:60 },
{ name:'酸洗槽 #1', start:12, len:18, color:'#f0a500', speedLimit:85 },
{ name:'酸洗槽 #2', start:30, len:18, color:'#e07000', speedLimit:85 },
{ name:'酸洗槽 #3', start:48, len:18, color:'#c05000', speedLimit:85 },
{ name:'漂洗段', start:66, len:12, color:'#2ecc71', speedLimit:90 },
{ name:'钝化段', start:78, len:6, color:'#27ae60', speedLimit:90 },
{ name:'五辊矫直机', start:84, len:5, color:'#a0522d', speedLimit:80 },
{ name:'出口夹送辊', start:89, len:3, color:'#4a6a9a', speedLimit:90 },
];
const HT_TOTAL_LEN = 92; // m 机组总长
// 仿真状态
let htState = {
headPos: 0, // 带头当前位置(m)
tailPos: -300, // 带尾当前位置(m),负值表示还在卷筒上
stripLen: 300, // 带钢长度(m)
vLine: 85, // 正常线速 m/min
vThread: 20, // 穿带速度
vTailMin: 15, // 带尾最低速
accel: 2.0, // 加速度 m/min/s
vUpPos: 25, // 带头加速完成位置
vDownPos: 8, // 带尾到出口夹送辊距离触发减速
running: true,
phase: 'THREAD', // THREAD | RUN | TAIL | DONE
timer: null,
dt: 0.15, // 仿真步长 s
};
function htGetSpeed(hp, tp) {
const s = htState;
// 带头穿带阶段
if (hp < HT_TOTAL_LEN) {
if (hp < s.vUpPos) {
// 线性加速
return s.vThread + (s.vLine - s.vThread) * (hp / s.vUpPos);
}
}
// 带尾减速阶段
const tailToExit = HT_TOTAL_LEN - tp;
if (tp > 0 && tailToExit < s.vDownPos) {
return Math.max(s.vTailMin, s.vLine * (tailToExit / s.vDownPos));
}
return s.vLine;
}
function htStep() {
const s = htState;
if (!s.running) return;
const v = htGetSpeed(s.headPos, s.tailPos); // m/min
const dx = v * s.dt / 60.0; // m per step
s.headPos += dx;
s.tailPos += dx;
// 状态机
if (s.headPos < s.vUpPos) {
s.phase = 'THREAD';
} else if (s.tailPos < 0) {
s.phase = 'RUN';
} else if (s.tailPos < HT_TOTAL_LEN) {
const tailToExit = HT_TOTAL_LEN - s.tailPos;
s.phase = tailToExit < s.vDownPos ? 'TAIL' : 'RUN';
} else {
s.phase = 'DONE';
s.running = false;
clearInterval(s.timer);
}
// 循环重置(演示用)
if (s.headPos > HT_TOTAL_LEN + 20 && s.tailPos > HT_TOTAL_LEN + 20) {
s.headPos = 5; s.tailPos = 5 - s.stripLen; s.running = true; s.phase = 'RUN';
}
htRenderCanvas();
htUpdateUI();
}
function htRenderCanvas() {
const canvas = document.getElementById('htCanvas');
if (!canvas) return;
const W = canvas.offsetWidth || 800;
const H = 90;
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#0a0f18';
ctx.fillRect(0, 0, W, H);
const toX = (pos) => 30 + (pos / HT_TOTAL_LEN) * (W - 60);
// 工艺段背景
HT_ZONES.forEach(z => {
const x1 = toX(z.start);
const x2 = toX(z.start + z.len);
ctx.fillStyle = z.color + '28';
ctx.fillRect(x1, 18, x2-x1, H-36);
ctx.strokeStyle = z.color + '60';
ctx.strokeRect(x1, 18, x2-x1, H-36);
// zone label
ctx.fillStyle = z.color;
ctx.font = '8px Consolas';
ctx.textAlign = 'center';
const midX = (x1+x2)/2;
// wrap long names
const short = z.name.length > 6 ? z.name.slice(0,5)+'…' : z.name;
ctx.fillText(short, midX, 14);
});
// 带钢本体
const hp = htState.headPos;
const tp = htState.tailPos;
const cx1 = Math.max(30, toX(Math.max(0, tp)));
const cx2 = Math.min(W-30, toX(Math.min(HT_TOTAL_LEN, hp)));
if (cx2 > cx1) {
const grad = ctx.createLinearGradient(cx1, 0, cx2, 0);
grad.addColorStop(0, '#1a3a5a');
grad.addColorStop(0.5, '#2a5a8a');
grad.addColorStop(1, '#1a3a5a');
ctx.fillStyle = grad;
ctx.fillRect(cx1, H/2-7, cx2-cx1, 14);
ctx.strokeStyle = '#3a7aaa';
ctx.lineWidth = 1;
ctx.strokeRect(cx1, H/2-7, cx2-cx1, 14);
}
// 带头标记
if (hp >= 0 && hp <= HT_TOTAL_LEN + 5) {
const hx = toX(Math.min(hp, HT_TOTAL_LEN));
ctx.fillStyle = '#28a745';
ctx.beginPath(); ctx.moveTo(hx, H/2-12); ctx.lineTo(hx+8, H/2); ctx.lineTo(hx, H/2+12); ctx.closePath(); ctx.fill();
ctx.fillStyle = '#28a745';
ctx.font = 'bold 9px Consolas';
ctx.textAlign = 'left';
ctx.fillText('HEAD', hx+10, H/2+3);
}
// 带尾标记
if (tp >= 0 && tp <= HT_TOTAL_LEN) {
const tx = toX(tp);
ctx.fillStyle = '#da3633';
ctx.beginPath(); ctx.moveTo(tx, H/2-12); ctx.lineTo(tx-8, H/2); ctx.lineTo(tx, H/2+12); ctx.closePath(); ctx.fill();
ctx.fillStyle = '#da3633';
ctx.font = 'bold 9px Consolas';
ctx.textAlign = 'right';
ctx.fillText('TAIL', tx-10, H/2+3);
}
// 位置刻度
[0,10,20,30,40,50,60,70,80,90].forEach(m => {
const x = toX(m);
ctx.strokeStyle = '#2a3a4a';
ctx.lineWidth = 0.5;
ctx.beginPath(); ctx.moveTo(x, H-6); ctx.lineTo(x, H); ctx.stroke();
ctx.fillStyle = '#4a6a8a';
ctx.font = '8px Consolas';
ctx.textAlign = 'center';
ctx.fillText(m+'m', x, H-1);
});
}
function htUpdateUI() {
const s = htState;
const v = htGetSpeed(s.headPos, s.tailPos);
document.getElementById('ht-head-pos').textContent = s.headPos.toFixed(1);
document.getElementById('ht-tail-pos').textContent = s.tailPos < 0 ? '未入线' : s.tailPos.toFixed(1);
document.getElementById('ht-strip-len').textContent = s.stripLen.toFixed(0);
document.getElementById('ht-linespd').textContent = v.toFixed(1);
const stateMap = {
THREAD: ['穿带中', '#f0a500', '带头正在穿过工艺线'],
RUN: ['正常轧制', '#28a745', '机组速度模式'],
TAIL: ['带尾通过', '#da3633', '带尾减速控制中'],
DONE: ['穿带完成', '#00c8ff', '等待下一卷'],
};
const [label, color, sub] = stateMap[s.phase] || ['—','#8b949e',''];
const sv = document.getElementById('ht-state-val');
const ss = document.getElementById('ht-state-sub');
if (sv) { sv.textContent = label; sv.style.color = color; }
if (ss) ss.textContent = sub;
// zone table
const tbody = document.getElementById('htZoneTbody');
if (tbody) {
tbody.innerHTML = HT_ZONES.map(z => {
const zEnd = z.start + z.len;
const headIn = s.headPos >= z.start;
const headOut = s.headPos >= zEnd;
const tailIn = s.tailPos >= z.start;
const tailOut = s.tailPos >= zEnd;
const headBadge = !headIn ? '<span class="badge badge-gray">未到达</span>'
: !headOut ? '<span class="badge badge-yellow">穿过中</span>'
: '<span class="badge badge-green">已通过</span>';
const tailBadge = s.tailPos < 0 ? '<span class="badge badge-gray">未入线</span>'
: !tailIn ? '<span class="badge badge-gray">未到达</span>'
: !tailOut ? '<span class="badge badge-yellow">通过中</span>'
: '<span class="badge badge-green">已通过</span>';
const eta = headIn ? '—' : ((z.start - s.headPos) / (v/60)).toFixed(0)+'s后';
const teta = s.tailPos < 0 ? '—' : tailOut ? '—' : ((zEnd - s.tailPos) / (v/60)).toFixed(0)+'s后';
return `<tr>
<td style="color:${z.color};font-weight:600;">${z.name}</td>
<td class="td-num">${z.len}</td>
<td class="td-num">${z.start}</td>
<td>${headBadge}</td>
<td>${tailBadge}</td>
<td class="td-num">${eta}</td>
<td class="td-num">${teta}</td>
<td class="td-num">${z.speedLimit}</td>
</tr>`;
}).join('');
}
}
function htCalcSpeed() {
const vt = parseFloat(document.getElementById('ht-vthread').value)||20;
const a = parseFloat(document.getElementById('ht-accel').value)||2.0;
const vl = parseFloat(document.getElementById('ht-vline').value)||85;
const upP = parseFloat(document.getElementById('ht-vup-pos').value)||25;
const dnP = parseFloat(document.getElementById('ht-vdown-pos').value)||8;
const vtail = parseFloat(document.getElementById('ht-vtail').value)||15;
// 加速距: v² = vt² + 2*a*d → d = (vl²-vt²)/(2*a) in m/min² need consistent units
const a_mps2 = a / 60; // m/s²
const vl_mps = vl / 60;
const vt_mps = vt / 60;
const accDist = ((vl_mps*vl_mps - vt_mps*vt_mps) / (2*a_mps2)).toFixed(1);
const accTime = ((vl_mps - vt_mps) / a_mps2).toFixed(1);
// 减速距
const decDist = ((vl_mps*vl_mps - (vtail/60)*(vtail/60)) / (2*a_mps2)).toFixed(1);
// 总通过时间估算
const totalTime = (HT_TOTAL_LEN / (vl/60)).toFixed(0);
document.getElementById('ht-res-accdist').textContent = accDist + ' m';
document.getElementById('ht-res-tthread').textContent = accTime + ' s';
document.getElementById('ht-res-decdist').textContent = decDist + ' m';
document.getElementById('ht-res-total').textContent = totalTime + ' s';
// 更新仿真参数
htState.vThread = vt; htState.vLine = vl; htState.accel = a;
htState.vUpPos = upP; htState.vDownPos = dnP; htState.vTailMin = vtail;
// 绘制速度曲线
htDrawSpeedCurve(vt, vl, vtail, upP, dnP);
}
function htDrawSpeedCurve(vt, vl, vtail, upPos, dnPos) {
const canvas = document.getElementById('htSpeedCurve');
if (!canvas || !canvas.getContext) return;
const W = canvas.offsetWidth || 700;
const H = 130;
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#0a0f18';
ctx.fillRect(0, 0, W, H);
const pad = {l:40, r:20, t:15, b:25};
const iW = W - pad.l - pad.r;
const iH = H - pad.t - pad.b;
const maxLen = HT_TOTAL_LEN + 10;
const maxV = vl * 1.1;
const toX = p => pad.l + (p/maxLen)*iW;
const toY = v => pad.t + iH - (v/maxV)*iH;
// grid
ctx.strokeStyle = '#1a2a3a'; ctx.lineWidth = 0.7;
[0,25,50,75,100].forEach(v => {
const y = toY(v);
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l+iW, y); ctx.stroke();
ctx.fillStyle = '#4a6a8a'; ctx.font = '9px Consolas'; ctx.textAlign = 'right';
ctx.fillText(v, pad.l-4, y+3);
});
[0,10,20,30,40,50,60,70,80,90].forEach(m => {
const x = toX(m);
ctx.beginPath(); ctx.moveTo(x, pad.t); ctx.lineTo(x, pad.t+iH); ctx.stroke();
ctx.fillStyle = '#4a6a8a'; ctx.font = '9px Consolas'; ctx.textAlign = 'center';
ctx.fillText(m+'m', x, H-3);
});
// axes labels
ctx.fillStyle = '#6e7681'; ctx.font = '9px Consolas'; ctx.textAlign = 'left';
ctx.fillText('速度(m/min)', 2, pad.t+10);
// HEAD speed curve
const ptH = [];
for (let p = 0; p <= HT_TOTAL_LEN; p += 0.5) {
let v = p < upPos ? vt + (vl-vt)*(p/upPos) : vl;
ptH.push([toX(p), toY(v)]);
}
ctx.beginPath(); ctx.strokeStyle = '#28a745'; ctx.lineWidth = 2;
ptH.forEach(([x,y],i) => i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y));
ctx.stroke();
ctx.fillStyle = '#28a745'; ctx.font = 'bold 10px Consolas'; ctx.textAlign = 'left';
ctx.fillText('带头速度', toX(2), toY(vl)-5);
// TAIL speed curve
const ptT = [];
for (let p = 0; p <= HT_TOTAL_LEN; p += 0.5) {
const tailToExit = HT_TOTAL_LEN - p;
let v = tailToExit < dnPos ? Math.max(vtail, vl*(tailToExit/dnPos)) : vl;
ptT.push([toX(p), toY(v)]);
}
ctx.beginPath(); ctx.strokeStyle = '#da3633'; ctx.lineWidth = 2;
ptT.forEach(([x,y],i) => i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y));
ctx.stroke();
ctx.fillStyle = '#da3633'; ctx.font = 'bold 10px Consolas'; ctx.textAlign = 'right';
ctx.fillText('带尾速度', toX(HT_TOTAL_LEN-2), toY(vl)-5);
// vLine reference
ctx.setLineDash([5,3]);
ctx.strokeStyle = '#00c8ff44'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(pad.l, toY(vl)); ctx.lineTo(pad.l+iW, toY(vl)); ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#00c8ff'; ctx.font = '9px Consolas'; ctx.textAlign = 'right';
ctx.fillText('V_line='+vl, pad.l+iW, toY(vl)-3);
}
function htSimToggle(btn) {
htState.running = !htState.running;
btn.textContent = htState.running ? '⏸ 仿真暂停' : '▶ 仿真运行';
if (htState.running && !htState.timer) {
htState.timer = setInterval(htStep, htState.dt * 1000);
}
}
function htReset() {
htState.headPos = 5;
htState.tailPos = 5 - htState.stripLen;
htState.running = true;
htState.phase = 'RUN';
htRenderCanvas();
htUpdateUI();
}
function htInit() {
htCalcSpeed();
htState.headPos = 55;
htState.tailPos = 55 - htState.stripLen;
if (htState.timer) clearInterval(htState.timer);
htState.timer = setInterval(htStep, htState.dt * 1000);
htRenderCanvas();
htUpdateUI();
}
function calcTension() {
const h = parseFloat(document.getElementById('t-thick').value) || 2.5;
const w = parseFloat(document.getElementById('t-width').value) || 1000;
const sy = parseFloat(document.getElementById('t-yield').value) || 380;
const k = parseFloat(document.getElementById('t-coef').value) || 0.25;
const A = h * w; // mm²
const Tmax = sy * A * k / 1000; // kN
const Tacid = Tmax * 0.80;
const Tout = Tmax * 0.90;
const Tspec = sy * k;
const Tunit = Tmax / w * 1000; // N/mm
const Tweld = Tmax * 0.50;
const Tacc = Tmax * 0.70;
document.getElementById('res-area').textContent = A.toFixed(0) + ' mm²';
document.getElementById('res-tmax').textContent = Tmax.toFixed(1) + ' kN';
document.getElementById('res-tacid').textContent = Tacid.toFixed(1) + ' kN';
document.getElementById('res-tout').textContent = Tout.toFixed(1) + ' kN';
document.getElementById('res-spec').textContent = Tspec.toFixed(1) + ' N/mm²';
document.getElementById('res-unit').textContent = Tunit.toFixed(1) + ' N/mm';
document.getElementById('res-weld').textContent = Tweld.toFixed(1) + ' kN';
document.getElementById('res-acc').textContent = Tacc.toFixed(1) + ' kN';
// update diagram display values
const vals = [
Tmax*0.6, Tmax*0.75, Tacid, Tacid, Tacid, Tacid, Tmax*0.8, Tmax*0.85, Tout, Tout
];
['tr-uw','tr-s1','tr-in','tr-a1','tr-a2','tr-a3','tr-rn','tr-lv','tr-s2','tr-rw'].forEach((id,i)=>{
const el = document.getElementById(id);
if (el) el.textContent = vals[i].toFixed(1)+'kN';
});
}
calcTension();
// ─── PROCESS CALC ───
function calcProcess() {
const sp = 85; // m/min
const len = parseFloat(document.getElementById('tank-len').value)||18;
const rt = (len / sp * 60).toFixed(1);
const maxSpd = Math.min(95, (len * 60 / 12).toFixed(0));
document.getElementById('p-rt').textContent = rt;
document.getElementById('p-maxspd').textContent = maxSpd;
}
calcProcess();
// ─── LEVELER CALC ───
function calcLeveler() {
const r1 = parseFloat(document.getElementById('lv-r1').value)||1.2;
const r2 = parseFloat(document.getElementById('lv-r2').value)||0.8;
const h = parseFloat(document.getElementById('lv-h').value)||2.5;
const D = parseFloat(document.getElementById('lv-d').value)||110;
const sy = parseFloat(document.getElementById('lv-sy').value)||380;
const W = 1000; // width mm
const totalDelta = r1+r2+(parseFloat(document.getElementById('lv-r3').value)||0.6);
const elongation = (totalDelta / h * 100 * 0.4).toFixed(2);
const elongTot = (totalDelta / h * 100 * 0.55).toFixed(2);
const force = (sy * W * h / 1e6 * 3.2).toFixed(1);
const moment = (sy * W * h * h / 6 / 1e6 * (D/2)).toFixed(0);
const stress = (sy * 0.12).toFixed(0);
document.getElementById('lv-ein').textContent = elongation;
document.getElementById('lv-etot').textContent = elongTot;
document.getElementById('lv-force').textContent = force;
document.getElementById('lv-moment').textContent = moment;
document.getElementById('lv-stress').textContent = stress;
document.getElementById('svgR2').textContent = 'δ='+r2.toFixed(1)+'mm';
document.getElementById('svgR4').textContent = 'δ='+(parseFloat(document.getElementById('lv-r4').value)||0.4).toFixed(1)+'mm';
}
calcLeveler();
// ─── CHART HELPERS ───
function mkLineChart(ctxId, labels, datasets, opts) {
const el = document.getElementById(ctxId);
if (!el) return;
const ctx = el.getContext('2d');
// clear previous
if (el._chart) { el._chart.destroy(); }
const cfg = {
type: 'line',
data: { labels, datasets },
options: {
animation: false,
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: opts.legend||false, labels: { color:'#8b949e', font:{size:10} } } },
scales: {
x: { ticks:{color:'#6e7681',font:{size:9}}, grid:{color:'#1a2a3a'} },
y: { ticks:{color:'#6e7681',font:{size:9}}, grid:{color:'#1a2a3a'} }
}
}
};
el._chart = new Chart(ctx, cfg);
}
function mkBarChart(ctxId, labels, datasets, opts) {
const el = document.getElementById(ctxId);
if (!el) return;
const ctx = el.getContext('2d');
if (el._chart) { el._chart.destroy(); }
el._chart = new Chart(ctx, {
type: 'bar',
data: { labels, datasets },
options: {
animation: false,
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: opts.legend||false, labels:{color:'#8b949e',font:{size:10}} } },
scales: {
x: { ticks:{color:'#6e7681',font:{size:9}}, grid:{color:'#1a2a3a'} },
y: { ticks:{color:'#6e7681',font:{size:9}}, grid:{color:'#1a2a3a'} }
}
}
});
}
function mkDoughnut(ctxId, labels, data, colors) {
const el = document.getElementById(ctxId);
if (!el) return;
const ctx = el.getContext('2d');
if (el._chart) { el._chart.destroy(); }
el._chart = new Chart(ctx, {
type: 'doughnut',
data: {
labels,
datasets: [{ data, backgroundColor: colors, borderColor:'#0d1117', borderWidth:2 }]
},
options: {
animation: false,
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: true, position:'right', labels:{color:'#8b949e',font:{size:10},boxWidth:12} }
}
}
});
}
function days14() {
const d=[]; const now=new Date();
for(let i=13;i>=0;i--){const dd=new Date(now); dd.setDate(dd.getDate()-i); d.push((dd.getMonth()+1)+'/'+(dd.getDate()));}
return d;
}
function rnd(min,max,n=14){return Array.from({length:n},()=>+(Math.random()*(max-min)+min).toFixed(1));}
// ─── TREND CHARTS ───
let trendPlay = true;
let trendInterval = null;
const trendData = {speed:[], tension:[], acid:[]};
for(let i=0;i<60;i++){
trendData.speed.push(+(75+Math.random()*15).toFixed(1));
trendData.tension.push(+(17+Math.random()*4).toFixed(1));
trendData.acid.push(+(165+Math.random()*20).toFixed(1));
}
function initTrendCharts() {
const labels = Array.from({length:60},(_,i)=>i%10===0?(i+'s'):'');
mkLineChart('trendCanvas', labels, [
{label:'速度',data:[...trendData.speed],borderColor:'#00c8ff',backgroundColor:'rgba(0,200,255,.05)',tension:.3,pointRadius:0,borderWidth:1.5},
{label:'张力×10',data:trendData.tension.map(v=>v*3),borderColor:'#f0a500',backgroundColor:'rgba(240,165,0,.05)',tension:.3,pointRadius:0,borderWidth:1.5},
], {legend:true});
mkLineChart('tc-speed', labels, [{label:'速度',data:[...trendData.speed],borderColor:'#00c8ff',backgroundColor:'rgba(0,200,255,.1)',tension:.3,pointRadius:0,fill:true,borderWidth:1.5}],{});
mkLineChart('tc-tension', labels, [{label:'张力',data:[...trendData.tension],borderColor:'#f0a500',backgroundColor:'rgba(240,165,0,.1)',tension:.3,pointRadius:0,fill:true,borderWidth:1.5}],{});
mkLineChart('tc-acid', labels, [{label:'浓度',data:[...trendData.acid],borderColor:'#28a745',backgroundColor:'rgba(40,167,69,.1)',tension:.3,pointRadius:0,fill:true,borderWidth:1.5}],{});
if (trendPlay) {
if (trendInterval) clearInterval(trendInterval);
trendInterval = setInterval(()=>{
trendData.speed.shift(); trendData.speed.push(+(75+Math.random()*15).toFixed(1));
trendData.tension.shift(); trendData.tension.push(+(17+Math.random()*4).toFixed(1));
trendData.acid.shift(); trendData.acid.push(+(165+Math.random()*20).toFixed(1));
updateTrendCharts();
}, 1200);
}
}
function updateTrendCharts() {
['trendCanvas','tc-speed','tc-tension','tc-acid'].forEach(id=>{
const el=document.getElementById(id);
if (el&&el._chart) {
const c = el._chart;
if (id==='trendCanvas') {
c.data.datasets[0].data=[...trendData.speed];
c.data.datasets[1].data=trendData.tension.map(v=>v*3);
} else if (id==='tc-speed') {
c.data.datasets[0].data=[...trendData.speed];
} else if (id==='tc-tension') {
c.data.datasets[0].data=[...trendData.tension];
} else if (id==='tc-acid') {
c.data.datasets[0].data=[...trendData.acid];
}
c.update('none');
}
});
}
function setTrendTab(el,id) {
document.querySelectorAll('#s-trend .tab-item').forEach(t=>t.classList.remove('active'));
el.classList.add('active');
}
function toggleTrendPlay(btn) {
trendPlay = !trendPlay;
btn.textContent = trendPlay ? '⏸ 暂停' : '▶ 播放';
if (!trendPlay && trendInterval) { clearInterval(trendInterval); trendInterval=null; }
else if (trendPlay) { initTrendCharts(); }
}
// ─── CAPACITY CHART ───
function initCapChart() {
const labels = days14();
const data = rnd(320,410);
mkBarChart('capChart', labels, [{
label:'日产量(t)', data,
backgroundColor: data.map(v=>v>=380?'rgba(40,167,69,.7)':'rgba(31,111,235,.6)'),
borderRadius:2
}], {legend:false});
}
// ─── KPI CHARTS ───
function initKpiCharts() {
const labels = ['1月','2月','3月','4月','5月'];
mkLineChart('kpiChart', labels, [
{label:'OEE(%)',data:[78,80,81,82,82.3],borderColor:'#28a745',tension:.3,pointRadius:3,borderWidth:2},
{label:'可用率(%)',data:[88,90,91,91,91.2],borderColor:'#00c8ff',tension:.3,pointRadius:3,borderWidth:2},
{label:'质量率(%)',data:[97,98,98.5,98.6,98.6],borderColor:'#f0a500',tension:.3,pointRadius:3,borderWidth:2},
], {legend:true});
}
// ─── Q GRADE CHARTS ───
function initQGradeCharts() {
mkDoughnut('gradeChart',['A级','B级','C级','D级'],[60,20,15,5],['#28a745','#00b4d8','#f0a500','#da3633']);
const labels = days14();
mkBarChart('qGradeMonth', labels, [
{label:'A级',data:rnd(55,75),backgroundColor:'rgba(40,167,69,.7)',borderRadius:2},
{label:'B级',data:rnd(15,25),backgroundColor:'rgba(0,180,216,.6)',borderRadius:2},
{label:'C级',data:rnd(5,15),backgroundColor:'rgba(240,165,0,.6)',borderRadius:2},
], {legend:true});
}
// ─── ENERGY CHART ───
function initEnergyChart() {
const labels = days14();
mkLineChart('energyChart', labels, [
{label:'电耗(kWh)',data:rnd(4200,5400),borderColor:'#f0a500',backgroundColor:'rgba(240,165,0,.1)',tension:.3,pointRadius:0,fill:true,borderWidth:1.5},
], {legend:false});
}
// ─── MATERIAL CHART ───
function initMatChart() {
const labels = days14();
mkBarChart('matChart', labels, [
{label:'盐酸(×100L)',data:rnd(22,35),backgroundColor:'rgba(0,200,255,.6)',borderRadius:2},
{label:'钝化液(L)',data:rnd(35,55),backgroundColor:'rgba(40,167,69,.6)',borderRadius:2},
], {legend:true});
}
// ─── SPARE CHART ───
function initSpareChart() {
const labels = ['Jan','Feb','Mar','Apr','May'];
mkBarChart('spareChart', labels, [
{label:'刷辊',data:[1,1,2,1,1],backgroundColor:'rgba(0,200,255,.7)',borderRadius:2},
{label:'矫直辊',data:[0,0,1,0,0],backgroundColor:'rgba(240,165,0,.7)',borderRadius:2},
{label:'泵叶轮',data:[0,1,0,0,1],backgroundColor:'rgba(218,54,51,.7)',borderRadius:2},
], {legend:true});
}
// ─── LEVELER TREND ───
function initLevelerTrend() {
const labels = days14();
mkLineChart('levelerTrend', labels, [
{label:'延伸率(%)',data:rnd(1.0,1.5),borderColor:'#00c8ff',tension:.3,pointRadius:0,borderWidth:1.5},
{label:'矫直力(MN)',data:rnd(2.8,4.0),borderColor:'#f0a500',tension:.3,pointRadius:0,borderWidth:1.5},
], {legend:true});
}
// ─── SCHEDULE CHARTS ───
function initSchedCharts() {
const eqElec = ['变频1','变频2','焊机变压','酸泵组','矫直机','除酸雾风机'];
mkBarChart('elecLoad', eqElec, [{
label:'负荷(%)',
data:[72,68,0,85,61,90],
backgroundColor:[
'rgba(0,200,255,.7)','rgba(0,200,255,.7)','rgba(110,118,129,.4)',
'rgba(240,165,0,.7)','rgba(0,200,255,.7)','rgba(218,54,51,.8)'
],
borderRadius:3
}], {legend:false});
const eqMech = ['开卷机','焊机压辊','入口活套','S1张力辊','矫直机','出口剪','卷取机'];
mkBarChart('mechHealth', eqMech, [{
label:'健康度(%)',
data:[92,98,75,88,82,95,91],
backgroundColor:[
'rgba(40,167,69,.7)','rgba(40,167,69,.7)','rgba(240,165,0,.7)',
'rgba(40,167,69,.7)','rgba(0,180,216,.7)','rgba(40,167,69,.7)','rgba(40,167,69,.7)'
],
borderRadius:3
}], {legend:false});
}
function switchSchedTab(el, id) {
document.querySelectorAll('#scheduleTabBar .tab-item').forEach(t=>t.classList.remove('active'));
document.querySelectorAll('#s-schedule .tab-content').forEach(c=>c.classList.remove('active'));
el.classList.add('active');
document.getElementById(id).classList.add('active');
setTimeout(initSchedCharts, 50);
}
// ─── CHART.JS CDN ───
(function loadChartJs() {
if (window.Chart) return;
const s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js';
s.onload = () => {
// auto-init first screen that needs a chart after load
const active = document.querySelector('.screen.active');
if (active) {
if (active.id === 's-trend') initTrendCharts();
if (active.id === 's-capacity') initCapChart();
}
};
document.head.appendChild(s);
})();
// Init tension on load
window.addEventListener('load', ()=>{ calcTension(); calcProcess(); calcLeveler(); htInit(); });
</script>
</body>
</html>