Files
klp-mono/apps/hand-factory/components/lines/acidity.vue
2025-10-31 19:10:08 +08:00

813 lines
21 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>
<!-- 刷新按钮固定在右下角所有tab都显示 -->
<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="status-bar">
<view class="status-item">
<text class="status-label">网络状态</text>
<text class="status-value" :class="'status-' + webStatus[0].value">{{ webStatus[0].value }}</text>
</view>
<view class="status-divider"></view>
<view class="status-item">
<text class="status-label">当前班组</text>
<text class="status-value">{{ webStatus[1].value }}</text>
</view>
<view class="status-divider"></view>
<view class="status-item">
<text class="status-label">更新时间</text>
<text class="status-value status-time">{{ lastUpdateTime }}</text>
</view>
</view>
<!-- 速度监控 -->
<view class="section">
<view class="section-title">速度监控</view>
<view class="metrics-grid-3">
<view class="metric-box" v-for="(item, index) in speedMetrics" :key="index">
<text class="metric-name">{{ item.label }}</text>
<text class="metric-value">{{ item.value }}</text>
<text class="metric-unit">{{ item.unit }}</text>
</view>
</view>
</view>
<!-- 酸槽温度趋势 -->
<view class="section">
<view class="section-title">酸槽温度趋势</view>
<view class="chart-box">
<qiun-data-charts type="line" :chartData="tempChartData" :opts="lineChartOpts" />
</view>
</view>
<!-- 活套运行状态 -->
<view class="section">
<view class="section-title">活套运行状态</view>
<view class="metrics-grid-3">
<view class="metric-box" v-for="(item, index) in looperMetrics" :key="index">
<text class="metric-name">{{ item.label }}</text>
<text class="metric-value">{{ item.value }}</text>
<text class="metric-unit">{{ item.unit }}</text>
</view>
</view>
</view>
<!-- 酸槽浓度监控 -->
<view class="section">
<view class="section-title">酸槽浓度监控</view>
<view class="tank-grid">
<view class="tank-card" v-for="(tank, index) in tankConcentration" :key="index">
<view class="tank-header">{{ tank.name }}</view>
<view class="tank-data">
<view class="tank-row">
<text class="data-label">酸浓度</text>
<text class="data-value">{{ tank.hclCont }} <text class="data-unit">g/L</text></text>
</view>
<view class="tank-divider"></view>
<view class="tank-row">
<text class="data-label">铁盐浓度</text>
<text class="data-value">{{ tank.feCont }} <text class="data-unit">g/L</text></text>
</view>
</view>
</view>
</view>
</view>
<!-- 工艺参数 -->
<view class="section">
<view class="section-title">工艺参数</view>
<view class="metrics-grid-2">
<view class="metric-box" v-for="(item, index) in processMetrics" :key="index">
<text class="metric-name">{{ item.label }}</text>
<text class="metric-value">{{ item.value }}</text>
<text class="metric-unit">{{ item.unit }}</text>
</view>
</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,2,3
speedMetrics: [
{ label: '出口带钢速度', value: '—', unit: 'm/min' },
{ label: '酸洗带钢速度', value: '—', unit: 'm/min' },
{ label: '圆盘剪速度', value: '—', unit: 'm/min' }
],
// 活套状态ID=8,9,10
looperMetrics: [
{ label: '入口活套', value: '—', unit: '%' },
{ label: '出口活套', value: '—', unit: '%' },
{ label: '联机活套', value: '—', unit: '%' }
],
// 酸槽浓度ID=11-16
tankConcentration: [
{ name: '1#酸槽', hclCont: '—', feCont: '—' },
{ name: '2#酸槽', hclCont: '—', feCont: '—' },
{ name: '3#酸槽', hclCont: '—', feCont: '—' }
],
// 其他工艺参数ID=7,17,18,19,20
processMetrics: [
{ label: '漂洗温度', value: '—', unit: '°C' },
{ label: '烘干温度', value: '—', unit: '°C' },
{ label: '漂洗电导率', value: '—', unit: 'g/L' },
{ label: '联机活套张力', value: '—', unit: 'kN' },
{ label: '拉矫机延伸率', value: '—', unit: '%' }
],
// 温度趋势图数据ID=4,5,6
tempChartData: {},
lineChartOpts: {
color: ["#0066cc", "#409eff", "#66b1ff"],
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: {
// 启动自动刷新每30秒
startAutoRefresh() {
this.refreshTimer = setInterval(() => {
console.log('自动刷新数据...')
this.refreshData(true) // 静默刷新
}, 30000) // 30秒刷新一次
},
// 停止自动刷新
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,2,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,9,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=11-16
this.tankConcentration = [
{
name: '1#酸槽',
hclCont: this.formatValue(this.getDefineById(11)?.currentValue),
feCont: this.formatValue(this.getDefineById(12)?.currentValue)
},
{
name: '2#酸槽',
hclCont: this.formatValue(this.getDefineById(13)?.currentValue),
feCont: this.formatValue(this.getDefineById(14)?.currentValue)
},
{
name: '3#酸槽',
hclCont: this.formatValue(this.getDefineById(15)?.currentValue),
feCont: this.formatValue(this.getDefineById(16)?.currentValue)
}
]
// 4. 其他工艺参数ID=7,17,18,19,20
const rinseTemp = this.getDefineById(7)
const windTemp = this.getDefineById(17)
const rinseFlow = this.getDefineById(18)
const telTension = this.getDefineById(19)
const tlvElong = this.getDefineById(20)
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'
},
{
label: tlvElong?.comments || '拉矫机延伸率',
value: this.formatValue(tlvElong?.currentValue),
unit: tlvElong?.units || '%'
}
]
},
// 加载温度趋势图3个酸槽温度ID=4,5,6
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 tank1Data = []
const tank2Data = []
const tank3Data = []
// 获取温度定义
const tank1Temp = this.getDefineById(4)
const tank2Temp = this.getDefineById(5)
const tank3Temp = this.getDefineById(6)
response.rows.forEach(item => {
const dateStr = this.formatDate(item.insdate)
categories.push(dateStr)
tank1Data.push(Number(item.value4) || 0) // ID=4 对应 value4
tank2Data.push(Number(item.value5) || 0) // ID=5 对应 value5
tank3Data.push(Number(item.value6) || 0) // ID=6 对应 value6
})
this.tempChartData = {
categories: categories.reverse(),
series: [
{ name: tank1Temp?.comments || '1#酸槽温度', data: tank1Data.reverse() },
{ name: tank2Temp?.comments || '2#酸槽温度', data: tank2Data.reverse() },
{ name: tank3Temp?.comments || '3#酸槽温度', data: tank3Data.reverse() }
]
}
}
}).catch(error => {
if (!isSilent) {
uni.hideLoading()
}
console.error('加载温度趋势失败:', error)
})
},
// 根据ID获取Define对象
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}`
},
// 格式化数值保留2位小数
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;
}
/* 酸槽监控网格 */
.tank-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
}
.tank-card {
background: #fff;
border: 2rpx solid #0066cc;
border-radius: 8rpx;
overflow: hidden;
}
.tank-header {
background: #0066cc;
color: #fff;
font-size: 28rpx;
font-weight: 500;
padding: 20rpx;
text-align: center;
}
.tank-data {
padding: 24rpx 16rpx;
}
.tank-row {
display: flex;
flex-direction: column;
align-items: center;
padding: 12rpx 0;
}
.data-label {
font-size: 22rpx;
color: #909399;
margin-bottom: 8rpx;
}
.data-value {
font-size: 36rpx;
font-weight: 600;
color: #303133;
}
.data-unit {
font-size: 20rpx;
font-weight: 400;
color: #909399;
margin-left: 4rpx;
}
.tank-divider {
height: 1rpx;
background: #e4e7ed;
margin: 12rpx 0;
}
</style>