Compare commits

...

6 Commits

Author SHA1 Message Date
lb
06fa7d40b3 update 设备加工数量 2023-09-21 17:03:07 +08:00
lb
f6f0480744 update 设备加工数量 2023-09-21 16:00:46 +08:00
lb
45e7f337bf update 设备加工数量 2023-09-21 15:54:11 +08:00
lb
8e0d63839a update 设备加工数量 2023-09-21 15:35:16 +08:00
lb
6c21f01fd7 update 设备加工数量 2023-09-21 15:14:38 +08:00
lb
52bc38499c update 设备效率分析 2023-09-21 14:54:16 +08:00
3 changed files with 610 additions and 91 deletions

View File

@ -6,7 +6,34 @@
--> -->
<template> <template>
<div class="pie-chart" :data-eqname="value.equipmentName || 'Default'"></div> <div class="chart-grid-item" style="">
<div
class="pie-chart"
ref="pieChart"
:data-eqname="value.equipmentName || 'Default'"></div>
<div class="data-view">
<div class="data-view__item">
<!-- <div class="data-view__item__value">111</div> -->
<div class="data-view__item__value">{{ textData.workTime }}</div>
<div class="data-view__item__title blue">工作时长</div>
</div>
<div class="data-view__item">
<!-- <div class="data-view__item__value">22</div> -->
<div class="data-view__item__value">{{ textData.stopTime }}</div>
<div class="data-view__item__title green">停机时长</div>
</div>
<div class="data-view__item">
<!-- <div class="data-view__item__value">10</div> -->
<div class="data-view__item__value">{{ textData.downTime }}</div>
<div class="data-view__item__title purple">故障时长</div>
</div>
<div class="data-view__item">
<!-- <div class="data-view__item__value">100%</div> -->
<div class="data-view__item__value">{{ textData.peEfficiency }}</div>
<div class="data-view__item__title yellow">速度开动率</div>
</div>
</div>
</div>
</template> </template>
<script> <script>
@ -19,7 +46,27 @@ export default {
data() { data() {
return { return {
chart: null, chart: null,
textData: {
workTime: '',
downTime: '',
stopTime: '',
peEfficiency: '',
},
config: { config: {
title: {
text: '产线1', //<=========
top: '35%',
left: '49%',
textAlign: 'center',
textStyle: {
fontSize: 18,
},
subtext: '设备', //<=========
subtextStyle: {
fontSize: 14,
},
},
color: ['#3da8fd', '#8ef0ab', '#6b5cfd', '#FFC72A', 'transparent'],
grid: { grid: {
top: 0, top: 0,
left: 0, left: 0,
@ -30,6 +77,7 @@ export default {
trigger: 'item', trigger: 'item',
}, },
legend: { legend: {
show: false,
top: '0%', top: '0%',
left: 'center', left: 'center',
textStyle: { textStyle: {
@ -39,34 +87,70 @@ export default {
itemHeight: 10, itemHeight: 10,
}, },
series: [ series: [
//
{ {
name: this.value.equipmentName || 'Default', //
name: '',
type: 'pie', type: 'pie',
radius: ['40%', '75%'], radius: ['75%', '90%'],
avoidLabelOverlap: false, center: ['50%', '45%'],
label: { label: {
show: false, show: false,
position: 'center',
}, },
data: ['workTime', 'stopTime', 'downTime'].map((v, index) => ({ data: [
name: ['工作时长', '停机时长', '故障时长'][index], //<=========
value: this.value[v], { name: '工作时长', value: 1048 },
})), { name: '停机时长', value: 735 },
// data: [ { name: '故障时长', value: 580 },
// { value: 1048, name: 'Search Engine' }, ],
// { value: 735, name: 'Direct' },
// { value: 580, name: 'Email' },
// { value: 484, name: 'Union Ads' },
// { value: 300, name: 'Video Ads' },
// ],
}, },
{
//
name: '',
type: 'pie',
center: ['50%', '45%'],
radius: ['60%', '75%'],
itemStyle: {
borderRadius: 10,
},
label: {
show: false,
},
data: [
//<=========
{ name: '总', value: 3000 },
{ name: '', value: 1400 },
],
},
// {
// name: this.value.equipmentName || 'Default',
// type: 'pie',
// radius: ['40%', '75%'],
// avoidLabelOverlap: false,
// label: {
// show: false,
// position: 'center',
// },
// data: ['workTime', 'stopTime', 'downTime'].map((v, index) => ({
// name: ['', '', ''][index],
// value: this.value[v],
// })),
// // data: [
// // { value: 1048, name: 'Search Engine' },
// // { value: 735, name: 'Direct' },
// // { value: 580, name: 'Email' },
// // { value: 484, name: 'Union Ads' },
// // { value: 300, name: 'Video Ads' },
// // ],
// },
], ],
}, },
}; };
}, },
mounted() { mounted() {
console.log('value', this.value);
if (!this.chart) { if (!this.chart) {
this.chart = echarts.init(this.$el); this.chart = echarts.init(this.$refs.pieChart);
this.$nextTick(() => { this.$nextTick(() => {
this.chart.setOption(this.config); this.chart.setOption(this.config);
}); });
@ -75,24 +159,120 @@ export default {
beforeDestroy() { beforeDestroy() {
if (this.chart) this.chart.dispose(); if (this.chart) this.chart.dispose();
}, },
methods: {}, watch: {
value: {
handler(val) {
this.updateConfig(val);
if (this.chart) this.chart.setOption(this.config);
},
deep: true,
immediate: true,
},
},
methods: {
updateConfig(item) {
const {
lineName, // 线
equipmentName, //
downTime, // (h)
stopTime, // (h)
workTime, // (h)
peEfficiency, //
timeEfficiency, //
//===============//
sectionName,
workRate,
stopRate,
downRate,
realProcSpeed,
designProcSpeed,
oee,
teep,
downCount,
mtbf,
mttr,
} = item;
this.config.title.text = lineName;
this.config.title.subtext = equipmentName;
this.config.series[0].data = [
{ name: '工作时长', value: workTime },
{ name: '停机时长', value: stopTime },
{ name: '故障时长', value: downTime },
];
this.config.series[1].data = [
{ name: '速度开动率', value: peEfficiency },
{ name: '', value: Math.ceil(peEfficiency) - peEfficiency },
];
//
this.textData = {
workTime: +workTime.toFixed(2),
stopTime: +stopTime.toFixed(2),
downTime: +downTime.toFixed(2),
peEfficiency: +peEfficiency.toFixed(2),
};
},
},
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.pie-chart { .chart-grid-item {
padding: 12px;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.pie-chart {
height: 1px;
width: 100%;
flex: 1;
padding: 12px; padding: 12px;
min-height: 320px;
background: #f1f1f1;
position: relative; position: relative;
} }
.pie-chart::before { .data-view {
content: attr(data-eqname); display: flex;
justify-content: center;
}
.data-view__item {
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
user-select: none;
padding: 0 6px;
}
.data-view__item:not(:last-child) {
border-right: 1px solid #f1f1f1;
}
.data-view__item__value {
font-size: 16px; font-size: 16px;
line-height: 1; line-height: 24px;
position: absolute; }
top: -16px;
left: 0; .data-view__item__title {
font-size: 8px;
line-height: 14px;
}
.blue {
color: #3da8fd;
}
.green {
color: #8ef0ab;
}
.purple {
color: #6b5cfd;
}
.yellow {
color: #ffc72a;
} }
</style> </style>

View File

@ -13,46 +13,64 @@
ref="search-bar" ref="search-bar"
@headBtnClick="handleSearchBarBtnClick" /> @headBtnClick="handleSearchBarBtnClick" />
<!-- 列表 --> <el-row>
<base-table <el-col class="custom-tabs">
class="base-table__margin" <el-tabs
:table-props="tableProps" v-model="activeName"
:table-data="list" :stretch="true"
@emitFun="handleEmitFun"> @tab-click="handleTabClick">
<!-- :page="queryParams.pageNo" <el-tab-pane :label="'\u2002数据列表\u2002'" name="table">
:limit="queryParams.pageSize" --> <!-- 列表 -->
<!-- <method-btn <base-table
v-if="tableBtn.length" class="base-table__margin"
slot="handleBtn" :table-props="tableProps"
label="操作" :page="1"
:method-list="tableBtn" :limit="10"
@clickBtn="handleTableBtnClick" /> --> :table-data="list"
</base-table> @emitFun="handleEmitFun"></base-table>
</el-tab-pane>
<el-tab-pane :label="'\u3000可视化\u3000'" name="graph">
<div
v-if="activeName == 'graph'"
class="graph"
style="display: flex; flex-direction: column; position: relative">
<div class="blue-title">各设备加工数量</div>
<div class="legend">
<div class="legend-item">
<span class="icon blue"></span>
<span class="text">工作时长</span>
</div>
<div class="legend-item">
<span class="icon green"></span>
<span class="text">停机时长</span>
</div>
<div class="legend-item">
<span class="icon purple"></span>
<span class="text">故障时长</span>
</div>
<div class="legend-item">
<span class="icon yellow"></span>
<span class="text">速度开动率</span>
</div>
</div>
<div class="graph-grid">
<div class="bg-grid grid-line">
<div class="grid-item" v-for="item in list.length" :key="item"></div>
</div>
<!-- 分页组件 --> <div class="bg-grid grid-charts">
<!-- <pagination <pie-chart
v-show="total > 0" v-for="item in list"
:total="total" :key="item.id"
:page.sync="queryParams.pageNo" :value="item" />
:limit.sync="queryParams.pageSize" <!-- <pie-chart v-for="item in 5" :key="item" :value="item" /> -->
@pagination="getList" /> --> </div>
</div>
<!-- 对话框(添加 / 修改) --> </div>
<base-dialog </el-tab-pane>
:dialogTitle="visualizationOpen ? '设备可视化' : '查看趋势'" </el-tabs>
:dialogVisible="open" </el-col>
:width="visualizationOpen ? '80%' : '700px'" </el-row>
@closed="closed"
@close="cancel"
@cancel="cancel"
@confirm="submitForm">
<div class="visualization" v-if="visualizationOpen">
<pie-chart v-for="item in list" :key="item.id" :value="item" />
</div>
<div v-if="trendOpen">
<h1>查看趋势</h1>
</div>
</base-dialog>
</div> </div>
</template> </template>
@ -68,6 +86,7 @@ export default {
props: {}, props: {},
data() { data() {
return { return {
activeName: 'table',
open: false, open: false,
visualizationOpen: false, visualizationOpen: false,
trendOpen: false, trendOpen: false,
@ -226,7 +245,7 @@ export default {
parent: 'dateFilterType', parent: 'dateFilterType',
// //
type: 'datePicker', type: 'datePicker',
label: '时间段', // label: '',
dateType: 'daterange', dateType: 'daterange',
format: 'yyyy-MM-dd', format: 'yyyy-MM-dd',
valueFormat: 'yyyy-MM-dd HH:mm:ss', valueFormat: 'yyyy-MM-dd HH:mm:ss',
@ -240,7 +259,7 @@ export default {
parent: 'dateFilterType', parent: 'dateFilterType',
// //
type: 'datePicker', type: 'datePicker',
label: '日期', // label: '',
dateType: 'date', dateType: 'date',
placeholder: '选择日期', placeholder: '选择日期',
format: 'yyyy-MM-dd', format: 'yyyy-MM-dd',
@ -255,16 +274,16 @@ export default {
name: 'search', name: 'search',
color: 'primary', color: 'primary',
}, },
{ // {
type: 'separate', // type: 'separate',
}, // },
{ // {
type: 'button', // type: 'button',
btnName: '设备可视化', // btnName: '',
name: 'visualization', // name: 'visualization',
plain: true, // plain: true,
color: 'success', // color: 'success',
}, // },
// { // {
// type: 'button', // type: 'button',
// btnName: 'OEE', // btnName: 'OEE',
@ -350,7 +369,6 @@ export default {
params: this.queryParams, params: this.queryParams,
}); });
if (code == 0) { if (code == 0) {
console.log('data', data);
this.list = data; this.list = data;
} }
}, },
@ -393,6 +411,8 @@ export default {
}, },
submitForm() {}, submitForm() {},
handleTabClick() {},
}, },
}; };
</script> </script>
@ -402,4 +422,141 @@ export default {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(240px, 1fr)); grid-template-columns: repeat(3, minmax(240px, 1fr));
} }
:deep(.custom-tabs) {
.el-tabs__header {
margin-bottom: 8px;
display: inline-block;
transform: translateY(-12px);
}
.el-tabs__item {
padding-left: 0 !important;
padding-right: 0 !important;
line-height: 36px !important;
height: 36px;
}
}
.blue-title {
position: relative;
padding: 4px 0;
padding-left: 12px;
font-size: 14px;
&::before {
content: '';
position: absolute;
left: 0;
top: 6px;
height: 16px;
width: 4px;
border-radius: 1px;
background: #0b58ff;
}
}
.graph-grid {
margin-top: 8px;
padding: 12px;
position: relative;
border-radius: 12px;
border: 1px solid #ccc;
// background: #0003;
}
.bg-grid {
display: grid;
place-content: center;
grid-template-columns: repeat(4, minmax(280px, 1fr));
grid-auto-columns: 280px;
grid-auto-rows: 290px;
overflow: hidden;
position: relative;
}
.grid-line::after {
content: '';
position: absolute;
top: -1px;
left: -1px;
width: calc(100% + 2px);
height: calc(100% + 2px);
display: inline-block;
border: 8px solid #fff;
}
.grid-charts {
position: absolute;
width: calc(100% - 24px);
top: 12px;
left: 12px;
}
.grid-item {
border: 1px solid #ccc;
}
.grid-item:not(:first-child) {
border-left: 0;
border-top: 0;
}
.legend {
position: absolute;
top: 8px;
right: 12px;
display: flex;
}
.legend .legend-item {
display: flex;
align-items: center;
margin-left: 12px;
}
.legend .legend-item .icon {
width: 10px;
height: 10px;
border-radius: 1px;
margin-right: 4px;
margin-top: 1px;
}
.legend .legend-item .text {
color: #8c8c8c;
}
.blue {
background-color: #3da8fd;
}
.green {
background-color: #8ef0ab;
}
.purple {
background-color: #6b5cfd;
}
.yellow {
background-color: #ffc72a;
}
@media screen and (max-width: 1390px) {
.bg-grid {
grid-template-columns: repeat(3, minmax(280px, 1fr));
}
}
@media screen and (max-width: 1190px) {
.bg-grid {
grid-template-columns: repeat(2, minmax(280px, 1fr));
}
}
@media screen and (max-width: 640px) {
.bg-grid {
grid-template-columns: repeat(1, minmax(280px, 1fr));
}
}
</style> </style>

View File

@ -9,16 +9,52 @@
<div style="flex: 1; display: flex; background: #f2f4f9"> <div style="flex: 1; display: flex; background: #f2f4f9">
<div <div
class="app-container" class="app-container"
style="margin-right: 12px; border-radius: 8px; background: #fff"> style="
<div class="factory-list" style="background: #ccc; height: 36px"></div> margin-right: 12px;
<!-- side bar --> border-radius: 8px;
background: #fff;
padding: 0;
">
<div <div
class="side-bar__left" class="factory-list__selector"
style="width: 240px; padding: 12px; height: 100%"> style="margin: 12px"
title="点击切换工厂"
@mouseenter="factoryListOpen = true"
@mouseleave="factoryListOpen = false">
{{ currentFactory?.label || '工厂名称' }}
<div class="factory-list__wrapper" :class="{ open: factoryListOpen }">
<ul
class="factory-list"
v-if="sidebarContent.length"
@click.prevent="factoryChangeHandler">
<li
v-for="fc in sidebarContent"
:key="fc.id"
:data-value="fc.id"
class="factory-list__item"
:class="{ 'is-current': fc.id == currentFactory?.id }">
{{ fc.label }}
</li>
</ul>
<div v-else style="color: #0008; width: 128px; text-align: center">
- -
</div>
</div>
</div>
<!-- side bar -->
<div class="side-bar__left" style="width: 240px; height: 100%">
<el-tree <el-tree
:data="sidebarContent" class="custom-tree-class"
:data="currentFactory?.children"
:props="treeProps" :props="treeProps"
@node-click="handleSidebarItemClick" /> :empty-text="' - 暂无数据 - '"
icon-class="custom-icon-class"
@node-click="handleSidebarItemClick">
<!-- <div class="custom-tree-node" slot-scope="{ node, data }">
<span class="icon"></span>
<span>{{ node.label }}</span>
</div> -->
</el-tree>
</div> </div>
</div> </div>
<div <div
@ -34,7 +70,7 @@
<el-row> <el-row>
<el-col class="custom-tabs"> <el-col class="custom-tabs">
<el-tabs v-model="activeName" @tab-click="handleTabClick"> <el-tabs v-model="activeName" @tab-click="handleTabClick">
<el-tab-pane label="数据列表" name="table"> <el-tab-pane :label="'\u2002数据列表\u2002'" name="table">
<base-table <base-table
v-if="mode == 'table'" v-if="mode == 'table'"
:table-props="tableProps" :table-props="tableProps"
@ -50,7 +86,7 @@
@clickBtn="handleTableBtnClick" /> --> @clickBtn="handleTableBtnClick" /> -->
</base-table> </base-table>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="柱状图" name="graph"> <el-tab-pane :label="'\u3000柱状图\u3000'" name="graph">
<div class="graph" style="height: 56vh"> <div class="graph" style="height: 56vh">
<!-- graph --> <!-- graph -->
<Graph v-if="list.length" :equipment-list="list" /> <Graph v-if="list.length" :equipment-list="list" />
@ -84,6 +120,16 @@ export default {
props: {}, props: {},
data() { data() {
return { return {
factoryListOpen: false,
currentFactory: null,
factoryList: [
{ name: '1', value: 1 },
{ name: '2', value: 2 },
{ name: '3', value: 3 },
{ name: '4', value: 4 },
{ name: '5', value: 5 },
{ name: '6', value: 6 },
],
sidebarContent: [ sidebarContent: [
// { // {
// id: 'fc1', // id: 'fc1',
@ -284,8 +330,16 @@ export default {
console.log('handle tab click: ', tab, event); console.log('handle tab click: ', tab, event);
}, },
factoryChangeHandler(event) {
this.factoryListOpen = false;
const fcId = event.target.dataset.value;
console.log('fc id', fcId);
this.handleSidebarItemClick({ id: fcId, type: '工厂' });
this.currentFactory = this.sidebarContent.find((item) => item.id == fcId);
},
handleSidebarItemClick({ label, id, type }) { handleSidebarItemClick({ label, id, type }) {
console.log('lable clicked!', label, id, type); console.log('label clicked!', label, id, type);
switch (type) { switch (type) {
case '设备': case '设备':
this.queryParams.equipmentId = id; this.queryParams.equipmentId = id;
@ -341,6 +395,7 @@ export default {
<style scoped> <style scoped>
.side-bar__left >>> .is-current { .side-bar__left >>> .is-current {
padding: 0;
color: #111; color: #111;
background: #f2f4f7; background: #f2f4f7;
} }
@ -369,9 +424,136 @@ export default {
transform: translateY(-12px); transform: translateY(-12px);
} }
.custom-tabs >>> .el-tabs__item { .custom-tabs >>> .el-tabs__item {
padding-left: 8px !important; padding-left: 0px !important;
padding-right: 8px !important; padding-right: 0px !important;
line-height: 36px !important; line-height: 36px !important;
height: 36px; height: 36px;
} }
.factory-list__selector {
position: relative;
height: 36px;
font-size: 16px;
line-height: 36px;
padding-left: 28px;
background: url(../../../assets/images/factory-icon.png) 0 / 10% no-repeat;
}
.factory-list__selector:hover {
cursor: pointer;
color: #0008;
}
.factory-list__selector::after {
/* content: ''; */
position: absolute;
top: 16px;
right: 12px;
display: inline-block;
width: 8px;
height: 8px;
/* background: #5c5c5c; */
border-color: #000;
border-width: 4px;
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
}
.factory-list__selector:hover::after {
/* cursor: pointer; */
border-left-color: #0008;
}
.factory-list__wrapper {
visibility: hidden;
position: absolute;
background: #fff;
box-shadow: 0 0 32px 10px #0002;
border-radius: 8px;
top: 36px;
left: 90px;
/* max-width: 128px; */
height: auto;
width: auto;
white-space: nowrap;
overflow: hidden;
/* transition: all 0.3s ease-out; */
z-index: 1000;
}
.factory-list__wrapper.open {
visibility: visible;
}
ul,
li {
margin: 0;
padding: 0;
list-style: none;
}
.factory-list {
color: #0008;
max-height: 240px;
overflow-y: auto;
}
.factory-list__item {
font-size: 16px;
line-height: 1;
padding: 8px 64px 8px 16px;
/* min-width: 64px; */
position: relative;
}
.factory-list__item:hover,
.factory-list__item.is-current {
background: #e3efff;
color: #0b58ff;
}
.factory-list__item.is-current::after {
content: '√';
position: absolute;
top: 8px;
right: 16px;
font-weight: bold;
}
.custom-tree-class >>> .el-tree-node__content {
height: auto !important;
padding: 8px 12px !important;
}
.custom-tree-class >>> .el-tree-node__children .el-tree-node__content {
padding: 8px 18px !important;
}
.custom-tree-class >>> .el-tree-node__children .el-tree-node__children .el-tree-node__content {
padding: 8px 24px !important;
}
</style>
<style>
.custom-icon-class {
margin-right: 8px;
width: 20px;
height: 24px;
background: url('../../../assets/images/tree-icon-1.png') 100% / contain
no-repeat;
}
.custom-icon-class.el-tree-node__expand-icon.expanded {
transform: unset;
}
.el-tree-node__children .custom-icon-class {
background: url('../../../assets/images/tree-icon-2.png') 100% / contain
no-repeat;
}
.el-tree-node__children .el-tree-node__children .custom-icon-class {
background: unset;
}
</style> </style>