This commit is contained in:
2026-04-29 08:57:28 +08:00
parent 00393f76c7
commit b46e09e8ec
36 changed files with 8457 additions and 2085 deletions

View 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>