修改产线图

This commit is contained in:
‘937886381’
2025-06-19 11:10:53 +08:00
48 changed files with 1995 additions and 81 deletions

View File

@@ -0,0 +1,116 @@
<template>
<div class='centerBottomL'>
<div class='title'>
<svg-icon icon-class="dataBoard3" class='icon'/>
<span>本月数据</span>
</div>
<div class='dataBox' style='top:50px'>
<p>
<span class='text'>总投入片数</span>
<span class='precent' :class='{precentR:monthData?.inputNumChange>=0,precentG:monthData?.inputNumChange<0}'>{{monthData?.inputNumChange || '-'}}%</span>
<img v-show='monthData?.inputNumChange<0' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
<img v-show='monthData?.inputNumChange>=0' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{monthData?.inputNum ? formatThousands(monthData.inputNum) : '-'}}</p>
</div>
<div class='dataBox'style='top:180px'>
<p>
<span class='text'>总生产片数</span>
<span class='precent' :class='{precentR:monthData?.outputNumChange>=0,precentG:monthData?.outputNumChange<0}'>{{monthData?.outputNumChange || '-'}}%</span>
<img v-show='monthData?.outputNumChange<0' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
<img v-show='monthData?.outputNumChange>=0' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{monthData?.outputNum ? formatThousands(monthData.outputNum) : '-'}}</p>
</div>
</div>
</template>
<script>
export default {
name: 'CenterBottomL',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
val.monthAndLastMonth && val.monthAndLastMonth.forEach(item => {
if (item.dataType === "本月") {
this.monthData = item
}
})
}
},
data() {
return {
monthData:{}
}
},
methods: {}
}
</script>
<style lang="scss" scoped>
.centerBottomL {
width: 337px;
height: 332px;
background: url('../../../../assets/images/dataBoard/center-bottom.png') no-repeat;
background-size: 100%;
position: absolute;
left: 440px;
bottom:23px;
.title {
margin: 7px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
margin-top: 5px;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.dataBox {
width: 290px;
height: 135px;
background: url('../../../../assets/images/dataBoard/numberBox.png') no-repeat;
background-size: 100%;
position: absolute;
padding:22px 0 0 16px;
left: 24px;
p{
margin: 0;
}
.text {
font-weight: 500;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 2px;
margin-right: 8px;
}
.precent {
font-size: 18px;
margin-right: 5px;
}
.precentR {
color: #FF271D;
}
.precentG {
color: #34F716;
}
.num {
font-weight: 500;
font-size: 38px;
color: #FFFFFF;
margin-top: 5px;
}
}
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div class='centerBottomR'>
<div class='title'>
<svg-icon icon-class="dataBoard3" class='icon'/>
<span>上月数据</span>
</div>
<div class='dataBox' style='top:50px'>
<p>
<span class='text'>总投入片数</span>
</p>
<p class='num'>{{lastMonthData?.inputNum ? formatThousands(lastMonthData.inputNum) : '-'}}</p>
</div>
<div class='dataBox'style='top:180px'>
<p>
<span class='text'>总生产片数</span>
</p>
<p class='num'>{{lastMonthData?.outputNum ? formatThousands(lastMonthData.outputNum) : '-'}}</p>
</div>
</div>
</template>
<script>
export default {
name: 'CenterBottomR',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
val.monthAndLastMonth && val.monthAndLastMonth.forEach(item => {
if (item.dataType === "上月") {
this.lastMonthData = item
}
})
}
},
data() {
return {
lastMonthData:{}
}
},
methods: {}
}
</script>
<style lang="scss" scoped>
.centerBottomR {
width: 337px;
height: 332px;
background: url('../../../../assets/images/dataBoard/center-bottom.png') no-repeat;
background-size: 100%;
position: absolute;
left: 791px;
bottom:23px;
.title {
margin: 7px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
margin-top: 5px;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.dataBox {
width: 290px;
height: 135px;
background: url('../../../../assets/images/dataBoard/numberBox.png') no-repeat;
background-size: 100%;
position: absolute;
padding:22px 0 0 16px;
left: 24px;
p{
margin: 0;
}
.text {
font-weight: 500;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 2px;
margin-right: 8px;
}
.num {
font-weight: 500;
font-size: 38px;
color: #FFFFFF;
margin-top: 5px;
}
}
}
</style>

View File

@@ -0,0 +1,352 @@
<template>
<div ref="centerTopBox" class='centerTopBox'>
<div>
<video src="/static/videos/01.webm" muted autoplay loop class='videoStyle'></video>
<div
class='eqTipBox'
v-show='showTooltip'
:style="'left:'+tooltipStyle.left+'px;top:'+tooltipStyle.top+'px;'"
>
<p><span class='eqTipTitle'>设备名称:</span><span class='eqTipNum'>{{eqTipMsg.name}}</span></p>
<p><span class='eqTipTitle'>进口数量:</span><span class='eqTipNum'>{{eqTipMsg.input}}</span></p>
<p><span class='eqTipTitle'>出口数量:</span><span class='eqTipNum'>{{eqTipMsg.output}}</span></p>
<p><span class='eqTipTitle'>报警状态:</span>
<span class='eqTipNum'>
<img v-show='eqTipMsg.alarm' :src='dotY' width='16'/>
<img v-show='!eqTipMsg.alarm' :src='dotG' width='16'/>
{{eqTipMsg.alarm?'报警':'未报警'}}
</span>
</p>
<p><span class='eqTipTitle'>在线状态:</span>
<span class='eqTipNum'>
<img v-show='eqTipMsg.status' :src='dotG' width='16'/>
<img v-show='!eqTipMsg.status' :src='dotR' width='16'/>
{{eqTipMsg.status?'在线':'离线'}}
</span>
</p>
</div>
<!-- 设备 -->
<div class='eqBox'>
<!-- line-从上到下-从右往左 -->
<span
v-for='(item,index) in eqList'
:key='index'
:style="'width:'+item.w+'px;height:'+item.h+'px;left:'+item.l+'px;top:'+item.t+'px'"
@mouseenter="handleMouseEnter(item, $event)"
@mouseleave="handleMouseLeave"
></span>
</div>
</div>
<div class='centerTopTopBox'>
<div style='display: inline-block;'>
<img src="../../../../assets/images/dataBoard/centerNumB.png" alt="" width='203'>
<p class='num'>{{dataObj?.productRate || '-'}}%</p>
<p class='title'>成品率</p>
</div>
<div style='display: inline-block;'>
<img src="../../../../assets/images/dataBoard/centerNumY.png" alt="" width='261'>
<p class='num' style='width: 260px;padding-left: 20px;'>{{dataObj?.todayProduct ? formatThousands(dataObj.todayProduct) : '-'}}</p>
<p class='title' style='color:#FFB625'>今日产量</p>
</div>
<div style='display: inline-block;'>
<img src="../../../../assets/images/dataBoard/centerNumY.png" alt="" width='261'>
<p class='num' style='width: 260px;padding-left: 20px;'>{{dataObj?.monthProduct ? formatThousands(dataObj.monthProduct) : '-'}}</p>
<p class='title' style='color:#FFB625'>本月产量</p>
</div>
<div style='display: inline-block;'>
<img src="../../../../assets/images/dataBoard/centerNumB.png" alt="" width='203'>
<p class='num'>{{dataObj?.alarmNum ? formatThousands(dataObj.alarmNum) : '-'}}</p>
<p class='title'>设备报警数</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CenterTop',
data() {
return {
showTooltip:false,
tooltipStyle: {
left: 0,
top: 0
},
dotY:require('../../../../assets/images/dataBoard/dotY.png'),
dotR:require('../../../../assets/images/dataBoard/dotR.png'),
dotG:require('../../../../assets/images/dataBoard/dotG.png'),
eqList:[
{name:'磨边机1',id:'1',w:70,h:18,l:830,t:160},
{name:'磨边机2',id:'2',w:70,h:18,l:830,t:183},
{name:'磨边机3',id:'3',w:70,h:18,l:832,t:206},
{name:'清洗机1',id:'4',w:30,h:15,l:798,t:160},
{name:'清洗机2',id:'5',w:30,h:15,l:798,t:183},
{name:'清洗机3',id:'6',w:30,h:15,l:800,t:206},
{name:'打孔机1',id:'7',w:13,h:12,l:730,t:163},
{name:'打孔机2',id:'8',w:13,h:12,l:730,t:187},
{name:'打孔机3',id:'9',w:13,h:12,l:730,t:210},
{name:'清洗机1',id:'10',w:32,h:15,l:665,t:160},
{name:'清洗机2',id:'11',w:32,h:15,l:665,t:183},
{name:'清洗机3',id:'12',w:32,h:15,l:665,t:206},
{name:'丝印机1',id:'13',w:30,h:12,l:605,t:163},
{name:'丝印机2',id:'14',w:30,h:12,l:605,t:187},
{name:'丝印机3',id:'15',w:30,h:12,l:605,t:210},
{name:'固化炉1',id:'16',w:37,h:12,l:558,t:163},
{name:'固化炉2',id:'17',w:37,h:12,l:558,t:187},
{name:'固化炉3',id:'18',w:37,h:12,l:558,t:210},
{name:'钢化炉',id:'19',w:75,h:15,l:382,t:209},
{name:'清洗机1',id:'20',w:30,h:15,l:228,t:173},
{name:'清洗机2',id:'21',w:30,h:15,l:228,t:206},
{name:'铺纸机1',id:'22',w:18,h:15,l:188,t:175},
{name:'铺纸机2',id:'23',w:18,h:15,l:188,t:206},
{name:'磨边机1',id:'24',w:70,h:18,l:832,t:233},
{name:'磨边机2',id:'25',w:70,h:18,l:833,t:257},
{name:'磨边机3',id:'26',w:70,h:18,l:834,t:281},
{name:'清洗机1',id:'27',w:30,h:15,l:800,t:233},
{name:'清洗机2',id:'28',w:30,h:15,l:801,t:257},
{name:'清洗机3',id:'29',w:30,h:15,l:802,t:281},
{name:'打孔机1',id:'30',w:13,h:12,l:731,t:240},
{name:'打孔机2',id:'31',w:13,h:12,l:731,t:262},
{name:'打孔机3',id:'32',w:13,h:12,l:731,t:285},
{name:'清洗机1',id:'33',w:32,h:15,l:666,t:234},
{name:'清洗机2',id:'34',w:32,h:15,l:666,t:258},
{name:'清洗机3',id:'35',w:32,h:15,l:666,t:282},
{name:'丝印机1',id:'36',w:30,h:12,l:605,t:238},
{name:'丝印机2',id:'37',w:30,h:12,l:605,t:262},
{name:'丝印机3',id:'38',w:30,h:12,l:605,t:286},
{name:'固化炉1',id:'39',w:37,h:12,l:558,t:238},
{name:'固化炉2',id:'40',w:37,h:12,l:558,t:262},
{name:'固化炉3',id:'41',w:37,h:12,l:558,t:286},
{name:'钢化炉',id:'42',w:75,h:15,l:382,t:238},
{name:'清洗机1',id:'43',w:30,h:15,l:228,t:234},
{name:'清洗机2',id:'44',w:30,h:15,l:228,t:267},
{name:'铺纸机1',id:'45',w:18,h:15,l:187,t:234},
{name:'铺纸机2',id:'46',w:18,h:15,l:186,t:267},
{name:'磨边机1',id:'47',w:70,h:18,l:834,t:318},
{name:'磨边机2',id:'48',w:70,h:18,l:834,t:342},
{name:'清洗机1',id:'49',w:30,h:15,l:802,t:319},
{name:'清洗机2',id:'50',w:30,h:15,l:802,t:342},
{name:'打孔机1',id:'51',w:13,h:12,l:731,t:324},
{name:'打孔机2',id:'52',w:13,h:12,l:731,t:348},
{name:'清洗机1',id:'53',w:32,h:15,l:666,t:320},
{name:'清洗机2',id:'54',w:32,h:15,l:666,t:342},
{name:'丝印机1',id:'55',w:30,h:12,l:605,t:324},
{name:'丝印机2',id:'56',w:30,h:12,l:605,t:348},
{name:'镀膜机1',id:'57',w:20,h:15,l:553,t:322},
{name:'镀膜机2',id:'58',w:20,h:15,l:553,t:346},
{name:'固化炉1',id:'59',w:37,h:12,l:506,t:324},
{name:'固化炉2',id:'60',w:37,h:12,l:506,t:347},
{name:'钢化炉',id:'61',w:75,h:15,l:340,t:346},
{name:'清洗机1',id:'62',w:30,h:15,l:240,t:343},
{name:'铺纸机1',id:'63',w:18,h:15,l:200,t:343},
{name:'铺纸机2',id:'64',w:18,h:15,l:170,t:343},
{name:'磨边机1',id:'65',w:70,h:18,l:838,t:380},
{name:'磨边机2',id:'66',w:70,h:18,l:838,t:405},
{name:'磨边机3',id:'67',w:70,h:18,l:839,t:428},
{name:'清洗机1',id:'68',w:30,h:15,l:804,t:380},
{name:'清洗机2',id:'69',w:30,h:15,l:805,t:405},
{name:'清洗机3',id:'70',w:30,h:15,l:806,t:427},
{name:'镀膜机1',id:'71',w:20,h:15,l:707,t:381},
{name:'镀膜机2',id:'72',w:20,h:15,l:707,t:407},
{name:'镀膜机3',id:'73',w:20,h:15,l:707,t:429},
{name:'固化炉1',id:'74',w:37,h:12,l:660,t:383},
{name:'固化炉2',id:'75',w:37,h:12,l:660,t:409},
{name:'固化炉3',id:'76',w:37,h:12,l:660,t:431},
{name:'镀膜机1',id:'77',w:20,h:15,l:605,t:382},
{name:'镀膜机2',id:'78',w:20,h:15,l:605,t:408},
{name:'镀膜机3',id:'79',w:20,h:15,l:605,t:430},
{name:'固化炉1',id:'80',w:37,h:12,l:557,t:383},
{name:'固化炉2',id:'81',w:37,h:12,l:557,t:409},
{name:'固化炉3',id:'82',w:37,h:12,l:557,t:431},
{name:'钢化炉',id:'83',w:75,h:15,l:379,t:381},
{name:'清洗机1',id:'84',w:30,h:15,l:220,t:379},
{name:'清洗机2',id:'85',w:30,h:18,l:220,t:410},
{name:'铺纸机1',id:'86',w:18,h:15,l:180,t:381},
{name:'铺纸机2',id:'87',w:18,h:15,l:180,t:414},
{name:'磨边机1',id:'88',w:70,h:18,l:817,t:465},
{name:'磨边机2',id:'89',w:70,h:18,l:817,t:488},
{name:'磨边机3',id:'90',w:70,h:18,l:819,t:512},
{name:'清洗机1',id:'91',w:30,h:15,l:784,t:462},
{name:'清洗机2',id:'92',w:30,h:15,l:784,t:488},
{name:'清洗机3',id:'93',w:30,h:15,l:785,t:512},
{name:'镀膜机1',id:'94',w:20,h:15,l:685,t:466},
{name:'镀膜机2',id:'95',w:20,h:15,l:685,t:490},
{name:'镀膜机3',id:'96',w:20,h:15,l:685,t:513},
{name:'固化炉1',id:'97',w:37,h:12,l:638,t:468},
{name:'固化炉2',id:'98',w:37,h:12,l:638,t:491},
{name:'固化炉3',id:'99',w:37,h:12,l:638,t:514},
{name:'镀膜机1',id:'100',w:20,h:15,l:584,t:468},
{name:'镀膜机2',id:'101',w:20,h:15,l:584,t:490},
{name:'镀膜机3',id:'102',w:20,h:15,l:584,t:513},
{name:'固化炉1',id:'103',w:37,h:12,l:535,t:468},
{name:'固化炉2',id:'104',w:37,h:12,l:535,t:492},
{name:'固化炉3',id:'105',w:37,h:12,l:535,t:514},
{name:'钢化炉',id:'106',w:75,h:15,l:344,t:513},
{name:'清洗机1',id:'107',w:30,h:18,l:185,t:473},
{name:'清洗机2',id:'108',w:30,h:18,l:185,t:511},
{name:'铺纸机1',id:'109',w:18,h:15,l:144,t:475},
{name:'铺纸机2',id:'110',w:18,h:15,l:144,t:511}
],
eqTipMsg:{
name:'',
input:null,
output:null,
alarm:'未报警',
status:'在线'
}
}
},
props: {
scaleNum: {
type: Number,
default: 1
},
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
scaleNum(val) {
this.scaleNum = val
},
dataObj(val) {
val.equipmentDets && val.equipmentDets.forEach(item => {
this.eqList.forEach(eq => {
if (eq.id == item.id) {
eq.name = item.name
eq.input = item.input
eq.output = item.output
eq.alarm = item.isError
eq.status = item.isRun
}
})
})
console.log(this.eqList)
}
},
mounted() {},
methods: {
// 鼠标hover事件
handleMouseEnter(item, event) {
this.showTooltip = true;
this.eqTipMsg.name = item.name;
this.eqTipMsg.input = item.input;
this.eqTipMsg.output = item.output;
this.eqTipMsg.alarm = item.alarm;
this.eqTipMsg.status = item.status;
this.updateTooltipPosition(event);
},
handleMouseLeave() {
this.showTooltip = false;
},
updateTooltipPosition(event) {
const rect = this.$refs.centerTopBox.getBoundingClientRect()
const offset = 15;
const tooltipWidth = 230*this.scaleNum;
const tooltipHeight = 170*this.scaleNum;
const startX = rect.left;
const startY = rect.top;
const endX = rect.width;
const endY = rect.height;
let posX = event.clientX-startX + offset;
let posY = event.clientY-startY + offset;
if (posX + tooltipWidth > endX) {
posX = event.clientX-startX - tooltipWidth - offset;
}
if (posY + tooltipHeight > endY) {
posY = event.clientY -startY - tooltipHeight - offset;
}
this.tooltipStyle.left = posX/this.scaleNum;
this.tooltipStyle.top = posY/this.scaleNum;
}
}
}
</script>
<style lang="scss" scoped>
p{
margin: 0;
}
.centerTopBox {
width: 1041px;
height: 600px;
background: url('../../../../assets/images/dataBoard/center-top.png') no-repeat;
background-size: 100%;
position: absolute;
left: 440px;
top:113px;
overflow: hidden;
.videoStyle {
width: 1920px;
height: 1080px;
position: absolute;
left:-440px;
top:-113px;
}
.eqTipBox {
width: 230px;
height: 170px;
background: url('../../../../assets/images/dataBoard/eq-tip.png') no-repeat;
background-size: 100%;
position: absolute;
padding-top: 10px;
z-index: 1;
span{
display: inline-block;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 1px;
vertical-align: middle;
}
.eqTipTitle {
width: 112px;
height: 30px;
padding-left: 15px;
}
.eqTipNum {
width: 112px;
height: 30px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.eqBox {
span{
display: inline-block;
// border: 1px solid red;
position: absolute;
}
}
.centerTopTopBox {
padding-left: 48px;
position: absolute;
top:30px;
.num {
width: 203px;
height: 65px;
line-height: 65px;
padding-left: 10px;
box-sizing: border-box;
font-weight: 500;
font-size: 38px;
color: #FFFFFF;
text-shadow: 0px 5px 2px rgba(0,0,0,0.62);
text-align: center;
position: absolute;
top:0px;
}
.title {
font-weight: 500;
font-size: 20px;
color: #00C8F7;
letter-spacing: 2px;
text-shadow: 0px 5px 2px rgba(0,0,0,0.62);
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<header class="dataBoardHeader">
<div class='topTitle'></div>
<!-- <div class='time-chsoose'>
<span class='title'>时间选择</span>
<span class='time-show'>{{time}}</span>
<el-date-picker
v-model="time"
type="date"
value-format='yyyy-MM-dd'
:clearable='false'
style='position: absolute;right: 0px;width: 119px;top:-2px;opacity: 0;'
>
</el-date-picker>
</div> -->
<div
type="text"
class="screen-btn"
:title="isFullScreen?'退出全屏':'全屏'"
@click="changeFullScreen"
>
<svg-icon v-if="isFullScreen" icon-class="unFullScreenView" />
<svg-icon v-else icon-class="fullScreenView" />
</div>
</header>
</template>
<script>
// import moment from 'moment'
export default {
name: 'DataBoardHeader',
props: {
isFullScreen: {
type: Boolean,
default: () => {
return false
}
},
},
data() {
return {
// time: moment().format('YYYY-MM-DD')
}
},
computed: {},
methods: {
changeFullScreen() {
this.$emit('screenfullChange')
},
exportPDF() {
this.$emit('exportPDF')
}
}
}
</script>
<style scoped lang="scss">
.dataBoardHeader {
background: url('../../../../assets/images/dataBoard/head.png') no-repeat;
height: 99px;
background-size: 100%;
background-position: 0 0;
position: relative;
.topTitle{
width: 465px;
height: 77px;
background: url('../../../../assets/images/dataBoard/data-board-title.png') no-repeat;
background-size: 100%;
position: absolute;
left: 731px;
top:19px;
}
.time-chsoose {
width: 212px;
height: 33px;
background: url('../../../../assets/images/dataBoard/time-choose.png') no-repeat;
background-size: 100%;
position: absolute;
right: 63px;
top:49px;
.title {
font-size: 18px;
color: #082049;
letter-spacing: 1px;
position: relative;
top: 4px;
left: 8px
}
.time-show {
font-size: 18px;
color: #FFFFFF;
line-height: 18px;
position: relative;
top: 4px;
left: 28px;
}
}
.screen-btn{
color: #00fff0;
height: 32px;
line-height: 32px;
font-size: 32px;
position: absolute;
right: 22px;
top:47px;
}
}
</style>

View File

@@ -0,0 +1,284 @@
<template>
<div class='leftBottomBox'>
<div class='title'>
<svg-icon icon-class="dataBoard2" class='icon'/>
<span>投入产出及良品率</span>
</div>
<div v-if='xData.length === 0' class='noData'>暂无数据</div>
<div v-else>
<div class="top_legend">
<span class="chart_legend_icon1">投入</span>
<span class="chart_legend_icon2">产出</span>
<span><span class="chart_legend_icon3"></span>良品率</span>
</div>
<div id='inOutputChart' style='width: 400px;height: 290px;'></div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
name: 'LeftBottom',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
this.xData = []
this.inputData = []
this.outputData = []
this.goodRateData = []
val.monthBar && val.monthBar.forEach(item => {
this.xData.push(item.dataType)
this.inputData.push(item.inputNum)
this.outputData.push(item.outputNum)
this.goodRateData.push(item.goodRate)
})
this.initChart();
}
},
data() {
return {
chartDom: '',
chart: '',
xData:[],
inputData:[],
outputData:[],
goodRateData:[],
}
},
mounted() {},
methods: {
initChart() {
if (
this.chart !== null &&
this.chart !== '' &&
this.chart !== undefined
) {
this.chart.dispose() // 页面多次刷新会出现警告Dom已经初始化了一个实例这是销毁实例
}
this.chartDom = document.getElementById('inOutputChart')
this.chart = echarts.init(this.chartDom)
var option;
option = {
grid: { top: 40, right: 10, bottom: 5, left: 10, containLabel: true },
legend: {
show: false,
},
xAxis: {
type: "category",
data: this.xData,
axisLabel: {
color: "#fff",
fontSize: 10,
interval: 0,
rotate:30
},
axisTick: { show: false },
axisLine: {
lineStyle: {
width: 2,
color: "#5982B2",
},
},
},
yAxis: [{
name: "单位/片",
nameTextStyle: {
color: "#DFF1FE",
fontSize: 12,
},
type: "value",
axisLabel: {
color: "#DFF1FE",
fontSize: 12,
formatter: "{value}",
},
axisLine: {
show: true,
lineStyle: {
width: 2,
color: "#5982B2",
},
},
splitLine: {
lineStyle: {
width: 2,
color: "#5982B2",
},
},
},{
name: "良品率/%",
nameTextStyle: {
color: "#DFF1FE",
fontSize: 12,
},
type: "value",
axisLabel: {
color: "#DFF1FE",
fontSize: 12,
formatter: "{value}",
},
axisLine: {
show: true,
lineStyle: {
width: 2,
color: "#5982B2",
},
},
splitLine: {
lineStyle: {
width: 2,
color: "#5982B2",
},
},
}],
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
className: "qhd-chart-tooltip",
show: true,
},
series: [
{
data: this.inputData,
type: "bar",
barWidth: 10,
barGap:0,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(157, 234, 245, 1)' },
{ offset: 1, color: 'rgba(110, 249, 222, 1)' },
]),
},
},
{
data:this.outputData,
type: "bar",
barWidth: 10,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(92, 183, 255, 1)' },
{ offset: 1, color: 'rgba(54, 75, 254, 1)' },
]),
},
},
{
data:this.goodRateData,
type: "line",
yAxisIndex: 1,
symbol:'circle',
symbolSize: 7,
color:'rgba(18, 255, 245, 1)',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(18, 255, 245, 0.8)' },
{ offset: 0.2, color: 'rgba(18, 255, 245, 0.2)' },
{ offset: 0.4, color: 'rgba(18, 255, 245, 0)' },
]),
}
}
],
}
option && this.chart.setOption(option);
}
}
}
</script>
<style lang='scss' scoped>
.leftBottomBox {
width: 402px;
height: 332px;
background: url('../../../../assets/images/dataBoard/left-bottom.png') no-repeat;
background-size: 100%;
position: absolute;
left: 23px;
bottom:23px;
.title {
margin: 7px 0 0 15px;
.icon {
width: 32px;
height: 32px;
margin-right: 5px;
vertical-align:middle;
margin-top: 5px;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.top_legend {
color: #fff;
font-size: 14px;
position: absolute;
left:120px;
top:50px;
.chart_legend_icon1{
margin-right: 10px;
}
.chart_legend_icon2{
margin-right: 14px;
}
.chart_legend_icon1:before {
display: inline-block;
content: "";
width: 10px;
height: 10px;
margin-right: 5px;
border-radius: 2px;
background: #73F8E0;
}
.chart_legend_icon2:before {
display: inline-block;
content: "";
width: 10px;
height: 10px;
margin-right: 5px;
border-radius: 2px;
background: #497EFF;
}
.chart_legend_icon3 {
display: inline-block;
width: 8px;
height: 8px;
margin-right: 8px;
border-radius: 4px;
background: #73F8E0;
position:relative;
}
.chart_legend_icon3:before {
display: inline-block;
content: "";
width: 16px;
height:2px;
background: #73F8E0;
position:absolute;
top:3px;
left:-4px;
}
}
}
.noData {
font-size: 24px;
text-align: center;
padding-top: 100px;
}
</style>
<style>
.qhd-chart-tooltip {
background: #0a2b4f77 !important;
border: none !important;
backdrop-filter: blur(12px);
}
.qhd-chart-tooltip * {
color: #fff !important;
}
</style>

View File

@@ -0,0 +1,162 @@
<template>
<div class='leftTopBox'>
<div class='title'>
<svg-icon icon-class="dataBoard1" class='icon'/>
<span>近期数据对比</span>
</div>
<div class='title-split'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'>本日</span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div>
<div class='data-box'>
<div class='left-icon' style="top:19px;">
<img src="../../../../assets//images/dataBoard/leftTopIcon1.png" alt="" width='54'>
<div>总投入</div>
</div>
<div class='left-icon' style="top:139px;">
<img src="../../../../assets//images/dataBoard/leftTopIcon2.png" alt="" width='48'>
<div>总生产</div>
</div>
<div class='right-data' style="top:15px;">
<p><span class='num'>{{todayData?.inputNum ? formatThousands(todayData.inputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{todayData?.inputArea ? formatThousands(todayData.inputArea) : '-'}}</span><span class='text'>面积/</span></p>
</div>
<div class='right-data' style="top:132px;">
<p><span class='num'>{{todayData?.outputNum ? formatThousands(todayData.outputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{todayData?.outputArea ? formatThousands(todayData.outputArea) : '-'}}</span><span class='text'>面积/</span></p>
</div>
</div>
<div class='title-split'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'>昨日</span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div>
<div class='data-box'>
<div class='left-icon' style="top:19px;">
<img src="../../../../assets//images/dataBoard/leftTopIcon1.png" alt="" width='54'>
<div>总投入</div>
</div>
<div class='left-icon' style="top:139px;">
<img src="../../../../assets//images/dataBoard/leftTopIcon2.png" alt="" width='48'>
<div>总生产</div>
</div>
<div class='right-data' style="top:15px;">
<p><span class='num'>{{yesterdayData?.inputNum ? formatThousands(yesterdayData.inputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{yesterdayData?.inputArea ? formatThousands(yesterdayData.inputArea) : '-'}}</span><span class='text'>面积/</span></p>
</div>
<div class='right-data' style="top:132px;">
<p><span class='num'>{{yesterdayData?.outputNum ? formatThousands(yesterdayData.outputNum) : '-'}}</span><span class='text'>片数</span></p>
<p><span class='num'>{{yesterdayData?.outputArea ? formatThousands(yesterdayData.outputArea) : '-'}}</span><span class='text'>面积/</span></p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'LeftTop',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
val.todayAndYesterday && val.todayAndYesterday.forEach(item => {
if (item.dataType === "今日") {
this.todayData = item
} else {
this.yesterdayData = item
}
})
}
},
data() {
return {
todayData:{},
yesterdayData:{}
}
},
methods: {}
}
</script>
<style lang='scss' scoped>
.leftTopBox {
width: 402px;
height: 600px;
background: url('../../../../assets/images/dataBoard/left-top.png') no-repeat;
background-size: 100%;
position: absolute;
left: 23px;
top:113px;
.title {
margin: 10px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.title-split {
margin-left: 95px;
margin-top: 10px;
.text{
display: inline-block;
width: 100px;
font-size: 22px;
color: #01CFCC;
line-height: 24px;
letter-spacing: 7px;
text-align: center;
}
}
.data-box {
width: 358px;
height: 230px;
background: url('../../../../assets/images/dataBoard/leftTopDataBox.png') no-repeat;
background-size: 100%;
margin-top: 8px;
position: relative;
left: 24px;
.left-icon {
font-weight: 500;
font-size: 16px;
color: #9DEAF5;
line-height: 22px;
letter-spacing: 1px;
text-shadow: 0px 4px 2px rgba(0,0,0,0.62);
position: absolute;
left: 30px;
}
.right-data {
position: absolute;
left: 100px;
p{
margin: 0;
};
.num {
font-weight: 500;
font-size: 32px;
color: #FFFFFF;
text-shadow: 0px 4px 2px rgba(0,0,0,0.62);
margin-right: 5px;
}
.text {
font-weight: 500;
font-size: 16px;
color: #FFFFFF;
letter-spacing: 1px;
text-shadow: 0px 4px 2px rgba(0,0,0,0.62);
}
}
}
}
</style>

View File

@@ -0,0 +1,198 @@
<template>
<div class='rightBottomBox'>
<div class='title'>
<svg-icon icon-class="dataBoard3" class='icon'/>
<span>本月<span class='dotted'></span>班组生产排名</span>
</div>
<div class='rankingLeft'>
<div class='rankingLeftTitle'>产量</div>
<div class='rankingTypeBox'>
<div style='margin-bottom: 10px;' v-for='(item,index) in teamProductionRank' :key='index'>
<img :src="require(`../../../../assets/images/dataBoard/ranking${index+1}.png`)" alt="" width="50">
<div class='rankingTextBox'>
<p class='text1'>{{item.name}}/</p>
<p class='text2'>{{item?.value ? formatThousands(item.value) : '-'}}</p>
</div>
</div>
</div>
</div>
<div class='rankingRight'>
<div class='rankingRightTitle'>
成品率
</div>
<div class='rankingTypeBox' style='left: 120px;'>
<div style='margin-bottom: 10px;' v-for='(item,index) in teamRateRank' :key='index'>
<img :src="require(`../../../../assets/images/dataBoard/ranking${index+1}.png`)" alt="" width="50">
<div class='rankingTextBox'>
<p class='text1'>{{item.name}}</p>
<p class='text2'>{{Math.trunc(item.value)}}<span style='font-size: 20px;'>.{{item.value1}}%</span></p>
</div>
</div>
<!-- <div>
<img src="../../../../assets//images//dataBoard/ranking2.png" alt="" width='50'>
<div class='rankingTextBox'>
<p class='text1'>班组1</p>
<p class='text2'>94<span style='font-size: 20px;'>.13%</span></p>
</div>
</div>
<div>
<img src="../../../../assets//images//dataBoard/ranking3.png" alt="" width='50'>
<div class='rankingTextBox'>
<p class='text1'>班组1</p>
<p class='text2'>90<span style='font-size: 20px;'>.42%</span></p>
</div>
</div> -->
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RightBottom',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
this.teamProductionRank = []
this.teamRateRank = []
val.teamProductionRank && val.teamProductionRank.forEach(item => {
let obj = {
name: item.name,
value: item.value,
rank: item.rank,
}
this.teamProductionRank.push(obj)
})
val.teamRateRank && val.teamRateRank.forEach(item => {
let obj = {
name: item.name,
value: item.value,
rank: item.rank,
value1: this.getPositiveDecimal(item.value)
}
this.teamRateRank.push(obj)
})
}
},
data() {
return {
teamProductionRank:[],
teamRateRank:[]
}
},
mounted() {},
methods: {
getPositiveDecimal(num) {
const str = Math.abs(num).toString();
const [, decimal] = str.split('.');
return decimal || '00';
}
}
}
</script>
<style lang='scss' scoped>
p{
margin: 0;
}
.rightBottomBox {
width: 755px;
height: 332px;
background: url('../../../../assets/images/dataBoard/right-bottom.png') no-repeat;
background-size: 100%;
position: absolute;
right: 23px;
bottom:23px;
.title {
margin: 10px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
.dotted {
display: inline-block;
width: 4px;
height: 4px;
border-radius: 2px;
background-color: #52FFF1;
margin: 0 8px;
}
}
}
.rankingLeft {
width: 405px;
height: 265px;
background: url('../../../../assets/images/dataBoard/rankingBg1.png') no-repeat;
background-size: 100%;
position: absolute;
left: 12px;
top: 48px;
.rankingLeftTitle {
width: 24px;
height: 73px;
font-weight: 500;
font-size: 24px;
color: #52FFF1;
line-height: 55px;
position: relative;
left: 37px;
top: 63px;
}
}
.rankingRight {
width: 338px;
height: 265px;
background: url('../../../../assets/images/dataBoard/rankingBg2.png') no-repeat;
background-size: 100%;
position: absolute;
right: 12px;
top: 48px;
.rankingRightTitle {
width: 24px;
height: 132px;
font-weight: 500;
font-size: 24px;
color: #52FFF1;
line-height: 55px;
position: relative;
left: 37px;
top: 43px;
}
}
.rankingTypeBox {
position: absolute;
left: 100px;
top:13px;
.rankingTextBox {
display: inline-block;
margin-left: 16px;
vertical-align: top;
.text1 {
font-weight: 500;
font-size: 17px;
color: #FFFFFF;
line-height: 22px;
letter-spacing: 1px;
padding-top: 5px;
}
.text2 {
font-weight: 500;
font-size: 32px;
color: #FFFFFF;
line-height: 22px;
margin-top: 8px;
}
}
}
}
</style>

View File

@@ -0,0 +1,352 @@
<template>
<div class='rightTopBox'>
<div class='title'>
<svg-icon icon-class="dataBoard4" class='icon'/>
<span>缺陷情况</span>
</div>
<div class='title-split'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'>总数</span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div>
<div class='dataBox'>
<div class='row'>
<div class='box' style='width: 105px;'>
<p class='name'>
<span style='margin-right: 3px;'>当日</span>
<img v-show='nokSumDet.today >= nokSumDet.yesterday' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
<img v-show='nokSumDet.today < nokSumDet.yesterday' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{nokSumDet?.today ? formatThousands(nokSumDet.today) : '-'}}</p>
</div>
<div class='box' style='width: 115px;'>
<p class='name'>
<span style='margin-right: 3px;'>本月</span>
<img v-show='nokSumDet.month >= nokSumDet.lastMonth' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
<img v-show='nokSumDet.month < nokSumDet.lastMonth' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{nokSumDet?.month ? formatThousands(nokSumDet.month) : '-'}}</p>
</div>
<div class='box' style='width: 110px;'>
<p class='name'>
<span style='margin-right: 3px;'>本年</span>
<img v-show='nokSumDet.year >= nokSumDet.lastYear' src="../../../../assets/images/dataBoard/arrUp.png" alt="" width='5' height='15'>
<img v-show='nokSumDet.year < nokSumDet.lastYear' src="../../../../assets/images/dataBoard/arrDown.png" alt="" width='5' height='15'>
</p>
<p class='num'>{{nokSumDet?.year ? formatThousands(nokSumDet.year) : '-'}}</p>
</div>
</div>
<div class='row' style='padding-top: 5px;'>
<div class='box' style='width: 105px;'>
<p class='name'>
<span>昨日</span>
</p>
<p class='num'>{{nokSumDet?.yesterday ? formatThousands(nokSumDet.yesterday) : '-'}}</p>
</div>
<div class='box' style='width: 115px;'>
<p class='name'>
<span>上月</span>
</p>
<p class='num'>{{nokSumDet?.lastMonth ? formatThousands(nokSumDet.lastMonth) : '-'}}</p>
</div>
<div class='box' style='width: 110px;'>
<p class='name'>
<span>上年</span>
</p>
<p class='num'>{{nokSumDet?.lastYear ? formatThousands(nokSumDet.lastYear) : '-'}}</p>
</div>
</div>
</div>
<div class='title-split2'>
<img src="../../../../assets//images/dataBoard/leftbar.png" alt="">
<span class='text'>全厂缺陷汇总</span>
<img src="../../../../assets//images/dataBoard/rightbar.png" alt="">
</div>
<div id='defectSumChart' style='width: 400px;height: 315px;'></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
const colors = [
"rgb(39, 96, 255)",
"rgb(91, 155, 255)",
"rgb(153, 214, 108)",
"rgb(18, 255, 245)",
"rgb(221, 177, 18)",
];
export default {
name: 'RightTop',
props: {
dataObj: {
type: Object,
default: () => {}
}
},
watch: {
dataObj(val) {
this.nokSumDet = val.nokSumDet
this.dataProps = []
val.nokPieDet && val.nokPieDet.forEach(item => {
let obj = {
value: item.num,
name: item.type
}
this.dataProps.push(obj)
})
this.initChart()
}
},
data() {
return {
nokSumDet:{},
chartDom: '',
chart: '',
dataProps:[],
rangArrValue:[],
dataList:[],
totalValue:0
}
},
mounted() {},
methods: {
initChart() {
if (
this.chart !== null &&
this.chart !== '' &&
this.chart !== undefined
) {
this.chart.dispose() // 页面多次刷新会出现警告Dom已经初始化了一个实例这是销毁实例
}
this.chartDom = document.getElementById('defectSumChart')
this.chart = echarts.init(this.chartDom)
var option;
this.getPersonnelList(this.dataProps)
option = {
color: colors,
graphic: [
{
type: "text",
left: "center",
top: "44%",
style: {
text: this.totalValue,
fill: "#fff",
width: 150,
height: 44,
fontSize: 31,
fontWeight: 400,
},
},
{
type: "text",
left: "center",
top: "55%",
style: {
text: "总数/件",
fill: "rgba(255, 255, 255, 0.70)",
width: 32,
height: 16,
fontSize: 16,
fontWeight: 400,
},
},
],
series: [
{
name: "产线缺陷分类",
type: "pie",
radius: ["45%", "60%"],
center: ["50%", "50%"],
label:{
formatter:function(params){
return `{color${params.dataIndex}|${params.percent}%}\n{style2|${params.name}}`
},
rich:{
color0:{color:'rgb(39, 96, 255)',fontSize:22},
color1:{color:'rgb(91, 155, 255)',fontSize:22},
color2:{color:'rgb(153, 214, 108)',fontSize:22},
color3:{color:'rgb(18, 255, 245)',fontSize:22},
color4:{color:'rgb(221, 177, 18)',fontSize:22},
color5:{color:'rgb(39, 96, 255)',fontSize:22},
color6:{color:'rgb(91, 155, 255)',fontSize:22},
color7:{color:'rgb(153, 214, 108)',fontSize:22},
color8:{color:'rgb(18, 255, 245)',fontSize:22},
color9:{color:'rgb(221, 177, 18)',fontSize:22},
style2:{
color:'#fff',
fontSize:14
}
}
},
data: this.dataList,
},
],
}
option && this.chart.setOption(option);
},
getCoordinates(startArc, endArc) {
const posi = [
Math.sin(startArc),
-Math.cos(startArc),
Math.sin(endArc),
-Math.cos(endArc),
];
const dx = posi[2] - posi[0];
const dy = posi[3] - posi[1];
return this.getLocation(dx, dy);
},
getLocation(dx,dy) {
const tanV = dx / dy;
const directSign = Math.abs(tanV) < 1;
const t = directSign ? tanV : 1 / tanV;
const sign1 = t > 0 ? 1 : -1;
const sign2 = dx > 0 ? 1 : -1;
const sign = directSign ? sign1 * sign2 : sign2;
const group1 = [0.5 - (sign * t) / 2, 0.5 + (sign * t) / 2];
const group2 = sign > 0 ? [0, 1] : [1, 0];
const group = [...group1, ...group2];
const keys = directSign ? ["x", "x2", "y", "y2"] : ["y", "y2", "x", "x2"];
let res = {};
keys.forEach((k, idx) => {
res[k] = group[idx];
});
return res;
},
async getPersonnelList(dataProps){
this.rangArrValue = [];
this.totalValue = 0;
this.dataList = [];
this.totalValue = dataProps.reduce(
(total, value) => total + value.value,
0
);
let cacheNum = 0;
for (let i = 0; i < dataProps.length; i++) {
const endNum = cacheNum + dataProps[i].value;
this.rangArrValue.push([cacheNum, endNum]);
cacheNum = endNum;
}
const angleArr = this.rangArrValue.map((arr) =>
arr.map((num) => (num / this.totalValue) * Math.PI * 2)
);
this.dataList = dataProps.map((item, index) => {
const range = this.getCoordinates(angleArr[index][0], angleArr[index][1]);
const startColor = colors[index%5];
const color = {
type: "linear",
x: range.x,
x2: range.x2,
y: range.y,
y2: range.y2,
colorStops: [
{
offset: 0,
color: startColor, // 起始颜色
},
{
offset: 1,
color: `${startColor.substring(0, startColor.length - 1)}, 0.2)`, // 终点颜色
},
],
global: false,
};
return {
name: item.name,
value: item.value,
itemStyle: {
color: color,
},
};
});
}
}
}
</script>
<style lang='scss' scoped>
p{
margin: 0;
}
.rightTopBox {
width: 402px;
height: 600px;
background: url('../../../../assets/images/dataBoard/left-top.png') no-repeat;
background-size: 100%;
position: absolute;
right: 23px;
top:113px;
.title {
margin: 10px 0 0 15px;
.icon {
width: 33px;
height: 33px;
margin-right: 5px;
vertical-align:middle;
}
span {
font-size: 24px;
color: #52FFF1;
line-height: 24px;
vertical-align:middle;
}
}
.title-split {
margin-left: 95px;
margin-top: 10px;
.text{
display: inline-block;
width: 100px;
font-size: 22px;
color: #01CFCC;
line-height: 24px;
letter-spacing: 7px;
text-align: center;
}
}
.title-split2 {
margin-left: 66px;
margin-top: 20px;
.text{
display: inline-block;
width: 160px;
font-size: 22px;
color: #01CFCC;
line-height: 24px;
letter-spacing: 2px;
text-align: center;
}
}
.dataBox {
width: 358px;
height: 160px;
background: url('../../../../assets/images/dataBoard/defectNum.png') no-repeat;
background-size: 100%;
position: relative;
left: 21px;
top:8px;
padding-left: 24px;
.row {
.box{
display: inline-block;
padding-top: 10px;
.name {
font-weight: 500;
font-size: 20px;
color: #FFFFFF;
letter-spacing: 2px;
}
.num {
font-weight: 500;
font-size: 28px;
color: #FFFFFF;
text-shadow: 0px 5px 2px rgba(0,0,0,0.62);
}
}
}
}
}
</style>

