feat:洗棒大屏页面布局

This commit is contained in:
shijing 2026-04-30 10:42:11 +08:00
parent 4f373edb48
commit bb52129444
1 changed files with 402 additions and 0 deletions

View File

@ -0,0 +1,402 @@
<template>
<el-container class="dashboard">
<el-header class="header">
<div class="title">洗棒大屏</div>
<div class="timer">{{ currentDay }} {{ currentTime }}</div>
</el-header>
<el-main style="padding:6px;overflow:hidden">
<el-row style="height:92vh">
<!-- 左栏产品已完成数 + 产品种类占比 -->
<el-col :span="6" style="height:100%;display:flex;flex-direction:column;gap:6px;padding-right:3px">
<div style="height:12vh">
<dv-border-box-12 style="height:100%">
<div class="numBlock_title">产品已完成数</div>
<div class="numBlock_value">{{ finishedCount }}</div>
</dv-border-box-12>
</div>
<div style="flex:1;position:relative">
<dv-border-box-1 style="height:100%">
<div class="chartBlockTitle">产品种类占比</div>
<dv-active-ring-chart :config="config_ring" style="height:calc(100% - 5vh);width:100%"/>
</dv-border-box-1>
</div>
</el-col>
<!-- 中栏设备实时曲线轮播 -->
<el-col :span="12" style="height:100%;padding:0 3px">
<dv-border-box-1 style="height:100%;display:flex;flex-direction:column">
<div class="chartBlockTitle">
设备实时曲线
<span class="eq-name-badge">{{ currentEqName }}</span>
<span class="interval-tip">每5s采集 · 每20s切换</span>
</div>
<div class="eq-dots">
<span
v-for="(eq, i) in equipmentList"
:key="eq.id"
class="eq-dot"
:class="{ active: i === currentEqIndex }"
></span>
</div>
<scEcharts :option="chartOption_curve" style="flex:1;width:96%;margin:0 2%"/>
<div class="curve-stats">
<span>当前值<em class="val-cur">{{ curStat.cur }}</em></span>
<span>最大值<em class="val-max">{{ curStat.max }}</em></span>
<span>最小值<em class="val-min">{{ curStat.min }}</em></span>
<span>采集点<em class="val-cur">{{ curStat.count }}</em></span>
</div>
</dv-border-box-1>
</el-col>
<!-- 右栏人员周产量 -->
<el-col :span="6" style="height:100%;padding-left:3px">
<dv-border-box-1 style="height:100%;display:flex;flex-direction:column">
<div class="chartBlockTitle">人员周产量</div>
<scEcharts :option="chartOption_right" style="flex:1;width:96%;margin:0 2%"/>
</dv-border-box-1>
</el-col>
</el-row>
</el-main>
</el-container>
</template>
<script>
const myColor = ['#1089E7', '#F57474', '#56D0E3', '#F8B448', '#8B78F6'];
export default {
name: 'XiboDept',
data() {
return {
currentTime: '',
currentDay: '',
dayInterval: null,
//
finishedCount: 0,
//
config_ring: {
data: [],
digitalFlopStyle: { fontSize: 14, fill: '#fff' },
},
// &
equipmentList: [],
currentEqIndex: 0,
chartDataMap: {},
cdInterval: null,
carouselInterval: null,
// 线线
chartOption_curve: {
backgroundColor: '',
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
grid: { left: '4%', right: '4%', top: '8%', bottom: '12%', containLabel: true },
xAxis: {
type: 'category',
data: [],
boundaryGap: false,
axisLabel: { color: '#7ecfcf', fontSize: 9 },
axisLine: { lineStyle: { color: 'rgba(83,198,243,0.3)' } },
splitLine: { show: false },
},
yAxis: {
type: 'value',
axisLabel: { color: '#7ecfcf', fontSize: 10 },
splitLine: { lineStyle: { color: 'rgba(83,198,243,0.1)', type: 'dashed' } },
},
series: [{
type: 'line',
data: [],
smooth: true,
lineStyle: { color: '#00e5ff', width: 2 },
itemStyle: { color: '#00e5ff' },
symbol: 'none',
areaStyle: {
color: {
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(0,229,255,0.3)' },
{ offset: 1, color: 'rgba(0,229,255,0)' },
],
},
},
}],
},
// 线
curStat: { cur: '--', max: '--', min: '--', count: 0 },
//
chartOption_right: {
backgroundColor: '',
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { top: '2%', left: '2%', right: '14%', bottom: '2%', containLabel: true },
xAxis: {
type: 'value',
axisLabel: { color: '#7ecfcf', fontSize: 9 },
splitLine: { lineStyle: { color: 'rgba(83,198,243,0.1)', type: 'dashed' } },
},
yAxis: {
type: 'category',
inverse: true,
data: ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十'],
axisLabel: { color: '#aef', fontSize: 10 },
axisLine: { show: false },
axisTick: { show: false },
},
series: [{
type: 'bar',
barWidth: 14,
data: [920, 796, 734, 661, 594, 508, 438, 360],
itemStyle: {
borderRadius: [0, 4, 4, 0],
color: (params) => myColor[params.dataIndex % 5],
},
label: { show: true, position: 'right', color: '#fff', fontSize: 10 },
}],
},
};
},
computed: {
currentEqName() {
return this.equipmentList[this.currentEqIndex]?.name || '--';
},
},
mounted() {
this.showTime();
this.dayInterval = setInterval(this.showTime, 1000);
this.getFinishedCount();
this.getMaterialRatio();
this.initEquipmentCurve();
},
beforeUnmount() {
clearInterval(this.dayInterval);
clearInterval(this.cdInterval);
clearInterval(this.carouselInterval);
},
methods: {
showTime() {
this.currentTime = this.$TOOL.dateFormat(new Date(), 'hh:mm:ss');
this.currentDay = this.$TOOL.dateFormat(new Date(), 'yyyy年MM月dd日');
},
async getFinishedCount() {
try {
const mgroups = await this.$API.mtm.mgroup.list.req({
belong_dept__name: '拉丝排板班组',
page: 0,
});
if (!mgroups || mgroups.length === 0) return;
let total = 0;
for (const mg of mgroups) {
const res = await this.$API.wpm.wmaterial.list.req({
tag: 'done',
page_size: 1,
mgroup: mg.id,
});
total += res.count || 0;
}
this.finishedCount = total;
} catch (e) {
console.error('getFinishedCount error', e);
}
},
getMaterialRatio() {
this.$API.bi.dataset.exec.req('materialCount', {}).then((res) => {
if (!res.data2?.ds0?.length) return;
const list = res.data2.ds0.filter(
(item) => item.dept_name === '拉丝排板班组'
);
const nameMap = {};
list.forEach((item) => {
nameMap[item.material_name] =
(nameMap[item.material_name] || 0) + (item.count || 0);
});
this.config_ring.data = Object.entries(nameMap).map(([name, value]) => ({
name,
value,
}));
});
},
async initEquipmentCurve() {
try {
const res = await this.$API.em.equipment.list.req({
belong_dept__name: '拉丝排板班组',
page: 0,
});
this.equipmentList = (res || []).filter((eq) => eq.cd_req_addr);
if (this.equipmentList.length === 0) return;
this.equipmentList.forEach((eq) => {
this.chartDataMap[eq.id] = [];
});
this.startCdPoll();
this.startCarousel();
} catch (e) {
console.error('initEquipmentCurve error', e);
}
},
startCdPoll() {
if (this.cdInterval) clearInterval(this.cdInterval);
this.pollOnce();
this.cdInterval = setInterval(this.pollOnce, 5000);
},
pollOnce() {
const eq = this.equipmentList[this.currentEqIndex];
if (!eq) return;
this.$API.em.cd.req({ method: eq.cd_req_addr }).then((res) => {
let numVal;
if (typeof res === 'number') {
numVal = res;
} else if (typeof res === 'object' && res !== null) {
const first = Object.values(res)[0];
numVal = parseFloat(first);
}
if (isNaN(numVal)) return;
const time = this.$TOOL.dateFormat(new Date(), 'hh:mm:ss');
const points = this.chartDataMap[eq.id];
points.push({ time, value: numVal });
if (points.length > 60) points.shift();
this.refreshChart(points);
}).catch((e) => console.warn('cd poll error:', e));
},
refreshChart(points) {
this.chartOption_curve.xAxis.data = points.map((p) => p.time);
this.chartOption_curve.series[0].data = points.map((p) => p.value);
if (points.length > 0) {
const values = points.map((p) => p.value);
this.curStat = {
cur: values[values.length - 1].toFixed(2),
max: Math.max(...values).toFixed(2),
min: Math.min(...values).toFixed(2),
count: values.length,
};
}
},
startCarousel() {
if (this.carouselInterval) clearInterval(this.carouselInterval);
this.carouselInterval = setInterval(() => {
this.currentEqIndex =
(this.currentEqIndex + 1) % this.equipmentList.length;
this.curStat = { cur: '--', max: '--', min: '--', count: 0 };
this.startCdPoll();
const eq = this.equipmentList[this.currentEqIndex];
this.refreshChart(this.chartDataMap[eq.id]);
}, 20000);
},
},
};
</script>
<style scoped>
@font-face {
font-family: 'myfont';
src: url('../../../utils/youShe.ttf');
}
.dashboard {
background-image: url('/public/img/photon_bg.png');
background-size: cover;
color: #fff;
font-family: 'Microsoft Yahei' !important;
}
.header {
background: url('/public/img/header_bgv.png');
background-size: 100% 100%;
height: 8vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
border-bottom: none;
}
.title {
font-family: 'myfont';
font-size: 4vh;
font-weight: bold;
color: rgb(149, 255, 255);
letter-spacing: 10px;
}
.timer {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
color: rgb(149, 255, 255);
}
.numBlock_title {
height: 4vh;
width: 100%;
font-size: 1.5vh;
line-height: 4vh;
text-align: center;
}
.numBlock_value {
height: 6vh;
width: 100%;
line-height: 6vh;
text-align: center;
font-size: 3.5vh;
font-family: 'myfont';
color: #00e5ff;
}
.chartBlockTitle {
width: 90%;
height: 5vh;
margin: auto;
line-height: 6vh;
text-align: left;
padding-left: 10px;
font-size: 1.6vh;
font-weight: 600;
border-bottom: 1px solid rgb(83 198 243);
display: flex;
align-items: center;
gap: 8px;
}
.eq-name-badge {
font-size: 1.4vh;
color: #00e5ff;
font-weight: bold;
}
.interval-tip {
font-size: 1.1vh;
color: rgba(149,255,255,0.5);
margin-left: auto;
}
.eq-dots {
display: flex;
gap: 6px;
padding: 4px 16px;
}
.eq-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(83, 198, 243, 0.25);
border: 1px solid rgba(83, 198, 243, 0.5);
display: inline-block;
}
.eq-dot.active {
background: #00e5ff;
box-shadow: 0 0 6px #00e5ff;
}
.curve-stats {
display: flex;
gap: 20px;
padding: 4px 16px 8px;
font-size: 1.2vh;
color: #7ecfcf;
border-top: 1px solid rgba(83, 198, 243, 0.2);
}
.curve-stats em { font-style: normal; font-weight: bold; }
.val-cur { color: #00e5ff; }
.val-max { color: #F8B448; }
.val-min { color: #56D0E3; }
</style>