统计视图
This commit is contained in:
parent
b123bb2131
commit
995839d70e
@ -4,8 +4,7 @@ NODE_ENV=development
|
|||||||
VITE_DEV=true
|
VITE_DEV=true
|
||||||
|
|
||||||
# 请求路径
|
# 请求路径
|
||||||
# VITE_BASE_URL='http://192.168.0.74:48080'
|
# VITE_BASE_URL='http://192.168.77.50:48080'
|
||||||
# VITE_BASE_URL='http://192.168.0.153:48080'
|
|
||||||
VITE_BASE_URL='http://10.10.100.17:48080'
|
VITE_BASE_URL='http://10.10.100.17:48080'
|
||||||
|
|
||||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
||||||
|
21
src/api/map/statistics.ts
Normal file
21
src/api/map/statistics.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
//统计车辆状态分类
|
||||||
|
export const robotStatusClassification = async (params) => {
|
||||||
|
return await request.get({ url: `/system/statistics/robotStatusClassification`, params })
|
||||||
|
}
|
||||||
|
|
||||||
|
//统计任务人工完成-自动完成
|
||||||
|
export const robotTaskAutomaticArtificial = async (params) => {
|
||||||
|
return await request.get({ url: `/system/statistics/robotTaskAutomaticArtificial`, params })
|
||||||
|
}
|
||||||
|
|
||||||
|
//统计故障根因分析
|
||||||
|
export const robotWarnMsgClassification = async (params) => {
|
||||||
|
return await request.get({ url: `/system/statistics/robotWarnMsgClassification`, params })
|
||||||
|
}
|
||||||
|
|
||||||
|
//车辆工作时长统计
|
||||||
|
export const robotWorkHourStatistics = async (params) => {
|
||||||
|
return await request.get({ url: `/system/statistics/robotWorkHourStatistics`, params })
|
||||||
|
}
|
19
src/styles/FormCreate/index.css
Normal file
19
src/styles/FormCreate/index.css
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "fc-icon";
|
||||||
|
src: url("@/styles/FormCreate/fonts/fontello.woff") format("woff");
|
||||||
|
}
|
||||||
|
.icon-doc-text:before {
|
||||||
|
content: "\f0f6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-server:before {
|
||||||
|
content: "\f233";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-address-card-o:before {
|
||||||
|
content: "\f2bc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-user-o:before {
|
||||||
|
content: "\f2c0";
|
||||||
|
}/*# sourceMappingURL=index.css.map */
|
1
src/styles/FormCreate/index.css.map
Normal file
1
src/styles/FormCreate/index.css.map
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["index.scss","index.css"],"names":[],"mappings":"AAEA;EACE,sBAAA;EACA,kEAAA;ACDF;ADIA;EACE,gBAAA;ACFF;;ADKA;EACE,gBAAA;ACFF;;ADKA;EACE,gBAAA;ACFF;;ADKA;EACE,gBAAA;ACFF","file":"index.css"}
|
@ -66,6 +66,15 @@
|
|||||||
class="current-item"
|
class="current-item"
|
||||||
:class="currentItem && currentItem.id == floor.id ? 'tool-active' : ''"
|
:class="currentItem && currentItem.id == floor.id ? 'tool-active' : ''"
|
||||||
@click="chooseLocationPoint(floor)"
|
@click="chooseLocationPoint(floor)"
|
||||||
|
:style="{
|
||||||
|
background:
|
||||||
|
floor.locationEnable === 0 ||
|
||||||
|
floor.locationLock === 0 ||
|
||||||
|
(floor.locationUseStatus === 1 && locationTypeStr === 'release') ||
|
||||||
|
(floor.locationUseStatus === 0 && locationTypeStr === 'take')
|
||||||
|
? '#FFE2E2'
|
||||||
|
: '#F6FFEF'
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div>层数: 第{{ floor.locationStorey }}层</div>
|
<div>层数: 第{{ floor.locationStorey }}层</div>
|
||||||
<div class="mt-4px">库位号: {{ floor.locationNo }}</div>
|
<div class="mt-4px">库位号: {{ floor.locationNo }}</div>
|
||||||
|
424
src/views/statisticalView/components/ChartCard.vue
Normal file
424
src/views/statisticalView/components/ChartCard.vue
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chart-card">
|
||||||
|
<div class="chart-header">
|
||||||
|
<div class="chart-header-title">
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
<div v-if="chartType === 'bar'" class="ml-2">
|
||||||
|
<el-cascader
|
||||||
|
v-model="selectedOptions"
|
||||||
|
:options="cascaderOptions"
|
||||||
|
:props="cascaderProps"
|
||||||
|
@change="handleChange"
|
||||||
|
placeholder="请选择区域"
|
||||||
|
class="!w-140px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dimension-switcher" v-if="chartType !== 'pie1'">
|
||||||
|
<button
|
||||||
|
v-for="item in dimensionOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:class="{ active: dimension === item.value }"
|
||||||
|
@click="handleDimensionChange(item.value)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||||
|
import { ElLoading } from 'element-plus'
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import * as MapApi from '@/api/map/map'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: String,
|
||||||
|
dimension: Number,
|
||||||
|
fetchData: Function,
|
||||||
|
chartType: {
|
||||||
|
type: String,
|
||||||
|
default: 'pie',
|
||||||
|
validator: (value) => ['pie1', 'bar', 'stackedBar', 'pie2'].includes(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['dimension-change'])
|
||||||
|
|
||||||
|
const chartRef = ref(null)
|
||||||
|
let chartInstance = null
|
||||||
|
|
||||||
|
const dimensionOptions = [
|
||||||
|
{ label: '周', value: 1 },
|
||||||
|
{ label: '月', value: 2 },
|
||||||
|
{ label: '季度', value: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 处理维度切换
|
||||||
|
const handleDimensionChange = (dimension) => {
|
||||||
|
emit('dimension-change', dimension)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose()
|
||||||
|
}
|
||||||
|
chartInstance = echarts.init(chartRef.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
const updateChart = async () => {
|
||||||
|
if (!chartInstance) return
|
||||||
|
|
||||||
|
const loading = ElLoading.service({
|
||||||
|
lock: true,
|
||||||
|
text: 'Loading',
|
||||||
|
background: 'rgba(0, 0, 0, 0.6)'
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await props.fetchData(props.dimension)
|
||||||
|
let option = {}
|
||||||
|
if (props.chartType === 'pie1') {
|
||||||
|
option = getPieOption1(data)
|
||||||
|
} else if (props.chartType === 'bar') {
|
||||||
|
option = getBarOption(data)
|
||||||
|
} else if (props.chartType === 'stackedBar') {
|
||||||
|
option = getStackedBarOption(data)
|
||||||
|
} else if (props.chartType === 'pie2') {
|
||||||
|
option = getPieOption2(data)
|
||||||
|
}
|
||||||
|
chartInstance.setOption(option)
|
||||||
|
|
||||||
|
loading.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 饼图1配置
|
||||||
|
const getPieOption1 = (data) => {
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b} : {c} ({d}%)'
|
||||||
|
},
|
||||||
|
toolbox: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
color: ['#5C7BD9', '#EE6666', '#91CC75', '#FAC858'],
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 10,
|
||||||
|
top: 70,
|
||||||
|
bottom: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
clockwise: false, //饼图的扇区是否是顺时针排布
|
||||||
|
minAngle: 2, //最小的扇区角度(0 ~ 360)
|
||||||
|
radius: ['50%', '65%'],
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
//图形样式
|
||||||
|
normal: {
|
||||||
|
borderColor: '#ffffff',
|
||||||
|
borderWidth: 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
normal: {
|
||||||
|
show: true,
|
||||||
|
position: 'center',
|
||||||
|
formatter: '{a|任务总数}:{c|23412}',
|
||||||
|
rich: {
|
||||||
|
c: {
|
||||||
|
align: 'center',
|
||||||
|
color: '#0D162A',
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
height: 30
|
||||||
|
},
|
||||||
|
a: {
|
||||||
|
align: 'center',
|
||||||
|
color: '#0D162A',
|
||||||
|
fontSize: 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
length: 15,
|
||||||
|
length2: 0,
|
||||||
|
maxSurfaceAngle: 80
|
||||||
|
},
|
||||||
|
data: data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通条形图配置
|
||||||
|
const getBarOption = (data) => {
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b} : {c}'
|
||||||
|
},
|
||||||
|
color: ['#2268FF', '#34bf49', '#ffdd00'],
|
||||||
|
toolbox: {},
|
||||||
|
legend: {
|
||||||
|
top: 14
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: data.categories
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
boundaryGap: [0, 0.01]
|
||||||
|
},
|
||||||
|
series: data.series
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 堆叠条形图配置
|
||||||
|
const getStackedBarOption = (data) => {
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} : {c}'
|
||||||
|
},
|
||||||
|
color: ['#FF7D7D', '#8a8acb', '#FAC858', '#91CC75'],
|
||||||
|
toolbox: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 14,
|
||||||
|
data: data.series.map((item) => item.name)
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: data.categories
|
||||||
|
// axisLabel: {
|
||||||
|
// interval: 0,
|
||||||
|
// rotate: 30
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: '告警数量',
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 14, // 文字大小
|
||||||
|
padding: [0, 0, 10, 0] // 文字内边距
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: data.series.map((item) => ({
|
||||||
|
...item,
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
label: {
|
||||||
|
// show: true
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 饼图2配置
|
||||||
|
const getPieOption2 = (data) => {
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b} : {c} ({d}%)'
|
||||||
|
},
|
||||||
|
color: ['#fc636b', '#6a67ce'],
|
||||||
|
toolbox: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 10,
|
||||||
|
top: 70,
|
||||||
|
bottom: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Access From',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['50%', '65%'],
|
||||||
|
center: ['50%', '60%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
//图形样式
|
||||||
|
normal: {
|
||||||
|
borderColor: '#ffffff',
|
||||||
|
borderWidth: 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
position: 'center'
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式调整
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.resize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先获取所有的地图
|
||||||
|
const selectedOptions = ref([])
|
||||||
|
const selectedId = ref(null)
|
||||||
|
const cascaderOptions = ref([])
|
||||||
|
|
||||||
|
// 配置级联选择器属性
|
||||||
|
const cascaderProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'name',
|
||||||
|
children: 'children'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMapList = async () => {
|
||||||
|
let res = await MapApi.getPositionMapGetMap()
|
||||||
|
cascaderOptions.value = Object.entries(res).map(([floor, areas]) => {
|
||||||
|
return {
|
||||||
|
id: `floor_${floor}`,
|
||||||
|
name: `${floor}层`,
|
||||||
|
children: areas.map((area) => ({
|
||||||
|
id: area.id,
|
||||||
|
name: `${area.area}`,
|
||||||
|
areaInfo: area
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置默认选中第一个选项
|
||||||
|
if (cascaderOptions.value.length > 0 && cascaderOptions.value[0].children.length > 0) {
|
||||||
|
selectedOptions.value = [cascaderOptions.value[0].id, cascaderOptions.value[0].children[0].id]
|
||||||
|
selectedId.value = cascaderOptions.value[0].children[0].id
|
||||||
|
emit('mapId-change', selectedId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择变化时的处理
|
||||||
|
const handleChange = (value) => {
|
||||||
|
// 最后一个值就是选中的区域ID
|
||||||
|
selectedId.value = value[value.length - 1]
|
||||||
|
emit('mapId-change', selectedId.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听维度变化
|
||||||
|
watch(
|
||||||
|
() => props.dimension,
|
||||||
|
() => {
|
||||||
|
updateChart()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => selectedId.value,
|
||||||
|
() => {
|
||||||
|
updateChart()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
initChart()
|
||||||
|
await getMapList()
|
||||||
|
await updateChart()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose()
|
||||||
|
chartInstance = null
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.chart-card {
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
.chart-header-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dimension-switcher {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dimension-switcher button {
|
||||||
|
padding: 4px 14px;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dimension-switcher button.active {
|
||||||
|
background: #00329f;
|
||||||
|
color: white;
|
||||||
|
border-color: #00329f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,462 +1,182 @@
|
|||||||
|
el-col
|
||||||
<template>
|
<template>
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<div class="top-box">
|
<div class="top-box">
|
||||||
<div class="top-box-left"> 统计视图 </div>
|
<div class="top-box-left"> 统计视图 </div>
|
||||||
<div class="top-box-right">
|
<!-- <div class="top-box-right">
|
||||||
<div class="top-box-right-title"> 统计方式 </div>
|
<div class="top-box-right-title"> 统计方式 </div>
|
||||||
<el-select v-model="type" placeholder="请选择" style="width: 100px; margin-left: 10px">
|
|
||||||
<el-option :label="'天'" :value="1" />
|
|
||||||
<el-option :label="'周'" :value="2" />
|
|
||||||
<el-option :label="'月'" :value="3" />
|
|
||||||
</el-select>
|
|
||||||
<el-button style="margin-left: 16px" @click="openForm()">看板管理</el-button>
|
<el-button style="margin-left: 16px" @click="openForm()">看板管理</el-button>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="dashboard-container">
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
|
<!-- 状态分类饼图 -->
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card style="width: 100%" shadow="never">
|
<chart-card
|
||||||
<div class="charts-title"> 任务总览 </div>
|
title="状态分类"
|
||||||
<div ref="chartDom" style="width: 100%; height: 400px"></div>
|
:dimension="dimension1"
|
||||||
</el-card>
|
:fetch-data="fetchStatusData"
|
||||||
|
chart-type="pie1"
|
||||||
|
@dimension-change="(val) => (dimension1 = val)"
|
||||||
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<!-- 任务总览条形图 -->
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card style="width: 100%" shadow="never">
|
<chart-card
|
||||||
<div class="charts-title"> 任务完成率 </div>
|
title="任务总览"
|
||||||
<div ref="chartDomFinish" style="width: 100%; height: 400px"></div>
|
:dimension="dimension2"
|
||||||
</el-card>
|
:fetch-data="fetchTaskOverviewData"
|
||||||
|
chart-type="bar"
|
||||||
|
@dimension-change="(val) => (dimension2 = val)"
|
||||||
|
@mapId-change="mapIdChange"
|
||||||
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="16" style="margin-top: 12px">
|
<el-row :gutter="16" class="mt-4">
|
||||||
|
<!-- 故障类目分析堆叠条形图 -->
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card style="width: 100%" shadow="never">
|
<chart-card
|
||||||
<div class="charts-title"> AGV工作利用率统计 </div>
|
title="故障类目分析"
|
||||||
<div ref="chartDomAgv" style="width: 100%; height: 400px"></div>
|
:dimension="dimension3"
|
||||||
</el-card>
|
:fetch-data="fetchFaultAnalysisData"
|
||||||
|
chart-type="stackedBar"
|
||||||
|
@dimension-change="(val) => (dimension3 = val)"
|
||||||
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<!-- 人工干预任务饼图 -->
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card style="width: 100%" shadow="never">
|
<chart-card
|
||||||
<div class="charts-title"> 任务异常数 </div>
|
title="人工干预任务"
|
||||||
<div ref="chartDomError" style="width: 100%; height: 400px"></div>
|
:dimension="dimension4"
|
||||||
</el-card>
|
:fetch-data="fetchManualInterventionData"
|
||||||
|
chart-type="pie2"
|
||||||
|
@dimension-change="(val) => (dimension4 = val)"
|
||||||
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
<BoarViewDialog ref="boarViewDialogRef" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick, reactive } from 'vue'
|
import { ref } from 'vue'
|
||||||
import * as echarts from 'echarts'
|
import ChartCard from './components/ChartCard.vue'
|
||||||
import BoarViewDialog from './boarViewDialog.vue'
|
import BoarViewDialog from './boarViewDialog.vue'
|
||||||
|
import * as StatisticsAPi from '@/api/map/statistics'
|
||||||
|
|
||||||
// 创建一个响应式引用来保存DOM元素
|
// 每个图表的独立维度状态
|
||||||
const chartDom = ref(null)
|
const dimension1 = ref(1) // 状态分类
|
||||||
const chartDomFinish = ref(null)
|
const dimension2 = ref(1) // 任务总览
|
||||||
const chartDomError = ref(null)
|
const dimension3 = ref(1) // 故障类目分析
|
||||||
const chartDomAgv = ref(null)
|
const dimension4 = ref(1) // 人工干预任务
|
||||||
const boarViewDialogRef = ref(null)
|
const selectedMapId = ref()
|
||||||
// 初始化ECharts实例并设置配置项(这里以折线图为例,但可灵活替换)
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick() // 确保DOM已经渲染完成
|
|
||||||
initEcharts()
|
|
||||||
})
|
|
||||||
|
|
||||||
const initEcharts = () => {
|
// 1. 状态分类饼图数据获取
|
||||||
initOne()
|
const fetchStatusData = async () => {
|
||||||
initTwo()
|
const res = await StatisticsAPi.robotStatusClassification()
|
||||||
initFour()
|
const chartData = [
|
||||||
}
|
{ value: res.chargeNum, name: '充电车辆' },
|
||||||
|
{ value: res.faultNum, name: '故障车辆' },
|
||||||
const chartInstance = ref(null)
|
{ value: res.doingTaskNum, name: '执行任务车辆' },
|
||||||
const initOne = () => {
|
{ value: res.idleNum, name: '空闲车辆' }
|
||||||
chartInstance.value = echarts.init(chartDom.value)
|
|
||||||
let ydata = [
|
|
||||||
{
|
|
||||||
name: '执行中',
|
|
||||||
value: 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '已完成',
|
|
||||||
value: 16
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '已取消',
|
|
||||||
value: 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '未开始',
|
|
||||||
value: 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '异常',
|
|
||||||
value: 10
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
let color = ['#0147EB', '#01BCEB', '#C800FF', '#F1CD0B', '#EB0000']
|
return chartData
|
||||||
let xdata = ['执行中', '已完成', '已取消', '未开始', '异常']
|
|
||||||
const option = {
|
|
||||||
backgroundColor: 'rgba(255,255,255,1)',
|
|
||||||
color: color,
|
|
||||||
// tooltip: {
|
|
||||||
// trigger: 'item',
|
|
||||||
// // formatter: '{a} <br/>{b} : {c} ({d}%)'
|
|
||||||
// },
|
|
||||||
legend: {
|
|
||||||
orient: 'vartical',
|
|
||||||
x: 'left',
|
|
||||||
top: 'center',
|
|
||||||
left: '60%',
|
|
||||||
bottom: '0%',
|
|
||||||
data: xdata,
|
|
||||||
itemWidth: 10,
|
|
||||||
itemHeight: 10,
|
|
||||||
itemGap: 16,
|
|
||||||
formatter: function (name) {
|
|
||||||
let str = ''
|
|
||||||
ydata.forEach((item) => {
|
|
||||||
if (item.name == name) {
|
|
||||||
str = `{c|${item.name}} {a|${item.value}}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return str
|
|
||||||
},
|
|
||||||
textStyle: {
|
|
||||||
rich: {
|
|
||||||
a: {
|
|
||||||
color: '#0D162A',
|
|
||||||
fontSize: 18
|
|
||||||
},
|
|
||||||
c: {
|
|
||||||
color: '#536387',
|
|
||||||
fontSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'pie',
|
|
||||||
clockwise: false, //饼图的扇区是否是顺时针排布
|
|
||||||
minAngle: 2, //最小的扇区角度(0 ~ 360)
|
|
||||||
radius: ['50%', '65%'],
|
|
||||||
center: ['30%', '50%'],
|
|
||||||
avoidLabelOverlap: false,
|
|
||||||
itemStyle: {
|
|
||||||
//图形样式
|
|
||||||
normal: {
|
|
||||||
borderColor: '#ffffff',
|
|
||||||
borderWidth: 6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
// '{text|{b}}\n{c} ({d}%)'
|
|
||||||
normal: {
|
|
||||||
show: true,
|
|
||||||
position: 'center',
|
|
||||||
formatter: '{c|234,12} \n {a|任务总数}',
|
|
||||||
rich: {
|
|
||||||
c: {
|
|
||||||
color: '#0D162A',
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
height: 30
|
|
||||||
},
|
|
||||||
|
|
||||||
a: {
|
|
||||||
align: 'center',
|
|
||||||
color: '#727272',
|
|
||||||
fontSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
labelLine: {
|
|
||||||
length: 15,
|
|
||||||
length2: 0,
|
|
||||||
maxSurfaceAngle: 80
|
|
||||||
},
|
|
||||||
data: ydata
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
chartInstance.value.setOption(option)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const chartInstanceTwo = ref(null)
|
// 2. 任务总览条形图数据获取
|
||||||
const initTwo = () => {
|
const fetchTaskOverviewData = async (dimension) => {
|
||||||
chartInstanceTwo.value = echarts.init(chartDomFinish.value)
|
const res = await StatisticsAPi.robotWorkHourStatistics({
|
||||||
let option = {
|
type: dimension,
|
||||||
backgroundColor: '#fff',
|
positionMapId: selectedMapId.value
|
||||||
grid: {
|
})
|
||||||
top: '9%',
|
|
||||||
bottom: '19%',
|
if (!res) {
|
||||||
left: '6%',
|
return {
|
||||||
right: '4%'
|
categories: [],
|
||||||
},
|
series: []
|
||||||
tooltip: {
|
}
|
||||||
trigger: 'axis',
|
} else {
|
||||||
label: {
|
const xAxisData = res.map((item) => {
|
||||||
show: true
|
return item.robotNo
|
||||||
}
|
})
|
||||||
},
|
const freeTimeNum = res.map((item) => {
|
||||||
xAxis: {
|
return item.freeTimeNum
|
||||||
boundaryGap: true, //默认,坐标轴留白策略
|
})
|
||||||
axisLine: {
|
const taskTimeNum = res.map((item) => {
|
||||||
show: false
|
return item.taskTimeNum
|
||||||
},
|
})
|
||||||
splitLine: {
|
const chargeTimeNum = res.map((item) => {
|
||||||
show: false
|
return item.chargeTimeNum
|
||||||
},
|
})
|
||||||
axisTick: {
|
|
||||||
show: false,
|
const chartData = {
|
||||||
alignWithLabel: true
|
categories: xAxisData,
|
||||||
},
|
series: [
|
||||||
data: [
|
{ name: '空闲时长', type: 'bar', data: freeTimeNum, barWidth: '20%', barGap: '25%' },
|
||||||
'武汉',
|
{ name: '任务时长', type: 'bar', data: taskTimeNum, barWidth: '20%', barGap: '25%' },
|
||||||
'襄阳',
|
{ name: '充电时长', type: 'bar', data: chargeTimeNum, barWidth: '20%', barGap: '25%' }
|
||||||
'黄冈',
|
|
||||||
'荆门',
|
|
||||||
'十堰',
|
|
||||||
'随州',
|
|
||||||
'鄂州',
|
|
||||||
'恩施',
|
|
||||||
'宜昌',
|
|
||||||
'孝感',
|
|
||||||
'咸宁',
|
|
||||||
'仙桃',
|
|
||||||
'潜江',
|
|
||||||
'天门',
|
|
||||||
'黄石',
|
|
||||||
'荆州',
|
|
||||||
'神农架'
|
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
yAxis: {
|
return chartData
|
||||||
axisLine: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
color: '#E2E7F5'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
splitArea: {
|
|
||||||
show: true,
|
|
||||||
areaStyle: {
|
|
||||||
color: 'rgb(245,250,254)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
symbol: 'circle',
|
|
||||||
symbolSize: 1,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#0147EB'
|
|
||||||
},
|
|
||||||
|
|
||||||
label: {
|
|
||||||
show: false,
|
|
||||||
distance: 1,
|
|
||||||
emphasis: {
|
|
||||||
show: true,
|
|
||||||
offset: [25, -2],
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
||||||
color: '#FFF',
|
|
||||||
padding: [8, 20, 8, 6],
|
|
||||||
//width:60,
|
|
||||||
height: 36,
|
|
||||||
formatter: function (params) {
|
|
||||||
var name = params.name
|
|
||||||
var value = params.data
|
|
||||||
var str = name + '\n数据量:' + value
|
|
||||||
return str
|
|
||||||
},
|
|
||||||
rich: {
|
|
||||||
bg: {
|
|
||||||
width: 78,
|
|
||||||
//height:42,
|
|
||||||
color: '#FFF',
|
|
||||||
padding: [20, 0, 20, 10]
|
|
||||||
},
|
|
||||||
br: {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
2000, 1800, 2800, 900, 1600, 2000, 3000, 2030, 1356, 1900, 4000, 3000, 2000, 3000, 4200,
|
|
||||||
3200, 3800
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
chartInstanceTwo.value.setOption(option)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const chartInstanceFour = ref(null)
|
// 3. 故障类目分析堆叠条形图数据获取
|
||||||
const initFour = () => {
|
const fetchFaultAnalysisData = async (dimension) => {
|
||||||
chartInstanceFour.value = echarts.init(chartDomError.value)
|
let res = await StatisticsAPi.robotWarnMsgClassification({
|
||||||
let option = {
|
type: dimension
|
||||||
backgroundColor: '#fff',
|
})
|
||||||
grid: {
|
|
||||||
top: '9%',
|
|
||||||
bottom: '19%',
|
|
||||||
left: '6%',
|
|
||||||
right: '4%'
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
label: {
|
|
||||||
show: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
boundaryGap: true, //默认,坐标轴留白策略
|
|
||||||
axisLine: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
alignWithLabel: true
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
'武汉',
|
|
||||||
'襄阳',
|
|
||||||
'黄冈',
|
|
||||||
'荆门',
|
|
||||||
'十堰',
|
|
||||||
'随州',
|
|
||||||
'鄂州',
|
|
||||||
'恩施',
|
|
||||||
'宜昌',
|
|
||||||
'孝感',
|
|
||||||
'咸宁',
|
|
||||||
'仙桃',
|
|
||||||
'潜江',
|
|
||||||
'天门',
|
|
||||||
'黄石',
|
|
||||||
'荆州',
|
|
||||||
'神农架'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
axisLine: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
color: '#E2E7F5'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
splitArea: {
|
|
||||||
show: true,
|
|
||||||
areaStyle: {
|
|
||||||
color: 'rgb(245,250,254)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
symbol: 'circle',
|
|
||||||
symbolSize: 1,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#0147EB'
|
|
||||||
},
|
|
||||||
|
|
||||||
label: {
|
const dates = Object.keys(res).sort((a, b) => new Date(a) - new Date(b))
|
||||||
show: false,
|
const warnLevels = ['1', '2', '3', '4']
|
||||||
distance: 1,
|
const series = warnLevels.map((level) => ({
|
||||||
emphasis: {
|
name: `级别${level}`,
|
||||||
show: true,
|
data: new Array(dates.length).fill(0)
|
||||||
offset: [25, -2],
|
}))
|
||||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
||||||
color: '#FFF',
|
dates.forEach((date, dateIndex) => {
|
||||||
padding: [8, 20, 8, 6],
|
const warnings = res[date]
|
||||||
//width:60,
|
warnings.forEach((warning) => {
|
||||||
height: 36,
|
const levelIndex = warnLevels.indexOf(warning.warnLevel)
|
||||||
formatter: function (params) {
|
if (levelIndex !== -1) {
|
||||||
var name = params.name
|
series[levelIndex].data[dateIndex] += warning.num
|
||||||
var value = params.data
|
|
||||||
var str = name + '\n数据量:' + value
|
|
||||||
return str
|
|
||||||
},
|
|
||||||
rich: {
|
|
||||||
bg: {
|
|
||||||
width: 78,
|
|
||||||
//height:42,
|
|
||||||
color: '#FFF',
|
|
||||||
padding: [20, 0, 20, 10]
|
|
||||||
},
|
|
||||||
br: {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
2000, 1800, 2800, 900, 1600, 2000, 3000, 2030, 1356, 1900, 4000, 3000, 2000, 3000, 4200,
|
|
||||||
3200, 3800
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
})
|
||||||
}
|
})
|
||||||
chartInstanceFour.value.setOption(option)
|
|
||||||
|
return { categories: dates, series }
|
||||||
}
|
}
|
||||||
// 销毁ECharts实例
|
|
||||||
onUnmounted(() => {
|
// 4. 人工干预任务饼图数据获取
|
||||||
if (chartInstance.value != null && chartInstance.value.dispose) {
|
const fetchManualInterventionData = async (dimension) => {
|
||||||
chartInstance.value.dispose()
|
const res = await StatisticsAPi.robotTaskAutomaticArtificial({
|
||||||
}
|
type: dimension
|
||||||
})
|
})
|
||||||
|
const chartData = [
|
||||||
|
{ value: res.artificialDoneNum, name: '人工' },
|
||||||
|
{ value: res.automaticDoneNum, name: '自动' }
|
||||||
|
]
|
||||||
|
return chartData
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapIdChange = (e) => {
|
||||||
|
selectedMapId.value = e
|
||||||
|
}
|
||||||
|
|
||||||
const openForm = () => {
|
const openForm = () => {
|
||||||
boarViewDialogRef.value.open()
|
boarViewDialogRef.value.open()
|
||||||
}
|
}
|
||||||
const type = ref(1)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
.top-box {
|
.dashboard-container {
|
||||||
width: 100%;
|
margin-top: 14px;
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
.top-box-left {
|
|
||||||
color: #0d162a;
|
@media (max-width: 1200px) {
|
||||||
font-size: 18px;
|
.dashboard-container {
|
||||||
}
|
grid-template-columns: 1fr;
|
||||||
.top-box-right {
|
}
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.top-box-right-title {
|
|
||||||
font-family:
|
|
||||||
PingFangSC,
|
|
||||||
PingFang SC;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #0d162a;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user