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