Compare commits
3 Commits
4a12457a10
...
projects/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d84c84d43 | ||
|
|
eb92a37c54 | ||
|
|
6f27310992 |
2
.env.dev
2
.env.dev
@@ -14,7 +14,7 @@ VUE_APP_TITLE = 上上电缆
|
||||
# 芋道管理系统/开发环境
|
||||
|
||||
# VUE_APP_BASE_API = 'http://192.168.0.31:48080'
|
||||
VUE_APP_BASE_API = 'http://172.16.32.236:48080'
|
||||
VUE_APP_BASE_API = 'http://172.16.15.221:48080'
|
||||
# VUE_APP_BASE_API = 'http://line.kszny.picaiba.com'
|
||||
|
||||
# 路由懒加载
|
||||
|
||||
@@ -58,3 +58,11 @@ export function createPCTask(data) {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function cancelUpdateTask(query) {
|
||||
return request({
|
||||
url: '/wms/job-main-task/update/task',
|
||||
method: 'post',
|
||||
params: query ,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,3 +7,35 @@ export function getLineEdgeLibraryList(data) {
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getAgvAlarmInfo(data) {
|
||||
return request({
|
||||
url: '/wms/large-screen/real-time/display',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getTaskTypeToday(data) {
|
||||
return request({
|
||||
url: '/wms/job-main-task/task/type/proportion/today',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getTaskStatusToday(data) {
|
||||
return request({
|
||||
url: '/wms/job-main-task/task/state/distribution/today',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getAgvExample(data) {
|
||||
return request({
|
||||
url: 'wms/large-screen/example',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
1
src/assets/icons/svg/greenLight.svg
Normal file
1
src/assets/icons/svg/greenLight.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1779174119304" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5762" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 434.517333a159.402667 159.402667 0 0 1 159.402667 159.402667v159.402667h-318.805334V593.92A159.402667 159.402667 0 0 1 512 434.517333m0-34.133333a193.877333 193.877333 0 0 0-193.536 193.536v193.536h387.072V593.92A193.877333 193.877333 0 0 0 512 400.384zM772.437333 830.464H251.562667a18.773333 18.773333 0 0 0 0 37.205333h520.874666a18.773333 18.773333 0 0 0 0-37.205333zM170.666667 341.333333a17.066667 17.066667 0 0 0-14.677334 8.533334 17.408 17.408 0 0 0 6.144 23.552L273.066667 438.954667a16.042667 16.042667 0 0 0 8.533333 2.389333 17.408 17.408 0 0 0 15.018667-8.533333 17.066667 17.066667 0 0 0-5.12-23.210667l-112.64-65.194667A15.36 15.36 0 0 0 170.666667 341.333333zM318.805333 204.8a17.408 17.408 0 0 0-8.192 2.389333 17.066667 17.066667 0 0 0-6.485333 23.210667l65.194667 112.981333a17.066667 17.066667 0 0 0 14.677333 8.533334 18.773333 18.773333 0 0 0 8.533333-2.389334 17.066667 17.066667 0 0 0 6.144-23.210666l-64.853333-111.616A17.066667 17.066667 0 0 0 318.805333 204.8zM716.8 204.8a17.066667 17.066667 0 0 0-14.677333 8.533333L637.952 327.68a17.066667 17.066667 0 0 0 6.144 23.210667 20.138667 20.138667 0 0 0 8.533333 2.389333 17.066667 17.066667 0 0 0 15.018667-8.533333l64.853333-112.981334a17.066667 17.066667 0 0 0-6.144-23.210666A18.773333 18.773333 0 0 0 716.8 204.8zM518.485333 156.330667a17.066667 17.066667 0 0 0-17.066666 17.066666V303.786667a17.066667 17.066667 0 0 0 17.066666 17.066666 17.408 17.408 0 0 0 17.066667-17.066666V173.397333a17.408 17.408 0 0 0-17.066667-17.066666zM853.333333 341.333333a15.36 15.36 0 0 0-8.533333 2.389334L732.501333 409.6a17.066667 17.066667 0 0 0-6.485333 23.210667 17.408 17.408 0 0 0 15.018667 8.533333 16.042667 16.042667 0 0 0 8.533333-2.389333l112.64-64.853334a17.408 17.408 0 0 0 6.144-23.552A17.066667 17.066667 0 0 0 853.333333 341.333333z" fill="#1afa29" p-id="5763"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
1
src/assets/icons/svg/redLight.svg
Normal file
1
src/assets/icons/svg/redLight.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1779174119304" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5762" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 434.517333a159.402667 159.402667 0 0 1 159.402667 159.402667v159.402667h-318.805334V593.92A159.402667 159.402667 0 0 1 512 434.517333m0-34.133333a193.877333 193.877333 0 0 0-193.536 193.536v193.536h387.072V593.92A193.877333 193.877333 0 0 0 512 400.384zM772.437333 830.464H251.562667a18.773333 18.773333 0 0 0 0 37.205333h520.874666a18.773333 18.773333 0 0 0 0-37.205333zM170.666667 341.333333a17.066667 17.066667 0 0 0-14.677334 8.533334 17.408 17.408 0 0 0 6.144 23.552L273.066667 438.954667a16.042667 16.042667 0 0 0 8.533333 2.389333 17.408 17.408 0 0 0 15.018667-8.533333 17.066667 17.066667 0 0 0-5.12-23.210667l-112.64-65.194667A15.36 15.36 0 0 0 170.666667 341.333333zM318.805333 204.8a17.408 17.408 0 0 0-8.192 2.389333 17.066667 17.066667 0 0 0-6.485333 23.210667l65.194667 112.981333a17.066667 17.066667 0 0 0 14.677333 8.533334 18.773333 18.773333 0 0 0 8.533333-2.389334 17.066667 17.066667 0 0 0 6.144-23.210666l-64.853333-111.616A17.066667 17.066667 0 0 0 318.805333 204.8zM716.8 204.8a17.066667 17.066667 0 0 0-14.677333 8.533333L637.952 327.68a17.066667 17.066667 0 0 0 6.144 23.210667 20.138667 20.138667 0 0 0 8.533333 2.389333 17.066667 17.066667 0 0 0 15.018667-8.533333l64.853333-112.981334a17.066667 17.066667 0 0 0-6.144-23.210666A18.773333 18.773333 0 0 0 716.8 204.8zM518.485333 156.330667a17.066667 17.066667 0 0 0-17.066666 17.066666V303.786667a17.066667 17.066667 0 0 0 17.066666 17.066666 17.408 17.408 0 0 0 17.066667-17.066666V173.397333a17.408 17.408 0 0 0-17.066667-17.066666zM853.333333 341.333333a15.36 15.36 0 0 0-8.533333 2.389334L732.501333 409.6a17.066667 17.066667 0 0 0-6.485333 23.210667 17.408 17.408 0 0 0 15.018667 8.533333 16.042667 16.042667 0 0 0 8.533333-2.389333l112.64-64.853334a17.408 17.408 0 0 0 6.144-23.552A17.066667 17.066667 0 0 0 853.333333 341.333333z" fill="#d81e06" p-id="5763"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
@@ -1,125 +1,138 @@
|
||||
<template>
|
||||
<div
|
||||
:class="className"
|
||||
:style="{ height: height, width: width, marginLeft: '10px' }" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
require('echarts/theme/macarons'); // echarts theme
|
||||
import resize from '@/utils/chartMixins/resize';
|
||||
|
||||
const animationDuration = 1000;
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
},
|
||||
ringData: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
targetId: '',
|
||||
};
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$el, 'macarons');
|
||||
const _this = this;
|
||||
this.chart.setOption({
|
||||
title: {
|
||||
text: this.title
|
||||
? '{space|}{tip|}{space|}{value|' + this.title + '}'
|
||||
: '',
|
||||
textStyle: {
|
||||
rich: {
|
||||
tip: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 50,
|
||||
backgroundColor: '#288AFF',
|
||||
},
|
||||
space: {
|
||||
width: 8,
|
||||
},
|
||||
value: {
|
||||
fontSize: 14,
|
||||
color: 'black',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
color: ['#33B36B', '#3A8DFF', '#F59A23'],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: '#999',
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: ['当前满位', '当前空位', '在途'],
|
||||
},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// name: '投入',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: [
|
||||
{
|
||||
name: '当前满位',
|
||||
value: this.ringData.occupiedQuantity
|
||||
},
|
||||
{
|
||||
name: '当前空位',
|
||||
value: this.ringData.idleQuantity
|
||||
},
|
||||
{
|
||||
name: '在途',
|
||||
value: this.ringData.transitQuantity
|
||||
},
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
:class="className"
|
||||
:style="{ height: height, width: width, marginLeft: '10px' }" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
require('echarts/theme/macarons'); // echarts theme
|
||||
import resize from '@/utils/chartMixins/resize';
|
||||
|
||||
const animationDuration = 1000;
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
},
|
||||
ringData: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
targetId: '',
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
ringData: {
|
||||
handler() {
|
||||
this.$nextTick(() => {
|
||||
this.initChart();
|
||||
});
|
||||
},
|
||||
deep: true,
|
||||
immediate: false,
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
console.log(this.ringData,'ringData');
|
||||
|
||||
this.chart = echarts.init(this.$el, 'macarons');
|
||||
const _this = this;
|
||||
this.chart.setOption({
|
||||
title: {
|
||||
text: this.title
|
||||
? '{space|}{tip|}{space|}{value|' + this.title + '}'
|
||||
: '',
|
||||
textStyle: {
|
||||
rich: {
|
||||
tip: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 50,
|
||||
backgroundColor: '#288AFF',
|
||||
},
|
||||
space: {
|
||||
width: 8,
|
||||
},
|
||||
value: {
|
||||
fontSize: 14,
|
||||
color: 'black',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
color: ['#33B36B', '#3A8DFF', '#F59A23'],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: '#999',
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: ['当前满位', '当前空位', '在途'],
|
||||
},
|
||||
grid: {
|
||||
containLabel: true,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// name: '投入',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: [
|
||||
{
|
||||
name: '当前满位',
|
||||
value: this.ringData.occupiedQuantity
|
||||
},
|
||||
{
|
||||
name: '当前空位',
|
||||
value: this.ringData.idleQuantity
|
||||
},
|
||||
{
|
||||
name: '在途',
|
||||
value: this.ringData.transitQuantity
|
||||
},
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,36 +2,32 @@
|
||||
<div style="flex: 1">
|
||||
<div class="agvStatusContent">
|
||||
<div class="title">AGV状态</div>
|
||||
<div class="agvDetailData">
|
||||
<div class="item" v-for="(agv, index) in agvList" :key="agv.id">
|
||||
<img class="car" src="../../../../assets/img/agvCar.png" alt="AGV小车">
|
||||
<div class="eqInfo">
|
||||
<div class="eqCode">
|
||||
<div class="num">{{ index + 1 }}</div>
|
||||
<div class="code">{{ agv.code }}</div>
|
||||
</div>
|
||||
<div class="eqStatusData">
|
||||
<!-- 状态图标 -->
|
||||
<img style="width: 16px; height: 16px; margin-right: 4px;" :src="require(`@/assets/img/${agv.statusImg}`)"
|
||||
:alt="agv.status">
|
||||
<!-- 状态文字(固定宽度) -->
|
||||
<div class="runningState" :style="getStatusColor(agv.status)">
|
||||
{{ agv.status }}
|
||||
<!-- 添加滚动容器 -->
|
||||
<div class="agvDetailDataWrapper">
|
||||
<div class="agvDetailData">
|
||||
<div class="item" v-for="(agv, index) in agvList" :key="agv.id">
|
||||
<img class="car" src="../../../../assets/img/agvCar.png" alt="AGV小车">
|
||||
<div class="eqInfo">
|
||||
<div class="eqCode">
|
||||
<div class="num">{{ index + 1 }}</div>
|
||||
<div class="code">{{ agv.deviceName }}</div>
|
||||
</div>
|
||||
<!-- 动态栅格容器 -->
|
||||
<div class="charge" :class="{
|
||||
'charge--run': agv.status !== '充电中', // 运行中/待命用run背景
|
||||
'charge--charge': agv.status === '充电中' // 充电中用charge背景
|
||||
}">
|
||||
<div class="grid-item" :style="{ backgroundColor: getGridColor(agv.status) }"
|
||||
v-for="(grid, idx) in generateGridArray(getGridCount(agv.progress))" :key="idx"
|
||||
></div>
|
||||
<div class="eqStatusData">
|
||||
<!-- 状态图标 -->
|
||||
<img style="width: 14px; height: 14px; margin-right: 4px;" :src="getStatusImage(agv.alarmTrigger)"
|
||||
:alt="agv.status">
|
||||
<!-- 状态文字 -->
|
||||
<div class="runningState" :style="getStatusColor(agv.status)">
|
||||
{{ agv.alarmTrigger === true ? '报警' : '正常' }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div style="margin-left: 130px;">
|
||||
<svg-icon style="font-size: 60px;"
|
||||
:icon-class="agv.alarmTrigger ? 'redLight' : 'greenLight'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="qty">
|
||||
<div class="num">{{ agv.completed }}</div>
|
||||
<div class="qtyTitle">今日完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,58 +40,25 @@ export default {
|
||||
name: 'AGVStatus',
|
||||
components: {},
|
||||
props: {
|
||||
energyObj: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
electricComu: 0,
|
||||
steamComu: 0
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
agvList: [
|
||||
{
|
||||
id: 1,
|
||||
code: '设备编号1212121',
|
||||
status: '运行中',
|
||||
statusImg: 'runCircle.png',
|
||||
completed: 47,
|
||||
progress: 80 // 进度80% → 显示4个栅格
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
code: '设备编号3434343',
|
||||
status: '待命',
|
||||
statusImg: 'standbyCircle.png',
|
||||
completed: 23,
|
||||
progress: 40 // 进度40% → 显示2个栅格
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
code: '设备编号5656565',
|
||||
status: '充电中',
|
||||
statusImg: 'chargeCircle.png',
|
||||
completed: 15,
|
||||
progress: 100 // 进度100% → 显示5个栅格
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
code: '设备编号7878787',
|
||||
status: '充电中',
|
||||
statusImg: 'chargeCircle.png',
|
||||
completed: 31,
|
||||
progress: 60 // 进度60% → 显示3个栅格
|
||||
}
|
||||
]
|
||||
agvList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getStatusImage(alarmTrigger) {
|
||||
// alarmTrigger: true → 报警(chargeCircle),false → 正常(runCircle)
|
||||
if (alarmTrigger) {
|
||||
return require('@/assets/img/chargeCircle.png')
|
||||
} else {
|
||||
return require('@/assets/img/runCircle.png')
|
||||
}
|
||||
},
|
||||
// 获取状态文字颜色
|
||||
getStatusColor(status) {
|
||||
const baseStyle = {
|
||||
color: '', // 默认颜色
|
||||
letterSpacing: '0px' // 默认字间距
|
||||
color: '',
|
||||
letterSpacing: '0px'
|
||||
};
|
||||
|
||||
switch (status) {
|
||||
@@ -104,37 +67,16 @@ export default {
|
||||
break;
|
||||
case '待命':
|
||||
baseStyle.color = 'rgba(245, 154, 36, 1)';
|
||||
baseStyle.letterSpacing = '8px'; // 待命状态单独加字间距(可调整数值)
|
||||
baseStyle.letterSpacing = '8px';
|
||||
break;
|
||||
case '充电中':
|
||||
baseStyle.color = 'rgba(0, 225, 37, 1)';
|
||||
break;
|
||||
default:
|
||||
baseStyle.color = '';
|
||||
baseStyle.color = '#666';
|
||||
}
|
||||
|
||||
return baseStyle;
|
||||
},
|
||||
// 根据状态获取背景图
|
||||
getBgImage(status) {
|
||||
return status === '充电中' ? 'chargeBg.png' : 'run.png';
|
||||
},
|
||||
// 根据状态获取栅格颜色
|
||||
getGridColor(status) {
|
||||
return status === '充电中'
|
||||
? 'rgba(0, 225, 37, 1)'
|
||||
: 'rgba(120, 138, 221, 1)';
|
||||
},
|
||||
getGridCount(progress) {
|
||||
// 总栅格位置为5个,进度0%→0个,100%→5个,按比例取整
|
||||
const totalGrids = 5;
|
||||
return Math.ceil((progress / 100) * totalGrids);
|
||||
},
|
||||
|
||||
// 生成栅格数组(用于v-for遍历)
|
||||
generateGridArray(count) {
|
||||
// 生成 [1,2,...,count] 的数组,长度为count
|
||||
return Array.from({ length: count }, (_, i) => i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,6 +89,8 @@ export default {
|
||||
background: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
padding: 16px 18px 26px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
@@ -159,6 +103,7 @@ export default {
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
padding-left: 12px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -172,45 +117,65 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.agvDetailData {
|
||||
// 滚动容器包装器
|
||||
.agvDetailDataWrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
margin-top: 15px;
|
||||
|
||||
// 隐藏滚动条
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.agvDetailData {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 8px; // 减小间距
|
||||
|
||||
.item {
|
||||
transform: scale(0.8); // 缩小到80%
|
||||
width: 332px;
|
||||
height: 101px;
|
||||
padding: 22px 0 0 10px;
|
||||
display: flex;
|
||||
background: url('../../../../assets/img/agvItemBg.png') no-repeat;
|
||||
background-size: cover;
|
||||
flex-shrink: 0;
|
||||
margin: -10px 0; // 负边距抵消缩放带来的空白
|
||||
|
||||
// 调整内部元素大小
|
||||
.car {
|
||||
width: 66px;
|
||||
height: 57px;
|
||||
width: 53px; // 从66px缩小
|
||||
height: 46px; // 从57px缩小
|
||||
}
|
||||
|
||||
.eqInfo {
|
||||
margin-left: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 6px; // 减小间距
|
||||
|
||||
.eqCode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.num {
|
||||
width: 21px;
|
||||
height: 24px;
|
||||
width: 17px; // 从21px缩小
|
||||
height: 19px; // 从24px缩小
|
||||
background: #0065FF;
|
||||
backdrop-filter: blur(1.5px);
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
font-size: 13px; // 从16px缩小
|
||||
color: #FFFFFF;
|
||||
line-height: 18px;
|
||||
line-height: 19px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -218,7 +183,7 @@ export default {
|
||||
margin-left: 4px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
font-size: 13px; // 从16px缩小
|
||||
color: #020203;
|
||||
line-height: 18px;
|
||||
}
|
||||
@@ -228,69 +193,14 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
// 状态文字固定宽度(确保三种状态宽度一致)
|
||||
.runningState {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
font-size: 13px; // 从16px缩小
|
||||
line-height: 20px;
|
||||
width: 48px; // 固定宽度,适配三种状态文字
|
||||
text-align: left; // 左对齐,避免空格填充
|
||||
width: 48px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
// 栅格容器样式
|
||||
.charge {
|
||||
width: 73px;
|
||||
height: 27px;
|
||||
margin-left: 5px;
|
||||
background-size: 100% 100% !important;
|
||||
background-repeat: no-repeat !important;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 1px;
|
||||
padding: 5px 0px 4px 3px;
|
||||
|
||||
// 运行中/待命状态的背景图
|
||||
&.charge--run {
|
||||
background-image: url('../../../../assets/img/runBg.png');
|
||||
}
|
||||
|
||||
// 充电中状态的背景图
|
||||
&.charge--charge {
|
||||
background-image: url('../../../../assets/img/chargeBg.png');
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 12px;
|
||||
height: 18px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 24px;
|
||||
gap: 12px;
|
||||
|
||||
.num {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 31px;
|
||||
color: #000000;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qtyTitle {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(2, 2, 3, 0.6);
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<div style="flex: 1">
|
||||
<div class="agvStatusContent">
|
||||
<div class="title">异常报警</div>
|
||||
<div class="alarm-item" style="margin-top: 13px;">
|
||||
<!-- <div class="alarm-item" style="margin-top: 13px;">
|
||||
<div class="item">
|
||||
9:05:21|FMS|AGV-03顶升失败,任务TSK25-1004中断
|
||||
</div>
|
||||
<div class="item">
|
||||
9:05:21|FMS|AGV-03顶升失败,任务TSK25-1004中断
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,928 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<div class="agvStatusContent">
|
||||
<div class="title">报警详情</div>
|
||||
|
||||
<!-- 有 deviceName 时:网格布局,固定显示5个 -->
|
||||
<div v-if="hasDeviceName" class="agvDetailDataGrid">
|
||||
<div class="item" v-for="(agv, index) in agvList" :key="agv.id || index">
|
||||
<div class="agvBasicInfo">
|
||||
<div class="eqInfo">
|
||||
<div class="eqCode">
|
||||
<div class="num">{{ index + 1 }}</div>
|
||||
<div class="code">{{ agv.deviceName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alarmDetailWrapper" v-if="agv.data && agv.data.length > 0">
|
||||
<div class="alarmDetailTitle">
|
||||
<span class="titleText">告警详情</span>
|
||||
<span class="alarmCount" v-if="agv.data.length > 1">({{ agv.data.length }})</span>
|
||||
</div>
|
||||
<div class="alarmDetailScroll" :ref="`scroll_${index}`" @mouseenter="pauseAutoScroll(index)"
|
||||
@mouseleave="resumeAutoScroll(index)">
|
||||
<div class="alarmDetailList">
|
||||
<div class="alarmItem" v-for="(alarm, alarmIndex) in agv.data" :key="alarmIndex">
|
||||
<div class="alarmHeader" v-if="agv.data.length > 1">
|
||||
<span class="alarmIndex">告警 {{ alarmIndex + 1 }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="alarm.warnName">
|
||||
<span class="fieldLabel">警告名称:</span>
|
||||
<span class="fieldValue">{{ alarm.warnName }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="alarm.faultComponent">
|
||||
<span class="fieldLabel">故障部件:</span>
|
||||
<span class="fieldValue">{{ alarm.faultComponent }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="alarm.faultEventName">
|
||||
<span class="fieldLabel">故障事件:</span>
|
||||
<span class="fieldValue">{{ alarm.faultEventName }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="alarm.eventCause">
|
||||
<span class="fieldLabel">事件原因:</span>
|
||||
<span class="fieldValue">{{ alarm.eventCause }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="alarm.troubleshootStep">
|
||||
<span class="fieldLabel">排查步骤:</span>
|
||||
<span class="fieldValue">{{ alarm.troubleshootStep }}</span>
|
||||
</div>
|
||||
<div class="alarmDivider" v-if="agv.data.length > 1 && alarmIndex < agv.data.length - 1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="noAlarm" v-else>
|
||||
<span class="noAlarmText">暂无告警信息</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 没有 deviceName 时:轮播滚动,同时显示2个 -->
|
||||
<div v-else class="agvDetailDataWrapper" @mouseenter="pauseCarousel" @mouseleave="resumeCarousel">
|
||||
<div class="carouselContainer" :style="{ transform: `translateY(-${currentOffset}px)` }">
|
||||
<div v-for="(item, index) in displayList" :key="item.id || index" class="carouselItemGroup">
|
||||
<div class="carouselItem">
|
||||
<div class="alarmBasicInfo">
|
||||
<div class="eqCode">
|
||||
<div class="num">{{ index + 1 }}</div>
|
||||
<div class="code">{{ item.warnName || '告警信息' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alarmDetailWrapper">
|
||||
<div class="alarmDetailList">
|
||||
<div class="alarmItem">
|
||||
<div class="alarmField" v-if="item.warnName">
|
||||
<span class="fieldLabel">警告名称:</span>
|
||||
<span class="fieldValue">{{ item.warnName }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="item.faultComponent">
|
||||
<span class="fieldLabel">故障部件:</span>
|
||||
<span class="fieldValue">{{ item.faultComponent }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="item.faultEventName">
|
||||
<span class="fieldLabel">故障事件:</span>
|
||||
<span class="fieldValue">{{ item.faultEventName }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="item.eventCause">
|
||||
<span class="fieldLabel">事件原因:</span>
|
||||
<span class="fieldValue">{{ item.eventCause }}</span>
|
||||
</div>
|
||||
<div class="alarmField" v-if="item.troubleshootStep">
|
||||
<span class="fieldLabel">排查步骤:</span>
|
||||
<span class="fieldValue">{{ item.troubleshootStep }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AGVStatus',
|
||||
components: {},
|
||||
props: {
|
||||
agvList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0,
|
||||
currentOffset: 0,
|
||||
carouselInterval: null,
|
||||
isPaused: false,
|
||||
itemHeight: 0,
|
||||
itemsPerView: 2,
|
||||
// 永久保存:仅记录【无deviceName】时的轮播位置
|
||||
savedCarouselIndex: 0,
|
||||
lastHasDeviceName: false,
|
||||
autoScrollTimers: {},
|
||||
scrollPausedStatus: {},
|
||||
currentAlarmIndex: {},
|
||||
// 标记是否已经初始化过轮播
|
||||
isCarouselInitialized: false,
|
||||
// 优化:防抖和缓存
|
||||
updateTimer: null,
|
||||
pendingUpdate: false,
|
||||
_retryCount: 0,
|
||||
_cachedScrollElements: null,
|
||||
_cachedAlarmItems: null,
|
||||
resizeObserver: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasDeviceName() {
|
||||
if (!this.agvList || this.agvList.length === 0) return false
|
||||
return this.agvList[0] && this.agvList[0].deviceName !== undefined
|
||||
},
|
||||
displayList() {
|
||||
return this.agvList || []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 优化1:使用防抖优化模式切换
|
||||
hasDeviceName(newVal, oldVal) {
|
||||
if (newVal === oldVal) return
|
||||
|
||||
console.log('模式切换:', '旧值=', oldVal, '新值=', newVal, '保存的位置=', this.savedCarouselIndex)
|
||||
|
||||
// 从有 deviceName 切换到无 deviceName
|
||||
if (newVal === false && oldVal === true) {
|
||||
console.log('切换到无deviceName模式,使用保存的位置:', this.savedCarouselIndex)
|
||||
this.isCarouselInitialized = false
|
||||
this.stopCarousel()
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.initCarousel()
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
// 从无 deviceName 切换到有 deviceName
|
||||
else if (newVal === true && oldVal === false) {
|
||||
console.log('切换到有deviceName模式,保存当前位置:', this.currentIndex)
|
||||
this.saveCurrentCarouselPosition()
|
||||
this.stopCarousel()
|
||||
}
|
||||
|
||||
this.lastHasDeviceName = newVal
|
||||
},
|
||||
|
||||
// 优化2:使用防抖和深度比较优化数据监听
|
||||
agvList: {
|
||||
handler(newList, oldList) {
|
||||
// 避免重复初始化
|
||||
if (this.pendingUpdate) return
|
||||
this.pendingUpdate = true
|
||||
|
||||
// 使用防抖
|
||||
if (this.updateTimer) clearTimeout(this.updateTimer)
|
||||
this.updateTimer = setTimeout(() => {
|
||||
// 只在数据真正变化时才重新初始化
|
||||
const hasChanged = JSON.stringify(newList) !== JSON.stringify(oldList)
|
||||
|
||||
if (hasChanged) {
|
||||
if (!this.hasDeviceName) {
|
||||
// 无 deviceName 模式:数据变化后重新初始化轮播
|
||||
console.log('无deviceName模式数据更新,保存的位置:', this.savedCarouselIndex)
|
||||
this.stopCarousel()
|
||||
this.isCarouselInitialized = false
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.initCarousel()
|
||||
}, 50)
|
||||
})
|
||||
} else {
|
||||
// 有 deviceName 模式:重新初始化自动滚动
|
||||
this.stopAllAutoScroll()
|
||||
// 清理缓存
|
||||
this._cachedScrollElements = null
|
||||
this._cachedAlarmItems = null
|
||||
this.$nextTick(() => this.initAutoScroll())
|
||||
}
|
||||
}
|
||||
|
||||
this.pendingUpdate = false
|
||||
}, 200) // 增加防抖延迟
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 尝试从 localStorage 读取保存的位置
|
||||
this.loadSavedPosition()
|
||||
this.lastHasDeviceName = this.hasDeviceName
|
||||
|
||||
if (!this.hasDeviceName) {
|
||||
this.initCarousel()
|
||||
}
|
||||
if (this.hasDeviceName) {
|
||||
this.initAutoScroll()
|
||||
}
|
||||
|
||||
// 使用 ResizeObserver 监听容器大小变化
|
||||
this.initResizeObserver()
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 清理定时器
|
||||
if (this.updateTimer) clearTimeout(this.updateTimer)
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect()
|
||||
}
|
||||
|
||||
// 保存位置
|
||||
if (!this.hasDeviceName) {
|
||||
this.saveCurrentCarouselPosition()
|
||||
}
|
||||
|
||||
this.stopCarousel()
|
||||
this.stopAllAutoScroll()
|
||||
|
||||
// 清理缓存
|
||||
this._cachedScrollElements = null
|
||||
this._cachedAlarmItems = null
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化 ResizeObserver
|
||||
*/
|
||||
initResizeObserver() {
|
||||
if (window.ResizeObserver) {
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
if (!this.hasDeviceName && this.isCarouselInitialized) {
|
||||
// 容器大小变化时重新计算高度
|
||||
this.recalculateItemHeight()
|
||||
}
|
||||
})
|
||||
|
||||
const container = this.$el?.querySelector('.agvDetailDataWrapper')
|
||||
if (container) {
|
||||
this.resizeObserver.observe(container)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 重新计算项目高度
|
||||
*/
|
||||
recalculateItemHeight() {
|
||||
const item = this.$el?.querySelector('.carouselItem')
|
||||
if (item) {
|
||||
const newHeight = item.offsetHeight + 12
|
||||
if (newHeight !== this.itemHeight) {
|
||||
this.itemHeight = newHeight
|
||||
this.currentOffset = this.currentIndex * this.itemHeight
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从 localStorage 加载保存的位置
|
||||
*/
|
||||
loadSavedPosition() {
|
||||
try {
|
||||
const saved = localStorage.getItem('agv_carousel_position')
|
||||
if (saved !== null) {
|
||||
const position = parseInt(saved, 10)
|
||||
if (!isNaN(position) && position >= 0) {
|
||||
this.savedCarouselIndex = position
|
||||
console.log('从 localStorage 加载轮播位置:', position)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('读取 localStorage 失败:', e)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存当前轮播位置到 localStorage
|
||||
*/
|
||||
saveCurrentCarouselPosition() {
|
||||
// 只有在无 deviceName 模式下才保存
|
||||
if (!this.hasDeviceName && this.displayList.length > 0) {
|
||||
const maxIndex = Math.max(0, this.displayList.length - this.itemsPerView)
|
||||
// 确保保存的位置在有效范围内
|
||||
let saveIndex = this.currentIndex
|
||||
if (saveIndex > maxIndex) {
|
||||
saveIndex = maxIndex
|
||||
}
|
||||
if (saveIndex >= 0) {
|
||||
this.savedCarouselIndex = saveIndex
|
||||
console.log('保存轮播位置到内存:', saveIndex)
|
||||
|
||||
// 同时保存到 localStorage
|
||||
try {
|
||||
localStorage.setItem('agv_carousel_position', saveIndex)
|
||||
console.log('保存轮播位置到 localStorage:', saveIndex)
|
||||
} catch (e) {
|
||||
console.error('保存到 localStorage 失败:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 优化后的轮播初始化
|
||||
*/
|
||||
initCarousel() {
|
||||
// 防止重复初始化
|
||||
if (this.isCarouselInitialized) {
|
||||
console.log('轮播已初始化,跳过')
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 requestAnimationFrame 代替 setTimeout
|
||||
requestAnimationFrame(() => {
|
||||
const container = this.$el?.querySelector('.carouselContainer')
|
||||
const item = this.$el?.querySelector('.carouselItem')
|
||||
|
||||
if (!container || !item) {
|
||||
// 最多重试3次
|
||||
if (this._retryCount < 3) {
|
||||
this._retryCount++
|
||||
console.log(`DOM未就绪,第${this._retryCount}次重试`)
|
||||
requestAnimationFrame(() => this.initCarousel())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this._retryCount = 0
|
||||
this.itemHeight = item.offsetHeight + 12
|
||||
const maxIndex = Math.max(0, this.displayList.length - this.itemsPerView)
|
||||
|
||||
// 读取保存的位置
|
||||
let idx = this.savedCarouselIndex
|
||||
|
||||
// 如果内存中的位置是0,尝试从localStorage读取
|
||||
if (idx === 0 && this.displayList.length > this.itemsPerView) {
|
||||
try {
|
||||
const saved = localStorage.getItem('agv_carousel_position')
|
||||
if (saved !== null) {
|
||||
const savedIdx = parseInt(saved, 10)
|
||||
if (!isNaN(savedIdx) && savedIdx > 0 && savedIdx <= maxIndex) {
|
||||
idx = savedIdx
|
||||
this.savedCarouselIndex = idx
|
||||
console.log('从 localStorage 恢复到位置:', idx)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('读取 localStorage 失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 边界检查
|
||||
idx = Math.min(idx, maxIndex)
|
||||
idx = Math.max(0, idx)
|
||||
|
||||
this.currentIndex = idx
|
||||
this.currentOffset = idx * this.itemHeight
|
||||
|
||||
// 标记已初始化
|
||||
this.isCarouselInitialized = true
|
||||
|
||||
console.log('轮播初始化完成,从位置继续:', this.currentIndex, '保存的位置:', this.savedCarouselIndex, '最大索引:', maxIndex)
|
||||
|
||||
// 启动轮播
|
||||
this.startCarousel()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动轮播
|
||||
*/
|
||||
startCarousel() {
|
||||
this.stopCarousel()
|
||||
|
||||
// 如果数据不足一页,不需要轮播
|
||||
if (this.displayList.length <= this.itemsPerView) {
|
||||
console.log('数据不足一页,不启动轮播')
|
||||
return
|
||||
}
|
||||
|
||||
this.carouselInterval = setInterval(() => {
|
||||
// 暂停时 或 有 deviceName 模式时,不滚动也不保存
|
||||
if (this.isPaused || this.hasDeviceName) return
|
||||
|
||||
const step = this.itemsPerView
|
||||
let newIndex
|
||||
|
||||
if (this.currentIndex + step < this.displayList.length) {
|
||||
newIndex = this.currentIndex + step
|
||||
} else {
|
||||
newIndex = 0
|
||||
}
|
||||
|
||||
this.currentIndex = newIndex
|
||||
this.currentOffset = this.currentIndex * this.itemHeight
|
||||
|
||||
// 保存当前位置
|
||||
this.saveCurrentCarouselPosition()
|
||||
}, 5000)
|
||||
|
||||
console.log('轮播已启动,间隔5秒')
|
||||
},
|
||||
|
||||
/**
|
||||
* 优化后的自动滚动初始化
|
||||
*/
|
||||
initAutoScroll() {
|
||||
// 使用 requestAnimationFrame 批量处理
|
||||
requestAnimationFrame(() => {
|
||||
this.agvList.forEach((agv, index) => {
|
||||
if (agv.data && agv.data.length) {
|
||||
// 延迟初始化,避免同时渲染
|
||||
setTimeout(() => {
|
||||
if (!this.currentAlarmIndex[index]) {
|
||||
this.$set(this.currentAlarmIndex, index, 0)
|
||||
}
|
||||
this.scrollToAlarm(index, 0)
|
||||
this.startAutoScroll(index)
|
||||
}, index * 50) // 错开初始化时间
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 优化:缓存 DOM 查询结果的滚动方法
|
||||
*/
|
||||
scrollToAlarm(index, alarmIndex) {
|
||||
const ref = this.$refs[`scroll_${index}`]
|
||||
if (!ref) return
|
||||
|
||||
// 缓存元素查询结果
|
||||
if (!this._cachedScrollElements) {
|
||||
this._cachedScrollElements = {}
|
||||
}
|
||||
|
||||
const el = ref[0] || ref
|
||||
if (this._cachedScrollElements[index] !== el) {
|
||||
this._cachedScrollElements[index] = el
|
||||
this._cachedAlarmItems = null // 清除缓存
|
||||
}
|
||||
|
||||
// 获取或缓存告警项
|
||||
if (!this._cachedAlarmItems || !this._cachedAlarmItems[index]) {
|
||||
if (!this._cachedAlarmItems) this._cachedAlarmItems = {}
|
||||
this._cachedAlarmItems[index] = el.querySelectorAll('.alarmItem')
|
||||
}
|
||||
|
||||
const items = this._cachedAlarmItems[index]
|
||||
if (items && items[alarmIndex]) {
|
||||
// 使用 requestAnimationFrame 优化滚动性能
|
||||
requestAnimationFrame(() => {
|
||||
el.scrollTo({
|
||||
top: items[alarmIndex].offsetTop - el.offsetTop,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动单个AGV的告警自动滚动
|
||||
*/
|
||||
startAutoScroll(index) {
|
||||
if (this.autoScrollTimers[index]) {
|
||||
clearInterval(this.autoScrollTimers[index])
|
||||
}
|
||||
|
||||
const len = this.agvList[index]?.data?.length
|
||||
if (!len || len <= 1) return
|
||||
|
||||
this.autoScrollTimers[index] = setInterval(() => {
|
||||
if (this.scrollPausedStatus[index]) return
|
||||
|
||||
const newIndex = (this.currentAlarmIndex[index] + 1) % len
|
||||
this.currentAlarmIndex[index] = newIndex
|
||||
this.scrollToAlarm(index, newIndex)
|
||||
}, 10000)
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止单个AGV的告警自动滚动
|
||||
*/
|
||||
stopAutoScroll(index) {
|
||||
if (this.autoScrollTimers[index]) {
|
||||
clearInterval(this.autoScrollTimers[index])
|
||||
delete this.autoScrollTimers[index]
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 暂停单个AGV的告警自动滚动
|
||||
*/
|
||||
pauseAutoScroll(index) {
|
||||
this.$set(this.scrollPausedStatus, index, true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复单个AGV的告警自动滚动
|
||||
*/
|
||||
resumeAutoScroll(index) {
|
||||
this.$set(this.scrollPausedStatus, index, false)
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止所有告警自动滚动
|
||||
*/
|
||||
stopAllAutoScroll() {
|
||||
Object.keys(this.autoScrollTimers).forEach(key => {
|
||||
clearInterval(this.autoScrollTimers[key])
|
||||
})
|
||||
this.autoScrollTimers = {}
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止轮播
|
||||
*/
|
||||
stopCarousel() {
|
||||
if (this.carouselInterval) {
|
||||
clearInterval(this.carouselInterval)
|
||||
this.carouselInterval = null
|
||||
}
|
||||
this.isCarouselInitialized = false
|
||||
},
|
||||
|
||||
/**
|
||||
* 暂停轮播(鼠标悬停时)
|
||||
*/
|
||||
pauseCarousel() {
|
||||
this.isPaused = true
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复轮播(鼠标离开时)
|
||||
*/
|
||||
resumeCarousel() {
|
||||
this.isPaused = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.agvStatusContent {
|
||||
width: 100%;
|
||||
height: 442px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
line-height: 16px;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
padding-left: 12px;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: #0A4BFF;
|
||||
border-radius: 1px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 有 deviceName 时:网格布局
|
||||
.agvDetailDataGrid {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
align-content: start;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
background: #F8F9FC;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
height: 376px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// 无 deviceName 时:轮播容器
|
||||
.agvDetailDataWrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.carouselContainer {
|
||||
transition: transform 0.5s ease-in-out;
|
||||
will-change: transform; // 优化动画性能
|
||||
}
|
||||
|
||||
.carouselItemGroup {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.carouselItem {
|
||||
background: #F8F9FC;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.alarmDetailWrapper {
|
||||
margin-top: 8px;
|
||||
|
||||
.alarmDetailList {
|
||||
gap: 6px;
|
||||
|
||||
.alarmItem {
|
||||
padding: 6px;
|
||||
|
||||
.alarmField {
|
||||
margin-bottom: 4px;
|
||||
font-size: 11px;
|
||||
|
||||
.fieldLabel {
|
||||
width: 65px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.fieldValue {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alarmBasicInfo {
|
||||
padding-bottom: 8px;
|
||||
|
||||
.eqCode {
|
||||
.num {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 11px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.agvBasicInfo {
|
||||
display: flex;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
flex-shrink: 0;
|
||||
|
||||
.eqInfo {
|
||||
.eqCode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.num {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #0065FF;
|
||||
border-radius: 2px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.code {
|
||||
margin-left: 6px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #020203;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alarmBasicInfo {
|
||||
display: flex;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
flex-shrink: 0;
|
||||
|
||||
.eqCode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.num {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #0065FF;
|
||||
border-radius: 4px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.code {
|
||||
margin-left: 8px;
|
||||
font-family: PingFangSC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #020203;
|
||||
line-height: 18px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alarmDetailWrapper {
|
||||
margin-top: 12px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
|
||||
.alarmDetailTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.titleText {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.alarmCount {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
.alarmDetailScroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
min-height: 0;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alarmDetailList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.alarmItem {
|
||||
padding: 8px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.alarmHeader {
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px dashed rgba(0, 0, 0, 0.06);
|
||||
|
||||
.alarmIndex {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #0A4BFF;
|
||||
}
|
||||
}
|
||||
|
||||
.alarmField {
|
||||
display: flex;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.fieldLabel {
|
||||
flex-shrink: 0;
|
||||
width: 70px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.fieldValue {
|
||||
flex: 1;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.alarmDivider {
|
||||
height: 1px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
margin: 8px 0 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.noAlarm {
|
||||
margin-top: 12px;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
border-radius: 6px;
|
||||
|
||||
.noAlarmText {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
.emptyState {
|
||||
height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<div class="agvStatusContent">
|
||||
<div class="title">异常原因Top5</div>
|
||||
<!-- <div class="title">异常原因Top5</div>
|
||||
<div class="alarm-item" style="margin-top: 14px;">
|
||||
<div class="item top1">
|
||||
<div class="item-data">
|
||||
@@ -78,7 +78,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -111,7 +111,7 @@ export default {
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.agvStatusContent {
|
||||
width: 402px;
|
||||
width: 1344px;
|
||||
height: 442px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<template>
|
||||
<!-- 动态绑定 ref:使用 props 中的 chartRef,而非硬编码 -->
|
||||
<div :ref="chartRef" id="coreLineChart" style="height: 170px; width: 100%;"></div>
|
||||
</template>
|
||||
|
||||
@@ -9,21 +8,23 @@ import * as echarts from 'echarts';
|
||||
export default {
|
||||
name: 'Container',
|
||||
props: {
|
||||
// 1. 重命名 props:ref → chartRef,避免关键字冲突,同时定义类型和默认值
|
||||
chartRef: {
|
||||
type: String,
|
||||
required: true, // 强制父组件传值,避免获取不到 DOM
|
||||
// validator: (value) => {
|
||||
// // 验证:ref 名不能为空,确保有效
|
||||
// return value.trim() !== '';
|
||||
// }
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
data() {
|
||||
return {};
|
||||
watch: {
|
||||
data: {
|
||||
handler() {
|
||||
this.initData();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
@@ -31,243 +32,122 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
// 2. 动态获取 DOM:通过 props 中的 chartRef 拿到对应的 ref 元素
|
||||
const chartDom = this.$refs[this.chartRef];
|
||||
if (!chartDom) {
|
||||
console.error(`图表容器未找到!请确认父组件传递的 chartRef 为 "${this.chartRef}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.data || this.data.length === 0) {
|
||||
console.warn('未传入数据或数据为空');
|
||||
return;
|
||||
}
|
||||
|
||||
const myChart = echarts.init(chartDom);
|
||||
|
||||
// 自定义颜色数组(与系列一一对应)
|
||||
const customColors = [
|
||||
'rgba(113, 100, 255, 1)',
|
||||
'rgba(40, 138, 255, 1)',
|
||||
'rgba(118, 218, 190, 1)',
|
||||
'rgba(255, 206, 106, 1)',
|
||||
];
|
||||
// 主任务类型映射
|
||||
const taskTypeMap = {
|
||||
1: '满盘搬运',
|
||||
2: '空盘搬运',
|
||||
3: '备货搬运',
|
||||
};
|
||||
|
||||
// 按你原有顺序定义颜色(顺序按映射顺序排)
|
||||
const colorMap = {
|
||||
1: 'rgba(40, 138, 255, 1)', // 满盘搬运
|
||||
2: 'rgba(168, 233, 255, 1)', // 空盘搬运
|
||||
3: 'rgba(143, 146, 255, 1)', // 备货搬运
|
||||
};
|
||||
|
||||
// 转换数据格式
|
||||
const seriesData = this.data.map(item => ({
|
||||
name: taskTypeMap[item.mainTaskType] || '未知类型',
|
||||
value: item.mainTaskCount,
|
||||
percent: item.percentage,
|
||||
originalType: item.mainTaskType,
|
||||
}));
|
||||
|
||||
// 生成 series 配置
|
||||
const option = {
|
||||
// 标题配置(主标题+副标题)
|
||||
// title: [
|
||||
// {
|
||||
// text: '月度',
|
||||
// left: 'center',
|
||||
// top: '35%',
|
||||
// textStyle: {
|
||||
// fontSize: 24,
|
||||
// letterSpacing: 5,
|
||||
// color: 'rgba(0, 0, 0, 0.85)',
|
||||
// fontFamily: 'PingFangSC, PingFang SC'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// text: '单位:万m²',
|
||||
// left: 'center',
|
||||
// top: '50%',
|
||||
// textStyle: {
|
||||
// fontSize: 16,
|
||||
// color: 'rgba(0, 0, 0, 0.55)',
|
||||
// fontFamily: 'PingFangSC, PingFang SC'
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
series: [
|
||||
{
|
||||
// name: '销量',
|
||||
type: 'pie',
|
||||
radius: ['60%', '90%'],
|
||||
radius: ['50%', '80%'],
|
||||
center: ['50%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
distance: 10,
|
||||
formatter: '{c}万m²\n{b}',
|
||||
formatter: (params) => {
|
||||
const percent = (params.percent).toFixed(1);
|
||||
return `${params.value}万m²\n${params.name}`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 14,
|
||||
color: 'rgba(0, 0, 0, 0.7)',
|
||||
fontFamily: 'PingFangSC, PingFang SC',
|
||||
lineHeight: 1.5
|
||||
}
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 0,
|
||||
length2: 30,
|
||||
lineStyle: {
|
||||
color: (params) => customColors[params.dataIndex]
|
||||
}
|
||||
color: (params) => colorMap[seriesData[params.dataIndex].originalType],
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
color: (params) => customColors[params.dataIndex]
|
||||
color: (params) => colorMap[seriesData[params.dataIndex].originalType],
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 348, name: '临时搬运',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => [
|
||||
`{b|${params.name.toLocaleString()}}`,
|
||||
`{c|${params.percent.toFixed(1)}% ${params.value}}`
|
||||
].join('\n'),
|
||||
rich: {
|
||||
b: {
|
||||
color: 'rgba(140, 140, 140, 1)',
|
||||
fontSize: 14,
|
||||
padding: [0, 0, 0, 0]
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(0, 0, 0, 0.65)',
|
||||
fontSize: 14,
|
||||
padding: [30, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
data: seriesData.map(item => ({
|
||||
...item,
|
||||
label: {
|
||||
show: true,
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => {
|
||||
const percent = (params.percent).toFixed(1);
|
||||
return [
|
||||
`{b|${params.name}}`,
|
||||
`{c|${percent}% ${params.value}}`
|
||||
].join('\n');
|
||||
},
|
||||
labelLine: {
|
||||
lineStyle: { color: 'rgba(80, 181, 255, 1)' }
|
||||
rich: {
|
||||
b: {
|
||||
color: 'rgba(140, 140, 140, 1)',
|
||||
fontSize: 14,
|
||||
padding: [0, 0, 0, 0],
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(0, 0, 0, 0.65)',
|
||||
fontSize: 14,
|
||||
padding: [30, 0, 0, 0],
|
||||
},
|
||||
},
|
||||
itemStyle: { color: 'rgba(80, 181, 255, 1)' }
|
||||
},
|
||||
{
|
||||
value: 435, name: '满盘搬运',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => [
|
||||
`{b|${params.name.toLocaleString()}}`,
|
||||
`{c|${params.percent.toFixed(1)}% ${params.value}}`
|
||||
].join('\n'),
|
||||
rich: {
|
||||
b: {
|
||||
color: 'rgba(140, 140, 140, 1)',
|
||||
fontSize: 14,
|
||||
padding: [0, 0, 0, 0]
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(0, 0, 0, 0.65)',
|
||||
fontSize: 14,
|
||||
padding: [30, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
length: 10,
|
||||
length2: 20,
|
||||
lineStyle: { color: 'rgba(40, 138, 255, 1)' }
|
||||
},
|
||||
itemStyle: { color: 'rgba(40, 138, 255, 1)' }
|
||||
labelLine: {
|
||||
lineStyle: { color: colorMap[item.originalType] },
|
||||
},
|
||||
{
|
||||
value: 580, name: '备料搬运',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => [
|
||||
`{b|${params.name.toLocaleString()}}`,
|
||||
`{c| ${params.value} ${params.percent.toFixed(1)}%}`
|
||||
].join('\n'),
|
||||
rich: {
|
||||
b: {
|
||||
color: 'rgba(140, 140, 140, 1)',
|
||||
fontSize: 14,
|
||||
padding: [0, 0, 0, 0]
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(0, 0, 0, 0.65)',
|
||||
fontSize: 14,
|
||||
padding: [30, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
lineStyle: { color: 'rgba(143, 146, 255, 1)' }
|
||||
},
|
||||
itemStyle: { color: 'rgba(143, 146, 255, 1)' }
|
||||
},
|
||||
{
|
||||
value: 484, name: '空盘搬运',
|
||||
label: {
|
||||
normal: {
|
||||
align: 'left',
|
||||
distanceToLabelLine: 2,
|
||||
formatter: (params) => [
|
||||
`{b|${params.name.toLocaleString()}}`,
|
||||
`{c| ${params.value} ${params.percent.toFixed(1)}%}`
|
||||
].join('\n'),
|
||||
rich: {
|
||||
b: {
|
||||
color: 'rgba(140, 140, 140, 1)',
|
||||
fontSize: 14,
|
||||
padding: [0, 0, 0, 0]
|
||||
},
|
||||
c: {
|
||||
color: 'rgba(0, 0, 0, 0.65)',
|
||||
fontSize: 14,
|
||||
padding: [30, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// label: {
|
||||
// normal: {
|
||||
// align: 'left',
|
||||
// distanceToLabelLine: 2,
|
||||
// formatter: (params) => [
|
||||
// `{b|${params.value.toLocaleString()}}`,
|
||||
// `{c|${params.name}}`
|
||||
// ].join('\n'),
|
||||
// rich: {
|
||||
// // hr: {
|
||||
// // color: 'rgba(168, 233, 255, 1)',
|
||||
// // fontSize: 20,
|
||||
// // padding: [26, 8, 0, 0]
|
||||
// // },
|
||||
// b: {
|
||||
// color: 'rgba(0, 0, 0, 0.65)',
|
||||
// fontSize: 18,
|
||||
// padding: [0, 0, 0, 0]
|
||||
// },
|
||||
// c: {
|
||||
// color: 'rgba(0, 0, 0, 0.65)',
|
||||
// fontSize: 14,
|
||||
// padding: [30, 0, 0, -5]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
labelLine: {
|
||||
lineStyle: { color: 'rgba(168, 233, 255, 1)' }
|
||||
},
|
||||
itemStyle: { color: 'rgba(168, 233, 255, 1)' }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
itemStyle: { color: colorMap[item.originalType] },
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
myChart.setOption(option);
|
||||
|
||||
// 窗口缩放监听
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
const resizeHandler = () => myChart.resize();
|
||||
window.addEventListener('resize', resizeHandler);
|
||||
|
||||
// 组件销毁清理
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.resize();
|
||||
});
|
||||
window.removeEventListener('resize', resizeHandler);
|
||||
myChart.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<div :ref="chartRef" id="coreLineChart" class="chart-container"></div>
|
||||
<!-- 自定义图例(三排三列 + 带数值) -->
|
||||
<div class="custom-legend">
|
||||
<div class="legend-item" v-for="(item, index) in chartData" :key="index"
|
||||
>
|
||||
<div class="legend-item" v-for="(item, index) in chartData" :key="index" @click="handleLegendClick(index)"
|
||||
:class="{ unselected: !selectedStatus[index] }">
|
||||
<!-- 左侧:颜色块 + 名称(垂直居中对齐) -->
|
||||
<div class="legend-left">
|
||||
<div class="legend-color" :style="{ backgroundColor: customColors[index] }"></div>
|
||||
<div class="legend-color" :style="{ backgroundColor: customColors[index % customColors.length] }"></div>
|
||||
<span class="legend-name">{{ item.name }}</span>
|
||||
</div>
|
||||
<!-- 右侧:数值(与左侧垂直居中对齐) -->
|
||||
@@ -28,7 +28,11 @@ export default {
|
||||
chartRef: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -45,35 +49,97 @@ export default {
|
||||
'rgba(80, 181, 255, 1)',
|
||||
'rgba(168, 233, 255, 1)',
|
||||
],
|
||||
chartData: [
|
||||
{ value: 348, name: '暂停中' },
|
||||
{ value: 435, name: '已终止-回收' },
|
||||
{ value: 580, name: '已终止-待回收' },
|
||||
{ value: 484, name: '已终止' },
|
||||
{ value: 484, name: '已完成' },
|
||||
{ value: 484, name: '执行中' },
|
||||
{ value: 484, name: '异常' },
|
||||
{ value: 484, name: '待执行' },
|
||||
{ value: 484, name: '待下发' },
|
||||
|
||||
]
|
||||
// 状态映射
|
||||
stateMap: {
|
||||
0: '待下发',
|
||||
1: '待执行',
|
||||
2: '执行中',
|
||||
3: '取货完成',
|
||||
4: '完成',
|
||||
5: '暂停中',
|
||||
6: '取消',
|
||||
7: '异常',
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 处理图表数据
|
||||
chartData() {
|
||||
if (!this.data || this.data.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.data
|
||||
.map(item => ({
|
||||
name: this.stateMap[item.mainTaskState] || `状态${item.mainTaskState}`,
|
||||
value: item.mainTaskCount,
|
||||
state: item.mainTaskState,
|
||||
percentage: item.percentage
|
||||
}));
|
||||
},
|
||||
|
||||
// 计算总数
|
||||
total() {
|
||||
return this.chartData.reduce((sum, item) => sum + item.value, 0);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听数据变化,重新渲染图表
|
||||
chartData: {
|
||||
handler() {
|
||||
this.initChart();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
// 监听选中状态变化,更新图表
|
||||
selectedStatus: {
|
||||
handler() {
|
||||
if (this.myChart) {
|
||||
this.updateChartSelection();
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initData();
|
||||
this.initChart();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
initChart() {
|
||||
const chartDom = this.$refs[this.chartRef];
|
||||
if (!chartDom) {
|
||||
console.error(`图表容器未找到!请确认父组件传递的 chartRef 为 "${this.chartRef}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化或重置图表
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose();
|
||||
}
|
||||
|
||||
this.myChart = echarts.init(chartDom);
|
||||
|
||||
// 初始化选中状态(全部选中)
|
||||
this.selectedStatus = this.chartData.map(() => true);
|
||||
const total = this.chartData.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
this.renderChart();
|
||||
|
||||
// 窗口缩放监听
|
||||
const resizeHandler = () => this.myChart?.resize();
|
||||
window.addEventListener('resize', resizeHandler);
|
||||
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', resizeHandler);
|
||||
this.myChart?.dispose();
|
||||
});
|
||||
},
|
||||
|
||||
renderChart() {
|
||||
if (!this.myChart || this.chartData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const option = {
|
||||
legend: { show: false },
|
||||
@@ -83,8 +149,6 @@ export default {
|
||||
backgroundColor: '#FFFFFF',
|
||||
boxShadow: '0px 4px 8px 0px rgba(0, 0, 0, 0.16)',
|
||||
borderRadius: '4px',
|
||||
width: 153,
|
||||
height: 42,
|
||||
borderWidth: 0,
|
||||
textStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.85)',
|
||||
@@ -92,8 +156,8 @@ export default {
|
||||
lineHeight: 1.5
|
||||
},
|
||||
formatter: (params) => {
|
||||
const color = this.customColors[params.dataIndex];
|
||||
const percent = ((params.value / total) * 100).toFixed(1);
|
||||
const color = this.customColors[params.dataIndex % this.customColors.length];
|
||||
const percent = ((params.value / this.total) * 100).toFixed(1);
|
||||
return `
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<div style="width: 12px; height: 12px; background: ${color};"></div>
|
||||
@@ -107,7 +171,7 @@ export default {
|
||||
},
|
||||
title: [
|
||||
{
|
||||
text: total.toString(),
|
||||
text: this.total.toString(),
|
||||
left: 'center',
|
||||
top: '35%',
|
||||
textStyle: {
|
||||
@@ -137,39 +201,43 @@ export default {
|
||||
label: { show: false },
|
||||
labelLine: { show: false },
|
||||
itemStyle: {
|
||||
color: (params) => this.customColors[params.dataIndex],
|
||||
color: (params) => this.customColors[params.dataIndex % this.customColors.length],
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
},
|
||||
data: this.chartData.map((item, index) => ({
|
||||
...item,
|
||||
itemStyle: { color: this.customColors[index] }
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
itemStyle: { color: this.customColors[index % this.customColors.length] }
|
||||
})),
|
||||
selected: this.selectedStatus.reduce((obj, selected, index) => {
|
||||
obj[this.chartData[index].name] = selected;
|
||||
return obj;
|
||||
}, {})
|
||||
selected: this.getSelectedMap()
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
option && this.myChart.setOption(option);
|
||||
window.addEventListener('resize', () => this.myChart?.resize());
|
||||
this.$once('hook:destroyed', () => {
|
||||
window.removeEventListener('resize', () => this.myChart?.resize());
|
||||
this.myChart?.dispose();
|
||||
});
|
||||
this.myChart.setOption(option, true);
|
||||
},
|
||||
handleLegendClick(index) {
|
||||
this.selectedStatus[index] = !this.selectedStatus[index];
|
||||
|
||||
getSelectedMap() {
|
||||
const selectedMap = {};
|
||||
this.chartData.forEach((item, index) => {
|
||||
selectedMap[item.name] = this.selectedStatus[index];
|
||||
});
|
||||
return selectedMap;
|
||||
},
|
||||
|
||||
updateChartSelection() {
|
||||
if (!this.myChart) return;
|
||||
|
||||
this.myChart.setOption({
|
||||
series: [{
|
||||
selected: this.selectedStatus.reduce((obj, selected, i) => {
|
||||
obj[this.chartData[i].name] = selected;
|
||||
return obj;
|
||||
}, {})
|
||||
selected: this.getSelectedMap()
|
||||
}]
|
||||
});
|
||||
},
|
||||
|
||||
handleLegendClick(index) {
|
||||
this.$set(this.selectedStatus, index, !this.selectedStatus[index]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -196,19 +264,24 @@ export default {
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr); // 三列
|
||||
grid-template-rows: repeat(3, 1fr); // 两行(6个item刚好2行3列)
|
||||
// gap: 15px 10px; // 行列间距
|
||||
align-items: center; // 网格内垂直居中
|
||||
gap: 10px 15px;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// 单个图例项:横向排列,垂直居中
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: left; // 内部元素垂直居中
|
||||
flex-direction: column;
|
||||
align-items: center; // 内部元素垂直居中
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
// gap: 10px; // 左右两部分间距
|
||||
padding: 4px 0;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧:颜色块 + 名称(垂直居中)
|
||||
@@ -238,22 +311,21 @@ export default {
|
||||
// 数值
|
||||
.legend-value {
|
||||
font-size: 14px;
|
||||
margin-left: 16px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-weight: 500;
|
||||
white-space: nowrap; // 防止换行
|
||||
}
|
||||
|
||||
// 未选中状态
|
||||
// .legend-item.unselected {
|
||||
.legend-item.unselected {
|
||||
|
||||
// .legend-name,
|
||||
// .legend-value {
|
||||
// color: rgba(0, 0, 0, 0.3);
|
||||
// }
|
||||
.legend-name,
|
||||
.legend-value {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
// .legend-color {
|
||||
// opacity: 0.5;
|
||||
// }
|
||||
// }
|
||||
.legend-color {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div style="flex: 1">
|
||||
<div class="agvStatusContent">
|
||||
<div class="title">实时任务</div>
|
||||
<!-- <div class="title">实时任务</div>
|
||||
<base-table style="width: 100%;margin-top: 6px;height: 300px;" :page="1" :limit="10" :show-index="true" :beilv="1"
|
||||
:tableConfig="tableProps" :table-data="maintenanceTasks" />
|
||||
:tableConfig="tableProps" :table-data="maintenanceTasks" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div style="flex: 1">
|
||||
<div class="agvStatusContent">
|
||||
<div class="title">任务状态占比 ·今日</div>
|
||||
<pieChart :chartRef=" 'taskStatusRef' " />
|
||||
<pieChart :chartRef=" 'taskStatusRef' " :data="data" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,12 +13,9 @@ export default {
|
||||
name: 'AGVStatus',
|
||||
components: { pieChart },
|
||||
props: {
|
||||
energyObj: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
electricComu: 0,
|
||||
steamComu: 0
|
||||
})
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div style="flex: 1">
|
||||
<div class="agvStatusContent">
|
||||
<div class="title">任务类型占比 ·今日</div>
|
||||
<pieChart :chartRef=" 'taskTypeRef' " />
|
||||
<pieChart :chartRef=" 'taskTypeRef' " :data ='data' />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,12 +13,9 @@ export default {
|
||||
name: 'AGVStatus',
|
||||
components: { pieChart },
|
||||
props: {
|
||||
energyObj: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
electricComu: 0,
|
||||
steamComu: 0
|
||||
})
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -1178,7 +1178,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道1 -->
|
||||
<div v-for="i in 3" :key="`BM1010${i}`" :ref="`BM1010${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '706px'
|
||||
}">
|
||||
<div :ref="`BM1010${i}L`" style="width: 11px;
|
||||
@@ -1195,7 +1195,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道2 -->
|
||||
<div v-for="i in 3" :key="`BM1020${i}`" :ref="`BM1020${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '740px'
|
||||
}">
|
||||
<div :ref="`BM1020${i}L`" style="width: 11px;
|
||||
@@ -1212,7 +1212,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道3 -->
|
||||
<div v-for="i in 3" :key="`BM1030${i}`" :ref="`BM1030${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '774px'
|
||||
}">
|
||||
<div :ref="`BM1030${i}L`" style="width: 11px;
|
||||
@@ -1229,7 +1229,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道4 -->
|
||||
<div v-for="i in 3" :key="`BM1040${i}`" :ref="`BM1040${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '808px'
|
||||
}">
|
||||
<div :ref="`BM1040${i}L`" style="width: 11px;
|
||||
@@ -1239,14 +1239,14 @@ padding: 1.5px;">
|
||||
</div>
|
||||
<div :ref="`BM1040${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
padding: 1..5px;">
|
||||
<div :class="getPositionClass(`BM1040${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 满盘缓存区2巷道5 -->
|
||||
<div v-for="i in 3" :key="`BM1050${i}`" :ref="`BM1050${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '842px'
|
||||
}">
|
||||
<div :ref="`BM1050${i}L`" style="width: 11px;
|
||||
@@ -1263,7 +1263,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道6 -->
|
||||
<div v-for="i in 3" :key="`BM1060${i}`" :ref="`BM1060${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '878px'
|
||||
}">
|
||||
<div :ref="`BM1060${i}L`" style="width: 11px;
|
||||
@@ -1280,7 +1280,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道7 -->
|
||||
<div v-for="i in 3" :key="`BM1070${i}`" :ref="`BM1070${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '910px'
|
||||
}">
|
||||
<div :ref="`BM1070${i}L`" style="width: 11px;
|
||||
@@ -1297,7 +1297,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道8 -->
|
||||
<div v-for="i in 3" :key="`BM1080${i}`" :ref="`BM1080${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '946px'
|
||||
}">
|
||||
<div :ref="`BM1080${i}L`" style="width: 11px;
|
||||
@@ -1314,7 +1314,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道9 -->
|
||||
<div v-for="i in 3" :key="`BM1090${i}`" :ref="`BM1090${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '980px'
|
||||
}">
|
||||
<div :ref="`BM1090${i}L`" style="width: 11px;
|
||||
@@ -1331,7 +1331,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道10 -->
|
||||
<div v-for="i in 3" :key="`BM1100${i}`" :ref="`BM1100${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '1012px'
|
||||
}">
|
||||
<div :ref="`BM1100${i}L`" style="width: 11px;
|
||||
@@ -1348,7 +1348,7 @@ padding: 1.5px;">
|
||||
|
||||
<!-- 满盘缓存区2巷道11 -->
|
||||
<div v-for="i in 3" :key="`BM1110${i}`" :ref="`BM1110${i}`" class="location" :style="{
|
||||
top: `${201 + (i - 1) * 13}px`,
|
||||
top: `${201 + (3 - i) * 13}px`,
|
||||
left: '1048px'
|
||||
}">
|
||||
<div :ref="`BM1110${i}L`" style="width: 11px;
|
||||
@@ -1362,7 +1362,6 @@ padding: 1.5px;">
|
||||
<div :class="getPositionClass(`BM1110${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 满盘缓存区3巷道1 -->
|
||||
<div v-for="i in 4" :key="`BM2010${i}`" :ref="`BM2010${i}`" class="location" :style="{
|
||||
top: `${250 + (i - 1) * 13}px`,
|
||||
@@ -1694,12 +1693,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM1010${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM1010${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM1010${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM1010${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM1010${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM1010${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝A线满盘缓存区第2巷道1排 -->
|
||||
@@ -1710,12 +1709,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM1020${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM1020${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM1020${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM1020${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM1020${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM1020${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝A线满盘缓存区第3巷道1排 -->
|
||||
@@ -1726,12 +1725,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM1030${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM1030${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM1030${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM1030${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM1030${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM1030${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝A线满盘缓存区第4巷道1排 -->
|
||||
@@ -1742,12 +1741,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM1040${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM1040${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM1040${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM1040${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM1040${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM1040${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝A线满盘缓存区第5巷道1排 -->
|
||||
@@ -1758,12 +1757,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM1050${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM1050${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM1050${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM1050${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM1050${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM1050${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝A线满盘缓存区第6巷道1排 -->
|
||||
@@ -1774,12 +1773,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM1060${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM1060${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM1060${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM1060${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM1060${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM1060${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝A线满盘缓存区第7巷道1排 -->
|
||||
@@ -1790,12 +1789,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM1070${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM1070${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM1070${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM1070${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM1070${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM1070${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝A线满盘缓存区第8巷道1排 -->
|
||||
@@ -1806,12 +1805,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM1080$${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM1080$${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM1080$${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM1080$${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM1080$${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM1080$${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1824,12 +1823,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM2010${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM2010${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM2010${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM2010${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM2010${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM2010${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝B线满盘缓存区第2巷道1排 -->
|
||||
@@ -1840,12 +1839,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM2020${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM2020${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM2020${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM2020${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM2020${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM2020${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝B线满盘缓存区第3巷道1排 -->
|
||||
@@ -1856,12 +1855,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM2030${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM2030${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM2030${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM2030${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM2030${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM2030${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝B线满盘缓存区第4巷道1排 -->
|
||||
@@ -1872,12 +1871,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM2040${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM2040${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM2040${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM2040${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM2040${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM2040${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝B线满盘缓存区第5巷道1排 -->
|
||||
@@ -1888,12 +1887,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM2050${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM2050${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM2050${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM2050${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM2050${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM2050${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝B线满盘缓存区第6巷道1排 -->
|
||||
@@ -1904,12 +1903,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM2060${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM2060${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM2060${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM2060${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM2060${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM2060${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝B线满盘缓存区第7巷道1排 -->
|
||||
@@ -1920,12 +1919,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM2070${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM2070${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM2070${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM2070${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM2070${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM2070${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 拉丝B线满盘缓存区第8巷道1排 -->
|
||||
@@ -1936,12 +1935,12 @@ padding: 1.5px;">
|
||||
<div :ref="`AM2080${i}L`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px;">
|
||||
<div :class="getPositionClass(`AM2080${i}L`)" ></div>
|
||||
<div :class="getPositionClass(`AM2080${i}L`)"></div>
|
||||
</div>
|
||||
<div :ref="`AM2080${i}R`" style="width: 11px;
|
||||
height: 11px;
|
||||
padding: 1.5px; ">
|
||||
<div :class="getPositionClass(`AM2080${i}R`)" ></div>
|
||||
<div :class="getPositionClass(`AM2080${i}R`)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 循环渲染状态项 -->
|
||||
@@ -2035,8 +2034,6 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
"lineEdgeLibraryState": 1,
|
||||
"usableState": 3,
|
||||
<style lang='scss' scoped>
|
||||
.threeDimensionalChart {
|
||||
width: 1493px;
|
||||
|
||||
@@ -1,49 +1,39 @@
|
||||
<template>
|
||||
<div id="dayReport" class="dayReport" :style="styles">
|
||||
<ReportHeader top-title="车间生产看板" />
|
||||
<div
|
||||
class="main-body"
|
||||
style="
|
||||
<div id="dayReport" class="dayReport" :style="styles">
|
||||
<ReportHeader top-title="车间生产看板" />
|
||||
<div class="main-body" style="
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: 16px 20px 22px 24px;
|
||||
flex-direction: column;
|
||||
">
|
||||
<div class="top" style="display: flex; gap: 16px">
|
||||
<div
|
||||
class="top-three"
|
||||
style="display: grid; gap: 12px; grid-template-columns: 367px 1493px">
|
||||
<agvStatus />
|
||||
<threeDimensionalChart :dataList="dataList" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px; margin-top: 6px">
|
||||
<div
|
||||
class="bottom-three"
|
||||
style="
|
||||
<div class="top" style="display: flex; gap: 16px">
|
||||
<div class="top-three" style="display: grid; gap: 12px; grid-template-columns: 367px 1493px">
|
||||
<agvStatus :agvList='agvAlarmListData' />
|
||||
<threeDimensionalChart :dataList="dataList" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="top" style="display: flex; gap: 16px; margin-top: 6px">
|
||||
<div class="bottom-three" style="
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 512px 930px 402px;
|
||||
grid-template-columns: 1344px 512px;
|
||||
">
|
||||
<div
|
||||
class="left-three"
|
||||
style="display: grid; gap: 16px; grid-template-rows: 213px 213px">
|
||||
<taskType />
|
||||
<taskStatus />
|
||||
</div>
|
||||
<div
|
||||
class="center-three"
|
||||
style="display: grid; gap: 16px; grid-template-rows: 330px 92px">
|
||||
<realTask />
|
||||
<alarm />
|
||||
|
||||
<!-- <taskStatus /> -->
|
||||
</div>
|
||||
<alarmTop />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center-three" style="">
|
||||
<!-- <realTask /> -->
|
||||
<alarmInfo :agvList='agvAlarmInfo' />
|
||||
|
||||
<!-- <taskStatus /> -->
|
||||
</div>
|
||||
<div class="left-three" style="display: grid; gap: 16px; grid-template-rows: 213px 213px">
|
||||
<taskType :data="taskTypeToday" />
|
||||
<taskStatus :data="taskStatusToday" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ReportHeader from './components/Header.vue';
|
||||
@@ -52,12 +42,12 @@ import agvStatus from './components/agvStatus.vue';
|
||||
import taskType from './components/taskType.vue';
|
||||
import realTask from './components/realTask.vue';
|
||||
import alarm from './components/alarm.vue';
|
||||
import alarmTop from './components/alarmTop.vue';
|
||||
import alarmInfo from './components/alarmInfo.vue';
|
||||
|
||||
import taskStatus from './components/taskStatus.vue';
|
||||
|
||||
import threeDimensionalChart from './components/threeDimensionalChart.vue';
|
||||
import { getLineEdgeLibraryList } from '../../../api/visualization/visualization';
|
||||
import { getLineEdgeLibraryList, getAgvAlarmInfo, getTaskTypeToday, getTaskStatusToday, getAgvExample } from '../../../api/visualization/visualization';
|
||||
|
||||
// import financeCosts from './components/financeCosts.vue'
|
||||
// import keyProductionIndicators from './components/keyProductionIndicators.vue'
|
||||
@@ -75,21 +65,28 @@ export default {
|
||||
taskStatus,
|
||||
realTask,
|
||||
alarm,
|
||||
alarmTop,
|
||||
alarmInfo,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// isFullScreen: false,
|
||||
timer: null,
|
||||
beilv: 1,
|
||||
value: 100,
|
||||
value: 100,
|
||||
taskTypeToday: [],
|
||||
taskStatusToday: [],
|
||||
agvAlarmInfo: [],
|
||||
agvAlarmListData: [],
|
||||
agvAlarmInfoData:[],
|
||||
timer: null, // 存储定时器
|
||||
dataList: [],
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
// this.init()
|
||||
this.windowWidth(document.documentElement.clientWidth);
|
||||
// this.init()
|
||||
this.windowWidth(document.documentElement.clientWidth);
|
||||
localStorage.removeItem('agv_carousel_position');
|
||||
},
|
||||
computed: {
|
||||
// ...mapGetters(['sidebar']),
|
||||
@@ -109,7 +106,7 @@ export default {
|
||||
this.beilv2 = this.clientWidth / 1920;
|
||||
this.timer = true;
|
||||
let _this = this;
|
||||
setTimeout(function () {
|
||||
setTimeout(function () {·
|
||||
_this.timer = false;
|
||||
}, 500);
|
||||
}
|
||||
@@ -133,89 +130,224 @@ export default {
|
||||
_this.clientWidth = `${document.documentElement.clientWidth}`;
|
||||
this.beilv = _this.clientWidth / baseWidth;
|
||||
})();
|
||||
};
|
||||
this.getData();
|
||||
},
|
||||
methods: {
|
||||
getData(data) {
|
||||
console.log(data);
|
||||
getLineEdgeLibraryList().then((res) => {
|
||||
console.log('res', res);
|
||||
};
|
||||
this.getData();
|
||||
this.loadAgvExampleData()
|
||||
this.timer = setInterval(() => {
|
||||
this.getData();
|
||||
}, 30000);
|
||||
|
||||
this.dataList = res.data;
|
||||
});
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁前清除定时器
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getData(data) {
|
||||
console.log(data);
|
||||
const [lineEdgeRes, taskTypeRes, taskStatusRes, agvAlarmRes] = await Promise.all([
|
||||
getLineEdgeLibraryList(),
|
||||
getTaskTypeToday(),
|
||||
getTaskStatusToday(),
|
||||
getAgvAlarmInfo()
|
||||
]);
|
||||
|
||||
this.dataList = lineEdgeRes.data;
|
||||
this.taskTypeToday = taskTypeRes.data;
|
||||
this.taskStatusToday = taskStatusRes.data;
|
||||
this.agvAlarmListData = agvAlarmRes;
|
||||
// this.agvAlarmListData = [
|
||||
// {
|
||||
// "deviceName": "AGV-01",
|
||||
// "alarmTrigger": false,
|
||||
// "data": [
|
||||
// {
|
||||
// "createTime": "2024-01-15 08:30:25",
|
||||
// "updateTime": "2024-01-15 09:15:30",
|
||||
// "creator": "admin",
|
||||
// "updater": "operator1",
|
||||
// "deleted": false,
|
||||
// "id": 1001,
|
||||
// "type": 1,
|
||||
// "code": "ERR-001",
|
||||
// "name": "电机过载保护",
|
||||
// "solutionContent": "检查电机负载,清理轨道障碍物",
|
||||
// "warnName": "电机异常",
|
||||
// "faultComponent": "驱动电机",
|
||||
// "faultEventName": "电机电流过高",
|
||||
// "eventCause": "轨道异物卡滞",
|
||||
// "troubleshootStep": "1.停止AGV运行 2.检查轨道 3.清理异物 4.重启设备",
|
||||
// "classification": 2,
|
||||
// "remark": "需要及时处理,避免电机损坏",
|
||||
// "status": 1,
|
||||
// "sort": 1,
|
||||
// "version": 1
|
||||
// },
|
||||
// {
|
||||
// "createTime": "2024-01-15 08:30:25",
|
||||
// "updateTime": "2024-01-15 09:15:30",
|
||||
// "creator": "admin",
|
||||
// "updater": "operator1",
|
||||
// "deleted": false,
|
||||
// "id": 1001,
|
||||
// "type": 1,
|
||||
// "code": "ERR-001",
|
||||
// "name": "电机过载保护",
|
||||
// "solutionContent": "检查电机负载,清理轨道障碍物",
|
||||
// "warnName": "电机异常",
|
||||
// "faultComponent": "驱动电机",
|
||||
// "faultEventName": "电机电流过高",
|
||||
// "eventCause": "轨道异物卡滞",
|
||||
// "troubleshootStep": "1.停止AGV运行 2.检查轨道 3.清理异物 4.重启设备",
|
||||
// "classification": 2,
|
||||
// "remark": "需要及时处理,避免电机损坏",
|
||||
// "status": 1,
|
||||
// "sort": 1,
|
||||
// "version": 1
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// "deviceName": "AGV-02",
|
||||
// "alarmTrigger": false,
|
||||
// "data": [
|
||||
// {
|
||||
// "createTime": "2024-01-15 10:20:15",
|
||||
// "updateTime": "2024-01-15 10:45:22",
|
||||
// "creator": "admin",
|
||||
// "updater": "operator2",
|
||||
// "deleted": false,
|
||||
// "id": 1002,
|
||||
// "type": 2,
|
||||
// "code": "ERR-002",
|
||||
// "name": "电池电量过低",
|
||||
// "solutionContent": "AGV已自动返回充电桩充电",
|
||||
// "warnName": "电量告警",
|
||||
// "faultComponent": "锂电池组",
|
||||
// "faultEventName": "电压低于阈值",
|
||||
// "eventCause": "连续运行时间过长,未及时充电",
|
||||
// "troubleshootStep": "1.等待AGV自动充电 2.检查充电桩连接 3.手动更换电池",
|
||||
// "classification": 1,
|
||||
// "remark": "电量剩余15%,需关注充电状态",
|
||||
// "status": 2,
|
||||
// "sort": 2,
|
||||
// "version": 1
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// "deviceName": "AGV-03",
|
||||
// "alarmTrigger": false,
|
||||
// "data": [
|
||||
// {
|
||||
// "createTime": "2024-01-15 14:35:42",
|
||||
// "updateTime": "2024-01-15 15:00:18",
|
||||
// "creator": "admin",
|
||||
// "updater": "operator3",
|
||||
// "deleted": false,
|
||||
// "id": 1003,
|
||||
// "type": 3,
|
||||
// "code": "ERR-003",
|
||||
// "name": "激光雷达通讯故障",
|
||||
// "solutionContent": "检查雷达连接线,重启雷达驱动",
|
||||
// "warnName": "传感器异常",
|
||||
// "faultComponent": "激光雷达",
|
||||
// "faultEventName": "通讯超时",
|
||||
// "eventCause": "线束松动或电磁干扰",
|
||||
// "troubleshootStep": "1.检查雷达指示灯 2.重新插拔连接线 3.重启系统 4.更换雷达",
|
||||
// "classification": 3,
|
||||
// "remark": "影响导航定位功能",
|
||||
// "status": 1,
|
||||
// "sort": 3,
|
||||
// "version": 2
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// "deviceName": "AGV-04",
|
||||
// "alarmTrigger": false,
|
||||
// "data": [
|
||||
// {
|
||||
// "createTime": "2024-01-15 09:45:33",
|
||||
// "updateTime": "2024-01-15 10:30:45",
|
||||
// "creator": "admin",
|
||||
// "updater": "operator1",
|
||||
// "deleted": false,
|
||||
// "id": 1004,
|
||||
// "type": 1,
|
||||
// "code": "ERR-004",
|
||||
// "name": "举升机构异常",
|
||||
// "solutionContent": "检查液压系统,补充液压油",
|
||||
// "warnName": "机械故障",
|
||||
// "faultComponent": "举升液压缸",
|
||||
// "faultEventName": "压力不足",
|
||||
// "eventCause": "液压油泄漏或油泵故障",
|
||||
// "troubleshootStep": "1.检查液压油位 2.排查泄漏点 3.更换密封圈 4.测试压力",
|
||||
// "classification": 2,
|
||||
// "remark": "举升高度不达标,需紧急处理",
|
||||
// "status": 1,
|
||||
// "sort": 4,
|
||||
// "version": 1
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// "deviceName": "AGV-05",
|
||||
// "alarmTrigger": false,
|
||||
// "data": [
|
||||
// {
|
||||
// "createTime": "2024-01-15 16:20:55",
|
||||
// "updateTime": "2024-01-15 17:05:33",
|
||||
// "creator": "admin",
|
||||
// "updater": "operator2",
|
||||
// "deleted": false,
|
||||
// "id": 1005,
|
||||
// "type": 2,
|
||||
// "code": "ERR-005",
|
||||
// "name": "导航定位偏移",
|
||||
// "solutionContent": "重新标定导航路径,更新地图",
|
||||
// "warnName": "定位异常",
|
||||
// "faultComponent": "导航系统",
|
||||
// "faultEventName": "定位精度超差",
|
||||
// "eventCause": "地面二维码磨损或反光柱被遮挡",
|
||||
// "troubleshootStep": "1.清洁二维码/反光柱 2.重新标定 3.更新导航地图 4.校准传感器",
|
||||
// "classification": 3,
|
||||
// "remark": "偏差超过5cm,需重新校准",
|
||||
// "status": 2,
|
||||
// "sort": 5,
|
||||
// "version": 1
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ];
|
||||
// 根据告警状态决定显示内容
|
||||
if (this.isAllAlarmTriggered()) {
|
||||
console.log('无告警,加载示例数据');
|
||||
// await this.loadAgvExampleData();
|
||||
this.agvAlarmInfo = this.agvAlarmInfoData
|
||||
} else {
|
||||
console.log('有告警,使用真实数据');
|
||||
this.agvAlarmInfo = agvAlarmRes;
|
||||
// this.agvAlarmInfo = this.agvAlarmListData;
|
||||
|
||||
}
|
||||
},
|
||||
async loadAgvExampleData() {
|
||||
const res = await getAgvExample()
|
||||
console.log('getAgvExample 接口返回:', res);
|
||||
this.agvAlarmInfoData = res
|
||||
},
|
||||
isAllAlarmTriggered() {
|
||||
if (!this.agvAlarmListData || this.agvAlarmListData.length === 0) return false;
|
||||
return this.agvAlarmListData.every(agv => agv.alarmTrigger === false);
|
||||
},
|
||||
windowWidth(value) {
|
||||
this.clientWidth = value;
|
||||
this.beilv2 = this.clientWidth / 1920;
|
||||
},
|
||||
// change() {
|
||||
// this.isFullScreen = screenfull.isFullscreen
|
||||
// },
|
||||
// init() {
|
||||
// if (!screenfull.isEnabled) {
|
||||
// this.$message({
|
||||
// message: 'you browser can not work',
|
||||
// type: 'warning'
|
||||
// })
|
||||
// return false
|
||||
// }
|
||||
// screenfull.on('change', this.change)
|
||||
// },
|
||||
// destroy() {
|
||||
// if (!screenfull.isEnabled) {
|
||||
// this.$message({
|
||||
// message: 'you browser can not work',
|
||||
// type: 'warning'
|
||||
// })
|
||||
// return false
|
||||
// }
|
||||
// screenfull.off('change', this.change)
|
||||
// },
|
||||
// // 全屏
|
||||
// screenfullChange() {
|
||||
// console.log('screenfull.enabled', screenfull.isEnabled);
|
||||
|
||||
// if (!screenfull.isEnabled) {
|
||||
// this.$message({
|
||||
// message: 'you browser can not work',
|
||||
// type: 'warning'
|
||||
// })
|
||||
// return false
|
||||
// }
|
||||
// screenfull.toggle(this.$refs.dayReportB)
|
||||
// },
|
||||
// changeDate(val) {
|
||||
// this.date = val
|
||||
// // this.weekDay = this.weekArr[moment(this.date).format('e')]
|
||||
// // this.getData()
|
||||
// if (this.date === moment().format('yyyy-MM-DD')) {
|
||||
// this.loopTime()
|
||||
// } else {
|
||||
// clearInterval(this.timer)
|
||||
// }
|
||||
// },
|
||||
// 导出
|
||||
// exportPDF() {
|
||||
// this.$message.success('正在导出,请稍等!')
|
||||
// const element = document.getElementById('dayRepDom')
|
||||
// element.style.display = 'block'
|
||||
// const fileName = '株洲碲化镉生产日报' + moment().format('yyMMDD') + '.pdf'
|
||||
// html2canvas(element, {
|
||||
// dpi: 300, // Set to 300 DPI
|
||||
// scale: 3 // Adjusts your resolution
|
||||
// }).then(function(canvas) {
|
||||
// const imgWidth = 595.28
|
||||
// const imgHeight = 841.89
|
||||
// const pageData = canvas.toDataURL('image/jpeg', 1.0)
|
||||
// const PDF = new JsPDF('', 'pt', [imgWidth, imgHeight])
|
||||
// PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
|
||||
// setTimeout(() => {
|
||||
// PDF.save(fileName) // 导出文件名
|
||||
// }, 1000)
|
||||
// })
|
||||
// element.style.display = 'none'
|
||||
// }
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* @Description:
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div
|
||||
<div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<!-- <div
|
||||
style="
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -20,13 +20,15 @@
|
||||
margin-right:5px
|
||||
">
|
||||
L
|
||||
</div>
|
||||
<span v-if="injectData.prop==='val4'">{{injectData.leftMaterialName+'-'+injectData.leftMaterialCode}}</span>
|
||||
<span v-else-if="injectData.prop==='val5'">{{injectData.leftLibraryStartName}}</span>
|
||||
<span v-else-if="injectData.prop==='val6'">{{injectData.leftLibraryStartName}}</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<div
|
||||
</div> -->
|
||||
<!-- <span v-if="injectData.prop === 'val4'">{{ injectData.leftMaterialCode }}</span> -->
|
||||
|
||||
<span v-if="injectData.prop==='val4'">{{ injectData.leftMaterialCode + '-' + injectData.leftMaterial }}</span>
|
||||
<span v-else-if="injectData.prop==='val5'">{{ removeSuffix(injectData.leftLibraryStartName, '左侧') }}</span>
|
||||
<span v-else-if="injectData.prop==='val6'">{{ removeSuffix(injectData.leftLibraryEndName, '左侧') }}</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<!-- <div
|
||||
style="
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -38,12 +40,12 @@
|
||||
margin-right:5px
|
||||
">
|
||||
R
|
||||
</div>
|
||||
<span v-if="injectData.prop==='val4'">{{injectData.rightMaterialName+'-'+injectData.rightMaterialCode}}</span>
|
||||
<span v-else-if="injectData.prop==='val5'">{{injectData.rightLibraryStartName}}</span>
|
||||
<span v-else-if="injectData.prop==='val6'">{{injectData.rightLibraryEndName}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- <span v-if="injectData.prop==='val4'">{{injectData.rightMaterialName+'-'+injectData.rightMaterialCode}}</span> -->
|
||||
<span v-if="injectData.prop==='val5'"> {{ removeSuffix(injectData.leftLibraryStartCode, 'L') }}</span>
|
||||
<span v-else-if="injectData.prop==='val6'">{{ removeSuffix(injectData.leftLibraryEndCode, 'L') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -59,6 +61,10 @@ export default {
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {},
|
||||
methods: {
|
||||
removeSuffix(str, suffix) {
|
||||
return str?.endsWith(suffix) ? str.slice(0, -suffix.length) : str;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,94 +1,48 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div>
|
||||
<search-bar
|
||||
:formConfigs="formConfig"
|
||||
ref="searchBarForm"
|
||||
@headBtnClick="buttonClick" />
|
||||
<div style="font-size: 14px">
|
||||
自动刷新(10s)
|
||||
<el-switch v-model="autoRefresh"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<base-table
|
||||
v-loading="dataListLoading"
|
||||
:table-props="tableProps"
|
||||
:page="listQuery.pageNo"
|
||||
:limit="listQuery.pageSize"
|
||||
:max-height="tableH"
|
||||
:table-data="tableData"
|
||||
@emitFun="getDataList">
|
||||
<method-btn
|
||||
v-if="tableBtn.length"
|
||||
slot="handleBtn"
|
||||
:width="100"
|
||||
label="操作"
|
||||
:method-list="tableBtn"
|
||||
@clickBtn="handleClick" />
|
||||
</base-table>
|
||||
<pagination
|
||||
:limit.sync="listQuery.pageSize"
|
||||
:page.sync="listQuery.pageNo"
|
||||
:total="listQuery.total"
|
||||
@pagination="getDataList" />
|
||||
<!-- //新增 -->
|
||||
<base-dialog
|
||||
:dialogTitle="'新建人工任务'"
|
||||
:dialogVisible="addOrUpdateVisible"
|
||||
@cancel="handleCancel"
|
||||
@confirm="handleConfirm"
|
||||
:before-close="handleCancel"
|
||||
:destroy-on-close="true"
|
||||
width="70%">
|
||||
<add-or-update
|
||||
ref="addOrUpdate"
|
||||
@setSN="setStepNum"
|
||||
@refreshDataList="successSubmit"></add-or-update>
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<el-row slot="footer" type="flex" justify="end">
|
||||
<el-col :span="24">
|
||||
<el-button
|
||||
v-if="stepNum > 1"
|
||||
size="small"
|
||||
class="btnTextStyle"
|
||||
@click="handleConfirm('up')">
|
||||
上一步
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
class="btnTextStyle"
|
||||
@click="handleCancel">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="btnTextStyle"
|
||||
size="small"
|
||||
@click="handleConfirm">
|
||||
{{ stepNum < 3 ? '下一步' : '创建任务' }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</slot>
|
||||
</template>
|
||||
</base-dialog>
|
||||
<!-- //详情 -->
|
||||
<detail-drawer
|
||||
ref="detailDrawer"
|
||||
@closeDrawer="closeDrawer"></detail-drawer>
|
||||
<!-- //有货终止 -->
|
||||
<base-dialog
|
||||
:dialogTitle="'终止任务'"
|
||||
:dialogVisible="stopStockVisible"
|
||||
@cancel="handleCancelStopStock"
|
||||
@confirm="handleConfirmStopStock"
|
||||
:before-close="handleCancelStopStock"
|
||||
:destroy-on-close="true"
|
||||
width="50%">
|
||||
<stop-in-stock ref="StopStockRef"></stop-in-stock>
|
||||
</base-dialog>
|
||||
</div>
|
||||
<div class="app-container">
|
||||
<div>
|
||||
<search-bar :formConfigs="formConfig" ref="searchBarForm" isFold @headBtnClick="buttonClick" />
|
||||
<div style="font-size: 14px">
|
||||
自动刷新(10s)
|
||||
<el-switch v-model="autoRefresh"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<base-table v-loading="dataListLoading" :table-props="tableProps" :page="listQuery.pageNo"
|
||||
:limit="listQuery.pageSize" :max-height="tableH" :table-data="tableData" @emitFun="getDataList">
|
||||
<method-btn v-if="tableBtn.length" slot="handleBtn" :width="100" label="操作" :method-list="tableBtn"
|
||||
@clickBtn="handleClick" />
|
||||
</base-table>
|
||||
<pagination :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="listQuery.total"
|
||||
@pagination="getDataList" />
|
||||
<!-- //新增 -->
|
||||
<base-dialog :dialogTitle="'新建人工任务'" :dialogVisible="addOrUpdateVisible" @cancel="handleCancel"
|
||||
@confirm="handleConfirm" :before-close="handleCancel" :destroy-on-close="true" width="70%">
|
||||
<add-or-update ref="addOrUpdate" @setSN="setStepNum" @refreshDataList="successSubmit"></add-or-update>
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<el-row slot="footer" type="flex" justify="end">
|
||||
<el-col :span="24">
|
||||
<el-button v-if="stepNum > 1" size="small" class="btnTextStyle" @click="handleConfirm('up')">
|
||||
上一步
|
||||
</el-button>
|
||||
<el-button size="small" class="btnTextStyle" @click="handleCancel">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" class="btnTextStyle" size="small" @click="handleConfirm">
|
||||
{{ stepNum < 3 ? '下一步' : '创建任务' }} </el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</slot>
|
||||
</template>
|
||||
</base-dialog>
|
||||
<!-- //详情 -->
|
||||
<detail-drawer ref="detailDrawer" @closeDrawer="closeDrawer"></detail-drawer>
|
||||
<!-- //有货终止 -->
|
||||
<base-dialog :dialogTitle="'终止任务'" :dialogVisible="stopStockVisible" @cancel="handleCancelStopStock"
|
||||
@confirm="handleConfirmStopStock" :before-close="handleCancelStopStock" :destroy-on-close="true" width="50%">
|
||||
<stop-in-stock ref="StopStockRef"></stop-in-stock>
|
||||
</base-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -102,9 +56,14 @@ import detailDrawer from './components/detailDrawer.vue';
|
||||
import stopInStock from './components/stopInStock.vue';
|
||||
import tableHeightMixin from '@/mixins/lb/tableHeightMixin';
|
||||
import { parseTime } from '@/filter/code-filter';
|
||||
import { getTaskPage } from '@/api/ssdl/taskList';
|
||||
import { getTaskPage, cancelUpdateTask } from '@/api/ssdl/taskList';
|
||||
|
||||
const tableProps = [
|
||||
{
|
||||
prop: 'id',
|
||||
label: '任务号',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
prop: 'mainTaskCode',
|
||||
label: '任务编号',
|
||||
@@ -127,19 +86,19 @@ const tableProps = [
|
||||
},
|
||||
{
|
||||
prop: 'val4',
|
||||
label: '搬运对象(L/R)',
|
||||
label: '搬运对象',
|
||||
subcomponent: subSpan3,
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
prop: 'val5',
|
||||
label: '起点(L/R)',
|
||||
label: '起点',
|
||||
subcomponent: subSpan3,
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
prop: 'val6',
|
||||
label: '终点(L/R)',
|
||||
label: '终点',
|
||||
subcomponent: subSpan3,
|
||||
width: 220,
|
||||
},
|
||||
@@ -171,28 +130,34 @@ export default {
|
||||
type: 'detail',
|
||||
btnName: '详情',
|
||||
},
|
||||
// {
|
||||
// type: 'stop',
|
||||
// btnName: '终止',
|
||||
// showParam: {
|
||||
// type: '&',
|
||||
// data: [
|
||||
// {
|
||||
// type: 'equal',
|
||||
// name: 'mainTaskState',
|
||||
// value: 2,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
{
|
||||
type: 'cancel',
|
||||
btnName: '取消',
|
||||
showParam: {
|
||||
type: '&',
|
||||
data: [
|
||||
{
|
||||
type: 'equal',
|
||||
name: 'mainTaskState',
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
].filter((v) => v),
|
||||
tableData: [],
|
||||
listQuery: {
|
||||
//分页
|
||||
pageSize: 10,
|
||||
pageNo: 1,
|
||||
total: 1,
|
||||
},
|
||||
total: 1,
|
||||
startTime: undefined, // ✅ 必须加
|
||||
endTime: undefined, // ✅ 必须加
|
||||
},
|
||||
urlOptions: {
|
||||
// getDataListURL: getProductPage,
|
||||
cancelURL: cancelUpdateTask,
|
||||
},
|
||||
formConfig: [
|
||||
{
|
||||
type: 'select',
|
||||
@@ -272,10 +237,45 @@ export default {
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '任务编号',
|
||||
placeholder: '任务编号',
|
||||
param: 'code',
|
||||
},
|
||||
label: '任务号',
|
||||
placeholder: '任务号',
|
||||
param: 'id',
|
||||
},
|
||||
{
|
||||
type: 'datePicker',
|
||||
label: '时间',
|
||||
dateType: 'datetimerange',
|
||||
format: 'yyyy-MM-dd HH:mm:ss',
|
||||
valueFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||
rangeSeparator: '-',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
param: 'timeSlot',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '物料编码',
|
||||
placeholder: '物料编码',
|
||||
param: 'materialCode',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '起点编号',
|
||||
placeholder: '起点编号',
|
||||
param: 'startCode',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '终点编号',
|
||||
placeholder: '终点编号',
|
||||
param: 'endCode',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: 'agv',
|
||||
placeholder: 'agv',
|
||||
param: 'agv',
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
btnName: '搜索',
|
||||
@@ -293,13 +293,13 @@ export default {
|
||||
{
|
||||
type: 'separate',
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
btnName: '新增',
|
||||
name: 'add',
|
||||
color: 'success',
|
||||
plain: true,
|
||||
},
|
||||
// {
|
||||
// type: 'button',
|
||||
// btnName: '新增',
|
||||
// name: 'add',
|
||||
// color: 'success',
|
||||
// plain: true,
|
||||
// },
|
||||
],
|
||||
addOrUpdateVisible: false, //dialog状态
|
||||
dataListLoading: false,
|
||||
@@ -344,7 +344,9 @@ export default {
|
||||
this.autoRefresh = true;
|
||||
},
|
||||
methods: {
|
||||
buttonClick(val) {
|
||||
buttonClick(val) {
|
||||
|
||||
|
||||
switch (val.btnName) {
|
||||
case 'search':
|
||||
this.listQuery.pageNo = 1;
|
||||
@@ -352,14 +354,21 @@ export default {
|
||||
this.listQuery.mainTaskType = val.val1 || null;
|
||||
this.listQuery.taskAttribute = val.val2 || null;
|
||||
this.listQuery.mainTaskState = val.val3 || null;
|
||||
this.listQuery.mainTaskCode = val.code || null;
|
||||
this.listQuery.materialCode = val.materialCode || null;
|
||||
this.listQuery.startCode = val.startCode || null;
|
||||
this.listQuery.endCode = val.endCode || null;
|
||||
this.listQuery.agv = val.agv || null;
|
||||
|
||||
this.listQuery.startTime = val.timeSlot ? val.timeSlot[0] : undefined
|
||||
this.listQuery.endTime = val.timeSlot ? val.timeSlot[1] : undefined
|
||||
console.log('val', this.listQuery);
|
||||
this.getDataList();
|
||||
break;
|
||||
case 'add':
|
||||
this.addOrUpdateHandle();
|
||||
break;
|
||||
case 'reset':
|
||||
this.listQuery.code = null;
|
||||
this.listQuery.id = null;
|
||||
this.listQuery = {
|
||||
pageSize: 10,
|
||||
pageNo: 1,
|
||||
@@ -373,7 +382,9 @@ export default {
|
||||
},
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true;
|
||||
this.dataListLoading = true;
|
||||
console.log(this.listQuery,'listQuery');
|
||||
|
||||
getTaskPage(this.listQuery).then((response) => {
|
||||
this.tableData = response.data.list;
|
||||
this.listQuery.total = response.data.total;
|
||||
@@ -405,8 +416,8 @@ export default {
|
||||
},
|
||||
//tableBtn点击
|
||||
handleClick(val) {
|
||||
if (val.type === 'stop') {
|
||||
this.stopFun(val.data);
|
||||
if (val.type === 'cancel') {
|
||||
this.cancelFun(val.data.id);
|
||||
// if (val.data === '无货') {
|
||||
// this.stopFun(val.data);
|
||||
// } else {
|
||||
@@ -420,7 +431,30 @@ export default {
|
||||
this.$refs.detailDrawer.init(val.data.id, 'detail');
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
cancelFun(id) {
|
||||
this.$confirm(`确认取消任务号为${id}的任务, 是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.urlOptions.cancelURL({ id }).then(({ data }) => {
|
||||
this.$message({
|
||||
message: "操作成功",
|
||||
type: "success",
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.getDataList();
|
||||
},
|
||||
});
|
||||
});
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '已取消操作'
|
||||
});
|
||||
});
|
||||
},
|
||||
stopFun(val) {
|
||||
this.$prompt(
|
||||
'确认终止该任务?此操作任务立即终止,车辆保持暂停,请人工回收货物,释放车辆!',
|
||||
|
||||
Reference in New Issue
Block a user