浏览代码

3703 3695 3692

tags/v1.2.0
lixin 1年前
父节点
当前提交
f260db24f4
共有 7 个文件被更改,包括 596 次插入21 次删除
  1. +7
    -5
      src/layout/components/Header/index.vue
  2. +4
    -4
      src/views/dashboard/components/Underlay.vue
  3. +22
    -2
      src/views/dashboard/components/WarningDrawer.vue
  4. +20
    -5
      src/views/early-warning/fire/components/FireModal.vue
  5. +520
    -0
      src/views/early-warning/fire/components/FlightDrawer.vue
  6. +22
    -5
      src/views/early-warning/fire/index.vue
  7. +1
    -0
      src/views/task-manage/all-task/components/DemandDrawer.vue

+ 7
- 5
src/layout/components/Header/index.vue 查看文件

@@ -11,7 +11,7 @@
<ul ref="scrollRef">
<li v-for="(item,index) in list" :key="index" @click="handleClick(item)">
<n-ellipsis :tooltip="false">
{{ `${item.airportName}正在执行${item.name}, 飞行中` }}
{{ `${item.airportName}正在执行紧急任务,${item.airportStatus}` }}
</n-ellipsis>
</li>
</ul>
@@ -105,7 +105,9 @@ export default defineComponent({
const queryList = async() => {
const res = await emergencyList()
if (res.code === 0) {
data.list = res.data
if (res.data) {
data.list = res.data
}
}
}

@@ -148,12 +150,12 @@ export default defineComponent({
display: flex;
align-items: center;
.header__scroll{
width: 450px;
width: 400px;
margin-right: 50px;
position: relative;
}
.scroll__content{
width: 450px;
width: 400px;
height: 30px;
overflow: hidden;
position: absolute;
@@ -164,7 +166,7 @@ export default defineComponent({
transition: all .5s;
}
li{
width: 450px;
width: 400px;
line-height: 30px;
color: rgba(255,255,255,1);
cursor: pointer;

+ 4
- 4
src/views/dashboard/components/Underlay.vue 查看文件

@@ -155,14 +155,14 @@ export default {
// 要素设置id
feature.setId(airport.id)
// 添加矢量图层
data.airLayer = new VectorLayer({
const airLayer = new VectorLayer({
source: new VectorSource({
features: [feature]
}),
visible: true
})
data.map.addLayer(data.airLayer)
data.vectorLayers.push(data.airLayer)
data.map.addLayer(airLayer)
data.vectorLayers.push(airLayer)
}
}
}
@@ -214,7 +214,7 @@ export default {
})
// 添加图层
data.map.addLayer(warningLayer)
data.vectorLayers.push(data.airLayer)
data.vectorLayers.push(warningLayer)
}
}


+ 22
- 2
src/views/dashboard/components/WarningDrawer.vue 查看文件

@@ -111,13 +111,16 @@ export default defineComponent({
operate: '悬停',
control: '手动控制',
uavFireDis: null,
// 表格定时器
chartTimer: null,
// 直播定时器
liveTimer: null,
videoInfo: {
url: null,
status: 'init'
},
uavInfo: []
uavInfo: [],
failTimer: null
})

/* 获取抽屉的信息 */
@@ -140,7 +143,7 @@ export default defineComponent({
status: 'init'
}
emergencyVideo.value?.disposeVideo()
// inspectionStore.resetList()
inspectionStore.resetList()
// 关闭定时器
clearTimer()
uavChart.value.clearChart()
@@ -206,6 +209,19 @@ export default defineComponent({
}
}
}, 2000)

// 3分钟后判断是否是否起飞成功
data.failTimer = setInterval(async() => {
const res = await getTaskDetail(value.id)
if (res.code === 0) {
if (res.data.playUrl) {
clearInterval(data.liveTimer)
data.liveTimer = null
} else {
handleDrawerColse()
}
}
}, 3 * 60 * 1000)
}
})

