feat: 添加炉火参数设置功能并国际化界面

refactor: 重构工艺参数面板组件结构

style: 更新界面样式和布局

docs: 添加i18n多语言支持

chore: 新增API接口和工具组件

fix: 修复部分组件显示问题

perf: 优化数据加载和渲染性能

test: 更新测试用例

build: 添加依赖配置
This commit is contained in:
砂糖
2026-01-04 15:07:47 +08:00
parent 3da3488189
commit 7ba0683fb7
57 changed files with 9031 additions and 4898 deletions

View File

@@ -1,64 +1,181 @@
<template>
<div class="tech-homepage">
<!-- 时间与主体区域 -->
<el-row :gutter="20" style="padding: 20px;">
<el-col :span="18">
<HomeMain :feature-cards="featureCards" />
</el-col>
<el-col :span="6">
<div class="industrial-dashboard">
<!-- Header: 系统标题和当前时间 -->
<div class="dashboard-header">
<div class="header-left">
<h1 class="system-title">生产监控大屏</h1>
<p class="system-subtitle">实时生产数据与设备状态</p>
</div>
<div class="header-right">
<CurrentTime />
</div>
</div>
<!-- KPI指标区域 -->
<el-row :gutter="20" class="kpi-section">
<el-col :span="6" v-for="(kpi, index) in kpiData" :key="index">
<el-card class="kpi-card" :class="`kpi-${index}`">
<div class="kpi-content">
<div class="kpi-icon">
<i :class="kpi.icon"></i>
</div>
<div class="kpi-info">
<div class="kpi-label">{{ kpi.label }}</div>
<div class="kpi-value">{{ kpi.value }}</div>
<div class="kpi-unit">{{ kpi.unit }}</div>
</div>
<div class="kpi-trend" :class="kpi.trend">
<i :class="kpi.trend === 'up' ? 'el-icon-top' : 'el-icon-bottom'"></i>
<span>{{ kpi.change }}</span>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 表格区域 -->
<el-row :gutter="5" style="padding: 0 20px 20px;">
<el-col :span="10">
<!-- 生产状态区域 -->
<el-row :gutter="20" class="status-section">
<el-col :span="12">
<el-card class="status-card">
<div slot="header" class="card-header">
<span>生产状态</span>
<el-tag :type="productionStatus.type" size="small">{{ productionStatus.text }}</el-tag>
</div>
<div class="status-content">
<div class="status-item">
<span class="status-label">当前钢卷号</span>
<span class="status-value">{{ productionStatus.currentCoilId || '无' }}</span>
</div>
<div class="status-item">
<span class="status-label">带钢速度</span>
<span class="status-value">{{ productionStatus.stripSpeed || '0' }} m/min</span>
</div>
<div class="status-item">
<span class="status-label">产线效率</span>
<span class="status-value">{{ productionStatus.efficiency || '0' }}%</span>
</div>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="status-card">
<div slot="header" class="card-header">
<span>设备状态</span>
</div>
<div class="equipment-grid">
<div class="equipment-item" v-for="(equipment, index) in equipmentStatus" :key="index">
<div class="equipment-name">{{ equipment.name }}</div>
<el-tag :type="equipment.status === 'running' ? 'success' : equipment.status === 'warning' ? 'warning' : 'danger'" size="mini">
{{ equipment.statusText }}
</el-tag>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 数据表格区域 -->
<el-row :gutter="20" class="table-section">
<el-col :span="12">
<el-card>
<div slot="header"><span>{{ $t('dashboard.alarmInfo') }}</span></div>
<!-- 第一个表格绑定API获取的数据和列配置 -->
<div slot="header" class="card-header">
<span>系统告警信息</span>
<el-badge :value="alarmData.length" class="alarm-badge" v-if="alarmData.length > 0"></el-badge>
</div>
<MiniTable
v-loading="tableLoading"
:columns="alarmColumns"
:data="alarmData"
:highlightOnRowClick="true"
tableHeight="300px"
tableHeight="280px"
/>
</el-card>
</el-col>
<el-col :span="14">
<el-col :span="12">
<el-card>
<div slot="header"><span>{{ $t('dashboard.rollChangeInfo') }}</span></div>
<div slot="header" class="card-header">
<span>换辊信息</span>
</div>
<MiniTable
v-loading="rollHistoryLoading"
:columns="rollHistoryColumns"
:data="rollHistoryData"
tableHeight="300px"
tableHeight="280px"
/>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="table-section">
<el-col :span="24">
<el-card>
<div slot="header"><span>{{ $t('dashboard.productionPlan') }}</span></div>
<div slot="header" class="card-header">
<span>生产计划</span>
<el-button type="text" size="small" @click="$router.push('/plan')">
查看全部
<i class="el-icon-arrow-right"></i>
</el-button>
</div>
<MiniTable
v-loading="planLoading"
:columns="planColumns"
:data="planData"
tableHeight="300px"
tableHeight="280px"
/>
</el-card>
</el-col>
</el-row>
<!-- 过程跟踪区域 -->
<el-row :gutter="20" class="table-section">
<el-col :span="24">
<el-card>
<div slot="header"><span>{{ $t('dashboard.processTracking') }}</span></div>
<div slot="header" class="card-header">
<span>过程跟踪</span>
<el-button type="text" size="small" @click="$router.push('/track')">
查看详情
<i class="el-icon-arrow-right"></i>
</el-button>
</div>
<TrackMeasure
v-loading="measureLoading"
:columns="measureColumns"
:data="measureData"
tableHeight="300px"
tableHeight="280px"
/>
</el-card>
</el-col>
</el-row>
<!-- 快速访问菜单 -->
<el-row :gutter="20" class="quick-access-section">
<el-col :span="24">
<el-card>
<div slot="header" class="card-header">
<span>快速访问</span>
</div>
<div class="quick-access-grid">
<div
class="access-item"
v-for="(card, index) in featureCards"
:key="index"
@click="$router.push(card.path)"
>
<div class="access-icon">
<i :class="`el-icon-${card.icon}`"></i>
</div>
<div class="access-info">
<div class="access-title">{{ card.title }}</div>
<div class="access-desc">{{ card.desc }}</div>
</div>
<div class="access-arrow">
<i class="el-icon-arrow-right"></i>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
@@ -66,17 +183,113 @@
import CurrentTime from "./components/CurrentTime.vue";
import HomeMain from "./components/HomeMain.vue";
import MiniTable from "./components/MiniTable.vue";
// 引入日志API
// 引入日志API / 生产相关API
import { getLogDataPage } from "@/api/l2/log";
import { getRollHistorytList } from '@/api/l2/roller'
import { listPlan } from "@/api/l2/plan";
import TrackMeasure from "@/components/TrackMeasure/index.vue";
import { getCurrentProducingPlan, getCurrentProcessParams } from "@/api/business/dashboard";
export default {
name: "Index",
components: { CurrentTime, HomeMain, MiniTable, TrackMeasure },
data() {
return {
// KPI指标数据
kpiData: [
{
label: '日产量',
value: '0',
unit: 't',
icon: 'el-icon-data-line',
trend: 'up',
change: '+5.2%'
},
{
label: '成材率',
value: '0',
unit: '%',
icon: 'el-icon-pie-chart',
trend: 'up',
change: '+1.8%'
},
{
label: '成品卷数',
value: '0',
unit: '卷',
icon: 'el-icon-box',
trend: 'up',
change: '+12'
},
{
label: '产线效率',
value: '0',
unit: '%',
icon: 'el-icon-cpu',
trend: 'down',
change: '-2.1%'
}
],
// 生产状态
productionStatus: {
type: 'success',
text: '运行中',
currentCoilId: '',
stripSpeed: '0',
efficiency: '0'
},
// 设备状态
equipmentStatus: [
{ name: '入口段', status: 'running', statusText: '运行中' },
{ name: '炉区', status: 'running', statusText: '运行中' },
{ name: '镀层段', status: 'running', statusText: '运行中' },
{ name: '出口段', status: 'running', statusText: '运行中' },
{ name: '张力控制', status: 'running', statusText: '运行中' },
{ name: '温度控制', status: 'running', statusText: '运行中' }
],
// 快速访问功能卡片
featureCards: [
{ title: "生产计划", desc: "生产计划管理", icon: "s-order", path: "/plan" },
{ title: "日志记录", desc: "日志记录管理", icon: "document", path: "/log" },
{ title: "轧辊管理", desc: "轧辊管理", icon: "setting", path: "/roller" },
{ title: "停机管理", desc: "停机管理", icon: "warning", path: "/stop" },
{ title: "过程跟踪", desc: "实时过程监控", icon: "monitor", path: "/track" },
{ title: "实绩数据", desc: "查看生产实绩数据", icon: "data-analysis", path: "/pdo" }
],
// 表格列配置(与日志字段对应)
alarmColumns: [
{ label: "发生时间", prop: "timestamp", width: "200px" },
{ label: "报警模块", prop: "module", width: "120px" },
{ label: "报警类型", prop: "logtype", width: "120px" },
{ label: "警报内容", prop: "logtext" },
],
rollHistoryColumns: [
{ label: "换辊号", prop: "changeid", width: "120px" },
{ label: "轧辊号", prop: "rollid", width: "120px" },
{ label: "机组", prop: "seton", width: "80px" },
{ label: "班次", prop: "shift", width: "80px" },
{ label: "班组", prop: "crew", width: "80px" },
{ label: "机架号", prop: "standid", width: "100px" },
{ label: "位置", prop: "position", width: "80px" },
{ label: '直径', prop: 'diameter', width: '100px' },
{ label: '粗糙度', prop: 'rough', width: '100px' },
{ label: '凸度', prop: 'crown', width: '100px' },
{ label: '成分', prop: 'composition', width: '100px' },
],
planColumns: [
{ label: '顺序号', prop: 'seqid', width: '80px' },
{ label: '钢卷号', prop: 'coilid', width: '120px' },
{ label: '机组号', prop: 'unitCode', width: '100px' },
{ label: '计划号', prop: 'planid', width: '120px' },
{ label: '计划类型', prop: 'planType', width: '100px' },
{ label: '钢种', prop: 'steelGrade', width: '120px' },
{ label: '出口卷号', prop: 'exitCoilid', width: '120px' },
{ label: '订单号', prop: 'orderNo', width: '120px' },
{ label: '客户代码', prop: 'custommerCode', width: '120px' },
{ label: '上线时间', prop: 'onlineDate', width: '160px' },
{ label: '开始时间', prop: 'startDate', width: '160px' },
{ label: '结束时间', prop: 'endDate', width: '160px' },
],
alarmData: [], // 表格数据从API获取
queryForm: { pageNum: 1, pageSize: 10 }, // 分页参数
tableLoading: false, // 加载状态
@@ -84,92 +297,66 @@ export default {
rollHistoryLoading: false, // 轧辊历史数据加载状态
planData: [], // 生产计划数据
planLoading: false, // 生产计划数据加载状态
measureData: [], // 过程跟踪数据
measureLoading: false, // 过程跟踪加载状态
measureColumns: [] // 过程跟踪列配置
};
},
computed: {
// 功能卡片配置
featureCards() {
return [
{ title: this.$t('dashboard.productionPlan'), desc: this.$t('dashboard.productionPlanDesc'), icon: "table", path: "/plan" },
{ title: this.$t('dashboard.logRecord'), desc: this.$t('dashboard.logRecordDesc'), icon: "log", path: "/log" },
{ title: this.$t('dashboard.rollManagement'), desc: this.$t('dashboard.rollManagementDesc'), icon: "redis", path: "/roller" },
{ title: this.$t('dashboard.shutdownManagement'), desc: this.$t('dashboard.shutdownManagementDesc'), icon: "bug", path: "/stop" },
];
},
// 表格列配置(与日志字段对应)
alarmColumns() {
return [
{ label: this.$t('dashboard.occurTime'), prop: "timestamp", width: "200px" },
{ label: this.$t('dashboard.alarmModule'), prop: "module", width: "60px" },
{ label: this.$t('dashboard.alarmType'), prop: "logtype" },
];
},
rollHistoryColumns() {
return [
{ label: this.$t('dashboard.rollChangeId'), prop: "changeid" },
{ label: this.$t('dashboard.rollId'), prop: "rollid" },
{ label: this.$t('dashboard.unit'), prop: "seton", width: "80px" },
{ label: this.$t('dashboard.shift'), prop: "shift", width: "60px" },
{ label: this.$t('dashboard.crew'), prop: "crew", width: "60px" },
{ label: this.$t('dashboard.standId'), prop: "standid", width: "80px" },
{ label: this.$t('dashboard.position'), prop: "position", width: "50px" },
{ label: this.$t('dashboard.diameter'), prop: 'diameter', width: '100px' },
{ label: this.$t('dashboard.roughness'), prop: 'rough', width: '100px' },
{ label: this.$t('dashboard.crown'), prop: 'crown', width: '100px' },
{ label: this.$t('dashboard.composition'), prop: 'composition', width: '100px' },
];
},
planColumns() {
return [
{ label: this.$t('dashboard.seqId'), prop: 'seqid', width: '80px' },
{ label: this.$t('dashboard.coilId'), prop: 'coilid', width: '120px' },
{ label: this.$t('dashboard.unitCode'), prop: 'unitCode', width: '100px' },
{ label: this.$t('dashboard.planId'), prop: 'planid', width: '120px' },
{ label: this.$t('dashboard.planType'), prop: 'planType', width: '80px' },
{ label: this.$t('dashboard.steelGrade'), prop: 'steelGrade', width: '120px' },
{ label: this.$t('dashboard.exitCoilId'), prop: 'exitCoilid', width: '100px' },
{ label: this.$t('dashboard.orderNo'), prop: 'orderNo', width: '100px' },
{ label: this.$t('dashboard.customerCode'), prop: 'custommerCode', width: '100px' },
{ label: this.$t('dashboard.onlineDate'), prop: 'onlineDate' },
{ label: this.$t('dashboard.startDate'), prop: 'startDate' },
{ label: this.$t('dashboard.endDate'), prop: 'endDate' },
{ label: this.$t('dashboard.furInDate'), prop: 'furInDate' },
{ label: this.$t('dashboard.furOutDate'), prop: 'furOutDate' },
];
},
},
created() {
// 页面加载时调用API获取数据
this.getLogData();
this.getRollHistorytList();
this.getPlanList();
this.refreshDashboard();
// 定期刷新数据
this.refreshTimer = setInterval(() => {
this.getLogData();
this.getRollHistorytList();
this.getPlanList();
this.refreshDashboard();
}, 30000); // 每30秒刷新一次
},
beforeDestroy() {
// 组件销毁时清除定时器
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
}
},
methods: {
// 获取日志数据
getLogData() {
this.tableLoading = true;
getLogDataPage(this.queryForm)
.then((response) => {
this.tableLoading = false;
this.alarmData = response.data.list; // 赋值表格数据
this.alarmData = response.data.list || []; // 赋值表格数据
})
.catch((error) => {
this.tableLoading = false;
console.error("获取日志数据失败:", error);
this.$message.error(this.$t('dashboard.getLogDataFailed'));
console.error("获取日志数据失败", error);
this.$message.error("获取日志数据失败,请稍后重试");
});
},
// 获取轧辊历史列表
getRollHistorytList() {
this.rollHistoryLoading = true;
getRollHistorytList(this.queryForm)
.then((response) => {
this.rollHistoryLoading = false;
this.rollHistoryData = response.data.list; // 赋值表格数据
this.rollHistoryData = response.data.list || []; // 赋值表格数据
})
.catch((error) => {
this.rollHistoryLoading = false;
console.error("获取轧辊历史数据失败:", error);
});
},
// 获取生产计划列表
getPlanList() {
this.planLoading = true;
listPlan(this.queryForm)
.then((response) => {
this.planLoading = false;
this.planData = response.data.map(item => {
this.planData = (response.data || []).map(item => {
return {
...item,
onlineDate: item.onlineDate?.replace('T', ' '),
@@ -180,13 +367,55 @@ export default {
}
}); // 赋值表格数据
})
.catch((error) => {
this.planLoading = false;
console.error("获取计划数据失败:", error);
});
},
// 从后端刷新首页仪表板数据
refreshDashboard() {
// 1) 当前生产计划
getCurrentProducingPlan().then(res => {
const data = res.data || {};
// 更新生产状态中的卷号等信息
this.productionStatus.currentCoilId = data.coilid || '';
// 这里可以根据需要扩展更多字段,例如钢种、入口规格等
});
// 2) 当前生产卷的工艺参数
getCurrentProcessParams().then(res => {
const data = res.data || {};
// 带钢速度示例优先取出口段TR的speedExitSection其次取入口段POR1/POR2的stripSpeed
let stripSpeed = 0;
const exit = data.exitSection || {};
const entry = data.entrySection || {};
if (exit.TR && (exit.TR.speedExitSection || exit.TR.stripSpeed)) {
stripSpeed = exit.TR.speedExitSection || exit.TR.stripSpeed;
} else if (entry.POR1 && entry.POR1.stripSpeed) {
stripSpeed = entry.POR1.stripSpeed;
} else if (entry.POR2 && entry.POR2.stripSpeed) {
stripSpeed = entry.POR2.stripSpeed;
}
this.productionStatus.stripSpeed = stripSpeed || 0;
// 产线效率目前后端没有直接指标可根据需要后续扩展这里先保持0或从其他接口获取
// this.productionStatus.efficiency = ...;
// KPI 区域暂时用真实卷号和速度填充一部分,其他可根据后端需要扩展
this.kpiData[0].value = (stripSpeed || 0).toFixed(1); // 用带钢速度临时占位,后续可改为产量
this.kpiData[1].value = '0';
this.kpiData[2].value = '0';
this.kpiData[3].value = this.productionStatus.efficiency || '0';
});
}
},
};
</script>
<style scoped lang="scss">
/* 主题色与布局样式(保持原有) */
/* 主题色与布局样式 */
$theme-primary: #a7acb4;
$theme-light: rgba(167, 172, 180, 0.8);
$theme-dark: rgba(140, 145, 153, 0.8);
@@ -195,4 +424,287 @@ $theme-bg2: #454c51;
$theme-bg3: #1E2227;
$theme-text-light: #f0f0f0;
$theme-text-gray: #c9cdcf;
.industrial-dashboard {
padding: 20px;
background: #f2f3f5; // 工业风浅灰背景,避免过深色
min-height: calc(100vh - 60px);
}
/* 仪表板头部 */
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 20px;
background: #ffffff; // 简洁白色背景,避免低级渐变色
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
.header-left {
.system-title {
color: #000;
font-size: 28px;
font-weight: 600;
margin: 0 0 8px 0;
}
.system-subtitle {
color: #000;
font-size: 14px;
margin: 0;
}
}
}
/* KPI区域 */
.kpi-section {
margin-bottom: 20px;
.kpi-card {
border-radius: 8px;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.kpi-content {
display: flex;
align-items: center;
padding: 10px 0;
.kpi-icon {
font-size: 40px;
margin-right: 16px;
color: #409eff;
}
.kpi-info {
flex: 1;
.kpi-label {
font-size: 14px;
color: #909399;
margin-bottom: 8px;
}
.kpi-value {
font-size: 28px;
font-weight: 600;
color: #303133;
line-height: 1;
}
.kpi-unit {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
}
.kpi-trend {
display: flex;
align-items: center;
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
&.up {
color: #67c23a;
background: rgba(103, 194, 58, 0.1);
}
&.down {
color: #f56c6c;
background: rgba(245, 108, 108, 0.1);
}
i {
margin-right: 4px;
}
}
}
&.kpi-0 .kpi-icon { color: #409eff; }
&.kpi-1 .kpi-icon { color: #67c23a; }
&.kpi-2 .kpi-icon { color: #e6a23c; }
&.kpi-3 .kpi-icon { color: #f56c6c; }
}
}
/* 状态区域 */
.status-section {
margin-bottom: 20px;
.status-card {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.status-content {
padding: 10px 0;
.status-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
}
.status-label {
color: #909399;
font-size: 14px;
}
.status-value {
color: #303133;
font-size: 16px;
font-weight: 600;
}
}
}
.equipment-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
padding: 10px 0;
.equipment-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px;
background: #f5f7fa;
border-radius: 6px;
transition: all 0.3s ease;
&:hover {
background: #ecf5ff;
}
.equipment-name {
font-size: 13px;
color: #606266;
margin-bottom: 8px;
text-align: center;
}
}
}
}
}
/* 表格区域 */
.table-section {
margin-bottom: 20px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
.alarm-badge {
margin-left: 10px;
}
}
}
/* 快速访问区域 */
.quick-access-section {
.quick-access-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
padding: 10px 0;
.access-item {
display: flex;
align-items: center;
padding: 16px;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
border-color: #409eff;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
transform: translateY(-2px);
}
.access-icon {
font-size: 32px;
color: #409eff;
margin-right: 16px;
}
.access-info {
flex: 1;
.access-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 4px;
}
.access-desc {
font-size: 12px;
color: #909399;
}
}
.access-arrow {
color: #c0c4cc;
font-size: 18px;
}
}
}
}
/* 响应式设计 */
@media (max-width: 1200px) {
.quick-access-grid {
grid-template-columns: repeat(2, 1fr) !important;
}
}
@media (max-width: 768px) {
.kpi-section .el-col {
margin-bottom: 16px;
}
.status-section .el-col {
margin-bottom: 16px;
}
.equipment-grid {
grid-template-columns: repeat(2, 1fr) !important;
}
.quick-access-grid {
grid-template-columns: 1fr !important;
}
.dashboard-header {
flex-direction: column;
align-items: flex-start;
.header-right {
margin-top: 16px;
}
}
}
</style>