@@ -5,10 +5,10 @@ VITE_PUBLIC_PATH = '/' | |||
VITE_APP_USE_MOCK = false | |||
# proxy | |||
VITE_PROXY = [["/api-local","http://127.0.0.1:8002/api"],["/api-mock","http://127.0.0.1:8003"]] | |||
VITE_PROXY = [["/api-local","https://lcxj-test.t-aaron.com/api"],["/api-mock","http://127.0.0.1:8003"]] | |||
# base api | |||
VITE_APP_GLOB_BASE_API = '/api-local' | |||
# mock base api | |||
VITE_APP_GLOB_BASE_API_MOCK = '/api-mock' | |||
VITE_APP_GLOB_BASE_API_MOCK = '/api-mock' |
@@ -62,11 +62,10 @@ export function missionLive(id) { | |||
* @description:获取机场详细信息 | |||
* @param id 机场id | |||
*/ | |||
export function getAirportInfo(data) { | |||
export function getAirportInfo(id) { | |||
return request({ | |||
url: `/index/getAirportDetail`, | |||
method: 'POST', | |||
data | |||
url: `/index/getAirportDetail/${id}`, | |||
method: 'GET' | |||
}) | |||
} | |||
/** | |||
@@ -103,3 +102,21 @@ export function getQuestionList(data) { | |||
hideMessage: true | |||
}) | |||
} | |||
// 预警列表 | |||
export function getWarning() { | |||
return request({ | |||
url: `/warning/list?status=1`, | |||
method: 'GET' | |||
}) | |||
} | |||
// 根据预警ID获取预警记录列表 | |||
export function getWarningRecord(params) { | |||
return request({ | |||
url: `/warning/record/list/by/warningid`, | |||
method: 'GET', | |||
params | |||
}) | |||
} | |||
@@ -57,7 +57,8 @@ export function fireHeart() { | |||
*/ | |||
export function updateWarning(id, status) { | |||
return request({ | |||
url: `/warning/status/${id}/${status}`, | |||
// url: `/warning/status/${id}/${status}`, | |||
url: `/warning/messageRead/status/${id}`, | |||
method: 'put', | |||
hideMessage: true | |||
}) |
@@ -40,9 +40,9 @@ export const tableProps = { | |||
// 可切换每页数量集合 | |||
pageSizes: [10, 20, 30, 40, 50], | |||
// 是否显示每页条数的选择器 | |||
showSizePicker: false, | |||
showSizePicker: true, | |||
// 是否显示快速跳转 | |||
showQuickJumper: false | |||
showQuickJumper: true | |||
} | |||
} | |||
} |
@@ -4,9 +4,10 @@ | |||
<script setup> | |||
import { isNullOrUndef } from '@/utils/is' | |||
import { useDialog } from 'naive-ui' | |||
import { useDialog, useDialogReactiveList } from 'naive-ui' | |||
const NDialog = useDialog() | |||
const dialogReactiveList = useDialogReactiveList() | |||
class Dialog { | |||
success(title, option) { | |||
@@ -42,6 +43,10 @@ class Dialog { | |||
...option | |||
}) | |||
} | |||
colseDialog() { | |||
dialogReactiveList.value[0]?.cancel() | |||
} | |||
} | |||
window['$dialog'] = new Dialog() | |||
@@ -50,5 +55,6 @@ Object.defineProperty(window, '$dialog', { | |||
configurable: false, | |||
writable: false | |||
}) | |||
</script> | |||
@@ -19,8 +19,8 @@ export const TASK_MODE = [ | |||
] | |||
export const TASK_TYPE = [ | |||
{ label: '日常巡检', value: 1 }, | |||
{ label: '应急巡检', value: 2 } | |||
{ label: '日常巡检', value: 1 } | |||
// { label: '应急巡检', value: 2 } | |||
] | |||
export const QUESTION_STATUS = [ |
@@ -11,6 +11,7 @@ export const getTimer = () => { | |||
export const timerHeart = async function() { | |||
if (getToken()) { | |||
setTimeout(() => { timerHeart() }, 5000) | |||
hasTimer.value = true | |||
const res = await fireHeart() | |||
if (res.code === 0) { | |||
@@ -48,10 +49,9 @@ export const timerHeart = async function() { | |||
} | |||
} | |||
) | |||
// } else { | |||
// $dialog.colseDialog() | |||
} | |||
setTimeout(() => { | |||
timerHeart() | |||
}, 5000) | |||
} | |||
} else { | |||
hasModal.value = false |
@@ -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: '' | |||
@@ -109,14 +107,11 @@ export default { | |||
watch(() => props.data, (value) => { | |||
if (JSON.stringify(value) !== '{}') { | |||
// console.log(props.data) | |||
data.detail = props.data | |||
data.detail = value | |||
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,17 +119,17 @@ 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' | |||
}) | |||
const warn = reactive({ | |||
warnList: [ | |||
{ icon: camera, label: '监控分布', value: 1, num: 0, list: [] }, | |||
{ icon: materials, label: '消防物资', value: 2, num: 0, list: [] }, | |||
{ icon: personnel, label: '防护人员', value: 3, num: 0, list: [] } | |||
{ icon: camera, label: '监控分布', value: 'camera', num: 0, list: [] }, | |||
{ icon: materials, label: '消防物资', value: 'materials', num: 0, list: [] } | |||
// { icon: personnel, label: '防护人员', value: 'personnel', num: 0, list: [] } | |||
], | |||
checkedWarn: [1, 2, 3] | |||
checkedWarn: ['camera', 'materials'] | |||
}) | |||
const task = reactive({ | |||
taskList: [], | |||
@@ -168,6 +168,8 @@ export default { | |||
const handleClick = (index) => { | |||
data.selectedTab = index | |||
if (index === 1) { | |||
data.portalTab = 'task' | |||
ques.checkedQues = QUESTION_TYPE.value?.map((item) => item.value) || null | |||
if (!data.showPatrol) { | |||
queryTaskList() | |||
const times = [formatDate(ques.times[0]), formatDate(ques.times[1])] | |||
@@ -177,6 +179,7 @@ export default { | |||
data.showWarning = false | |||
} else { | |||
getWarnList() | |||
warn.checkedWarn = ['camera', 'materials'] | |||
data.showWarning = true | |||
data.showPatrol = false | |||
} | |||
@@ -221,11 +224,12 @@ export default { | |||
} | |||
const res = await getQuestionList(params) | |||
if (res.code === 0) { | |||
ques.checkedQues = QUESTION_TYPE.value?.map((item) => item.value) || null | |||
ques.message = res.data?.map((item) => { | |||
item.icon = ICON_LIST[item.type] | |||
return item | |||
}) | |||
emit('send', { tabs: data.selectedTab, data: ques.message, type: ques.checkedQues }) | |||
emit('send', { tabs: data.selectedTab, data: ques.message, type: ques.checkedQues, ops: 'query' }) | |||
} else { | |||
ques.message = null | |||
} | |||
@@ -238,7 +242,7 @@ export default { | |||
warn.warnList[0].list = camera?.data || [] | |||
warn.warnList[1].num = maertral?.data?.length || 0 | |||
warn.warnList[1].list = maertral?.data || [] | |||
emit('send', { tabs: data.selectedTab, data: warn.warnList, type: warn.checkedWarn }) | |||
emit('send', { tabs: data.selectedTab, data: warn.warnList, type: warn.checkedWarn, ops: 'query' }) | |||
}) | |||
.catch(err => { | |||
console.log(err) | |||
@@ -251,7 +255,7 @@ export default { | |||
* @return {*} | |||
*/ | |||
const handleWarnChange = async(value) => { | |||
emit('send', { tabs: data.selectedTab, data: warn.warnList, type: value }) | |||
emit('send', { tabs: data.selectedTab, data: warn.warnList, type: value, ops: 'select' }) | |||
} | |||
/** | |||
* @description: 变更僧林巡查问题选择 | |||
@@ -259,7 +263,7 @@ export default { | |||
* @return {*} | |||
*/ | |||
const handleQuesChange = async(value) => { | |||
emit('send', { tabs: data.selectedTab, data: ques.message, type: value }) | |||
emit('send', { tabs: data.selectedTab, data: ques.message, type: value, ops: 'select' }) | |||
} | |||
return { |
@@ -0,0 +1,369 @@ | |||
<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>{{ fireDetail.location }}</span> | |||
</p> | |||
<p class="alarm-detail"> | |||
<span>发现方式:</span> | |||
<span>{{ fireDetail.discoveryWay }}</span> | |||
</p> | |||
<p class="alarm-detail"> | |||
<span>上报时间:</span> | |||
<span>{{ fireDetail.time }}</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" /> | |||
<a>可用机场列表</a> | |||
</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 class="alarm-title">可用机场列表</p> | |||
<div class="airport-params"> | |||
<p> | |||
<span class="airport-name">机场1</span> | |||
<span class="airport-find">发现隐患的机场</span> | |||
</p> | |||
<p class="uav-params"> | |||
<img src="@/assets/gis/images/range.png"> | |||
<span>6km</span> | |||
<span>续航里程</span> | |||
</p> | |||
<p class="uav-params"> | |||
<img src="@/assets/gis/images/power.png"> | |||
<span>60%</span> | |||
<span>无人机电量</span> | |||
</p> | |||
<p class="uav-params" style="width: 252px"> | |||
<img src="@/assets/gis/images/measure.png"> | |||
<span style="width: 130px">3km</span> | |||
<span style="width: 130px">机场距离火灾隐患点</span> | |||
</p> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { reactive, toRefs, watch } from 'vue' | |||
import { EARLY_SOURCE } from '@/utils/dictionary.js' | |||
import { getWarningRecord } from '@/api/dashboard/index.js' | |||
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' | |||
} | |||
], | |||
fireDetail: {} | |||
}) | |||
watch(() => props.data, (value) => { | |||
if (JSON.stringify(value) !== '{}') { | |||
data.warningList = props.data | |||
showDetail(data.warningList) | |||
} | |||
}) | |||
const showDetail = async(value) => { | |||
data.fireDetail.location = value?.location | |||
data.fireDetail.discoveryWay = '' | |||
EARLY_SOURCE?.map((item) => { | |||
if (item.value === parseInt(value?.discoveryWay)) { | |||
data.fireDetail.discoveryWay = item.label | |||
} | |||
}) | |||
data.fireDetail.time = value?.updateTime | |||
const res = await getWarningRecord({ | |||
warningId: value.id | |||
}) | |||
console.log(res) | |||
} | |||
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: 200px; | |||
height: 30px; | |||
line-height: 30px; | |||
font-size: 14px; | |||
color: #fff; | |||
padding-left: 6px; | |||
margin-top: 10px; | |||
display: inline-block; | |||
} | |||
.alarm-detail { | |||
width: 100%; | |||
height: 30px; | |||
span:first-child { | |||
width: 85px; | |||
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: 320px; | |||
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 0 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; | |||
} | |||
a { | |||
color: rgba(0, 119, 255, 1); | |||
font-size: 10px; | |||
margin-left: 6px; | |||
cursor: pointer; | |||
} | |||
} | |||
.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); | |||
} | |||
.airport-params { | |||
width: 278px; | |||
height: 191px; | |||
display: inline-block; | |||
border-radius: 3px; | |||
background: rgba(44, 44, 44, 1); | |||
margin-left: 3px; | |||
margin-top: 10px; | |||
} | |||
.airport-name { | |||
width: 180px; | |||
height: 30px; | |||
display: inline-block; | |||
line-height: 30px; | |||
color: #fff; | |||
font-size: 14px; | |||
padding-left: 5px; | |||
} | |||
.airport-find { | |||
width: 80px; | |||
height: 30px; | |||
font-size: 12px; | |||
color: rgba(255, 141, 26, 1); | |||
} | |||
.uav-params { | |||
width: 121px; | |||
height: 64px; | |||
border-radius: 3px; | |||
background: rgba(66, 66, 66, 1); | |||
float: left; | |||
margin: 10px 0 0 13px; | |||
img { | |||
width: 34px; | |||
height: 34px; | |||
margin: 16px 0 14px 10px; | |||
float: left; | |||
} | |||
span { | |||
width: 60px; | |||
display: inline-block; | |||
height: 30px; | |||
line-height: 30px; | |||
text-align: center; | |||
color: #fff; | |||
font-size: 12px; | |||
} | |||
} | |||
</style> | |||
@@ -66,6 +66,7 @@ export default { | |||
}) | |||
watch(() => props.detail, (value) => { | |||
console.log(value) | |||
if (JSON.stringify(value) !== '{}') { | |||
const fileImageList = value.fileMarkerUrl.split(',') | |||
data.fileImageList = [] |
@@ -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,7 +1,7 @@ | |||
<template> | |||
<div class="basic"> | |||
<OneMap /> | |||
<Extend class="extend" @send="getmessage" /> | |||
<OneMap ref="Map" /> | |||
<Extend ref="extendRef" class="extend" @send="getmessage" /> | |||
<remoate-sensing | |||
class="remoate-sensing-extrinsic" | |||
@command="setCommandAndLog" | |||
@@ -10,21 +10,23 @@ | |||
</template> | |||
<script> | |||
import { ref } from 'vue' | |||
import { useRouter } from 'vue-router' | |||
import OneMap from './components/OneMap.vue' | |||
import Extend from './components/Extend.vue' | |||
import { ref, onMounted } from 'vue' | |||
import RemoateSensing from './components/remoateSensing.vue' | |||
export default { | |||
name: 'HomePage', | |||
components: { RemoateSensing, OneMap, Extend }, | |||
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) | |||
} | |||
const data = ref({ | |||
currentDroneInfo: { | |||
@@ -53,10 +55,15 @@ export default { | |||
$message.error(errorMsg || e.message) | |||
}) | |||
} | |||
onMounted(() => { | |||
extendRef.value.handleClick(0) | |||
}) | |||
return { | |||
data, | |||
toSystem, | |||
extendRef, | |||
getmessage, | |||
Map, | |||
setCommandAndLog | |||
} | |||
} |
@@ -67,7 +67,7 @@ export const form = reactive({ | |||
inspectionType: null, | |||
airportId: null, | |||
inspectionLine: null, | |||
type: null, | |||
type: 1, | |||
executionStartTime: null, | |||
note: null | |||
}, | |||
@@ -76,7 +76,7 @@ export const form = reactive({ | |||
inspectionType: [{ required: true, type: 'number', message: '请选择巡检方式', trigger: 'blur' }], | |||
airportId: [{ required: true, type: 'number', message: '请选择巡检机场', trigger: 'blur' }], | |||
inspectionLine: [{ required: true, type: 'number', message: '请选择巡检路线', trigger: 'blur' }], | |||
type: [{ required: true, type: 'number', message: '请选择任务类型', trigger: 'blur' }], | |||
// type: [{ required: true, type: 'number', message: '请选择任务类型', trigger: 'blur' }], | |||
executionStartTime: [{ required: true, type: 'date', message: '请选择巡检时间', trigger: ['blur', 'change'] }] | |||
}, | |||
formItem: [ | |||
@@ -84,7 +84,7 @@ export const form = reactive({ | |||
{ type: 'select', key: 'inspectionType', label: '巡检方式', props: { options: TASK_MODE }}, | |||
{ type: 'select', key: 'airportId', label: '巡检机场', props: { options: airOptions }}, | |||
{ type: 'select', key: 'inspectionLine', label: '巡检路线', props: { options: lineOptions }}, | |||
{ type: 'select', key: 'type', label: '任务类型', props: { options: TASK_TYPE }}, | |||
// { type: 'select', key: 'type', label: '任务类型', props: { options: TASK_TYPE }}, | |||
{ type: 'date', key: 'executionStartTime', label: '巡检时间', props: { | |||
type: 'datetime', | |||
valueFormat: 'yyyy-MM-dd HH:mm:ss', format: 'yyyy-MM-dd HH:mm:ss', |
@@ -51,16 +51,16 @@ export const search = reactive({ | |||
placeholder: '请选择任务状态', | |||
options: lineOptions | |||
} | |||
}, | |||
{ | |||
label: '任务类型', | |||
key: 'type', | |||
type: 'select', | |||
props: { | |||
placeholder: '请选择任务状态', | |||
options: TASK_TYPE | |||
} | |||
} | |||
// { | |||
// label: '任务类型', | |||
// key: 'type', | |||
// type: 'select', | |||
// props: { | |||
// placeholder: '请选择任务状态', | |||
// options: TASK_TYPE | |||
// } | |||
// } | |||
] | |||
}) | |||
@@ -114,17 +114,17 @@ const data = reactive({ | |||
key: 'inspectionLineName', | |||
align: 'center' | |||
}, | |||
{ | |||
title: '任务类型', | |||
key: 'type', | |||
align: 'center', | |||
render(row) { | |||
return h(TableTags, { | |||
data: row.type, | |||
filters: TASK_TYPE | |||
}) | |||
} | |||
}, | |||
// { | |||
// title: '任务类型', | |||
// key: 'type', | |||
// align: 'center', | |||
// render(row) { | |||
// return h(TableTags, { | |||
// data: row.type, | |||
// filters: TASK_TYPE | |||
// }) | |||
// } | |||
// }, | |||
{ | |||
title: '巡检时间', | |||
key: 'executionStartTime', |