View File

@@ -1,100 +1,234 @@
<!--
filename: index.vue
author: liubin
date: 2023-10-11 09:32:04
description: 设备看板
-->
<template>
<div ref="dataBoard" class="data-board">
<el-button type="text" @click="goback" class="go-back--btn">返回</el-button>
<div ref='dataBoardBoxB' class='dataBoardBoxB'>
<div id="dataBoardBox" class='dataBoardBox' style="width: 1920px;height: 1080px;" :style="{ transform: 'scale(' + scaleNum + ')' }">
<DataBoardHeader
:is-full-screen="isFullScreen"
@screenfullChange="screenfullChange"
/>
<LeftTop :dataObj='dataObj'/>
<LeftBottom :dataObj='dataObj'/>
<CenterTop :scaleNum='scaleNum' :dataObj='dataObj'/>
<CenterBottomL :dataObj='dataObj'/>
<CenterBottomR :dataObj='dataObj'/>
<RightTop :dataObj='dataObj'/>
<RightBottom :dataObj='dataObj'/>
</div>
</div>
</template>
<script>
import DataBoardHeader from './components/Header.vue';
import LeftTop from './components/LeftTop.vue';
import LeftBottom from './components/LeftBottom.vue';
import CenterTop from './components/CenterTop.vue';
import CenterBottomL from './components/CenterBottomL.vue';
import CenterBottomR from './components/CenterBottomR.vue';
import RightTop from './components/RightTop.vue';
import RightBottom from './components/RightBottom.vue';
import { debounce } from '@/utils/debounce';
import screenfull from 'screenfull';
import { getAccessToken } from '@/utils/auth';
import store from "@/store";
export default {
name: 'DataBoard',
components: {},
components: { DataBoardHeader,LeftTop,LeftBottom,CenterTop,CenterBottomL,CenterBottomR,RightTop,RightBottom },
props: {},
data() {
return {
appMain: null, // dom
parentStyle: {
margin: '8px 14px 0px 16px',
minHeight: 'calc(100vh - 120px - 8px)',
}, // object
mainFooter: null, // dom
isFullScreen: false,
scaleNum: 0.8,
dataObj:{},
sseReader: null, // 保存流读取器
abortController: null, // 用于中止 fetch 请求
retryCount: 0, // 当前重试次数
isDestroyed: false // 标记组件是否已销毁
};
},
// mounted() {
// this.$nextTick(() => {
// this.modify();
// });
// },
// activated() {
// this.modify();
// },
// deactivated() {
// this.recover();
// },
// beforeDestroy() {
// this.recover();
// },
methods: {
// modify() {
// // 在这个页面临时修改下父类的margin结束时需还原
// this.appMain = document.querySelector('.app-main');
// // this.appMain.style.minHeight = 'calc(100vh - 90px)';
// this.appMain.style.margin = 0;
// // 在这个页面临时删除 main-footer 元素,结束时需还原
// // this.mainFooter = document.querySelector('.main-footer').cloneNode(true);
// // document.querySelector('.main-footer').remove();
// this.$refs.dataBoard.classList.add('data-board');
// },
// recover() {
// this.$refs.dataBoard.classList.remove('data-board');
// this.$nextTick(() => {
// this.appMain.style.margin = this.parentStyle.margin;
// // this.appMain.style.minHeight = this.parentStyle.minHeight;
// // this.appMain.insertAdjacentElement('afterend', this.mainFooter);
// });
// },
goback() {
this.$router.go(-1);
created() {
this.init()
},
mounted() {
this.boxReset = debounce(() => {
this.resetSize()
}, 300)
this.boxReset()
window.addEventListener('resize', () => {
this.boxReset()
})
this.getData()
},
beforeDestroy() {
this.closeSSE();
},
computed: {
sidebarOpened() {
return this.$store.state.app.sidebar.opened
}
},
watch: {
sidebarOpened(newVal, oldVal) {
this.boxReset()
}
},
methods: {
async getData() {
let _this = this;
if (_this.isDestroyed) return;
const url = process.env.VUE_APP_BASE_API+'/admin-api/monitoring/message/subscribe/'+store.getters.userId+'-'+Date.now();
const token = getAccessToken()
const headers = new Headers({
'Authorization': `Bearer ${token}`,
'Content-Type': 'text/event-stream'
});
try {
// 创建中止控制器
this.abortController = new AbortController();
// 发起 fetch 请求(替换为你的接口地址)
const response = await fetch(url, {
method: 'GET',
headers: headers,
signal: _this.abortController.signal // 绑定中止信号
});
// 获取流读取器
_this.sseReader = response.body.getReader();
const decoder = new TextDecoder();
// 持续读取流数据
while (true) {
const { done, value } = await _this.sseReader.read();
if (done) {
console.log('SSE 连接正常关闭');
_this.handleReconnect(); // 触发重连
break;
}
// 处理 SSE 事件数据
const data = decoder.decode(value);
console.log('收到消息:', data);
if (_this.isValidData(data)){
_this.upDateMsg(data);
}
}
} catch (error) {
// 主动中止的请求不报错
if (error.name === 'AbortError') return;
console.error('SSE 连接异常:', error);
_this.handleReconnect(); // 触发重连
}
},
closeSSE() {
this.isDestroyed = true; // 标记销毁
if (this.abortController) {
this.abortController.abort(); // 中止 fetch 请求
}
if (this.sseReader) {
this.sseReader.cancel(); // 关闭流读取器
this.sseReader = null;
}
console.log('SSE 连接已强制关闭');
},
handleReconnect() {
if (this.isDestroyed) return;
// 指数退避策略(最大重试 5 次)
const maxRetries = 5;
if (this.retryCount < maxRetries) {
const delay = Math.pow(2, this.retryCount) * 1000;
setTimeout(() => {
this.retryCount++;
this.initSSE();
}, delay);
} else {
console.error('SSE 重连次数已达上限');
}
},
isValidData (data) {
return data.trim().startsWith('data:{') && !data.includes('heartbeat');
},
upDateMsg(data) {
const jsonStr = data.replace(/^data:/, '').trim();
try {
const dataObj = JSON.parse(jsonStr);
this.dataObj = dataObj
console.log('dataObj',this.dataObj)
} catch (e) {
console.error('JSON 解析失败:', e);
}
},
change() {
this.isFullScreen = screenfull.isFullscreen
},
init() {
if (!screenfull.isEnabled) {
this.$message({
message: 'you browser can not work',
type: 'warning'
})
return false
}
screenfull.on('change', this.change)
},
destroy() {
if (!screenfull.isEnabled) {
this.$message({
message: 'you browser can not work',
type: 'warning'
})
return false
}
screenfull.off('change', this.change)
},
// 全屏
screenfullChange() {
if (!screenfull.isEnabled) {
this.$message({
message: 'you browser can not work',
type: 'warning'
})
return false
}
screenfull.toggle(this.$refs.dataBoardBoxB)
},
resetSize() {
const dataBoardBox = document.getElementById('dataBoardBox')
const rw = parseFloat(window.innerWidth)
const rh = parseFloat(window.innerHeight)
const bw = parseFloat(dataBoardBox.style.width)
const bh = parseFloat(dataBoardBox.style.height)
let wx = 0
let hy = 0
if (screenfull.isFullscreen) {
wx = rw / bw
hy = rh / bh
} else {
if (this.$store.state.app.sidebar.opened) {
wx = (rw - 283) / bw
} else {
wx = (rw - 88) / bw
}
hy = (rh - 150) / bh
}
this.scaleNum = wx < hy ? wx : hy
},
},
};
</script>
<style scoped lang="scss">
.data-board {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('../../../assets/images/DataBoard.png') 100% 100% / contain
no-repeat;
}
.go-back--btn {
position: fixed;
top: 28px;
left: 24px;
color: #fff;
font-size: 18px;
letter-spacing: 6px;
&:hover {
text-decoration: underline;
.dataBoardBoxB {
width: 100%;
height: calc(100vh - 150px);
position: relative;
overflow: auto;
.dataBoardBox {
position: absolute;
transform-origin: 16px 8px;
font-size: 16px;
top: 0px;
left: 0px;
background: url('../../../assets/images/dataBoard/background.png') no-repeat;
background-size: cover;
background-position: 0 0;
overflow: auto;
}
}
&::after {
content: "\2BAA";
position: absolute;
top: 6px;
right: -26px;
font-size: 24px;
}
}
</style>