<template>
|
<el-dialog
|
class="gd-dialog"
|
v-model="visible"
|
:title="titleEnum[dialogMode]"
|
@closed="visible = false"
|
width="80%"
|
destroy-on-close
|
:close-on-click-modal="false"
|
>
|
<div class="content" style="display: flex">
|
<div class="processBox" v-if="dialogMode !== 'add' && processList.length">
|
<el-timeline class="gd-timeline">
|
<el-timeline-item
|
v-for="(activity, index) in processList"
|
:key="index"
|
:icon="Check"
|
:type="index === processList.length - 1 ? 'success' : 'info'"
|
:color="activity.color"
|
:hollow="activity.hollow"
|
:timestamp="activity.flowDesc.split(' ')[1]"
|
>
|
<div class="item-content">
|
<div>{{ activity.flowDesc.split(' ')[0] }}</div>
|
<div class="flowName">{{ activity.flowName }}</div>
|
</div>
|
</el-timeline-item>
|
</el-timeline>
|
</div>
|
<div class="leftBox">
|
<div v-if="dialogReadonly">
|
<div class="detail-title">工单详情</div>
|
<el-row class="detail-row-view">
|
<el-col :span="12">
|
<div class="label">工单名称</div>
|
<div class="val">{{ formData.workOrderName }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">工单类型</div>
|
<div class="val">{{ getDictLabel(formData.workOrderType, dictObj.workOrderType) }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">执行时间范围</div>
|
<div class="val">
|
{{ dayjs(formData.executeStartTime).format('YYYY-MM-DD') }} ~
|
{{ dayjs(formData.executeEndTime).format('YYYY-MM-DD') }}
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">设备需求</div>
|
<div class="val">{{ getDictLabel(formData.deviceLoadDemand, dictObj.deviceLoadDemand) }}</div>
|
</el-col>
|
<el-col :span="24">
|
<div class="label">工单备注</div>
|
<div class="val">{{ formData.remark }}</div>
|
</el-col>
|
</el-row>
|
<div class="detail-title" :style="{ marginTop: pxToRem(20) }">已选设备</div>
|
<el-table class="setHeight" ref="deviceTableRef" :data="deviceList" row-key="id">
|
<el-table-column prop="nickname" label="设备名称" />
|
<el-table-column prop="deviceName" label="设备型号" />
|
<el-table-column prop="devicePayload" label="设备负载" />
|
</el-table>
|
<template v-if="hasPatrolTaskList">
|
<div class="detail-title" :style="{ marginTop: pxToRem(20) }">巡查任务名称</div>
|
<el-table class="setHeight" :data="patrolTaskList" row-key="id">
|
<el-table-column prop="patrolTaskName" show-overflow-tooltip label="巡查任务名称" />
|
<el-table-column prop="patrolTaskType" show-overflow-tooltip label="巡查任务类型">
|
<template v-slot="{ row }">
|
{{ getDictLabel(row.patrolTaskType, dictObj.patrolTaskType) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="taskStatus" show-overflow-tooltip label="任务状态">
|
<template v-slot="{ row }">
|
{{ getDictLabel(row.taskStatus, dictObj.taskStatus) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="executeTime" show-overflow-tooltip label="任务执行时间" />
|
<el-table-column prop="recommendFlyerName" show-overflow-tooltip label="选择飞手" />
|
<el-table-column prop="deviceName" show-overflow-tooltip label="选择设备" />
|
</el-table>
|
</template>
|
</div>
|
<el-form
|
class="gd-dialog-form"
|
v-else
|
ref="formRef"
|
:model="formData"
|
:rules="rules"
|
:disabled="dialogReadonly"
|
label-width="90px"
|
>
|
<el-row>
|
<el-col :span="12">
|
<el-form-item label="工单名称" prop="workOrderName">
|
<el-input class="gd-input" v-model="formData.workOrderName" placeholder="请输入" clearable />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="工单类型" prop="workOrderType">
|
<el-select
|
class="gd-select"
|
popper-class="gd-select-popper"
|
v-model="formData.workOrderType"
|
placeholder="请选择"
|
clearable
|
>
|
<el-option
|
v-for="item in dictObj.workOrderType"
|
:key="item.dictKey"
|
:label="item.dictValue"
|
:value="item.dictKey"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="执行时间" prop="executeStartTime">
|
<el-date-picker
|
class="gd-date-picker"
|
popper-class="gd-date-picker-popper"
|
v-model="dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="设备需求" prop="deviceLoadDemand">
|
<el-select
|
class="gd-select"
|
popper-class="gd-select-popper"
|
v-model="formData.deviceLoadDemand"
|
placeholder="请选择"
|
clearable
|
@change="loadDemandChange"
|
>
|
<el-option
|
v-for="item in dictObj.deviceLoadDemand"
|
:key="item.dictKey"
|
:label="item.dictValue"
|
:value="item.dictKey"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="24">
|
<el-form-item label="工单备注" prop="remark">
|
<el-input
|
class="gd-input"
|
type="textarea"
|
:rows="4"
|
v-model="formData.remark"
|
placeholder="请输入"
|
clearable
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
<div class="detail-title" :style="{ marginTop: pxToRem(20) }">工单执行范围</div>
|
<CommonCesiumMap
|
ref="mapRef"
|
class="leftMap gd-cesium"
|
:active="visible"
|
:flat-mode="false"
|
:terrain="true"
|
:layer-mode="4"
|
:boundary="false"
|
:zoomToBoundary="false"
|
@ready="mapReady"
|
/>
|
</div>
|
<div class="rightBox" v-if="!dialogReadonly">
|
<div class="detail-title">推荐设备</div>
|
<template v-if="formData.deviceLoadDemand && pointList.length">
|
<el-table
|
ref="deviceTableRef"
|
:data="deviceList"
|
row-key="id"
|
@selection-change="handleDeviceSelectionChange"
|
>
|
<el-table-column type="selection" width="55" :reserve-selection="true" />
|
<el-table-column prop="nickname" label="设备名称" />
|
<el-table-column prop="deviceName" label="设备型号" />
|
<el-table-column prop="devicePayload" label="设备负载" />
|
</el-table>
|
</template>
|
<div v-else class="tipSelect">请先选择设备需求并在地图上绘制工单范围</div>
|
</div>
|
</div>
|
|
<template #footer>
|
<template v-if="requester">
|
<el-button
|
v-if="['11'].includes(gdStatus) || suddenlyEdit || (!dialogReadonly && !formData.id)"
|
:loading="submitting"
|
:disabled="submitting"
|
@click="handleSubmit"
|
>
|
{{ gdStatus === '11' || !gdStatus ? '发布' : '提交修改' }}
|
</el-button>
|
<el-button v-if="['11', '21', '23', '24', '25', '31', '60'].includes(gdStatus)" @click="viewDescription">
|
{{ gdStatusObj[gdStatus].reason }}
|
</el-button>
|
<el-button @click="addDescription" v-if="['20', '24', '25', '50'].includes(gdStatus)">
|
{{ gdStatus === '50' ? '结算' : '申请取消' }}
|
</el-button>
|
<el-button @click="requestModification" v-if="['20', '25'].includes(gdStatus)">申请修改</el-button>
|
</template>
|
<template v-else>
|
<el-button v-if="['21', '23'].includes(gdStatus)" @click="viewDescription">
|
{{ gdStatusObj[gdStatus].reason }}
|
</el-button>
|
<el-button v-if="gdStatus === '10'" @click="addDescription">拒绝接单</el-button>
|
<el-button v-if="gdStatus === '10'" @click="statusChange(1)">接单</el-button>
|
<el-button v-if="gdStatus === '22'" @click="statusChange(8)">不同意修改</el-button>
|
<el-button v-if="gdStatus === '22'" @click="statusChange(7)">同意修改</el-button>
|
<el-button v-if="gdStatus === '21'" @click="addDescription">不同意取消</el-button>
|
<el-button v-if="gdStatus === '21'" @click="statusChange(5)">同意取消</el-button>
|
<el-button v-if="gdStatus === '30'" @click="addDescription">协商修改</el-button>
|
<el-button v-if="gdStatus === '31'" @click="statusChange(10)">同意</el-button>
|
</template>
|
</template>
|
|
<RefuseOrderDialog
|
ref="refuseOrderDialogRef"
|
v-if="rejectVisible"
|
v-model="rejectVisible"
|
@success="rejectSuccess"
|
/>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { computed, ref, onMounted, nextTick } from 'vue'
|
import { ElMessage } from 'element-plus'
|
import { dateRangeFormat, fieldRules, flyVisual, geomAnalysis, getDictLabel } from '@ztzf/utils'
|
import {
|
gdWorkOrderDetailApi,
|
gdWorkOrderFlowListApi,
|
gdWorkOrderHandleStatusApi,
|
gdWorkOrderSaveApi,
|
} from './orderManageApi'
|
import { gdManageDeviceListApi } from './gdManageDeviceApi'
|
import { cartesian3Convert } from '@/utils/cesium/mapUtil'
|
import * as Cesium from 'cesium'
|
import { DrawPolygon } from '@/utils/cesium/DrawPolygon'
|
import { pxToRem } from '@/utils/rem'
|
import dayjs from 'dayjs'
|
import RefuseOrderDialog from '@/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue'
|
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
|
import { gdPatrolTaskPageApi } from '@/views/orderView/orderManage/inspectionRequest/inspectionRequestApi'
|
import { Check } from '@element-plus/icons-vue'
|
// 初始化表单数据
|
const initForm = () => ({
|
workOrderName: '',
|
workOrderType: '',
|
executeStartTime: '',
|
executeEndTime: '',
|
deviceLoadDemand: '',
|
remark: '',
|
geom: '',
|
recommendDeviceIds: '',
|
})
|
|
const mapRef = ref(null)
|
const rejectVisible = ref(false)
|
const dictObj = inject('dictObj')
|
const dateRange = ref([])
|
const patrolTaskList = ref([])
|
const emit = defineEmits(['success'])
|
const formRef = ref(null) // 表单实例
|
const formData = ref(initForm()) // 表单数据
|
const visible = defineModel() // 弹框显隐
|
const dialogMode = ref('add') // 弹框模式
|
const submitting = ref(false) // 提交中
|
const dialogReadonly = computed(() => dialogMode.value === 'view')
|
const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
|
const deviceList = ref([]) // 设备列表
|
const selectedDevices = ref([]) // 选中的设备
|
const deviceTableRef = ref(null) // 设备表格实例
|
const refuseOrderDialogRef = ref(null)
|
let viewer
|
let drawPolygonExample
|
let pointList = ref([])
|
const requester = inject('requester')
|
const suddenlyEdit = ref(false)
|
const processList = ref([])
|
let viewPlane
|
const hasPatrolTaskList = computed(() => ['30', '40', '50', '60'].includes(String(formData.value.workOrderStatus)))
|
|
const gdStatusObj = {
|
'10': { reason: '拒绝原因', operationType: '2' },
|
'11': { reason: '拒绝原因' },
|
'20': { reason: '取消原因', operationType: '3' },
|
'21': { reason: '原因', operationType: '6' },
|
'22': { reason: '驳回原因' },
|
'23': { reason: '取消原因' },
|
'24': { reason: '驳回原因', operationType: '3' },
|
'25': { reason: '驳回原因', operationType: '3' },
|
'30': { reason: '修改原因', operationType: '9' },
|
'31': { reason: '修改原因' },
|
'40': { reason: '' },
|
'50': { reason: '情况说明', operationType: '11' },
|
'60': { reason: '情况说明' },
|
}
|
|
// 工单状态:0草稿、10发布中_接单中、11发布中_拒绝接单、20响应中_待拆分、21响应中_申请取消、22响应中_申请修改、23响应中_已取消、
|
// 24响应中_拒绝取消、25响应中_拒绝修改 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、50验收通过_待结算、60结算完成_已结算
|
const gdStatus = computed(() => formData.value?.workOrderStatus)
|
|
const rules = {
|
workOrderName: fieldRules(true, 50),
|
workOrderType: fieldRules(true),
|
executeStartTime: fieldRules(true),
|
deviceLoadDemand: fieldRules(true),
|
}
|
|
// 操作类型:1接单,2拒接接单,3申请取消,4申请修改, 5同意取消
|
// 6不同意取消 7.同意修改 8.不同意修改 9.协商修改,10:同意协商修改,11.结算
|
function statusChange(operationType) {
|
gdWorkOrderHandleStatusApi({
|
operationType: operationType,
|
workOrderId: formData.value.id,
|
}).then(res => {
|
visible.value = false
|
emit('success')
|
})
|
}
|
|
function requestModification() {
|
suddenlyEdit.value = true
|
dialogMode.value = 'edit'
|
nextTick(() => {
|
syncSelection()
|
viewPlane && viewer.entities.remove(viewPlane)
|
editPolygon()
|
})
|
}
|
|
function rejectSuccess() {
|
visible.value = false
|
emit('success')
|
}
|
|
// 填写说明
|
function addDescription() {
|
rejectVisible.value = true
|
nextTick(() => {
|
refuseOrderDialogRef.value.open({
|
mode: 'add',
|
row: formData.value,
|
type: gdStatusObj[gdStatus.value].operationType,
|
formLabel: gdStatusObj[gdStatus.value].reason,
|
})
|
})
|
}
|
|
// 查看说明
|
function viewDescription() {
|
rejectVisible.value = true
|
nextTick(() => {
|
refuseOrderDialogRef.value.open({
|
mode: 'view',
|
row: formData.value,
|
formLabel: gdStatusObj[gdStatus.value].reason,
|
})
|
})
|
}
|
|
// 获取推荐设备列表
|
async function getDeviceList() {
|
try {
|
const str = [...pointList.value, pointList.value[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
|
let geom = `POLYGON((${str}))`
|
const res = await gdManageDeviceListApi({
|
deviceIds: formData.value.recommendDeviceIds,
|
devicePayload: dialogMode.value === 'add' ? formData.value.deviceLoadDemand : '',
|
geom: dialogMode.value === 'add' ? geom : '',
|
})
|
deviceList.value = res?.data?.data ?? []
|
} catch (error) {
|
console.error('获取设备列表失败:', error)
|
}
|
}
|
|
// 设备需求变化
|
function loadDemandChange() {
|
formData.value.recommendDeviceIds = ''
|
selectedDevices.value = []
|
getDeviceList()
|
syncSelection()
|
}
|
|
// 设备选择变化
|
function handleDeviceSelectionChange(rows) {
|
selectedDevices.value = rows
|
formData.value.recommendDeviceIds = rows.map(item => item.id).join(',')
|
}
|
|
// 提交新增/编辑
|
async function handleSubmit() {
|
const isValid = await formRef.value?.validate().catch(() => false)
|
if (!isValid) return
|
if (!pointList.value.length) {
|
return ElMessage.warning('请绘制范围')
|
}
|
if (!formData.value.recommendDeviceIds) {
|
return ElMessage.warning('请选择设备')
|
}
|
submitting.value = true
|
try {
|
const str = [...pointList.value, pointList.value[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
|
formData.value.geom = `POLYGON((${str}))`
|
if (!formData.value.id) {
|
formData.value.workOrderStatus = '10'
|
}
|
await gdWorkOrderSaveApi(formData.value)
|
ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
|
visible.value = false
|
emit('success')
|
} finally {
|
submitting.value = false
|
}
|
}
|
|
watch(
|
() => dateRange.value,
|
newVal => {
|
const range = dateRangeFormat(dateRange.value)
|
formData.value.executeStartTime = range[0]
|
formData.value.executeEndTime = range[1]
|
}
|
)
|
|
// 加载时间线list
|
function loadList() {
|
gdWorkOrderFlowListApi({ workOrderId: formData.value.id,type: '0' }).then(res => {
|
processList.value = res.data.data
|
})
|
}
|
|
// 加载详情
|
async function loadDetail() {
|
if (!formData.value.id) return
|
loadList()
|
const res = await gdWorkOrderDetailApi({ id: formData.value.id })
|
formData.value = res?.data?.data ?? {}
|
dateRange.value = [formData.value.executeStartTime, formData.value.executeEndTime]
|
}
|
|
// 新增模式绘制
|
function addPolygon() {
|
drawPolygonExample = new DrawPolygon(viewer)
|
drawPolygonExample.initHandler(viewer)
|
drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
|
}
|
|
// 绘制完成回调
|
const drawFinished = async data => {
|
pointList.value = _.cloneDeep(data).map(item => {
|
const val = cartesian3Convert(item, viewer)
|
return { ...val, lng: val.longitude, lat: val.latitude }
|
})
|
formData.value.recommendDeviceIds = ''
|
selectedDevices.value = []
|
await getDeviceList()
|
syncSelection()
|
}
|
|
// 编辑面
|
function editPolygon() {
|
if (!formData.value?.geom) return
|
pointList.value = geomAnalysis(formData.value.geom)
|
drawPolygonExample?.destroy()
|
drawPolygonExample = new DrawPolygon(viewer)
|
let noClosedList = _.cloneDeep(pointList.value)
|
noClosedList.pop()
|
drawPolygonExample.initPolygon(
|
viewer,
|
noClosedList.map(item => ({ lng: item.longitude, lat: item.latitude }))
|
)
|
drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
|
}
|
|
// 查看面
|
function viewPolygon() {
|
if (!formData.value?.geom) return
|
pointList.value = geomAnalysis(formData.value.geom)
|
const result = pointList.value.map(item => [item.longitude, item.latitude]).flat()
|
viewPlane = viewer.entities?.add({
|
customType: 'control_group',
|
position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
|
polyline: {
|
positions: Cesium.Cartesian3.fromDegreesArray(result),
|
clampToGround: true,
|
width: 3,
|
material: Cesium.Color.RED,
|
},
|
})
|
flyVisual({
|
positionsData: pointList.value.map(i => [i.longitude, i.latitude, i.height || 0]),
|
viewer,
|
})
|
}
|
|
// 同步关联场景
|
function syncSelection() {
|
if (!deviceTableRef.value) return
|
deviceTableRef.value.clearSelection()
|
const deviceIds = formData.value.recommendDeviceIds.split(',')
|
deviceList.value.forEach(device => {
|
if (deviceIds.includes(String(device.id))) {
|
deviceTableRef.value?.toggleRowSelection(device, true)
|
}
|
})
|
}
|
|
// 获取巡检任务
|
function gdPatrolTaskPage() {
|
gdPatrolTaskPageApi({ current: 1, size: 1000, workOrderId: formData.value.id }).then(res => {
|
patrolTaskList.value = res.data.data.records || []
|
})
|
}
|
|
function setMapDevice() {
|
gdManageDeviceListApi().then(res => {
|
renderingDevice(res?.data?.data || [])
|
})
|
}
|
|
import droneIcon from '@/assets/images/orderView/orderManage/drone.png'
|
|
function renderingDevice(list) {
|
// 渲染红点
|
list.forEach(item => {
|
const position = Cesium.Cartesian3.fromDegrees(item.longitude || 115.0, item.latitude || 27.11)
|
viewer.entities.add({
|
position: position,
|
billboard: {
|
image: droneIcon,
|
width: 40,
|
height: 40,
|
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
},
|
})
|
})
|
}
|
|
// 打开弹框
|
async function open({ mode = 'add', row } = {}) {
|
dialogMode.value = mode
|
formData.value = dialogMode.value === 'add' ? initForm() : row
|
initMap()
|
if (dialogMode.value === 'add') {
|
addPolygon()
|
} else {
|
await loadDetail()
|
hasPatrolTaskList.value && gdPatrolTaskPage()
|
dialogMode.value === 'edit' ? editPolygon() : viewPolygon()
|
}
|
// setMapDevice()
|
await nextTick()
|
syncSelection()
|
getDeviceList()
|
}
|
|
// 初始化地图实例
|
function initMap() {
|
if (viewer) return
|
const map = mapRef.value?.getMap()
|
viewer = map?.viewer || null
|
}
|
|
function mapReady() {
|
dialogMode.value === 'add' && mapRef.value.flyBoundary()
|
}
|
|
defineExpose({ open })
|
</script>
|
|
<style lang="scss" scoped>
|
.content {
|
display: flex;
|
gap: 0 20px;
|
|
.leftBox {
|
width: 0;
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
|
.gd-cesium {
|
width: 100%;
|
height: 423px;
|
}
|
}
|
|
.rightBox {
|
width: 350px;
|
.title {
|
font-weight: 500;
|
font-size: 14px;
|
color: #272e37;
|
}
|
}
|
|
.processBox {
|
width: 312px;
|
.item-content {
|
position: relative;
|
.flowName {
|
width: 80px;
|
position: absolute;
|
left: -120px;
|
top: 0px;
|
}
|
}
|
}
|
}
|
|
.tipSelect {
|
text-align: center;
|
height: 200px;
|
line-height: 200px;
|
}
|
</style>
|