| | |
| | | <template> |
| | | <el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false" |
| | | :close-on-click-modal="false"> |
| | | <el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false" :close-on-click-modal="false"> |
| | | <div class="dialog-container"> |
| | | <div class="left-container"> |
| | | <CommonCesiumMap |
| | | ref="mapRef" |
| | | class="leftMap command-cesium" |
| | | :active="visible" |
| | | :flat-mode="false" |
| | | :terrain="true" |
| | | :layer-mode="4" |
| | | :boundary="false" |
| | | /> |
| | | <div |
| | | v-if="showTypePanel && !readonly" |
| | | class="shape-type-panel" |
| | | > |
| | | <CommonCesiumMap ref="mapRef" class="leftMap command-cesium" :active="visible" :flat-mode="false" |
| | | :terrain="true" :layer-mode="4" :boundary="false" /> |
| | | <div v-if="showTypePanel && !readonly" class="shape-type-panel"> |
| | | <div class="panel-header"> |
| | | <span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span> |
| | | <el-icon class="panel-close" @click.stop="handleTypePanelClose"><Close /></el-icon> |
| | | <el-icon class="panel-close" @click.stop="handleTypePanelClose"> |
| | | <Close /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="panel-body"> |
| | | <span class="panel-label">区域类型</span> |
| | | <el-select |
| | | class="command-select" |
| | | popper-class="command-select-popper" |
| | | v-model="activeAreaType" |
| | | placeholder="请选择" |
| | | @change="handleAreaTypeChange" |
| | | > |
| | | <el-option |
| | | v-for="item in dictObj.areaType" |
| | | :key="item.dictKey" |
| | | :label="item.dictValue" |
| | | :value="item.dictKey" |
| | | /> |
| | | <el-select class="command-select" popper-class="command-select-popper" v-model="activeAreaType" |
| | | placeholder="请选择" @change="handleAreaTypeChange"> |
| | | <el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue" |
| | | :value="item.dictKey" /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <div class="map-toolbar"> |
| | | <button class="tool-btn" type="button" @click="handleDraw('polygon')"> |
| | | <span class="tool-icon polygon"></span> |
| | | <span class="tool-text">多边形</span> |
| | | </button> |
| | | <button class="tool-btn" type="button" @click="handleDraw('rectangle')"> |
| | | <span class="tool-icon rect"></span> |
| | | <span class="tool-text">矩形</span> |
| | | </button> |
| | | <button class="tool-btn" type="button" @click="handleDraw('ellipse')"> |
| | | <span class="tool-icon ellipse"></span> |
| | | <span class="tool-text">椭圆</span> |
| | | </button> |
| | | <button class="tool-btn" type="button" @click="handleDraw('buffer')"> |
| | | <span class="tool-icon buffer"></span> |
| | | <span class="tool-text">缓冲圆</span> |
| | | </button> |
| | | <button class="tool-btn danger" type="button" @click="handleClearShape"> |
| | | <span class="tool-icon trash"></span> |
| | | <span class="tool-text">删除</span> |
| | | </button> |
| | | <el-tooltip content="多边形" placement="left"> |
| | | <button class="tool-btn" type="button" @click="handleDraw('polygon')"> |
| | | <img class="tool-icon-image" :src="polygonIcon" alt="多边形" /> |
| | | </button> |
| | | </el-tooltip> |
| | | <el-tooltip content="矩形" placement="left"> |
| | | <button class="tool-btn" type="button" @click="handleDraw('rectangle')"> |
| | | <img class="tool-icon-image" :src="rectIcon" alt="矩形" /> |
| | | </button> |
| | | </el-tooltip> |
| | | <el-tooltip content="椭圆" placement="left"> |
| | | <button class="tool-btn" type="button" @click="handleDraw('ellipse')"> |
| | | <img class="tool-icon-image" :src="ellipseIcon" alt="椭圆" /> |
| | | </button> |
| | | </el-tooltip> |
| | | <el-tooltip content="缓冲圆" placement="left"> |
| | | <button class="tool-btn" type="button" @click="handleDraw('buffer')"> |
| | | <img class="tool-icon-image" :src="bufferIcon" alt="缓冲圆" /> |
| | | </button> |
| | | </el-tooltip> |
| | | <el-tooltip content="删除" placement="left"> |
| | | <button class="tool-btn danger" type="button" @click="handleClearShape"> |
| | | <img class="tool-icon-image" :src="trashIcon" alt="删除" /> |
| | | </button> |
| | | </el-tooltip> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="right-container"> |
| | | <div class="header"> |
| | | <span>{{ titleEnum[dialogMode] }}</span> |
| | | |
| | | <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon> |
| | | |
| | | <el-icon class="close-btn" @click.stop="visible = false"> |
| | | <Close /> |
| | | </el-icon> |
| | | </div> |
| | | |
| | | <div class="content" v-if="readonly"> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <div class="detail-title">绘制区域列表</div> |
| | | <div class="command-table-container"> |
| | | <div class="command-table-content"> |
| | | <el-table class="command-table" :data="shapeList" row-key="id"> |
| | | <el-table-column prop="areaType" label="类型"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(row.areaType, dictObj.areaType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="drawType" label="绘制类型"> |
| | | <template v-slot="{ row }"> |
| | | {{ drawTypeLabelMap[row.drawType] || '-' }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="detail-title">关联设备</div> |
| | | <div class="command-table-container"> |
| | | <div class="command-table-content"> |
| | | <el-table class="command-table" ref="deviceTableRef" :data="deviceOptions" |
| | | row-key="id" @row-click="handleDeviceRowClick"> |
| | | <el-table class="command-table" ref="deviceTableRef" :data="deviceOptions" row-key="id" |
| | | @row-click="handleDeviceRowClick"> |
| | | <el-table-column prop="deviceName" label="设备名称" /> |
| | | <el-table-column prop="deviceType" label="类型"> |
| | | <template v-slot="{ row }"> |
| | |
| | | <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly" |
| | | label-width="96px"> |
| | | <el-form-item label="区域名称" prop="areaName"> |
| | | <el-input class="command-input" v-model="formData.areaName" maxlength="50" |
| | | placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.areaName" maxlength="50" placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="响应机制" prop="responseMechanism"> |
| | | <el-input class="command-input" v-model="formData.responseMechanism" |
| | | maxlength="200" placeholder="请输入" clearable /> |
| | | <el-input class="command-input" v-model="formData.responseMechanism" maxlength="200" |
| | | placeholder="请输入" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="触发条件" prop="triggerCondition"> |
| | | <el-select class="command-select" popper-class="command-select-popper" |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="可飞行时段" prop="flyDateStart"> |
| | | <el-date-picker class="command-date-picker" |
| | | popper-class="command-date-picker-popper" v-model="flyDateRange" |
| | | type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" |
| | | :disabled-date="disabledFlyDate" :disabled-time="disabledFlyTime" |
| | | <el-date-picker class="command-date-picker" popper-class="command-date-picker-popper" |
| | | v-model="flyDateRange" type="datetimerange" range-separator="至" start-placeholder="开始时间" |
| | | end-placeholder="结束时间" :disabled-date="disabledFlyDate" :disabled-time="disabledFlyTime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="关联派出所" prop="policeStationId"> |
| | |
| | | |
| | | <div class="shape-table-container"> |
| | | <div class="shape-table-title">绘制区域列表</div> |
| | | <el-table class="command-table" :data="shapeList" row-key="id" height="360"> |
| | | <el-table class="command-table" :data="shapeList" row-key="id" height="auto"> |
| | | <el-table-column prop="areaType" label="类型"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(row.areaType, dictObj.areaType) }} |
| | |
| | | 关联设备 |
| | | </div> |
| | | |
| | | <el-input class="command-input" v-model="searchName" placeholder="请输入" |
| | | clearable></el-input> |
| | | <el-input class="command-input" v-model="searchName" placeholder="请输入" clearable></el-input> |
| | | </div> |
| | | |
| | | <el-form-item prop="deviceIds" label-width="0"> |
| | | <el-table class="command-table" ref="deviceTableRef" |
| | | :data="deviceOptions.filter(item => item.deviceName.includes(searchName))" row-key="id" |
| | | @selection-change="handleDeviceSelectionChange" |
| | | @row-click="handleDeviceRowClick"> |
| | | @selection-change="handleDeviceSelectionChange" @row-click="handleDeviceRowClick"> |
| | | <el-table-column type="selection" width="55" :reserve-selection="true" /> |
| | | <el-table-column prop="deviceName" label="设备名称" /> |
| | | <el-table-column prop="deviceType" label="类型"> |
| | |
| | | </el-form> |
| | | |
| | | <div class="footer"> |
| | | <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button> |
| | | <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : |
| | | '取消' |
| | | }}</el-button> |
| | | <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting" |
| | | :disabled="submitting" @click="handleSubmit"> |
| | | 确定 |
| | |
| | | import * as Cesium from 'cesium' |
| | | import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi' |
| | | import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice' |
| | | import polygonIcon from '@/assets/images/areaMap/polygon.png' |
| | | import rectIcon from '@/assets/images/areaMap/rect.png' |
| | | import ellipseIcon from '@/assets/images/areaMap/ellipse.png' |
| | | import bufferIcon from '@/assets/images/areaMap/buffer.png' |
| | | import trashIcon from '@/assets/images/areaMap/trash.png' |
| | | |
| | | const initForm = () => ({ |
| | | areaName: '', // 区域名称 |
| | |
| | | if (!isValid) return |
| | | submitting.value = true |
| | | try { |
| | | let str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',') |
| | | formData.value.geom = `POLYGON((${str}))` |
| | | await fwAreaDivideSubmitApi(formData.value) |
| | | const areaDivide = { ...formData.value } |
| | | delete areaDivide.geom |
| | | const areaDivideExtList = shapeList.value.map(shape => { |
| | | const areaTypeKey = shape?.areaType ?? '' |
| | | const areaTypeValue = getDictLabel(areaTypeKey, resolveAreaTypeOptions()) |
| | | return { |
| | | id: shape?.extId, |
| | | areaDivideId: areaDivide?.id ?? '', |
| | | areaCode: areaDivide?.areaCode ?? '', |
| | | areaTypeKey, |
| | | areaTypeValue, |
| | | geomJson: JSON.stringify(shape), |
| | | } |
| | | }) |
| | | await fwAreaDivideSubmitApi({ |
| | | areaDivide, |
| | | areaDivideExtList, |
| | | }) |
| | | const actionText = dialogMode.value === 'add' ? '新增' : '编辑' |
| | | saveOperationLog({ |
| | | requestUri: route.path, |
| | |
| | | nextForm.id = detail.id |
| | | } |
| | | formData.value = nextForm |
| | | activeAreaType.value = detail?.areaType ?? '' |
| | | flyDateRange.value = [formData.value.flyDateStart, formData.value.flyDateEnd].filter(Boolean) |
| | | syncShapeListFromDetail(detail) |
| | | } |
| | | |
| | | // 格式化可飞行时间段 |
| | |
| | | flat: true, |
| | | faceForward: true, |
| | | }), |
| | | asynchronous: false, |
| | | asynchronous: false |
| | | }) |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | function renderShapeList () { |
| | | if (!viewer || !visible.value || readonly.value) { |
| | | if (!viewer || !visible.value) { |
| | | clearShapeDisplay() |
| | | return |
| | | } |
| | |
| | | } |
| | | |
| | | function getDetectAreaTypeKey () { |
| | | const areaTypeOptions = Array.isArray(dictObj?.value?.areaType) |
| | | ? dictObj.value.areaType |
| | | : dictObj?.areaType |
| | | |
| | | const areaTypeOptions = resolveAreaTypeOptions() |
| | | if (!Array.isArray(areaTypeOptions)) return '' |
| | | const target = areaTypeOptions.find(item => item?.dictKey === '1') |
| | | return target?.dictKey ?? '' |
| | | } |
| | | |
| | | function resolveAreaTypeOptions () { |
| | | return Array.isArray(dictObj?.value?.areaType) |
| | | ? dictObj.value.areaType |
| | | : dictObj?.areaType |
| | | } |
| | | |
| | | function parseGeomJson (geomJson) { |
| | | if (!geomJson) return null |
| | | if (typeof geomJson === 'object') return geomJson |
| | | if (typeof geomJson !== 'string') return null |
| | | const trimmed = geomJson.trim() |
| | | if (!trimmed) return null |
| | | try { |
| | | return JSON.parse(trimmed) |
| | | } catch (error) { |
| | | const legacyPoints = geomAnalysis(trimmed) |
| | | if (Array.isArray(legacyPoints) && legacyPoints.length >= 3) { |
| | | return { drawType: 'polygon', points: legacyPoints } |
| | | } |
| | | } |
| | | return null |
| | | } |
| | | |
| | | function syncShapeListFromDetail (detail) { |
| | | const extList = Array.isArray(detail?.fwAreaDivideExtList) ? detail.fwAreaDivideExtList : [] |
| | | if (!extList.length) { |
| | | shapeList.value = [] |
| | | activeAreaType.value = '' |
| | | currentShapeType.value = 'polygon' |
| | | return |
| | | } |
| | | shapeList.value = extList.map((item, index) => { |
| | | const isShapePayload = item?.drawType || item?.points |
| | | if (isShapePayload) { |
| | | return { |
| | | id: item?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`, |
| | | extId: item?.extId ?? item?.id, |
| | | areaType: item?.areaType ?? '', |
| | | drawType: item?.drawType ?? 'polygon', |
| | | points: Array.isArray(item?.points) ? item.points : [], |
| | | displayPoints: Array.isArray(item?.displayPoints) ? item.displayPoints : null, |
| | | meta: item?.meta ?? null, |
| | | } |
| | | } |
| | | const parsed = parseGeomJson(item?.geomJson) || {} |
| | | const drawType = parsed?.drawType || 'polygon' |
| | | const areaType = item?.areaTypeKey ?? parsed?.areaType ?? '' |
| | | const points = Array.isArray(parsed?.points) ? parsed.points : [] |
| | | const displayPoints = Array.isArray(parsed?.displayPoints) ? parsed.displayPoints : null |
| | | const meta = parsed?.meta ?? null |
| | | return { |
| | | id: parsed?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`, |
| | | extId: item?.id, |
| | | areaType, |
| | | drawType, |
| | | points, |
| | | displayPoints, |
| | | meta, |
| | | } |
| | | }) |
| | | activeAreaType.value = shapeList.value[0]?.areaType ?? '' |
| | | currentShapeType.value = shapeList.value[0]?.drawType ?? 'polygon' |
| | | } |
| | | |
| | | function showTypePanelAtCurrent () { |
| | |
| | | |
| | | // 查看面 |
| | | function viewPolygon () { |
| | | if (!viewer) return |
| | | if (shapeList.value.length) { |
| | | renderShapeList() |
| | | return |
| | | } |
| | | if (!formData.value?.geom) return |
| | | pointList = geomAnalysis(formData.value.geom) |
| | | const result = pointList.map(item => [item.longitude, item.latitude]).flat() |
| | |
| | | |
| | | // 同步选择状态 |
| | | function syncDeviceSelection () { |
| | | if (!deviceTableRef.value) return |
| | | deviceTableRef.value.clearSelection() |
| | | const rows = [] |
| | | const arr = formData.value.deviceIds.split(',') |
| | | deviceOptions.value.forEach(row => { |
| | | if (arr.includes(row.id)) { |
| | | deviceTableRef.value.toggleRowSelection(row, true) |
| | | rows.push(row) |
| | | } |
| | | }) |
| | | selectedDeviceRows.value = rows |
| | | if (readonly.value) { |
| | | renderDeviceRanges(selectedDeviceRows.value) |
| | | return |
| | | } |
| | | if (!deviceTableRef.value) return |
| | | deviceTableRef.value.clearSelection() |
| | | rows.forEach(row => { |
| | | deviceTableRef.value.toggleRowSelection(row, true) |
| | | }) |
| | | } |
| | | |
| | | // 打开弹框 |
| | |
| | | shapeList.value = [] |
| | | activeShapeId.value = null |
| | | activeAreaType.value = '' |
| | | pointList = [] |
| | | await nextTick() |
| | | initMap() |
| | | clearShapeDisplay() |
| | |
| | | // default no draw mode |
| | | } else if (dialogMode.value === 'edit') { |
| | | await loadDetail() |
| | | pointList = geomAnalysis(formData.value.geom) || [] |
| | | if (pointList.length) { |
| | | activeShapeId.value = `shape_${Date.now()}_${Math.random().toString(16).slice(2, 6)}` |
| | | shapeList.value = [ |
| | | { |
| | | id: activeShapeId.value, |
| | | areaType: activeAreaType.value, |
| | | drawType: currentShapeType.value, |
| | | points: _.cloneDeep(pointList), |
| | | }, |
| | | ] |
| | | } |
| | | viewPolygon() |
| | | } else { |
| | | await loadDetail() |
| | |
| | | } |
| | | |
| | | .tool-btn { |
| | | padding: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 6px; |
| | | width: 64px; |
| | | padding: 8px 6px; |
| | | background: rgba(255, 255, 255, 0.08); |
| | | border: 1px solid rgba(255, 255, 255, 0.18); |
| | | border-radius: 6px; |
| | | border-radius: 8px; |
| | | color: #ffffff; |
| | | font-size: 12px; |
| | | line-height: 1; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .tool-btn:hover { |
| | |
| | | |
| | | .tool-icon { |
| | | position: relative; |
| | | width: 20px; |
| | | height: 20px; |
| | | border: 2px solid #ffffff; |
| | | border-radius: 3px; |
| | | } |
| | | |
| | | .tool-icon-image { |
| | | width: 36px; |
| | | height: 36px; |
| | | } |
| | | |
| | | .tool-icon.polygon { |
| | |
| | | } |
| | | |
| | | .shape-table-container { |
| | | margin-top: 8px; |
| | | padding: 8px; |
| | | border-radius: 8px; |
| | | background: rgba(27, 34, 56, 0.6); |
| | | } |
| | | margin-bottom: 20px; |
| | | |
| | | .shape-table-title { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-start; |
| | | padding-right: 1.2rem; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 500; |
| | | font-size: 1.4rem; |
| | | color: #D4D5D7; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | box-sizing: border-box; |
| | | .shape-table-title { |
| | | margin-bottom: 20px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-start; |
| | | padding-right: 1.2rem; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 500; |
| | | font-size: 1.4rem; |
| | | color: #D4D5D7; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | box-sizing: border-box; |
| | | } |
| | | } |
| | | </style> |