增加设备综合数据看板页面

master
Yangwl 3 months ago
parent e0f5ae616e
commit e8edb52bda

@ -69,4 +69,26 @@ export function getdictlist(data) {
method: 'get',
params: data
});
}
}
export function getworkFaultReason(data) {
return request({
url: '/device/deviceInterface/getworkFaultReason',
method: 'get',
params: data
});
}
export function getworkFaultDesc(data) {
return request({
url: '/device/deviceInterface/getworkFaultDesc',
method: 'get',
params: data
});
}
export function getWave(data) {
return request({
url: '/device/deviceInterface/getWave',
method: 'get',
params: data
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

@ -33,6 +33,7 @@ export const constantRoutes = [
{path: '/cs', component: () => import('@/views/cs/index.vue')},
{path: '/cs2', component: () => import('@/views/cs/index2.vue')},
{path: '/cs3', component: () => import('@/views/cs/index3.vue')},
{path: '/cs5', component: () => import('@/views/cs/index5.vue')},
{path: '/cs6', component: () => import('@/views/cs/index4.vue')},
{path: '/cs3_1', component: () => import('@/views/cs/index3_1.vue')},
{

@ -0,0 +1,99 @@
<template>
<div id="data-view">
<dv-full-screen-container>
<top-header />
<div class="main-content">
<!-- <digital-flop />-->
<cards />
<div class="block-left-right-content">
<ranking-board />
<div class="block-top-bottom-content">
<div class="block-top-content">
<rose-chart />
<water-level-chart />
<scroll-board />
</div>
</div>
</div>
</div>
</dv-full-screen-container>
</div>
</template>
<script>
import topHeader from './index5/topHeader'
import digitalFlop from './index5/digitalFlop'
import rankingBoard from './index5/rankingBoard'
import roseChart from './index5/roseChart'
import waterLevelChart from './index5/waterLevelChart'
import scrollBoard from './index5/scrollBoard'
import cards from './index5/cards'
export default {
name: 'DataView',
components: {
topHeader,
digitalFlop,
rankingBoard,
roseChart,
waterLevelChart,
scrollBoard,
cards
},
data () {
return {}
},
methods: {}
}
</script>
<style lang="less">
#data-view {
width: 100%;
height: 100%;
background-color: #030409;
color: #fff;
#dv-full-screen-container {
background-image: url("~@/assets/board/bg.png");
background-size: 100% 100%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
display: flex;
flex-direction: column;
}
.block-left-right-content {
flex: 1;
display: flex;
margin-top: 20px;
}
.block-top-bottom-content {
flex: 1;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding-left: 20px;
}
.block-top-content {
height: 100%;
display: flex;
flex-grow: 0;
box-sizing: border-box;
padding-bottom: 20px;
}
}
</style>

@ -0,0 +1,278 @@
<template>
<div id="cards">
<div
class="card-item"
v-for="(card, i) in cards"
:key="card.title"
>
<div class="card-header">
<div class="card-header-left">{{ card.title }}</div>
<div class="card-header-right">{{ '0' + (i + 1) }}</div>
</div>
<dv-charts class="ring-charts" :option="card.ring" />
<div class="card-footer">
<div class="card-footer-item">
<div class="footer-title">{{ card.desc }}</div>
<div class="footer-detail">
<dv-digital-flop :config="card.total" style="width:70%;height:35px;" />
</div>
</div>
<div class="card-footer-item">
<div class="footer-title">已处理</div>
<div class="footer-detail">
<dv-digital-flop :config="card.num" style="width:70%;height:35px;" />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {
getCompletedRate,
} from "@/api/kanban/spectaculars";
export default {
name: 'Cards',
data () {
return {
cards: [
{
title: "点检",
desc: "今日",
total: {
number: [],
content: '{nt}',
textAlign: 'right',
style: {
fill: '#ea6027',
fontWeight: 'bold'
}
},
num: {
number: [],
content: '{nt}',
textAlign: 'right',
style: {
fill: '#26fcd8',
fontWeight: 'bold'
}
},
ring: {
series: [
{
type: 'gauge',
startAngle: -Math.PI / 2,
endAngle: Math.PI * 1.5,
arcLineWidth: 13,
radius: '80%',
data: [],
axisLabel: {
show: false
},
axisTick: {
show: false
},
pointer: {
show: false
},
backgroundArc: {
style: {
stroke: '#224590'
}
},
details: {
show: true,
formatter: '完成占比{value}%',
style: {
fill: '#1ed3e5',
fontSize: 20
}
}
}
],
color: ['#03d3ec']
}
},
],
equipmentinfo: {},
}
},
methods: {
createData () {
// const { randomExtend } = this
const _this = this;
const titles = ['点检', '巡检', '保养', '维修'];
const dataMap = { //
'点检': ['spotInspectionTotal', 'spotInspectionFinish'],
'巡检': ['inspectionTotal', 'inspectionFinish'],
'保养': ['maintenanceTotal', 'maintenanceFinish'],
'维修': ['repairTotal', 'repairFinish']
};
getCompletedRate({
poolName: "ds_1000",
}).then((response) => {
if (response.data) {
_this.equipmentinfo = response.data;
this.cards = titles.map(title => {
const [totalKey, finishKey] = dataMap[title];
const total = this.equipmentinfo[totalKey] || 0; //
const finish = this.equipmentinfo[finishKey] || 0;
const ratio = Number((finish / total).toFixed(2));
return {
title: title,
desc: title === '保养' || '巡检' ? '本月' : '今日',
total: {
number: [total],
content: '{nt}',
textAlign: 'right',
style: {
fill: '#ea6027',
fontWeight: 'bold'
}
},
num: {
number: [finish],
content: '{nt}',
textAlign: 'right',
style: {
fill: '#26fcd8',
fontWeight: 'bold'
}
},
ring: {
series: [
{
type: 'gauge',
startAngle: -Math.PI / 2,
endAngle: Math.PI * 1.5,
arcLineWidth: 13,
radius: '80%',
data: [{ name: '占比', value: ratio || 0 }],
axisLabel: {
show: false
},
axisTick: {
show: false
},
pointer: {
show: false
},
backgroundArc: {
style: {
stroke: '#224590'
}
},
details: {
show: true,
formatter: '完成占比{value}%',
style: {
fill: '#1ed3e5',
fontSize: 20
}
}
}
],
color: ['#03d3ec']
}
}
})
}
});
},
randomExtend (minNum, maxNum) {
if (arguments.length === 1) {
return parseInt(Math.random() * minNum + 1, 10)
} else {
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10)
}
}
},
mounted () {
const { createData } = this
createData()
setInterval(this.createData, 30000)
}
}
</script>
<style lang="less">
#cards {
display: flex;
justify-content: space-between;
height: 45%;
.card-item {
background-color: rgba(6, 30, 93, 0.5);
border-top: 2px solid rgba(1, 153, 209, .5);
width: 22%;
display: flex;
flex-direction: column;
}
.card-header {
display: flex;
height: 20%;
align-items: center;
justify-content: space-between;
.card-header-left {
font-size: 18px;
font-weight: bold;
padding-left: 20px;
}
.card-header-right {
padding-right: 20px;
font-size: 40px;
color: #03d3ec;
}
}
.ring-charts {
height: 55%;
}
.card-footer {
height: 25%;
display: flex;
align-items: center;
justify-content: space-around;
}
.card-footer-item {
padding: 5px 10px 0px 10px;
box-sizing: border-box;
width: 40%;
background-color: rgba(6, 30, 93, 0.7);
border-radius: 3px;
.footer-title {
font-size: 15px;
margin-bottom: 5px;
}
.footer-detail {
font-size: 20px;
color: #1294fb;
display: flex;
font-size: 18px;
align-items: center;
.dv-digital-flop {
margin-right: 5px;
}
}
}
}
</style>

@ -0,0 +1,218 @@
<template>
<div id="digital-flop">
<div
class="digital-flop-item"
v-for="item in digitalFlopData"
:key="item.title"
>
<div class="digital-flop-title">{{ item.title }}</div>
<div class="digital-flop">
<dv-digital-flop
:config="item.number"
style="width:100px;height:50px;"
/>
<div class="unit">{{ item.unit }}</div>
</div>
</div>
<dv-decoration-10 />
</div>
</template>
<script>
export default {
name: 'DigitalFlop',
data () {
return {
digitalFlopData: []
}
},
methods: {
createData () {
const { randomExtend } = this
this.digitalFlopData = [
{
title: '点检',
number: {
number: [randomExtend(20000, 30000)],
content: '{nt}',
textAlign: 'right',
style: {
fill: '#4d99fc',
fontWeight: 'bold'
}
},
unit: '次'
},
{
title: '巡检',
number: {
number: [randomExtend(20, 30)],
content: '{nt}',
textAlign: 'right',
style: {
fill: '#f46827',
fontWeight: 'bold'
}
},
unit: '次'
},
{
title: '保养',
number: {
number: [randomExtend(20, 30)],
content: '{nt}',
textAlign: 'right',
style: {
fill: '#40faee',
fontWeight: 'bold'
}
},
unit: '次'
},
{
title: '维修',
number: {
number: [randomExtend(10, 20)],
content: '{nt}',
textAlign: 'right',
style: {
fill: '#4d99fc',
fontWeight: 'bold'
}
},
unit: '次'
},
// {
// title: '',
// number: {
// number: [randomExtend(5, 10)],
// content: '{nt}',
// textAlign: 'right',
// style: {
// fill: '#f46827',
// fontWeight: 'bold'
// }
// },
// unit: ''
// },
// {
// title: '',
// number: {
// number: [randomExtend(5, 10)],
// content: '{nt}',
// textAlign: 'right',
// style: {
// fill: '#40faee',
// fontWeight: 'bold'
// }
// },
// unit: ''
// },
// {
// title: '',
// number: {
// number: [randomExtend(5, 10)],
// content: '{nt}',
// textAlign: 'right',
// style: {
// fill: '#4d99fc',
// fontWeight: 'bold'
// }
// },
// unit: ''
// },
// {
// title: '',
// number: {
// number: [randomExtend(5, 10)],
// content: '{nt}',
// textAlign: 'right',
// style: {
// fill: '#f46827',
// fontWeight: 'bold'
// }
// },
// unit: ''
// },
// {
// title: '',
// number: {
// number: [randomExtend(5, 10)],
// content: '{nt}',
// textAlign: 'right',
// style: {
// fill: '#40faee',
// fontWeight: 'bold'
// }
// },
// unit: ''
// }
]
},
randomExtend (minNum, maxNum) {
if (arguments.length === 1) {
return parseInt(Math.random() * minNum + 1, 10)
} else {
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10)
}
}
},
mounted () {
const { createData } = this
createData()
setInterval(createData, 30000)
}
}
</script>
<style lang="less">
#digital-flop {
position: relative;
height: 15%;
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
background-color: rgba(6, 30, 93, 0.5);
.dv-decoration-10 {
position: absolute;
width: 95%;
left: 2.5%;
height: 5px;
bottom: 0px;
}
.digital-flop-item {
width: 20%;
height: 80%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-left: 3px solid rgb(6, 30, 93);
border-right: 3px solid rgb(6, 30, 93);
}
.digital-flop-title {
font-size: 20px;
margin-bottom: 20px;
}
.digital-flop {
display: flex;
}
.unit {
margin-left: 10px;
display: flex;
align-items: flex-end;
box-sizing: border-box;
padding-bottom: 13px;
}
}
</style>

