488 lines
9.5 KiB
Vue
488 lines
9.5 KiB
Vue
<!--
|
|
* @Author: zwq
|
|
* @Date: 2026-04-21 10:22:15
|
|
* @LastEditors: zwq
|
|
* @LastEditTime: 2026-04-24 10:35:32
|
|
* @Description:
|
|
-->
|
|
<template>
|
|
<div
|
|
class="status-timegraph-container"
|
|
style="flex: 1; display: flex; flex-direction: column">
|
|
<el-row
|
|
class=""
|
|
style="
|
|
margin-bottom: 12px;
|
|
background: #fff;
|
|
padding: 16px 16px 0;
|
|
border-radius: 8px;
|
|
">
|
|
<el-form
|
|
:inline="true"
|
|
ref="searchBarForm"
|
|
:model="formInline"
|
|
class="searchBar">
|
|
<span class="blue-block"></span>
|
|
<el-form-item label="产线/工段">
|
|
<el-cascader
|
|
size="small"
|
|
:options="options"
|
|
ref="cascaderRef"
|
|
collapse-tags
|
|
@change="handleCascaderChange"
|
|
:props="{
|
|
value: 'id',
|
|
label: 'name',
|
|
checkStrictly: true,
|
|
multiple: true,
|
|
emitPath: false,
|
|
}"></el-cascader>
|
|
</el-form-item>
|
|
<el-form-item label="时间范围" prop="recordTime">
|
|
<el-date-picker
|
|
size="small"
|
|
v-model="formInline.recordTime"
|
|
type="daterange"
|
|
format="yyyy-MM-dd"
|
|
value-format="yyyy-MM-dd HH:mm:ss"
|
|
range-separator="-"
|
|
start-placeholder="开始时间"
|
|
end-placeholder="结束时间"></el-date-picker>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button size="small" type="primary" @click="handleRecordSearch">
|
|
查询
|
|
</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</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" style="margin-bottom: 10px">
|
|
<el-col :span="12">
|
|
<div class="blue-title">关键设备运行率</div>
|
|
<line-chart
|
|
v-if="lineChartData.length"
|
|
ref="lineChart"
|
|
:chartData="lineChartData"></line-chart>
|
|
<h2 v-else class="no-data-bg"></h2>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<div class="blue-title">今日停机/故障汇总</div>
|
|
<base-table
|
|
:page="1"
|
|
:limit="100"
|
|
:table-props="tableProps"
|
|
:table-data="tableData"
|
|
:height="300"></base-table>
|
|
</el-col>
|
|
</el-row>
|
|
<el-divider></el-divider>
|
|
<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="min-height: 400px; flex: 1"></div>
|
|
<h2 v-if="!graphList || graphList.length == 0" class="no-data-bg"></h2>
|
|
</div>
|
|
</el-row>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import Gantt from './gantt';
|
|
import { getGroupPlanTree } from '@/api/group/Schedule';
|
|
import lineChart from './lineChart.vue';
|
|
import subDiv from './subDiv.vue';
|
|
|
|
const tableProps = [
|
|
{
|
|
prop: 'equipmentName',
|
|
label: '设备',
|
|
},
|
|
{
|
|
prop: 'sectionName',
|
|
label: '工段',
|
|
},
|
|
{
|
|
prop: 'stopCount',
|
|
label: '停机',
|
|
},
|
|
{
|
|
prop: 'downCount',
|
|
label: '故障',
|
|
},
|
|
{
|
|
prop: 'stopTime',
|
|
label: '停机时长',
|
|
},
|
|
{
|
|
prop: 'stopRate',
|
|
label: '占比',
|
|
subcomponent: subDiv,
|
|
},
|
|
];
|
|
|
|
export default {
|
|
name: '',
|
|
components: {
|
|
lineChart,
|
|
},
|
|
props: {},
|
|
data() {
|
|
return {
|
|
chart: null,
|
|
formInline: {
|
|
factoryId: null,
|
|
lineId: null,
|
|
sectionId: null,
|
|
equId: null,
|
|
recordTime: null,
|
|
},
|
|
options: [], // 产线/工段选项
|
|
graphList: [],
|
|
startTime: null,
|
|
gantt: null,
|
|
lineChartData: [],
|
|
tableData: [],
|
|
tableProps,
|
|
};
|
|
},
|
|
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.startTime = new Date(formattedDate);
|
|
this.formInline.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;
|
|
|
|
getGroupPlanTree().then((res) => {
|
|
this.options = res.data;
|
|
});
|
|
this.getList();
|
|
},
|
|
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: {
|
|
handleRecordSearch() {
|
|
if (!this.formInline.recordTime || this.formInline.recordTime.length <= 0) {
|
|
this.$message.warning('请选择时间段');
|
|
return;
|
|
}
|
|
|
|
this.startTime = new Date(this.formInline.recordTime);
|
|
this.getList();
|
|
},
|
|
handleCascaderChange() {
|
|
const checkedNodes = this.$refs.cascaderRef.getCheckedNodes();
|
|
this.formInline.factoryId = [];
|
|
this.formInline.lineId = [];
|
|
this.formInline.sectionId = [];
|
|
this.formInline.equId = [];
|
|
checkedNodes.forEach((node) => {
|
|
if (node.checked) {
|
|
if (node.level === 1) {
|
|
this.formInline.factoryId.push(node.value);
|
|
} else if (node.level === 2) {
|
|
this.formInline.lineId.push(node.value);
|
|
} else if (node.level === 3) {
|
|
this.formInline.sectionId.push(node.value);
|
|
} else if (node.level === 4) {
|
|
this.formInline.equId.push(node.value);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
findMin() {
|
|
let min = 0;
|
|
this.graphList.forEach((arr) => {
|
|
arr.forEach((item) => {
|
|
if (min < item.startTime) min = item.startTime;
|
|
});
|
|
});
|
|
return min;
|
|
},
|
|
|
|
/** 对象到数组的转换 */
|
|
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: '/monitoring/equipment-overall/getGantt',
|
|
method: 'get',
|
|
params: this.formInline,
|
|
});
|
|
if (code == 0) {
|
|
this.graphList = this.objectToArray(data);
|
|
}
|
|
const { code: code1, data: data1 } = await this.$axios({
|
|
url: '/monitoring/equipment-overall/getUseRate',
|
|
method: 'get',
|
|
params: this.formInline,
|
|
});
|
|
if (code1 == 0) {
|
|
this.lineChartData = data1;
|
|
this.$nextTick(() => {
|
|
if (this.lineChartData.length) {
|
|
this.$refs.lineChart.initChart();
|
|
}
|
|
});
|
|
}
|
|
const { code: code2, data: data2 } = await this.$axios({
|
|
url: '/monitoring/equipment-overall/getHistoryStopAndError',
|
|
method: 'get',
|
|
params: this.formInline,
|
|
});
|
|
if (code2 == 0) {
|
|
this.tableData = data2;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</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>
|