<template>
|
<div class="create-plan-wrapper">
|
<div class="header">
|
新建计划
|
</div>
|
<div class="content">
|
<a-form ref="valueRef" layout="horizontal" :hideRequiredMark="true" :rules="rules" :model="planForm"
|
labelAlign="left">
|
<a-form-item label="计划名称" name="name" :labelCol="{ span: 23 }">
|
<a-input style="background: black;" placeholder="请输入计划名称" v-model:value="planForm.name" />
|
</a-form-item>
|
|
<!-- 航线 -->
|
<a-form-item label="执行航线" :wrapperCol="{ offset: 12 }" name="fileId">
|
<router-link :to="{ name: 'select-plan' }" @click="selectRoute">
|
选择航线
|
</router-link>
|
</a-form-item>
|
|
<a-form-item v-if="planForm.fileId" style="margin-top: -15px;">
|
<div class="wayline-panel" style="padding-top: 5px;">
|
<div class="title">
|
<a-tooltip :title="wayline.name">
|
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
|
{{ wayline.name }}
|
</div>
|
</a-tooltip>
|
|
<div class="ml10">
|
<UserOutlined />
|
</div>
|
|
<a-tooltip :title="wayline.user_name">
|
<div class="ml5 pr10"
|
style="width: 80px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
|
{{ wayline.user_name }}
|
</div>
|
</a-tooltip>
|
</div>
|
<div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
|
<span>
|
<RocketOutlined />
|
</span>
|
<span class="ml5">{{
|
Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(wayline.drone_model_key)]
|
}}</span>
|
<span class="ml10">
|
<CameraFilled style="border-top: 1px solid; padding-top: -3px;" />
|
</span>
|
<span class="ml5" v-for="payload in wayline.payload_model_keys" :key="payload.id">
|
{{ Object.keys(EDeviceType)[Object.values(EDeviceType).indexOf(payload)] }}
|
</span>
|
</div>
|
<div class="mt5 ml10" style="color: hsla(0,0%,100%,0.35);">
|
<span class="mr10">Update at {{ new Date(wayline.update_time).toLocaleString() }}</span>
|
</div>
|
</div>
|
</a-form-item>
|
|
<!-- 设备 -->
|
<a-form-item label="执行设备" :wrapperCol="{ offset: 12 }" v-model:value="planForm.dockSn" name="dockSn">
|
<router-link :to="{ name: 'select-plan' }" @click="selectDevice">选择设备
|
</router-link>
|
</a-form-item>
|
<a-form-item v-if="planForm.dockSn" style="margin-top: -15px;">
|
<div class="panel" style="padding-top: 5px;" @click="selectDock(dock)">
|
<div class="title">
|
<a-tooltip :title="dock.nickname">
|
<div class="pr10" style="width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
|
{{ dock.nickname }}
|
</div>
|
</a-tooltip>
|
</div>
|
<div class="ml10 mt5" style="color: hsla(0,0%,100%,0.65);">
|
<span>
|
<RocketOutlined />
|
</span>
|
<span class="ml5">{{ dock.children?.nickname ?? 'No drone' }}</span>
|
</div>
|
</div>
|
</a-form-item>
|
|
<!-- 任务策略 -->
|
<a-form-item label="任务策略" name="taskType" class="plan-timer-form-item" :labelCol="{ span: 23 }">
|
<div style="white-space: nowrap;">
|
<a-radio-group class="group_radio" v-model:value="planForm.taskType" button-style="solid"
|
@change="taskTypeChange">
|
<a-radio-button v-for="type in TaskTypeOptions" :value="type.value" :key="type.value">{{
|
type.label
|
}}
|
</a-radio-button>
|
</a-radio-group>
|
|
</div>
|
|
</a-form-item>
|
|
<!-- 单次定时- -->
|
<a-form-item label="执行时间" v-if="planForm.taskType === TaskType.Timed" name="executeSingleTime"
|
:labelCol="{ span: 23 }">
|
<a-date-picker v-model:value="planForm.executeSingleTime" format="YYYY-MM-DD HH:mm"
|
valueFormat="YYYY-MM-DD HH:mm" show-time placeholder="请选择日期" />
|
</a-form-item>
|
|
<!--重复定时-->
|
<!--执行日期-->
|
<a-form-item v-if="planForm.taskType === TaskType.RepeatTimed || planForm.taskType === TaskType.Continuous"
|
label="执行日期" name="repeatExecuteTimeArr" :labelCol="{ span: 23 }">
|
<a-range-picker v-model:value="planForm.repeatExecuteTimeArr" format="YYYY-MM-DD" valueFormat="YYYY-MM-DD"
|
:placeholder="['开始日期', '结束日期']" />
|
</a-form-item>
|
|
<!--执行时间-->
|
<a-form-item v-if="planForm.taskType === TaskType.RepeatTimed" label="执行时间" name="repeatTimeArr"
|
:labelCol="{ span: 23 }">
|
<div v-for="(item, index) in repeatTimeArr" :key="index">
|
<a-time-picker style="background: black" v-model:value="item.value" placeholder="请选择时间" format="HH:mm"
|
valueFormat="HH:mm" />
|
|
<a-button @click="addTime" type="default" shape="circle" :size="btnSize">
|
<template #icon>
|
<PlusCircleOutlined />
|
</template>
|
</a-button>
|
|
<a-button @click="deleteTime(item)" type="default" shape="circle" :size="btnSize">
|
<template #icon>
|
<MinusCircleOutlined />
|
</template>
|
</a-button>
|
</div>
|
</a-form-item>
|
<a-form-item v-if="planForm.taskType === TaskType.Continuous" label="执行时间" name="repeatTimeArr"
|
:labelCol="{ span: 23 }">
|
<div v-for="(item, index) in repeatTimeArr" :key="index">
|
<a-time-picker style="background: black" v-model:value="item.value[0]" placeholder="请选择时间" format="HH:mm"
|
valueFormat="HH:mm" />
|
<div style="color: white">-</div>
|
<a-time-picker style="background: black" v-model:value="item.value[1]" placeholder="请选择时间" format="HH:mm"
|
valueFormat="HH:mm" />
|
|
<a-button @click="addTime" type="default" shape="circle" :size="btnSize">
|
<template #icon>
|
<PlusCircleOutlined />
|
</template>
|
</a-button>
|
|
<a-button @click="deleteTime(item)" type="default" shape="circle" :size="btnSize">
|
<template #icon>
|
<MinusCircleOutlined />
|
</template>
|
</a-button>
|
</div>
|
</a-form-item>
|
|
<!-- 任务开始执行的电量 -->
|
<a-form-item v-if="planForm.taskType === TaskType.Continuous" label="任务开始执行的电量" :labelCol="{ span: 23 }"
|
name="startPower">
|
<a-input style="background: black;" placeholder="请输入计划名称" v-model:value="planForm.startPower" />
|
|
</a-form-item>
|
|
<!--重复频率-->
|
<a-form-item v-if="planForm.taskType === TaskType.RepeatTimed || planForm.taskType === TaskType.Continuous"
|
label="重复频率" name="frequencyValue" :labelCol="{ span: 23 }">
|
<div>
|
<div style="color: white">每</div>
|
<a-input style="background-color: black;" placeholder="请输入计划名称" v-model:value="planForm.frequencyValue" />
|
<a-select v-model:value="planForm.frequencyType" style="width: 200px"
|
:options="FrequencyTypeOptions"></a-select>
|
</div>
|
</a-form-item>
|
|
<!--重复规则-->
|
<a-form-item v-if="planForm.taskType === TaskType.RepeatTimed || planForm.taskType === TaskType.Continuous"
|
label="重复规则" name="repeatRuleType" :labelCol="{ span: 23 }">
|
<div>
|
<a-select v-model:value="planForm.repeatRuleType" style="background-color:black;width: 200px"
|
@change="repeatRuleTypeChange" :options="RepeatRuleTypeOptions"></a-select>
|
</div>
|
|
<div v-if="planForm.repeatRuleType === RepeatRuleType.day">
|
<a-button class="btn" :class="[item.checked ? 'btn-selected' : 'btn-unselected']"
|
v-for="(item, index) in dayNumArr" :key="index" size="small" @click="selectBtn(item)">
|
{{ item.value }}
|
</a-button>
|
</div>
|
|
<div v-if="planForm.repeatRuleType === RepeatRuleType.week">
|
<a-select v-model:value="planForm.repeatRuleValueWeek[0]" style="width: 200px"
|
:options="WhichWeekOptions"></a-select>
|
<a-select v-model:value="planForm.repeatRuleValueWeek[1]" style="width: 200px"
|
:options="WhichDayOptions"></a-select>
|
</div>
|
|
</a-form-item>
|
|
<!-- 相对机场返航高度 -->
|
<a-form-item label="相对机场返航高度(ALT)" :labelCol="{ span: 23 }" name="rthAltitude">
|
<div class="form-alt">
|
<a-button class="alt-btn" type="primary" size="small" :disabled="calculateDisabled('-', 100)"
|
@click="calculate('-', 100)">-100</a-button>
|
<a-button class="alt-btn" type="primary" size="small" :disabled="calculateDisabled('-', 10)"
|
@click="calculate('-', 10)">-10</a-button>
|
<a-button class="alt-btn" type="primary" size="small" :disabled="calculateDisabled('-', 1)"
|
@click="calculate('-', 1)">-1</a-button>
|
|
<a-input class="alt-input" v-model:value="planForm.rthAltitude" :min="20" :max="1500" required />
|
|
<a-button class="alt-btn" type="primary" size="small" :disabled="calculateDisabled('+', 1)"
|
@click="calculate('+', 1)">+1</a-button>
|
<a-button class="alt-btn" type="primary" size="small" :disabled="calculateDisabled('+', 10)"
|
@click="calculate('+', 10)">+10</a-button>
|
<a-button class="alt-btn" type="primary" size="small" :disabled="calculateDisabled('+', 100)"
|
@click="calculate('+', 100)"> +100</a-button>
|
</div>
|
|
</a-form-item>
|
|
<!-- Lost Action -->
|
<a-form-item label="航线飞行中失联" :labelCol="{ span: 23 }" name="outOfControl">
|
<div class="form-outOfControl" style="white-space: nowrap;">
|
<a-radio-group v-model:value="planForm.outOfControl" button-style="solid">
|
<a-radio-button v-for="(action, acIndex) in OutOfControlActionOptions " :value="action.value"
|
:key="action.value" :style="{ marginRight: ( acIndex === 0 ? '10px' : '0px' ) }">
|
{{ action.label }}
|
</a-radio-button>
|
</a-radio-group>
|
</div>
|
</a-form-item>
|
|
<!--完成动作-->
|
<a-form-item label="完成动作" :labelCol="{ span: 23 }" name="finishAction">
|
<a-select disabled v-model:value="planForm.finishAction" style="width: 200px"
|
:options="FinishActionOptions"></a-select>
|
</a-form-item>
|
|
<!--断点续飞-->
|
<a-form-item label="自动断点续飞" :labelCol="{ span: 20 }" name="breakContinue">
|
<a-switch v-model:checked="planForm.breakContinue" :checkedValue="1" :unCheckedValue="2" />
|
</a-form-item>
|
|
<!--提交,取消按钮-->
|
<a-form-item class="width-100" style="margin-bottom: 40px;">
|
<div class="footer">
|
<a-button class="mr10" style="background: #3c3c3c;" @click="closePlan">取消
|
</a-button>
|
<a-button type="primary" @click="onSubmit" :disabled="disabled">确定
|
</a-button>
|
</div>
|
</a-form-item>
|
</a-form>
|
</div>
|
</div>
|
<div v-if="drawerVisible"
|
style="position: absolute; left: 370px; width: 280px; height: 100vh; float: right; top: 0; z-index: 1000; color: white; background: #282828;">
|
<div>
|
<router-view :name="routeName" />
|
</div>
|
<div style="position: absolute; top: 15px; right: 10px;">
|
<a style="color: white;" @click="closePanel">
|
<CloseOutlined />
|
</a>
|
</div>
|
</div>
|
</template>
|
|
<script lang="ts" setup>
|
import { computed, onMounted, onUnmounted, reactive, ref, toRaw, UnwrapRef, watch } from 'vue'
|
import {
|
CloseOutlined,
|
RocketOutlined,
|
CameraFilled,
|
UserOutlined,
|
PlusCircleOutlined,
|
MinusCircleOutlined
|
} from '@ant-design/icons-vue'
|
import { ELocalStorageKey, ERouterName } from '/@/types'
|
import { useMyStore } from '/@/store'
|
import { WaylineType, WaylineFile } from '/@/types/wayline'
|
import { Device, EDeviceType } from '/@/types/device'
|
import { createPlan, waylineJob } from '/@/api/wayline'
|
import { getRoot } from '/@/root'
|
import {
|
TaskType,
|
OutOfControlActionOptions,
|
OutOfControlAction,
|
TaskTypeOptions,
|
FrequencyTypeOptions,
|
RepeatRuleTypeOptions,
|
RepeatRuleType,
|
WhichWeekOptions, WhichDayOptions, FinishActionOptions, FrequencyType
|
} from '/@/types/task'
|
import moment, { Moment } from 'moment'
|
import { RuleObject } from 'ant-design-vue/es/form/interface'
|
import { clickPoint, pointCenter } from '/@/hooks/use-center-point'
|
|
const btnSize = 'size'
|
const root = getRoot()
|
const store = useMyStore()
|
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
|
const wayline = computed<WaylineFile>(() => {
|
return store.state.waylineInfo
|
})
|
watch(() => wayline, (newVal) => {
|
if (newVal.value.id) {
|
planForm.fileId = newVal.value.id
|
}
|
}, {
|
deep: true
|
})
|
|
const calculateDisabled = computed(() => {
|
return function (a: string, b: number) {
|
return a === '-' ? planForm.rthAltitude - b < 20 : planForm.rthAltitude + b > 1500
|
}
|
})
|
|
// 表单对象
|
|
const planForm = reactive<waylineJob>({
|
name: '新建计划',
|
fileId: '',
|
dockSn: '',
|
workspaceId: '',
|
waylineId: '',
|
taskType: TaskType.Immediate,
|
|
executeSingleTime: '',
|
repeatExecuteTimeArr: [],
|
executeStartTime: '',
|
executeEndTime: '',
|
executeRepeatTime: [],
|
frequencyValue: 1,
|
frequencyType: FrequencyType.month,
|
repeatRuleType: RepeatRuleType.day,
|
repeatRuleValueWeek: [1, 1],
|
startPower: 90,
|
rthAltitude: 20,
|
outOfControl: 0,
|
finishAction: 1,
|
breakContinue: 2,
|
})
|
|
interface commonObj {
|
index: number
|
value: any
|
checked?: boolean
|
}
|
|
const dayNumArr = ref<commonObj[]>([])
|
|
initDayNumArr()
|
|
function initDayNumArr () {
|
dayNumArr.value = []
|
for (let i = 0; i < 31; i++) {
|
dayNumArr.value.push({
|
index: i + 1,
|
value: i + 1,
|
checked: false
|
})
|
}
|
}
|
|
// 重复定时的执行时间
|
const repeatTimeArr = ref<commonObj[]>([
|
{ index: 0, value: '' },
|
])
|
|
function taskTypeChange (e: any) {
|
if (e.target.value === TaskType.Continuous) {
|
repeatTimeArr.value = [
|
{ index: 0, value: [] }
|
]
|
} else if (e.target.value === TaskType.RepeatTimed) {
|
repeatTimeArr.value = [
|
{ index: 0, value: '' }
|
]
|
}
|
}
|
|
const dock = computed<Device>(() => {
|
return store.state.dockInfo
|
})
|
|
const disabled = ref(false)
|
|
const routeName = ref('')
|
|
const drawerVisible = ref(false)
|
const valueRef = ref()
|
const rules = {
|
name: [
|
{ required: true, message: '请输入计划名称' },
|
{ max: 20, message: '长度不能超过20个字符', trigger: 'blur' }
|
],
|
// fileId: [{ required: true, message: 'Select Route' }],
|
// dockSn: [{ required: true, message: 'Select Device' }],
|
// select_execute_time: [{ required: true, message: 'Select start time' }],
|
rthAltitude: [
|
{
|
validator: async (rule: RuleObject, value: string) => {
|
if (!/^[0-9]{1,}$/.test(value)) {
|
throw new Error('RTH Altitude Relative Require number')
|
}
|
},
|
}
|
],
|
outOfControl: [{ required: true, message: 'Select Lost Action' }],
|
}
|
|
function onSubmit () {
|
valueRef.value.validate().then(() => {
|
disabled.value = true
|
const form = buildForm()
|
createPlan(form).then(res => {
|
disabled.value = false
|
}).finally(() => {
|
closePlan()
|
})
|
}).catch((e: any) => {
|
console.log('validate err', e)
|
})
|
}
|
|
// 构建表单
|
function buildForm () {
|
const form: any = {}
|
|
// 公共字段
|
form.name = planForm.name
|
form.workspaceId = store.state.common.projectId
|
form.fileId = planForm.fileId
|
form.dockSn = planForm.dockSn
|
form.taskType = planForm.taskType
|
form.rthAltitude = planForm.rthAltitude
|
form.outOfControl = planForm.outOfControl
|
form.breakContinue = planForm.breakContinue
|
form.finishAction = planForm.finishAction
|
form.status = 1
|
|
// 根据不同任务策略给form添加不同字段
|
if (planForm.taskType === TaskType.Timed) {
|
form.executeSingleTime = planForm.executeSingleTime
|
} else if (planForm.taskType === TaskType.RepeatTimed) {
|
form.executeStartTime = planForm.repeatExecuteTimeArr[0]
|
form.executeEndTime = planForm.repeatExecuteTimeArr[1]
|
|
form.executeRepeatTime = repeatTimeArr.value.map(e => e.value)
|
|
form.frequencyType = planForm.frequencyType
|
form.frequencyValue = planForm.frequencyValue
|
form.repeatRuleType = planForm.repeatRuleType
|
if (planForm.repeatRuleType === 1) {
|
form.repeatRuleValue = planForm.repeatRuleValueDay
|
} else {
|
form.repeatRuleValue = planForm.repeatRuleValueWeek
|
}
|
} else if (planForm.taskType === TaskType.Continuous) {
|
form.executeStartTime = planForm.repeatExecuteTimeArr[0]
|
form.executeEndTime = planForm.repeatExecuteTimeArr[1]
|
form.executeRepeatTime = planForm.executeRepeatTime
|
form.frequencyType = planForm.frequencyType
|
form.frequencyValue = planForm.frequencyValue
|
form.startPower = planForm.startPower
|
|
form.executeRepeatTime = repeatTimeArr.value.map(e => e.value)
|
form.repeatRuleType = planForm.repeatRuleType
|
if (planForm.repeatRuleType === 1) {
|
form.repeatRuleValue = planForm.repeatRuleValueDay
|
} else {
|
form.repeatRuleValue = planForm.repeatRuleValueWeek
|
}
|
}
|
|
console.log('表单:', form)
|
return form
|
}
|
|
function closePlan () {
|
root.$router.push('/' + ERouterName.TASK)
|
}
|
|
function closePanel () {
|
drawerVisible.value = false
|
routeName.value = ''
|
}
|
|
function selectRoute () {
|
drawerVisible.value = true
|
routeName.value = 'WaylinePanel'
|
}
|
|
function selectDevice () {
|
drawerVisible.value = true
|
routeName.value = 'DockPanel'
|
}
|
|
// 重复定时添加执行时间
|
function addTime () {
|
const index = repeatTimeArr.value[repeatTimeArr.value.length - 1].index
|
if (planForm.taskType === TaskType.RepeatTimed) {
|
repeatTimeArr.value.push({
|
index: index + 1,
|
value: ''
|
})
|
} else if (planForm.taskType === TaskType.Continuous) {
|
repeatTimeArr.value.push({
|
index: index + 1,
|
value: []
|
})
|
}
|
}
|
|
// 重复定时删除执行时间
|
function deleteTime (item: commonObj) {
|
repeatTimeArr.value = repeatTimeArr.value.filter(e => e.index !== item.index)
|
}
|
|
// 重复规则变更
|
function repeatRuleTypeChange (value: any, option: any) {
|
|
}
|
|
// 计算高度
|
function calculate (operator: string, num: number) {
|
if (operator === '+') {
|
planForm.rthAltitude = planForm.rthAltitude + num
|
} else {
|
planForm.rthAltitude = planForm.rthAltitude - num
|
}
|
}
|
|
function selectBtn (item: commonObj) {
|
console.log(item, '++++++++++++++++++++++')
|
item.checked = !item.checked
|
planForm.repeatRuleValueDay = dayNumArr.value.filter(e => e.checked).map(e => e.value)
|
console.log('dayNumArr', dayNumArr.value)
|
console.log('重复规则值:', planForm.repeatRuleValueDay)
|
}
|
|
</script>
|
|
<style lang="scss">
|
.create-plan-wrapper {
|
background-color: #232323;
|
color: fff;
|
padding-bottom: 0;
|
height: 100vh;
|
display: flex;
|
flex-direction: column;
|
width: 320px;
|
|
.header {
|
height: 52px;
|
border-bottom: 1px solid #4f4f4f;
|
font-weight: 700;
|
font-size: 16px;
|
padding-left: 10px;
|
display: flex;
|
align-items: center;
|
}
|
|
::-webkit-scrollbar {
|
display: none;
|
}
|
|
.content {
|
height: calc(100% - 54px);
|
overflow-y: auto;
|
|
form {
|
margin: 10px;
|
}
|
|
form label,
|
input,
|
.ant-input,
|
.ant-calendar-range-picker-separator,
|
.ant-input:hover,
|
.ant-time-picker .anticon,
|
.ant-calendar-picker .anticon {
|
background-color: #232323;
|
color: #fff;
|
}
|
|
.ant-input-suffix {
|
color: #fff;
|
}
|
|
.plan-timer-form-item {
|
|
.ant-radio-button-wrapper {
|
background-color: #232323;
|
color: #fff;
|
width: 23%;
|
text-align: center;
|
padding: 0;
|
|
&.ant-radio-button-wrapper-checked {
|
background-color: #1890ff;
|
}
|
}
|
|
.group_radio {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
}
|
}
|
|
.footer {
|
display: flex;
|
padding: 10px 0;
|
|
button {
|
width: 45%;
|
color: #fff;
|
border: 0;
|
}
|
}
|
}
|
|
.wayline-panel {
|
background: #3c3c3c;
|
margin-left: auto;
|
margin-right: auto;
|
margin-top: 10px;
|
height: 90px;
|
width: 95%;
|
font-size: 13px;
|
border-radius: 2px;
|
cursor: pointer;
|
|
.title {
|
display: flex;
|
color: white;
|
flex-direction: row;
|
align-items: center;
|
height: 30px;
|
font-weight: bold;
|
margin: 0px 10px 0 10px;
|
}
|
}
|
|
.panel {
|
background: #3c3c3c;
|
margin-left: auto;
|
margin-right: auto;
|
margin-top: 10px;
|
height: 70px;
|
width: 95%;
|
font-size: 13px;
|
border-radius: 2px;
|
cursor: pointer;
|
|
.title {
|
display: flex;
|
color: white;
|
flex-direction: row;
|
align-items: center;
|
height: 30px;
|
font-weight: bold;
|
margin: 0px 10px 0 10px;
|
}
|
}
|
|
.form-alt {
|
display: flex;
|
align-items: center;
|
|
.alt-btn {
|
margin: 0 2px 0 2px;
|
width: 45px;
|
padding: 0 4px;
|
}
|
|
.alt-input {
|
width: 50px;
|
padding: 0;
|
text-align: center;
|
}
|
}
|
|
.btn-selected {
|
background: #2d8cf0 !important;
|
color: white !important;
|
}
|
|
.btn-unselected {
|
background: #3c3c3c !important;
|
color: white !important;
|
}
|
</style>
|