@ -0,0 +1,89 @@
<template>
<div id="ranking-board">
<div class="ranking-board-title">巡检上报问题数量 TOP8</div>
<dv-scroll-ranking-board :config="config" />
</div>
</template>
<script>
import moment from "moment";
import {getworkFaultDesc} from "../../../api/kanban/equipment";
export default {
name: 'RankingBoard',
data () {
return {
config: {
data: [],
rowNum: 9
}
}
},
methods: {
async getdatalist() {
try {
const response = await getworkFaultDesc({ poolName: "ds_1000" });
//
if (response?.data?.length) {
// 1
const processedData = response.data.map(item => ({
name: item.name || '未知分类', //
value: Number(item.value) || 0 //
})).filter(item => item.value > 0); //
// 2
this.config = {
...this.config, //
data: processedData //
};
} else {
//
this.config.data = [{ name: '暂无数据', value: 0 }];
}
} catch (error) {
console.error('数据获取失败:', error);
//
this.config.data = [{ name: '数据异常', value: 0 }];
} finally {
}
}
},
mounted() {
this.getdatalist();
//
this.timer = setInterval(() => {
this.getdatalist();
}, 60000); // 60
},
beforeDestroy() {
clearInterval(this.timer); //
}
}
</script>
<style lang="less">
#ranking-board {
width: 20%;
box-shadow: 0 0 3px blue;
display: flex;
flex-direction: column;
background-color: rgba(6, 30, 93, 0.5);
border-top: 2px solid rgba(1, 153, 209, .5);
box-sizing: border-box;
padding: 0px 30px;
.ranking-board-title {
font-weight: bold;
height: 50px;
display: flex;
align-items: center;
font-size: 20px;
}
.dv-scroll-ranking-board {
flex: 1;
}
}
</style>