@@ -320,6 +336,10 @@ export default defineComponent({
clearInterval(data.liveTimer)
data.liveTimer = null
}
if (data.failTimer) {
clearInterval(data.failTimer)
data.failTimer = null
}
}

function handleVideoStatus(status) {

+ 20
- 5
src/views/early-warning/fire/components/FireModal.vue 查看文件

@@ -27,7 +27,7 @@
<p v-for="(item,index) in fireDetail.warningRecordVOList" :key="index" class="content">
<span>{{ `${item.recordStartTime}` }}</span>
<span style="margin-left: 40px">{{ `${item.airportName}执行任务,` }}</span>
<span>查看详情</span>
<span class="view-detail" @click="viewDetail(item.missionId)">查看详情</span>
</p>
</div>
<p class="title">处理记录</p>
@@ -62,7 +62,8 @@ export default defineComponent({
},
emits: {
'update:visible': null,
'reload': null
'reload': null,
'flight': null
},
setup(props, { emit }) {
const data = reactive({
@@ -86,6 +87,14 @@ export default defineComponent({
emit('update:visible', false)
}

/**
* 任务id查询任务
* @param {} missionId
*/
const viewDetail = (missionId) => {
emit('flight', missionId)
}

onMounted(async() => {
const res = await fireDetail(props.data.id)
if (res.code === 0) {
@@ -96,9 +105,9 @@ export default defineComponent({
data.fireDetail.discoveryWay = item.label
}
})
// 过滤 飞行失败的任务
// 只展示 飞行完成的任务
data.fireDetail.warningRecordVOList.map((item) => {
if (item.status !== 3) {
if (item.status === 4) {
data.warningList.push(item)
}
})
@@ -114,7 +123,8 @@ export default defineComponent({
return {
...toRefs(data),
getModalOptions,
handleClose
handleClose,
viewDetail
}
}
})
@@ -142,4 +152,9 @@ export default defineComponent({
min-height: 32px;
font-size: 16px;
}

.view-detail{
cursor: pointer;
color: rgba(24, 144, 255, 0.8);
}
</style>

+ 520
- 0
src/views/early-warning/fire/components/FlightDrawer.vue 查看文件

@@ -0,0 +1,520 @@
<template>
<n-drawer v-bind="getDrawerOptions" @update:show="handleDrawerColse">
<n-drawer-content closable title="轨迹回放">
<div class="main_container">
<div id="history-map" />
<!-- 视频播放 -->
<div class="videobox">
<!-- 视频开关 -->
<div
class="flagbox"
:class="videoShow == 'back' ? 'flagbox_open' : 'flaxbox_back'"
>
<span class="videotitle">巡检视频</span>
<img
v-if="videoShow == 'back'"
src="@/assets/img/back.png"
alt=""
class="closedetail imageicon"
@click="showVideo('open')"
>
<img
v-else
src="@/assets/img/open.png"
alt=""
class="closedetail imageicon"
@click="showVideo('back')"
>
</div>
<!-- 直播视频 -->
<div
class="video_content"
:class="videoShow == 'back' ? 'video_show' : 'video_hidden'"
>
<VideoPlayer id="demand-video" ref="originRef" @video-status="handleVideoStatus" />
</div>
</div>
</div>
</n-drawer-content>
</n-drawer>
</template>

<script>
import { defineComponent, computed, reactive, toRefs, ref, watch, nextTick } from 'vue'
import { Map, View, Feature } from 'ol'
import 'ol/ol.css'
import { Tile, Vector as VectorLayer } from 'ol/layer'
import { Style, Icon, Text, Fill } from 'ol/style'
import LineString from 'ol/geom/LineString'
import Point from 'ol/geom/Point'
import { XYZ, Vector as VectorSource } from 'ol/source'
import { transform, fromLonLat } from 'ol/proj'
import { getVectorContext } from 'ol/render'
import * as control from 'ol/control'
import { styleList } from '@/utils/style.js'
import { getTrackList, emergencyRecord } from '@/api/task/index.js'
import { airportList, getWarningInfo } from '@/api/dashboard/index.js'
import uav_icon from '@/assets/images/airport.png'
import warningIcon from '@/assets/gis/images/fire.png'
// 视频组件
import VideoPlayer from '@/components/VideoPlayer/index.vue'
import gcj02Mecator from '@/utils/map/gcj02Mecator.js'
import { gcj02towgs84 } from '@/utils/coordinate-util.js'
export default defineComponent({
name: 'FlightDrawer',
components: { VideoPlayer },
props: {
/* 可见 */
visible: {
type: Boolean,
default: false
},
/* 选中的数据 */
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null
},
setup(props, { emit }) {
const originRef = ref(null)

const data = reactive({
show: props.visible,
mapData: null,
mapView: null,
liveTrackLayer: null,
lineTrackLayer: null,
trackLayer: null,
trackInfo: null,
socket: null,
// 轨迹数据 格式
trackList: [
{ lng: '118.765591', lat: '32.050993' },
{ lng: '118.665591', lat: '32.150993' },
{ lng: '118.565591', lat: '32.070993' },
{ lng: '118.465591', lat: '32.020993' },
{ lng: '118.365591', lat: '32.110993' },
{ lng: '118.265591', lat: '32.080993' },
{ lng: '118.165591', lat: '32.150993' },
{ lng: '118.765591', lat: '32.150993' },
{ lng: '118.66551', lat: '32.050993' },
{ lng: '118.365591', lat: '32.060993' },
{ lng: '118.265591', lat: '32.180993' },
{ lng: '118.165591', lat: '32.050993' }
],
videoShow: 'back', // 视频展示开关
videoInfo: {
url: null,
currentTime: 0,
duration: 100,
isLive: false,
status: 'init'
},
vectorLayers: []
})

/* 获取抽屉的信息 */
const getDrawerOptions = computed(() => {
return {
show: props.visible,
width: '100%',
placement: 'right'
}
})

function initOriginPlayer() {
data.videoInfo.status = 'init'
const origin = {
width: '100%',
height: '446px',
// 播放的是原视频而非 AI分析后的视频
source: props.data.videoUrl
}
originRef.value?.init(origin)
setTimeout(() => {
if (data.videoInfo.status === 'init') {
initOriginPlayer()
}
}, 30000)
}

/* 获取轨迹数据 */
const getTrackData = async function() {
const res = await getTrackList(props.data.id)
const trackList = res.data
data.trackList = trackList
data.lineTrajectoryList = trackList
return Promise.resolve({
trackList
})
}

// 绘制机场图层
const drawAirport = async(id) => {
const res = await airportList({ page: 1, limit: 100 })
if (res.code === 0) {
if (res.data.length > 0) {
for (let i = 0; i < res.data.length; i++) {
if (id === res.data[i].id) {
const airport = res.data[i]
const lngLat = gcj02towgs84(
parseFloat(airport.longitude),
parseFloat(airport.latitude)
)
const feature = new Feature({
geometry: new Point(fromLonLat(lngLat))
})
// 要素设置样式
feature.setStyle(
new Style({
image: new Icon({
src: uav_icon
}),
text: new Text({
// 文字内容
text: airport.name,
// 位置
textAlign: 'center',
// 基准线
textBaseline: 'top',
offsetY: 30,
// 文字样式
font: 'normal 20px Microsoft YaHei',
backgroundFill: new Fill({
color: '#1890FF'
}),
padding: [3, 6, 3, 6],
// 文字颜色
fill: new Fill({
color: '#fff'
})
})
})
)
// 要素设置id
feature.setId(airport.id)
// 添加矢量图层
const airLayer = new VectorLayer({
source: new VectorSource({
features: [feature]
}),
visible: true
})
data.mapData.addLayer(airLayer)
data.vectorLayers.push(airLayer)
}
}
}
}
}

const getEmergencyRecord = async(id) => {
const res = await emergencyRecord({
emergencyMissionId: id
})
if (res.code === 0) {
drawWarning(res.data?.warningId)
}
}

// 绘制预警图层
const drawWarning = async(id) => {
const res = await getWarningInfo(id)
if (res.code === 0) {
data.warningLngLat =
[parseFloat(res.data?.lng),
parseFloat(res.data?.lat)]
const feature = new Feature({
geometry: new Point(fromLonLat(data.warningLngLat))
})
// 要素设置样式
feature.setStyle(
new Style({
// 图标
image: new Icon({
src: warningIcon,
crossOrigin: 'anonymous'
})
})
)
// 要素设置属性
feature.setProperties({
// 类型设为 预警
type: 'warning',
// 属性
props: res.data
})

// 添加图层
const warningLayer = new VectorLayer({
source: new VectorSource({
features: [feature]
}),
visible: true
})
// 添加图层
data.mapData.addLayer(warningLayer)
data.vectorLayers.push(warningLayer)
}
}

watch(() => props.visible, (visible) => {
if (visible) {
nextTick(() => {
if (!data.mapData) {
initMap()
// 飞行完成 轨迹回放
data.drawerTitle = '轨迹回放'
if (data.videoInfo.status === 'init') {
initOriginPlayer()
}
getTrackData().then(res => {
if (res.trackList.length > 0) {
initTrack(formatTradeList(res.trackList), 'route')
}
})
// 绘制机场
if (props.data.airportId) {
drawAirport(props.data.airportId)
}
// 绘制预警点
if (props.data.id) {
getEmergencyRecord(props.data.id)
}
}
})
} else {
originRef.value?.disposeVideo()
data.videoInfo = {
url: null,
currentTime: 0,
duration: 100,
isLive: false,
status: 'init'
}
data.videoShow = 'back'
data.mapData = null
}
}, { deep: true })

/* 初始化地图 */
const initMap = function() {
const layers = [
new Tile({
visible: true,
source: new XYZ({
projection: gcj02Mecator,
url: 'https://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=6&x={x}&y={y}&z={z}'
})
})
]

data.mapView = new View({
maxZoom: 18,
zoom: 5,
center: transform([118.837886, 32.057175], 'EPSG:4326', 'EPSG:3857')
})
data.mapData = new Map({
layers: layers,
target: 'history-map',
view: data.mapView,
controls: control.defaults({
attribution: false,
rotate: false,
zoom: false
})
})
data.mapData?.render()
}

/* 处理轨迹列表 */
const formatTradeList = function(trackList) {
return trackList.map((item) =>
fromLonLat([parseFloat(item.lng), parseFloat(item.lat)])
)
}

/* 初始化轨迹 */
const initTrack = function(coordinate, routeType) {
let vectorLayer = null
const route = new LineString(coordinate)
const routeFeature = new Feature({
type: routeType,
geometry: route
})
const startMarker = new Feature({
type: 'icon',
geometry: new Point(route.getFirstCoordinate())
})
const position = startMarker.getGeometry().clone()
const geoMarker = new Feature({
type: 'geoMarker',
geometry: position
})
geoMarker.setProperties({ type: 'geoMarker' }, false)
if (data.trackLayer) {
data.trackLayer.setSource(
new VectorSource({
features: [geoMarker, routeFeature]
})
)
vectorLayer = data.trackLayer
} else {
vectorLayer = new VectorLayer({
source: new VectorSource({
features: [geoMarker, routeFeature]
}),
style: function(feature) {
return styleList[feature.get('type')]
}
})
}
data.mapData.addLayer(vectorLayer)
data.mapView.fit(route, { padding: [50, 50, 50, 50] })
vectorLayer.on('postrender', moveFeature.bind())
function moveFeature(event) {
/* 计算飞行的百分比 */
const percent = data.videoInfo.currentTime / data.videoInfo.duration
const currentCoordinate = route.getCoordinateAt(percent)
position.setCoordinates(currentCoordinate)
geoMarker.setGeometry(null)
const vectorContext = getVectorContext(event)
vectorContext.setStyle(styleList['geoMarker'])
vectorContext.drawGeometry(position)
data.mapData?.render()
}
}

function handleVideoStatus(status) {
const { currentTime, duration } = originRef.value?.getTime()
data.videoInfo = {
...data.videoInfo,
currentTime,
duration,
status: status
}
}

function handleDrawerColse() {
emit('update:visible', false)
data.vectorLayers.forEach((layer) => {
if (layer) {
data.mapData.removeLayer(layer)
}
})
data.vectorLayers.length = 0
}

/* 视频窗口展开收起 */
const showVideo = function(value) {
data.videoShow = value
}

return {
...toRefs(data),
originRef,
getDrawerOptions,
handleDrawerColse,
handleVideoStatus,
showVideo
}
}
})
</script>

<style scoped lang='scss'>
.n-button+.n-button{
margin-left: 30px;
}
.main_container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
#history-map {
width: 100%;
height: 100%;
}
/* 视频 */
.videobox {
width: 600px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-end;
position: absolute;
top: 10px;
right: 10px;
}
.flagbox {
width: 600px;
height: 27px;
background: #fff;
padding: 0 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.flagbox_open {
width: 600px;
animation: openbox 1s;
}
.flaxbox_back {
width: 116px;
animation: backbox 1s;
}
@keyframes backbox {
from {
width: 600px;
}
to {
width: 116px;
}
}
@keyframes openbox {
from {
width: 116px;
}
to {
width: 600px;
}
}
.video_content {
width: 600px;
display: flex;
justify-content: space-between;
}
.video_show {
opacity: 1;
animation: showIt 1s;
}
@keyframes showIt {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.video_hidden {
opacity: 0;
animation: hiddenIt 1s;
}
@keyframes hiddenIt {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.imageicon {
width: 16px;
height: 16px;
margin-right: 10px;
}
</style>

+ 22
- 5
src/views/early-warning/fire/index.vue 查看文件

@@ -13,7 +13,9 @@
</div>

<!-- 新增、编辑弹窗 -->
<FireModal v-if="modalShow" v-model:visible="modalShow" :type="modalType" :data="rowData" @reload="handleSearch" />
<FireModal v-if="modalShow" v-model:visible="modalShow" :type="modalType" :data="rowData" @reload="handleSearch" @flight="showFlight" />

<FlightDrawer v-model:visible="flightDrawer" :data="flightData" />
</template>
<script>
import { search } from './tools/search.js'
@@ -21,16 +23,19 @@ import table from './tools/table.js'
import HeadSearch from '@/components/Search/index.vue'
import DataTable from '@/components/DataTable/index.vue'
import FireModal from './components/FireModal.vue'
import FlightDrawer from './components/FlightDrawer.vue'
import { firePage } from '@/api/early/fire.js'
import { unref, toRefs, reactive, onUnmounted } from 'vue'
import { getTaskDetail } from '@/api/task/index.js'
export default {
name: 'MenuPage',
components: { HeadSearch, DataTable, FireModal },
components: { HeadSearch, DataTable, FireModal, FlightDrawer },
setup() {
const data = reactive({
...toRefs(table),
...toRefs(search)
...toRefs(search),
flightDrawer: false,
flightData: {}
})

const loadDataTable = async(res) => {
@@ -40,13 +45,25 @@ export default {
}
return await firePage(_params)
}

// 展示飞行
const showFlight = async(missionId) => {
// 根据任务id 查询任务详情
const res = await getTaskDetail(missionId)
if (res.code === 0) {
data.flightData = res.data
data.flightDrawer = true
}
}

onUnmounted(() => {
data.searchParams = null
})

return {
...toRefs(data),
loadDataTable
loadDataTable,
showFlight
}
},
methods: {

+ 1
- 0
src/views/task-manage/all-task/components/DemandDrawer.vue 查看文件

@@ -148,6 +148,7 @@ export default defineComponent({
}

watch(() => props.visible, (visible) => {
console.log(props.data)
if (visible) {
nextTick(() => {
if (data.videoInfo.status === 'init') {

正在加载...
取消
保存