更新
This commit is contained in:
487
src/views/equipment/equipmentOverview/index-his.vue
Normal file
487
src/views/equipment/equipmentOverview/index-his.vue
Normal file
@@ -0,0 +1,487 @@
|
||||
<!--
|
||||
* @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>
|
||||
Reference in New Issue
Block a user