|
|
@@ -1,607 +0,0 @@ |
|
|
|
<!-- |
|
|
|
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> |
|
|
|
<!-- <h1>设备状态时序图</h1> --> |
|
|
|
<!-- 搜索工作栏 --> |
|
|
|
<SearchBar |
|
|
|
:formConfigs="searchBarFormConfig" |
|
|
|
ref="search-bar" |
|
|
|
:remove-blue="true" |
|
|
|
@select-changed="handleSearchBarSelectChange" |
|
|
|
@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 waiting"></div> |
|
|
|
<div>待机</div> |
|
|
|
</div> --> |
|
|
|
<div class="legend"> |
|
|
|
<div class="icon fault"></div> |
|
|
|
<div>故障</div> |
|
|
|
</div> |
|
|
|
<!-- <div class="legend"> |
|
|
|
<div class="icon lack"></div> |
|
|
|
<div>缺料</div> |
|
|
|
</div> |
|
|
|
<div class="legend"> |
|
|
|
<div class="icon full"></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 * as echarts from 'echarts'; |
|
|
|
import Gantt from './gantt'; |
|
|
|
|
|
|
|
export default { |
|
|
|
name: 'SGStatus', |
|
|
|
components: {}, |
|
|
|
props: {}, |
|
|
|
data() { |
|
|
|
return { |
|
|
|
chart: null, |
|
|
|
searchBarFormConfig: [ |
|
|
|
{ |
|
|
|
type: 'select', |
|
|
|
label: '产线', |
|
|
|
placeholder: '请选择产线', |
|
|
|
selectOptions: [], |
|
|
|
param: 'lineId', |
|
|
|
onchange: true, |
|
|
|
}, |
|
|
|
{ |
|
|
|
type: 'select', |
|
|
|
label: '工段', |
|
|
|
placeholder: '请选择工段', |
|
|
|
selectOptions: [], |
|
|
|
param: 'sectionId', |
|
|
|
}, |
|
|
|
// 时间段 |
|
|
|
{ |
|
|
|
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: { |
|
|
|
lineId: null, |
|
|
|
sectionId: null, |
|
|
|
equipmentId: null, |
|
|
|
recordTime: null, |
|
|
|
}, |
|
|
|
graphList: [], |
|
|
|
open: false, |
|
|
|
eqList: [], |
|
|
|
startTime: null, |
|
|
|
gantt: null |
|
|
|
// demo: [ |
|
|
|
// [ |
|
|
|
// { |
|
|
|
// equipmentName: '下片机', |
|
|
|
// duration: 30, |
|
|
|
// relativeDuration: 0.6, |
|
|
|
// status: 0, |
|
|
|
// startPos: 0, |
|
|
|
// startTime: 1691568181000, |
|
|
|
// }, |
|
|
|
// { |
|
|
|
// equipmentName: '下片机', |
|
|
|
// duration: 20, |
|
|
|
// relativeDuration: 0.4, |
|
|
|
// status: 2, |
|
|
|
// startPos: 30, |
|
|
|
// startTime: 1691569981000 |
|
|
|
// }, |
|
|
|
// ], |
|
|
|
// ], |
|
|
|
}; |
|
|
|
}, |
|
|
|
computed: {}, |
|
|
|
created() { |
|
|
|
this.initProductline(); |
|
|
|
this.initWorksection(); |
|
|
|
this.initEquipment(); |
|
|
|
// this.getList(); |
|
|
|
}, |
|
|
|
mounted() {}, |
|
|
|
watch: { |
|
|
|
graphList: { |
|
|
|
handler(val) { |
|
|
|
if (val && val.length) { |
|
|
|
this.$nextTick(() => { |
|
|
|
if (!this.chart) this.initChart(); |
|
|
|
this.setInitialConfig(); |
|
|
|
this.handleGraphList(); |
|
|
|
}); |
|
|
|
} |
|
|
|
return; |
|
|
|
}, |
|
|
|
deep: true, |
|
|
|
immediate: true, |
|
|
|
}, |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
setInitialConfig() { |
|
|
|
console.log('in setInitialConfig', this.chartOption); |
|
|
|
this.chart.setOption(this.chartOption); |
|
|
|
}, |
|
|
|
|
|
|
|
handleGraphList() { |
|
|
|
console.log('in handleGraphList:', this.graphList); |
|
|
|
return; |
|
|
|
const min = this.queryParams.recordTime |
|
|
|
? new Date(this.queryParams.recordTime).getTime() |
|
|
|
: this.findMin(); |
|
|
|
|
|
|
|
console.log('min is', min); |
|
|
|
this.chartOption.xAxis.min = getStartTime(min); |
|
|
|
this.chartOption.xAxis.max = getStartTime(min + 3600 * 24 * 1000); |
|
|
|
this.graphList.forEach((arr) => { |
|
|
|
this.chartOption.yAxis[0].data.push(arr.key); |
|
|
|
arr.forEach((item) => { |
|
|
|
this.chartOption.series[0].data.push({ |
|
|
|
name: ['运行', '故障', '计划停机'][item.status], |
|
|
|
value: [ |
|
|
|
0, |
|
|
|
item.startTime, |
|
|
|
item.startTime + item.duration * 60 * 1000, |
|
|
|
item.duration * 60 * 1000, |
|
|
|
], |
|
|
|
itemStyle: { |
|
|
|
normal: { |
|
|
|
color: types[item.status].color, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}); |
|
|
|
}); |
|
|
|
console.log('chartOptions', this.chartOption); |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
findMin() { |
|
|
|
let min = 0; |
|
|
|
this.graphList.forEach((arr) => { |
|
|
|
arr.forEach((item) => { |
|
|
|
if (min < item.startTime) min = item.startTime; |
|
|
|
}); |
|
|
|
}); |
|
|
|
return min; |
|
|
|
}, |
|
|
|
|
|
|
|
initChart() { |
|
|
|
const el = document.getElementById('status-chart'); |
|
|
|
this.gantt = new Gantt(el); |
|
|
|
this.gantt.init(); |
|
|
|
}, |
|
|
|
|
|
|
|
/** 重置查询条件 */ |
|
|
|
initQuery() { |
|
|
|
this.queryParams.lineId = null; |
|
|
|
this.queryParams.equipmentId = null; |
|
|
|
this.queryParams.sectionId = 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.graphList = this.objectToArray(data); |
|
|
|
console.log('graph list', this.graphList); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** 准备设备数据 */ |
|
|
|
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, |
|
|
|
}; |
|
|
|
}); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** 准备产线数据 */ |
|
|
|
async initProductline() { |
|
|
|
const { code, data } = await this.$axios({ |
|
|
|
url: '/base/production-line/listAll', |
|
|
|
method: 'get', |
|
|
|
}); |
|
|
|
if (code == 0) { |
|
|
|
this.searchBarFormConfig[0].selectOptions = data.map((item) => { |
|
|
|
return { |
|
|
|
name: item.name, |
|
|
|
id: item.id, |
|
|
|
}; |
|
|
|
}); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** 准备工段数据 */ |
|
|
|
async initWorksection() { |
|
|
|
const { code, data } = await this.$axios({ |
|
|
|
url: '/base/workshop-section/listAll', |
|
|
|
method: 'get', |
|
|
|
}); |
|
|
|
if (code == 0) { |
|
|
|
this.searchBarFormConfig[1].selectOptions = data.map((item) => { |
|
|
|
return { |
|
|
|
name: item.name, |
|
|
|
id: item.id, |
|
|
|
}; |
|
|
|
}); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
handleSearchBarSelectChange({ param, value }) { |
|
|
|
if (!value) { |
|
|
|
this.searchBarFormConfig[1].selectOptions = []; |
|
|
|
return; |
|
|
|
} |
|
|
|
switch (param) { |
|
|
|
case 'lineId': |
|
|
|
this.$axios({ |
|
|
|
url: '/base/workshop-section/listByParentId', |
|
|
|
method: 'get', |
|
|
|
params: { |
|
|
|
id: value, |
|
|
|
}, |
|
|
|
}).then(({ code, data }) => { |
|
|
|
if (code == 0) { |
|
|
|
this.searchBarFormConfig[1].selectOptions = data.map((item) => { |
|
|
|
return { |
|
|
|
name: item.name, |
|
|
|
id: item.id, |
|
|
|
}; |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
handleSearchBarBtnClick({ btnName, ...payload }) { |
|
|
|
switch (btnName) { |
|
|
|
case 'search': |
|
|
|
if (!payload.recordTime || payload.recordTime.length <= 0) { |
|
|
|
this.$message.error('请选择时间段'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
this.startTime = new Date(payload.recordTime).getTime(); |
|
|
|
|
|
|
|
this.queryParams.lineId = payload.lineId || null; |
|
|
|
this.queryParams.sectionId = payload.sectionId || null; |
|
|
|
this.queryParams.equipmentId = payload.equipmentId || null; |
|
|
|
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() { |
|
|
|
const { code, data } = await this.$axios({ |
|
|
|
url: '/analysis/equipment-analysis/status', |
|
|
|
method: 'get', |
|
|
|
params: this.queryParams, |
|
|
|
}); |
|
|
|
if (code == 0) { |
|
|
|
const newEqlist = this.objectToArray(data); |
|
|
|
if (!newEqlist || newEqlist.length == 0) { |
|
|
|
this.$message.error('该设备没有状态数据'); |
|
|
|
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: #5ad8a6; |
|
|
|
// 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: #000; |
|
|
|
} |
|
|
|
|
|
|
|
.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> |