<!-- 设备资源弹窗 -->
|
<template>
|
<el-dialog
|
v-model="dialogVisible"
|
:title="dialogTitle"
|
class="gd-dialog"
|
destroy-on-close
|
:close-on-click-modal="false"
|
>
|
<el-row class="detail-row-view" v-if="dialogType === 'view'">
|
<el-col :span="12">
|
<div class="label">设备类型</div>
|
<div class="val">{{ getDictLabel(formData.deviceType, deviceTypeOptions) }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">设备型号</div>
|
<div class="val">{{ formData.deviceName }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">设备名称</div>
|
<div class="val">{{ formData.nickname }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">负载设备</div>
|
<div class="val">{{ getDictLabel(formData.devicePayload, devicePayloadOptions) }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">流量剩余(GB)</div>
|
<div class="val">无</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">流量到期时间</div>
|
<div class="val">无</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">设备状态</div>
|
<div class="val">{{ getDictLabel(formData.modeCode, dictObj.modeCode) }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">设备SN</div>
|
<div class="val">{{ formData.deviceSn }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">设备位置</div>
|
<div class="val">{{ formData.latitude }}, {{ formData.longitude }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">保险有效期</div>
|
<div class="val">无</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">固件版本</div>
|
<div class="val">{{ formData.firmwareVersion }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">所属部门</div>
|
<div class="val">{{ formData.deviceDeptName }}</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">最新在线时间</div>
|
<div class="val">无</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="label">设备注册时间</div>
|
<div class="val">无</div>
|
</el-col>
|
</el-row>
|
|
<el-form
|
ref="formRef"
|
:model="formData"
|
:rules="rules"
|
class="gd-form"
|
label-width="160px"
|
v-else
|
>
|
<el-row>
|
<el-col :span="12">
|
<el-form-item label="设备类型" prop="deviceType">
|
<el-select
|
class="gd-select"
|
popper-class="gd-select-popper"
|
v-model="formData.deviceType"
|
placeholder="请选择"
|
:disabled="dialogType === 'view'"
|
>
|
<el-option
|
v-for="item in deviceTypeOptions"
|
: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="deviceName">
|
<el-input
|
class="gd-input"
|
v-model="formData.deviceName"
|
placeholder="请输入"
|
:disabled="dialogType === 'view'"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="设备名称" prop="nickname">
|
<el-input
|
class="gd-input"
|
v-model="formData.nickname"
|
placeholder="请输入"
|
:disabled="dialogType === 'view'"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="负载设备" prop="devicePayload">
|
<el-radio-group
|
v-model="formData.devicePayload"
|
:disabled="dialogType === 'view'"
|
>
|
<el-radio
|
v-for="item in devicePayloadOptions"
|
:key="item.dictKey"
|
:label="item.dictKey"
|
>
|
{{ item.dictValue }}
|
</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="设备状态" prop="modeCode">
|
<el-select
|
class="gd-select"
|
popper-class="gd-select-popper"
|
v-model="formData.modeCode"
|
placeholder="请选择"
|
:disabled="dialogType === 'view'"
|
>
|
<el-option
|
v-for="item in modeCodeOptions"
|
:key="item.dictKey"
|
:label="item.dictValue"
|
:value="item.dictKey"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="设备SN" prop="deviceSn">
|
<el-input
|
class="gd-input"
|
v-model="formData.deviceSn"
|
placeholder="请输入"
|
:disabled="dialogType === 'view'"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="设备位置" prop="deviceLocation">
|
<el-button class="address-btn" @click="selectLocation">
|
{{ formData.longitude }}, {{ formData.latitude }}
|
</el-button>
|
</el-form-item>
|
</el-col>
|
<!-- <el-col :span="12">
|
<el-form-item label="所属部门" prop="departmentId">
|
<el-tree-select
|
class="gd-select"
|
popper-class="gd-tree-select-popper"
|
v-model="formData.departmentId"
|
:data="deptTree"
|
:props="treeProps"
|
node-key="id"
|
check-strictly
|
clearable
|
@change="handleSearch"
|
/>
|
</el-form-item>
|
</el-col> -->
|
</el-row>
|
</el-form>
|
<!-- <div style="width: 100%; height: 400px;">
|
<CommonCesiumMap
|
ref="mapRef"
|
class="gd-cesium"
|
:active="dialogVisible"
|
:flat-mode="false"
|
:terrain="true"
|
:layer-mode="4"
|
:boundary="false"
|
:zoomToBoundary="false"
|
@ready="readyMap"
|
/>
|
</div> -->
|
<template #footer>
|
<el-button color="#F2F3F5" v-if="dialogType !== 'view'" @click="handleClose">取消</el-button>
|
<el-button
|
v-if="dialogType !== 'view'"
|
type="primary"
|
color="#4C34FF"
|
@click="handleSubmit"
|
>
|
确定
|
</el-button>
|
</template>
|
</el-dialog>
|
<el-dialog v-model="visible1"
|
title="选择设备位置"
|
class="gd-dialog"
|
destroy-on-close
|
:close-on-click-modal="false"
|
@close="positionClosed"
|
>
|
<div style="width: 100%; height: 600px;">
|
<CommonCesiumMap
|
ref="mapRef"
|
class="gd-cesium"
|
:active="visible1"
|
:flat-mode="false"
|
:terrain="true"
|
:layer-mode="4"
|
:boundary="false"
|
:zoomToBoundary="false"
|
@ready="mapReady"
|
/>
|
</div>
|
|
<template #footer>
|
<el-button color="#F2F3F5" @click="handleMapCancel">取消</el-button>
|
<el-button color="#4C34FF" type="primary" @click="handleMapConfirm">确定</el-button>
|
</template>
|
</el-dialog>
|
</template>
|
<script setup>
|
import { ref, reactive, computed, watch, inject, nextTick } from 'vue'
|
import { submitExternalEquipment } from './equipmentResourceApi'
|
import { ElMessage } from 'element-plus'
|
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
|
import * as Cesium from 'cesium'
|
import axios from 'axios'
|
import _ from 'lodash'
|
import { getDictLabel,fieldRules } from '@ztzf/utils'
|
|
const props = defineProps({
|
modelValue: {
|
type: Boolean,
|
default: false
|
},
|
dialogType: {
|
type: String,
|
default: 'add', // add, edit, view
|
validator: (value) => ['add', 'edit', 'view'].includes(value)
|
},
|
equipmentData: {
|
type: Object,
|
default: () => ({})
|
}
|
})
|
|
const dictObj = inject('dictObj')
|
|
const visible1 = ref(false) // 弹框显隐
|
const mapRef = ref(null)
|
const tempLocation = ref({ longitude: null, latitude: null })
|
|
|
|
const deptTree = ref([]) // 部门树
|
const treeProps = {
|
label: 'name',
|
children: 'children',
|
}
|
|
// 设备类型(0机场,1无人机)
|
const modeCodeOptions = ref([
|
{ dictKey: 0, dictValue: '在线' },
|
{ dictKey: 1, dictValue: '离线' }
|
])
|
|
// 设备类型(0机场,1无人机)
|
const deviceTypeOptions = ref([
|
{ dictKey: 0, dictValue: '机场' },
|
{ dictKey: 1, dictValue: '无人机' }
|
])
|
|
// 设备负载(0红外,1喇叭,2闪光灯)
|
const devicePayloadOptions = ref([
|
{ dictKey: '红外', dictValue: '红外' },
|
{ dictKey: '喇叭', dictValue: '喇叭' },
|
{ dictKey: '闪光灯', dictValue: '闪光灯' }
|
])
|
|
const emit = defineEmits(['update:modelValue', 'confirm'])
|
|
const dialogVisible = computed({
|
get: () => props.modelValue,
|
set: (value) => emit('update:modelValue', value)
|
})
|
|
const dialogTitle = computed(() => {
|
switch (props.dialogType) {
|
case 'add':
|
return '添加'
|
case 'edit':
|
return '编辑'
|
case 'view':
|
return '查看'
|
default:
|
return '设备'
|
}
|
})
|
|
const formRef = ref(null)
|
const initForm = () => ({
|
childSn: '', // 子设备(一般用途是机场下面的无人机)
|
deviceName: '', // 设备型号
|
devicePayload: '', // 设备负载(红外,喇叭,闪光灯)
|
deviceSn: '', // 设备编码
|
deviceType: null, // 设备类型(0机场,1无人机)
|
firmwareVersion: '', // 固件版本
|
insureExpiredTime: '', // 保险过期时间yyyy-MM-dd HH:mm:ss
|
latitude: 0, // 当前纬度
|
longitude: 0, // 当前经度
|
modeCode: null, // 设备状态:{0:"在线",1:"离线"}
|
nickname: '', // 设备昵称
|
trafficExpireTime: '', // 流量到期时间yyyy-MM-dd HH:mm:ss
|
trafficRemaining: '', // 剩余流量(单位:GB)
|
address: '', // 设备位置
|
})
|
|
const formData = ref(initForm())
|
|
|
const rules = {
|
deviceType: fieldRules(true),
|
deviceModel: fieldRules(true, 50),
|
deviceName: fieldRules(true, 50),
|
deviceSn: fieldRules(true, 50),
|
deviceType: fieldRules(true),
|
modeCode: fieldRules(true),
|
nickname: fieldRules(true, 50),
|
}
|
|
// const rules = {
|
// deviceType: [
|
// { required: true, message: '请选择', trigger: 'change' }
|
// ],
|
// deviceModel: [
|
// { required: true, message: '请输入', trigger: 'blur' }
|
// ],
|
// deviceName: [
|
// { required: true, message: '请输入', trigger: 'blur' }
|
// ],
|
// deviceStatus: [
|
// { required: true, message: '请选择', trigger: 'change' }
|
// ],
|
// deviceSn: [
|
// { required: true, message: '请输入', trigger: 'blur' }
|
// ]
|
// }
|
|
let viewer
|
let map
|
let redPointEntity
|
let leftClickBound = false
|
function setMapPoint (longitude, latitude) {
|
if (!viewer || longitude == null || latitude == null) return
|
if (!redPointEntity) {
|
redPointEntity = viewer.entities.add({
|
position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
|
point: {
|
color: Cesium.Color.RED,
|
pixelSize: 10,
|
},
|
label: {
|
text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
|
font: '14px',
|
fillColor: Cesium.Color.WHITE,
|
backgroundColor: Cesium.Color.BLACK.withAlpha(0.6),
|
backgroundPadding: new Cesium.Cartesian2(5, 5),
|
pixelOffset: new Cesium.Cartesian2(0, -20),
|
showBackground: true,
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
|
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
outlineWidth: 1,
|
outlineColor: Cesium.Color.WHITE,
|
eyeOffset: new Cesium.Cartesian3(0, 0, -20),
|
},
|
})
|
} else {
|
redPointEntity.position = Cesium.Cartesian3.fromDegrees(longitude, latitude)
|
redPointEntity.label.text = `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
|
}
|
}
|
|
async function selectLocation () {
|
visible1.value = true
|
if (viewer) return
|
await nextTick()
|
map = mapRef.value?.getMap()
|
viewer = map?.viewer || null
|
if (viewer && !leftClickBound) {
|
map.publicCesium?.addLeftClickEvent?.(null, LeftClickEvent)
|
leftClickBound = true
|
}
|
if (formData.value.longitude != null && formData.value.latitude != null) {
|
tempLocation.value = { longitude: formData.value.longitude, latitude: formData.value.latitude }
|
setMapPoint(formData.value.longitude, formData.value.latitude)
|
}
|
}
|
|
async function getLocationName () {
|
const tk = import.meta.env.VITE_APP_TDT_TOKEN
|
const { latitude, longitude } = tempLocation.value
|
const http = axios.create({ withCredentials: false })
|
const res = await http({
|
url: `https://api.tianditu.gov.cn/geocoder?postStr={'lon':${longitude},'lat':${latitude},'ver':1}&tk=${tk}`,
|
method: 'get',
|
})
|
formData.value.address = res.data.result.formatted_address
|
}
|
|
function handleMapConfirm () {
|
if (tempLocation.value.longitude != null && tempLocation.value.latitude != null) {
|
formData.value.longitude = _.round(tempLocation.value.longitude, 6)
|
formData.value.latitude = _.round(tempLocation.value.latitude, 6)
|
getLocationName()
|
}
|
visible1.value = false
|
}
|
|
function handleMapCancel () {
|
tempLocation.value = { longitude: null, latitude: null }
|
visible1.value = false
|
}
|
|
function LeftClickEvent (click) {
|
const pos = click.position // Cartesian2 屏幕坐标
|
// 屏幕坐标 -> 椭球面坐标(不考虑地形/模型)
|
const cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
|
if (!cartesian3) {
|
return { longitude: 112, latitude: 23 }
|
}
|
// 椭球面坐标 -> 经纬度
|
const carto = Cesium.Cartographic.fromCartesian(cartesian3)
|
const longitude = Cesium.Math.toDegrees(carto.longitude)
|
const latitude = Cesium.Math.toDegrees(carto.latitude)
|
tempLocation.value = { longitude, latitude }
|
// 添加红点(全局仅保留一个)并渲染经纬度标签
|
if (!redPointEntity) {
|
redPointEntity = viewer.entities.add({
|
position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
|
point: {
|
color: Cesium.Color.RED,
|
pixelSize: 10,
|
},
|
label: {
|
text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
|
font: '14px',
|
fillColor: Cesium.Color.WHITE, // 文字颜色:白色
|
backgroundColor: Cesium.Color.BLACK.withAlpha(0.6), //背景颜色
|
backgroundPadding: new Cesium.Cartesian2(5, 5), // 水平/垂直内边距(像素)
|
pixelOffset: new Cesium.Cartesian2(0, -20), // 可选:微调位置
|
showBackground: true, // 确保背景显示(某些版本需要)
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
|
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
outlineWidth: 1,
|
outlineColor: Cesium.Color.WHITE,
|
eyeOffset: new Cesium.Cartesian3(0, 0, -20), // 负值更靠近相机(显示在前)
|
},
|
})
|
} else {
|
redPointEntity.position = Cesium.Cartesian3.fromDegrees(longitude, latitude)
|
redPointEntity.label.text = `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
|
}
|
}
|
|
function positionClosed () {
|
// 清除地图相关数据
|
if (viewer && leftClickBound) {
|
map.publicCesium?.removeLeftClickEvent?.(null, LeftClickEvent)
|
leftClickBound = false
|
}
|
redPointEntity = null
|
viewer = null
|
}
|
|
// 监听设备数据变化,更新表单
|
watch(
|
() => props.equipmentData,
|
(newData) => {
|
if (newData) {
|
Object.assign(formData.value, newData)
|
} else {
|
formData.value = initForm()
|
}
|
},
|
{ deep: true, immediate: true }
|
)
|
|
// 处理关闭
|
function handleClose() {
|
dialogVisible.value = false
|
positionClosed()
|
resetForm()
|
}
|
|
// 重置表单
|
function resetForm() {
|
formRef.value?.resetFields()
|
// 重置为默认值
|
Object.assign(formData.value, {
|
childSn: '', // 子设备(一般用途是机场下面的无人机)
|
deviceName: '', // 设备型号f
|
devicePayload: '', // 设备负载(红外,喇叭,闪光灯)
|
deviceSn: '', // 设备编码
|
deviceType: null, // 设备类型(0机场,1无人机)
|
firmwareVersion: '', // 固件版本
|
insureExpiredTime: '', // 保险过期时间yyyy-MM-dd HH:mm:ss
|
latitude: 0, // 当前纬度
|
longitude: 0, // 当前经度
|
modeCode: null, // 设备状态:{0:"在线",1:"离线"}
|
nickname: '', // 设备昵称
|
trafficExpireTime: '', // 流量到期时间yyyy-MM-dd HH:mm:ss
|
trafficRemaining: '', // 剩余流量(单位:GB)
|
address: '', // 设备位置
|
departmentId: '', // 所属部门
|
})
|
}
|
|
// 处理提交
|
async function handleSubmit() {
|
const isValid = await formRef.value?.validate()
|
if (!isValid) {
|
return
|
}
|
|
try {
|
if (props.dialogType === 'add') {
|
await submitExternalEquipment(formData.value)
|
ElMessage.success('添加成功')
|
} else if (props.dialogType === 'edit') {
|
await submitExternalEquipment(formData.value)
|
ElMessage.success('编辑成功')
|
}
|
emit('confirm')
|
dialogVisible.value = false
|
resetForm()
|
} catch (error) {
|
ElMessage.error('操作失败')
|
}
|
}
|
|
// 地图加载完毕
|
function mapReady() {
|
mapRef.value.flyBoundary()
|
}
|
|
// 初始化地图实例
|
function initMap() {
|
if (viewer) return
|
const map = mapRef.value?.getMap()
|
viewer = map?.viewer || null
|
}
|
</script>
|
<style scoped lang="scss">
|
.gd-form {
|
.el-form-item {
|
margin-bottom: 20px;
|
}
|
}
|
.command-page-map-view-dialog {
|
margin: 0;
|
padding: 20px;
|
display: flex;
|
flex-direction: column;
|
width: 80%;
|
height: 812px;
|
background: #1A1A2A;
|
border-radius: 6px;
|
border: 1px solid #2E2E46;
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
|
.el-dialog__header,
|
.el-dialog__footer {
|
margin: 0;
|
padding: 0;
|
}
|
|
.el-dialog__footer {
|
margin-top: 20px;
|
text-align: center;
|
|
.el-button {
|
margin: 0 10px;
|
}
|
}
|
|
.el-dialog__body {
|
display: flex;
|
flex-direction: column;
|
height: 100%;
|
min-height: 600px;
|
padding: 0;
|
|
.dialog-container {
|
display: flex;
|
height: 100%;
|
|
.left-container {
|
width: 100%;
|
height: 100%;
|
|
.leftMap {
|
width: 100%;
|
height: 100%;
|
min-height: 600px;
|
}
|
}
|
}
|
}
|
}
|
|
</style>
|