feat: 新增锌线生产监控模块及相关API和组件

refactor(auth): 增加锌线系统token管理功能
feat(api): 添加锌线停机记录、生产报表和设备快照API
feat(views): 实现锌线实时监控、生产统计和停机统计页面
feat(components): 开发锌线生产报表、停机统计和班组绩效组件
feat(utils): 新增锌线专用请求工具zinc1Request
chore(vue.config): 配置锌线API代理
This commit is contained in:
砂糖
2026-01-19 13:29:44 +08:00
parent f6000018d8
commit 212c2b16eb
19 changed files with 3762 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
<template>
<div class="report-page" style="padding: 20px;">
<!-- 汇总统计信息 el-descriptions 展示 -->
<el-card shadow="hover" class="summary-card" style="margin-bottom: 20px;">
<div slot="header" class="clearfix">
<span style="font-size: 16px; font-weight: bold;">生产报表-数据汇总</span>
</div>
<el-descriptions
:data="productStatistic"
title=""
border
column="4"
size="middle"
label-width="140px"
>
<el-descriptions-item label="总出口宽度(mm)">{{ productStatistic.totalExitWidth || 0 }}</el-descriptions-item>
<el-descriptions-item label="总出口长度(m)">{{ formatNum(productStatistic.totalExitLength) }}</el-descriptions-item>
<el-descriptions-item label="总理论重量(kg)">{{ formatNum(productStatistic.totalTheoryWeight) }}</el-descriptions-item>
<el-descriptions-item label="总实际重量(kg)">{{ formatNum(productStatistic.totalActualWeight) }}</el-descriptions-item>
<el-descriptions-item label="总出口厚度(mm)">{{ formatNum(productStatistic.totalExitThickness) }}</el-descriptions-item>
<el-descriptions-item label="平均出口宽度(mm)">{{ formatNum(productStatistic.avgExitWidth) }}</el-descriptions-item>
<el-descriptions-item label="平均出口长度(m)">{{ formatNum(productStatistic.avgExitLength) }}</el-descriptions-item>
<el-descriptions-item label="平均理论重量(kg)">{{ formatNum(productStatistic.avgTheoryWeight) }}</el-descriptions-item>
<el-descriptions-item label="平均实际重量(kg)">{{ formatNum(productStatistic.avgActualWeight) }}</el-descriptions-item>
<el-descriptions-item label="平均出口厚度(mm)">{{ formatNum(productStatistic.avgExitThickness) }}</el-descriptions-item>
<el-descriptions-item label="总来料重量(kg)">{{ formatNum(productStatistic.totalEntryWeight) }}</el-descriptions-item>
<el-descriptions-item label="总卷数">{{ productStatistic.coilCount || 0 }}</el-descriptions-item>
<el-descriptions-item label="成材率" span="4" label-width="140px">
<span style="color: #1890ff;">{{ formatRate(productStatistic.yieldRate) }}</span>
</el-descriptions-item>
</el-descriptions>
</el-card>
<!-- 明细数据 el-table 展示 -->
<el-card shadow="hover" class="detail-card">
<div slot="header" class="clearfix">
<span style="font-size: 16px; font-weight: bold;">生产报表-明细数据</span>
</div>
<el-table
:data="reportDetails"
border
stripe
size="small"
v-loading="tableLoading"
element-loading-text="加载中..."
style="width: 100%;"
highlight-current-row
>
<el-table-column prop="exitMatId" label="出口物料编码" align="center" />
<el-table-column prop="entryMatId" label="入口物料编码" align="center" />
<el-table-column prop="groupNo" label="班组号" align="center" width="60" />
<el-table-column prop="shiftNo" label="班次号" align="center" width="60" />
<el-table-column prop="steelGrade" label="钢种" align="center" width="80" />
<el-table-column prop="exitWidth" label="出口宽度(mm)" align="center">
<template #default="scope">{{ formatNum(scope.row.exitWidth) }}</template>
</el-table-column>
<el-table-column prop="exitLength" label="出口长度(m)" align="center">
<template #default="scope">{{ formatNum(scope.row.exitLength) }}</template>
</el-table-column>
<el-table-column prop="exitThickness" label="出口厚度(mm)" align="center">
<template #default="scope">{{ formatNum(scope.row.exitThickness) }}</template>
</el-table-column>
<el-table-column prop="theoryWeight" label="理论重量(kg)" align="center">
<template #default="scope">{{ formatNum(scope.row.theoryWeight) }}</template>
</el-table-column>
<el-table-column prop="actualWeight" label="实际重量(kg)" align="center">
<template #default="scope">{{ formatNum(scope.row.actualWeight) }}</template>
</el-table-column>
<el-table-column prop="onlineTime" label="上线时间" align="center" width="190">
<template #default="scope">{{ formatTime(scope.row.onlineTime) }}</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" align="center" width="190">
<template #default="scope">{{ formatTime(scope.row.endTime) }}</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
import { getReportSummary, getReportDetails } from '@/api/lines/zinc/report'
export default {
data() {
return {
productStatistic: {}, // 汇总数据
reportDetails: [], // 明细数据
tableLoading: false, // 表格加载状态
}
},
methods: {
// 数字格式化保留2位小数空值显示0
formatNum(num) {
return num ? Number(num).toFixed(2) : '0.00'
},
// 成材率格式化保留2位小数+百分比
formatRate(rate) {
return rate ? (Number(rate) * 100).toFixed(2) + '%' : '0.00%'
},
// 时间格式化处理ISO时间/空值/null统一格式
formatTime(time) {
if (!time) return '-'
const date = new Date(time)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hh = date.getHours().toString().padStart(2, '0')
const mm = date.getMinutes().toString().padStart(2, '0')
const ss = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hh}:${mm}:${ss}`
},
// 获取汇总数据
async getReportSummary() {
try {
const res = await getReportSummary()
this.productStatistic = res || {}
} catch (err) {
this.$message.error('获取汇总数据失败!')
console.error(err)
}
},
// 获取明细数据
async getReportDetails() {
this.tableLoading = true
try {
const res = await getReportDetails()
this.reportDetails = res || []
} catch (err) {
this.$message.error('获取明细数据失败!')
console.error(err)
} finally {
this.tableLoading = false
}
}
},
mounted() {
// 页面加载时调用两个接口
this.getReportSummary()
this.getReportDetails()
}
}
</script>
<style scoped>
.report-page {
background: #f5f7fa;
min-height: calc(100vh - 120px);
}
.summary-card, .detail-card {
background: #fff;
}
</style>

View File

@@ -0,0 +1,5 @@
<template>
<div>
实时监控
</div>
</template>

View File

@@ -0,0 +1,165 @@
<template>
<div class="stoppage-page" style="padding: 20px;">
<!-- 月份筛选查询区域 -->
<div class="search-box" style="margin: 16px 0; display: flex; align-items: center;">
<el-date-picker
v-model="month"
type="month"
placeholder="请选择月份"
value-format="yyyy-MM"
style="width: 200px;"
@change="handleMonthChange"
/>
<el-button type="primary" icon="el-icon-search" style="margin-left: 10px;" @click="handleQuery">查询</el-button>
</div>
<!-- 停机统计表格 全部修改适配真实JSON数据字段 -->
<el-table
:data="stoppageList"
border
stripe
size="small"
v-loading="loading"
element-loading-text="数据加载中..."
style="width: 100%;"
highlight-current-row
>
<el-table-column prop="stopType" label="停机类型" align="center" width="140" />
<el-table-column prop="startDate" label="停机开始时间" align="center" width="200" />
<el-table-column prop="endDate" label="停机结束时间" align="center" width="200" />
<el-table-column prop="duration" label="停机时长" align="center" width="120">
<template #default="scope">
{{ formatDuration(scope.row.duration) }}
</template>
</el-table-column>
<el-table-column prop="insdate" label="数据录入时间" align="center" width="200" />
<!-- 生产相关附属字段 -->
<el-table-column prop="coilid" label="钢卷号" align="center" width="120" />
<el-table-column prop="shift" label="班次" align="center" width="100" />
<!-- <el-table-column prop="crew" label="班组人员" align="center" width="120" />
<el-table-column prop="area" label="区域" align="center" width="100" />
<el-table-column prop="unit" label="机组" align="center" width="100" />
<el-table-column prop="seton" label="开机人" align="center" width="100" /> -->
<!-- 备注字段 -->
<el-table-column prop="remark" label="备注" align="center" min-width="220" show-overflow-tooltip />
</el-table>
</div>
</template>
<script>
import { listStoppage } from '@/api/lines/zinc/stoppage'
export default {
data() {
return {
stoppageList: [], // 停机统计列表
total: 0, // 数据总条数
loading: false, // 加载状态
month: '', // 选中的月份 yyyy-MM格式
queryParams: {
startDate: '', // 开始时间 yyyy-MM-dd✅ 与后端字段名一致
endDate: '' // 结束时间 yyyy-MM-dd✅ 与后端字段名一致
}
}
},
created() {
// 初始化:默认选中当前月份,并生成当月的起止时间
this.initDefaultMonth()
},
mounted() {
// 页面加载时,默认查询当月停机数据
this.listStoppage()
},
methods: {
// 格式化持续时间:将分钟数转换为"X天X小时X分钟"格式
formatDuration(minutes) {
if (minutes === null || minutes === undefined || minutes === '') return '—'
const totalMinutes = Math.floor(Number(minutes))
if (isNaN(totalMinutes) || totalMinutes < 0) return '—'
const days = Math.floor(totalMinutes / (24 * 60))
const hours = Math.floor((totalMinutes % (24 * 60)) / 60)
const mins = Math.floor(totalMinutes % 60)
let result = ''
if (days > 0) {
result += `${days}`
}
if (hours > 0 || days > 0) {
result += `${hours}小时`
}
if (mins > 0 || result === '') {
result += `${mins}分钟`
}
return result || '0分钟'
},
/** 初始化默认月份和起止时间 */
initDefaultMonth() {
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
this.month = `${year}-${month}`
// 根据当前月份生成时间范围
this.setMonthTimeRange(this.month)
},
/** 选中月份 → 生成 当月1号00:00:00 至 下个月1号00:00:00 的时间格式 */
setMonthTimeRange(month) {
if (!month) return
// 开始时间:选中月份的 1号 00:00:00
this.queryParams.startDate = `${month}-01`
// 解析选中的月份为日期对象
const selectMonth = new Date(month + '-01')
// 下个月1号月份+1日期为1号
const nextMonth = new Date(selectMonth.setMonth(selectMonth.getMonth() + 1))
const nextYear = nextMonth.getFullYear()
const nextMonthNum = (nextMonth.getMonth() + 1).toString().padStart(2, '0')
// 结束时间:下个月 1号 00:00:00
this.queryParams.endDate = `${nextYear}-${nextMonthNum}-01`
},
/** 月份选择器切换事件 */
handleMonthChange(val) {
this.setMonthTimeRange(val)
},
/** 查询按钮点击事件 */
handleQuery() {
this.listStoppage()
},
/** 核心:查询停机统计列表数据,带时间筛选参数 */
async listStoppage() {
this.loading = true
try {
const params = { ...this.queryParams }
const res = await listStoppage(params)
// 适配后端返回格式:数组/分页对象都兼容
this.stoppageList = res.data || res || []
this.total = res.total || this.stoppageList.length
} catch (err) {
this.$message.error('查询停机统计数据失败!')
console.error('停机统计查询异常:', err)
this.stoppageList = []
this.total = 0
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.stoppage-page {
background: #f5f7fa;
min-height: calc(100vh - 40px);
}
.search-box {
background: #fff;
padding: 10px 16px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<div>
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="实时监控" name="1">
</el-tab-pane>
<el-tab-pane label="生产统计" name="2">
</el-tab-pane>
<el-tab-pane label="停机统计" name="3">
</el-tab-pane>
<!-- <el-tab-pane label="班组绩效" name="4">
</el-tab-pane> -->
</el-tabs>
<div>
<div v-if="activeTab === '1'">
<realTimeMonitor></realTimeMonitor>
</div>
<div v-if="activeTab === '2'">
<productStatistic></productStatistic>
</div>
<div v-if="activeTab === '3'">
<shutdownStatistic></shutdownStatistic>
</div>
<!-- <div v-if="activeTab === '4'">
<TeamPerformance></TeamPerformance>
</div> -->
</div>
</div>
</template>
<script>
// import TeamPerformance from './components/team-performance.vue'
import ShutdownStatistic from './components/shutdown-statistic.vue';
import ProductStatistic from './components/product-statistic.vue';
import RealTimeMonitor from './components/real-time-monitoring.vue';
export default {
data() {
return {
activeTab: '1'
}
},
components: {
// TeamPerformance,
ShutdownStatistic,
ProductStatistic,
RealTimeMonitor
}
}
</script>
<style></style>