@@ -103,3 +103,4 @@ export function getQuestionList(data) { | |||
hideMessage: true | |||
}) | |||
} | |||
@@ -1,196 +0,0 @@ | |||
<template> | |||
<n-card> | |||
<div class="card__title"> | |||
<p class="card__title--left">机场状态</p> | |||
<p class="card__title--right"> | |||
<n-form | |||
inline | |||
:label-width="80" | |||
:model="videoForm" | |||
label-placement="left" | |||
> | |||
<n-form-item label="机场选择:" path="airportId"> | |||
<n-select v-model:value="videoForm.airportId" :options="airOptions" @update:value="getAirportInfo" /> | |||
</n-form-item> | |||
</n-form> | |||
</p> | |||
</div> | |||
<div class="card__video"> | |||
<div class="video__item"> | |||
<VideoPlayer id="video-inner" ref="videoRef" :use-empty="true"> | |||
<template #empty> | |||
<div class="video__item--empty"> | |||
<img src="@/assets/images/lose-control.png"> | |||
<p>暂无信号</p> | |||
</div> | |||
</template> | |||
</VideoPlayer> | |||
</div> | |||
<div class="video__item"> | |||
<div class="item__weather"> | |||
<ul> | |||
<li v-for="(item,index) in weatherList" :key="index"> {{ item.label }}: {{ item.value }} </li> | |||
</ul> | |||
</div> | |||
<BaseMap ref="mapRef" :coordinate="coordinate" /> | |||
</div> | |||
</div> | |||
</n-card> | |||
</template> | |||
<script> | |||
import { dataToSelect } from '@/utils/handleData.js' | |||
import { airportList, airportWeather } from '@/api/dashboard/index.js' | |||
import VideoPlayer from '@/components/VideoPlayer/index.vue' | |||
import BaseMap from '@/components/BaseMap/BaseMap.vue' | |||
import { ref, reactive, toRefs, nextTick } from 'vue' | |||
import { gcj02towgs84 } from '@/utils/coordinate-util.js' | |||
export default { | |||
name: 'TaskCard', | |||
components: { VideoPlayer, BaseMap }, | |||
setup() { | |||
const mapRef = ref() | |||
const videoRef = ref() | |||
const data = reactive({ | |||
videoForm: { | |||
airportId: null | |||
}, | |||
airOptions: [], | |||
airOptionsAll: [], | |||
weatherList: [], | |||
coordinate: [108.94043, 31.008173] | |||
}) | |||
/** | |||
* @description: 获取机场数据 | |||
* @param {*} res | |||
* @return {*} | |||
*/ | |||
const loadAirport = (async function() { | |||
const res = await airportList({ page: 1, limit: 60 }) | |||
if (res.code === 0) { | |||
data.airOptionsAll = res.data | |||
// console.log(data.airOptionsAll) | |||
data.airOptions = dataToSelect(res.data, { label: 'name', value: 'id' }) | |||
data.videoForm.airportId = res.data[0].id | |||
getAirportInfo(res.data[0].id) | |||
} | |||
})() | |||
async function getAirportInfo(id) { | |||
const airItem = data.airOptionsAll.find((item) => { return item.id === id }) | |||
const coordinate = gcj02towgs84(parseFloat(airItem?.longitude), parseFloat(airItem?.latitude)) | |||
markPoint(coordinate) | |||
const res = await airportWeather(id) | |||
if (res.code === 0) { | |||
const parm = res.data.wth?.parm || [] | |||
data.weatherList = parm.length !== 0 ? [ | |||
// { label: '天气', value: '' }, | |||
{ label: '气温', value: (parm.tmp / 10).toFixed(1) + ' ℃' }, | |||
{ label: '湿度', value: (parm.hum / 10).toFixed(1) + ' RH' }, | |||
{ label: '风度', value: (parm.wspd / 10).toFixed(1) + ' m/s' }, | |||
{ label: '风向', value: (parm.wdir).toFixed(1) + ' °' } | |||
] : [] | |||
videoRef.value.disposeVideo() | |||
nextTick(() => { | |||
initPlayer() | |||
}) | |||
} | |||
} | |||
/* 地图标点 */ | |||
const markPoint = function(coordinate) { | |||
try { | |||
mapRef.value.getLayer(coordinate) | |||
} catch (e) { | |||
markPoint(coordinate) | |||
} | |||
} | |||
function initPlayer() { | |||
const row = data.airOptionsAll.find((item) => { return item.id === data.videoForm.airportId }) | |||
const options = { | |||
width: '100%', | |||
height: '100%', | |||
source: row?.flvExternalMonitorUrl, | |||
isLive: true | |||
} | |||
videoRef.value?.init(options) | |||
} | |||
return { | |||
...toRefs(data), | |||
mapRef, | |||
videoRef, | |||
loadAirport, | |||
getAirportInfo | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang='scss'> | |||
.card__title{ | |||
line-height: 20px; | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
margin-bottom: 15px; | |||
.card__title--left{ | |||
font-size: 18px; | |||
padding-left: 8px; | |||
border-left: 4px solid rgba(24, 144, 255, 1); | |||
} | |||
.card__title--right{ | |||
.n-select{ | |||
width: 180px; | |||
} | |||
} | |||
} | |||
.card__video{ | |||
display: flex; | |||
height: calc(100% - 55px); | |||
.video__item{ | |||
width: calc(50% - 10px); | |||
position: relative; | |||
&:first-child{ | |||
margin-right: 20px | |||
} | |||
.video__item--empty{ | |||
position: absolute; | |||
width: 100%; | |||
height: 100%; | |||
background: rgba(3, 3, 3, 1); | |||
img{ | |||
position: absolute; | |||
left: 50%; | |||
top: 45%; | |||
transform: translate(-50%,-50%); | |||
} | |||
p{ | |||
position: absolute; | |||
left: 50%; | |||
top: 60%; | |||
transform: translate(-50%,-50%); | |||
font-size: 12px; | |||
color: rgba(255, 255, 255, 1); | |||
} | |||
} | |||
.item__weather{ | |||
position: absolute; | |||
z-index: 100; | |||
padding: 5px 6px; | |||
font-size: 12px; | |||
right: 0; | |||
color: rgba(255, 255, 255, 1); | |||
background: rgba(0, 0, 0, 0.4); | |||
} | |||
} | |||
} | |||
::v-deep(.n-form){ | |||
.n-form-item-feedback-wrapper{ | |||
display: none; | |||
} | |||
} | |||
</style> |
@@ -66,7 +66,6 @@ export default { | |||
{ icon: new URL('../../../assets/images/north.png', import.meta.url).href, indicatorValue: null, indicatorName: '风向' }, | |||
{ icon: new URL('../../../assets/images/atmosPressure.png', import.meta.url).href, indicatorValue: null, indicatorName: '大气压力' }, | |||
{ icon: new URL('../../../assets/images/airHumidity.png', import.meta.url).href, indicatorValue: null, indicatorName: '空气湿度' }, | |||
{ icon: new URL('../../../assets/images/airTemperature.png', import.meta.url).href, indicatorValue: null, indicatorName: '空气温度' }], | |||
innerMonitorOptions: { | |||
// width: '256px', | |||
@@ -81,7 +80,6 @@ export default { | |||
height: '198px', | |||
control: true, // 是否显示控制 | |||
controlBtns: [ | |||
'fullScreen' | |||
], // 显示所有按钮, | |||
src: '' | |||
@@ -114,9 +112,7 @@ export default { | |||
innerVideoRef.value?.disposeVideo() | |||
outVideoRef.value?.disposeVideo() | |||
initPlayer(data.detail.internalMonitorUrl, data.detail.externalMonitorUrl) | |||
getAirportInfo({ | |||
airportId: data.detail.id | |||
}) | |||
getAirportInfo(data.detail.id) | |||
.then(res => { | |||
if (res.code === 0) { | |||
// console.log(res, '机场详情') |
@@ -89,7 +89,7 @@ | |||
</template> | |||
<script> | |||
import { reactive, toRefs } from 'vue' | |||
import { onMounted, reactive, toRefs } from 'vue' | |||
import { startOfDay } from 'date-fns/esm' | |||
import { getMissionList, getQuestionList } from '@/api/dashboard/index.js' | |||
import { cameraList } from '@/api/basic/monitor.js' | |||
@@ -119,7 +119,7 @@ export default { | |||
{ name: '火灾预警', path: 'warning', selected: { color: 'rgba(51, 133, 255, 1)', path: 'warning_select' }}, | |||
{ name: '森林巡查', path: 'patrol', selected: { color: 'rgba(51, 133, 255, 1)', path: 'patrol_select' }} | |||
], | |||
showWarning: false, | |||
showWarning: true, | |||
showPatrol: false, | |||
portalTab: 'task' | |||
}) |
@@ -0,0 +1,280 @@ | |||
<template> | |||
<div class="fire-alarm"> | |||
<div class="fire-details"> | |||
<p class="alarm-title">火灾详情</p> | |||
<div style="width: 100%; height: 210px"> | |||
视频位置 | |||
</div> | |||
<p class="alarm-detail"> | |||
<span>火灾位置:</span> | |||
<span>xxxxxxxxxxxxxx位置</span> | |||
</p> | |||
<p class="alarm-detail"> | |||
<span>发现方式:</span> | |||
<span>监控摄像、无人机巡检、人工巡检</span> | |||
</p> | |||
<p class="alarm-detail"> | |||
<span>上报时间:</span> | |||
<span>2023-01-28 10:00:00</span> | |||
</p> | |||
</div> | |||
<p class="dividing-line" /> | |||
<div class="airport-dispatch"> | |||
<p class="alarm-title">机场调度</p> | |||
<p class="dispatch-detail"> | |||
<span>选择机场:</span> | |||
<n-select v-model:value="value" :options="airpotOptions" /> | |||
</p> | |||
<p class="dispatch-detail"> | |||
<span>飞行高度:</span> | |||
<n-slider v-model:value="value" :default-value="100" :format-tooltip="formatHeight" :max="600" :show-tooltip="true" :placement="bottom" /> | |||
</p> | |||
<p class="execute-btn">立即执行</p> | |||
<p class="task-log"> | |||
<span>任务记录</span> | |||
<ul> | |||
<li> 2023-01-23 10:00:00 XXXXX机场执行任务 执行中</li> | |||
<li> 2023-01-23 10:00:00 XXXXX机场执行任务 已完成</li> | |||
<li> 2023-01-23 10:00:00 XXXXX机场执行任务 已完成</li> | |||
</ul> | |||
</p> | |||
</div> | |||
<p class="dividing-line" /> | |||
<div class="fire-verify"> | |||
<p class="alarm-title">火灾核实</p> | |||
<n-input | |||
v-model:value="value" | |||
type="textarea" | |||
placeholder="请输入火灾核实描述" | |||
/> | |||
<p class="verify-btns"> | |||
<span>忽略</span> | |||
<span>已处理</span> | |||
</p> | |||
</div> | |||
</div> | |||
<div class="available-airports"> | |||
<p>可用机场列表</p> | |||
<div> | |||
<p> | |||
<span>机场1</span> | |||
<span>发现隐患的机场</span> | |||
</p> | |||
<p> | |||
<span>续航里程</span> | |||
<span>无人机电量</span> | |||
<span>机场距离火灾隐患点</span> | |||
</p> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { reactive, toRefs, watch } from 'vue' | |||
export default { | |||
name: 'FireAlarm', | |||
components: { }, | |||
props: { | |||
data: { | |||
type: Object, | |||
default: () => { } | |||
} | |||
}, | |||
emits: [], | |||
setup(props, { emit }) { | |||
const data = reactive({ | |||
airpotOptions: [ | |||
{ | |||
label: '机场1', | |||
value: 'airport1' | |||
}, | |||
{ | |||
label: '机场2', | |||
value: 'airport2' | |||
} | |||
] | |||
}) | |||
watch(() => props.data, (value) => { | |||
}) | |||
const formatHeight = (value) => { | |||
return value + 'm' | |||
} | |||
return { | |||
...toRefs(data), | |||
formatHeight | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.fire-alarm { | |||
position: absolute; | |||
left: 1345px; | |||
top: 10px; | |||
width: 407px; | |||
height: 925px; | |||
opacity: 0.85; | |||
border-radius: 1px; | |||
background: rgba(0, 0, 0, 1); | |||
} | |||
.alarm-title { | |||
width: 100%; | |||
height: 30px; | |||
line-height: 30px; | |||
font-size: 14px; | |||
color: #fff; | |||
padding-left: 6px; | |||
} | |||
.alarm-detail { | |||
width: 100%; | |||
height: 30px; | |||
span:first-child { | |||
width: 90px; | |||
height: 30px; | |||
line-height: 30px; | |||
display: inline-block; | |||
color: rgba(139, 139, 139, 1); | |||
font-size: 14px; | |||
text-align: right; | |||
} | |||
span:nth-child(2){ | |||
width: 310px; | |||
height: 30px; | |||
line-height: 30px; | |||
display: inline-block; | |||
color: rgba(255, 255, 255, 1); | |||
font-size: 14px; | |||
text-align: left; | |||
padding-left: 5px; | |||
} | |||
} | |||
.dividing-line{ | |||
width: 100%; | |||
height: 0px; | |||
opacity: 1; | |||
background: rgba(112, 112, 112, 1); | |||
border: 0.2px solid rgba(107, 107, 107, 1); | |||
margin: 15px 0; | |||
} | |||
::v-deep(.dispatch-detail) { | |||
width: 100%; | |||
height: 34px; | |||
margin: 5px 0; | |||
span { | |||
width: 90px; | |||
height: 34px; | |||
line-height: 34px; | |||
display: inline-block; | |||
color: rgba(139, 139, 139, 1); | |||
font-size: 14px; | |||
text-align: right; | |||
} | |||
.n-select { | |||
width: 225px; | |||
height: 32px; | |||
border-radius: 2px; | |||
display: inline-block; | |||
margin-left: 5px; | |||
} | |||
.n-slider { | |||
width: 270px; | |||
display: inline-block; | |||
margin-left: 6px; | |||
vertical-align: middle; | |||
.n-slider-handle-indicator--top { | |||
margin-bottom: 0; | |||
} | |||
} | |||
} | |||
.execute-btn { | |||
width: 347px; | |||
height: 32px; | |||
margin: 10px 0; | |||
border-radius: 17px; | |||
background: rgba(31, 31, 31, 1); | |||
border: 2px solid rgba(57, 146, 247, 1); | |||
color: #fff; | |||
line-height: 32px; | |||
font-size: 14px; | |||
text-align: center; | |||
margin-left: 30px; | |||
cursor: pointer; | |||
} | |||
.task-log { | |||
width: 100%; | |||
margin-top: 30px; | |||
span { | |||
width: 100%; | |||
height: 30px; | |||
display: inline-block; | |||
line-height: 30px; | |||
color: #fff; | |||
font-size: 14px; | |||
padding-left: 25px; | |||
} | |||
} | |||
::v-deep(.fire-verify){ | |||
width: 100%; | |||
.n-input { | |||
margin-left: 35px; | |||
width: 350px; | |||
height: 90px; | |||
} | |||
.verify-btns { | |||
width: 100%; | |||
height: 32px; | |||
margin-top: 30px; | |||
span { | |||
width: 162px; | |||
height: 32px; | |||
display: inline-block; | |||
border-radius: 17px; | |||
background: rgba(31, 31, 31, 1); | |||
border: 2px solid rgba(57, 146, 247, 1); | |||
line-height: 32px; | |||
color: #fff; | |||
font-size: 14px; | |||
text-align: center; | |||
cursor: pointer; | |||
} | |||
span:nth-child(1){ | |||
background: rgba(57, 146, 247, 1); | |||
margin-left: 37px; | |||
margin-right: 6px; | |||
} | |||
} | |||
} | |||
.available-airports { | |||
position: absolute; | |||
left: 764px; | |||
top: 91px; | |||
width: 285px; | |||
height: 449px; | |||
opacity: 0.85; | |||
border-radius: 1px; | |||
background: rgba(0, 0, 0, 1); | |||
} | |||
</style> | |||
@@ -0,0 +1,200 @@ | |||
<template> | |||
<div v-show="suppliesShow" class="supplies-content"> | |||
<div class="supplies-title"> | |||
<p>消防物资</p> | |||
<span class="close-supplies" @click="closeSupplies" /> | |||
</div> | |||
<p class="supplies-type"> | |||
<img :src="`/src/assets/gis/images/toLeft.png`" @click="deSuppliesIndex"> | |||
<span v-for="(item ,index) in MATERIAL_TYPE" v-show="item.value === suppliesType" :key="index"> | |||
{{ item.label }} | |||
</span> | |||
<img :src="`/src/assets/gis/images/toRight.png`" @click="inSuppliesIndex"> | |||
</p> | |||
<div class="supplies-table"> | |||
<ul class="table-title"> | |||
<li> | |||
<span>序号</span> | |||
<span>物资名称</span> | |||
<span>作用</span> | |||
</li> | |||
</ul> | |||
<ul class="table-content"> | |||
<li v-for="(item, index) in goodsFeaturesByType" :key="index"> | |||
<span>{{ index + 1 }}</span> | |||
<span>{{ item.goodsName }}</span> | |||
<span>{{ item.goodsAction }}</span> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { reactive, toRefs, watch } from 'vue' | |||
import { MATERIAL_TYPE } from '@/utils/dictionary.js' | |||
import { goodsList } from '@/api/basic/material.js' | |||
export default { | |||
name: 'SuppliesInfo', | |||
components: { }, | |||
props: { | |||
data: { | |||
type: Object, | |||
default: () => { } | |||
} | |||
}, | |||
emits: ['close'], | |||
setup(props, { emit }) { | |||
const data = reactive({ | |||
detail: props.data, | |||
suppliesShow: true, | |||
MATERIAL_TYPE, | |||
suppliesType: 1, | |||
goodsFeatures: [], | |||
goodsFeaturesByType: [] | |||
}) | |||
watch(() => props.data, (value) => { | |||
if (JSON.stringify(value) !== '{}') { | |||
showGoodsTable(value) | |||
} | |||
}) | |||
const deSuppliesIndex = () => { | |||
// type索引自减一 | |||
data.suppliesType = data.suppliesType === 1 ? MATERIAL_TYPE.length : --data.suppliesType | |||
filterGoods() | |||
} | |||
const inSuppliesIndex = () => { | |||
// type索引自增一 | |||
data.suppliesType = data.suppliesType < MATERIAL_TYPE.length ? ++data.suppliesType : 1 | |||
filterGoods() | |||
} | |||
// 筛选数据 | |||
const filterGoods = () => { | |||
data.goodsFeaturesByType.length = 0 | |||
data.goodsFeatures.forEach((feature) => { | |||
if (feature.goodsType === data.suppliesType) { | |||
data.goodsFeaturesByType.push(feature) | |||
} | |||
}) | |||
} | |||
const showGoodsTable = async(value) => { | |||
data.suppliesShow = true | |||
const res = await goodsList({ | |||
warehouseId: value.id | |||
}) | |||
if (res.code === 0) { | |||
data.goodsFeatures = res.data | |||
filterGoods() | |||
} | |||
} | |||
const closeSupplies = () => { | |||
emit('close') | |||
} | |||
return { | |||
...toRefs(data), | |||
deSuppliesIndex, | |||
inSuppliesIndex, | |||
closeSupplies | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.supplies-content { | |||
width: 282px; | |||
height: 409px; | |||
background-color: rgba(0, 0, 0, 0.8); | |||
} | |||
.supplies-title { | |||
width: 282px; | |||
height: 30px; | |||
p { | |||
width: 250px; | |||
height: 30px; | |||
color: white; | |||
font-size: 12px; | |||
line-height: 30px; | |||
padding-left: 15px; | |||
float: left; | |||
} | |||
span { | |||
width: 30px; | |||
height: 30px; | |||
display: inline-block; | |||
background: url('@/assets/gis/images/close-icon.png') no-repeat; | |||
background-size: 100% 100%; | |||
cursor: pointer; | |||
} | |||
} | |||
.supplies-type { | |||
width: 270px; | |||
height: 32px; | |||
background: rgba(61, 61, 61, 1); | |||
display: flex; | |||
align-items: center; | |||
cursor: pointer; | |||
margin-left: 6px; | |||
img { | |||
margin: 0 8px; | |||
} | |||
span { | |||
width: 210px; | |||
height: 30px; | |||
display: inline-block; | |||
line-height: 30px; | |||
font-size: 14px; | |||
color: #fff; | |||
text-align: center; | |||
} | |||
} | |||
.supplies-table { | |||
width: 270px; | |||
height: 335px; | |||
background: rgba(61, 61, 61, 1); | |||
margin-left: 6px; | |||
margin-top: 2px; | |||
} | |||
.supplies-table li { | |||
width: 100%; | |||
height: 30px; | |||
line-height: 30px; | |||
span { | |||
width: 90px; | |||
height: 30px; | |||
line-height: 30px; | |||
display: inline-block; | |||
color: #fff; | |||
font-size: 12px; | |||
text-align: center; | |||
} | |||
} | |||
::v-deep(.table-content) { | |||
height: 300px; | |||
overflow: auto; | |||
scrollbar-width: none; | |||
-ms-overflow-style: none; | |||
&::-webkit-scrollbar { | |||
display: none; | |||
} | |||
} | |||
</style> | |||
@@ -1,120 +0,0 @@ | |||
<template> | |||
<n-card> | |||
<div class="card__title"> | |||
<p class="card__title--left">待飞行任务</p> | |||
<p class="card__title--right" @click="handlePreviewMore">查看更多<SvgIcon icon-class="arrow" /></p> | |||
</div> | |||
<div class="card__table"> | |||
<n-data-table | |||
:bordered="false" | |||
:single-column="true" | |||
:columns="columns" | |||
:data="tableData" | |||
:pagination="false" | |||
:max-height="340" | |||
> | |||
<template #empty> | |||
<img src="@/assets/images/no-task.png" alt=""> | |||
</template> | |||
</n-data-table> | |||
</div> | |||
</n-card> | |||
</template> | |||
<script> | |||
import { useRouter } from 'vue-router' | |||
import SvgIcon from '@/components/SvgIcon/index.vue' | |||
import { getTaskList } from '@/api/task/index.js' | |||
import { reactive, toRefs } from 'vue' | |||
export default { | |||
name: 'TaskCard', | |||
components: { SvgIcon }, | |||
setup() { | |||
const router = useRouter() | |||
const data = reactive({ | |||
tableData: [], | |||
columns: [ | |||
{ | |||
title: '任务名称', | |||
key: 'name', | |||
align: 'center' | |||
}, | |||
{ | |||
title: '待飞行时间', | |||
key: 'executionStartTime', | |||
align: 'center' | |||
}, | |||
{ | |||
title: '创建人', | |||
key: 'createUser', | |||
align: 'center' | |||
} | |||
] | |||
}) | |||
/** | |||
* @description: 加载表格数据 | |||
* @param {*} res | |||
* @return {*} | |||
*/ | |||
const loadDataTable = (async function() { | |||
const res = await getTaskList({ page: 1, limit: 6, status: 1 }) | |||
if (res.code === 0) { | |||
data.tableData = res.data.records | |||
} | |||
})() | |||
function handlePreviewMore() { | |||
router.replace('/taskManage/all') | |||
} | |||
return { | |||
...toRefs(data), | |||
loadDataTable, | |||
handlePreviewMore | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang='scss'> | |||
.card__title{ | |||
line-height: 20px; | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
margin-bottom: 15px; | |||
.card__title--left{ | |||
font-size: 18px; | |||
padding-left: 8px; | |||
border-left: 4px solid rgba(24, 144, 255, 1); | |||
} | |||
.card__title--right{ | |||
font-size: 12px; | |||
display: flex; | |||
align-items: center; | |||
cursor: pointer; | |||
svg{ | |||
font-size: 24px; | |||
} | |||
&:hover{ | |||
color: rgba(24, 144, 255, 1); | |||
} | |||
} | |||
} | |||
.card__table{ | |||
height: calc(100% - 40px); | |||
} | |||
::v-deep(.n-data-table){ | |||
.n-data-table-tr{ | |||
.n-data-table-th{ | |||
padding: 8px 12px; | |||
background: rgba(191, 223, 255, 1); | |||
} | |||
.n-data-table-td--last-row{ | |||
border-bottom: none; | |||
} | |||
} | |||
} | |||
</style> |
@@ -1,222 +0,0 @@ | |||
<template> | |||
<n-card> | |||
<div class="card__title"> | |||
<p class="card__title--left">飞行视频</p> | |||
<p class="card__title--right"> | |||
<n-form | |||
inline | |||
:label-width="80" | |||
:model="videoForm" | |||
label-placement="left" | |||
> | |||
<n-form-item label="机场选择:" path="airportId"> | |||
<n-select v-model:value="videoForm.airportId" :options="airOptions" @update:value="handleAirportChange" /> | |||
</n-form-item> | |||
<n-form-item label="回放选择:" path="taskId"> | |||
<n-select v-model:value="videoForm.taskId" :options="taskOptions" clearable @update:value="handleVideoChange" /> | |||
</n-form-item> | |||
</n-form> | |||
</p> | |||
</div> | |||
<div class="card__video"> | |||
<div class="video__item"> | |||
<VideoPlayer id="dashboard-video" ref="originRef" :use-empty="true" @video-status="handleVideoStatus"> | |||
<template #empty> | |||
<div class="card__video--empty"> | |||
<img src="@/assets/images/no-live.png"> | |||
<p>当前暂无直播</p> | |||
</div> | |||
</template> | |||
</VideoPlayer> | |||
</div> | |||
</div> | |||
</n-card> | |||
</template> | |||
<script> | |||
import { dataToSelect } from '@/utils/handleData.js' | |||
import { airportList } from '@/api/dashboard/index.js' | |||
import { getTaskList } from '@/api/task/index.js' | |||
import { missionLive } from '@/api/dashboard/index.js' | |||
import VideoPlayer from '@/components/VideoPlayer/index.vue' | |||
import { ref, reactive, onUnmounted, watch, toRefs, nextTick } from 'vue' | |||
export default { | |||
name: 'TaskCard', | |||
components: { VideoPlayer }, | |||
setup() { | |||
const originRef = ref() | |||
const data = reactive({ | |||
videoForm: { | |||
airportId: null, | |||
taskId: null | |||
}, | |||
airOptions: [], | |||
taskOptions: [], | |||
hasPlayer: false, | |||
airportIdBack: null, | |||
videoInfo: { | |||
url: null, | |||
isLive: false, | |||
status: 'init' | |||
} | |||
}) | |||
/** | |||
* @description: 加载表格数据 | |||
* @param {*} res | |||
* @return {*} | |||
*/ | |||
const loadAirport = (async function(id) { | |||
const res = await airportList({ page: 1, limit: 60 }) | |||
if (res.code === 0) { | |||
data.airOptions = dataToSelect(res.data, { label: 'name', value: 'id' }) | |||
data.videoForm.airportId = id || res.data[0]?.id | |||
handleAirportChange(data.videoForm.airportId) | |||
} | |||
})() | |||
async function loadTaskOption(id) { | |||
const res = await getTaskList({ page: 1, limit: 600, status: 4, airportId: id }) | |||
if (res.code === 0) { | |||
data.taskOptions = dataToSelect(res.data.records, { label: 'name', value: 'id' }) | |||
} | |||
} | |||
function handleAirportChange(value) { | |||
// if (value === data.airportIdBack) return | |||
data.airportIdBack = value | |||
data.videoForm.taskId = null | |||
missionLive(value) | |||
.then(res => { | |||
if (res.code === 0) { | |||
data.videoInfo = { | |||
url: res.data?.aiplayUrl, | |||
isLive: true, | |||
status: 'init' | |||
} | |||
} | |||
}) | |||
loadTaskOption(value) | |||
} | |||
async function handleVideoChange(value) { | |||
const row = data.taskOptions.find((item) => { return item.id === value }) | |||
if (!value) { | |||
const res = await airportList({ page: 1, limit: 60 }) | |||
if (res.code === 0) { | |||
data.airOptions = dataToSelect(res.data, { label: 'name', value: 'id' }) | |||
handleAirportChange(data.videoForm.airportId) | |||
} | |||
} else { | |||
data.videoInfo = { | |||
url: row.aiVideoUrl, | |||
isLive: false, | |||
status: 'init' | |||
} | |||
} | |||
} | |||
watch(() => data.videoInfo.url, | |||
(value) => { | |||
nextTick(() => { | |||
originRef.value?.disposeVideo() | |||
nextTick(() => { | |||
initVideoPlayer() | |||
}) | |||
}) | |||
}) | |||
/* 初始化播放器 */ | |||
function initVideoPlayer() { | |||
data.videoInfo.status = 'init' | |||
const origin = { | |||
width: '100%', | |||
height: '100%', | |||
source: data.videoInfo.url, | |||
isLive: data.videoInfo.isLive | |||
} | |||
originRef.value?.init(origin) | |||
setTimeout(() => { | |||
if (data.videoInfo.status === 'init') { | |||
originRef.value?.disposeVideo() | |||
initVideoPlayer() | |||
} | |||
}, 30000) | |||
} | |||
function handleVideoStatus(status) { | |||
data.videoInfo = { | |||
...data.videoInfo, | |||
status | |||
} | |||
} | |||
onUnmounted(() => { | |||
originRef.value?.disposeVideo() | |||
}) | |||
return { | |||
...toRefs(data), | |||
originRef, | |||
loadAirport, | |||
handleAirportChange, | |||
handleVideoChange, | |||
handleVideoStatus | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang='scss'> | |||
.card__title{ | |||
line-height: 20px; | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
margin-bottom: 15px; | |||
.card__title--left{ | |||
font-size: 18px; | |||
padding-left: 8px; | |||
border-left: 4px solid rgba(24, 144, 255, 1); | |||
} | |||
.card__title--right{ | |||
.n-select{ | |||
width: 180px; | |||
} | |||
} | |||
} | |||
.card__video{ | |||
display: flex; | |||
height: calc(100% - 55px); | |||
position: relative; | |||
.video__item{ | |||
width: 100%; | |||
} | |||
.card__video--empty{ | |||
position: absolute; | |||
width: 100%; | |||
height: 100%; | |||
background: rgba(3, 3, 3, 1); | |||
img{ | |||
position: absolute; | |||
left: 50%; | |||
top: 45%; | |||
transform: translate(-50%,-50%); | |||
} | |||
p{ | |||
position: absolute; | |||
left: 50%; | |||
top: 60%; | |||
transform: translate(-50%,-50%); | |||
font-size: 12px; | |||
color: rgba(255, 255, 255, 1); | |||
} | |||
} | |||
} | |||
::v-deep(.n-form){ | |||
.n-form-item-feedback-wrapper{ | |||
display: none; | |||
} | |||
} | |||
</style> |
@@ -1,56 +0,0 @@ | |||
<template> | |||
<div class="dashboard__main"> | |||
<div class="dashboard__top"> | |||
<TaskCard /> | |||
<VideoCard /> | |||
</div> | |||
<div class="dishboard__bottom"> | |||
<AirCard /> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { useRouter } from 'vue-router' | |||
import TaskCard from './components/TaskCard.vue' | |||
import VideoCard from './components/VideoCard.vue' | |||
import AirCard from './components/AirCard.vue' | |||
export default { | |||
name: 'HomePage', | |||
components: { TaskCard, VideoCard, AirCard }, | |||
setup(props) { | |||
const router = useRouter() | |||
function toSystem() { | |||
router.push({ path: '/login' }) | |||
} | |||
return { | |||
toSystem | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.dashboard__main{ | |||
height: calc(100vh - 80px); | |||
.dashboard__top{ | |||
display: flex; | |||
height: 50%; | |||
.n-card{ | |||
overflow: hidden; | |||
&:first-child{ | |||
width: 50%; | |||
margin-right: 20px; | |||
} | |||
} | |||
} | |||
.dishboard__bottom{ | |||
display: flex; | |||
height: calc(50% - 20px); | |||
margin-top: 20px; | |||
} | |||
.n-card{ | |||
border-radius: 10px; | |||
} | |||
} | |||
</style> | |||
@@ -1,6 +1,6 @@ | |||
<template> | |||
<div class="basic"> | |||
<OneMap /> | |||
<OneMap ref="Map"/> | |||
<Extend ref="extendRef" class="extend" @send="getmessage" /> | |||
</div> | |||
</template> | |||
@@ -16,12 +16,12 @@ export default { | |||
setup(props) { | |||
const router = useRouter() | |||
const extendRef = ref() | |||
const Map = ref() | |||
function toSystem() { | |||
router.push({ path: '/login' }) | |||
} | |||
const getmessage = (data) => { | |||
console.log(data) | |||
Map.value.addPonit(data) | |||
} | |||
onMounted(() => { | |||
extendRef.value.handleClick(0) | |||
@@ -29,7 +29,8 @@ export default { | |||
return { | |||
toSystem, | |||
extendRef, | |||
getmessage | |||
getmessage, | |||
Map | |||
} | |||
} | |||
} |