2026-05-27 16:38:40 +08:00
|
|
|
|
<template>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="mat-page">
|
|
|
|
|
|
<!-- 顶部状态条 -->
|
|
|
|
|
|
<div class="status-bar">
|
|
|
|
|
|
<div class="status-item">
|
|
|
|
|
|
<span class="kv-label">当前卷号</span>
|
|
|
|
|
|
<span class="kv-value">{{ current.coil_no || '—' }}</span>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="status-item">
|
|
|
|
|
|
<span class="kv-label">工艺段速度</span>
|
|
|
|
|
|
<span class="kv-value">{{ current.speed.toFixed(1) }} <span class="kv-unit">m/min</span></span>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="status-item">
|
|
|
|
|
|
<span class="kv-label">焊缝位置</span>
|
|
|
|
|
|
<span class="kv-value">{{ (weld.position * 100).toFixed(1) }} <span class="kv-unit">%</span></span>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<div class="status-item">
|
|
|
|
|
|
<span class="kv-label">当前设备</span>
|
|
|
|
|
|
<span class="kv-value">{{ currentEquipment.label }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="status-item">
|
|
|
|
|
|
<span class="kv-label">开卷张力</span>
|
|
|
|
|
|
<span class="kv-value">{{ uncoiler.tension.toFixed(1) }} <span class="kv-unit">kN</span></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="status-item">
|
|
|
|
|
|
<span class="kv-label">收卷张力</span>
|
|
|
|
|
|
<span class="kv-value">{{ recoiler.tension.toFixed(1) }} <span class="kv-unit">kN</span></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="status-item" style="margin-left:auto;">
|
|
|
|
|
|
<span :class="['badge', l1Online ? 'badge-green' : 'badge-yellow']">{{ l1Online ? 'L1 在线' : '模拟数据' }}</span>
|
|
|
|
|
|
<span class="kv-label" style="margin-left:8px;">{{ rtItems.length }} 测点</span>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-06-20 18:19:06 +08:00
|
|
|
|
<!-- 产线总图 -->
|
|
|
|
|
|
<div class="line-wrap card">
|
|
|
|
|
|
<div class="card-header">推拉酸洗线 - 物料跟踪总图</div>
|
|
|
|
|
|
<div class="line-body">
|
|
|
|
|
|
<svg viewBox="0 0 1900 280" preserveAspectRatio="xMidYMid meet" class="line-svg">
|
|
|
|
|
|
<rect x="0" y="0" width="1900" height="280" fill="#0a1218" />
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 顶部标签 -->
|
|
|
|
|
|
<g v-for="eq in equipments" :key="'lab-'+eq.k" font-family="Arial,sans-serif">
|
|
|
|
|
|
<text :x="eq.x" y="20" text-anchor="middle" font-size="10.5" fill="#c8d4e0">{{ eq.label }}</text>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 主带钢线 -->
|
|
|
|
|
|
<path d="M 40 160 L 1860 160" stroke="#5a6a75" stroke-width="3" fill="none"/>
|
|
|
|
|
|
<path d="M 40 160 L 1860 160" stroke="#aabbcc" stroke-width="1.2" fill="none" stroke-dasharray="6 10">
|
|
|
|
|
|
<animate attributeName="stroke-dashoffset" from="16" to="0" dur="0.7s" repeatCount="indefinite"/>
|
|
|
|
|
|
</path>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 各设备图形 -->
|
|
|
|
|
|
<g v-for="eq in equipments" :key="eq.k" :transform="`translate(${eq.x}, 160)`">
|
|
|
|
|
|
<!-- 开卷机 -->
|
|
|
|
|
|
<template v-if="eq.type==='coiler'">
|
|
|
|
|
|
<circle r="38" fill="#1a232c" stroke="#3a4a55" stroke-width="2"/>
|
|
|
|
|
|
<circle r="22" fill="#0a1218" stroke="#5a6a75" stroke-width="1.5"/>
|
|
|
|
|
|
<circle r="8" fill="#2a3a48" stroke="#5a7090" stroke-width="1"/>
|
|
|
|
|
|
<path d="M-38 0 a38 38 0 0 1 76 0" stroke="#00c8ff" stroke-width="1" fill="none" opacity="0.5">
|
|
|
|
|
|
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="3s" repeatCount="indefinite"/>
|
|
|
|
|
|
</path>
|
|
|
|
|
|
<text y="58" text-anchor="middle" font-size="10" fill="#b8c4cf">DC-1</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 九辊矫直机:5上4下 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='rolls9'">
|
|
|
|
|
|
<rect x="-44" y="-26" width="88" height="52" fill="#1a232c" stroke="#3a4a55" stroke-width="1.5" rx="3"/>
|
|
|
|
|
|
<g v-for="i in 5" :key="'t'+i">
|
|
|
|
|
|
<circle :cx="-36 + (i-1)*18" cy="-10" r="6" fill="#2a3540" stroke="#7090a8" stroke-width="1"/>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
<g v-for="i in 4" :key="'b'+i">
|
|
|
|
|
|
<circle :cx="-27 + (i-1)*18" cy="10" r="6" fill="#2a3540" stroke="#7090a8" stroke-width="1"/>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
<text y="44" text-anchor="middle" font-size="9" fill="#b8c4cf">STR-9</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 切头/切尾剪 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='shear'">
|
|
|
|
|
|
<rect x="-30" y="-26" width="60" height="52" fill="#1a232c" stroke="#3a4a55" stroke-width="1.5" rx="2"/>
|
|
|
|
|
|
<line x1="-18" y1="-16" x2="18" y2="16" stroke="#da3633" stroke-width="2.2"/>
|
|
|
|
|
|
<line x1="-18" y1="16" x2="18" y2="-16" stroke="#da3633" stroke-width="2.2"/>
|
|
|
|
|
|
<circle cx="-18" cy="-16" r="3" fill="#da3633"/>
|
|
|
|
|
|
<circle cx="18" cy="-16" r="3" fill="#da3633"/>
|
|
|
|
|
|
<circle cx="0" cy="0" r="3" fill="#ffdd44"/>
|
|
|
|
|
|
<text y="44" text-anchor="middle" font-size="9" fill="#b8c4cf">{{ eq.code }}</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 酸洗槽 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='acid'">
|
|
|
|
|
|
<path d="M -32 -24 L 32 -24 L 28 26 L -28 26 Z" fill="#3a2a18" stroke="#a06030" stroke-width="2"/>
|
|
|
|
|
|
<path d="M -30 -10 L 30 -10 L 27 24 L -27 24 Z" fill="#ffaa44" opacity="0.55">
|
|
|
|
|
|
<animate attributeName="opacity" values="0.5;0.7;0.5" dur="2.5s" repeatCount="indefinite"/>
|
|
|
|
|
|
</path>
|
|
|
|
|
|
<path d="M -22 -10 q 4 -6 8 0 t 8 0 t 8 0 t 8 0" stroke="#ffd28a" stroke-width="1" fill="none" opacity="0.7"/>
|
|
|
|
|
|
<!-- 蒸汽 -->
|
|
|
|
|
|
<g opacity="0.6">
|
|
|
|
|
|
<circle cx="-12" cy="-30" r="3" fill="#cccccc">
|
|
|
|
|
|
<animate attributeName="cy" values="-30;-46;-30" dur="2s" repeatCount="indefinite"/>
|
|
|
|
|
|
<animate attributeName="opacity" values="0.6;0;0.6" dur="2s" repeatCount="indefinite"/>
|
|
|
|
|
|
</circle>
|
|
|
|
|
|
<circle cx="6" cy="-32" r="2.5" fill="#cccccc">
|
|
|
|
|
|
<animate attributeName="cy" values="-32;-50;-32" dur="2.3s" repeatCount="indefinite"/>
|
|
|
|
|
|
<animate attributeName="opacity" values="0.5;0;0.5" dur="2.3s" repeatCount="indefinite"/>
|
|
|
|
|
|
</circle>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
<text x="0" y="44" text-anchor="middle" font-size="9" fill="#ffaa44">{{ acid[eq.idx].temp.toFixed(0) }}°C · {{ acid[eq.idx].conc.toFixed(0) }}g/L</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 漂洗段 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='rinse'">
|
|
|
|
|
|
<path d="M -34 -24 L 34 -24 L 30 26 L -30 26 Z" fill="#142a2e" stroke="#4080a0" stroke-width="2"/>
|
|
|
|
|
|
<path d="M -32 -8 L 32 -8 L 29 24 L -29 24 Z" fill="#3aa0c8" opacity="0.55">
|
|
|
|
|
|
<animate attributeName="opacity" values="0.5;0.7;0.5" dur="2.5s" repeatCount="indefinite"/>
|
|
|
|
|
|
</path>
|
|
|
|
|
|
<path d="M -22 -8 q 4 -5 8 0 t 8 0 t 8 0 t 8 0" stroke="#bce4f0" stroke-width="1" fill="none" opacity="0.7"/>
|
|
|
|
|
|
<text y="44" text-anchor="middle" font-size="9" fill="#3aa0c8">5级逆流</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 热风烘干段 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='dryer'">
|
|
|
|
|
|
<rect x="-36" y="-26" width="72" height="52" fill="#2a2010" stroke="#a08030" stroke-width="2" rx="3"/>
|
|
|
|
|
|
<g stroke="#ffaa00" stroke-width="1.6" fill="none">
|
|
|
|
|
|
<path d="M -26 -12 q 4 -6 8 0 t 8 0 t 8 0 t 8 0 t 8 0">
|
|
|
|
|
|
<animate attributeName="opacity" values="0.4;1;0.4" dur="1.4s" repeatCount="indefinite"/>
|
|
|
|
|
|
</path>
|
|
|
|
|
|
<path d="M -26 4 q 4 -6 8 0 t 8 0 t 8 0 t 8 0 t 8 0">
|
|
|
|
|
|
<animate attributeName="opacity" values="0.7;0.3;0.7" dur="1.4s" repeatCount="indefinite"/>
|
|
|
|
|
|
</path>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
<text y="44" text-anchor="middle" font-size="9" fill="#ffaa00">{{ dryer.t1.toFixed(0) }}/{{ dryer.t2.toFixed(0) }}/{{ dryer.t3.toFixed(0) }}°C</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 夹送辊 / 挤干辊 (两辊上下) -->
|
|
|
|
|
|
<template v-else-if="eq.type==='pinch'">
|
|
|
|
|
|
<rect x="-30" y="-26" width="60" height="52" fill="#1a232c" stroke="#3a4a55" stroke-width="1.5" rx="2"/>
|
|
|
|
|
|
<ellipse cx="0" cy="-12" rx="22" ry="6" fill="#2a3540" stroke="#7090a8" stroke-width="1.2"/>
|
|
|
|
|
|
<ellipse cx="0" cy="12" rx="22" ry="6" fill="#2a3540" stroke="#7090a8" stroke-width="1.2"/>
|
|
|
|
|
|
<line x1="-22" y1="-12" x2="-22" y2="12" stroke="#5a6a75" stroke-width="1"/>
|
|
|
|
|
|
<line x1="22" y1="-12" x2="22" y2="12" stroke="#5a6a75" stroke-width="1"/>
|
|
|
|
|
|
<text y="44" text-anchor="middle" font-size="9" fill="#b8c4cf">{{ eq.code }}</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 活套坑 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='loop'">
|
|
|
|
|
|
<rect x="-40" y="-26" width="80" height="58" fill="#1a232c" stroke="#3a4a55" stroke-width="2" rx="3"/>
|
|
|
|
|
|
<path d="M -32 -16 Q -20 32 -8 -16 Q 4 32 16 -16 Q 28 32 36 -16" stroke="#00c8ff" stroke-width="1.8" fill="none">
|
|
|
|
|
|
<animate attributeName="opacity" values="0.6;1;0.6" dur="1.6s" repeatCount="indefinite"/>
|
|
|
|
|
|
</path>
|
|
|
|
|
|
<text y="48" text-anchor="middle" font-size="9" fill="#b8c4cf">LOOP</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 三辊张力装置 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='tension3'">
|
|
|
|
|
|
<rect x="-32" y="-26" width="64" height="52" fill="#1a232c" stroke="#3a4a55" stroke-width="1.5" rx="3"/>
|
|
|
|
|
|
<circle cx="-16" cy="-8" r="8" fill="#2a3540" stroke="#7090a8" stroke-width="1.2"/>
|
|
|
|
|
|
<circle cx="16" cy="-8" r="8" fill="#2a3540" stroke="#7090a8" stroke-width="1.2"/>
|
|
|
|
|
|
<circle cx="0" cy="12" r="9" fill="#2a3540" stroke="#7090a8" stroke-width="1.2"/>
|
|
|
|
|
|
<text y="44" text-anchor="middle" font-size="9" fill="#b8c4cf">TEN-3</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 平整机 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='leveler'">
|
|
|
|
|
|
<rect x="-34" y="-26" width="68" height="52" fill="#1a232c" stroke="#3a4a55" stroke-width="2" rx="2"/>
|
|
|
|
|
|
<circle cx="0" cy="-14" r="11" fill="#3a4a55" stroke="#90a0b0" stroke-width="1.4"/>
|
|
|
|
|
|
<circle cx="0" cy="14" r="11" fill="#3a4a55" stroke="#90a0b0" stroke-width="1.4"/>
|
|
|
|
|
|
<line x1="-28" y1="0" x2="-12" y2="0" stroke="#7090a8" stroke-width="1"/>
|
|
|
|
|
|
<line x1="12" y1="0" x2="28" y2="0" stroke="#7090a8" stroke-width="1"/>
|
|
|
|
|
|
<text y="44" text-anchor="middle" font-size="9" fill="#b8c4cf">SPM</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 静电涂油机 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='oiler'">
|
|
|
|
|
|
<rect x="-26" y="-26" width="52" height="52" fill="#1a232c" stroke="#3a4a55" stroke-width="1.5" rx="2"/>
|
|
|
|
|
|
<path d="M 0 -14 L -10 4 L 10 4 Z" fill="#3a4a55" stroke="#90a0b0" stroke-width="1"/>
|
|
|
|
|
|
<g fill="#88ccff">
|
|
|
|
|
|
<circle cx="-6" cy="10" r="1.6">
|
|
|
|
|
|
<animate attributeName="cy" values="6;22;6" dur="1.2s" repeatCount="indefinite"/>
|
|
|
|
|
|
</circle>
|
|
|
|
|
|
<circle cx="0" cy="14" r="1.4">
|
|
|
|
|
|
<animate attributeName="cy" values="8;22;8" dur="1.4s" repeatCount="indefinite"/>
|
|
|
|
|
|
</circle>
|
|
|
|
|
|
<circle cx="6" cy="10" r="1.6">
|
|
|
|
|
|
<animate attributeName="cy" values="6;22;6" dur="1.3s" repeatCount="indefinite"/>
|
|
|
|
|
|
</circle>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
<text y="44" text-anchor="middle" font-size="9" fill="#b8c4cf">EOL</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 卷取机 -->
|
|
|
|
|
|
<template v-else-if="eq.type==='recoiler'">
|
|
|
|
|
|
<circle r="38" fill="#1a232c" stroke="#3a4a55" stroke-width="2"/>
|
|
|
|
|
|
<circle r="22" fill="#0a1218" stroke="#5a6a75" stroke-width="1.5"/>
|
|
|
|
|
|
<circle r="8" fill="#2a3a48" stroke="#5a7090" stroke-width="1"/>
|
|
|
|
|
|
<path d="M-38 0 a38 38 0 0 1 76 0" stroke="#00c8ff" stroke-width="1" fill="none" opacity="0.5">
|
|
|
|
|
|
<animateTransform attributeName="transform" type="rotate" from="360" to="0" dur="3s" repeatCount="indefinite"/>
|
|
|
|
|
|
</path>
|
|
|
|
|
|
<text y="58" text-anchor="middle" font-size="10" fill="#b8c4cf">REC-1</text>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 当前设备高亮光环 -->
|
|
|
|
|
|
<circle v-if="eq.k === currentEquipment.k" r="48" fill="none" stroke="#ffdd44" stroke-width="2" stroke-dasharray="4 4" opacity="0.7">
|
|
|
|
|
|
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="6s" repeatCount="indefinite"/>
|
|
|
|
|
|
</circle>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 焊缝标记 -->
|
|
|
|
|
|
<g :transform="`translate(${weldX}, 160)`">
|
|
|
|
|
|
<circle r="11" fill="#ffdd00" opacity="0.35">
|
|
|
|
|
|
<animate attributeName="r" values="9;22;9" dur="1.0s" repeatCount="indefinite"/>
|
|
|
|
|
|
<animate attributeName="opacity" values="0.7;0.05;0.7" dur="1.0s" repeatCount="indefinite"/>
|
|
|
|
|
|
</circle>
|
|
|
|
|
|
<circle r="6" fill="#ffee44">
|
|
|
|
|
|
<animate attributeName="fill" values="#ffee44;#ff7700;#ffee44" dur="0.6s" repeatCount="indefinite"/>
|
|
|
|
|
|
</circle>
|
|
|
|
|
|
<text y="-18" text-anchor="middle" font-size="11" fill="#ffdd44" font-weight="bold">WELD</text>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 图例 -->
|
|
|
|
|
|
<g transform="translate(20,260)" font-size="10" fill="#8b949e">
|
|
|
|
|
|
<circle cx="6" cy="-3" r="5" fill="#ffee44"/>
|
|
|
|
|
|
<text x="18" y="0">焊缝位置 {{ (weld.position * 100).toFixed(1) }}%</text>
|
|
|
|
|
|
<rect x="160" y="-7" width="12" height="8" fill="#ffaa44" opacity="0.5"/>
|
|
|
|
|
|
<text x="178" y="0">酸洗液</text>
|
|
|
|
|
|
<rect x="230" y="-7" width="12" height="8" fill="#3aa0c8" opacity="0.5"/>
|
|
|
|
|
|
<text x="248" y="0">漂洗水</text>
|
|
|
|
|
|
<circle cx="310" cy="-3" r="5" fill="none" stroke="#ffdd44" stroke-width="1.5" stroke-dasharray="2 2"/>
|
|
|
|
|
|
<text x="322" y="0">当前设备</text>
|
|
|
|
|
|
<text x="420" y="0" fill="#aabbcc">— — 带钢运行方向 →</text>
|
|
|
|
|
|
</g>
|
|
|
|
|
|
</svg>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-06-26 13:57:19 +08:00
|
|
|
|
<!-- 下半: 分段 Tab -->
|
|
|
|
|
|
<div class="card sec-card">
|
|
|
|
|
|
<div class="tab-bar">
|
|
|
|
|
|
<div v-for="t in tabs" :key="t.k" :class="['tab', { active: tab === t.k }]" @click="tab = t.k">
|
|
|
|
|
|
{{ t.label }}
|
|
|
|
|
|
<span class="tab-cnt">{{ t.count }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="tab-spacer"></div>
|
|
|
|
|
|
<span class="hd-cnt">{{ rtItems.length }} 项实时数据</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 入口段 -->
|
|
|
|
|
|
<div v-if="tab === 'entry'" class="pane">
|
|
|
|
|
|
<div class="pane-grid entry-grid">
|
|
|
|
|
|
<!-- 左:在线计划 + 移动按钮 -->
|
|
|
|
|
|
<div class="sub-card">
|
|
|
|
|
|
<div class="sub-header">在线计划 · 入口移动
|
|
|
|
|
|
<span class="ch-badge">在线 {{ onlinePlans.length }} / 生产中 {{ producingPlan ? 1 : 0 }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="sub-body">
|
|
|
|
|
|
<div v-if="producingPlan" class="producing-row">
|
|
|
|
|
|
<span class="badge badge-yellow">生产中</span>
|
|
|
|
|
|
<span class="kv-label">冷卷号</span><span class="kv-value">{{ producingPlan.cold_coil_no || producingPlan.plan_no }}</span>
|
|
|
|
|
|
<span class="kv-label">钢种</span><span class="kv-value">{{ producingPlan.steel_grade || '—' }}</span>
|
|
|
|
|
|
<span class="kv-label">规格</span><span class="kv-value">{{ fmt(producingPlan.product_thickness) }}×{{ fmt(producingPlan.product_width, 0) }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<table class="data-table compact" v-if="onlinePlans.length">
|
|
|
|
|
|
<thead><tr><th>冷卷号</th><th>钢种</th><th>厚度</th><th>宽度</th><th>分卷</th><th>操作</th></tr></thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="p in onlinePlans" :key="p.id">
|
|
|
|
|
|
<td class="td-num">{{ p.cold_coil_no || p.plan_no }}</td>
|
|
|
|
|
|
<td>{{ p.steel_grade || '—' }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmt(p.product_thickness) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fmt(p.product_width, 0) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ p.split_count || 1 }}</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<button class="btn btn-primary btn-sm" :disabled="moving" @click="movePlan(p)">移动 →</button>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
<div v-else-if="!producingPlan" class="td-muted empty-row">暂无在线计划</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 右:入口段实时参数 -->
|
|
|
|
|
|
<div class="sub-card">
|
|
|
|
|
|
<div class="sub-header">入口段实时参数</div>
|
|
|
|
|
|
<div class="sub-body">
|
|
|
|
|
|
<div class="kpi-grid">
|
|
|
|
|
|
<div v-for="it in entryItems" :key="it.k" class="kpi">
|
|
|
|
|
|
<div class="kpi-label">{{ it.label }}</div>
|
|
|
|
|
|
<div class="kpi-value">{{ it.val }}<span class="kpi-unit" v-if="it.unit">{{ it.unit }}</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 酸洗段 -->
|
|
|
|
|
|
<div v-if="tab === 'acid'" class="pane">
|
|
|
|
|
|
<div class="tank-grid">
|
|
|
|
|
|
<div v-for="(a, i) in acid" :key="'a'+i" class="sub-card">
|
|
|
|
|
|
<div class="sub-header acid">{{ i+1 }}# 酸洗槽</div>
|
|
|
|
|
|
<div class="sub-body">
|
|
|
|
|
|
<div class="kpi-grid two">
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">槽温度</div><div class="kpi-value">{{ fix(a.temp,1) }}<span class="kpi-unit">°C</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">罐液位</div><div class="kpi-value">{{ fix(a.level,2) }}<span class="kpi-unit">m</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">槽浓度</div><div class="kpi-value">{{ fix(a.conc,1) }}<span class="kpi-unit">g/L</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">槽电导率</div><div class="kpi-value">{{ fix(a.cond,1) }}<span class="kpi-unit">mS/cm</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">罐浓度</div><div class="kpi-value">{{ fix(a.tank_conc,1) }}<span class="kpi-unit">g/L</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">罐电导率</div><div class="kpi-value">{{ fix(a.tank_cond,1) }}<span class="kpi-unit">mS/cm</span></div></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="sub-card">
|
|
|
|
|
|
<div class="sub-header">酸雾塔 + 冷凝水罐</div>
|
|
|
|
|
|
<div class="sub-body">
|
|
|
|
|
|
<div class="kpi-grid two">
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">酸雾塔 PH</div><div class="kpi-value">{{ fix(acid_mist.ph,2) }}</div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">变频器频率</div><div class="kpi-value">{{ fix(acid_mist.vfd_speed,1) }}<span class="kpi-unit">Hz</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">变频器电流</div><div class="kpi-value">{{ fix(acid_mist.vfd_current,1) }}<span class="kpi-unit">A</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">冷凝罐液位</div><div class="kpi-value">{{ fix(acid_cond.level,2) }}<span class="kpi-unit">m</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">冷凝罐温度</div><div class="kpi-value">{{ fix(acid_cond.temp,1) }}<span class="kpi-unit">°C</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">冷凝电导率</div><div class="kpi-value">{{ fix(acid_cond.cond,1) }}<span class="kpi-unit">μS/cm</span></div></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
</div>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-06-26 13:57:19 +08:00
|
|
|
|
<!-- 漂洗段 -->
|
|
|
|
|
|
<div v-if="tab === 'rinse'" class="pane">
|
|
|
|
|
|
<div class="tank-grid">
|
|
|
|
|
|
<div v-for="(r, i) in rinse" :key="'r'+i" class="sub-card">
|
|
|
|
|
|
<div class="sub-header rinse">{{ i+1 }}# 漂洗</div>
|
|
|
|
|
|
<div class="sub-body">
|
|
|
|
|
|
<div class="kpi-grid two">
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">槽温度</div><div class="kpi-value">{{ fix(rinse_tank_temp[i],1) }}<span class="kpi-unit">°C</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">罐液位</div><div class="kpi-value">{{ fix(r.level,2) }}<span class="kpi-unit">m</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">槽浓度</div><div class="kpi-value">{{ fix(r.conc,2) }}<span class="kpi-unit">g/L</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">槽电导率</div><div class="kpi-value">{{ fix(r.cond,2) }}<span class="kpi-unit">μS/cm</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">罐浓度</div><div class="kpi-value">{{ fix(r.tank_conc,2) }}<span class="kpi-unit">g/L</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">罐电导率</div><div class="kpi-value">{{ fix(r.tank_cond,2) }}<span class="kpi-unit">μS/cm</span></div></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="sub-card">
|
|
|
|
|
|
<div class="sub-header">漂洗酸雾塔 + 冷凝罐 + 烘干</div>
|
|
|
|
|
|
<div class="sub-body">
|
|
|
|
|
|
<div class="kpi-grid two">
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">雾塔 PH</div><div class="kpi-value">{{ fix(rinse_mist.ph,2) }}</div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">变频器频率</div><div class="kpi-value">{{ fix(rinse_mist.vfd_speed,1) }}<span class="kpi-unit">Hz</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">变频器电流</div><div class="kpi-value">{{ fix(rinse_mist.vfd_current,1) }}<span class="kpi-unit">A</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">冷凝液位</div><div class="kpi-value">{{ fix(rinse_cond.level,2) }}<span class="kpi-unit">m</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">冷凝温度</div><div class="kpi-value">{{ fix(rinse_cond.temp,1) }}<span class="kpi-unit">°C</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">冷凝电导率</div><div class="kpi-value">{{ fix(rinse_cond.cond,2) }}<span class="kpi-unit">μS/cm</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">烘干 1 段</div><div class="kpi-value">{{ fix(dryer.t1,0) }}<span class="kpi-unit">°C</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">烘干 2 段</div><div class="kpi-value">{{ fix(dryer.t2,0) }}<span class="kpi-unit">°C</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">烘干 3 段</div><div class="kpi-value">{{ fix(dryer.t3,0) }}<span class="kpi-unit">°C</span></div></div>
|
|
|
|
|
|
</div>
|
2026-06-20 18:19:06 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-06-26 13:57:19 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 出口段 -->
|
|
|
|
|
|
<div v-if="tab === 'exit'" class="pane">
|
|
|
|
|
|
<div class="pane-grid two">
|
|
|
|
|
|
<div class="sub-card">
|
|
|
|
|
|
<div class="sub-header">三辊张力装置</div>
|
|
|
|
|
|
<div class="sub-body">
|
|
|
|
|
|
<table class="data-table compact">
|
|
|
|
|
|
<thead><tr><th>变频器</th><th>速度 m/min</th><th>电流 A</th><th>扭矩 kN·m</th></tr></thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="(v, i) in tension_vfd" :key="i">
|
|
|
|
|
|
<td>VFD-{{ i+1 }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fix(v.speed,1) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fix(v.current,0) }}</td>
|
|
|
|
|
|
<td class="td-num">{{ fix(v.torque,2) }}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="sub-card">
|
|
|
|
|
|
<div class="sub-header">平整 + 涂油 + 收卷</div>
|
|
|
|
|
|
<div class="sub-body">
|
|
|
|
|
|
<div class="kpi-grid two">
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">平整辊缝</div><div class="kpi-value">{{ fix(leveler.gap,2) }}<span class="kpi-unit">mm</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">轧制力</div><div class="kpi-value">{{ fix(leveler.force,0) }}<span class="kpi-unit">kN</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">延伸率</div><div class="kpi-value">{{ fix(leveler.elongation,2) }}<span class="kpi-unit">%</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">收卷张力</div><div class="kpi-value">{{ fix(recoiler.tension,1) }}<span class="kpi-unit">kN</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">收卷直径</div><div class="kpi-value">{{ fix(recoiler.diameter,0) }}<span class="kpi-unit">mm</span></div></div>
|
|
|
|
|
|
<div class="kpi"><div class="kpi-label">收卷速度</div><div class="kpi-value">{{ fix(recoiler.speed,1) }}<span class="kpi-unit">m/min</span></div></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 跟踪表 -->
|
|
|
|
|
|
<div v-if="tab === 'track'" class="pane">
|
|
|
|
|
|
<table class="data-table compact tracking-table">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th style="width:32px;">#</th>
|
|
|
|
|
|
<th>设备</th>
|
|
|
|
|
|
<th style="width:64px;">状态</th>
|
|
|
|
|
|
<th>当前钢卷</th>
|
|
|
|
|
|
<th style="width:80px;">辊缝 mm</th>
|
|
|
|
|
|
<th style="width:78px;">速度</th>
|
|
|
|
|
|
<th style="width:80px;">张力/温度</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="(eq, i) in equipments" :key="eq.k"
|
|
|
|
|
|
:class="{ 'row-active': eq.k === currentEquipment.k, 'row-passed': i < currentEquipment.idx, 'row-pending': i > currentEquipment.idx }">
|
|
|
|
|
|
<td class="td-num">{{ i + 1 }}</td>
|
|
|
|
|
|
<td>{{ eq.label }}</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<span v-if="eq.k === currentEquipment.k" class="badge badge-yellow">加工中</span>
|
|
|
|
|
|
<span v-else-if="i < currentEquipment.idx" class="badge badge-blue">已过</span>
|
|
|
|
|
|
<span v-else class="badge badge-gray">待入</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="td-num">{{ rowOf(eq, i).coil }}</td>
|
|
|
|
|
|
<td class="td-num">{{ rowOf(eq, i).gap }}</td>
|
|
|
|
|
|
<td class="td-num">{{ rowOf(eq, i).speed }}</td>
|
|
|
|
|
|
<td class="td-num">{{ rowOf(eq, i).aux }}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2026-06-21 23:42:22 +08:00
|
|
|
|
import { getPlans, startProducing } from '@/api'
|
2026-06-20 18:19:06 +08:00
|
|
|
|
function rnd(base, amp) { return base + (Math.random() - 0.5) * amp }
|
|
|
|
|
|
function fix(v, n = 1) { return Number(v).toFixed(n) }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
|
2026-06-20 18:19:06 +08:00
|
|
|
|
const EQUIPMENTS = [
|
|
|
|
|
|
{ k:'uncoiler', label:'开卷机', type:'coiler', code:'DC-1' },
|
|
|
|
|
|
{ k:'straightener', label:'九辊矫直机', type:'rolls9', code:'STR-9' },
|
|
|
|
|
|
{ k:'crop_shear', label:'切头剪', type:'shear', code:'CRP' },
|
|
|
|
|
|
{ k:'acid1', label:'酸洗槽1', type:'acid', idx:0 },
|
|
|
|
|
|
{ k:'acid2', label:'酸洗槽2', type:'acid', idx:1 },
|
|
|
|
|
|
{ k:'acid3', label:'酸洗槽3', type:'acid', idx:2 },
|
|
|
|
|
|
{ k:'acid4', label:'酸洗槽4', type:'acid', idx:3 },
|
|
|
|
|
|
{ k:'acid5', label:'酸洗槽5', type:'acid', idx:4 },
|
|
|
|
|
|
{ k:'rinse', label:'漂洗段', type:'rinse' },
|
|
|
|
|
|
{ k:'dryer', label:'热风烘干段', type:'dryer' },
|
|
|
|
|
|
{ k:'br1', label:'1号夹送辊', type:'pinch', code:'BR-1' },
|
|
|
|
|
|
{ k:'loop', label:'活套坑', type:'loop' },
|
|
|
|
|
|
{ k:'br2', label:'2号夹送辊', type:'pinch', code:'BR-2' },
|
|
|
|
|
|
{ k:'br3', label:'3号夹送辊', type:'pinch', code:'BR-3' },
|
|
|
|
|
|
{ k:'tension', label:'三辊张力装置', type:'tension3', code:'TEN-3' },
|
|
|
|
|
|
{ k:'leveler', label:'平整机', type:'leveler', code:'SPM' },
|
|
|
|
|
|
{ k:'tail_shear', label:'切尾剪', type:'shear', code:'TLS' },
|
|
|
|
|
|
{ k:'oiler', label:'静电涂油机', type:'oiler', code:'EOL' },
|
|
|
|
|
|
{ k:'recoiler', label:'卷取机', type:'recoiler', code:'REC-1' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 默认辊缝值 (mm)
|
|
|
|
|
|
const DEFAULT_GAP = {
|
|
|
|
|
|
straightener: 4.20,
|
|
|
|
|
|
br1: 3.80, br2: 3.80, br3: 3.80,
|
|
|
|
|
|
tension: 4.00,
|
|
|
|
|
|
leveler: 3.50,
|
2026-05-27 16:38:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'Material',
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
2026-06-20 18:19:06 +08:00
|
|
|
|
l1Online: false,
|
|
|
|
|
|
current: { coil_no: '26053552', speed: 95.0 },
|
|
|
|
|
|
prev_coil_no: '26053551',
|
|
|
|
|
|
weld: { position: 0.08 },
|
|
|
|
|
|
|
|
|
|
|
|
uncoiler: { tension: 18.5, speed: 92.0, current: 240, torque: 1.8, diameter: 1450 },
|
|
|
|
|
|
straightener: { speed: 92.0, current: 165, torque: 1.5, gap: 4.20 },
|
|
|
|
|
|
br1: { speed: 92.0, current: 145, torque: 1.3, gap: 3.80 },
|
|
|
|
|
|
br2: { speed: 92.0, current: 142, torque: 1.3, gap: 3.80 },
|
|
|
|
|
|
br3: { speed: 92.0, current: 140, torque: 1.3, gap: 3.80 },
|
|
|
|
|
|
tension_vfd: [
|
|
|
|
|
|
{ speed: 92.0, current: 158, torque: 1.6 },
|
|
|
|
|
|
{ speed: 92.0, current: 156, torque: 1.5 },
|
|
|
|
|
|
{ speed: 92.0, current: 154, torque: 1.5 },
|
|
|
|
|
|
],
|
|
|
|
|
|
tension_gap: 4.00,
|
|
|
|
|
|
leveler: { gap: 3.50, force: 280, elongation: 0.45 },
|
|
|
|
|
|
recoiler: { tension: 22.4, diameter: 980, speed: 95 },
|
|
|
|
|
|
|
|
|
|
|
|
acid: [
|
|
|
|
|
|
{ temp: 82, conc: 198, level: 0.97, cond: 215, tank_conc: 195, tank_cond: 210 },
|
|
|
|
|
|
{ temp: 81, conc: 188, level: 1.03, cond: 205, tank_conc: 185, tank_cond: 200 },
|
|
|
|
|
|
{ temp: 81, conc: 175, level: 0.94, cond: 192, tank_conc: 172, tank_cond: 188 },
|
|
|
|
|
|
{ temp: 80, conc: 162, level: 0.74, cond: 178, tank_conc: 158, tank_cond: 175 },
|
|
|
|
|
|
{ temp: 74, conc: 148, level: 0.71, cond: 162, tank_conc: 145, tank_cond: 160 },
|
|
|
|
|
|
],
|
|
|
|
|
|
acid_mist: { ph: 6.8, vfd_speed: 48.5, vfd_current: 32.6 },
|
|
|
|
|
|
acid_cond: { level: 1.85, temp: 42.5, cond: 12.5 },
|
|
|
|
|
|
|
|
|
|
|
|
rinse_tank_temp: [65, 62, 58, 54, 48],
|
|
|
|
|
|
rinse: [
|
|
|
|
|
|
{ conc: 0.5, cond: 18.5, level: 0.45, tank_conc: 0.4, tank_cond: 17.5 },
|
|
|
|
|
|
{ conc: 0.3, cond: 12.2, level: 0.54, tank_conc: 0.3, tank_cond: 11.8 },
|
|
|
|
|
|
{ conc: 0.2, cond: 6.8, level: 0.18, tank_conc: 0.2, tank_cond: 6.5 },
|
|
|
|
|
|
{ conc: 0.1, cond: 2.5, level: 0.77, tank_conc: 0.1, tank_cond: 2.4 },
|
|
|
|
|
|
{ conc: 0.0, cond: 0.8, level: 0.81, tank_conc: 0.0, tank_cond: 0.7 },
|
|
|
|
|
|
],
|
|
|
|
|
|
rinse_mist: { ph: 7.0, vfd_speed: 45.2, vfd_current: 28.4 },
|
|
|
|
|
|
rinse_cond: { level: 2.10, temp: 38.6, cond: 4.5 },
|
|
|
|
|
|
|
|
|
|
|
|
dryer: { t1: 145, t2: 168, t3: 152 },
|
|
|
|
|
|
|
|
|
|
|
|
_timer: null,
|
2026-06-21 23:42:22 +08:00
|
|
|
|
_plansTimer: null,
|
|
|
|
|
|
plans: [],
|
|
|
|
|
|
moving: false,
|
2026-06-26 13:57:19 +08:00
|
|
|
|
tab: 'entry',
|
2026-05-27 16:38:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-06-20 18:19:06 +08:00
|
|
|
|
computed: {
|
2026-06-26 13:57:19 +08:00
|
|
|
|
tabs() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ k: 'entry', label: '入口段', count: this.onlinePlans.length },
|
|
|
|
|
|
{ k: 'acid', label: '酸洗段', count: 5 },
|
|
|
|
|
|
{ k: 'rinse', label: '漂洗段', count: 5 },
|
|
|
|
|
|
{ k: 'exit', label: '出口段', count: 3 },
|
|
|
|
|
|
{ k: 'track', label: '物料跟踪表', count: this.equipments.length },
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
entryItems() {
|
|
|
|
|
|
const f = (v, n=1) => Number(v).toFixed(n)
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ k:'u_t', label:'开卷机张力', val: f(this.uncoiler.tension, 1), unit:'kN' },
|
|
|
|
|
|
{ k:'p_s', label:'工艺段速度', val: f(this.current.speed, 1), unit:'m/min' },
|
|
|
|
|
|
{ k:'u_s', label:'开卷机速度', val: f(this.uncoiler.speed, 1), unit:'m/min' },
|
|
|
|
|
|
{ k:'u_d', label:'开卷机卷径', val: f(this.uncoiler.diameter, 0),unit:'mm' },
|
|
|
|
|
|
{ k:'u_c', label:'开卷机电流', val: f(this.uncoiler.current, 0), unit:'A' },
|
|
|
|
|
|
{ k:'u_q', label:'开卷机扭矩', val: f(this.uncoiler.torque, 2), unit:'kN·m' },
|
|
|
|
|
|
{ k:'st_s',label:'九辊矫直机 速度', val: f(this.straightener.speed, 1), unit:'m/min' },
|
|
|
|
|
|
{ k:'st_g',label:'九辊矫直机 辊缝', val: f(this.straightener.gap, 2), unit:'mm' },
|
|
|
|
|
|
{ k:'st_c',label:'九辊矫直机 电流', val: f(this.straightener.current, 0),unit:'A' },
|
|
|
|
|
|
{ k:'st_q',label:'九辊矫直机 扭矩', val: f(this.straightener.torque, 2), unit:'kN·m' },
|
|
|
|
|
|
{ k:'b1s', label:'1号夹送辊 速度', val: f(this.br1.speed, 1), unit:'m/min' },
|
|
|
|
|
|
{ k:'b1g', label:'1号夹送辊 辊缝', val: f(this.br1.gap, 2), unit:'mm' },
|
|
|
|
|
|
{ k:'b1c', label:'1号夹送辊 电流', val: f(this.br1.current, 0), unit:'A' },
|
|
|
|
|
|
{ k:'b1q', label:'1号夹送辊 扭矩', val: f(this.br1.torque, 2), unit:'kN·m' },
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
2026-06-21 23:42:22 +08:00
|
|
|
|
onlinePlans() { return this.plans.filter(p => p.status === 'online') },
|
|
|
|
|
|
producingPlan() { return this.plans.find(p => p.status === 'producing') || null },
|
2026-06-20 18:19:06 +08:00
|
|
|
|
equipments() {
|
|
|
|
|
|
const n = EQUIPMENTS.length
|
|
|
|
|
|
const xStart = 50, xEnd = 1850
|
|
|
|
|
|
const step = (xEnd - xStart) / (n - 1)
|
|
|
|
|
|
return EQUIPMENTS.map((e, i) => ({ ...e, x: xStart + step * i }))
|
2026-05-27 16:38:40 +08:00
|
|
|
|
},
|
2026-06-20 18:19:06 +08:00
|
|
|
|
weldX() {
|
|
|
|
|
|
const p = Math.max(0, Math.min(1, this.weld.position))
|
|
|
|
|
|
return 50 + (1850 - 50) * p
|
2026-05-27 16:38:40 +08:00
|
|
|
|
},
|
2026-06-20 18:19:06 +08:00
|
|
|
|
currentEquipment() {
|
|
|
|
|
|
const n = this.equipments.length
|
|
|
|
|
|
const idx = Math.max(0, Math.min(n - 1, Math.floor(this.weld.position * n)))
|
|
|
|
|
|
return { ...this.equipments[idx], idx }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
},
|
2026-06-20 18:19:06 +08:00
|
|
|
|
rtItems() {
|
|
|
|
|
|
const items = []
|
|
|
|
|
|
const push = (k, label, val, unit) => items.push({ k, label, val, unit })
|
|
|
|
|
|
|
|
|
|
|
|
push('u_t', '开卷机 开卷张力', fix(this.uncoiler.tension, 1), 'kN')
|
|
|
|
|
|
push('u_s', '开卷机 速度反馈', fix(this.uncoiler.speed, 1), 'm/min')
|
|
|
|
|
|
push('u_c', '开卷机 电流反馈', fix(this.uncoiler.current, 0), 'A')
|
|
|
|
|
|
push('u_q', '开卷机 扭矩反馈', fix(this.uncoiler.torque, 2), 'kN·m')
|
|
|
|
|
|
|
|
|
|
|
|
push('st_s', '九辊矫直机 速度反馈',fix(this.straightener.speed, 1), 'm/min')
|
|
|
|
|
|
push('st_c', '九辊矫直机 电流反馈',fix(this.straightener.current, 0), 'A')
|
|
|
|
|
|
push('st_q', '九辊矫直机 扭矩反馈',fix(this.straightener.torque, 2), 'kN·m')
|
|
|
|
|
|
|
|
|
|
|
|
for (const [k, name] of [['br1','1号夹送辊'], ['br2','2号夹送辊'], ['br3','3号夹送辊']]) {
|
|
|
|
|
|
push(k+'_s', `${name} 速度反馈`, fix(this[k].speed, 1), 'm/min')
|
|
|
|
|
|
push(k+'_c', `${name} 电流反馈`, fix(this[k].current, 0), 'A')
|
|
|
|
|
|
push(k+'_q', `${name} 扭矩反馈`, fix(this[k].torque, 2), 'kN·m')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.tension_vfd.forEach((v, i) => {
|
|
|
|
|
|
push(`tv${i}s`, `三辊张力 变频器${i+1} 速度反馈`, fix(v.speed, 1), 'm/min')
|
|
|
|
|
|
push(`tv${i}c`, `三辊张力 变频器${i+1} 电流反馈`, fix(v.current, 0), 'A')
|
|
|
|
|
|
push(`tv${i}q`, `三辊张力 变频器${i+1} 扭矩反馈`, fix(v.torque, 2), 'kN·m')
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
push('r_t', '收卷机 收卷张力', fix(this.recoiler.tension, 1), 'kN')
|
|
|
|
|
|
|
|
|
|
|
|
this.acid.forEach((a, i) => {
|
2026-06-26 13:57:19 +08:00
|
|
|
|
push(`at${i}`, `酸洗${i+1}# 槽温度`, fix(a.temp, 1), '°C')
|
2026-06-20 18:19:06 +08:00
|
|
|
|
push(`al${i}`, `酸洗${i+1}# 罐液位`, fix(a.level, 2), 'm')
|
|
|
|
|
|
push(`ac${i}`, `酸洗${i+1}# 槽浓度`, fix(a.conc, 1), 'g/L')
|
|
|
|
|
|
push(`ae${i}`, `酸洗${i+1}# 槽电导率`, fix(a.cond, 1), 'mS/cm')
|
|
|
|
|
|
push(`atc${i}`,`酸洗${i+1}# 罐浓度`, fix(a.tank_conc, 1), 'g/L')
|
|
|
|
|
|
push(`ate${i}`,`酸洗${i+1}# 罐电导率`, fix(a.tank_cond, 1), 'mS/cm')
|
|
|
|
|
|
})
|
|
|
|
|
|
push('amp', '酸雾塔 PH', fix(this.acid_mist.ph, 2), '')
|
|
|
|
|
|
push('ams', '酸雾塔 变频器频率', fix(this.acid_mist.vfd_speed, 1), 'Hz')
|
|
|
|
|
|
push('amc', '酸雾塔 变频器电流', fix(this.acid_mist.vfd_current,1),'A')
|
|
|
|
|
|
push('acl', '酸侧冷凝水罐 液位', fix(this.acid_cond.level, 2), 'm')
|
|
|
|
|
|
push('act', '酸侧冷凝水罐 温度', fix(this.acid_cond.temp, 1), '°C')
|
|
|
|
|
|
push('acc', '酸侧冷凝水罐 电导率', fix(this.acid_cond.cond, 1), 'μS/cm')
|
|
|
|
|
|
|
|
|
|
|
|
this.rinse.forEach((r, i) => {
|
|
|
|
|
|
const t = this.rinse_tank_temp[i]
|
2026-06-26 13:57:19 +08:00
|
|
|
|
push(`rt${i}`, `漂洗${i+1}# 槽温度`, fix(t, 1), '°C')
|
2026-06-20 18:19:06 +08:00
|
|
|
|
push(`rl${i}`, `漂洗${i+1}# 罐液位`, fix(r.level, 2), 'm')
|
|
|
|
|
|
push(`rc${i}`, `漂洗${i+1}# 槽浓度`, fix(r.conc, 2), 'g/L')
|
|
|
|
|
|
push(`re${i}`, `漂洗${i+1}# 槽电导率`, fix(r.cond, 2), 'μS/cm')
|
|
|
|
|
|
push(`rtc${i}`,`漂洗${i+1}# 罐浓度`, fix(r.tank_conc, 2), 'g/L')
|
|
|
|
|
|
push(`rte${i}`,`漂洗${i+1}# 罐电导率`, fix(r.tank_cond, 2), 'μS/cm')
|
|
|
|
|
|
})
|
|
|
|
|
|
push('rmp', '漂洗酸雾塔 PH', fix(this.rinse_mist.ph, 2), '')
|
|
|
|
|
|
push('rms', '漂洗酸雾塔 变频器频率', fix(this.rinse_mist.vfd_speed, 1), 'Hz')
|
|
|
|
|
|
push('rmc', '漂洗酸雾塔 变频器电流', fix(this.rinse_mist.vfd_current,1),'A')
|
|
|
|
|
|
push('rcl', '漂洗冷凝水罐 液位', fix(this.rinse_cond.level, 2), 'm')
|
|
|
|
|
|
push('rct', '漂洗冷凝水罐 温度', fix(this.rinse_cond.temp, 1), '°C')
|
|
|
|
|
|
push('rcc', '漂洗冷凝水罐 电导率', fix(this.rinse_cond.cond, 2), 'μS/cm')
|
|
|
|
|
|
|
|
|
|
|
|
push('lvg', '平整机 辊缝', fix(this.leveler.gap, 2), 'mm')
|
|
|
|
|
|
push('lvf', '平整机 轧制力', fix(this.leveler.force, 0), 'kN')
|
|
|
|
|
|
push('lve', '平整机 延伸率', fix(this.leveler.elongation,2), '%')
|
|
|
|
|
|
|
|
|
|
|
|
push('dt1','烘干1段温度', fix(this.dryer.t1, 0), '°C')
|
|
|
|
|
|
push('dt2','烘干2段温度', fix(this.dryer.t2, 0), '°C')
|
|
|
|
|
|
push('dt3','烘干3段温度', fix(this.dryer.t3, 0), '°C')
|
|
|
|
|
|
|
|
|
|
|
|
return items
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
2026-06-21 23:42:22 +08:00
|
|
|
|
fmt(v, n = 2) { return v != null && v !== '' ? Number(v).toFixed(n) : '—' },
|
2026-06-26 13:57:19 +08:00
|
|
|
|
fix(v, n = 1) { return Number(v).toFixed(n) },
|
2026-06-21 23:42:22 +08:00
|
|
|
|
fmtTime(t) { return t ? t.slice(0, 16).replace('T', ' ') : '—' },
|
|
|
|
|
|
async loadPlans() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getPlans({ page: 1, page_size: 50 })
|
|
|
|
|
|
this.plans = res.data.items || []
|
|
|
|
|
|
// 把生产中的卷号同步到产线显示
|
|
|
|
|
|
if (this.producingPlan && this.producingPlan.cold_coil_no) {
|
|
|
|
|
|
this.current.coil_no = this.producingPlan.cold_coil_no
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) { /* ignore */ }
|
|
|
|
|
|
},
|
|
|
|
|
|
async movePlan(p) {
|
|
|
|
|
|
if (this.moving) return
|
|
|
|
|
|
this.moving = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
await startProducing(p.id)
|
|
|
|
|
|
this.$message && this.$message.success(`已开始生产 ${p.cold_coil_no || p.plan_no}`)
|
|
|
|
|
|
await this.loadPlans()
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
this.$message && this.$message.error('移动失败')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.moving = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-06-20 18:19:06 +08:00
|
|
|
|
// 一行的展示数据:根据设备状态决定卷号/速度/辊缝/辅助列
|
|
|
|
|
|
rowOf(eq, i) {
|
|
|
|
|
|
const curIdx = this.currentEquipment.idx
|
|
|
|
|
|
const isHere = i === curIdx
|
|
|
|
|
|
const passed = i < curIdx
|
|
|
|
|
|
const cur = this.current.coil_no || '—'
|
|
|
|
|
|
const prev = this.prev_coil_no || '—'
|
|
|
|
|
|
|
|
|
|
|
|
let coil = '—'
|
|
|
|
|
|
if (isHere) coil = cur
|
|
|
|
|
|
else if (passed) coil = cur // 已被本卷穿过
|
|
|
|
|
|
else coil = prev // 还在上一卷尾部
|
|
|
|
|
|
|
|
|
|
|
|
const speed = (isHere || passed) ? this.current.speed.toFixed(1) : prev !== '—' ? '0.0' : '—'
|
|
|
|
|
|
|
|
|
|
|
|
let gap = '—'
|
|
|
|
|
|
let aux = '—'
|
|
|
|
|
|
switch (eq.type) {
|
|
|
|
|
|
case 'coiler':
|
|
|
|
|
|
gap = '—'
|
|
|
|
|
|
aux = this.uncoiler.tension.toFixed(1) + ' kN'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'recoiler':
|
|
|
|
|
|
gap = '—'
|
|
|
|
|
|
aux = this.recoiler.tension.toFixed(1) + ' kN'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'rolls9':
|
|
|
|
|
|
gap = this.straightener.gap.toFixed(2)
|
|
|
|
|
|
aux = this.straightener.torque.toFixed(2) + ' kN·m'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'pinch':
|
|
|
|
|
|
gap = this[eq.k].gap.toFixed(2)
|
|
|
|
|
|
aux = this[eq.k].torque.toFixed(2) + ' kN·m'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'tension3':
|
|
|
|
|
|
gap = this.tension_gap.toFixed(2)
|
|
|
|
|
|
aux = this.tension_vfd[0].torque.toFixed(2) + ' kN·m'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'leveler':
|
|
|
|
|
|
gap = this.leveler.gap.toFixed(2)
|
|
|
|
|
|
aux = this.leveler.force.toFixed(0) + ' kN'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'acid':
|
|
|
|
|
|
gap = '—'
|
|
|
|
|
|
aux = this.acid[eq.idx].temp.toFixed(1) + ' °C'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'rinse':
|
|
|
|
|
|
gap = '—'
|
|
|
|
|
|
aux = this.rinse_tank_temp[0].toFixed(1) + ' °C'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'dryer':
|
|
|
|
|
|
gap = '—'
|
|
|
|
|
|
aux = this.dryer.t2.toFixed(0) + ' °C'
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'shear':
|
|
|
|
|
|
case 'oiler':
|
|
|
|
|
|
case 'loop':
|
|
|
|
|
|
gap = '—'
|
|
|
|
|
|
aux = '—'
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
return { coil, gap, speed, aux }
|
|
|
|
|
|
},
|
|
|
|
|
|
tick() {
|
|
|
|
|
|
this.weld.position = (this.weld.position + 0.012) % 1
|
|
|
|
|
|
// 新一卷开始时滚动卷号
|
|
|
|
|
|
if (this.weld.position < 0.012) {
|
|
|
|
|
|
this.prev_coil_no = this.current.coil_no
|
|
|
|
|
|
const n = parseInt(this.current.coil_no || '26053552', 10) + 1
|
|
|
|
|
|
this.current.coil_no = String(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
this.current.speed = Math.max(0, rnd(this.current.speed, 4))
|
|
|
|
|
|
|
|
|
|
|
|
const wig = (o, key, amp) => { o[key] = rnd(o[key], amp) }
|
|
|
|
|
|
wig(this.uncoiler, 'tension', 0.4); wig(this.uncoiler, 'speed', 2)
|
|
|
|
|
|
wig(this.uncoiler, 'current', 6); wig(this.uncoiler, 'torque', 0.1)
|
|
|
|
|
|
wig(this.straightener, 'speed', 2); wig(this.straightener, 'current', 5)
|
|
|
|
|
|
wig(this.straightener, 'torque', 0.1); wig(this.straightener, 'gap', 0.01)
|
|
|
|
|
|
;['br1','br2','br3'].forEach(k => {
|
|
|
|
|
|
wig(this[k], 'speed', 2); wig(this[k], 'current', 5)
|
|
|
|
|
|
wig(this[k], 'torque', 0.1); wig(this[k], 'gap', 0.01)
|
|
|
|
|
|
})
|
|
|
|
|
|
this.tension_vfd.forEach(v => { wig(v, 'speed', 2); wig(v, 'current', 5); wig(v, 'torque', 0.1) })
|
|
|
|
|
|
this.tension_gap = rnd(this.tension_gap, 0.01)
|
|
|
|
|
|
wig(this.leveler, 'gap', 0.005); wig(this.leveler, 'force', 8); wig(this.leveler, 'elongation', 0.02)
|
|
|
|
|
|
wig(this.recoiler, 'tension', 0.4)
|
|
|
|
|
|
this.acid.forEach(a => {
|
|
|
|
|
|
wig(a, 'temp', 0.3); wig(a, 'conc', 1); wig(a, 'cond', 0.8); wig(a, 'level', 0.02)
|
|
|
|
|
|
wig(a, 'tank_conc', 1); wig(a, 'tank_cond', 0.8)
|
|
|
|
|
|
})
|
|
|
|
|
|
wig(this.acid_mist, 'ph', 0.05); wig(this.acid_mist, 'vfd_speed', 0.6); wig(this.acid_mist, 'vfd_current', 0.4)
|
|
|
|
|
|
wig(this.acid_cond, 'level', 0.02); wig(this.acid_cond, 'temp', 0.3); wig(this.acid_cond, 'cond', 0.2)
|
|
|
|
|
|
this.rinse.forEach(r => {
|
|
|
|
|
|
wig(r, 'conc', 0.05); wig(r, 'cond', 0.3); wig(r, 'level', 0.02)
|
|
|
|
|
|
wig(r, 'tank_conc', 0.05); wig(r, 'tank_cond', 0.3)
|
|
|
|
|
|
})
|
|
|
|
|
|
for (let i = 0; i < this.rinse_tank_temp.length; i++) this.rinse_tank_temp[i] = rnd(this.rinse_tank_temp[i], 0.4)
|
|
|
|
|
|
wig(this.rinse_mist, 'ph', 0.05); wig(this.rinse_mist, 'vfd_speed', 0.6); wig(this.rinse_mist, 'vfd_current', 0.4)
|
|
|
|
|
|
wig(this.rinse_cond, 'level', 0.02); wig(this.rinse_cond, 'temp', 0.3); wig(this.rinse_cond, 'cond', 0.1)
|
|
|
|
|
|
wig(this.dryer, 't1', 2); wig(this.dryer, 't2', 2); wig(this.dryer, 't3', 2)
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {
|
|
|
|
|
|
this.tick()
|
|
|
|
|
|
this._timer = setInterval(this.tick, 2000)
|
2026-06-21 23:42:22 +08:00
|
|
|
|
this.loadPlans()
|
|
|
|
|
|
this._plansTimer = setInterval(this.loadPlans, 10000)
|
2026-06-20 18:19:06 +08:00
|
|
|
|
},
|
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
|
if (this._timer) clearInterval(this._timer)
|
2026-06-21 23:42:22 +08:00
|
|
|
|
if (this._plansTimer) clearInterval(this._plansTimer)
|
2026-06-20 18:19:06 +08:00
|
|
|
|
},
|
2026-05-27 16:38:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
@import '@/assets/styles/variables';
|
|
|
|
|
|
|
2026-06-20 18:19:06 +08:00
|
|
|
|
.mat-page { display: flex; flex-direction: column; gap: 10px; }
|
|
|
|
|
|
|
|
|
|
|
|
.status-bar {
|
|
|
|
|
|
display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
background: $bg-card; border: 1px solid $border; border-radius: 6px;
|
2026-05-27 16:38:40 +08:00
|
|
|
|
}
|
2026-06-20 18:19:06 +08:00
|
|
|
|
.status-item { display: flex; align-items: center; gap: 6px; font-size: 12px; }
|
|
|
|
|
|
.status-item .kv-label { color: $text-muted; font-size: 11px; }
|
|
|
|
|
|
.status-item .kv-value { color: $sms-highlight; font-weight: 600; }
|
|
|
|
|
|
.status-item .kv-unit { color: $text-muted; font-size: 10px; margin-left: 2px; }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
|
2026-06-20 18:19:06 +08:00
|
|
|
|
.line-wrap { padding: 0; }
|
|
|
|
|
|
.line-body { padding: 6px 10px 10px; background: #0a1218; }
|
|
|
|
|
|
.line-svg { width: 100%; height: 280px; display: block; }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
|
2026-06-26 13:57:19 +08:00
|
|
|
|
.sec-card { padding: 0; }
|
|
|
|
|
|
.tab-bar {
|
|
|
|
|
|
display: flex; align-items: center; gap: 2px;
|
|
|
|
|
|
border-bottom: 1px solid $border; padding: 0 10px;
|
|
|
|
|
|
background: #161d24;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tab {
|
|
|
|
|
|
padding: 9px 16px; font-size: 12.5px; cursor: pointer;
|
|
|
|
|
|
color: $text-muted; border-bottom: 2px solid transparent;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tab:hover { color: #c8d4e0; }
|
|
|
|
|
|
.tab.active { color: $sms-highlight; border-bottom-color: $sms-highlight; font-weight: 600; }
|
|
|
|
|
|
.tab-cnt {
|
|
|
|
|
|
display: inline-block; margin-left: 5px;
|
|
|
|
|
|
background: rgba(255,255,255,.06); border-radius: 9px;
|
|
|
|
|
|
padding: 0 6px; font-size: 10px; color: #9aa8b6;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tab.active .tab-cnt { background: rgba(0,200,255,.15); color: #00c8ff; }
|
|
|
|
|
|
.tab-spacer { flex: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
.pane { padding: 10px 12px; }
|
|
|
|
|
|
.pane-grid { display: grid; grid-template-columns: 1fr 1.4fr; gap: 10px; }
|
|
|
|
|
|
.pane-grid.two { grid-template-columns: 1fr 1fr; }
|
|
|
|
|
|
.entry-grid { grid-template-columns: 1fr 1.3fr; }
|
|
|
|
|
|
.tank-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; }
|
|
|
|
|
|
|
|
|
|
|
|
.sub-card { background: #0f161c; border: 1px solid $border; border-radius: 4px; display: flex; flex-direction: column; }
|
|
|
|
|
|
.sub-header {
|
|
|
|
|
|
padding: 6px 10px; font-size: 12px; color: #c8d4e0;
|
|
|
|
|
|
background: #161d24; border-bottom: 1px solid $border;
|
|
|
|
|
|
display: flex; align-items: center; gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.sub-header.acid { color: #ffaa44; }
|
|
|
|
|
|
.sub-header.rinse { color: #3aa0c8; }
|
|
|
|
|
|
.sub-body { padding: 8px 10px; flex: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
.kpi-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px 10px; }
|
|
|
|
|
|
.kpi-grid.two { grid-template-columns: repeat(2, 1fr); }
|
|
|
|
|
|
.kpi { display: flex; flex-direction: column; gap: 2px; }
|
|
|
|
|
|
.kpi-label { color: #8b9aab; font-size: 10.5px; }
|
|
|
|
|
|
.kpi-value {
|
|
|
|
|
|
color: #00c8ff; font-family: monospace; font-size: 14px; font-weight: 600;
|
|
|
|
|
|
background: #0a1218; border: 1px solid #2a3540; border-radius: 3px;
|
|
|
|
|
|
padding: 3px 8px; text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
.kpi-unit { color: #6b7c8d; font-size: 10px; font-weight: 400; margin-left: 4px; }
|
2026-06-21 23:42:22 +08:00
|
|
|
|
|
2026-06-26 13:57:19 +08:00
|
|
|
|
.producing-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
|
|
|
|
|
|
padding: 6px 4px 8px; font-size: 12px;
|
|
|
|
|
|
border-bottom: 1px dashed $border; margin-bottom: 6px;
|
|
|
|
|
|
.kv-label { color: $text-muted; font-size: 11px; margin-left: 4px; }
|
2026-06-21 23:42:22 +08:00
|
|
|
|
.kv-value { color: $sms-highlight; font-weight: 600; }
|
|
|
|
|
|
}
|
2026-06-26 13:57:19 +08:00
|
|
|
|
.empty-row { text-align: center; padding: 10px; font-size: 12px; }
|
2026-06-21 23:42:22 +08:00
|
|
|
|
.btn-sm { padding: 2px 10px; font-size: 11px; }
|
2026-06-26 13:57:19 +08:00
|
|
|
|
.hd-cnt { font-size: 11px; color: #6b7c8d; font-weight: 400; }
|
2026-06-20 18:19:06 +08:00
|
|
|
|
|
|
|
|
|
|
.data-table.compact th, .data-table.compact td { padding: 5px 8px; font-size: 11.5px; }
|
|
|
|
|
|
.tracking-table tr.row-active { background: rgba(255, 221, 68, 0.10); }
|
|
|
|
|
|
.tracking-table tr.row-active td { color: #ffdd44 !important; font-weight: 600; }
|
|
|
|
|
|
.tracking-table tr.row-passed td { color: #6b8aaa; }
|
|
|
|
|
|
.tracking-table tr.row-pending td { color: #5a6a78; }
|
2026-05-27 16:38:40 +08:00
|
|
|
|
</style>
|