【类 型】:feat
【原 因】:1飞行数据删除功能 2导航栏取消添加 对拼等子页面显示 放到列表页显示 3.通过选飞机 直接到对拼页面 【过 程】: 【影 响】:
This commit is contained in:
parent
241a418e59
commit
8d06b53183
@ -243,7 +243,7 @@ export default {
|
|||||||
if (this.enableSaveToFile && typeof this.getData === 'function') {
|
if (this.enableSaveToFile && typeof this.getData === 'function') {
|
||||||
this.map.addControl(new SaveToFileControl({
|
this.map.addControl(new SaveToFileControl({
|
||||||
Data: this.getData,
|
Data: this.getData,
|
||||||
filename: `export_${Date.now()}.json`,
|
filename: `飞行数据_${Date.now()}.json`,
|
||||||
title: '保存当前数据',
|
title: '保存当前数据',
|
||||||
icon: '💾'
|
icon: '💾'
|
||||||
}), 'top-left')
|
}), 'top-left')
|
||||||
@ -984,7 +984,7 @@ export default {
|
|||||||
* @param {obj} lonLat {lon:lon,lat:lat} 经纬度
|
* @param {obj} lonLat {lon:lon,lat:lat} 经纬度
|
||||||
* @param {Number} zoom 地图放大率
|
* @param {Number} zoom 地图放大率
|
||||||
*/
|
*/
|
||||||
goto (lonLat, zoom = 18) {
|
goto (lonLat, zoom = 12) {
|
||||||
this.map.flyTo({
|
this.map.flyTo({
|
||||||
center: lonLat,
|
center: lonLat,
|
||||||
zoom: zoom,
|
zoom: zoom,
|
||||||
|
@ -87,7 +87,8 @@ const routes = [
|
|||||||
title: '机型添加',
|
title: '机型添加',
|
||||||
icon: 'el-icon-plus',
|
icon: 'el-icon-plus',
|
||||||
roles: ['admin', 'editor'],
|
roles: ['admin', 'editor'],
|
||||||
tapName: 'plane'
|
tapName: 'plane',
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -131,7 +132,8 @@ const routes = [
|
|||||||
title: '飞机添加',
|
title: '飞机添加',
|
||||||
icon: 'el-icon-plus',
|
icon: 'el-icon-plus',
|
||||||
roles: ['admin', 'editor'],
|
roles: ['admin', 'editor'],
|
||||||
tapName: 'plane'
|
tapName: 'plane',
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -146,13 +148,14 @@ const routes = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/register/crosFrequency',
|
path: '/register/crosFrequency/:shop_id/:plane_id',
|
||||||
component: () => import('@/views/layout/components/main/register/crosFrequency'),
|
component: () => import('@/views/layout/components/main/register/crosFrequency'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '飞机对频',
|
title: '飞机对频',
|
||||||
icon: 'el-icon-link',
|
icon: 'el-icon-link',
|
||||||
roles: ['admin', 'editor'],
|
roles: ['admin', 'editor'],
|
||||||
tapName: 'plane'
|
tapName: 'plane',
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -273,7 +276,8 @@ const routes = [
|
|||||||
title: '站点添加',
|
title: '站点添加',
|
||||||
icon: 'el-icon-plus',
|
icon: 'el-icon-plus',
|
||||||
roles: ['admin', 'editor'],
|
roles: ['admin', 'editor'],
|
||||||
tapName: 'plane'
|
tapName: 'plane',
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -383,7 +387,8 @@ const routes = [
|
|||||||
title: '账户添加',
|
title: '账户添加',
|
||||||
icon: 'iconfont icon-xinzengguanliyuan',
|
icon: 'iconfont icon-xinzengguanliyuan',
|
||||||
roles: ['admin', 'editor'],
|
roles: ['admin', 'editor'],
|
||||||
tapName: 'admin'
|
tapName: 'admin',
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -461,7 +466,8 @@ const routes = [
|
|||||||
title: 'SPU添加',
|
title: 'SPU添加',
|
||||||
icon: 'iconfont icon-huoquchanpin',
|
icon: 'iconfont icon-huoquchanpin',
|
||||||
roles: ['admin', 'editor'],
|
roles: ['admin', 'editor'],
|
||||||
tapName: 'admin'
|
tapName: 'admin',
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -492,7 +498,8 @@ const routes = [
|
|||||||
title: 'SKU添加',
|
title: 'SKU添加',
|
||||||
icon: 'iconfont icon-sku1',
|
icon: 'iconfont icon-sku1',
|
||||||
roles: ['admin', 'editor'],
|
roles: ['admin', 'editor'],
|
||||||
tapName: 'admin'
|
tapName: 'admin',
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1 +1 @@
|
|||||||
@import 'https://at.alicdn.com/t/c/font_3703467_17bw22w7wt3g.css'; //iconfont阿里巴巴
|
@import 'https://at.alicdn.com/t/c/font_3703467_6zigre3tbm3.css'; //iconfont阿里巴巴
|
@ -178,6 +178,23 @@ export async function getFlyData (idArr, startTime, endTime) {
|
|||||||
const res = await api.post('getFlyDataByIdArr', params, 'Plane')
|
const res = await api.post('getFlyDataByIdArr', params, 'Plane')
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract 删除指定的飞行数据记录
|
||||||
|
* @param {Array} idArr 飞行数据记录ID数组
|
||||||
|
* @returns 删除结果
|
||||||
|
*/
|
||||||
|
export async function deleteFlyData (idArr) {
|
||||||
|
if (!Array.isArray(idArr) || idArr.length === 0) {
|
||||||
|
throw new Error('参数错误,idArr 应为非空数组')
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.append('idArr', idArr.join(',')) // 逗号分隔传给后端
|
||||||
|
|
||||||
|
const res = await api.post('deleteFlyDataByIdArr', params, 'Plane')
|
||||||
|
return res
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @abstract 上传保存飞机飞行数据(解锁至加锁 即飞行结束触发)
|
* @abstract 上传保存飞机飞行数据(解锁至加锁 即飞行结束触发)
|
||||||
* @param {Object} data 包含 id, start_time, end_time, gps_path, distance, power_used
|
* @param {Object} data 包含 id, start_time, end_time, gps_path, distance, power_used
|
||||||
|
@ -160,9 +160,7 @@ export function formatPrice (value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 等待 Mapbox 地图的画布容器(Canvas Container)准备就绪。
|
* @description:等待 Mapbox 地图的画布容器(Canvas Container)准备就绪。因为 Mapbox 初始化是异步的,某些操作需要等待画布容器加载完成才能执行。
|
||||||
* 因为 Mapbox 初始化是异步的,某些操作需要等待画布容器加载完成才能执行。
|
|
||||||
*
|
|
||||||
* @param {Object} map - Mapbox GL JS 地图实例对象
|
* @param {Object} map - Mapbox GL JS 地图实例对象
|
||||||
* @param {number} maxRetry - 最大重试次数,防止无限等待,默认5次
|
* @param {number} maxRetry - 最大重试次数,防止无限等待,默认5次
|
||||||
* @returns {Promise<HTMLElement>} 返回一个 Promise,成功时返回画布容器元素,失败时抛出错误
|
* @returns {Promise<HTMLElement>} 返回一个 Promise,成功时返回画布容器元素,失败时抛出错误
|
||||||
@ -192,3 +190,32 @@ export function waitForMapCanvasReady (map, maxRetry = 5) {
|
|||||||
check(maxRetry)
|
check(maxRetry)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 把对象保存为 JSON 文件并触发下载
|
||||||
|
* @param {*} data - 要保存的对象数据
|
||||||
|
* @param {string} filename - 下载的文件名,默认为 data_时间
|
||||||
|
*/
|
||||||
|
export function saveObjectToFile (data, filename = `data_${Date.now()}.json`) {
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
alert('无效的数据对象')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将对象格式化为 JSON 字符串
|
||||||
|
const content = JSON.stringify(data, null, 2)
|
||||||
|
|
||||||
|
// 创建 Blob 对象
|
||||||
|
const blob = new Blob([content], { type: 'application/json' })
|
||||||
|
|
||||||
|
// 创建一个临时下载链接
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = URL.createObjectURL(blob)
|
||||||
|
link.download = filename
|
||||||
|
|
||||||
|
// 模拟点击以触发下载
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
// 释放资源
|
||||||
|
URL.revokeObjectURL(link.href)
|
||||||
|
}
|
||||||
|
@ -12,31 +12,27 @@
|
|||||||
<el-main class="border p-20">
|
<el-main class="border p-20">
|
||||||
<el-form label-width="120px">
|
<el-form label-width="120px">
|
||||||
<el-form-item label="物理ID">
|
<el-form-item label="物理ID">
|
||||||
<el-tag :type="macAdd === null ? 'danger' : ''">{{ macAdd === null ? 'NoThing' : macAdd }}</el-tag>
|
<el-tag :type="macAdd === null ? 'danger' : ''">
|
||||||
|
{{ macAdd === null ? 'NoThing' : macAdd }}
|
||||||
|
</el-tag>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="商铺分配">
|
<el-form-item label="商铺分配">
|
||||||
<el-select v-model="shop_id" filterable placeholder="请选择" @blur="air_id = ''; disabled = true"
|
<span>{{ getShopName(shop_id) }}</span>
|
||||||
:disabled="macAdd === null ? true : false">
|
|
||||||
<el-option v-for="item in shopList" :key="item.id" :label="item.name" :value="item.shop_id">
|
|
||||||
<el-avatar class="vm" shape="square" :size="25"
|
|
||||||
:src="$store.state.settings.host + '/Data/UploadFiles/logo/' + item.logo">
|
|
||||||
</el-avatar>
|
|
||||||
<span class="rspan">{{ item.name }}</span>
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="飞机列表">
|
|
||||||
<el-radio-group v-if="airList.length != 0" v-model="air_id" @change="disabled = false;">
|
<el-form-item label="飞机信息">
|
||||||
<el-radio class="m-b-10 m-l-0" v-for="item in airList" :key="item.id" :label="item.id" border>
|
<span>{{ getAirName(plane_id) }}</span>
|
||||||
{{ item.name }}
|
|
||||||
{{ item.macadd }}
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
<span v-else>No data</span>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button icon="iconfont icon-jiekou m-r-5" :type="macAdd === null ? 'info' : 'primary'"
|
<el-button
|
||||||
@click="onSubmit" :disabled="macAdd === null ? 'disabled' : false">对频
|
icon="iconfont icon-jiekou m-r-5"
|
||||||
|
:type="macAdd === null ? 'info' : 'primary'"
|
||||||
|
@click="onSubmit"
|
||||||
|
:disabled="macAdd === null"
|
||||||
|
>
|
||||||
|
对频
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -44,7 +40,6 @@
|
|||||||
</el-container>
|
</el-container>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -56,54 +51,63 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
shop_id: null,
|
shop_id: null,
|
||||||
air_id: null
|
plane_id: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// 获取商铺列表
|
|
||||||
shopList () {
|
shopList () {
|
||||||
return this.$store.state.shopList
|
return this.$store.state.shopList
|
||||||
},
|
},
|
||||||
// 过滤掉 不对应客户的 飞机列表
|
|
||||||
airList () {
|
airList () {
|
||||||
return this.$store.state.airList.filter((item) => item.shop_id === this.shop_id)
|
return this.$store.state.airList
|
||||||
},
|
},
|
||||||
// 对频信息 物理地址
|
|
||||||
macAdd () {
|
macAdd () {
|
||||||
return this.$store.state.crosFrequency
|
return this.$store.state.crosFrequency
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created () {
|
||||||
|
this.shop_id = this.$route.params.shop_id
|
||||||
|
this.plane_id = this.$route.params.plane_id
|
||||||
|
console.log('shop_id:', this.shop_id, 'plane_id:', this.plane_id)
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* @Description: 检查飞机发布过来的 mac地址 是否已经存在
|
* 获取商铺名称
|
||||||
* @Return: bool
|
|
||||||
*/
|
*/
|
||||||
isMacAdd () {
|
getShopName (id) {
|
||||||
let b = false
|
const shop = this.shopList.find(item => item.shop_id === id)
|
||||||
this.$store.state.airList.map((item) => {
|
return shop ? shop.name : '未知商铺'
|
||||||
if (item.macadd === this.macAdd) {
|
|
||||||
b = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return b
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @description: 提交表单
|
* 获取飞机名称和 mac 地址
|
||||||
|
*/
|
||||||
|
getAirName (id) {
|
||||||
|
const air = this.airList.find(item => item.id === id)
|
||||||
|
return air ? `${air.name}` : '未知飞机'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 检查是否已有同名 mac 地址
|
||||||
|
*/
|
||||||
|
isMacAdd () {
|
||||||
|
return this.airList.some(item => item.macadd === this.macAdd)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 提交表单
|
||||||
*/
|
*/
|
||||||
onSubmit () {
|
onSubmit () {
|
||||||
const params = { macAdd: this.macAdd, id: this.air_id }
|
const params = { macAdd: this.macAdd, id: this.plane_id }
|
||||||
if (this.isMacAdd()) {
|
if (this.isMacAdd()) {
|
||||||
this.$confirm('已经有同名mac地址,请谨慎操作?', '提示', {
|
this.$confirm('已经有同名mac地址,请谨慎操作?', '提示', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
apiCrosFrequency(params)// 对频
|
apiCrosFrequency(params)
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.$message.info('取消对频')
|
this.$message.info('取消对频')
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
apiCrosFrequency(params)// 对频
|
apiCrosFrequency(params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,6 +118,6 @@ export default {
|
|||||||
.rspan {
|
.rspan {
|
||||||
float: right;
|
float: right;
|
||||||
color: #8492a6;
|
color: #8492a6;
|
||||||
font-size: 13px
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
364
src/views/layout/components/main/register/flyData copy.vue
Normal file
364
src/views/layout/components/main/register/flyData copy.vue
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
<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-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" @map-ready="onMapReady" :enableSaveToFile="true" :getData="() => flyDataList"/>
|
||||||
|
</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 Math.round((item.end_time - item.start_time) / 60) // 秒转分钟
|
||||||
|
},
|
||||||
|
飞行距离: (item) => Number(item.distance || 0),
|
||||||
|
消耗电量: (item) => Number(item.power_used || 0),
|
||||||
|
作业架次: () => 1 // 作业架次就是统计条数,固定1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.radioClass === '飞行轨迹') return []// 不处理轨迹数据
|
||||||
|
|
||||||
|
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.onMapReady()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
boxShow (val) {
|
||||||
|
if (!val) {
|
||||||
|
this.onMapReady()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dateRange: {
|
||||||
|
handler () {
|
||||||
|
this.loadFlyData()
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
source (newVal) {
|
||||||
|
if (Array.isArray(newVal) && newVal.length > 1) {
|
||||||
|
this.initChart()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
radioClass (val) {
|
||||||
|
if (val === '飞行轨迹') {
|
||||||
|
this.boxShow = false
|
||||||
|
} else {
|
||||||
|
this.boxShow = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initChart()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 地图组件回调地图加载完成后执行
|
||||||
|
onMapReady () {
|
||||||
|
this.drawAllPathsOnMap()
|
||||||
|
},
|
||||||
|
// 地图轨迹绘制
|
||||||
|
drawAllPathsOnMap () {
|
||||||
|
if (!this.$refs.mapbox) return
|
||||||
|
|
||||||
|
// 清理旧轨迹
|
||||||
|
this.$refs.mapbox.clearMapElements(['path'], ['path'])
|
||||||
|
|
||||||
|
// 遍历所有飞行数据,绘制轨迹
|
||||||
|
this.flyDataList.forEach(item => {
|
||||||
|
if (!item.gps_path) return
|
||||||
|
try {
|
||||||
|
const pathArray = JSON.parse(item.gps_path)
|
||||||
|
if (Array.isArray(pathArray) && pathArray.length > 0) {
|
||||||
|
this.$refs.mapbox.createPathWithArray(pathArray)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('gps_path 解析失败', item.gps_path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 跳转到第一个轨迹的起点(经纬度)
|
||||||
|
if (this.flyDataList.length > 0 && this.flyDataList[0].gps_path) {
|
||||||
|
try {
|
||||||
|
const firstPath = JSON.parse(this.flyDataList[0].gps_path)
|
||||||
|
if (Array.isArray(firstPath) && firstPath.length > 0) {
|
||||||
|
const [lon, lat] = firstPath[0]
|
||||||
|
this.$refs.mapbox.goto({ lon, lat })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 解析失败不跳转
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 获取飞行数据
|
||||||
|
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 === '飞行距离' ? '米' : this.radioClass === '消耗电量' ? '毫安' : (this.radioClass === '作业架次' ? '次' : '')
|
||||||
|
},
|
||||||
|
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>
|
@ -4,13 +4,14 @@
|
|||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<!-- 日期选择 -->
|
<!-- 日期选择 -->
|
||||||
<span class="m-r-20 m-b-20" style="position: relative;">
|
<span class="m-r-20 m-b-20" style="position: relative;">
|
||||||
<DateRangePicker v-model="dateRange" style="top:-10px"/>
|
<DateRangePicker v-model="dateRange" style="top:-10px" />
|
||||||
</span>
|
</span>
|
||||||
<!-- 组合按钮 -->
|
<!-- 组合按钮 -->
|
||||||
<el-button-group class="m-r-20 m-b-20">
|
<el-button-group class="m-r-20 m-b-20">
|
||||||
<el-button type="primary" icon="el-icon-plus" @click="$router.replace('/site/add')">详情保存</el-button>
|
<el-button type="primary" icon="iconfont icon-save-3-fill" @click="saveDataTOFile()">
|
||||||
<el-button type="danger" icon="el-icon-delete"
|
<font class="m-l-5">详情保存</font>
|
||||||
@click="deleteSite(countSelIdArr($refs.myTable.selection))">删除</el-button>
|
</el-button>
|
||||||
|
<el-button type="danger" icon="el-icon-delete" @click="delFlyData()">删除</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
<!-- 项目选择 -->
|
<!-- 项目选择 -->
|
||||||
<el-radio-group v-model="radioClass" class="m-r-20 m-b-20">
|
<el-radio-group v-model="radioClass" class="m-r-20 m-b-20">
|
||||||
@ -39,8 +40,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
import MapBox from '@/components/MapBox'
|
import MapBox from '@/components/MapBox'
|
||||||
import { getFlyData } from '@/utils/api/table'
|
import { getFlyData, deleteFlyData } from '@/utils/api/table'
|
||||||
import DateRangePicker from '@/components/DateRangePicker'
|
import DateRangePicker from '@/components/DateRangePicker'
|
||||||
|
import { saveObjectToFile } from '@/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FlyData',
|
name: 'FlyData',
|
||||||
@ -220,6 +222,41 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 删除飞行数据
|
||||||
|
async delFlyData () {
|
||||||
|
if (this.flyDataList.length === 0) {
|
||||||
|
this.$message.warning('暂无数据可删除')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const idArr = this.flyDataList.map(item => item.id)
|
||||||
|
|
||||||
|
this.$confirm('是否确认删除所有飞行数据?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
const res = await deleteFlyData(idArr)
|
||||||
|
|
||||||
|
if (res.data.status === 1) {
|
||||||
|
this.$message.success(`飞行数据已删除: ${res.data.total} 条`)
|
||||||
|
this.flyDataList = [] // 前端清空
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
this.flyDataList = []
|
||||||
|
}).catch(() => {
|
||||||
|
this.$message.info('已取消删除')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 保存数据到文件
|
||||||
|
saveDataTOFile () {
|
||||||
|
if (this.flyDataList.length === 0) {
|
||||||
|
this.$message.warning('暂无数据可保存')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const fileName = `飞行数据_${new Date().toISOString().slice(0, 10)}.json`
|
||||||
|
saveObjectToFile(this.flyDataList, fileName)
|
||||||
|
},
|
||||||
// 地图组件回调地图加载完成后 执行
|
// 地图组件回调地图加载完成后 执行
|
||||||
onMapReady () {
|
onMapReady () {
|
||||||
this.drawAllHistoricalPaths()
|
this.drawAllHistoricalPaths()
|
||||||
|
@ -46,15 +46,16 @@
|
|||||||
{{ scope.row.apply_time | parseTime('{y}-{m}-{d}') }}
|
{{ scope.row.apply_time | parseTime('{y}-{m}-{d}') }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="controler" label="操作" width="380" min-width="380">
|
<el-table-column prop="controler" label="操作" width="460" min-width="460">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button type="primary" class="iconfont icon-youxishoubing"
|
<el-button type="primary" class="iconfont icon-youxishoubing"
|
||||||
@click="$router.replace(`/planes/index/${scope.row.id}/${scope.row.name}`)"><span
|
@click="$router.replace(`/planes/index/${scope.row.id}/${scope.row.name}`)"><span
|
||||||
class="m-l-5">操作</span></el-button>
|
class="m-l-5">控制</span></el-button>
|
||||||
<el-button type="warning" icon="el-icon-edit"
|
<el-button type="warning" icon="el-icon-edit"
|
||||||
@click="$router.replace(`/register/edit/${scope.row.id}`)">编辑</el-button>
|
@click="$router.replace(`/register/edit/${scope.row.id}`)">编辑</el-button>
|
||||||
<el-button type="danger" icon="el-icon-delete" @click="deleteAir([scope.row.id])">删除</el-button>
|
<el-button type="danger" icon="el-icon-delete" @click="deleteAir([scope.row.id])">删除</el-button>
|
||||||
|
<el-button type="success" icon="el-icon-link" @click="crosFrequencyToPage(form.shop_id,scope.row.id)">对频</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -149,6 +150,12 @@ export default {
|
|||||||
*/
|
*/
|
||||||
deleteAir (idArr) {
|
deleteAir (idArr) {
|
||||||
this.$store.dispatch('fetchDelAir', idArr)
|
this.$store.dispatch('fetchDelAir', idArr)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @description: 跳转到对频页面
|
||||||
|
*/
|
||||||
|
crosFrequencyToPage (shopId, planeId) {
|
||||||
|
this.$router.push(`/register/crosFrequency/${shopId}/${planeId}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
45
src/views/layout/components/main/video/index.vue
Normal file
45
src/views/layout/components/main/video/index.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<video
|
||||||
|
ref="video"
|
||||||
|
autoplay
|
||||||
|
controls
|
||||||
|
playsinline
|
||||||
|
muted
|
||||||
|
width="640"
|
||||||
|
height="360"
|
||||||
|
></video>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
url: 'http://82.156.122.87:80/rtc/v1/whep/?app=live&stream=083AF27BB2D0',
|
||||||
|
sdk: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.startPlay()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async startPlay () {
|
||||||
|
if (this.sdk) {
|
||||||
|
this.sdk.close()
|
||||||
|
this.sdk = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sdk = new window.SrsRtcWhipWhepAsync()
|
||||||
|
this.$refs.video.srcObject = this.sdk.stream
|
||||||
|
|
||||||
|
try {
|
||||||
|
const session = await this.sdk.play(this.url)
|
||||||
|
console.log('播放成功,session:', session)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('播放失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user