}) | }) | ||||
} | } | ||||
/** | |||||
* @description: 获取飞行轨迹 | |||||
* @param {*} id 机场id | |||||
* @return {*} | |||||
*/ | |||||
export function missionLive(id) { | |||||
return request({ | |||||
url: `/mission/live/${id}`, | |||||
method: 'GET' | |||||
}) | |||||
} | |||||
<template> | <template> | ||||
<div v-if="isFailed"> | |||||
<div v-if="isFailed && useEmpty"> | |||||
<slot name="empty" /> | <slot name="empty" /> | ||||
</div> | </div> | ||||
<div v-else :id="getPlayerId" /> | <div v-else :id="getPlayerId" /> | ||||
options: { | options: { | ||||
type: Object, | type: Object, | ||||
default: () => {} | default: () => {} | ||||
}, | |||||
useEmpty: { | |||||
type: Boolean, | |||||
default: false | |||||
} | } | ||||
}, | }, | ||||
emits: ['timeUpdate', 'video-status'], | emits: ['timeUpdate', 'video-status'], | ||||
duration | duration | ||||
}) | }) | ||||
}, | }, | ||||
pause(player, e) { | |||||
const currentTime = player.getCurrentTime() | |||||
const duration = player.getDuration() | |||||
emit('video-status', { | |||||
status: 'pause', | |||||
currentTime, | |||||
duration | |||||
}) | |||||
}, | |||||
play(player, e) { | |||||
const currentTime = player.getCurrentTime() | |||||
const duration = player.getDuration() | |||||
emit('video-status', { | |||||
status: 'play', | |||||
currentTime, | |||||
duration | |||||
}) | |||||
}, | |||||
error(player, e) { | error(player, e) { | ||||
disposeVideo() | |||||
// disposeVideo() | |||||
isFailed.value = true | isFailed.value = true | ||||
} | } | ||||
}) | }) |
</div> | </div> | ||||
<div class="card__video"> | <div class="card__video"> | ||||
<div class="video__item"> | <div class="video__item"> | ||||
<VideoPlayer :options="getVideoOptions.inner"> | |||||
<VideoPlayer :options="getVideoOptions.inner" :use-empty="true"> | |||||
<template #empty> | <template #empty> | ||||
<div class="video__item--empty"> | <div class="video__item--empty"> | ||||
<img src="@/assets/images/lose-control.png"> | <img src="@/assets/images/lose-control.png"> | ||||
height: '100%', | height: '100%', | ||||
// source: row?.externalMonitorUrl, | // source: row?.externalMonitorUrl, | ||||
source: 'http://101.43.84.72:8080/live/34020000001320000001@34020000001320000001.flv', | source: 'http://101.43.84.72:8080/live/34020000001320000001@34020000001320000001.flv', | ||||
// source: row?.id === 2 ? live1 : video2, | |||||
// source: 'https://live.play.t-aaron.com/live/THSAl_hd.m3u8', | |||||
isLive: true | isLive: true | ||||
} | } | ||||
// outer: { | // outer: { |
<n-select v-model:value="videoForm.airportId" :options="airOptions" @update:value="handleAirportChange" /> | <n-select v-model:value="videoForm.airportId" :options="airOptions" @update:value="handleAirportChange" /> | ||||
</n-form-item> | </n-form-item> | ||||
<n-form-item label="回放选择:" path="taskId"> | <n-form-item label="回放选择:" path="taskId"> | ||||
<n-select v-model:value="videoForm.taskId" :options="taskOptions" @update:value="handleVideoChange" /> | |||||
<n-select v-model:value="videoForm.taskId" :options="taskOptions" clearable @update:value="handleVideoChange" /> | |||||
</n-form-item> | </n-form-item> | ||||
</n-form> | </n-form> | ||||
</p> | </p> | ||||
import { dataToSelect } from '@/utils/handleData.js' | import { dataToSelect } from '@/utils/handleData.js' | ||||
import { airportList } from '@/api/dashboard/index.js' | import { airportList } from '@/api/dashboard/index.js' | ||||
import { getTaskList } from '@/api/task/index.js' | import { getTaskList } from '@/api/task/index.js' | ||||
import { missionLive } from '@/api/dashboard/index.js' | |||||
import VideoPlayer from '@/components/VideoPlayer/index.vue' | import VideoPlayer from '@/components/VideoPlayer/index.vue' | ||||
import { reactive, computed, toRefs } from 'vue' | |||||
import { reactive, computed, watch, toRefs, nextTick } from 'vue' | |||||
export default { | export default { | ||||
name: 'TaskCard', | name: 'TaskCard', | ||||
setup() { | setup() { | ||||
const data = reactive({ | const data = reactive({ | ||||
videoForm: { | videoForm: { | ||||
airportId: 2, | |||||
taskId: '' | |||||
airportId: null, | |||||
taskId: null | |||||
}, | }, | ||||
airOptionsAll: [], | |||||
airOptions: [], | airOptions: [], | ||||
taskOptions: [], | taskOptions: [], | ||||
hasLive: false | |||||
hasLive: false, | |||||
airportIdBack: null, | |||||
airportUrl: { | |||||
origin: null, | |||||
analyse: null | |||||
}, | |||||
liveUrl: { | |||||
origin: null, | |||||
analyse: null | |||||
} | |||||
}) | }) | ||||
/** | /** | ||||
const loadAirport = (async function() { | const loadAirport = (async function() { | ||||
const res = await airportList({ page: 1, limit: 60 }) | const res = await airportList({ page: 1, limit: 60 }) | ||||
if (res.code === 0) { | if (res.code === 0) { | ||||
data.airOptionsAll = res.data | |||||
data.airOptions = dataToSelect(res.data, { label: 'name', value: 'id' }) | data.airOptions = dataToSelect(res.data, { label: 'name', value: 'id' }) | ||||
data.videoForm.airportId = res.data[0]?.id | data.videoForm.airportId = res.data[0]?.id | ||||
if (res.data[0]?.id) { | |||||
loadTaskOption(res.data[0].id) | |||||
} | |||||
handleAirportChange(data.videoForm.airportId) | |||||
} | } | ||||
})() | })() | ||||
} | } | ||||
function handleAirportChange(value) { | function handleAirportChange(value) { | ||||
if (value === data.airportIdBack) return | |||||
data.airportIdBack = value | |||||
missionLive(value) | |||||
.then(res => { | |||||
if (res.code === 0) { | |||||
data.airportUrl = { | |||||
origin: res.data?.playUrl, | |||||
analyse: res.data?.aiplayUrl | |||||
} | |||||
data.liveUrl = { ...data.airportUrl } | |||||
} | |||||
}) | |||||
loadTaskOption(value) | loadTaskOption(value) | ||||
} | } | ||||
function handleVideoChange(value) { | function handleVideoChange(value) { | ||||
// 1 | |||||
if (!value) { | |||||
data.liveUrl = { ...data.airportUrl } | |||||
} else { | |||||
data.liveUrl = { | |||||
origin: 'http://101.43.84.72:8080/live/34020000001320000001@34020000001320000001.flv', | |||||
analyse: 'http://101.43.84.72:8080/live/34020000001320000001@34020000001320000001.flv' | |||||
} | |||||
} | |||||
} | } | ||||
watch(() => data.liveUrl, | |||||
(value) => { | |||||
nextTick(() => { | |||||
data.hasLive = (value.origin && value.analyse) || false | |||||
}) | |||||
}) | |||||
const getVideoOptions = computed(() => { | const getVideoOptions = computed(() => { | ||||
const row = data.airOptionsAll.find((item) => { return item.id === data.videoForm.airportId }) | |||||
const { origin, analyse } = data.liveUrl | |||||
return { | return { | ||||
origin: { | origin: { | ||||
id: 'video-origin', | id: 'video-origin', | ||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
source: row?.internalMonitorUrl, | |||||
source: origin, | |||||
isLive: true | isLive: true | ||||
}, | }, | ||||
analyse: { | analyse: { | ||||
id: 'video-analyse', | id: 'video-analyse', | ||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
source: row?.externalMonitorUrl, | |||||
source: analyse, | |||||
isLive: true | isLive: true | ||||
} | } | ||||
} | } |
class="video_content" | class="video_content" | ||||
:class="videoShow == 'back' ? 'video_show' : 'video_hidden'" | :class="videoShow == 'back' ? 'video_show' : 'video_hidden'" | ||||
> | > | ||||
<VideoPlayer :ref="videoRef" :options="getVideoOptions" @time-update="handleOriginTime" @video-status="handleOriginStatus" /> | |||||
<VideoPlayer :ref="aiVideoRef" :options="getVideoAiOptions" @time-update="handleAiTime" @video-status="handleAiStatus" /> | |||||
<VideoPlayer ref="videoRef" :options="getVideoOptions" @time-update="handleOriginTime" @video-status="handleOriginStatus" /> | |||||
<VideoPlayer ref="aiVideoRef" :options="getVideoAiOptions" @time-update="handleAiTime" @video-status="handleAiStatus" /> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import { defineComponent, computed, reactive, toRefs, onMounted, ref, watch } from 'vue' | |||||
import { defineComponent, computed, reactive, toRefs, ref, watch } from 'vue' | |||||
import { Map, View, Feature } from 'ol' | import { Map, View, Feature } from 'ol' | ||||
import 'ol/ol.css' | import 'ol/ol.css' | ||||
import { Tile, Vector as VectorLayer } from 'ol/layer' | import { Tile, Vector as VectorLayer } from 'ol/layer' | ||||
import { XYZ, Vector as VectorSource } from 'ol/source' | import { XYZ, Vector as VectorSource } from 'ol/source' | ||||
import { transform, fromLonLat } from 'ol/proj' | import { transform, fromLonLat } from 'ol/proj' | ||||
import { getVectorContext } from 'ol/render' | import { getVectorContext } from 'ol/render' | ||||
import VectorContext from 'ol/render/VectorContext' | |||||
import { Draw } from 'ol/interaction' | |||||
import { toRaw } from '@vue/reactivity' | |||||
import * as control from 'ol/control' | import * as control from 'ol/control' | ||||
import { styleList } from '../tools/style.js' | import { styleList } from '../tools/style.js' | ||||
const data = reactive({ | const data = reactive({ | ||||
show: props.visible, | show: props.visible, | ||||
mapData: null, | mapData: null, | ||||
view: null, | |||||
mapView: null, | |||||
trackLayer: null, | trackLayer: null, | ||||
trackInfo: null, | trackInfo: null, | ||||
// 轨迹数据 | // 轨迹数据 | ||||
videoStatus: { | videoStatus: { | ||||
status: 'init', | status: 'init', | ||||
aiStatus: 'init' | aiStatus: 'init' | ||||
} | |||||
}, | |||||
canSkip: true | |||||
}) | }) | ||||
/* 获取抽屉的信息 */ | /* 获取抽屉的信息 */ | ||||
const aiVideoRef = ref(null) | const aiVideoRef = ref(null) | ||||
const handleOriginStatus = function(params) { | const handleOriginStatus = function(params) { | ||||
data.videoStatus.status = params.status | |||||
switch (params.status) { | |||||
case 'pause': | |||||
aiVideoRef.value?.pauseVideo() | |||||
break | |||||
case 'play': | |||||
aiVideoRef.value.playVideo() | |||||
break | |||||
case 'ready': | |||||
data.videoStatus.status = params.status | |||||
break | |||||
} | |||||
data.videoInfo = { | data.videoInfo = { | ||||
...params | ...params | ||||
} | } | ||||
} | } | ||||
const handleAiStatus = function(params) { | const handleAiStatus = function(params) { | ||||
data.videoStatus.aiStatus = params.status | |||||
switch (params.status) { | |||||
case 'pause': | |||||
videoRef.value?.pauseVideo() | |||||
break | |||||
case 'play': | |||||
videoRef.value.playVideo() | |||||
break | |||||
case 'ready': | |||||
data.videoStatus.aiStatus = params.status | |||||
break | |||||
} | |||||
data.aiVideoInfo = { | data.aiVideoInfo = { | ||||
...params | ...params | ||||
} | } | ||||
...params | ...params | ||||
} | } | ||||
const len = Math.abs(params.currentTime - data.aiVideoInfo.currentTime) | const len = Math.abs(params.currentTime - data.aiVideoInfo.currentTime) | ||||
if (params.status === 'skip' || len > 3) { | |||||
aiVideoRef.value.seekTime(params.currentTime) | |||||
if ((params.status === 'skip' || len > 3) && data.canSkip) { | |||||
aiVideoRef.value.seekTime(params.currentTime + 0.1) | |||||
data.canSkip = false | |||||
setTimeout(() => { | |||||
data.canSkip = true | |||||
}, 200) | |||||
} | } | ||||
} | } | ||||
const handleAiTime = function(params) { | const handleAiTime = function(params) { | ||||
...params | ...params | ||||
} | } | ||||
const len = Math.abs(params.currentTime - data.videoInfo.currentTime) | const len = Math.abs(params.currentTime - data.videoInfo.currentTime) | ||||
if (params.status === 'skip' || len > 3) { | |||||
videoRef.value.seekTime(params.currentTime) | |||||
if ((params.status === 'skip' || len > 3) && data.canSkip) { | |||||
videoRef.value.seekTime(params.currentTime + 0.1) | |||||
data.canSkip = false | |||||
setTimeout(() => { | |||||
data.canSkip = true | |||||
}, 200) | |||||
} | } | ||||
} | } | ||||
/* 播放视频 */ | /* 播放视频 */ | ||||
aiVideoRef.value.playVideo() | aiVideoRef.value.playVideo() | ||||
} | } | ||||
watch(() => data.videoStatus, (value) => { | |||||
if (value.status === 'ready' && value.aiStatus === 'ready') { | |||||
console.log(value, '===================') | |||||
watch([() => props.visible, () => data.videoStatus], ([visible, status]) => { | |||||
if (visible) { | |||||
// initTrack(formatTradeList(data.trackList), 'route') | |||||
} | |||||
if (status.status === 'ready' && status.aiStatus === 'ready') { | |||||
startAllVideo() | startAllVideo() | ||||
initMap() | |||||
initTrack(formatTradeList(data.trackList), 'route') | |||||
} | } | ||||
}) | |||||
onMounted(() => { | |||||
initMap() | |||||
// 加载航线 | |||||
initTrack(formatTradeList(data.trackList), 'route') | |||||
}) | |||||
}, { deep: true }) | |||||
/* 初始化地图 */ | /* 初始化地图 */ | ||||
const initMap = function() { | const initMap = function() { | ||||
}) | }) | ||||
] | ] | ||||
data.view = new View({ | |||||
data.mapView = new View({ | |||||
maxZoom: 18, | maxZoom: 18, | ||||
zoom: 5, | zoom: 5, | ||||
center: transform([118.837886, 32.057175], 'EPSG:4326', 'EPSG:3857') | center: transform([118.837886, 32.057175], 'EPSG:4326', 'EPSG:3857') | ||||
data.mapData = new Map({ | data.mapData = new Map({ | ||||
layers: layers, | layers: layers, | ||||
target: 'history-map', | target: 'history-map', | ||||
view: data.view, | |||||
view: data.mapView, | |||||
controls: control.defaults({ | controls: control.defaults({ | ||||
attribution: false, | attribution: false, | ||||
rotate: false, | rotate: false, | ||||
}) | }) | ||||
} | } | ||||
data.mapData.addLayer(vectorLayer) | data.mapData.addLayer(vectorLayer) | ||||
data.view.fit(route, { padding: [50, 50, 50, 50] }) | |||||
data.mapView.fit(route, { padding: [50, 50, 50, 50] }) | |||||
vectorLayer.on('postrender', moveFeature.bind()) | vectorLayer.on('postrender', moveFeature.bind()) | ||||
function moveFeature(event) { | function moveFeature(event) { |
</template> | </template> | ||||
<template #toolbar> | <template #toolbar> | ||||
<n-button @click="handleReported"> | <n-button @click="handleReported"> | ||||
{{ reportStatus ? '修改并生成报告': '提交并生成报告' }} | |||||
{{ isReported ? '修改并生成报告': '提交并生成报告' }} | |||||
</n-button> | </n-button> | ||||
</template> | </template> | ||||
</DataTable> | </DataTable> | ||||
import DataTable from '@/components/DataTable/index.vue' | import DataTable from '@/components/DataTable/index.vue' | ||||
import ConfirmModal from './components/ConfirmModal.vue' | import ConfirmModal from './components/ConfirmModal.vue' | ||||
import PositionDrawer from './components/PositionDrawer.vue' | import PositionDrawer from './components/PositionDrawer.vue' | ||||
import { getQuestionList, generateReport } from '@/api/task/index.js' | |||||
import { getQuestionList, generateReport, questionAnalyze } from '@/api/task/index.js' | |||||
import { unref, reactive, toRefs, provide } from 'vue' | import { unref, reactive, toRefs, provide } from 'vue' | ||||
export default { | export default { | ||||
name: 'QuestionPage', | name: 'QuestionPage', | ||||
if (res.code === 0) { | if (res.code === 0) { | ||||
const { confirm, notreviewed } = res.data | const { confirm, notreviewed } = res.data | ||||
if (notreviewed === 0) { | if (notreviewed === 0) { | ||||
generateReport(props.data.id) | |||||
await generateReport(props.data.id) | |||||
.then(res => { | |||||
if (res.code === 0) { | |||||
data.isReported = true | |||||
} | |||||
}) | |||||
} else { | } else { | ||||
$dialog.confirm( | $dialog.confirm( | ||||
{ | { | ||||
title: '提示', | title: '提示', | ||||
showIcon: false, | showIcon: false, | ||||
content: `已核实${confirm}条,未核实${notreviewed}条,是否提交并生成报告?`, | content: `已核实${confirm}条,未核实${notreviewed}条,是否提交并生成报告?`, | ||||
confirm: generateReport(props.data.id) | |||||
confirm: () => { | |||||
generateReport(props.data.id) | |||||
.then(res => { | |||||
if (res.code === 0) { | |||||
data.isReported = true | |||||
} | |||||
}) | |||||
} | |||||
} | } | ||||
) | ) | ||||
} | } |