<template>
|
<el-dialog class="command-dialog-mange" modal-class="add-edit-details" v-model="isShowAddEditDetails" :title="txtTitle"
|
width="70%" :close-on-click-modal="false" :destroy-on-close="true" @close="closeFun"
|
>
|
<div class="add-edit-details">
|
<div class="process" v-if="txtTitle !== '新建工单'">
|
<div class="order_title">{{ formParams.name }}</div>
|
<div class="custom-steps-container">
|
<!-- 标题行 -->
|
<div class="steps-titles">
|
<div class="step-title" v-for="(record, index) in formParams.record_list"
|
:class="{ active: record.user_id >= 0 }" :key="index">
|
{{ record.status_str }}
|
</div>
|
</div>
|
<!-- Element Steps 组件 -->
|
<el-steps :active="formParams.active" align-center class="custom-steps">
|
<el-step v-for="(record, index) in formParams.record_list" :key="index">
|
<template #description>
|
<span class="step-description" style="position: relative; display: inline-block">
|
{{ record.user_name }}
|
</span>
|
<span class="step-timer">
|
{{ record.interval_time_str }}
|
</span>
|
<div class="step-description">
|
{{ record.create_time_str }}
|
</div>
|
</template>
|
</el-step>
|
</el-steps>
|
</div>
|
</div>
|
<el-form ref="orderFormRef" :model="formParams" :rules="rules" label-width="100px">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="工单名称" prop="name">
|
<el-input v-model="formParams.name" placeholder="请输入工单名称" maxlength="100" show-word-limit
|
:disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
|
</el-form-item>
|
</el-col>
|
<el-col :span="6">
|
<el-form-item label="关联航线" prop="file_id">
|
<el-input
|
v-if="formParams?.wayline_file_region_vo?.is_deleted"
|
v-model="formParams.wayline_file_region_vo.name"
|
:disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
|
</el-input>
|
<el-select
|
v-if="!formParams?.wayline_file_region_vo?.is_deleted || txtTitle === '新建工单'"
|
v-model="formParams.file_id" placeholder="请选择航线"
|
filterable
|
@change="getFlyingNest"
|
:disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
|
<el-option v-for="item in lineList" :key="item.wayline_id" :label="item.name" :value="item.wayline_id" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="6">
|
<el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
|
<el-input-number class="command-input" v-model="formParams.rth_altitude" :min="50" :max="500"
|
:disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input-number>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="关联机巢" prop="device_sns">
|
<el-select v-model="formParams.device_sns" placeholder="请选择机巢" multiple
|
:disabled="(txtTitle === '工单审核' || txtTitle === '工单详情')">
|
<el-option v-for="item in drone_sns" :key="item.device_sn" :label="item.nickname"
|
:value="item.device_sn" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="6">
|
<el-form-item label="关联算法" prop="ai_types">
|
<el-select v-model="formParams.ai_types" placeholder="请选择关联算法" :multiple-limit="3" multiple
|
:disabled="logCollect || (txtTitle === '工单审核' || txtTitle === '工单详情')" @change="aiTypeChange">
|
<el-option v-for="item in aiTypeList" :key="item.dictKey" :label="item.dictValue"
|
:value="item.dictKey" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="6">
|
<el-form-item class="safe-height" label="自定义识别区" prop="enable_custom_area">
|
<el-checkbox v-model="formParams.enable_custom_area" :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-checkbox>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="12" v-if="txtTitle === '新建工单'">
|
<el-form-item label="工单内容" prop="content">
|
<el-input type="textarea" v-model="formParams.content" rows="4" placeholder="请输入工单内容" maxlength="255"
|
show-word-limit :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
|
</el-form-item>
|
</el-col>
|
|
<el-col :span="12" prop="statusStr" v-else>
|
<el-form-item label="当前状态">
|
{{ getOrderStatus(formParams.status) }}
|
</el-form-item>
|
</el-col>
|
|
<el-col :span="7">
|
<el-form-item label="周期频次" prop="date_range">
|
<el-date-picker :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'" v-model="formParams.date_range"
|
type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
|
:disabled-date="disabledDate" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="2">
|
<el-select v-model="formParams.rep_fre_type" placeholder="请选择频次"
|
:disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
|
<el-option v-for="item in weeks" :key="item" :label="item" :value="item" />
|
</el-select>
|
</el-col>
|
<el-col :span="3">
|
<el-time-picker :style="{ width: pxToRem(140) }" v-model="formParams.deal_time"
|
:disabled="txtTitle === '工单审核' || txtTitle === '工单详情'" prop="deal_time" value-format="HH:mm"
|
:picker-options="{ selectableRange: '00:00 - 23:59' }" />
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20" v-if="txtTitle === '工单审核' || txtTitle === '工单详情' || txtTitle === '工单编辑'">
|
<el-col :span="12">
|
<el-form-item label="工单内容" prop="content">
|
<el-input type="textarea" v-model="formParams.content" rows="4" placeholder="请输入工单内容" maxlength="255"
|
show-word-limit :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20" style="height: 400px">
|
<el-col :span="24">
|
<map-container v-if="isShowAddEditDetails" ref="mapContainerRef"></map-container>
|
</el-col>
|
</el-row>
|
</el-form>
|
</div>
|
<template #footer v-if="txtTitle === '新建工单'">
|
<div class="dialog-footer">
|
<el-button type="danger" @click="submitForm(1)" icon="el-icon-position">发起</el-button>
|
<!-- <el-button type="primary" @click="submitForm(0)" icon="el-icon-document-add">存草稿</el-button>-->
|
<el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
|
</div>
|
</template>
|
<template #footer v-if="txtTitle === '工单审核'">
|
<div class="dialog-footer">
|
<el-button type="primary" v-if="formParams.status == 1 && permission.orderLogpass"
|
@click="orderPass(formParams.id)">通过
|
</el-button>
|
<el-button type="danger" v-if="formParams.status == 1 && permission.rejection_btn"
|
@click="orderReject(formParams.id)">驳回
|
</el-button>
|
<el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
|
</div>
|
</template>
|
<template #footer v-if="txtTitle === '工单编辑'">
|
<div class="dialog-footer">
|
<el-button type="danger"
|
v-if="formParams.status == 0 || (formParams.status == 2 && userInfo.user_id === formParams.create_user)"
|
@click="submitForm(1)" icon="el-icon-position">发布
|
</el-button>
|
<el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { pxToRem, pxToRemNum } from '@/utils/rem'
|
import { ref, shallowRef, onMounted, computed } from 'vue'
|
import { newGetWorkspacesPage } from '@/api/resource/wayline'
|
import { getDictionaryByCode } from '@/api/system/dictbiz'
|
import { getFlyingNestBy } from '@/api/device/device'
|
import {
|
saveUpdateOrderLog,
|
orderLogDetails,
|
orderLogPass,
|
orderLogReject,
|
orderLogRecall, getWaylineMaxTerrainHeight
|
} from '@/api/tickets/orderLog'
|
import dayjs from 'dayjs'
|
import 'dayjs/locale/zh-cn' // 导入中文语言包
|
import _ from 'lodash'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import weekday from 'dayjs/plugin/weekday'
|
dayjs.extend(weekday)
|
dayjs.locale('zh-cn')
|
import { useStore } from 'vuex'
|
import { useIdentificationArea } from '@ztzf/hooks'
|
|
const emit = defineEmits(['refresh'])
|
|
const store = useStore()
|
const userInfo = computed(() => store.state.user.userInfo)
|
const permission = computed(() => store.state.user.permission)
|
|
const waylineTypes = ref('')
|
const logCollect = ref(false)
|
|
// safeHeight 计算属性
|
const safeHeight = computed(() => {
|
const height = store.state.common.safeHeight
|
return Number(height) || 0 // 确保是数字类型
|
})
|
|
// positionsArr 计算属性
|
const positionsArr = computed(() => {
|
const arr = store.state.common.positionsArr
|
return arr || [] // 确保是数组类型
|
})
|
|
const txtTitle = inject('txtTitle')
|
const rowObj = inject('rowObj')
|
|
const isShowAddEditDetails = defineModel('show')
|
const mapContainerRef = ref(null)
|
const orderFormRef = ref(null)
|
const formParams = ref({})
|
const rules = ref({
|
name: [
|
{ required: true, message: '请输入工单名称', trigger: 'blur' },
|
{ max: 100, message: '工单名称不能超过100个字', trigger: 'blur' }
|
],
|
file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
|
device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
|
content: [
|
{ required: true, message: '请输入工单内容', trigger: 'blur' },
|
{ max: 255, message: '工单内容不能超过255个字', trigger: 'blur' }
|
],
|
date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
|
rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
|
deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
|
})
|
//周期
|
const weeks = ref(['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'])
|
|
let lineList = inject('lineList')
|
let aiTypeList = inject('aiTypeList')
|
|
// 获取航线
|
// let lineList = ref([])
|
// function getLines() {
|
// const formData = {
|
// name: '',
|
// waylineType: undefined,
|
// current: 1,
|
// size: 99999999,
|
// descs: 'update_time'
|
// }
|
// newGetWorkspacesPage(formData).then(res => {
|
// lineList.value = res.data.data.records
|
// })
|
// }
|
|
function getOrderStatus (status) {
|
const statusTextMap = {
|
0: '草稿',
|
1: '待审核',
|
2: '已驳回',
|
3: '已通过'
|
}
|
return statusTextMap[status] || '未知状态'
|
}
|
|
function disabledDate (time) {
|
return time.getTime() < Date.now() - 8.64e7 // 86400000 = 24 * 60 * 60 * 1000
|
}
|
|
// 关联机巢
|
const drone_sns = ref([])
|
function getFlyingNest (waylineId) {
|
let currentLine = lineList.value.find(item => item.wayline_id === waylineId)
|
if (!formParams.ai_types || formParams.ai_types.length === 0) {
|
if (currentLine.wayline_type === '0' || currentLine.wayline_type === '3') {
|
logCollect.value = false
|
} else {
|
logCollect.value = true
|
}
|
}
|
// 选中航线对象
|
initMapLine({}, polygon => {
|
const params = {
|
type: ['2', '4', '5', '7', '8', '9', '10'].includes(currentLine.wayline_type) ? 2 : 0,
|
wayline_id: waylineId,
|
polygon
|
}
|
// 重置
|
formParams.value.device_sns = []
|
drone_sns.value = []
|
getFlyingNestBy(params).then(res => {
|
drone_sns.value = res.data.data
|
initMapLine()
|
})
|
})
|
}
|
|
// 根据算法 重新加载航线
|
function aiTypeChange(val) {
|
formParams.ai_types = val
|
waylineTypes.value = ''
|
if(val.length > 0) {
|
waylineTypes.value = '3'
|
}
|
const formData = {
|
name: '',
|
waylineType: undefined,
|
current: 1,
|
size: 99999999,
|
descs: 'update_time',
|
waylineTypes: waylineTypes.value
|
}
|
newGetWorkspacesPage(formData).then(res => {
|
lineList.value = res.data.data.records
|
})
|
}
|
|
// 获取算法
|
// let aiTypeList = ref([])
|
// function aitypeList() {
|
// getDictionaryByCode('SF').then(res => {
|
// aiTypeList.value = res.data.data['SF']
|
// })
|
// }
|
|
// initMapLine 方法
|
const initMapLine = async (infos = {}, cb = () => { }) => {
|
let currentLine = lineList.value.find(item => item.wayline_id == formParams.value.file_id)
|
|
if (!currentLine) {
|
currentLine = formParams.value.wayline_file_region_vo
|
}
|
|
await nextTick()
|
if (mapContainerRef.value?.initAddEntity) {
|
mapContainerRef.value.initAddEntity('polyline', {
|
url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key}?_t=${new Date().getTime()}`,
|
type: currentLine.wayline_type,
|
cb,
|
infos
|
})
|
}
|
}
|
|
function formatDate (date) {
|
if (!date) return undefined
|
const d = new Date(date)
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(
|
d.getDate()
|
).padStart(2, '0')} 00:00:00`
|
}
|
|
async function returnHeight () {
|
let checkedList = drone_sns.value.filter(item => formParams.value.device_sns.includes(item.device_sn))
|
const maxItem = checkedList.reduce((max, item) => {
|
return item.drone_height > max.drone_height ? item : max
|
})
|
// 返航绝对高度
|
let backHeight = formParams.value.rth_altitude // + maxItem.drone_height
|
console.log(backHeight, 'backHeight')
|
// 安全起飞高度
|
let positions = positionsArr.value
|
positions.unshift({ latitude: maxItem.latitude, longitude: maxItem.longitude, height: maxItem.drone_height })
|
positions.push({ latitude: maxItem.latitude, longitude: maxItem.longitude, height: maxItem.drone_height })
|
let resultHeight = 0
|
await getWaylineMaxTerrainHeight(positions).then(res => {
|
resultHeight = safeHeight.value > (res.data.data + 30) ? safeHeight.value : (res.data.data + 30)
|
})
|
let valueHeight = _.round(backHeight - resultHeight, 2)
|
return _.round(backHeight - resultHeight, 2)
|
}
|
|
function submitForm (status) {
|
orderFormRef.value?.validate(async (valid, fields) => {
|
if (valid) {
|
if (formParams.value?.wayline_file_region_vo?.is_deleted) {
|
await ElMessageBox.confirm(`当前的航线已被删除,是否重新选择航线?`, '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
});
|
// 如果是还确定那边重新把关联航线和机巢放出来选
|
// 确保wayline_file_region_vo对象存在再进行赋值
|
if (formParams.value && formParams.value.wayline_file_region_vo) {
|
formParams.value.wayline_file_region_vo.is_deleted = false;
|
}
|
formParams.value.file_id = '';
|
formParams.value.device_sns = [];
|
}
|
|
formParams.value.begin_time = formatDate(formParams.value.date_range[0])
|
formParams.value.end_time = formatDate(formParams.value.date_range[1])
|
// 如果选中日期包含当前天,那么选中的时间点不能小于当前时间
|
if (formParams.value.deal_time) {
|
const selectedDate = dayjs(formParams.value.date_range[0]).format('YYYY-MM-DD')
|
const selectedTime = dayjs(selectedDate + ' ' + formParams.value.deal_time).toDate()
|
const now = new Date()
|
|
if (selectedDate === dayjs().format('YYYY-MM-DD') && selectedTime < now) {
|
return ElMessage({ message: '任务时间不能小于当前时间', type: 'warning' })
|
}
|
}
|
let checkedList = drone_sns.value.filter(item => formParams.value.device_sns.includes(item.device_sn))
|
const maxItem = checkedList.reduce((max, item) => {
|
return item.drone_height > max.drone_height ? item : max
|
})
|
// 返航绝对高度
|
let backHeight = formParams.value.rth_altitude // + maxItem.drone_height
|
console.log(backHeight, 'backHeight')
|
// 安全起飞高度
|
let positions = positionsArr.value
|
positions.unshift({ latitude: maxItem.latitude, longitude: maxItem.longitude, height: maxItem.drone_height })
|
positions.push({ latitude: maxItem.latitude, longitude: maxItem.longitude, height: maxItem.drone_height })
|
let resultHeight = 0
|
let valueHeight = 0
|
await getWaylineMaxTerrainHeight(positions).then(res => {
|
resultHeight = (safeHeight.value + maxItem.drone_height) > (res.data.data + 30) ? (safeHeight.value + maxItem.drone_height) : (res.data.data + 30)
|
console.log(resultHeight, maxItem.drone_height, '999')
|
valueHeight = (resultHeight - maxItem.drone_height) > backHeight ? _.round(resultHeight - maxItem.drone_height) : 0
|
})
|
if (valueHeight > 0) {
|
await ElMessageBox.confirm(`当前返航高度存在安全隐患,建议调整为${valueHeight}米以上后进行发布`, '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
}
|
formParams.value.enable_custom_area = formParams.value.enable_custom_area ? 1 : 0
|
const submitData = {
|
...formParams.value,
|
status: status,
|
ai_types: formParams.value.ai_types?.length ? formParams.value.ai_types : []
|
}
|
await saveUpdateOrderLog(submitData)
|
if (txtTitle === '新建工单') {
|
ElMessage({ message: '工单创建成功', type: 'success' })
|
} else {
|
ElMessage({ message: '工单发布成功', type: 'success' })
|
}
|
cancel()
|
} else {
|
console.log('表单验证失败', fields)
|
}
|
})
|
}
|
// 审核通过
|
function orderPass (id) {
|
orderLogPass(id).then(res => {
|
ElMessage.success('审核通过')
|
cancel()
|
})
|
}
|
// 驳回
|
const orderReject = async (id) => {
|
try {
|
const { value } = await ElMessageBox.prompt('', '驳回原因', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
inputPlaceholder: '请输入驳回原因',
|
inputValidator: (value) => {
|
if (!value) {
|
return '驳回原因不能为空'
|
}
|
}
|
})
|
// 用户输入了内容并点击确定
|
await orderLogReject(id, value)
|
ElMessage.success('驳回成功')
|
cancel()
|
} catch (error) {
|
// 用户点击取消或关闭弹窗
|
if (error !== 'cancel' && error !== 'close') {
|
ElMessage.error('驳回失败')
|
console.error('驳回错误:', error)
|
}
|
}
|
}
|
|
const initMapPosition = async () => {
|
await nextTick()
|
|
if (mapContainerRef.value?.initAddEntity) {
|
mapContainerRef.value.initAddEntity('initPosition')
|
}
|
}
|
|
// 监听txtTitle
|
watch(isShowAddEditDetails, async (newVal, oldVal) => {
|
formParams.value = { rth_altitude: 120 }
|
if (txtTitle.value === '工单审核' || txtTitle.value === '工单详情' || txtTitle.value === '工单编辑') {
|
const response = await orderLogDetails(rowObj.value.id)
|
const data = response.data.data
|
formParams.value = {
|
...data
|
}
|
formParams.value.enable_custom_area = formParams.value.enable_custom_area === 1 ? true : false
|
drone_sns.value = data.device_list
|
initMapLine(data.device_map_infos)
|
} else {
|
initMapPosition()
|
}
|
}, {
|
immediate: true // 立即执行一次
|
})
|
function closeFun() {
|
formParams.value = {}
|
isShowAddEditDetails.value = false
|
}
|
|
// function handleBeforeClose () {
|
// cancel()
|
// }
|
function cancel () {
|
formParams.value = {}
|
isShowAddEditDetails.value = false
|
emit('refresh')
|
}
|
|
// 自定义识别区渲染
|
const {setArea,removeArea } = useIdentificationArea()
|
watch(()=>[formParams.value.enable_custom_area,formParams.value.ai_types],async () => {
|
const {enable_custom_area,ai_types} = formParams.value
|
if (enable_custom_area && ai_types?.length > 0) {
|
setArea(formParams.value.ai_types,mapContainerRef.value?.getViewer())
|
} else{
|
removeArea()
|
}
|
},{ immediate: true }) // 待验证 等后端加enable_custom_area
|
|
// onMounted(async () => {
|
// aitypeList()
|
// getLines()
|
// });
|
</script>
|
|
<style lang="scss" scoped>
|
.add-edit-details {
|
.safe-height {
|
display: flex;
|
align-items: center;
|
:deep(.el-form-item__label) {
|
width: 120px !important;
|
}
|
|
:deep(.el-input-number) {
|
width: 180px !important;
|
}
|
}
|
|
.week_pc_one {
|
flex: 1;
|
|
.week-r {
|
margin-right: 4px;
|
}
|
|
&> ::v-deep(div) {
|
width: 100% !important;
|
}
|
}
|
|
.process {
|
.order_title {
|
height: 30px;
|
line-height: 30px;
|
text-align: center;
|
font-size: 20px;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.custom-steps-container {
|
width: 100%;
|
margin: 7px 0;
|
}
|
|
.steps-titles {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 16px;
|
position: relative;
|
}
|
|
.step-title {
|
text-align: center;
|
flex: 1;
|
font-size: 16px;
|
color: #999;
|
position: relative;
|
padding-bottom: 10px;
|
}
|
|
.step-title.active {
|
color: #409eff;
|
font-weight: bold;
|
}
|
|
.custom-steps {
|
margin-top: -20px;
|
/* 向上移动与标题重叠 */
|
}
|
|
.el-step__head {
|
margin-top: 0;
|
}
|
|
.el-step__description {
|
margin-top: 8px;
|
padding: 0 20px;
|
}
|
|
.step-description {
|
font-size: 14px;
|
color: #666;
|
line-height: 2.2;
|
}
|
}
|
}
|
</style>
|