food/src/views/layout/components/main/register/flyData.vue
air e575d42358 【类 型】:feat
【原  因】:飞行数据统计 添加地图组件 规划路径  未完待续吃
【过  程】:
【影  响】:
2025-06-14 21:42:02 +08:00

343 lines
8.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<div class="fly-data-wrapper">
<div class="top-bar">
<DateRangePicker v-model="dateRange" class="m-r-20 m-b-20" />
<el-radio-group v-model="radioClass">
<el-radio-button label="飞行时长"></el-radio-button>
<el-radio-button label="飞行距离"></el-radio-button>
<el-radio-button label="消耗电量"></el-radio-button>
<el-radio-button label="飞行轨迹"></el-radio-button>
</el-radio-group>
</div>
<div class="chart-area" v-if="flyDataList.length">
<div v-if="boxShow" id="main" class="chart-container"></div>
<map-box v-else ref="mapbox" />
</div>
<div v-else class="no-data-tip">暂无数据</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import MapBox from '@/components/MapBox'
import { getFlyData } from '@/utils/api/table'
import DateRangePicker from '@/components/DateRangePicker'
export default {
name: 'FlyData',
data () {
const end = new Date()
end.setHours(23, 59, 59, 999)
const start = new Date()
start.setDate(end.getDate() - 6)
start.setHours(0, 0, 0, 0)
return {
flyDataList: [],
selectedPlaneIdArr: this.$store.state.app.toFlyDataIdArr,
dateRange: [start, end],
radioClass: '飞行时长',
boxShow: true
}
},
components: {
DateRangePicker,
MapBox
},
computed: {
source () {
if (!this.flyDataList.length) return []
const start = this.dateRange[0]
const end = this.dateRange[1]
const rangeInMs = end - start
const oneDay = 24 * 60 * 60 * 1000
const oneYear = 365 * oneDay
let groupBy = 'day'
if (rangeInMs > oneYear) {
groupBy = 'year'
} else if (rangeInMs > 30 * oneDay) {
groupBy = 'month'
}
const formatMap = {
day: (d) => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`,
month: (d) => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}`,
year: (d) => `${d.getFullYear()}`
}
const formatFn = formatMap[groupBy]
const timeLabels = []
const cursor = new Date(start.getTime())
const endTime = end.getTime()
while (cursor.getTime() <= endTime) {
timeLabels.push(formatFn(new Date(cursor)))
if (groupBy === 'day') {
cursor.setDate(cursor.getDate() + 1)
} else if (groupBy === 'month') {
cursor.setMonth(cursor.getMonth() + 1)
} else if (groupBy === 'year') {
cursor.setFullYear(cursor.getFullYear() + 1)
}
}
const groupedData = {}
const keyMap = {
飞行时长: (item) => {
if (!item.start_time || !item.end_time) return 0
return (item.end_time - item.start_time) / 60
},
飞行距离: (item) => Number(item.distance || 0),
消耗电量: (item) => Number(item.power_used || 0)
}
this.flyDataList.forEach(item => {
const planeName = item.plane_name
const date = new Date(item.start_time * 1000)
const key = formatFn(date)
if (!groupedData[planeName]) groupedData[planeName] = {}
if (!groupedData[planeName][key]) groupedData[planeName][key] = 0
groupedData[planeName][key] += keyMap[this.radioClass](item)
})
const source = [['product', ...timeLabels]]
for (const planeName in groupedData) {
const row = [planeName]
for (const label of timeLabels) {
row.push(groupedData[planeName][label] || 0)
}
source.push(row)
}
return source
},
series () {
const lineSeries = this.source.slice(1).map(() => ({
type: 'line',
smooth: true,
seriesLayoutBy: 'row',
emphasis: { focus: 'series' }
}))
const firstColumn = this.source[0]?.[1] || ''
if (!firstColumn) return lineSeries
const pieSeries = {
type: 'pie',
id: 'pie',
radius: '30%',
center: ['50%', '25%'],
emphasis: { focus: 'self' },
label: {
formatter: `{b}: {@[${firstColumn}]} ({d}%)`
},
encode: {
itemName: 'product',
value: firstColumn,
tooltip: firstColumn
}
}
return [...lineSeries, pieSeries]
}
},
created () {
},
watch: {
flyDataList (newVal) {
// 图标销毁 和 初始化
if (!newVal.length && this.myChart) {
this.myChart.dispose()
this.myChart = null
} else if (newVal.length) {
this.$nextTick(() => {
this.initChart()
})
}
// 多个飞机轨迹全部绘制
if (!this.boxShow && newVal.length) {
this.$nextTick(() => {
this.drawAllPathsOnMap()
})
}
},
boxShow (newVal) {
if (!newVal) { // boxShow 为 false显示地图时重新绘制轨迹
this.$nextTick(() => {
this.drawAllPathsOnMap()
})
}
},
dateRange: {
handler () {
this.loadFlyData()
},
immediate: true
},
source (newVal) {
if (newVal.length > 1) {
this.initChart()
}
},
radioClass (val) {
if (val === '飞行轨迹') {
this.boxShow = false
} else {
this.boxShow = true
this.$nextTick(() => {
this.initChart()
})
}
}
},
methods: {
// 地图轨迹绘制
drawAllPathsOnMap () {
if (!this.$refs.mapbox) return
if (!this.flyDataList.length) return
// 清理旧轨迹如果你的mapbox组件支持
this.$refs.mapbox.clearPaths && this.$refs.mapbox.clearPaths()
this.flyDataList.forEach(item => {
if (item.gps_path) {
const pathArray = JSON.parse(item.gps_path)
this.$refs.mapbox.createPathWithArray(pathArray)
}
})
},
// 获取飞行数据
async loadFlyData () {
if (this.selectedPlaneIdArr.length === 0) {
this.$router.push('/register/index')
return
}
if (!this.dateRange || this.dateRange.length !== 2) {
this.$message.warning('请选择日期范围')
return
}
const startTimestamp = Math.floor(this.dateRange[0].getTime() / 1000)
const endTimestamp = Math.floor(this.dateRange[1].getTime() / 1000)
const res = await getFlyData(this.selectedPlaneIdArr, startTimestamp, endTimestamp)
if (res.data.status === 1) {
this.flyDataList = res.data.dataList
} else {
this.$message.error(res.data.msg)
}
},
initChart () {
const chartDom = document.getElementById('main')
if (!chartDom) return
if (this.myChart) {
this.myChart.dispose()
}
this.myChart = echarts.init(chartDom)
const firstColumnIndex = 1
this.myChart.setOption({
legend: {},
tooltip: {
trigger: 'axis',
showContent: true
},
dataset: {
source: this.source
},
xAxis: { type: 'category' },
yAxis: {
gridIndex: 0,
name: this.radioClass === '飞行时长' ? '分钟' : this.radioClass === '飞行距离' ? '米' : 'mAh'
},
grid: { top: '55%' },
series: [
...this.source.slice(1).map(() => ({
type: 'line',
smooth: true,
seriesLayoutBy: 'row',
emphasis: { focus: 'series' }
})),
{
type: 'pie',
id: 'pie',
radius: '30%',
center: ['50%', '25%'],
emphasis: { focus: 'self' },
label: {
formatter: `{b}: {@[${firstColumnIndex}]} ({d}%)`
},
encode: {
itemName: 'product',
value: firstColumnIndex,
tooltip: firstColumnIndex
}
}
]
})
this.myChart.on('updateAxisPointer', (event) => {
const xAxisInfo = event.axesInfo[0]
if (xAxisInfo) {
const dimension = xAxisInfo.value + 1
this.myChart.setOption({
series: {
id: 'pie',
label: {
formatter: `{b}: {@[${dimension}]} ({d}%)`
},
encode: {
value: dimension,
tooltip: dimension
}
}
})
}
})
window.addEventListener('resize', () => {
this.myChart.resize()
})
}
}
}
</script>
<style lang="scss" scoped>
@import "@/styles/theme.scss";
.fly-data-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.top-bar {
flex: 0 0 auto;
padding-bottom: 10px;
}
.chart-area {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.chart-container {
flex: 1;
width: 100%;
}
</style>