@ -0,0 +1,117 @@
<template>
<div id="rose-chart">
<div class="rose-chart-title">设备故障原因分布</div>
<dv-charts :option="option" />
</div>
</template>
<script>
import moment from "moment";
import { getworkFaultReason } from "@/api/kanban/equipment";
export default {
name: 'RoseChart',
data () {
return {
option: {
series: [
{
type: 'pie',
radius: '50%',
roseSort: false,
data: [],
insideLabel: {
show: false
},
outsideLabel: {
formatter: '{name} {percent}%',
labelLineEndLength: 20,
style: {
fill: '#fff'
},
labelLineStyle: {
stroke: '#fff'
}
},
roseType: true
}
],
color: ['#da2f00', '#fa3600', '#ff4411', '#ff724c', '#541200', '#801b00', '#a02200', '#5d1400', '#b72700']
}
}
},
methods: {
async createData() {
try {
const response = await getworkFaultReason({ poolName: "ds_1000" });
if (response.data?.length) {
// 1
const processedData = response.data.map(item => ({
name: item.name || '未知故障', //
value: Number(item.value) || 0 //
}));
// 2
this.option = {
...this.option, //
series: [{
...this.option.series, //
data: processedData, //
//
type: 'pie',
radius: '50%',
roseSort: false,
insideLabel: { show: false },
outsideLabel: {
formatter: '{name} {percent}%',
labelLineEndLength: 20,
style: { fill: '#fff' },
labelLineStyle: { stroke: '#fff' }
},
roseType: true
}]
};
// 3使 ECharts
// this.myChart.setOption(this.option, true);
}
} catch (error) {
console.error('数据获取失败:', error);
//
this.option.series.data = [{ name: '数据异常', value: 100 }];
}
}
},
mounted () {
const { createData } = this
createData()
setInterval(createData, 30000)
}
}
</script>
<style lang="less">
#rose-chart {
width: 30%;
height: 100%;
background-color: rgba(6, 30, 93, 0.5);
border-top: 2px solid rgba(1, 153, 209, .5);
box-sizing: border-box;
.rose-chart-title {
height: 50px;
font-weight: bold;
text-indent: 20px;
font-size: 20px;
display: flex;
align-items: center;
}
.dv-charts-container {
height: calc(~"100% - 50px");
}
}
</style>

