refactor(FurCurrent): 重构参数显示逻辑,优化闪烁效果 style(FurnaceHistoryPanel): 调整表格列宽和溢出处理 feat(LabelPrint): 使用dom-to-image提升打印质量 feat(QualityCertificate): 添加生产过程曲线图表 feat: 新增ParamEcharts组件用于统一图表渲染 refactor(line): 使用ParamEcharts重构监控图表组件
179 lines
5.3 KiB
Vue
179 lines
5.3 KiB
Vue
<template>
|
||
<!-- 炉火参数表单 -->
|
||
<div class="app-container">
|
||
<!-- flex布局容器,开启自动换行 -->
|
||
<div class="params-list">
|
||
<!-- 遍历筛选后的驱动数据键值对 -->
|
||
<div
|
||
v-for="[key, value] in filteredDriveData"
|
||
:key="key"
|
||
:class="['param-item', { blink: blinkKeyMap[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: {}, // 存储上一次的驱动数据,用于对比变化
|
||
blinkKeyMap: {}, // 每个key独立的闪烁状态(对象形式:{ key1: true, key2: false })
|
||
timerMap: {} // 每个key独立的定时器缓存,用于清除旧定时器
|
||
}
|
||
},
|
||
// 筛选包含Actual(不区分大小写)的键值对
|
||
computed: {
|
||
filteredDriveData() {
|
||
// 校验
|
||
if (!this.driveData || typeof this.driveData !== 'object') {
|
||
return [];
|
||
}
|
||
return Object.entries(this.driveData).filter(([key]) => {
|
||
return key.toLowerCase().includes('actual');
|
||
});
|
||
}
|
||
},
|
||
watch: {
|
||
driveData: {
|
||
handler(newVal) {
|
||
if (Object.keys(this.prevDriveData).length === 0) {
|
||
// 首次加载数据,仅缓存数据,不触发闪烁
|
||
this.prevDriveData = JSON.parse(JSON.stringify(newVal));
|
||
return;
|
||
}
|
||
|
||
// 对比新值和旧值,找出变化的属性名
|
||
Object.entries(newVal).forEach(([key, value]) => {
|
||
// 仅对比已有属性的变化,且仅处理筛选后的key(包含Actual)
|
||
if (this.prevDriveData.hasOwnProperty(key) && key.toLowerCase().includes('actual')) {
|
||
if (this.prevDriveData[key] !== value) {
|
||
this.triggerBlink(key); // 为每个变化的key单独触发闪烁
|
||
}
|
||
}
|
||
});
|
||
|
||
// 更新缓存的旧数据
|
||
this.prevDriveData = JSON.parse(JSON.stringify(newVal));
|
||
},
|
||
deep: true
|
||
}
|
||
},
|
||
methods: {
|
||
// 单独触发某个key的闪烁(核心优化:独立控制)
|
||
triggerBlink(key) {
|
||
// 清除该key的旧定时器,避免1秒内多次更新导致定时器叠加
|
||
if (this.timerMap[key]) {
|
||
clearTimeout(this.timerMap[key]);
|
||
}
|
||
|
||
// 设置当前key为闪烁状态(触发动画)
|
||
this.$set(this.blinkKeyMap, key, true);
|
||
|
||
// 单独为该key设置定时器,到期后关闭闪烁状态
|
||
this.timerMap[key] = setTimeout(() => {
|
||
this.$set(this.blinkKeyMap, key, false);
|
||
// 定时器执行后,清除缓存的定时器ID
|
||
delete this.timerMap[key];
|
||
}, 500); // 闪烁时长可自定义(1秒)
|
||
},
|
||
// 格式化标签名(将驼峰命名转为中文式分段,提升可读性)
|
||
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;
|
||
}
|
||
},
|
||
// 组件销毁时清除所有定时器,避免内存泄漏
|
||
beforeUnmount() {
|
||
Object.values(this.timerMap).forEach(timer => {
|
||
clearTimeout(timer);
|
||
});
|
||
}
|
||
}
|
||
</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: 2px 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类添加时,动画重新触发 */
|
||
.blink {
|
||
border: 2px solid #4cd964; /* 绿色边框 */
|
||
animation: borderBlink 0.5s ease-in-out 1; /* 强制执行1次动画,避免叠加 */
|
||
}
|
||
</style> |