Files
klp-mono/apps/hand-factory/components/lines/paint.vue
2025-12-07 12:54:39 +08:00

596 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page-container">
<!-- 简洁标签栏 -->
<view class="tab-container">
<view
v-for="item in tabData"
:key="item.value"
@click="currentTab = item.value"
class="tab-item"
:class="{ 'tab-active': currentTab === item.value }"
>
<text class="tab-label">{{ item.text }}</text>
<view class="tab-indicator" v-if="currentTab === item.value"></view>
</view>
</view>
<!-- 刷新按钮 -->
<view class="refresh-btn-fixed" @click="refreshData">
<text class="refresh-icon" :class="{ 'rotating': isRefreshing }"></text>
</view>
<scroll-view scroll-y class="scroll-container" v-if="currentTab === 1">
<!-- 未启用提示 -->
<view class="disabled-container">
<view class="disabled-icon">🔒</view>
<view class="disabled-title">该功能未启用</view>
<view class="disabled-desc">涂装线实时监控功能暂未启用请联系管理员</view>
</view>
</scroll-view>
<scroll-view scroll-y class="scroll-container" v-if="currentTab == 2">
<klp-product-statistic></klp-product-statistic>
</scroll-view>
<scroll-view scroll-y class="scroll-container" v-if="currentTab == 3">
<klp-shutdown-statistic></klp-shutdown-statistic>
</scroll-view>
<scroll-view scroll-y class="scroll-container" v-if="currentTab == 4">
<klp-team-performance></klp-team-performance>
</scroll-view>
</view>
</template>
<script>
import { getAllPlantStateDefines, listPlantStateHistory, getCurrentShift } from '@/api/pocket/plantState'
import config from '@/config'
export default {
data() {
return {
currentTab: 1,
tabData: [
{ text: "实时监控", value: 1 },
{ text: "生产统计", value: 2 },
{ text: "停机统计", value: 3 },
{ text: "班组绩效", value: 4 }
],
webStatus: [
{ label: '网络状态', value: '检测中...' },
{ label: '当前班组', value: '—' }
],
lastUpdateTime: '—',
isRefreshing: false,
refreshTimer: null,
// 涂装速度ID=1-3
speedMetrics: [
{ label: '出口带钢速度', value: '—', unit: 'm/min' },
{ label: '涂装带钢速度', value: '—', unit: 'm/min' },
{ label: '圆盘剪速度', value: '—', unit: 'm/min' }
],
// 活套状态ID=8-10
looperMetrics: [
{ label: '入口活套', value: '—', unit: '%' },
{ label: '出口活套', value: '—', unit: '%' },
{ label: '联机活套', value: '—', unit: '%' }
],
// 工艺参数ID=7, 17-19
processMetrics: [
{ label: '漂洗温度', value: '—', unit: '°C' },
{ label: '烘干温度', value: '—', unit: '°C' },
{ label: '漂洗电导率', value: '—', unit: 'g/L' },
{ label: '联机活套张力', value: '—', unit: 'kN' }
],
// 拉矫参数ID=20-23
tlvMetrics: [
{ label: '拉矫延伸率', value: '—', unit: '%' },
{ label: '破磷机插入量1', value: '—', unit: 'mm' },
{ label: '破磷机插入量2', value: '—', unit: 'mm' },
{ label: '破磷机插入量3', value: '—', unit: 'mm' }
],
// 温度趋势图ID=4-7, 17
tempChartData: {},
lineChartOpts: {
color: ["#0066cc", "#409eff", "#66b1ff", "#a0cfff", "#d9ecff"],
padding: [15, 15, 0, 15],
enableScroll: false,
legend: {
show: true,
position: "top",
fontSize: 10,
lineHeight: 14,
itemGap: 6
},
dataLabel: false, // 隐藏数据标签
dataPointShape: false, // 隐藏数据点
xAxis: {
disableGrid: true,
rotateLabel: true,
itemCount: 5, // 减少标签数量
labelCount: 5,
fontSize: 10
},
yAxis: {
gridType: "dash",
dashLength: 4,
gridColor: "#e4e7ed",
showTitle: true,
fontSize: 10,
data: [{ min: 0, title: "温度(°C)" }]
},
extra: {
line: {
type: "curve",
width: 2,
activeType: "hollow"
}
}
},
plantStateDefines: []
};
},
mounted() {
this.loadAllData()
this.startAutoRefresh()
},
beforeDestroy() {
this.stopAutoRefresh()
},
methods: {
startAutoRefresh() {
this.refreshTimer = setInterval(() => {
this.refreshData(true)
}, 30000)
},
stopAutoRefresh() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
this.refreshTimer = null
}
},
loadAllData() {
this.checkNetworkStatus()
this.loadCurrentShift()
this.initPlantStateDefines()
this.updateLastTime()
},
refreshData(isSilent = false) {
if (this.isRefreshing) return
this.isRefreshing = true
if (!isSilent) uni.showLoading({ title: '刷新中' })
Promise.all([
this.checkNetworkStatus(),
this.loadCurrentShift(),
this.initPlantStateDefines(isSilent)
]).finally(() => {
this.isRefreshing = false
if (!isSilent) {
uni.hideLoading()
uni.showToast({ title: '刷新成功', icon: 'success', duration: 1500 })
}
this.updateLastTime()
})
},
updateLastTime() {
const now = new Date()
const hour = String(now.getHours()).padStart(2, '0')
const minute = String(now.getMinutes()).padStart(2, '0')
const second = String(now.getSeconds()).padStart(2, '0')
this.lastUpdateTime = `${hour}:${minute}:${second}`
},
checkNetworkStatus() {
return new Promise((resolve) => {
const startTime = Date.now()
uni.request({
url: config.baseUrl + '/pocket/proPlantStateDefine/allWithValues',
method: 'GET',
timeout: 5000,
success: (res) => {
const responseTime = Date.now() - startTime
if (responseTime < 500) {
this.webStatus[0].value = '通畅'
} else if (responseTime < 2000) {
this.webStatus[0].value = '卡顿'
} else {
this.webStatus[0].value = '异常'
}
resolve()
},
fail: () => {
this.webStatus[0].value = '异常'
resolve()
}
})
})
},
loadCurrentShift() {
return getCurrentShift().then(response => {
if (response.code === 200 && response.data) {
const shiftData = response.data
const shiftName = this.getShiftName(shiftData.shift)
const crewName = this.getCrewName(shiftData.crew)
this.webStatus[1].value = `${crewName} / ${shiftName}`
}
}).catch(error => {
console.error('加载班组信息失败:', error)
})
},
getShiftName(shift) {
const shiftMap = { 'A': '早班', 'B': '中班', 'C': '晚班' }
return shiftMap[shift] || shift || '—'
},
getCrewName(crew) {
const crewMap = { 1: '甲', 2: '乙', 3: '丙', 4: '丁' }
return crewMap[crew] || crew || '—'
},
initPlantStateDefines(isSilent = false) {
if (!isSilent) uni.showLoading({ title: '加载中' })
return getAllPlantStateDefines().then(response => {
if (response.code === 200 && response.data) {
this.plantStateDefines = response.data
if (!isSilent) console.log('彩涂线状态定义已加载:', this.plantStateDefines)
this.updateCurrentMetrics()
return this.loadTempTrend(isSilent)
} else {
if (!isSilent) uni.hideLoading()
}
}).catch(error => {
if (!isSilent) uni.hideLoading()
console.error('加载状态定义失败:', error)
})
},
updateCurrentMetrics() {
// 1. 速度监控ID=1-3
const exitSpeed = this.getDefineById(1)
const plSpeed = this.getDefineById(2)
const trimSpeed = this.getDefineById(3)
this.speedMetrics = [
{ label: exitSpeed?.comments || '出口带钢速度', value: this.formatValue(exitSpeed?.currentValue), unit: exitSpeed?.units || 'm/min' },
{ label: plSpeed?.comments || '涂装带钢速度', value: this.formatValue(plSpeed?.currentValue), unit: plSpeed?.units || 'm/min' },
{ label: trimSpeed?.comments || '圆盘剪速度', value: this.formatValue(trimSpeed?.currentValue), unit: trimSpeed?.units || 'm/min' }
]
// 2. 活套状态ID=8-10
const celLooper = this.getDefineById(8)
const cxlLooper = this.getDefineById(9)
const telLooper = this.getDefineById(10)
this.looperMetrics = [
{ label: celLooper?.comments || '入口活套', value: this.formatValue(celLooper?.currentValue), unit: celLooper?.units || '%' },
{ label: cxlLooper?.comments || '出口活套', value: this.formatValue(cxlLooper?.currentValue), unit: cxlLooper?.units || '%' },
{ label: telLooper?.comments || '联机活套', value: this.formatValue(telLooper?.currentValue), unit: telLooper?.units || '%' }
]
// 3. 工艺参数ID=7, 17-19
const rinseTemp = this.getDefineById(7)
const windTemp = this.getDefineById(17)
const rinseFlow = this.getDefineById(18)
const telTension = this.getDefineById(19)
this.processMetrics = [
{ label: rinseTemp?.comments || '漂洗温度', value: this.formatValue(rinseTemp?.currentValue), unit: rinseTemp?.units || '°C' },
{ label: windTemp?.comments || '烘干温度', value: this.formatValue(windTemp?.currentValue), unit: windTemp?.units || '°C' },
{ label: rinseFlow?.comments || '漂洗电导率', value: this.formatValue(rinseFlow?.currentValue), unit: rinseFlow?.units || 'g/L' },
{ label: telTension?.comments || '联机活套张力', value: this.formatValue(telTension?.currentValue), unit: telTension?.units || 'kN' }
]
// 4. 拉矫参数ID=20-23
const tlvElong = this.getDefineById(20)
const tlvMesh1 = this.getDefineById(21)
const tlvMesh2 = this.getDefineById(22)
const tlvMesh3 = this.getDefineById(23)
this.tlvMetrics = [
{ label: tlvElong?.comments || '拉矫延伸率', value: this.formatValue(tlvElong?.currentValue), unit: tlvElong?.units || '%' },
{ label: tlvMesh1?.comments || '破磷机插入量1', value: this.formatValue(tlvMesh1?.currentValue), unit: tlvMesh1?.units || 'mm' },
{ label: tlvMesh2?.comments || '破磷机插入量2', value: this.formatValue(tlvMesh2?.currentValue), unit: tlvMesh2?.units || 'mm' },
{ label: tlvMesh3?.comments || '破磷机插入量3', value: this.formatValue(tlvMesh3?.currentValue), unit: tlvMesh3?.units || 'mm' }
]
},
loadTempTrend(isSilent = false) {
return listPlantStateHistory({ pageNum: 1, pageSize: 30 }).then(response => {
if (!isSilent) uni.hideLoading()
if (response.code === 200 && response.rows && response.rows.length > 0) {
const categories = []
const temp1Data = []
const temp2Data = []
const temp3Data = []
const temp4Data = []
const temp5Data = []
response.rows.forEach(item => {
const dateStr = this.formatDate(item.insdate)
categories.push(dateStr)
temp1Data.push(Number(item.value4) || 0) // 1#酸槽温度
temp2Data.push(Number(item.value5) || 0) // 2#酸槽温度
temp3Data.push(Number(item.value6) || 0) // 3#酸槽温度
temp4Data.push(Number(item.value7) || 0) // 漂洗温度
temp5Data.push(Number(item.value17) || 0) // 烘干温度
})
this.tempChartData = {
categories: categories.reverse(),
series: [
{ name: '1#酸槽温度', data: temp1Data.reverse() },
{ name: '2#酸槽温度', data: temp2Data.reverse() },
{ name: '3#酸槽温度', data: temp3Data.reverse() },
{ name: '漂洗温度', data: temp4Data.reverse() },
{ name: '烘干温度', data: temp5Data.reverse() }
]
}
}
}).catch(error => {
if (!isSilent) uni.hideLoading()
console.error('加载温度趋势失败:', error)
})
},
getDefineById(id) {
return this.plantStateDefines.find(item => item.id == id)
},
formatDate(dateStr) {
if (!dateStr) return ''
const date = new Date(dateStr)
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return `${hour}:${minute}`
},
formatValue(value) {
if (value === null || value === undefined || value === '') return '—'
const num = Number(value)
if (isNaN(num)) return '—'
return num.toFixed(2)
}
}
};
</script>
<style scoped lang="scss">
/* 页面容器 */
.page-container {
min-height: 100vh;
background: #f5f7fa;
}
/* 标签栏 */
.tab-container {
display: flex;
background: #fff;
border-bottom: 2rpx solid #e4e7ed;
}
.tab-item {
flex: 1;
text-align: center;
padding: 28rpx 0;
position: relative;
.tab-label {
font-size: 28rpx;
color: #606266;
font-weight: 400;
}
&.tab-active {
.tab-label {
color: #0066cc;
font-weight: 500;
}
}
.tab-indicator {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 3rpx;
background: #0066cc;
}
}
/* 刷新按钮 */
.refresh-btn-fixed {
position: fixed;
right: 32rpx;
bottom: 120rpx;
width: 96rpx;
height: 96rpx;
background: #0066cc;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 20rpx rgba(0, 102, 204, 0.4);
z-index: 999;
&:active {
opacity: 0.8;
transform: scale(0.95);
}
}
.refresh-icon {
font-size: 48rpx;
color: #fff;
display: block;
line-height: 1;
&.rotating {
animation: rotate 1s linear infinite;
}
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 滚动容器 */
.scroll-container {
height: calc(100vh - 96rpx);
padding: 24rpx;
}
/* 顶部状态栏 */
.status-bar {
display: flex;
align-items: center;
background: #fff;
padding: 24rpx 32rpx;
margin-bottom: 24rpx;
border-radius: 8rpx;
border: 1rpx solid #e4e7ed;
}
.status-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
}
.status-label {
font-size: 26rpx;
color: #909399;
}
.status-value {
font-size: 28rpx;
font-weight: 500;
color: #303133;
&.status-通畅 { color: #67c23a; }
&.status-卡顿 { color: #e6a23c; }
&.status-异常 { color: #f56c6c; }
&.status-time {
color: #909399;
font-size: 24rpx;
}
}
.status-divider {
width: 1rpx;
height: 40rpx;
background: #e4e7ed;
}
/* 区块样式 */
.section {
margin-bottom: 24rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 500;
color: #303133;
margin-bottom: 20rpx;
padding-left: 16rpx;
border-left: 4rpx solid #0066cc;
}
/* 指标卡片 - 3列布局 */
.metrics-grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
}
/* 指标卡片 - 2列布局 */
.metrics-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.metric-box {
background: #fff;
border: 1rpx solid #e4e7ed;
border-radius: 8rpx;
padding: 28rpx 20rpx;
text-align: center;
}
.metric-name {
display: block;
font-size: 24rpx;
color: #909399;
margin-bottom: 16rpx;
}
.metric-value {
display: block;
font-size: 48rpx;
font-weight: 600;
color: #0066cc;
margin-bottom: 8rpx;
line-height: 1;
}
.metric-unit {
display: block;
font-size: 22rpx;
color: #909399;
}
/* 图表容器 */
.chart-box {
background: #fff;
border: 1rpx solid #e4e7ed;
border-radius: 8rpx;
padding: 24rpx 16rpx;
}
/* 未启用容器 */
.disabled-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: calc(100vh - 200rpx);
padding: 40rpx;
text-align: center;
}
.disabled-icon {
font-size: 120rpx;
margin-bottom: 32rpx;
opacity: 0.6;
}
.disabled-title {
font-size: 32rpx;
font-weight: 600;
color: #303133;
margin-bottom: 16rpx;
}
.disabled-desc {
font-size: 26rpx;
color: #909399;
line-height: 1.6;
}
</style>