484 lines
9.3 KiB
Vue
484 lines
9.3 KiB
Vue
<!--
|
|
filename: index.vue
|
|
author: liubin
|
|
date: 2023-09-04 09:34:52
|
|
description: 设备状态时序图
|
|
-->
|
|
|
|
<template>
|
|
<div
|
|
class="status-timegraph-container"
|
|
style="background: #f2f4f9; flex: 1; display: flex; flex-direction: column">
|
|
<el-row
|
|
class=""
|
|
style="
|
|
margin-bottom: 12px;
|
|
background: #fff;
|
|
padding: 16px 16px 0;
|
|
border-radius: 8px;
|
|
">
|
|
<div class="blue-title">生产节拍时序图</div>
|
|
<SearchBar
|
|
:formConfigs="searchBarFormConfig"
|
|
ref="search-bar"
|
|
:remove-blue="true"
|
|
@headBtnClick="handleSearchBarBtnClick" />
|
|
</el-row>
|
|
|
|
<el-row
|
|
class=""
|
|
style="
|
|
height: 1px;
|
|
flex: 1;
|
|
margin-bottom: 12px;
|
|
background: #fff;
|
|
padding: 16px 16px 32px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
">
|
|
<el-row :gutter="20">
|
|
<el-col :span="6">
|
|
<div class="blue-title">设备状态时序图</div>
|
|
</el-col>
|
|
<el-col :span="18" class="legend-row">
|
|
<div class="legend">
|
|
<div class="icon running"></div>
|
|
<div>运行中</div>
|
|
</div>
|
|
<div class="legend">
|
|
<div class="icon fault"></div>
|
|
<div>故障</div>
|
|
</div>
|
|
<div class="legend">
|
|
<div class="icon stop"></div>
|
|
<div>计划停机</div>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
<div
|
|
class="main-area"
|
|
style="flex: 1; display: flex; flex-direction: column">
|
|
<div
|
|
class="graphs"
|
|
v-show="graphList.length"
|
|
id="status-chart"
|
|
style="height: 1px; flex: 1"></div>
|
|
<h2 v-if="!graphList || graphList.length == 0" class="no-data-bg"></h2>
|
|
</div>
|
|
</el-row>
|
|
|
|
<!-- 对话框(添加 / 修改) -->
|
|
<base-dialog
|
|
dialogTitle="添加设备"
|
|
:dialogVisible="open"
|
|
width="500px"
|
|
@close="open = false"
|
|
@cancel="open = false"
|
|
@confirm="submitForm">
|
|
<el-select
|
|
v-if="open"
|
|
style="width: 100%"
|
|
v-model="queryParams.equipmentId"
|
|
placeholder="请选择一个设备">
|
|
<el-option
|
|
v-for="eq in eqList"
|
|
:key="eq.id"
|
|
:value="eq.id"
|
|
:label="eq.name"></el-option>
|
|
</el-select>
|
|
</base-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import Gantt from './gantt';
|
|
|
|
export default {
|
|
name: 'SGStatus',
|
|
components: {},
|
|
props: {},
|
|
data() {
|
|
return {
|
|
chart: null,
|
|
searchBarFormConfig: [
|
|
// 时间段
|
|
{
|
|
type: 'datePicker',
|
|
label: '时间段',
|
|
dateType: 'date',
|
|
format: 'yyyy-MM-dd',
|
|
valueFormat: 'yyyy-MM-dd HH:mm:ss',
|
|
rangeSeparator: '-',
|
|
placeholder: '选择日期',
|
|
param: 'recordTime',
|
|
required: true,
|
|
},
|
|
{
|
|
type: 'button',
|
|
btnName: '查询',
|
|
name: 'search',
|
|
color: 'primary',
|
|
},
|
|
{
|
|
type: 'separate',
|
|
},
|
|
{
|
|
type: 'button',
|
|
btnName: '添加对比',
|
|
name: 'compare',
|
|
color: 'primary',
|
|
plain: true,
|
|
},
|
|
],
|
|
queryParams: {
|
|
equipmentId: null,
|
|
recordTime: null,
|
|
},
|
|
graphList: [],
|
|
existingEquipments: [],
|
|
open: false,
|
|
eqList: [],
|
|
startTime: null,
|
|
gantt: null,
|
|
};
|
|
},
|
|
computed: {},
|
|
created() {
|
|
const now = new Date();
|
|
const year = now.getFullYear();
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
const formattedDate = `${year}-${month}-${day} 00:00:00`;
|
|
this.$nextTick(() => {
|
|
this.$refs['search-bar'].formInline.recordTime = formattedDate;
|
|
});
|
|
this.queryParams.equipmentId = Number(this.$route.query.eqid);
|
|
this.startTime = new Date(formattedDate);
|
|
this.queryParams.recordTime = formattedDate
|
|
? [
|
|
formattedDate,
|
|
new Date(new Date(formattedDate).getTime() + 24 * 3600 * 1000)
|
|
.toLocaleDateString()
|
|
.split('/')
|
|
.map((value, index) => {
|
|
if (index == 1 || index == 2) {
|
|
return value.padStart(2, '0');
|
|
}
|
|
return value;
|
|
})
|
|
.join('-') + ' 00:00:00',
|
|
]
|
|
: null;
|
|
this.getList();
|
|
this.initEquipment();
|
|
},
|
|
mounted() {
|
|
},
|
|
watch: {
|
|
graphList: {
|
|
handler(val) {
|
|
if (val && val.length) {
|
|
this.$nextTick(() => {
|
|
if (!this.gantt) {
|
|
this.gantt = new Gantt('#status-chart', this.startTime);
|
|
this.gantt.init(val);
|
|
return;
|
|
}
|
|
this.gantt.update(val);
|
|
});
|
|
}
|
|
return;
|
|
},
|
|
deep: true,
|
|
immediate: true,
|
|
},
|
|
},
|
|
methods: {
|
|
findMin() {
|
|
let min = 0;
|
|
this.graphList.forEach((arr) => {
|
|
arr.forEach((item) => {
|
|
if (min < item.startTime) min = item.startTime;
|
|
});
|
|
});
|
|
return min;
|
|
},
|
|
|
|
/** 重置查询条件 */
|
|
initQuery() {
|
|
this.queryParams.equipmentId = null;
|
|
this.queryParams.recordTime = null;
|
|
},
|
|
|
|
/** 对象到数组的转换 */
|
|
objectToArray(obj) {
|
|
return Object.keys(obj).map((key) => {
|
|
obj[key].sort((a, b) => a.startTime - b.startTime);
|
|
obj[key].key = key;
|
|
return obj[key];
|
|
});
|
|
},
|
|
|
|
async getList() {
|
|
const { code, data } = await this.$axios({
|
|
url: '/analysis/equipment-analysis/status',
|
|
method: 'get',
|
|
params: this.queryParams,
|
|
});
|
|
if (code == 0) {
|
|
this.existingEquipments = Object.values(data).map(
|
|
(eq) => eq[0].equipmentId
|
|
);
|
|
this.graphList = this.objectToArray(data);
|
|
}
|
|
},
|
|
|
|
/** 准备设备数据 */
|
|
async initEquipment() {
|
|
const { code, data } = await this.$axios({
|
|
url: '/base/equipment/listAll',
|
|
method: 'get',
|
|
});
|
|
if (code == 0) {
|
|
this.eqList = data.map((item) => {
|
|
return {
|
|
name: item.name,
|
|
id: item.id,
|
|
};
|
|
});
|
|
}
|
|
},
|
|
|
|
handleSearchBarBtnClick({ btnName, ...payload }) {
|
|
this.queryParams.equipmentId = Number(this.$route.query.eqid);
|
|
switch (btnName) {
|
|
case 'search':
|
|
if (!payload.recordTime || payload.recordTime.length <= 0) {
|
|
this.$message.warning('请选择时间段');
|
|
return;
|
|
}
|
|
|
|
this.startTime = new Date(payload.recordTime);
|
|
this.queryParams.recordTime = payload.recordTime
|
|
? [
|
|
payload.recordTime,
|
|
new Date(
|
|
new Date(payload.recordTime).getTime() + 24 * 3600 * 1000
|
|
)
|
|
.toLocaleDateString()
|
|
.split('/')
|
|
.map((value, index) => {
|
|
if (index == 1 || index == 2) {
|
|
return value.padStart(2, '0');
|
|
}
|
|
return value;
|
|
})
|
|
.join('-') + ' 00:00:00',
|
|
]
|
|
: null;
|
|
this.getList();
|
|
break;
|
|
case 'compare':
|
|
this.open = true;
|
|
break;
|
|
}
|
|
},
|
|
|
|
async submitForm() {
|
|
if (this.existingEquipments.indexOf(this.queryParams.equipmentId) >= 0) {
|
|
this.$message.warning('该设备已存在');
|
|
return;
|
|
}
|
|
const { ...params } = this.queryParams;
|
|
const { code, data } = await this.$axios({
|
|
url: '/analysis/equipment-analysis/status',
|
|
method: 'get',
|
|
params: params,
|
|
});
|
|
if (code == 0) {
|
|
const newEqlist = this.objectToArray(data);
|
|
if (!newEqlist || newEqlist.length == 0) {
|
|
this.$message.warning('该设备没有状态数据');
|
|
return;
|
|
}
|
|
this.graphList.push(newEqlist[0]);
|
|
}
|
|
this.open = false;
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.graph {
|
|
// border: 1px solid #ccc;
|
|
// padding: 12px 12px 28px 12px;
|
|
// margin: 64px 0;
|
|
position: relative;
|
|
display: flex;
|
|
}
|
|
|
|
.graph-title {
|
|
// position: absolute;
|
|
// top: -64px;
|
|
// left: -1px;
|
|
// padding: 8px 18px;
|
|
padding: 0 12px;
|
|
font-size: 14px;
|
|
line-height: 1;
|
|
}
|
|
|
|
.graph-content {
|
|
display: flex;
|
|
flex: 1;
|
|
padding: 22px 12px;
|
|
border: 1px solid #ccc;
|
|
border-bottom-width: 2px;
|
|
border-top: none;
|
|
position: relative;
|
|
}
|
|
|
|
.graph-content::after,
|
|
.graph-content::before {
|
|
content: '';
|
|
position: absolute;
|
|
width: 3px;
|
|
height: 80%;
|
|
background: #fff;
|
|
right: -1px;
|
|
top: 0;
|
|
}
|
|
|
|
.graph-content::before {
|
|
right: unset;
|
|
left: -1px;
|
|
}
|
|
|
|
.graph-item,
|
|
.graph-item-fixed {
|
|
// height: 88px;
|
|
// width: 24px;
|
|
flex: 1;
|
|
// border: 1px solid #ccc;
|
|
position: relative;
|
|
}
|
|
|
|
.graph-item-fixed {
|
|
flex: unset;
|
|
}
|
|
|
|
.graph-item::before,
|
|
.graph-item-fixed::before {
|
|
position: absolute;
|
|
bottom: -16px;
|
|
left: 0;
|
|
content: attr(data-time);
|
|
// font-size - js
|
|
// rotate - js
|
|
// color - js, default:
|
|
color: #777;
|
|
transform-origin: left top;
|
|
transform: rotate(12deg);
|
|
}
|
|
|
|
.graph-item-fixed::after,
|
|
.graph-item::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: -3px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.graph-item.tick::after,
|
|
.graph-item-fixed.tick::after {
|
|
width: 1px;
|
|
height: 6px;
|
|
border-left: 1px solid #777;
|
|
}
|
|
|
|
.running {
|
|
background-color: #288aff;
|
|
// background-color: #84f04e;
|
|
}
|
|
|
|
.waiting {
|
|
background-color: #5ad8a6;
|
|
// background-color: #409eff;
|
|
}
|
|
|
|
.fault {
|
|
// background-color: #ea5b5b;
|
|
background-color: #fc9c91;
|
|
}
|
|
|
|
.full {
|
|
// background-color: #e6a23c;
|
|
background-color: #598fff;
|
|
}
|
|
|
|
.lack {
|
|
// background-color: #a69c8d;
|
|
background-color: #7585a2;
|
|
}
|
|
|
|
.stop {
|
|
background-color: #ffdc94;
|
|
}
|
|
|
|
.legend-row {
|
|
margin: 6px 0;
|
|
padding-right: 12px;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
|
|
> .legend:not(:last-child) {
|
|
margin-right: 12px;
|
|
}
|
|
|
|
.legend {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.icon {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 2px;
|
|
margin-right: 4px;
|
|
margin-top: 1px;
|
|
}
|
|
}
|
|
|
|
.blue-title {
|
|
position: relative;
|
|
padding: 4px 0;
|
|
padding-left: 12px;
|
|
font-size: 14px;
|
|
color: #606266;
|
|
font-weight: 700;
|
|
margin-bottom: 12px;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 6px;
|
|
height: 16px;
|
|
width: 4px;
|
|
border-radius: 1px;
|
|
background: #0b58ff;
|
|
}
|
|
}
|
|
|
|
.echarts__status-chart {
|
|
background: #ccc;
|
|
}
|
|
|
|
.echarts__status-chart > div {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
</style>
|