@ -0,0 +1,95 @@
<template>
<div id="scroll-board">
<!-- 添加加载状态提示 -->
<div v-if="loading" class="loading-text">...</div>
<!-- 添加数据为空提示 -->
<div v-if="!loading && config.data.length === 0" class="empty-text"></div>
<dv-scroll-board v-else :config="config" />
</div>
</template>
<script>
import { getRepairWorkOrder } from "@/api/kanban/equipment";
import moment from "moment";
export default {
name: 'ScrollBoard',
data() {
return {
loading: false, //
config: {
header: ['序号', '设备', '故障信息', '计划维修时间'], //
data: [],
index: false, // 使
columnWidth: [50, 120, 200, 200], //
align: ['center'],
rowNum: 7,
headerBGC: '#1981f6',
headerHeight: 45,
oddRowBGC: 'rgba(0, 44, 81, 0.8)',
evenRowBGC: 'rgba(10, 29, 50, 0.8)'
}
}
},
methods: {
async getdatalist() {
try {
this.loading = true;
const response = await getRepairWorkOrder({
poolName: "ds_1000"
});
if (response.data?.length > 0) {
//
const processedData = response.data.map((item, index) => {
//
const deviceName = item.equipmentName || '未知设备';
const faultInfo = item.workFaultDesc || '无故障描述';
//
const repairTime = item.workPlanTime ?
moment(item.workPlanTime).format("YYYY-MM-DD HH:mm:ss") :
'--';
//
return [`${index + 1}`, deviceName, faultInfo, repairTime];
});
//
this.config = {
...this.config,
data: processedData
};
}
} catch (error) {
console.error('数据获取失败:', error);
//
this.config.data = [['-', '数据加载失败', '请稍后重试', '-']];
} finally {
this.loading = false;
}
}
},
mounted() {
this.getdatalist();
//
this.timer = setInterval(() => {
this.getdatalist();
}, 60000); // 60
},
beforeDestroy() {
clearInterval(this.timer); //
}
}
</script>
<style lang="less">
#scroll-board {
width: 50%;
box-sizing: border-box;
margin-left: 20px;
height: 100%;
overflow: hidden;
}
</style>

