feat(跟踪): 添加炉火实时参数组件并优化界面
refactor(登录): 调整登录页logo布局样式 fix(钢种管理): 为详情面板添加加载状态 style(发送历史): 调整表格列宽配置
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 17 KiB |
@@ -19,12 +19,12 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<el-table v-loading="loading" :data="list" border size="mini" height="380">
|
<el-table v-loading="loading" :data="list" border size="mini" height="380">
|
||||||
<el-table-column label="Job ID" prop="jobId" width="90" />
|
<el-table-column label="Job ID" prop="jobId" />
|
||||||
<el-table-column label="Device" prop="deviceName" width="140" />
|
<el-table-column label="Device" prop="deviceName" />
|
||||||
<el-table-column label="Status" prop="status" width="140" />
|
<el-table-column label="Status" prop="status" />
|
||||||
<el-table-column label="Create Time" prop="createTime" width="170" />
|
<el-table-column label="Create Time" prop="createTime" />
|
||||||
<el-table-column label="Finish Time" prop="finishTime" width="170" />
|
<el-table-column label="Finish Time" prop="finishTime" />
|
||||||
<el-table-column label="Action" width="150" fixed="right">
|
<el-table-column label="Action" fixed="right">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button type="text" size="mini" @click="apply(scope.row)">Apply</el-button>
|
<el-button type="text" size="mini" @click="apply(scope.row)">Apply</el-button>
|
||||||
<el-button type="text" size="mini" @click="openDetail(scope.row.jobId)">Detail</el-button>
|
<el-button type="text" size="mini" @click="openDetail(scope.row.jobId)">Detail</el-button>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Detail dialog -->
|
<!-- Detail dialog -->
|
||||||
<el-dialog title="Send Detail" :visible.sync="detailVisible" width="90%" append-to-body>
|
<el-dialog title="Send Detail" :visible.sync="detailVisible" width="90%" append-to-body v-loading="detailLoading">
|
||||||
<div v-if="detail">
|
<div v-if="detail">
|
||||||
<el-tabs type="border-card">
|
<el-tabs type="border-card">
|
||||||
<el-tab-pane
|
<el-tab-pane
|
||||||
@@ -85,7 +85,8 @@ export default {
|
|||||||
groupType: 'FURNACE'
|
groupType: 'FURNACE'
|
||||||
},
|
},
|
||||||
detailVisible: false,
|
detailVisible: false,
|
||||||
detail: null
|
detail: null,
|
||||||
|
detailLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -111,9 +112,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async openDetail(jobId) {
|
async openDetail(jobId) {
|
||||||
|
this.detailVisible = true;
|
||||||
|
this.detailLoading = true
|
||||||
const res = await getSendJob(jobId)
|
const res = await getSendJob(jobId)
|
||||||
this.detail = res.data
|
this.detail = res.data
|
||||||
this.detailVisible = true
|
this.detailLoading = false
|
||||||
},
|
},
|
||||||
async apply(row) {
|
async apply(row) {
|
||||||
const res = await getSendJob(row.jobId)
|
const res = await getSendJob(row.jobId)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: detail panel / 右侧:明细面板 -->
|
<!-- Right: detail panel / 右侧:明细面板 -->
|
||||||
<div class="right-panel">
|
<div class="right-panel" v-loading="rightLoading">
|
||||||
<!-- No selection tip / 未选择卡片提示 -->
|
<!-- No selection tip / 未选择卡片提示 -->
|
||||||
<div class="empty-detail" v-if="!selectedItem && !dialogVisible">
|
<div class="empty-detail" v-if="!selectedItem && !dialogVisible">
|
||||||
<el-empty description="Please select a steel grade from the left to view details"></el-empty>
|
<el-empty description="Please select a steel grade from the left to view details"></el-empty>
|
||||||
@@ -356,6 +356,7 @@ export default {
|
|||||||
btnLoading: false,
|
btnLoading: false,
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
dialogTitle: 'Add Steel Grade', // 新增钢种
|
dialogTitle: 'Add Steel Grade', // 新增钢种
|
||||||
|
rightLoading: false,
|
||||||
formData: {
|
formData: {
|
||||||
gradeid: null,
|
gradeid: null,
|
||||||
name: '',
|
name: '',
|
||||||
@@ -435,11 +436,14 @@ export default {
|
|||||||
// Card click event: load and display details / 卡片点击事件:加载详情并展示
|
// Card click event: load and display details / 卡片点击事件:加载详情并展示
|
||||||
handleCardClick(item) {
|
handleCardClick(item) {
|
||||||
// Call detail API to get complete data / 调用详情接口获取完整数据
|
// Call detail API to get complete data / 调用详情接口获取完整数据
|
||||||
|
this.rightLoading = true
|
||||||
getSteelGradeInfo(item.gradeid).then(res => {
|
getSteelGradeInfo(item.gradeid).then(res => {
|
||||||
this.selectedItem = { ...res.data }
|
this.selectedItem = { ...res.data }
|
||||||
|
this.rightLoading = false
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Fallback: use list data / 降级处理:使用列表数据
|
// Fallback: use list data / 降级处理:使用列表数据
|
||||||
this.selectedItem = { ...item }
|
this.selectedItem = { ...item }
|
||||||
|
this.rightLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleAdd() {
|
handleAdd() {
|
||||||
|
|||||||
155
src/views/l2/track/components/FurCurrent.vue
Normal file
155
src/views/l2/track/components/FurCurrent.vue
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 炉火参数表单 -->
|
||||||
|
<div class="app-container">
|
||||||
|
<h3>炉火实时参数</h3>
|
||||||
|
<!-- flex布局容器,开启自动换行 -->
|
||||||
|
<div class="params-list">
|
||||||
|
<!-- 遍历驱动数据的所有键值对 -->
|
||||||
|
<div
|
||||||
|
v-for="[key, value] in Object.entries(driveData)"
|
||||||
|
:key="key"
|
||||||
|
:class="['param-item', { blink: changedKeys.includes(key) }]"
|
||||||
|
>
|
||||||
|
<!-- 上方label -->
|
||||||
|
<span class="param-label">{{ formatLabel(key) }}</span>
|
||||||
|
<!-- 下方value -->
|
||||||
|
<span class="param-value">{{ formatValue(value) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FurCurrent',
|
||||||
|
props: {
|
||||||
|
driveData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
prevDriveData: {}, // 存储上一次的驱动数据,用于对比变化
|
||||||
|
changedKeys: [] // 存储当前变化的属性名,用于控制闪烁样式
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
driveData: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (Object.keys(this.prevDriveData).length === 0) {
|
||||||
|
// 首次加载数据,仅缓存数据,不触发闪烁
|
||||||
|
this.prevDriveData = JSON.parse(JSON.stringify(newVal));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对比新值和旧值,找出变化的属性名
|
||||||
|
const changedKeys = [];
|
||||||
|
Object.entries(newVal).forEach(([key, value]) => {
|
||||||
|
// 排除首次不存在的属性,仅对比已有属性的变化
|
||||||
|
if (this.prevDriveData.hasOwnProperty(key)) {
|
||||||
|
// 由于是数字类型,直接对比值是否不同
|
||||||
|
if (this.prevDriveData[key] !== value) {
|
||||||
|
changedKeys.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (changedKeys.length > 0) {
|
||||||
|
this.changedKeys = changedKeys; // 设置变化的属性,触发闪烁
|
||||||
|
// 1秒后清空变化的属性,移除闪烁样式
|
||||||
|
setTimeout(() => {
|
||||||
|
this.changedKeys = [];
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新缓存的旧数据
|
||||||
|
this.prevDriveData = JSON.parse(JSON.stringify(newVal));
|
||||||
|
console.log('driveData updated:', newVal, 'changed keys:', changedKeys);
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 格式化标签名(将驼峰命名转为中文式分段,提升可读性)
|
||||||
|
formatLabel(key) {
|
||||||
|
if (!key) return '';
|
||||||
|
// 驼峰命名转空格分隔
|
||||||
|
const result = key.replace(/([A-Z])/g, ' $1');
|
||||||
|
// 首字母大写
|
||||||
|
return result.charAt(0).toUpperCase() + result.slice(1);
|
||||||
|
},
|
||||||
|
// 格式化值(数字类型保留4位小数,提升展示美观度)
|
||||||
|
formatValue(value) {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value.toFixed(4);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* flex布局容器 - 开启换行 */
|
||||||
|
.params-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap; /* flex自动换行核心属性 */
|
||||||
|
gap: 16px; /* 项目之间的间距(水平+垂直) */
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 固定宽度的参数项 - 内部上下布局 */
|
||||||
|
.param-item {
|
||||||
|
width: 200px; /* 固定宽度,可根据需求调整 */
|
||||||
|
padding: 12px 16px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex; /* 内部flex垂直布局 */
|
||||||
|
flex-direction: column; /* 垂直排列:label在上,value在下 */
|
||||||
|
align-items: flex-start; /* 左对齐,可改为center居中 */
|
||||||
|
gap: 8px; /* label和value之间的垂直间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 14px;
|
||||||
|
word-wrap: break-word; /* 超长label自动换行 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-value {
|
||||||
|
color: #666666;
|
||||||
|
font-family: 'Courier New', monospace; /* 等宽字体,数字对齐更美观 */
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 闪烁动画定义 */
|
||||||
|
@keyframes borderBlink {
|
||||||
|
0% {
|
||||||
|
border-color: #4cd964; /* 初始绿色 */
|
||||||
|
box-shadow: 0 0 0 0 rgba(76, 217, 100, 0.2);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-color: #28a745; /* 深绿色 */
|
||||||
|
box-shadow: 0 0 8px 2px rgba(76, 217, 100, 0.5);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
border-color: #4cd964; /* 恢复初始绿色 */
|
||||||
|
box-shadow: 0 0 0 0 rgba(76, 217, 100, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 闪烁样式 */
|
||||||
|
.blink {
|
||||||
|
border: 2px solid #4cd964; /* 绿色边框 */
|
||||||
|
animation: borderBlink 1s ease-in-out; /* 执行闪烁动画 */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="track-container">
|
<div class="track-container">
|
||||||
<!-- Set Values Floating Panel / 设定值悬浮窗 -->
|
<!-- Set Values Floating Panel / 设定值悬浮窗 -->
|
||||||
<FloatingPanel ref="setValuesPanel" title="Set Values" width="720px">
|
<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 Current" width="400px" storageKey="TRACK_FUR_CURRENT">
|
||||||
|
<FurCurrent :driveData="realtimeData.furnace" />
|
||||||
|
</FloatingPanel>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<!-- 左侧:设备列表 -->
|
<!-- 左侧:设备列表 -->
|
||||||
<el-col :span="16">
|
<el-col :span="16">
|
||||||
@@ -101,6 +105,11 @@
|
|||||||
<!-- 熔炉段 -->
|
<!-- 熔炉段 -->
|
||||||
<span class="section-info" v-if="positionData.technologySpeed">Speed: {{ positionData.technologySpeed.toFixed(1) }} m/min</span>
|
<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>
|
||||||
<div class="section-summary" v-if="furnaceSectionMetrics.length">
|
<div class="section-summary" v-if="furnaceSectionMetrics.length">
|
||||||
<div class="summary-item" v-for="item in furnaceSectionMetrics" :key="item.label">
|
<div class="summary-item" v-for="item in furnaceSectionMetrics" :key="item.label">
|
||||||
@@ -572,6 +581,7 @@
|
|||||||
import { adjustPosition, getTrackMatPosition, operateMat, getBackData, getPlanQueue } from '@/api/l2/track'
|
import { adjustPosition, getTrackMatPosition, operateMat, getBackData, getPlanQueue } from '@/api/l2/track'
|
||||||
import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
||||||
import wsManager from '@/utils/websocketManager'
|
import wsManager from '@/utils/websocketManager'
|
||||||
|
import FurCurrent from './components/FurCurrent.vue'
|
||||||
|
|
||||||
// Device metadata mapping based on backend DeviceEnum (section, source, parameter fields)
|
// Device metadata mapping based on backend DeviceEnum (section, source, parameter fields)
|
||||||
// 基于后端 DeviceEnum 的前端映射(区域、来源、参数字段)
|
// 基于后端 DeviceEnum 的前端映射(区域、来源、参数字段)
|
||||||
@@ -695,7 +705,7 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
|||||||
const CLEANING_FIELDS = new Set(['cleaningVoltage', 'cleaningCurrent', 'alkaliConcentration', 'alkaliTemperature'])
|
const CLEANING_FIELDS = new Set(['cleaningVoltage', 'cleaningCurrent', 'alkaliConcentration', 'alkaliTemperature'])
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { FloatingPanel, LatestSetValues },
|
components: { FloatingPanel, LatestSetValues, FurCurrent },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// WebSocket 连接状态
|
// WebSocket 连接状态
|
||||||
@@ -809,7 +819,8 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
|||||||
operateMatStatus: false, // 操作对话框显示状态
|
operateMatStatus: false, // 操作对话框显示状态
|
||||||
returnInfo: {},
|
returnInfo: {},
|
||||||
isLoadingReturn: false,
|
isLoadingReturn: false,
|
||||||
returnError: ''
|
returnError: '',
|
||||||
|
furData: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -875,7 +886,10 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
|||||||
},
|
},
|
||||||
// 分段汇总数据
|
// 分段汇总数据
|
||||||
entrySectionMetrics() {
|
entrySectionMetrics() {
|
||||||
return this.buildSectionMetrics('ENTRY', ['stripSpeed', 'tensionPorBr1', 'tensionPorBr2', 'celLength', 'celCapacity'])
|
return this.buildSectionMetrics('ENTRY', [
|
||||||
|
'stripSpeed', 'tensionPorBr1', 'tensionBr1Br2', 'tensionBr2Br3', 'celLength', 'celCapacity',
|
||||||
|
// 'celLengthMax', 'celLengthMin', 'cleaningCurrent', 'cleaningVoltage',
|
||||||
|
'stripLocation', 'rinseTemperature', 'alkaliTemperature'])
|
||||||
},
|
},
|
||||||
furnaceSectionMetrics() {
|
furnaceSectionMetrics() {
|
||||||
return this.buildSectionMetrics('FURNACE', [
|
return this.buildSectionMetrics('FURNACE', [
|
||||||
@@ -885,7 +899,10 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
|||||||
])
|
])
|
||||||
},
|
},
|
||||||
coatSectionMetrics() {
|
coatSectionMetrics() {
|
||||||
return this.buildSectionMetrics('COAT', ['stripSpeedTmExit', 'avrCoatingWeightTop', 'avrCoatingWeightBottom', 'tmElongation', 'tlElongation'])
|
return this.buildSectionMetrics('COAT', [
|
||||||
|
'stripSpeedTmExit', 'avrCoatingWeightTop', 'avrCoatingWeightBottom',
|
||||||
|
'tmElongation', 'tlElongation', 'tensionBr6toBr7Br8', 'tensionBr8Tm',
|
||||||
|
'tensionTmBr9', 'tlElongation'])
|
||||||
},
|
},
|
||||||
exitSectionMetrics() {
|
exitSectionMetrics() {
|
||||||
return this.buildSectionMetrics('EXIT', ['speedExitSection', 'coilLength', 'cxlLength', 'cxlCapacity', 'tensionBr9Tr'])
|
return this.buildSectionMetrics('EXIT', ['speedExitSection', 'coilLength', 'cxlLength', 'cxlCapacity', 'tensionBr9Tr'])
|
||||||
@@ -947,6 +964,10 @@ import { getDriveSetupValue, getFurnaceSetupValue } from '@/api/l2/setupValue'
|
|||||||
this.$refs.setValuesPanel && this.$refs.setValuesPanel.open()
|
this.$refs.setValuesPanel && this.$refs.setValuesPanel.open()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openFurCurrentPanel() {
|
||||||
|
this.$refs.furCurrentPanel && this.$refs.furCurrentPanel.open()
|
||||||
|
},
|
||||||
|
|
||||||
// Load latest set values (Drive & Furnace) / 加载最新的设定值
|
// Load latest set values (Drive & Furnace) / 加载最新的设定值
|
||||||
async loadSetupValues() {
|
async loadSetupValues() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -191,6 +191,9 @@ export default {
|
|||||||
|
|
||||||
.form-header {
|
.form-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 48px;
|
margin-bottom: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,9 +201,15 @@ export default {
|
|||||||
font-family: 'Pacifico', cursive;
|
font-family: 'Pacifico', cursive;
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
color: white;
|
color: white;
|
||||||
|
width: 150px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.system-name {
|
.system-name {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|||||||
Reference in New Issue
Block a user