| New file |
| | |
| | | <!-- 设备资源弹窗 --> |
| | | <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">设备状态</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-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> |