feat: L2过程跟踪UI压缩

This commit is contained in:
砂糖
2026-02-24 11:19:52 +08:00
parent 4e058c344f
commit cca1e9dc15

View File

@@ -1,16 +1,173 @@
<template>
<div class="track-container">
<div>
<!-- Set Values Floating Panel / 设定值悬浮窗 -->
<FloatingPanel ref="setValuesPanel" title="Set Values" width="720px" storageKey="TRACK_SET_VALUES"> <!-- 设定值 -->
<LatestSetValues :driveData="setupValue.drive" :furnaceData="setupValue.furnace" />
</FloatingPanel>
<FloatingPanel ref="furCurrentPanel" title="Furnace Real-time Parameters" width="400px" storageKey="TRACK_FUR_CURRENT"> <!-- 炉火实时参数 -->
<FloatingPanel ref="furCurrentPanel" title="Furnace Real-time Parameters" width="400px"
storageKey="TRACK_FUR_CURRENT"> <!-- 炉火实时参数 -->
<FurCurrent :driveData="realtimeData.furnace" />
</FloatingPanel>
<el-row :gutter="20">
<el-row :gutter="6" v-loading="isLoading">
<!-- 左侧设备列表 -->
<el-col :span="16">
<div v-if="!isLoading" class="device-layout">
<!-- Entry Section / 入口段区域 -->
<div class="section-area entry-area">
<div class="section-header">
Entry Section <!-- 入口段 -->
<span class="section-info" v-if="positionData.entrySpeed">Speed: {{ positionData.entrySpeed.toFixed(1) }}
m/min</span> <!-- 速度 -->
</div>
<div class="section-summary" v-if="entrySectionMetrics.length">
<div class="summary-item" v-for="item in entrySectionMetrics" :key="item.label">
<span class="summary-label">{{ item.label }}</span>
<span class="summary-value">{{ item.value }}</span>
</div>
</div>
<div class="device-grid">
<div v-for="device in entryDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-status">
<div class="device-code">{{ device.positionNameEn }}</div>
<span class="coil-id"
:class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
<!-- Furnace Section / 熔炉段区域 -->
<div class="section-area furnace-area">
<div class="section-header">
Furnace Section <!-- 熔炉段 -->
<span class="section-info" v-if="positionData.technologySpeed">Speed: {{
positionData.technologySpeed.toFixed(1) }} m/min</span> <!-- 速度 -->
<el-button type="text" size="small" @click="openFurCurrentPanel">
<i class="el-icon-paperclip"></i>
More <!-- 更多 -->
</el-button>
</div>
<div class="section-summary" v-if="furnaceSectionMetrics.length">
<div class="summary-item" v-for="item in furnaceSectionMetrics" :key="item.label">
<span class="summary-label">{{ item.label }}</span>
<span class="summary-value">{{ item.value }}</span>
</div>
</div>
<div class="device-grid">
<div v-for="device in furnaceDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-status">
<div class="device-code">{{ device.positionNameEn }}</div>
<span class="coil-id"
:class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
<!-- Coating Section / 涂层段区域 -->
<div class="section-area coat-area">
<div class="section-header">
Coating Section <!-- 涂层段 -->
<span class="section-info" v-if="positionData.coatSpeed">Speed: {{ positionData.coatSpeed.toFixed(1) }}
m/min</span> <!-- 速度 -->
</div>
<div class="section-summary" v-if="coatSectionMetrics.length">
<div class="summary-item" v-for="item in coatSectionMetrics" :key="item.label">
<span class="summary-label">{{ item.label }}</span>
<span class="summary-value">{{ item.value }}</span>
</div>
</div>
<div class="device-grid">
<div v-for="device in coatDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-status">
<div class="device-code">{{ device.positionNameEn }}</div>
<span class="coil-id"
:class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
<!-- Exit Section / 出口段区域 -->
<div class="section-area exit-area">
<div class="section-header">
Exit Section <!-- 出口段 -->
<span class="section-info" v-if="positionData.exitSpeed">Speed: {{ positionData.exitSpeed.toFixed(1) }}
m/min</span> <!-- 速度 -->
</div>
<div class="section-summary" v-if="exitSectionMetrics.length">
<div class="summary-item" v-for="item in exitSectionMetrics" :key="item.label">
<span class="summary-label">{{ item.label }}</span>
<span class="summary-value">{{ item.value }}</span>
</div>
</div>
<div class="device-grid">
<div v-for="device in exitDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-status">
<div class="device-code">{{ device.positionNameEn }}</div>
<span class="coil-id"
:class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
<!-- Other Section / 其他段区域 -->
<!-- <div class="section-area" v-if="exitOtherDevicesList.length">
<div class="section-header">
Other Section
</div>
<div class="device-grid other-exit-grid">
<div v-for="device in exitOtherDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-status">
<div class="device-code">{{ device.positionNameEn }}</div>
<span class="coil-id"
:class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div> -->
</div>
</el-col>
<!-- 右侧信息面板 -->
<el-col :span="8">
<!-- Loading Status / 加载状态 -->
<div v-if="isLoading" class="loading-container">
<el-icon class="is-loading"><i class="el-icon-loading"></i></el-icon>
@@ -23,9 +180,9 @@
<div class="ws-status-left">
<!-- Set Values Floating Window Trigger / 设定值悬浮窗触发按钮 -->
<el-tooltip content="Set Values" placement="top"> <!-- 设定值 -->
<el-button type="text" class="ws-open-btn" @click="openSetValuesPanel">
<i class="el-icon-setting"></i>
</el-button>
<!-- <el-button type="text" class="ws-open-btn" @click="openSetValuesPanel"> -->
<i class="el-icon-setting" @click="openSetValuesPanel"></i>
<!-- </el-button> -->
</el-tooltip>
<el-tooltip content="Measurement Data" placement="top"> <!-- 测量数据 -->
<el-badge :is-dot="true" :type="socketStatus.measure ? 'success' : 'danger'">
@@ -56,153 +213,12 @@
<!-- Right: quick links -->
<div class="ws-status-right">
<el-button type="primary" size="small" @click="$router.push('/furnace')">Furnace Settings</el-button> <!-- 炉火设置 -->
<el-button type="primary" size="small" @click="$router.push('/drive')">Drive Settings</el-button> <!-- 传动设置 -->
<el-button type="primary" size="small" @click="$router.push('/furnace')">Furnace Settings</el-button>
<!-- 炉火设置 -->
<el-button type="primary" size="small" @click="$router.push('/drive')">Drive Settings</el-button>
<!-- 传动设置 -->
</div>
</div>
<div v-if="!isLoading" class="device-layout">
<!-- Entry Section / 入口段区域 -->
<div class="section-area entry-area">
<div class="section-header">
Entry Section <!-- 入口段 -->
<span class="section-info" v-if="positionData.entrySpeed">Speed: {{ positionData.entrySpeed.toFixed(1) }} m/min</span> <!-- 速度 -->
</div>
<div class="section-summary" v-if="entrySectionMetrics.length">
<div class="summary-item" v-for="item in entrySectionMetrics" :key="item.label">
<span class="summary-label">{{ item.label }}</span>
<span class="summary-value">{{ item.value }}</span>
</div>
</div>
<div class="device-grid">
<div v-for="device in entryDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-code">{{ device.positionNameEn }}</div>
<div class="device-status">
<span class="coil-id" :class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
<!-- Furnace Section / 熔炉段区域 -->
<div class="section-area furnace-area">
<div class="section-header">
Furnace Section <!-- 熔炉段 -->
<span class="section-info" v-if="positionData.technologySpeed">Speed: {{ positionData.technologySpeed.toFixed(1) }} m/min</span> <!-- 速度 -->
<el-button type="text" size="small" @click="openFurCurrentPanel">
<i class="el-icon-paperclip"></i>
More <!-- 更多 -->
</el-button>
</div>
<div class="section-summary" v-if="furnaceSectionMetrics.length">
<div class="summary-item" v-for="item in furnaceSectionMetrics" :key="item.label">
<span class="summary-label">{{ item.label }}</span>
<span class="summary-value">{{ item.value }}</span>
</div>
</div>
<div class="device-grid">
<div v-for="device in furnaceDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-code">{{ device.positionNameEn }}</div>
<div class="device-status">
<span class="coil-id" :class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
<!-- Coating Section / 涂层段区域 -->
<div class="section-area coat-area">
<div class="section-header">
Coating Section <!-- 涂层段 -->
<span class="section-info" v-if="positionData.coatSpeed">Speed: {{ positionData.coatSpeed.toFixed(1) }} m/min</span> <!-- 速度 -->
</div>
<div class="section-summary" v-if="coatSectionMetrics.length">
<div class="summary-item" v-for="item in coatSectionMetrics" :key="item.label">
<span class="summary-label">{{ item.label }}</span>
<span class="summary-value">{{ item.value }}</span>
</div>
</div>
<div class="device-grid">
<div v-for="device in coatDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-code">{{ device.positionNameEn }}</div>
<div class="device-status">
<span class="coil-id" :class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
<!-- Exit Section / 出口段区域 -->
<div class="section-area exit-area">
<div class="section-header">
Exit Section <!-- 出口段 -->
<span class="section-info" v-if="positionData.exitSpeed">Speed: {{ positionData.exitSpeed.toFixed(1) }} m/min</span> <!-- 速度 -->
</div>
<div class="section-summary" v-if="exitSectionMetrics.length">
<div class="summary-item" v-for="item in exitSectionMetrics" :key="item.label">
<span class="summary-label">{{ item.label }}</span>
<span class="summary-value">{{ item.value }}</span>
</div>
</div>
<div class="device-grid">
<div v-for="device in exitDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-code">{{ device.positionNameEn }}</div>
<div class="device-status">
<span class="coil-id" :class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
<!-- Other Section / 其他段区域 -->
<div class="section-area" v-if="exitOtherDevicesList.length">
<div class="section-header">
Other Section <!-- 其他段 -->
</div>
<div class="device-grid other-exit-grid">
<div v-for="device in exitOtherDevicesList" :key="device.positionNameEn" class="device-card" :class="{
active: selectedCard && selectedCard.positionNameEn === device.positionNameEn,
'has-data': hasMatId(device.positionNameEn)
}" @click="selectDevice(device.positionNameEn)">
<div class="device-name">{{ device.positionNameCn }}</div>
<div class="device-code">{{ device.positionNameEn }}</div>
<div class="device-status">
<span class="coil-id" :class="{ 'status-working': isDeviceWorking(device.positionNameEn), 'status-idle': !isDeviceWorking(device.positionNameEn) }">
{{ getDeviceStatus(device.positionNameEn) }}
</span>
</div>
</div>
</div>
</div>
</div>
</el-col>
<!-- 右侧信息面板 -->
<el-col :span="8">
<div class="info-panels">
<!-- Production Plan List / 生产计划列表 -->
<div class="panel">
@@ -210,22 +226,18 @@
<i class="el-icon-s-order"></i> Production Plan <!-- 生产计划 -->
</div>
<div class="plan-list-vertical">
<el-empty v-if="planQueue.length === 0" description="No production plan" :image-size="80" /> <!-- 暂无生产计划 -->
<div
v-else
v-for="(plan, index) in planQueue"
:key="plan.id || plan.planid || index"
class="plan-item-vertical"
:class="getPlanItemClass(plan)"
@click="selectPlan(plan)"
>
<el-empty v-if="planQueue.length === 0" description="No production plan" :image-size="80" />
<!-- 暂无生产计划 -->
<div v-else v-for="(plan, index) in planQueue" :key="plan.id || plan.planid || index"
class="plan-item-vertical" :class="getPlanItemClass(plan)" @click="selectPlan(plan)">
<div class="plan-item-top">
<div class="plan-order-dot" :class="getPlanOrderClass(plan.status)">{{ index + 1 }}</div>
<div class="plan-title-text">
<div class="plan-id">Plan ID: {{ plan.planid || '-' }}</div> <!-- 计划号 -->
<div class="plan-coil">Coil ID: {{ plan.coilid || '-' }}</div> <!-- 钢卷号 -->
</div>
<el-tag :type="getPlanStatusTagType(plan.status)" size="mini">{{ getPlanStatusText(plan.status) }}</el-tag>
<el-tag :type="getPlanStatusTagType(plan.status)" size="mini">{{ getPlanStatusText(plan.status)
}}</el-tag>
</div>
<div class="plan-item-bottom">
<span>Steel Grade: {{ plan.steelGrade || '-' }}</span> <!-- 钢种 -->
@@ -256,15 +268,19 @@
</div>
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="Plan ID">{{ selectedPlan.planid || '-' }}</el-descriptions-item> <!-- 计划号 -->
<el-descriptions-item label="Coil ID">{{ selectedPlan.coilid || '-' }}</el-descriptions-item> <!-- 钢卷 -->
<el-descriptions-item label="Sequence No">{{ selectedPlan.seqid || '-' }}</el-descriptions-item> <!-- 顺序号 -->
<el-descriptions-item label="Plan ID">{{ selectedPlan.planid || '-' }}</el-descriptions-item>
<!-- 计划 -->
<el-descriptions-item label="Coil ID">{{ selectedPlan.coilid || '-' }}</el-descriptions-item>
<!-- 钢卷号 -->
<el-descriptions-item label="Sequence No">{{ selectedPlan.seqid || '-' }}</el-descriptions-item>
<!-- 顺序号 -->
<el-descriptions-item label="Status"> <!-- 状态 -->
<el-tag :type="getPlanStatusTagType(selectedPlan.status)" size="small" effect="dark">
{{ getPlanStatusText(selectedPlan.status) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="Steel Grade">{{ selectedPlan.steelGrade || '-' }}</el-descriptions-item> <!-- 钢种 -->
<el-descriptions-item label="Steel Grade">{{ selectedPlan.steelGrade || '-' }}</el-descriptions-item>
<!-- 钢种 -->
<el-descriptions-item label="Entry Thickness"> <!-- 入口厚度 -->
{{ selectedPlan.entryThick ? selectedPlan.entryThick + ' mm' : '-' }}
</el-descriptions-item>
@@ -277,13 +293,17 @@
<el-descriptions-item label="Entry Length"> <!-- 入口长度 -->
{{ selectedPlan.entryLength ? selectedPlan.entryLength + ' mm' : '-' }}
</el-descriptions-item>
<el-descriptions-item label="Order No">{{ selectedPlan.orderNo || '-' }}</el-descriptions-item> <!-- 订单号 -->
<el-descriptions-item label="Unit Code">{{ selectedPlan.unitCode || '-' }}</el-descriptions-item> <!-- 机组 -->
<el-descriptions-item label="Plan Type">{{ selectedPlan.planType || '-' }}</el-descriptions-item> <!-- 计划类型 -->
<el-descriptions-item label="Order No">{{ selectedPlan.orderNo || '-' }}</el-descriptions-item>
<!-- 订单 -->
<el-descriptions-item label="Unit Code">{{ selectedPlan.unitCode || '-' }}</el-descriptions-item>
<!-- 机组号 -->
<el-descriptions-item label="Plan Type">{{ selectedPlan.planType || '-' }}</el-descriptions-item>
<!-- 计划类型 -->
</el-descriptions>
<!-- Time Information / 时间信息 -->
<div class="plan-time-info" v-if="selectedPlan.onlineDate || selectedPlan.startDate || selectedPlan.endDate">
<div class="plan-time-info"
v-if="selectedPlan.onlineDate || selectedPlan.startDate || selectedPlan.endDate">
<div class="info-subtitle">Time Information</div> <!-- 时间信息 -->
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="Online Time" v-if="selectedPlan.onlineDate"> <!-- 上线时间 -->
@@ -305,10 +325,7 @@
<div class="panel-title">
<i class="el-icon-bell"></i> Recent Operation
<!-- 最近操作 -->
<el-tag
:type="getOperationTagType(signalData.operation)"
size="mini"
effect="dark">
<el-tag :type="getOperationTagType(signalData.operation)" size="mini" effect="dark">
{{ getOperationConfig(signalData.operation).icon }} {{ getOperationText(signalData.operation) }}
</el-tag>
</div>
@@ -320,8 +337,8 @@
<div class="signal-detail">
<div><strong>Operation Type:</strong> <!-- 操作类型 -->
<span :class="getOperationTextClass(signalData.operation)">
{{ getOperationText(signalData.operation) }}
</span>
{{ getOperationText(signalData.operation) }}
</span>
</div>
<div><strong>Coil ID:</strong> {{ producingCoilId || signalData.entryMatId || '-' }}</div> <!-- 钢卷号 -->
<div><strong>Plan ID:</strong> {{ signalData.planId || '-' }}</div> <!-- 计划ID -->
@@ -341,13 +358,16 @@
<div class="panel">
<div class="panel-title">Operation Buttons</div> <!-- 操作按钮 -->
<div class="btn-list">
<el-button class="action-btn" size="small" @click="handleOperate(selectedCard, 'ONLINE')">Coil Online</el-button> <!-- 钢卷上线 -->
<el-button class="action-btn" size="small" @click="handleOperate(selectedCard, 'UNLOAD')">Manual Unload</el-button> <!-- 手动卸卷 -->
<el-button class="action-btn" size="small"
@click="handleOperate(selectedCard, 'ALL_RETURN')">Full Return</el-button> <!-- 整卷回退 -->
<el-button class="action-btn" size="small"
@click="handleOperate(selectedCard, 'HALF_RETURN')">Half Return</el-button> <!-- 卷回退 -->
<el-button class="action-btn" size="small" @click="handleOperate(selectedCard, 'BLOCK')">Unload & Block</el-button> <!-- 卸卷并封闭 -->
<el-button class="action-btn" size="small" @click="handleOperate(selectedCard, 'ONLINE')">Coil
Online</el-button> <!-- 钢卷上线 -->
<el-button class="action-btn" size="small" @click="handleOperate(selectedCard, 'UNLOAD')">Manual
Unload</el-button> <!-- 手动卸卷 -->
<el-button class="action-btn" size="small" @click="handleOperate(selectedCard, 'ALL_RETURN')">Full
Return</el-button> <!-- 卷回退 -->
<el-button class="action-btn" size="small" @click="handleOperate(selectedCard, 'HALF_RETURN')">Half
Return</el-button> <!-- 半卷回退 -->
<el-button class="action-btn" size="small" @click="handleOperate(selectedCard, 'BLOCK')">Unload &
Block</el-button> <!-- 卸卷并封闭 -->
</div>
</div>
<!-- Device Basic Info / 设备基本信息 -->
@@ -384,44 +404,22 @@
<!-- Adjustment Tool: Select two positions with two dropdowns / 调整工具选择两个位置两个下拉选分别双向绑定 -->
<el-form :model="adjustForm" ref="adjustForm" label-width="120px" class="adjust-form">
<el-form-item label="Current Position" prop="current"> <!-- 当前位置 -->
<el-select
v-model="adjustForm.current"
placeholder="Please select current position"
class="adjust-select"
filterable
> <!-- 请选择当前位置 -->
<el-option
v-for="item in matMapList"
:key="item.positionNameEn"
:label="item.positionNameCn"
:value="item.positionNameEn"
class="adjust-option"
></el-option>
<el-select v-model="adjustForm.current" placeholder="Please select current position"
class="adjust-select" filterable> <!-- 请选择当前位置 -->
<el-option v-for="item in matMapList" :key="item.positionNameEn" :label="item.positionNameCn"
:value="item.positionNameEn" class="adjust-option"></el-option>
</el-select>
</el-form-item>
<el-form-item label="Target Position" prop="target"> <!-- 目标位置 -->
<el-select
v-model="adjustForm.target"
placeholder="Please select target position"
class="adjust-select"
filterable
> <!-- 请选择目标位置 -->
<el-option
v-for="item in matMapList"
:key="item.positionNameEn"
:label="item.positionNameCn"
:value="item.positionNameEn"
class="adjust-option"
></el-option>
<el-select v-model="adjustForm.target" placeholder="Please select target position" class="adjust-select"
filterable> <!-- 请选择目标位置 -->
<el-option v-for="item in matMapList" :key="item.positionNameEn" :label="item.positionNameCn"
:value="item.positionNameEn" class="adjust-option"></el-option>
</el-select>
</el-form-item>
</el-form>
<el-button
type="primary"
:disabled="!adjustForm.current || !adjustForm.target"
@click="handleConfirmAdjust"
class="adjust-confirm-btn"
>Confirm Adjustment</el-button> <!-- 确认调整 -->
<el-button type="primary" :disabled="!adjustForm.current || !adjustForm.target" @click="handleConfirmAdjust"
class="adjust-confirm-btn">Confirm Adjustment</el-button> <!-- 确认调整 -->
</div>
</div>
@@ -429,25 +427,17 @@
</el-row>
<!-- Calculation Setup Result Dialog / 计算结果对话框 -->
<el-dialog
title="Calculation Result"
:visible.sync="showCalcResultDialog"
width="80%"
v-if="calcSetupResult"
>
<el-dialog title="Calculation Result" :visible.sync="showCalcResultDialog" width="80%" v-if="calcSetupResult">
<div class="calc-result-header" v-if="calcSetupResult">
<el-tag :type="calcSetupResult && calcSetupResult.flag ? 'success' : 'danger'">
{{ calcSetupResult && calcSetupResult.flag ? 'Calculation Successful' : 'Calculation Failed' }} <!-- 计算成功 / 计算失败 -->
{{ calcSetupResult && calcSetupResult.flag ? 'Calculation Successful' : 'Calculation Failed' }}
<!-- 计算成功 / 计算失败 -->
</el-tag>
<span>Key: {{ calcSetupResult && calcSetupResult.key ? calcSetupResult.key : '-' }}</span>
</div>
<el-table
v-if="calcSetupResult && calcSetupResult.flag && calcSetupResult.lists && calcSetupResult.lists.length > 0"
:data="calcSetupResult.lists"
border
stripe
max-height="500"
>
:data="calcSetupResult.lists" border stripe max-height="500">
<el-table-column prop="passno" label="Pass No" width="80" fixed></el-table-column> <!-- 道次号 -->
<el-table-column prop="entryThick" label="Entry Thickness (mm)" width="120"></el-table-column> <!-- 入口厚度 -->
<el-table-column prop="exitThick" label="Exit Thickness (mm)" width="120"></el-table-column> <!-- 出口厚度 -->
@@ -487,10 +477,12 @@
<!-- Return Related Fields / 回退相关字段 -->
<template v-if="['ALL_RETURN', 'HALF_RETURN'].includes(operateMatForm.operation)">
<el-form-item label="Return Coil ID" prop="returnMatId"> <!-- 回退钢卷号 -->
<el-input v-model="operateMatForm.returnMatId" placeholder="Please enter Return Coil ID"></el-input> <!-- 请输入回退卷号 -->
<el-input v-model="operateMatForm.returnMatId" placeholder="Please enter Return Coil ID"></el-input>
<!-- 请输入回退卷号 -->
</el-form-item>
<el-form-item label="Return Weight" prop="returnWeight"> <!-- 回退重量 -->
<el-input v-model="operateMatForm.returnWeight" placeholder="Please enter Return Weight"></el-input> <!-- 请输入回退重量 -->
<el-input v-model="operateMatForm.returnWeight" placeholder="Please enter Return Weight"></el-input>
<!-- 请输入回退重量 -->
</el-form-item>
<el-form-item label="Return Remark" prop="returnRemark"> <!-- 回退备注 -->
<el-input v-model="operateMatForm.returnRemark" rows="3"></el-input>
@@ -500,7 +492,8 @@
<!-- Output Length Fields / 产出长度字段 -->
<template v-if="['PRODUCING', 'PRODUCT'].includes(operateMatForm.operation)">
<el-form-item label="Output Coil Length" prop="coilLength"> <!-- 产出钢卷长度 -->
<el-input v-model="operateMatForm.coilLength" type="number" placeholder="Please enter Output Coil Length"></el-input> <!-- 请输入产出钢卷长度 -->
<el-input v-model="operateMatForm.coilLength" type="number"
placeholder="Please enter Output Coil Length"></el-input> <!-- 请输入产出钢卷长度 -->
</el-form-item>
</template>
</el-form>
@@ -541,15 +534,17 @@ const DEVICE_META = {
TOWER: { sectionType: 'PROCESS', sourceType: 'COAT', paramFields: ['scsExitStripTemp'] },
TM: { sectionType: 'PROCESS', sourceType: 'COAT', paramFields: ['tensionBr5Tm', 'stripSpeedTmExit'] },
TL: { sectionType: 'PROCESS', sourceType: 'COAT', paramFields: ['tlElongation', 'tensionTlBr7'] },
COAT: { sectionType: 'PROCESS', sourceType: 'COAT', paramFields: [
'avrCoatingWeightTop','stdCoatingWeightTop','maxCoatingWeightTop','minCoatingWeightTop',
'avrCoatingWeightBottom','stdCoatingWeightBottom','maxCoatingWeightBottom','minCoatingWeightBottom',
'airKnifePressure','airKnifeFlow','airKnifeGap','stripSpeedTmExit','tensionBr8Tm',
'tensionTmBr9','tensionBr8Br9','tmMask','tmElongation','rollForceOperator','rollForceDrive',
'motorTorque','bendingForce','antiCrimpingRollMesh','billyRollMesh',
'tensionTlBr10Br11','tensionBr9toBr10Br11','tlFlag','tlElongation','levelingUnit1Mesh','levelingUnit2Mesh',
'antiCrossBowUnitMesh','tensionBr10Br11toBr12','stripSpeedAfp','stripTempAfp'
] },
COAT: {
sectionType: 'PROCESS', sourceType: 'COAT', paramFields: [
'avrCoatingWeightTop', 'stdCoatingWeightTop', 'maxCoatingWeightTop', 'minCoatingWeightTop',
'avrCoatingWeightBottom', 'stdCoatingWeightBottom', 'maxCoatingWeightBottom', 'minCoatingWeightBottom',
'airKnifePressure', 'airKnifeFlow', 'airKnifeGap', 'stripSpeedTmExit', 'tensionBr8Tm',
'tensionTmBr9', 'tensionBr8Br9', 'tmMask', 'tmElongation', 'rollForceOperator', 'rollForceDrive',
'motorTorque', 'bendingForce', 'antiCrimpingRollMesh', 'billyRollMesh',
'tensionTlBr10Br11', 'tensionBr9toBr10Br11', 'tlFlag', 'tlElongation', 'levelingUnit1Mesh', 'levelingUnit2Mesh',
'antiCrossBowUnitMesh', 'tensionBr10Br11toBr12', 'stripSpeedAfp', 'stripTempAfp'
]
},
CXL1: { sectionType: 'EXIT', sourceType: 'EXIT', paramFields: ['cxlLength', 'cxlCapacity', 'tensionCxl'] },
CXL2: { sectionType: 'EXIT', sourceType: 'EXIT', paramFields: ['cxlLength', 'cxlCapacity', 'tensionCxl'] },
@@ -854,7 +849,7 @@ export default {
entrySectionMetrics() {
return this.buildSectionMetrics('ENTRY', [
'stripSpeed', 'tensionPorBr1', 'tensionBr1Br2', 'tensionBr2Br3', 'celLength', 'celCapacity',
'celLengthMax', 'celLengthMin', 'cleaningCurrent', 'cleaningVoltage',
'celLengthMax', 'celLengthMin', 'cleaningCurrent', 'cleaningVoltage',
'stripLocation', 'rinseTemperature'])
},
furnaceSectionMetrics() {
@@ -868,7 +863,7 @@ export default {
coatSectionMetrics() {
return this.buildSectionMetrics('COAT', [
'stripSpeedTmExit',
'tlElongation', 'tensionBr6toBr7Br8', 'tensionBr8Tm',
'tlElongation', 'tensionBr6toBr7Br8', 'tensionBr8Tm',
'tensionTmBr9'])
},
exitSectionMetrics() {
@@ -896,7 +891,8 @@ export default {
const exitDevices = this.getDevicesBySection('EXIT')
const looper = this.matMapList.find(d => ['CXL1', 'CXL2'].includes(d.positionNameEn))
const base = exitDevices.filter(d => !['INS', 'EXC', 'WEIGHT', 'CXL1', 'CXL2'].includes(d.positionNameEn))
const base = exitDevices
// .filter(d => !['INS', 'EXC', 'WEIGHT', 'CXL1', 'CXL2'].includes(d.positionNameEn))
if (!looper) return base
const tr = this.matMapList.find(d => d.positionNameEn === 'TR')
if (!tr) return base.concat([{ ...looper, positionNameEn: 'CXL', positionNameCn: 'Exit Looper' }])
@@ -1179,7 +1175,7 @@ export default {
processSignalData(data) {
// 先记录是否为连续生产中信号 Record if it's a continuous producing signal
const repeatProducing =this.lastSignalOperation === 'PRODUCING' && data.operation === 'PRODUCING'
const repeatProducing = this.lastSignalOperation === 'PRODUCING' && data.operation === 'PRODUCING'
this.signalData = data
const operationText = this.getOperationText(data.operation)
@@ -1187,13 +1183,13 @@ export default {
const config = this.getOperationConfig(data.operation)
// 检测到上线、生产中、生产完成等关键操作时,刷新生产计划队列 Refresh plan queue when detecting key operations
if (['ONLINE', 'PRODUCING', 'PRODUCT'].includes(data.operation) ) {
if (['ONLINE', 'PRODUCING', 'PRODUCT'].includes(data.operation)) {
console.log(`Detected ${operationText} operation, refreshing plan queue...`) // 检测到${operationText}操作,刷新生产计划队列...
this.scheduleRefreshPlanQueue()
}
// 右上角通知(所有操作都显示) Top-right notification (display for all operations)
if(!repeatProducing){
if (!repeatProducing) {
this.$notify({
title: `${config.icon} ${config.title}`,
message: `${autoFlagText} ${operationText}\nCoil ID: ${data.entryMatId}\nPlan ID: ${data.planId || '-'}`, // 钢卷号 / 计划ID
@@ -1856,17 +1852,16 @@ export default {
.device-layout {
background: #f5f5f5;
border-radius: 4px;
padding: 15px;
height: calc(100vh - 60px);
height: calc(100vh - 84px);
overflow-y: auto;
}
/* 分区区域 */
.section-area {
margin-bottom: 20px;
margin-bottom: 10px;
background: #fafafa;
border-radius: 6px;
padding: 12px;
padding: 6px;
border: 1px solid #e8e8e8;
}
@@ -1874,8 +1869,8 @@ export default {
font-size: 15px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-bottom: 8px;
margin-bottom: 6px;
padding-bottom: 6px;
border-bottom: 2px solid #ddd;
}
@@ -1883,18 +1878,17 @@ export default {
.device-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
gap: 6px;
}
/* 设备卡片 */
.device-card {
background: #fff;
border: 2px solid #e0e0e0;
border-radius: 6px;
padding: 12px;
padding: 6px;
cursor: pointer;
transition: all 0.2s ease;
min-height: 120px;
/* min-height: 100px; */
}
.device-card:hover {
@@ -1933,12 +1927,16 @@ export default {
.device-code {
font-size: 11px;
color: #999;
margin-bottom: 6px;
/* margin-bottom: 6px; */
}
.device-status {
margin-top: 8px;
padding-top: 8px;
display: flex;
/* justify-content: space-between; */
gap: 6px;
align-items: center;
border-top: 1px solid #f0f0f0;
}
@@ -1962,7 +1960,7 @@ export default {
}
.mini-data {
font-size: 11px;
font-size: 10px;
color: #666;
margin-bottom: 3px;
display: flex;
@@ -1980,8 +1978,8 @@ export default {
.section-summary {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 6px;
margin: 8px 0 14px;
}
@@ -1990,7 +1988,7 @@ export default {
border: 1px solid #e0e6ed;
border-radius: 6px;
padding: 10px 12px;
font-size: 13px;
font-size: 10px;
display: flex;
justify-content: space-between;
align-items: center;
@@ -2008,14 +2006,37 @@ export default {
}
/* 段落色系 */
.entry-area .section-header { border-bottom-color: #409eff; }
.entry-area .summary-value { color: #409eff; }
.furnace-area .section-header { border-bottom-color: #e6a23c; }
.furnace-area .summary-value { color: #e6a23c; }
.coat-area .section-header { border-bottom-color: #67c23a; }
.coat-area .summary-value { color: #67c23a; }
.exit-area .section-header { border-bottom-color: #909399; }
.exit-area .summary-value { color: #909399; }
.entry-area .section-header {
border-bottom-color: #409eff;
}
.entry-area .summary-value {
color: #409eff;
}
.furnace-area .section-header {
border-bottom-color: #e6a23c;
}
.furnace-area .summary-value {
color: #e6a23c;
}
.coat-area .section-header {
border-bottom-color: #67c23a;
}
.coat-area .summary-value {
color: #67c23a;
}
.exit-area .section-header {
border-bottom-color: #909399;
}
.exit-area .summary-value {
color: #909399;
}
.panel {
border: 1px solid #ddd;
@@ -2132,8 +2153,13 @@ export default {
}
@keyframes rotating {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 生产计划队列面板 */
@@ -2283,10 +2309,22 @@ export default {
flex-shrink: 0;
}
.plan-order-dot.order-producing { background: #67c23a; animation: blink-green-dot 1.2s infinite; }
.plan-order-dot.order-online { background: #e6a23c; }
.plan-order-dot.order-ready { background: #409eff; }
.plan-order-dot.order-new { background: #909399; }
.plan-order-dot.order-producing {
background: #67c23a;
animation: blink-green-dot 1.2s infinite;
}
.plan-order-dot.order-online {
background: #e6a23c;
}
.plan-order-dot.order-ready {
background: #409eff;
}
.plan-order-dot.order-new {
background: #909399;
}
.plan-title-text {
flex: 1;
@@ -2297,8 +2335,13 @@ export default {
color: #303133;
}
.plan-id { font-weight: 600; }
.plan-coil { color: #606266; }
.plan-id {
font-weight: 600;
}
.plan-coil {
color: #606266;
}
.plan-item-bottom {
display: flex;
@@ -2368,10 +2411,13 @@ export default {
/* 绿色闪烁动画 */
@keyframes pulse-green {
0%, 100% {
0%,
100% {
border-color: #67c23a;
box-shadow: 0 0 0 0 rgba(103, 194, 58, 0.4);
}
50% {
border-color: #85ce61;
box-shadow: 0 0 0 4px rgba(103, 194, 58, 0.6), 0 0 15px rgba(103, 194, 58, 0.4);
@@ -2411,10 +2457,13 @@ export default {
/* 绿色点闪烁动画 */
@keyframes blink-green-dot {
0%, 100% {
0%,
100% {
background: #67c23a;
box-shadow: 0 0 5px rgba(103, 194, 58, 0.5);
}
50% {
background: #85ce61;
box-shadow: 0 0 10px rgba(103, 194, 58, 0.8);