@ -0,0 +1,45 @@
<template>
<div id="top-header">
<dv-decoration-8 class="header-left-decoration" />
<dv-decoration-5 class="header-center-decoration" />
<dv-decoration-8 class="header-right-decoration" :reverse="true" />
<div class="center-title">设备管理综合数据</div>
</div>
</template>
<script>
export default {
name: 'TopHeader'
}
</script>
<style lang="less">
#top-header {
position: relative;
width: 100%;
height: 100px;
display: flex;
justify-content: space-between;
flex-shrink: 0;
.header-center-decoration {
width: 40%;
height: 60px;
margin-top: 30px;
}
.header-left-decoration, .header-right-decoration {
width: 25%;
height: 60px;
}
.center-title {
position: absolute;
font-size: 30px;
font-weight: bold;
left: 50%;
top: 15px;
transform: translateX(-50%);
}
}
</style>

@ -0,0 +1,139 @@
<template>
<div id="water-level-chart">
<div class="water-level-chart-title">设备维修完成情况</div>
<div class="water-level-chart-details">
累计完成<span>{{number}}</span>
</div>
<div class="chart-container">
<dv-water-level-pond :config="config" />
</div>
</div>
</template>
<script>
import {getWave} from "../../../api/kanban/equipment";
export default {
name: 'WaterLevelChart',
data () {
return {
config: {
data: [],
shape: 'round',
waveHeight: 25,
waveNum: 2
},
number: 0
}
},
methods: {
async getdatalist() {
try {
const response = await getWave({ poolName: "ds_1000" });
//
if (response?.data) {
// 1number { number: 123 }
this.number = response.data.number || 0;
// 2data
const processedData = [response.data.ratio];
// config
this.config = {
...this.config, //
data: processedData, //
};
} else {
//
this.number = 0;
this.config = {
...this.config,
data: [0]
};
}
} catch (error) {
console.error('数据获取失败:', error);
//
this.number = 0;
this.config = {
...this.config,
data:[0]
};
}
}
},
mounted() {
this.getdatalist();
//
this.timer = setInterval(() => {
this.getdatalist();
}, 60000); // 60
},
beforeDestroy() {
clearInterval(this.timer); //
}
}
</script>
<style lang="less">
#water-level-chart {
width: 20%;
box-sizing: border-box;
margin-left: 20px;
background-color: rgba(6, 30, 93, 0.5);
border-top: 2px solid rgba(1, 153, 209, .5);
display: flex;
flex-direction: column;
.water-level-chart-title {
font-weight: bold;
height: 50px;
display: flex;
align-items: center;
font-size: 20px;
justify-content: center;
}
.water-level-chart-details {
height: 15%;
display: flex;
justify-content: center;
font-size: 17px;
align-items: flex-end;
span {
font-size: 35px;
font-weight: bold;
color: #58a1ff;
margin: 0 5px;
margin-bottom: -5px;
}
}
.chart-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.dv-water-pond-level {
max-width: 90%;
width: 200px;
height: 200px;
border: 10px solid #19c3eb;
border-radius: 50%;
ellipse {
stroke: transparent !important;
}
text {
font-size: 40px;
}
}
}
</style>
Loading…
Cancel
Save