<template>
|
<div class="add-task">
|
<div class="task-map" :class="{ 'fullscreen': isFullscreen }">
|
<div class="sf-img" @click="handleMapClick"><img :src="isFullscreen ? suoSvg : fangSvg" alt="" /></div>
|
<div id="taskMap"></div>
|
</div>
|
<div class="task-form" v-if="!isFullscreen">
|
<div class="task-title">任务内容</div>
|
<van-form>
|
<div class="card">
|
<div class="label">
|
<span>*</span>
|
任务名称
|
</div>
|
<van-field v-model="formParams.name" placeholder="请输入" />
|
</div>
|
<div class="card">
|
<div class="label">
|
<span>*</span>
|
日期选择
|
</div>
|
<van-field v-model="time" is-link readonly name="datePicker" placeholder="请选择"
|
@click="timeShow = true" />
|
<van-popup v-model:show="timeShow" destroy-on-close position="bottom">
|
<van-picker-group title="预约日期" :tabs="['选择日期', '选择时间']" next-step-text="下一步"
|
@confirm="onTimeConfirm" @cancel="timeShow = false">
|
<van-date-picker v-model="timeDate" :min-date="minDate" />
|
<van-time-picker v-model="formParams.execute_time_arr" />
|
</van-picker-group>
|
</van-popup>
|
</div>
|
<div class="card">
|
<div class="label">关联算法</div>
|
<van-field v-model="sfValue" is-link readonly name="picker" placeholder="请选择"
|
@click="sfShow = true" />
|
<van-popup v-model:show="sfShow" position="bottom" :style="{ height: '50%' }"
|
@click-overlay="onClickOverlay">
|
<!-- 直接在 popup 标签内添加内容 -->
|
<div class="popup-content">
|
<div class="item" v-for="item in taskAlgorithm">
|
<div class="item-content" @click="handleCheckboxChange(item)">
|
<img v-if="item.checked" :src="gouSvg" alt="" />
|
<div v-else class="empty"></div>
|
<div class="text">{{ item.text }}</div>
|
</div>
|
<!-- <van-checkbox v-model="item.checked" @update:model-value="handleCheckboxChange(item, index)">{{ item.text }}</van-checkbox>-->
|
</div>
|
</div>
|
</van-popup>
|
<!-- <van-popup v-model:show="sfShow" destroy-on-close position="bottom">-->
|
<!-- <van-picker-->
|
<!-- :columns="taskAlgorithm"-->
|
<!-- :v-model="formParams.ai_types"-->
|
<!-- @confirm="onSfConfirm"-->
|
<!-- @cancel="sfShow = false"-->
|
<!-- />-->
|
<!-- </van-popup>-->
|
</div>
|
<div class="card">
|
<div class="label">
|
<span>*</span>
|
选择航线
|
</div>
|
<van-field v-model="lineName" is-link readonly name="picker" placeholder="请选择"
|
@click="lineShow = true" />
|
<van-popup v-model:show="lineShow" destroy-on-close position="bottom">
|
<van-picker :columns="routeOptions" :model-value="lineValue" @confirm="onConfirm"
|
@cancel="lineShow = false" />
|
</van-popup>
|
</div>
|
<div class="card">
|
<div class="label">任务描述</div>
|
<van-field rows="1" autosize type="textarea" show-word-limit v-model="formParams.remark"
|
placeholder="请输入" />
|
</div>
|
</van-form>
|
<div class="task-title">可飞行机巢列表</div>
|
<div class="task-list" :class="item.checked ? 'active' : ''" v-for="(item, index) in tableData" :key="index"
|
@click="checkedTable(item)">
|
<div class="name">{{ item.nickname }}</div>
|
<div>预计到达{{ item.estimated_arrival_time }}(min)</div>
|
<div>执行里程{{ item.flight_range }}(km)</div>
|
</div>
|
<div class="task-btn">
|
<van-button round block type="primary" color="#1D6FE9" :loading="loading" loading-text="发布中..."
|
@click="handleSubmit">
|
任务发布
|
</van-button>
|
</div>
|
</div>
|
</div>
|
</template>
|
<script setup>
|
import { newGetWorkspacesPage } from '@/api/routePlan'
|
import { getMultipleDictionary } from '@/api/system/dictbiz'
|
import { getFlyingNestBy, getWaylineSplit, createTask } from '@/api/home/task'
|
import { analysisPointLineKmz, handlePointListForKmz } from '@/views/RoutePlan/PointAirLine/pointWayLineUtils'
|
import { getWaylineSplitApi } from '@/api/routePlan'
|
import _ from 'lodash'
|
import dayjs from 'dayjs'
|
import L from 'leaflet'
|
import 'leaflet/dist/leaflet.css'
|
import EventBus from '@/utils/eventBus'
|
import sl from '@/appDataSource/leafletMapIcon/sl.svg'
|
import yx from '@/appDataSource/leafletMapIcon/yx.svg'
|
import droneIconSvg from '@/appDataSource/leafletMapIcon/drone-icon.svg'
|
import suoSvg from '@/appDataSource/inspectionTask/suo-bg.svg'
|
import fangSvg from '@/appDataSource/inspectionTask/fang-bg.svg'
|
import startImg from '@/assets/images/signMachineNest/rwqfd.png'
|
import endPointImg from '@/assets/images/EndPointicon.png'
|
import gouSvg from '@/appDataSource/inspectionTask/gou.svg'
|
import { showNotify } from 'vant'
|
import { useRoute } from 'vue-router'
|
import {
|
basemapLayer0,
|
basemapLayer1,
|
basemapLayer2,
|
basemapLayer3,
|
polylineOptions0,
|
polylineOptions1,
|
polylineOptions2,
|
} from '@/const/leafletConst'
|
import 'leaflet-ant-path'
|
import { useAreaBoundary } from '@/hooks/useAreaBoundary'
|
import error from '@/error'
|
|
const route = useRoute()
|
|
const isFullscreen = ref(false)
|
let originalPosition = null
|
|
const basemap0 = L.layerGroup([basemapLayer0, basemapLayer1])
|
const basemap1 = L.layerGroup([basemapLayer2, basemapLayer3])
|
const layers = [
|
{
|
src: sl,
|
name: '天地图电子',
|
key: 1,
|
map: basemap0,
|
},
|
{
|
src: yx,
|
name: '天地图影像',
|
key: 2,
|
map: basemap1,
|
},
|
]
|
let map = null
|
let currentPolylineArr = []
|
let markersArr = []
|
let isDisabled = ref(false)
|
|
console.log(route.query, '单机巢数据')
|
|
const loading = ref(false)
|
const time = ref('')
|
const minDate = ref(new Date())
|
const timeDate = ref([dayjs().year(), dayjs().month() + 1, dayjs().date()])
|
const lineName = ref('')
|
const lineValue = ref([])
|
const hours = dayjs().hour().toString().padStart(2, '0')
|
const minutes = dayjs().minute().toString().padStart(2, '0')
|
const formParams = ref({
|
name: '',
|
ai_types: [],
|
dock_sns: [],
|
execute_time_arr: [hours, minutes],
|
file_id: '',
|
remark: '',
|
rth_altitude: 120,
|
type: 0,
|
begin_time: '',
|
end_time: '',
|
})
|
const timeShow = ref(false)
|
const sfValue = ref('')
|
const sfShow = ref(false)
|
const lineShow = ref(false)
|
|
function onTimeConfirm (value) {
|
time.value = `${value[0].selectedOptions[0].text}-${value[0].selectedOptions[1].text}-${value[0].selectedOptions[2].text} ${value[1].selectedOptions[0].text}:${value[1].selectedOptions[1].text}`
|
timeDate.value = [
|
value[0].selectedOptions[0].text,
|
value[0].selectedOptions[1].text,
|
value[0].selectedOptions[2].text,
|
]
|
formParams.value.execute_time_arr = [`${value[1].selectedOptions[0].text}:${value[1].selectedOptions[1].text}`]
|
timeShow.value = false
|
}
|
// 获取航线
|
const routeOptions = ref([])
|
const getRouteList = async () => {
|
const formData = {
|
name: '',
|
waylineType: undefined,
|
current: 1,
|
size: 99999999,
|
descs: 'update_time',
|
dockSn: route.query.device_sn,
|
isSplit: route.query.device_sn === '' ? '' : 0,
|
}
|
const res = await newGetWorkspacesPage(formData)
|
if (res.data.code === 0) {
|
routeOptions.value = res.data.data.records.map(item => ({
|
text: item.name,
|
value: item.wayline_id,
|
url: `${item.domain_url}${item.object_key}`,
|
is_split: item.is_split,
|
id: item.id,
|
wayline_type: item.wayline_type,
|
}))
|
}
|
}
|
|
let savePointList = []
|
async function onConfirm ({ selectedValues, selectedOptions }) {
|
lineShow.value = false
|
lineValue.value = selectedValues
|
lineName.value = selectedOptions[0].text
|
formParams.value.file_id = selectedOptions[0].value
|
isDisabled.value = false
|
pageParams.value.type = 0
|
pageParams.value.polygon = []
|
const { pointList, polygonList } = await analysisPointLineKmz(selectedOptions[0].url)
|
if (selectedOptions[0].is_split) {
|
splitLine(selectedOptions[0].id)
|
lineShow.value = false
|
return
|
} else {
|
if ([2, 4, 5, 6, 7, 10].includes(Number(selectedOptions[0].wayline_type))) {
|
pageParams.value.type = 2
|
pageParams.value.polygon = polygonList
|
}
|
await getTableList()
|
}
|
|
savePointList = _.clone(pointList)
|
// pointList.unshift({ latitude: tableData.value[0].latitude, longitude: tableData.value[0].longitude });
|
let points = pointList.map(item => {
|
return [item.latitude, item.longitude]
|
})
|
if (currentPolylineArr && currentPolylineArr.length > 0) {
|
currentPolylineArr.map(item => {
|
map.removeLayer(item)
|
})
|
}
|
if (markersArr && markersArr.length > 0) {
|
markersArr.map(item => {
|
map.removeLayer(item)
|
})
|
}
|
// 连接到机巢
|
const checkedSn = tableData.value.filter(item => item.checked)
|
points.unshift([checkedSn[0].latitude, checkedSn[0].longitude])
|
renderLineAndPoint(points, 0)
|
}
|
|
// 拆分航线显示
|
function splitLine (id) {
|
if (currentPolylineArr && currentPolylineArr.length > 0) {
|
currentPolylineArr.map(item => {
|
map.removeLayer(item)
|
})
|
}
|
if (markersArr && markersArr.length > 0) {
|
markersArr.map(item => {
|
map.removeLayer(item)
|
})
|
}
|
getWaylineSplitApi(id).then(async res => {
|
// 拆分大航线
|
let url = `${res.data.data.domain_url}${res.data.data.object_key}`
|
const { pointList } = await analysisPointLineKmz(url)
|
console.log(pointList)
|
// 请求拆分列表
|
getSplitTableList(pointList)
|
res.data.data.wayline_file_list.forEach(async (item, index) => {
|
let url = `${item.domain_url}${item.object_key}`
|
const { pointList } = await analysisPointLineKmz(url)
|
let points = pointList.map(item => {
|
return [item.latitude, item.longitude]
|
})
|
renderLineAndPoint(points, index)
|
})
|
})
|
}
|
|
// 渲染线和点
|
function renderLineAndPoint (points, index) {
|
const colors = [polylineOptions0, polylineOptions1, polylineOptions2]
|
const currentPolyline = L.polyline.antPath(points, colors[index]).addTo(map)
|
currentPolylineArr.push(currentPolyline)
|
// 创建起点图标
|
const startIcon = L.icon({
|
iconUrl: droneIconSvg, // 起点图片路径
|
iconSize: [28, 28], // 图标大小
|
})
|
|
// 创建终点图标
|
const endIcon = L.icon({
|
iconUrl: endPointImg, // 终点图片路径
|
iconSize: [28, 28],
|
iconAnchor: [16, 28],
|
popupAnchor: [0, -28],
|
})
|
// 添加起点标记
|
let startMarker = L.marker(points[0], { icon: startIcon }).addTo(map)
|
// 添加终点标记
|
let endMarker = L.marker(points[points.length - 1], { icon: endIcon }).addTo(map)
|
markersArr.push(startMarker, endMarker)
|
// 调整地图视图显示整个航线
|
map.fitBounds(currentPolyline.getBounds().pad(0.1))
|
}
|
|
// 请求算法字典字段
|
const taskAlgorithm = ref([])
|
const requestDictionary = () => {
|
getMultipleDictionary('SF').then(res => {
|
if (res.code !== 0) {
|
// 处理数据
|
let result = res.data.data['SF']
|
taskAlgorithm.value = result.map(item => ({
|
checked: false,
|
text: item.dictValue,
|
value: item.dictKey,
|
}))
|
}
|
})
|
}
|
function onClickOverlay () {
|
taskAlgorithm.value
|
sfValue.value = taskAlgorithm.value.filter(item => item.checked).map(item => item.text)
|
formParams.value.ai_types = taskAlgorithm.value.filter(item => item.checked).map(item => item.value)
|
sfShow.value = false
|
}
|
function handleCheckboxChange (item, index) {
|
item.checked = !item.checked
|
let result = taskAlgorithm.value.filter(item => item.checked)
|
if (item.checked && result.length > 3) {
|
nextTick(() => {
|
item.checked = false
|
})
|
}
|
}
|
// 获取列表
|
let pageParams = ref({
|
wayline_id: '',
|
type: 0,
|
polygon: [],
|
map_lat_lng_list: [],
|
safe_height: 120,
|
point_height: 120,
|
sn: route.query.device_sn,
|
})
|
let pagingParams = ref({
|
current: 1,
|
size: 10,
|
})
|
let tableData = ref([])
|
async function getTableList () {
|
pageParams.value.wayline_id = formParams.value.file_id
|
await getFlyingNestBy(pageParams.value, pagingParams.value).then(res => {
|
if (res.data.data && res.data.data.length > 0) {
|
tableData.value = res.data.data.map((item, index) => ({
|
...item,
|
checked: index === 0 ? true : false,
|
estimated_arrival_time: `${item.estimated_arrival_time > 0 ? _.round(item.estimated_arrival_time / 60, 0) : 0}`,
|
flight_range: `${item.flight_range > 0 ? _.round(item.flight_range / 1000, 0) : 0}`,
|
minute: item.estimated_arrival_time > 0 ? _.round(item.estimated_arrival_time / 60, 0) : 0,
|
}))
|
formParams.value.dock_sns = [tableData.value[0].device_sn]
|
}
|
})
|
}
|
// 航线拆分请求
|
async function getSplitTableList (arr) {
|
let paramsXYZ = arr.map(item => ({
|
x: item.longitude,
|
y: item.latitude,
|
}))
|
const res = await getWaylineSplit({
|
points: paramsXYZ,
|
sns: [],
|
type: 0,
|
safe_height: 120,
|
point_height: 120,
|
})
|
isDisabled.value = true
|
tableData.value = (res.data?.data || []).map((item, index) => ({
|
...item,
|
checked: item.drone_wayline && item.drone_wayline.length > 0 ? true : false,
|
estimated_arrival_time: `${item.estimated_arrival_time > 0 ? _.round(item.estimated_arrival_time / 60, 0) : 0}`,
|
flight_range: `${item.flight_range > 0 ? _.round(item.flight_range / 1000, 0) : 0}`,
|
minute: item.estimated_arrival_time > 0 ? _.round(item.estimated_arrival_time / 60, 0) : 0,
|
}))
|
formParams.value.dock_sns = tableData.value.filter(item => item.checked).map(item => item.device_sn)
|
}
|
|
function checkedTable (item) {
|
if (isDisabled.value) {
|
return showNotify({ type: 'warning', message: '该航线需要多台无人机共同完成!' })
|
}
|
// 全部设置成false
|
tableData.value.forEach(i => {
|
i.checked = false
|
})
|
item.checked = true
|
formParams.value.dock_sns = [item.device_sn]
|
// 清除航线 针对单航线切换
|
if (currentPolylineArr && currentPolylineArr.length > 0) {
|
currentPolylineArr.map(item => {
|
map.removeLayer(item)
|
})
|
}
|
if (markersArr && markersArr.length > 0) {
|
markersArr.map(item => {
|
map.removeLayer(item)
|
})
|
}
|
const pointList = _.clone(savePointList)
|
pointList.unshift({ latitude: item.latitude, longitude: item.longitude })
|
let points = pointList.map(item => {
|
return [item.latitude, item.longitude]
|
})
|
renderLineAndPoint(points, 0)
|
}
|
|
function handleSubmit () {
|
if (loading.value) return
|
if (formParams.value.name === '') {
|
showNotify({ type: 'warning', message: '请输入任务名称' })
|
return
|
}
|
if (time.value === '') {
|
showNotify({ type: 'warning', message: '请选择日期' })
|
return
|
}
|
if (lineName.value === '') {
|
showNotify({ type: 'warning', message: '请选择航线' })
|
return
|
}
|
formParams.value.begin_time = `${timeDate.value[0]}-${timeDate.value[1]}-${timeDate.value[2]} 00:00:00`
|
formParams.value.end_time = `${timeDate.value[0]}-${timeDate.value[1]}-${timeDate.value[2]} 23:59:59`
|
|
const now = new Date()
|
const selectedDate = `${timeDate.value[0]}-${timeDate.value[1]}-${timeDate.value[2]}`
|
if (dayjs(now).isSame(selectedDate, 'day')) {
|
const nowTime = dayjs().format('HH:mm')
|
if (nowTime > formParams.value.execute_time_arr[0]) {
|
showNotify({ type: 'warning', message: '任务执行时间已超时,请重新选择时间' })
|
return
|
}
|
}
|
|
loading.value = true
|
createTask(formParams.value)
|
.then(res => {
|
if (res.data.code === 0) {
|
showNotify({ type: 'success', message: '发布成功' })
|
loading.value = false
|
const transmitData = { data: { type: 'submitSuccess', fun: 'add' } }
|
wx.miniProgram.switchTab({ url: `/pages/inspectionTask/index` })
|
wx.miniProgram.postMessage(transmitData)
|
uni.postMessage(transmitData)
|
}
|
})
|
.finally(() => {
|
loading.value = false
|
})
|
}
|
let markersLayer = null
|
// 全屏地图
|
async function handleMapClick () {
|
toggleFullscreen()
|
}
|
|
async function toggleFullscreen () {
|
if (isFullscreen.value) {
|
exitFullscreen()
|
} else {
|
enterFullscreen()
|
}
|
}
|
async function enterFullscreen () {
|
originalPosition = {
|
center: map.getCenter(),
|
zoom: map.getZoom(),
|
}
|
isFullscreen.value = true
|
|
await nextTick()
|
|
setTimeout(() => {
|
refreshMap()
|
}, 350)
|
}
|
|
async function exitFullscreen () {
|
isFullscreen.value = false
|
nextTick(() => {
|
setTimeout(() => {
|
refreshMap()
|
// 恢复原始位置
|
if (originalPosition) {
|
map.setView(originalPosition.center, originalPosition.zoom)
|
}
|
}, 350)
|
})
|
}
|
|
function refreshMap () {
|
if (map) {
|
map.invalidateSize()
|
// map.fire('resize');
|
setTimeout(() => {
|
// map.setView(map.getCenter());
|
}, 50)
|
}
|
}
|
let boundaryInstance = useAreaBoundary()
|
const initMap = async () => {
|
if (map) return
|
map = L.map('taskMap', {
|
preferCanvas: true,
|
crs: L.CRS.EPSG4326,
|
zoomControl: false,
|
attributionControl: false,
|
doubleClickZoom: false,
|
editable: true, //绘制控件
|
}).setView([25.992338, 114.823254], 3)
|
boundaryInstance.initBoundary(map).then(res => {
|
if (route.query.latitude && route.query.longitude) {
|
map.setView([route.query.latitude, route.query.longitude], 13)
|
} else {
|
boundaryInstance.flyMaxBoundary()
|
}
|
})
|
|
markersLayer = L.layerGroup().addTo(map) // 创建一个标注层,便于管理和移除
|
}
|
|
onMounted(async () => {
|
requestDictionary()
|
getRouteList()
|
await nextTick()
|
initMap()
|
map.addLayer(layers[0].map)
|
})
|
|
onUnmounted(() => {
|
// EventBus.off('mapSetView', mapSetView)
|
})
|
</script>
|
<style scoped lang="scss">
|
.add-task {
|
width: 100%;
|
height: 100vh;
|
overflow: auto;
|
|
.task-map {
|
position: relative;
|
width: 100%;
|
height: 206px;
|
|
.sf-img {
|
position: absolute;
|
top: 8px;
|
right: 11px;
|
z-index: 999;
|
}
|
}
|
|
#taskMap {
|
width: 100%;
|
height: 100%;
|
}
|
|
.fullscreen {
|
position: fixed;
|
top: 0;
|
left: 0;
|
width: 100vw;
|
height: 100vh;
|
z-index: 997;
|
}
|
|
.task-form {
|
z-index: 0;
|
font-family: Source Han Sans CN, Source Han Sans CN;
|
|
.task-title {
|
height: 32px;
|
line-height: 32px;
|
font-weight: bold;
|
font-size: 16px;
|
color: #222324;
|
padding-left: 12px;
|
padding-top: 6px;
|
}
|
|
.card {
|
margin: 12px;
|
background: #ffffff;
|
border-radius: 6px 6px 6px 6px;
|
|
.label {
|
padding-top: 8px;
|
padding-left: 12px;
|
color: #222324;
|
|
span {
|
color: red;
|
}
|
}
|
|
.van-cell {
|
border-radius: 6px 6px 6px 6px;
|
}
|
|
:deep(.van-field__value) {
|
border-bottom: 1px solid #efefef;
|
}
|
|
:deep(.van-cell__right-icon) {
|
margin-left: 0px;
|
border-bottom: 1px solid #efefef;
|
}
|
|
.popup-content {
|
margin: 12px;
|
padding: 8px;
|
|
.item-content {
|
display: flex;
|
align-items: center;
|
|
img {
|
width: 20px;
|
height: 20px;
|
}
|
|
.empty {
|
width: 20px;
|
height: 20px;
|
border: 1px solid #f5f5f5;
|
}
|
|
.text {
|
margin-left: 18px;
|
height: 40px;
|
line-height: 40px;
|
width: 80%;
|
font-family: Source Han Sans CN, Source Han Sans CN;
|
font-weight: 400;
|
font-size: 14px;
|
color: #222324;
|
border-bottom: 1px solid #f5f5f5;
|
}
|
}
|
}
|
}
|
|
.task-list {
|
border: 1px solid greenyellow;
|
width: 90%;
|
height: 38px;
|
line-height: 38px;
|
background: #ffffff;
|
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.03);
|
border-radius: 20px 20px 20px 20px;
|
border: 1px solid #e9e9e9;
|
font-weight: 400;
|
display: flex;
|
justify-content: space-around;
|
margin: 14px auto;
|
}
|
|
.name {
|
font-weight: bold;
|
}
|
|
.active {
|
background: #1d6fe9;
|
color: white;
|
}
|
|
.task-btn {
|
text-align: center;
|
margin: 10px auto;
|
margin-bottom: 20px;
|
height: 38px;
|
width: 60%;
|
|
:deep(.van-button) {
|
height: 38px;
|
line-height: 38px;
|
}
|
}
|
}
|
}
|
</style>
|