| | |
| | | <template> |
| | | <div class="AINowFly"> |
| | | <div>智引即飞</div> |
| | | <div class="title">智引即飞<img @click="closeClick" src="@/assets/images/aiNowFly/close.png"/></div> |
| | | <el-form ref="ruleFormRef" :model="params" :rules="rules" label-width="auto" size="small" status-icon> |
| | | <el-form-item label="任务名称" prop="taskName"> |
| | | <el-input v-model="params.taskName" /> |
| | | <el-form-item label="任务名称" prop="name"> |
| | | <el-input class="ztzf-input" v-model="params.name" /> |
| | | </el-form-item> |
| | | <el-form-item label="关联算法" prop="ai_types"> |
| | | <el-select v-model="params.ai_types" placeholder="请选择"> |
| | | <el-select :teleported="false" class="ztzf-select" v-model="params.ai_types" placeholder="请选择" multiple collapse-tags collapse-tags-tooltip> |
| | | <el-option v-for="item in taskAlgorithm" :key="item.id" :label="item.dictValue" :value="item.dictKey" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="地址" > |
| | | <el-input disabled type="text" v-model="di" /> |
| | | <el-form-item label="地址"> |
| | | <el-input class="ztzf-input" disabled type="text" v-model="di" placeholder="地名&经纬度"/> |
| | | </el-form-item> |
| | | <!-- todo--> |
| | | <el-form-item label="飞行事件"> |
| | | <div class="event"> |
| | | <img src="../../assets/images/aiNowFly/event/startRecord.png" alt=""> |
| | | <div class="img-close" v-show="isPhoto"> |
| | | <img @click="isPhoto=false" class="close" src="@/assets/images/aiNowFly/close.png" alt=""> |
| | | <img src="../../assets/images/aiNowFly/event/takePhoto.png" alt=""> |
| | | </div> |
| | | <div class="img-close" v-show="isStop"> |
| | | <img @click="isStop=false" class="close" src="@/assets/images/aiNowFly/close.png" alt=""> |
| | | <img src="../../assets/images/aiNowFly/event/hover.png" alt=""> |
| | | </div> |
| | | <div class="add-event" @click="isShowEvent = !isShowEvent">+ |
| | | <div class="event-select" v-show="isShowEvent"> |
| | | <div class="photo" @click="isPhoto=true">拍照</div> |
| | | <div class="stop" @click="isStop=true">悬停</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div>可用机巢列表</div> |
| | | <el-table :data="list" @selection-change="handleSelectionChange"> |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column type="index" label="序号" /> |
| | | <el-table-column prop="nickname" label="机巢名称" /> |
| | | <el-table-column prop="minute" label="预计飞行时长(分钟)" /> |
| | | <el-table-column prop="exe_distance" label="预计飞行距离(m)" /> |
| | | </el-table> |
| | | <el-button type="primary" @click="nowFly">迅速起飞</el-button> |
| | | <div class="title-list">可飞行机巢列表:</div> |
| | | <div class="tabledata"> |
| | | <div class="table-content"> |
| | | <div class="tabler-item"> |
| | | <el-checkbox v-model="isCheckAll" @change="checkedAll"/> |
| | | </div> |
| | | <div class="table-item">机巢名称</div> |
| | | <div class="table-item">可飞行时间</div> |
| | | <div class="table-item">可飞行距离</div> |
| | | </div> |
| | | <div class="table-content" v-for="item in list"> |
| | | <div class="tabler-item"> |
| | | <el-checkbox v-model="item.checked" @change="handleItemCheck"/> |
| | | </div> |
| | | <div class="table-item">{{ item.nickname }}</div> |
| | | <div class="table-item">{{ item.minute }}(min)</div> |
| | | <div class="table-item">{{ item.exe_distance }}(km)</div> |
| | | </div> |
| | | </div> |
| | | <div class="btn-submit" @click="nowFly"><img src="@/assets/images/aiNowFly/fly.png" alt=""></div> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import { Cartesian3 } from 'cesium' |
| | | import uavImg from '@/assets/images/home/useUavHome/uavImg.png' |
| | | import endingImg from '@/assets/images/aiNowFly/ending.png' |
| | | import endingHighImg from '@/assets/images/aiNowFly/ending-high.png' |
| | | import startingImg from '@/assets/images/aiNowFly/starting.png' |
| | | import ImageTrailMaterial from '@/utils/cesium/ImageTrailMaterial' |
| | | import lineImg from '@/assets/images/arrow-right-blue.png' |
| | | import _ from 'lodash' |
| | |
| | | import dayjs from 'dayjs' |
| | | import { useStore } from 'vuex' |
| | | |
| | | |
| | | |
| | | const now = dayjs().format('YYYY/MM/DD HH:mm') |
| | | const now = dayjs().format('YYYYMMDDHHmm') |
| | | const nowTime = dayjs().format('YYYY-MM-DD HH:mm:ss') |
| | | |
| | | const paramsInit = { |
| | | taskName: `智引即飞 ${now}`, |
| | | name: `智引即飞${now}`, |
| | | ai_types: '', |
| | | longitude: '', |
| | | latitude: '', |
| | | dock_sns: [], |
| | | type: 1, |
| | | begin_time: nowTime, |
| | | end_time: nowTime, |
| | | action_modes: [{action_actuator_func: 'startRecord'}], |
| | | } |
| | | const params = ref(_.cloneDeep(paramsInit)) |
| | | const di = computed(() => params.value.longitude + ',' + params.value.latitude) |
| | | |
| | | // 事件默认隐藏 |
| | | const isShowEvent = ref(false); |
| | | |
| | | // 添加事件 |
| | | const isPhoto = ref(false) |
| | | const isStop = ref(false) |
| | | |
| | | const rules = ref({ |
| | | taskName: [ |
| | | name: [ |
| | | { required: true, message: '请输入任务名称', trigger: 'blur' }, |
| | | { min: 3, max: 50, message: '长度在 3 到 20 个字符', trigger: 'blur' }, |
| | | ], |
| | |
| | | await ruleFormRef.value.validate() |
| | | if (!params.value.dock_sns.length) return ElMessage.warning('请选择将要飞行的机巢') |
| | | if (!params.value.longitude) return ElMessage.warning('请选择飞行的点') |
| | | if (params.value.dock_sns.length === 1){ |
| | | store.commit('setSingleUavHome', { device_sn: params.value.dock_sns[0]}) |
| | | }else{ |
| | | store.commit('setFootActiveIndex', 0) |
| | | // 先清除,再push |
| | | params.value.action_modes = [{action_actuator_func:'startRecord'}] |
| | | if (isPhoto.value) { |
| | | params.value.action_modes.push({action_actuator_func:'takePhoto'}) |
| | | } |
| | | if (isStop.value) { |
| | | params.value.action_modes.push({action_actuator_func:'hover'}) |
| | | } |
| | | createTask(params.value).then(res => { |
| | | ElMessage.success('起飞成功') |
| | | if (params.value.dock_sns.length === 1) { |
| | | store.commit('setSingleUavHome', { device_sn: params.value.dock_sns[0] }) |
| | | } else { |
| | | store.commit('setFootActiveIndex', 0) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const renderingLine = () => { |
| | |
| | | const start = Cartesian3.fromDegrees(Number(item.longitude), Number(item.latitude)) |
| | | const end = Cartesian3.fromDegrees(Number(longitude), Number(latitude)) |
| | | const positionsList = [start, end] |
| | | // 计算中点位置 |
| | | const midpoint = Cartesian3.midpoint(start, end, new Cartesian3()) |
| | | // 计算线段方向 |
| | | const direction = Cartesian3.subtract(end, start, new Cartesian3()) |
| | | Cartesian3.normalize(direction, direction) |
| | | // 线 |
| | | // 添加中点文字标签 |
| | | viewer.entities.add({ |
| | | id: `AINow-time-${index}`, |
| | | position: midpoint, |
| | | label: { |
| | | text: `预计${item.minute}分钟`, |
| | | font: 'bold 16px Source Han Sans CN', // 加粗并增大字号 |
| | | // fillColor: new Cesium.Color(27/255, 179/255, 255/255, 1.0), |
| | | // fillColor: Cesium.Color.WHITE, |
| | | // style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
| | | // verticalOrigin: Cesium.VerticalOrigin.CENTER, |
| | | // horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
| | | // eyeOffset: new Cesium.Cartesian3(0, 0, -10), |
| | | // pixelOffset: new Cesium.Cartesian2(0, -10), |
| | | rotation: 12.785398, // 修改旋转计算方式 |
| | | } |
| | | }) |
| | | viewer.entities.add({ |
| | | id: `AINow-line-${index}`, |
| | | polyline: { |
| | | width: 4, |
| | | width: 2, |
| | | positions: positionsList, |
| | | material: new ImageTrailMaterial({ |
| | | color: { alpha: 1, blue: 1, green: 1, red: 1 }, |
| | | speed: 20, |
| | | image: lineImg, |
| | | repeat: { x: Math.floor(40), y: 1 }, |
| | | }), |
| | | material: index === 0 ? new Cesium.Color(2/255, 192/255, 255/255, 1) : new Cesium.Color(27/255, 179/255, 255/255, 0.4), |
| | | clampToGround: false, |
| | | }, |
| | | }) |
| | |
| | | viewer.entities.add({ |
| | | id: `AINow-point-${index}`, |
| | | position: start, |
| | | label: { |
| | | text: item.nickname, |
| | | font: '12pt Source Han Sans CN', |
| | | fillColor: Cesium.Color.WHITE, |
| | | outlineColor: Cesium.Color.BLACK, |
| | | outlineWidth: 2, |
| | | style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
| | | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | pixelOffset: new Cesium.Cartesian2(0, -9), |
| | | }, |
| | | // label: { |
| | | // text: item.nickname, |
| | | // font: '12pt Source Han Sans CN', |
| | | // fillColor: Cesium.Color.WHITE, |
| | | // outlineColor: Cesium.Color.BLACK, |
| | | // outlineWidth: 2, |
| | | // style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
| | | // verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | // pixelOffset: new Cesium.Cartesian2(0, -9), |
| | | // }, |
| | | billboard: { |
| | | image: new Cesium.ConstantProperty(uavImg), |
| | | width: 24, |
| | | height: 24, |
| | | image: new Cesium.ConstantProperty(index === 0 ? endingHighImg : endingImg), |
| | | width: 40, |
| | | height: 40, |
| | | }, |
| | | properties: { |
| | | customData: { |
| | |
| | | viewer.entities.add({ |
| | | position: earthPosition, |
| | | id: `AINow-event-0`, |
| | | point: { |
| | | pixelSize: 10, |
| | | color: Cesium.Color.RED, |
| | | outlineColor: Cesium.Color.WHITE, |
| | | outlineWidth: 2, |
| | | }, |
| | | billboard: { |
| | | image: new Cesium.ConstantProperty(startingImg), |
| | | width: 40, |
| | | height: 40, |
| | | } |
| | | }) |
| | | const { longitude, latitude } = cartesian3Convert(earthPosition, viewer) |
| | | eventPoint = { longitude, latitude } |
| | | params.value = {...params.value,longitude, latitude} |
| | | params.value = { ...params.value, longitude, latitude } |
| | | getFJList() |
| | | } |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | let pagingParams = ref({ |
| | | current: 1, |
| | | size: 10 |
| | | }); |
| | | |
| | | const isCheckAll = ref(false); |
| | | // 获取飞机列表 |
| | | const getFJList = async () => { |
| | | params.value.dock_sns = [] |
| | | const getListParams = { ...eventPoint, page: 1, limit: 9999, type: 1 } |
| | | const res = await getFlyingNestBy(getListParams) |
| | | let pageParams = ref({ |
| | | ...eventPoint, |
| | | type: 1, |
| | | }); |
| | | params.value.dock_sns = []; |
| | | const res = await getFlyingNestBy(pageParams.value, pagingParams.value) |
| | | list.value = (res.data?.data || []).map(item => ({ |
| | | ...item, |
| | | checked: false, |
| | | minute: _.round(item.estimated_arrival_time / 60, 0), |
| | | })) |
| | | if (!list.value.length) ElMessage.warning('附近暂无可用无人机') |
| | | renderingLine() |
| | | } |
| | | |
| | | // 单个选择框变化 |
| | | const handleItemCheck = () => { |
| | | const allChecked = list.value.every(item => item.checked) |
| | | const someChecked = list.value.some(item => item.checked) |
| | | isCheckAll.value = allChecked |
| | | params.value.dock_sns = list.value.filter(item => item.checked).map(item => item.device_sn) |
| | | } |
| | | |
| | | // 全选 |
| | | const checkedAll = () => { |
| | | list.value.forEach(item => { |
| | | item.checked = isCheckAll.value |
| | | }) |
| | | params.value.dock_sns = isCheckAll.value ? list.value.map(item => item.device_sn) : [] |
| | | } |
| | | |
| | | const initMap = () => { |
| | |
| | | handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK) |
| | | handler?.destroy() |
| | | handler = null |
| | | } |
| | | |
| | | // 关闭 |
| | | const closeClick = () => { |
| | | removeAll() |
| | | params.value = _.cloneDeep(paramsInit) |
| | | list.value = [] |
| | | eventPoint = {} |
| | | store.commit('setFootActiveIndex', 0) |
| | | // 显示相关的按钮 |
| | | store.commit('setHideBottomIcon', true) |
| | | } |
| | | |
| | | onBeforeUnmount(() => { |
| | |
| | | <style scoped lang="scss"> |
| | | .AINowFly { |
| | | position: absolute; |
| | | top: 200px; |
| | | right: 0; |
| | | width: 400px; |
| | | height: 700px; |
| | | background: white; |
| | | color: black; |
| | | font-size: 20px; |
| | | top: 263px; |
| | | right: 74px; |
| | | width: 320px; |
| | | height: 518.6px; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | background: url('@/assets/images/aiNowFly/ai-fly-bg.png') no-repeat center / 100% 100%; |
| | | .title { |
| | | height: 50px; |
| | | line-height: 50px; |
| | | margin-left: 20px; |
| | | font-family: Segoe UI, Segoe UI; |
| | | font-weight: bold; |
| | | font-size: 20px; |
| | | letter-spacing: 2px; |
| | | background: linear-gradient(180deg, #FFFFFF 50%, #35D0FF 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | img { |
| | | position: absolute; |
| | | cursor: pointer; |
| | | width: 16px; |
| | | height: 16px; |
| | | right: 15px; |
| | | top: 15px; |
| | | } |
| | | } |
| | | .el-form { |
| | | padding: 20px 16px 13px 11px; |
| | | :deep(.el-form-item__label) { |
| | | color: #FFFFFF; |
| | | } |
| | | :deep(.el-input__wrapper), |
| | | :deep(.el-select), |
| | | :deep(.el-select__wrapper) { |
| | | background-color: #021740; |
| | | &.is-focus { |
| | | box-shadow: 0 0 0 1px #026AD6; |
| | | } |
| | | } |
| | | :deep(.el-tag, .el-tag.el-tag--primary), |
| | | :deep(.el-tag.el-tag--info) { |
| | | --el-tag-bg-color: none !important; |
| | | --el-tag-border-color: none !important; |
| | | --el-tag-hover-color: none !important; |
| | | } |
| | | :deep(.el-input__wrapper.is-disabled) { |
| | | background-color: #021740; |
| | | box-shadow: 0 0 0 1px #026AD6; |
| | | } |
| | | .event { |
| | | display: flex; |
| | | img { |
| | | width: 40px; |
| | | height: 34px; |
| | | margin-right: 10px; |
| | | } |
| | | .img-close { |
| | | position: relative; |
| | | .close { |
| | | cursor: pointer; |
| | | width: 8px; |
| | | height: 8px; |
| | | position: absolute; |
| | | top: -8px; |
| | | right: 1px; |
| | | } |
| | | } |
| | | .add-event { |
| | | width: 38px; |
| | | height: 38px; |
| | | background: rgba(83,179,255,0.16); |
| | | border-radius: 8px 8px 8px 8px; |
| | | border: 1px dashed; |
| | | text-align: center; |
| | | line-height: 38px; |
| | | color: #11C4FF; |
| | | position: relative; |
| | | cursor: pointer; |
| | | // border-image: linear-gradient(180deg, rgba(17, 196, 255, 1), rgba(255, 255, 255, 1)) 1 1; |
| | | .event-select { |
| | | z-index: 10; |
| | | position: absolute; |
| | | top: 40px; |
| | | width: 72px; |
| | | height: 70px; |
| | | background: #0F1929; |
| | | box-shadow: inset 0px -50px 50px 0px rgba(27,148,255,0.13); |
| | | border-radius: 8px 8px 8px 8px; |
| | | border: 1px solid #51A8FF; |
| | | text-align: center; |
| | | color: #D3E9FF; |
| | | .photo { |
| | | height: 36px; |
| | | border-bottom: 1px solid #51A8FF; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .title-list { |
| | | padding: 0 0 14px 11px; |
| | | } |
| | | .tabledata { |
| | | min-height: 120px; |
| | | border: 1px dashed #fff; |
| | | margin: 0px 10px; |
| | | .table-content { |
| | | padding: 0 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | &:not(:last-child) { |
| | | border-bottom: 1px dashed #fff; |
| | | } |
| | | } |
| | | .table-item { |
| | | width: 100px; |
| | | text-align: center; |
| | | height: 32px; |
| | | line-height: 32px; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | } |
| | | |
| | | .btn-submit { |
| | | cursor: pointer; |
| | | margin-left: 88px; |
| | | margin-top: 12px; |
| | | img { width: 138px; height: 38px; } |
| | | } |
| | | } |
| | | </style> |