update 厂区看板优化

master
yinq 1 week ago
parent 26f8c70069
commit bad1327441

@ -52,4 +52,10 @@ export function workshopColumns(query) {
return request({
url: '/ems/board/workshopColumns', method: 'get', params: query
})
}
export function workshopEnergyBoard(query) {
return request({
url: '/ems/board/workshopEnergyBoard', method: 'get', params: query
})
}

@ -26,9 +26,10 @@ export default {
this.initChart(option)
},
initChart(option) {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption(option)
if (!this.chart) {
this.chart = echarts.init(this.$el, 'macarons')
}
this.chart.setOption(option, true)
},
getChart() {
return this.chart

@ -1,507 +1,365 @@
<template>
<div class="board-wrap">
<div class="bg">
<div class="header">
<div class="title">车间能源看板</div>
<div class="toolbar">
<el-select v-model="typeData" size="mini" @change="getData" class="type-select">
<el-option v-for="item in energyOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="time">{{ clockText }}</div>
</div>
<div class="content">
<div class="panel trend-panel">
<div class="panel-title">近24小时{{ currentTypeMeta.label }}耗量曲线</div>
<Chart class="trend-chart" ref="trendChart"></Chart>
</div>
<div class="columns">
<div
class="energy-col"
:class="{
'power-col': String(col.monitorType) === '2',
'steam-col': String(col.monitorType) === '4'
}"
v-for="col in columns"
:key="col.monitorType"
>
<div class="col-head">
<div class="energy-name">{{ col.name }}</div>
<div class="panel data-panel" ref="dataPanel">
<div class="col-head" ref="colHead">
<div class="energy-name">{{ column.name || currentTypeMeta.label }}</div>
<div class="today">
当日能耗
<span class="num">{{ formatNumber(col.todayConsumption) }}</span>
<span class="unit">{{ col.todayUnit }}</span>
<span class="num">{{ formatNumber(column.todayConsumption) }}</span>
<span class="unit">{{ column.todayUnit || currentTypeMeta.unit }}</span>
</div>
</div>
<div class="table-head">
<div class="table-head" ref="tableHead">
<span>计量设备</span>
<span>{{ String(col.monitorType) === '2' ? '电流电压' : '实时数据' }}</span>
<span>{{ typeData === '2' ? '实时数据' : '实时数据' }}</span>
<span>当日耗量</span>
</div>
<div class="rows">
<div class="row" v-for="row in col.rows" :key="col.monitorType + '-' + rowKey(row)">
<div class="row" v-for="row in pagedRows" :key="typeData + '-' + rowKey(row)">
<span :title="row.monitorName">{{ row.monitorName || row.monitorCode }}</span>
<span class="realtime">{{ displayRealtime(row) }}</span>
<span>{{ formatNumber(rowTodayVal(row)) }} {{ col.todayUnit }}</span>
<span>{{ formatNumber(rowTodayVal(row)) }} {{ column.todayUnit || currentTypeMeta.unit }}</span>
</div>
<div class="empty" v-if="!col.rows || col.rows.length === 0"></div>
<div class="empty" v-if="totalRows === 0"></div>
</div>
<div class="pager-wrap" v-if="totalRows > pageSize">
<el-pagination
:current-page.sync="currentPage"
:page-size="pageSize"
layout="prev, pager, next"
:total="totalRows"
small
@current-change="onPageChange"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { workshopColumns } from '@/api/board'
import Chart from '@/components/Charts/Chart.vue'
import { workshopEnergyBoard } from '@/api/board'
export default {
name: 'WorkshopIndex',
components: { Chart },
data() {
return {
clockText: '',
timer: null,
dataTimer: null,
columns: []
typeData: '2',
energyOptions: [
{ label: '电', value: '2', unit: 'kWh' },
{ label: '水', value: '3', unit: 'm³' },
{ label: '蒸汽', value: '4', unit: 't' },
{ label: '压缩空气', value: '5', unit: 'm³' },
{ label: '氮气', value: '6', unit: 'm³' }
],
column: { rows: [] },
trend24h: [],
currentPage: 1,
pageSize: 8,
dataReqSeq: 0
}
},
computed: {
currentTypeMeta() {
return this.energyOptions.find(v => v.value === this.typeData) || this.energyOptions[0]
},
totalRows() {
return (this.column.rows || []).length
},
pagedRows() {
const rows = this.column.rows || []
const start = (this.currentPage - 1) * this.pageSize
return rows.slice(start, start + this.pageSize)
}
},
mounted() {
this.tickClock()
this.timer = setInterval(this.tickClock, 1000)
this.getData()
this.dataTimer = setInterval(this.getData, 2 * 60 * 1000)
window.addEventListener('resize', this.updatePageSize)
},
beforeDestroy() {
if (this.timer) clearInterval(this.timer)
if (this.dataTimer) clearInterval(this.dataTimer)
window.removeEventListener('resize', this.updatePageSize)
},
methods: {
tickClock() {
const d = new Date()
const pad = n => (n < 10 ? '0' + n : '' + n)
this.clockText = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
},
getData() {
workshopColumns().then(res => {
this.columns = (res.data && res.data.columns) || []
const reqSeq = ++this.dataReqSeq
const requestType = this.typeData
this.trend24h = []
this.$nextTick(() => this.renderTrendChart())
workshopEnergyBoard({ monitorType: requestType }).then(res => {
if (reqSeq !== this.dataReqSeq || requestType !== this.typeData) return
this.column = (res.data && (res.data.column || (res.data.columns || [])[0])) || { rows: [] }
this.currentPage = 1
const rawTrend = (res.data && res.data.trend24h) || []
const monitorCodeSet = new Set((this.column.rows || []).map(v => String(v.monitorCode || '')))
const filteredTrend = rawTrend.filter(v => monitorCodeSet.has(String(v.monitorCode || '')))
this.trend24h = filteredTrend.length > 0 ? filteredTrend : rawTrend
this.$nextTick(() => {
this.updatePageSize()
this.renderTrendChart()
})
})
},
updatePageSize() {
this.pageSize = 8
const maxPage = Math.max(1, Math.ceil(this.totalRows / this.pageSize))
if (this.currentPage > maxPage) {
this.currentPage = maxPage
}
},
renderTrendChart() {
if (!this.$refs.trendChart) return
const normalized = this.normalizeTrend(this.trend24h || [])
const xData = normalized.xData
const series = normalized.series
const safeSeries = series.length > 0
? series
: [{
name: `${this.currentTypeMeta.label}耗量`,
type: 'line',
smooth: true,
showSymbol: false,
data: (xData || []).map(() => 0)
}]
const safeXData = xData.length > 0 ? xData : this.buildRecentHourLabels()
this.$refs.trendChart.setData({
grid: { left: '4%', right: '3%', top: '16%', bottom: '10%', containLabel: true },
tooltip: { trigger: 'axis' },
legend: {
type: 'scroll',
top: 0,
textStyle: { color: '#bcdfff', fontSize: 11 }
},
xAxis: {
type: 'category',
data: safeXData,
axisLabel: { color: '#cde7ff', interval: 2, fontSize: 10 },
axisLine: { lineStyle: { color: 'rgba(145,198,255,0.4)' } }
},
yAxis: {
type: 'value',
name: `单位(${this.column.todayUnit || this.currentTypeMeta.unit})`,
nameTextStyle: { color: '#9fc4e8' },
axisLabel: { color: '#cde7ff' },
splitLine: { lineStyle: { color: 'rgba(145,198,255,0.2)', type: 'dashed' } }
},
series: safeSeries
})
},
onPageChange(page) {
this.currentPage = page
},
normalizeTrend(rawTrend) {
const first = rawTrend[0]
const isDeviceSeries = first && (Array.isArray(first.points) || Array.isArray(first.data))
if (isDeviceSeries) {
const series = []
let xData = []
rawTrend.forEach((device, index) => {
const points = Array.isArray(device.points) ? device.points : (Array.isArray(device.data) ? device.data : [])
const currentXData = points.map(v => v.time || v.timeKey || '')
if (currentXData.length > xData.length) {
xData = currentXData
}
series.push({
name: device.monitorName || device.monitorCode || `设备${index + 1}`,
type: 'line',
smooth: true,
showSymbol: false,
data: points.map(v => this.toChartNumber(v.expend))
})
})
return { xData, series }
}
return {
xData: rawTrend.map(v => v.time || v.timeKey || ''),
series: [{
name: `${this.currentTypeMeta.label}耗量`,
type: 'line',
smooth: true,
showSymbol: false,
data: rawTrend.map(v => this.toChartNumber(v.expend))
}]
}
},
buildRecentHourLabels() {
const list = []
const now = new Date()
for (let i = 23; i >= 0; i--) {
const d = new Date(now.getTime() - i * 60 * 60 * 1000)
const m = d.getMonth() + 1
const day = d.getDate()
const h = d.getHours()
const mm = m < 10 ? `0${m}` : `${m}`
const dd = day < 10 ? `0${day}` : `${day}`
const hh = h < 10 ? `0${h}` : `${h}`
list.push(`${mm}-${dd} ${hh}:00`)
}
return list
},
toChartNumber(value) {
if (value === null || value === undefined || value === '') return 0
const num = Number(value)
return Number.isFinite(num) ? num : 0
},
rowKey(row) {
return row.monitorCode != null ? String(row.monitorCode) : ''
},
rowTodayVal(row) {
const v = row.rowTodayConsumption != null ? row.rowTodayConsumption : row.rowtodayconsumption
return v
return row.rowTodayConsumption != null ? row.rowTodayConsumption : row.rowtodayconsumption
},
displayRealtime(row) {
const type = String(row.monitorType != null ? row.monitorType : '')
if (type === '2') {
const u = this.joinPhases(row, ['vA', 'vB', 'vC'], 'V')
const i = this.joinPhases(row, ['iA', 'iB', 'iC'], 'A')
if (!u && !i) return '--'
if (!u) return i
if (!i) return u
return `${u} / ${i}`
const vA = this.valueText(row, 'vA')
const iA = this.valueText(row, 'iA')
const zxyg = this.valueText(row, 'zxyg')
return `电压A:${vA}V\n电流A:${iA}A\n正向有功:${zxyg}kWh`
}
const instant = row.instantFlow != null ? row.instantFlow : row.instantflow
const cum =
row.cumulativeFlow != null
? row.cumulativeFlow
: row.cumulativeflow != null
? row.cumulativeflow
: null
const cum = row.cumulativeFlow != null ? row.cumulativeFlow : (row.cumulativeflow != null ? row.cumulativeflow : null)
const instantUnit = row.instantUnit || row.instantunit || ''
const unit = row.realtimeUnit || row.realtimeunit || ''
const instantNum = this.formatNumber(instant)
const num = this.formatNumber(cum)
if (instantNum === '--' && num === '--') return '--'
const instantText = instantNum === '--' ? '--' : (instantUnit ? `${instantNum} ${instantUnit}` : instantNum)
const cumText = num === '--' ? '--' : (unit ? `${num} ${unit}` : num)
return `瞬时:${instantText}\n累计:${cumText}`
},
joinPhases(row, keys, suffix) {
const parts = []
for (const k of keys) {
const raw = row[k] != null ? row[k] : row[k.toLowerCase()]
if (raw === null || raw === undefined || raw === '') continue
const n = Number(raw)
if (Number.isNaN(n)) continue
parts.push(`${n.toFixed(2)}${suffix}`)
}
return parts.length ? parts.join('/') : ''
valueText(row, key) {
const raw = row[key] != null ? row[key] : row[key.toLowerCase()]
if (raw === null || raw === undefined || raw === '') return '--'
const num = Number(raw)
return Number.isNaN(num) ? '--' : num.toFixed(2)
},
formatNumber(val) {
if (val === null || val === undefined || val === '') return '--'
const num = Number(val)
if (Number.isNaN(num)) return '--'
return num.toFixed(2)
}
}
}
</script>
<style scoped lang="less">
.board-wrap {
width: 100%;
height: 100%;
}
.board-wrap { width: 100%; height: 100%; }
.bg {
width: 100%;
height: 100%;
min-height: 100vh;
background: #061223 url('~@/assets/board/bg1.jpg') no-repeat center;
background-size: cover;
padding: 12px;
box-sizing: border-box;
color: #d8ecff;
overflow-x: auto;
}
.header {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 12px;
position: relative;
}
.title { font-size: 28px; font-weight: 600; letter-spacing: 2px; text-align: center; }
.time { position: absolute; right: 0; font-size: 14px; color: #9fc4e8; }
.toolbar { position: absolute; left: 0; }
.type-select /deep/ .el-input__inner {
background-color: rgba(0, 0, 0, 0.25);
border: 1px solid rgba(120, 180, 255, 0.45);
color: #d8ecff;
}
.title {
.content {
display: grid;
grid-template-columns: 58% 42%;
gap: 12px;
height: calc(100vh - 84px);
}
font-size: 28px;
.panel {
background: linear-gradient(180deg, rgba(8, 30, 56, 0.9), rgba(4, 21, 42, 0.86));
border: 1px solid rgba(120, 180, 255, 0.28);
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
font-weight: 600;
.trend-panel { padding: 10px; }
.panel-title { color: #9ec6ef; font-size: 15px; margin-bottom: 8px; }
.panel-subtitle { color: #6fa4d8; font-size: 12px; margin-top: -4px; margin-bottom: 6px; }
.trend-chart { width: 100%; height: calc(100% - 28px); min-height: 300px; }
letter-spacing: 2px;
.data-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }
.col-head { padding: 10px; border-bottom: 1px solid rgba(120, 180, 255, 0.22); text-align: center; }
.energy-name { font-size: 18px; font-weight: 600; margin-bottom: 4px; }
.today { font-size: 13px; color: #9ec6ef; }
.today .num { color: #ffd166; font-weight: 600; }
.table-head,
.row {
display: grid;
grid-template-columns: 1.3fr 1.3fr 0.8fr;
gap: 6px;
padding: 8px 10px;
font-size: 12px;
text-align: center;
}
.time {
position: absolute;
right: 0;
font-size: 14px;
color: #9fc4e8;
}
.columns {
display: flex;
gap: 10px;
min-width: 1500px;
}
.energy-col {
width: calc((100% - 40px) / 5);
min-width: 0;
background: rgba(7, 24, 45, 0.82);
border: 1px solid rgba(120, 180, 255, 0.25);
border-radius: 8px;
min-height: calc(100vh - 96px);
display: flex;
flex-direction: column;
}
.col-head {
padding: 10px 10px 8px;
border-bottom: 1px solid rgba(120, 180, 255, 0.22);
text-align: center;
}
.energy-name {
font-size: 18px;
font-weight: 600;
margin-bottom: 4px;
}
.today {
font-size: 13px;
color: #9ec6ef;
}
.today .num {
color: #ffd166;
font-weight: 600;
}
.table-head {
display: grid;
grid-template-columns: 1.45fr 1.05fr 0.7fr;
gap: 6px;
padding: 8px 10px;
font-size: 12px;
color: #7faad5;
border-bottom: 1px solid rgba(120, 180, 255, 0.18);
text-align: center;
}
.rows {
flex: 1;
overflow: auto;
.rows { flex: 1; min-height: 0; overflow: hidden; }
.row { border-bottom: 1px dashed rgba(120, 180, 255, 0.12); }
.row span:first-child { white-space: normal; word-break: break-all; line-height: 1.35; }
.realtime { white-space: pre-line; word-break: break-all; line-height: 1.2; font-size: 11px; }
.empty { padding: 12px 10px; color: #7098c2; font-size: 12px; text-align: center; }
.pager-wrap { padding: 4px 10px; display: flex; justify-content: center; border-top: 1px solid rgba(120, 180, 255, 0.12); }
.pager-wrap /deep/ .el-pagination button,
.pager-wrap /deep/ .el-pager li {
background: transparent;
color: #9ec6ef;
}
.row {
display: grid;
grid-template-columns: 1.45fr 1.05fr 0.7fr;
gap: 6px;
padding: 8px 10px;
font-size: 12px;
border-bottom: 1px dashed rgba(120, 180, 255, 0.12);
text-align: center;
}
.row span:first-child {
white-space: normal;
word-break: break-all;
line-height: 1.35;
}
.row .realtime {
white-space: pre-line;
word-break: break-all;
line-height: 1.2;
font-size: 11px;
}
.power-col .table-head,
.power-col .row {
grid-template-columns: 1fr 1.15fr 0.85fr;
}
.steam-col .table-head,
.steam-col .row {
grid-template-columns: 1.75fr 0.95fr 0.65fr;
}
.empty {
padding: 12px 10px;
color: #7098c2;
font-size: 12px;
text-align: center;
}
.pager-wrap /deep/ .el-pager li.active { color: #ffd166; }
</style>

Loading…
Cancel
Save