【类 型】:feat
【原 因】:需要根据用户选择展示飞行时长、飞行距离、消耗电量的不同统计图表 【过 程】: - 新增 radioClass 状态切换逻辑 - 根据选择动态处理 flyDataList,计算不同类型字段(如时长、距离、电量) - 动态生成 ECharts dataset.source 和 series 配置 - 统一处理时间粒度(日 / 月 / 年) - 保持图表响应式和联动饼图功能 【影 响】: 用户可通过单选按钮自由切换三种维度图表
This commit is contained in:
parent
2a1c1a24ed
commit
647eb16956
59
package-lock.json
generated
59
package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
"element-ui": "^2.15.14",
|
"element-ui": "^2.15.14",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mapbox-gl": "^2.15.0",
|
"mapbox-gl": "^2.15.0",
|
||||||
@ -6291,6 +6292,20 @@
|
|||||||
"safer-buffer": "^2.1.0"
|
"safer-buffer": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/echarts": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0",
|
||||||
|
"zrender": "5.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/echarts/node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@ -17487,6 +17502,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||||
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
|
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/zrender": {
|
||||||
|
"version": "5.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
|
||||||
|
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zrender/node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -22265,6 +22293,22 @@
|
|||||||
"safer-buffer": "^2.1.0"
|
"safer-buffer": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"echarts": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "2.3.0",
|
||||||
|
"zrender": "5.6.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ee-first": {
|
"ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@ -31208,6 +31252,21 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"zrender": {
|
||||||
|
"version": "5.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
|
||||||
|
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "2.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,18 +9,19 @@
|
|||||||
"lint": "eslint --ext .js,.vue src"
|
"lint": "eslint --ext .js,.vue src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.5",
|
|
||||||
"vue": "^2.6.11",
|
|
||||||
"vuex": "^3.4.0",
|
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
"element-ui": "^2.15.14",
|
"element-ui": "^2.15.14",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mapbox-gl": "^2.15.0",
|
"mapbox-gl": "^2.15.0",
|
||||||
"mqtt": "^2.18.9",
|
"mqtt": "^2.18.9",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
"vue": "^2.6.11",
|
||||||
"vue-router": "^3.6.5",
|
"vue-router": "^3.6.5",
|
||||||
"vue-template-compiler": "^2.7.16"
|
"vue-template-compiler": "^2.7.16",
|
||||||
|
"vuex": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mapbox/mapbox-gl-draw": "^1.5.0",
|
"@mapbox/mapbox-gl-draw": "^1.5.0",
|
||||||
|
131
src/components/DateRangePicker.vue
Normal file
131
src/components/DateRangePicker.vue
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="internalValue"
|
||||||
|
type="daterange"
|
||||||
|
align="right"
|
||||||
|
unlink-panels
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
:picker-options="pickerOptions"
|
||||||
|
format="yyyy-MM-dd"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'DateRangePicker',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
internalValue: [], // 内部绑定值
|
||||||
|
pickerOptions: {
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
text: '今天',
|
||||||
|
onClick (picker) {
|
||||||
|
const now = new Date()
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
|
start.setHours(0, 0, 0, 0)
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
picker.$emit('pick', [start, end])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '本月',
|
||||||
|
onClick (picker) {
|
||||||
|
const now = new Date()
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||||
|
start.setHours(0, 0, 0, 0)
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
picker.$emit('pick', [start, end])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '本年',
|
||||||
|
onClick (picker) {
|
||||||
|
const now = new Date()
|
||||||
|
const start = new Date(now.getFullYear(), 0, 1)
|
||||||
|
start.setHours(0, 0, 0, 0)
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
picker.$emit('pick', [start, end])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '最近一周',
|
||||||
|
onClick (picker) {
|
||||||
|
const now = new Date()
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
const start = new Date(end)
|
||||||
|
start.setDate(end.getDate() - 6)
|
||||||
|
start.setHours(0, 0, 0, 0)
|
||||||
|
picker.$emit('pick', [start, end])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '最近一个月',
|
||||||
|
onClick (picker) {
|
||||||
|
const now = new Date()
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate())
|
||||||
|
start.setHours(0, 0, 0, 0)
|
||||||
|
picker.$emit('pick', [start, end])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '最近三个月',
|
||||||
|
onClick (picker) {
|
||||||
|
const now = new Date()
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate())
|
||||||
|
start.setHours(0, 0, 0, 0)
|
||||||
|
picker.$emit('pick', [start, end])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '上月',
|
||||||
|
onClick (picker) {
|
||||||
|
const now = new Date()
|
||||||
|
const start = new Date(now.getFullYear(), now.getMonth() - 1, 1)
|
||||||
|
start.setHours(0, 0, 0, 0)
|
||||||
|
const end = new Date(now.getFullYear(), now.getMonth(), 0)
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
picker.$emit('pick', [start, end])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
handler (newVal) {
|
||||||
|
if (Array.isArray(newVal) && newVal.length === 2) {
|
||||||
|
this.internalValue = [...newVal]
|
||||||
|
} else {
|
||||||
|
this.internalValue = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onChange (val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
this.$emit('change', val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -99,6 +99,17 @@ const routes = [
|
|||||||
roles: ['admin', 'editor'],
|
roles: ['admin', 'editor'],
|
||||||
tapName: 'plane'
|
tapName: 'plane'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/register/flyData',
|
||||||
|
component: () => import('@/views/layout/components/main/register/flyData'),
|
||||||
|
meta: {
|
||||||
|
title: '飞行数据统计',
|
||||||
|
icon: 'el-icon-data-line',
|
||||||
|
roles: ['admin', 'editor'],
|
||||||
|
tapName: 'plane',
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -5,10 +5,11 @@ const state = {
|
|||||||
isWideScreen: window.innerWidth < 480, // 屏幕宽度是否小于480
|
isWideScreen: window.innerWidth < 480, // 屏幕宽度是否小于480
|
||||||
defaultLonLat: null, // 地图默认经纬度
|
defaultLonLat: null, // 地图默认经纬度
|
||||||
defaultZoom: null, // 地图默认缩放
|
defaultZoom: null, // 地图默认缩放
|
||||||
orderSerch: null, // 订单列表页搜索条件
|
|
||||||
|
|
||||||
/* 页面临时传参 */
|
/* 页面参数 */
|
||||||
toMessageIdArr: [] // 用户管理 发布公告页面 id临时传参
|
orderSerch: null, // 订单列表页搜索条件
|
||||||
|
toMessageIdArr: [], // 用户管理 发布公告页面 id临时传参
|
||||||
|
toFlyDataIdArr: [] // 飞机飞行数据 临时传参
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@ -56,8 +57,13 @@ const mutations = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/* 临时传参 */
|
/* 临时传参 */
|
||||||
|
// 管理员管理 发布公告页面 传递id数组
|
||||||
setToMessageIdArr (state, idArr) {
|
setToMessageIdArr (state, idArr) {
|
||||||
state.toMessageIdArr = idArr
|
state.toMessageIdArr = idArr
|
||||||
|
},
|
||||||
|
// 飞机飞行数据 传递id数组
|
||||||
|
setToFlyDataIdArr (state, idArr) {
|
||||||
|
state.toFlyDataIdArr = idArr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,3 +161,20 @@ export async function pubMessage (tit, message, idArr, endTime) {
|
|||||||
const res = await api.post('pubMessage', params, 'Admin') // 模块名根据你实际配置来
|
const res = await api.post('pubMessage', params, 'Admin') // 模块名根据你实际配置来
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract 获取指定飞机组的飞行数据(按时间范围)
|
||||||
|
* @param {Array} idArr 飞机ID数组
|
||||||
|
* @param {Number} startTime 开始时间
|
||||||
|
* @param {Number} endTime 结束时间
|
||||||
|
* @returns 飞行数据列表
|
||||||
|
*/
|
||||||
|
export async function getFlyData (idArr, startTime, endTime) {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.append('idArr', idArr.join(',')) // 后端只收字符串,用逗号分隔
|
||||||
|
params.append('startTime', startTime)
|
||||||
|
params.append('endTime', endTime)
|
||||||
|
|
||||||
|
const res = await api.post('getFlyDataByIdArr', params, 'Plane')
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
303
src/views/layout/components/main/register/flyData.vue
Normal file
303
src/views/layout/components/main/register/flyData.vue
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
<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-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-area">
|
||||||
|
<div v-if="flyDataList.length" id="main" class="chart-container"></div>
|
||||||
|
<div v-else class="no-data-tip">暂无数据</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
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: '飞行时长'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
DateRangePicker
|
||||||
|
},
|
||||||
|
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.toISOString().slice(0, 10),
|
||||||
|
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 () {
|
||||||
|
this.loadFlyData()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
flyDataList (newVal) {
|
||||||
|
if (!newVal.length && this.myChart) {
|
||||||
|
this.myChart.dispose()
|
||||||
|
this.myChart = null
|
||||||
|
} else if (newVal.length) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dateRange: {
|
||||||
|
handler () {
|
||||||
|
this.loadFlyData()
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
source (newVal) {
|
||||||
|
if (newVal.length > 1) {
|
||||||
|
this.initChart()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
radioClass () {
|
||||||
|
this.initChart()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
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
|
||||||
|
console.log('飞行数据列表:', this.flyDataList)
|
||||||
|
} 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>
|
@ -6,6 +6,7 @@
|
|||||||
<el-button type="danger" icon="el-icon-delete" @click="deleteAir(countSelIdArr($refs.myTable.selection))">删除
|
<el-button type="danger" icon="el-icon-delete" @click="deleteAir(countSelIdArr($refs.myTable.selection))">删除
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="warning" icon="el-icon-edit" @click="toEditPage()">编辑</el-button>
|
<el-button type="warning" icon="el-icon-edit" @click="toEditPage()">编辑</el-button>
|
||||||
|
<el-button type="success" icon="el-icon-data-line" @click="toFlyDataPage(countSelIdArr($refs.myTable.selection))">飞行数据</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
<!-- 用户select选项 -->
|
<!-- 用户select选项 -->
|
||||||
<el-button-group class="m-b-20">
|
<el-button-group class="m-b-20">
|
||||||
@ -116,6 +117,17 @@ export default {
|
|||||||
this.$message.error('只能选择一条记录')
|
this.$message.error('只能选择一条记录')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @description: 跳转到飞机数据统计页面
|
||||||
|
*/
|
||||||
|
toFlyDataPage (selIdArr) {
|
||||||
|
if (selIdArr.length === 0) {
|
||||||
|
this.$message.error('请选择至少一架飞机')
|
||||||
|
} else {
|
||||||
|
this.$store.commit('app/setToFlyDataIdArr', selIdArr)// 传参
|
||||||
|
this.$router.push('/register/flyData/')
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @description: 删除飞机
|
* @description: 删除飞机
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user