Merge remote-tracking branch 'origin/master'
5 files modified
45 files deleted
| | |
| | | row-key="id" |
| | | > |
| | | <el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" /> |
| | | <el-table-column prop="deviceType" show-overflow-tooltip label="设备类型" /> |
| | | <el-table-column prop="deviceType" show-overflow-tooltip label="设备类型"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(row.deviceType, dictObj.deviceType) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | |
| | | row-key="id" |
| | | > |
| | | <el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" /> |
| | | <el-table-column prop="deviceType" show-overflow-tooltip label="设备类型" /> |
| | | <el-table-column prop="deviceType" show-overflow-tooltip label="设备类型"> |
| | | <template v-slot="{ row }"> |
| | | {{ getDictLabel(row.deviceType, dictObj.deviceType) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | |
| | | buildEllipsePositions, |
| | | } from '@/utils/cesium/shapeTools' |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import { |
| | | createDeviceRangePrimitive, |
| | | getDeviceLngLat, |
| | | normalizeDeviceRange, |
| | | } from '@/utils/cesium/deviceRange' |
| | | import { saveOperationLog } from '@ztzf/apis' |
| | | import { useRoute } from 'vue-router' |
| | | import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants' |
| | | import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png' |
| | | |
| | | const initForm = () => ({ |
| | | sceneName: '', // 配置名称 |
| | |
| | | const searchName = ref('') |
| | | const areaList = ref([]) // 关联区域列表 |
| | | const selectedAreaRows = ref([]) //选中列表 |
| | | let lastSelectedAreaIds = [] |
| | | const deviceList = ref([]) // 设备列表 |
| | | const deviceLoading = ref(false) |
| | | const dictObj = inject('dictObj') |
| | |
| | | const route = useRoute() |
| | | let viewer |
| | | let areaDisplaySource |
| | | const deviceRangePrimitiveMap = new Map() |
| | | const deviceIconEntityMap = new Map() |
| | | let deviceRangeRenderToken = 0 |
| | | |
| | | const rules = { |
| | | sceneName: fieldRules(true, 50), |
| | |
| | | async function loadDeviceListByAreaIds(areaIdsKey) { |
| | | if (!areaIdsKey) { |
| | | deviceList.value = [] |
| | | clearDeviceRangePrimitives() |
| | | clearDeviceIconEntities() |
| | | return |
| | | } |
| | | deviceLoading.value = true |
| | | try { |
| | | const res = await fwDeviceListApi({ areaIds: areaIdsKey }) |
| | | deviceList.value = res?.data?.data ?? [] |
| | | await nextTick() |
| | | renderDeviceRanges(deviceList.value) |
| | | } finally { |
| | | deviceLoading.value = false |
| | | } |
| | |
| | | |
| | | // 关联区域变更 |
| | | async function handleAreaSelectionChange(rows) { |
| | | const prevIds = new Set(lastSelectedAreaIds) |
| | | const selectedRows = await ensureAreaExtList(rows) |
| | | selectedAreaRows.value = selectedRows |
| | | const ids = selectedRows.map(item => item.id) |
| | | lastSelectedAreaIds = [...ids] |
| | | formData.value.areaDivideIds = ids.join(',') |
| | | formData.value.areaCount = ids.length |
| | | if (ids.some(id => !prevIds.has(id))) { |
| | | await nextTick() |
| | | fitViewToSelectedAreas() |
| | | } |
| | | } |
| | | |
| | | // 渲染面 |
| | |
| | | function clearAreaDisplay() { |
| | | if (!areaDisplaySource) return |
| | | areaDisplaySource.entities.removeAll() |
| | | } |
| | | |
| | | function clearDeviceRangePrimitives() { |
| | | if (!viewer || !deviceRangePrimitiveMap.size) return |
| | | for (const primitive of deviceRangePrimitiveMap.values()) { |
| | | viewer.scene.primitives.remove(primitive) |
| | | } |
| | | deviceRangePrimitiveMap.clear() |
| | | } |
| | | |
| | | function clearDeviceIconEntities() { |
| | | if (!viewer || !deviceIconEntityMap.size) return |
| | | for (const entity of deviceIconEntityMap.values()) { |
| | | viewer.entities.remove(entity) |
| | | } |
| | | deviceIconEntityMap.clear() |
| | | } |
| | | |
| | | function normalizeShapePoint(point) { |
| | |
| | | }) |
| | | } |
| | | |
| | | function addDeviceIconEntity(device, position) { |
| | | if (!viewer || !position) return |
| | | const id = String(device?.id ?? '') |
| | | if (!id || deviceIconEntityMap.has(id)) return |
| | | const entity = viewer.entities.add({ |
| | | position: Cesium.Cartesian3.fromDegrees(position.lng, position.lat, position.height || 0), |
| | | billboard: { |
| | | image: equipmentIcon, |
| | | width: 28, |
| | | height: 38, |
| | | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, |
| | | }, |
| | | }) |
| | | deviceIconEntityMap.set(id, entity) |
| | | } |
| | | |
| | | async function renderDeviceRanges(rows) { |
| | | if (!visible.value) return |
| | | if (!viewer) initMap() |
| | | if (!viewer) return |
| | | deviceRangeRenderToken += 1 |
| | | const renderToken = deviceRangeRenderToken |
| | | const selectedRows = Array.isArray(rows) ? rows : [] |
| | | const nextIds = new Set(selectedRows.map(row => String(row?.id))) |
| | | for (const [id, primitive] of deviceRangePrimitiveMap.entries()) { |
| | | if (!nextIds.has(id)) { |
| | | viewer.scene.primitives.remove(primitive) |
| | | deviceRangePrimitiveMap.delete(id) |
| | | } |
| | | } |
| | | for (const [id, entity] of deviceIconEntityMap.entries()) { |
| | | if (!nextIds.has(id)) { |
| | | viewer.entities.remove(entity) |
| | | deviceIconEntityMap.delete(id) |
| | | } |
| | | } |
| | | const pendingRows = selectedRows.filter(row => { |
| | | const id = String(row?.id) |
| | | return id && !deviceRangePrimitiveMap.has(id) |
| | | }) |
| | | if (!pendingRows.length) return |
| | | const primitives = await Promise.all( |
| | | pendingRows.map(async row => { |
| | | const position = getDeviceLngLat(row) |
| | | if (!position) return { row, primitive: null, position: null } |
| | | const rangeMeters = normalizeDeviceRange(row.effectiveRangeKm) |
| | | const primitive = await createDeviceRangePrimitive(viewer, position, rangeMeters) |
| | | return { row, primitive, position } |
| | | }) |
| | | ) |
| | | if (renderToken !== deviceRangeRenderToken) return |
| | | primitives.forEach(({ row, primitive, position }) => { |
| | | if (position) { |
| | | addDeviceIconEntity(row, position) |
| | | } |
| | | if (!primitive) return |
| | | const id = String(row?.id) |
| | | if (!nextIds.has(id)) return |
| | | deviceRangePrimitiveMap.set(id, primitive) |
| | | viewer.scene.primitives.add(primitive) |
| | | }) |
| | | } |
| | | |
| | | watch(() => selectedAreaRows.value, renderSelectedAreas) |
| | | const deviceAreaIdsKey = computed(() => |
| | | selectedAreaRows.value.map(item => item?.id).filter(Boolean).join(',') |
| | |
| | | } |
| | | ) |
| | | watch( |
| | | () => deviceList.value, |
| | | val => { |
| | | renderDeviceRanges(val) |
| | | } |
| | | ) |
| | | watch( |
| | | () => visible.value, |
| | | val => { |
| | | if (!val) clearAreaDisplay() |
| | | if (!val) { |
| | | clearAreaDisplay() |
| | | clearDeviceRangePrimitives() |
| | | clearDeviceIconEntities() |
| | | } |
| | | } |
| | | ) |
| | | |
| | |
| | | selectedAreaRows.value = ensuredRows |
| | | if (!ensuredRows.length) return |
| | | formData.value.areaCount = ensuredRows.length |
| | | lastSelectedAreaIds = ensuredRows.map(row => row.id) |
| | | } |
| | | |
| | | // 初始化地图实例 |
| | |
| | | |
| | | // 获取字典 |
| | | function getDictList() { |
| | | getDictionaryByCode('CommandSceneType,deviceMode,areaType').then(res => { |
| | | getDictionaryByCode('CommandSceneType,deviceMode,areaType,deviceType').then(res => { |
| | | dictObj.value = res.data.data |
| | | }) |
| | | } |
| | |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="deviceSn" show-overflow-tooltip width="140" label="反制设备编码" /> |
| | | <el-table-column prop="areaCode" show-overflow-tooltip width="120" label="场景" /> |
| | | <el-table-column prop="areaCode" show-overflow-tooltip width="120" label="区域" /> |
| | | <el-table-column prop="sceneName" show-overflow-tooltip width="120" label="场景" /> |
| | | <el-table-column prop="areaName" show-overflow-tooltip width="120" label="区域" /> |
| | | <el-table-column show-overflow-tooltip width="172" label="部署位置"> |
| | | <template v-slot="{ row }"> |
| | | {{ row.deployLongitude && row.deployLatitude ? `${row.deployLongitude}, ${row.deployLatitude}` : '' }} |
| | |
| | | <div class="login-time"> |
| | | {{ time }} |
| | | </div> |
| | | <p class="title">{{ login.info }}</p> |
| | | <div class="login-brand"> |
| | | <p class="title">{{ login.info }}</p> |
| | | </div> |
| | | </div> |
| | | <div class="login-border"> |
| | | <div class="login-main"> |
| | |
| | | created() { |
| | | this.handleLogin(); |
| | | this.getTime(); |
| | | if(!loginTitle) { |
| | | if(!loginTitle) { |
| | | this.getSysConfigInfo(); |
| | | } |
| | | |
| | | |
| | | }, |
| | | mounted() {}, |
| | | computed: { |
| | |
| | | width: 100%; |
| | | height: 100%; |
| | | margin: 0 auto; |
| | | background-color: #469BC0; |
| | | // background-image: url("/img/bg/bg.jpg"); |
| | | background-color: #0b1220; |
| | | overflow: hidden; |
| | | background-size: 100% 100%; |
| | | } |
| | | |
| | | .login-container::before, |
| | | .login-container::after { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: -20%; |
| | | z-index: 0; |
| | | background: radial-gradient(60% 60% at 20% 30%, rgba(88, 176, 255, 0.35), transparent 60%), |
| | | radial-gradient(50% 50% at 80% 20%, rgba(124, 92, 255, 0.35), transparent 65%), |
| | | radial-gradient(50% 50% at 70% 80%, rgba(0, 212, 255, 0.35), transparent 65%), |
| | | linear-gradient(135deg, rgba(10, 21, 38, 0.95), rgba(13, 24, 43, 0.92)); |
| | | filter: blur(10px); |
| | | animation: float-sheen 16s ease-in-out infinite; |
| | | } |
| | | |
| | | .login-container::after { |
| | | inset: -30%; |
| | | opacity: 0.5; |
| | | background: conic-gradient(from 120deg, rgba(95, 228, 255, 0.25), rgba(128, 120, 255, 0.15), transparent 50%); |
| | | animation: float-sheen 24s ease-in-out infinite reverse; |
| | | } |
| | | |
| | | .login-weaper { |
| | | margin: 0 auto; |
| | | width: 1000px; |
| | | box-shadow: -4px 5px 10px rgba(0, 0, 0, 0.4); |
| | | position: relative; |
| | | z-index: 1; |
| | | border-radius: 16px; |
| | | box-shadow: 0 30px 60px rgba(6, 12, 24, 0.55); |
| | | backdrop-filter: blur(10px); |
| | | animation: pop-in 0.7s ease-out; |
| | | |
| | | .el-input-group__append { |
| | | border: none; |
| | |
| | | } |
| | | |
| | | .login-left { |
| | | border-top-left-radius: 5px; |
| | | border-bottom-left-radius: 5px; |
| | | border-top-left-radius: 16px; |
| | | border-bottom-left-radius: 16px; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | background-color: #5A9CF8 !important; |
| | | background: linear-gradient(160deg, rgba(80, 160, 255, 0.95), rgba(73, 96, 255, 0.92)) !important; |
| | | box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.12); |
| | | color: #fff; |
| | | float: left; |
| | | width: 50%; |
| | |
| | | |
| | | .login-left .img { |
| | | width: 140px; |
| | | filter: drop-shadow(0 8px 18px rgba(10, 22, 38, 0.35)); |
| | | } |
| | | |
| | | .login-time { |
| | |
| | | color: #fff; |
| | | font-weight: 200; |
| | | opacity: 0.9; |
| | | font-size: 18px; |
| | | font-size: 16px; |
| | | overflow: hidden; |
| | | letter-spacing: 1px; |
| | | } |
| | | |
| | | .login-left .title { |
| | | text-align: center; |
| | | color: #fff; |
| | | font-weight: bold; |
| | | font-size: 30px; |
| | | letter-spacing: 2px; |
| | | font-size: 32px; |
| | | letter-spacing: 3px; |
| | | text-shadow: 0 8px 20px rgba(0, 0, 0, 0.35); |
| | | } |
| | | |
| | | .login-border { |
| | | border-left: none; |
| | | border-top-right-radius: 5px; |
| | | border-bottom-right-radius: 5px; |
| | | border-top-right-radius: 16px; |
| | | border-bottom-right-radius: 16px; |
| | | color: #fff; |
| | | background-color: #fff; |
| | | background: rgba(255, 255, 255, 0.92); |
| | | width: 50%; |
| | | float: left; |
| | | box-sizing: border-box; |
| | | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4); |
| | | } |
| | | |
| | | .login-main { |
| | | margin: 0 auto; |
| | | width: 65%; |
| | | box-sizing: border-box; |
| | | padding: 40px 0; |
| | | } |
| | | |
| | | .login-main > h3 { |
| | |
| | | } |
| | | |
| | | .login-title { |
| | | color: #333; |
| | | margin-bottom: 40px; |
| | | font-weight: 500; |
| | | font-size: 22px; |
| | | color: #1a1f2b; |
| | | margin-bottom: 36px; |
| | | font-weight: 600; |
| | | font-size: 24px; |
| | | text-align: center; |
| | | letter-spacing: 4px; |
| | | letter-spacing: 6px; |
| | | text-transform: uppercase; |
| | | } |
| | | |
| | | .login-menu { |
| | |
| | | .btn-submit { |
| | | width: 100%; |
| | | height: 45px; |
| | | border: 1px solid var(--el-color-primary); |
| | | background: none; |
| | | border: 1px solid rgba(78, 155, 255, 0.7); |
| | | background: linear-gradient(120deg, rgba(78, 155, 255, 0.15), rgba(95, 228, 255, 0.25)); |
| | | font-size: 18px; |
| | | letter-spacing: 2px; |
| | | font-weight: 300; |
| | | color: var(--el-color-primary); |
| | | color: #1a3f6e; |
| | | cursor: pointer; |
| | | margin-top: 30px; |
| | | font-family: "neo"; |
| | | transition: 0.25s; |
| | | box-shadow: 0 12px 24px rgba(35, 93, 204, 0.2); |
| | | } |
| | | |
| | | .login-submit { |
| | | width: 60%; |
| | | height: 45px; |
| | | border: 1px solid var(--el-color-primary); |
| | | background: none; |
| | | border: 1px solid rgba(78, 155, 255, 0.7); |
| | | background: linear-gradient(120deg, rgba(78, 155, 255, 0.15), rgba(95, 228, 255, 0.25)); |
| | | font-size: 18px; |
| | | letter-spacing: 2px; |
| | | font-weight: 300; |
| | | color: var(--el-color-primary); |
| | | color: #1a3f6e; |
| | | cursor: pointer; |
| | | margin-top: 30px; |
| | | font-family: "neo"; |
| | | transition: 0.25s; |
| | | box-shadow: 0 12px 24px rgba(35, 93, 204, 0.2); |
| | | } |
| | | |
| | | .register-submit { |
| | | width: 36%; |
| | | height: 45px; |
| | | border: 1px solid #EF2F63FF; |
| | | background: none; |
| | | border: 1px solid rgba(239, 47, 99, 0.7); |
| | | background: linear-gradient(120deg, rgba(239, 47, 99, 0.15), rgba(255, 120, 160, 0.25)); |
| | | font-size: 18px; |
| | | letter-spacing: 2px; |
| | | font-weight: 300; |
| | | color: #EF2F63FF; |
| | | color: #b31e4b; |
| | | cursor: pointer; |
| | | margin-top: 30px; |
| | | font-family: "neo"; |
| | | transition: 0.25s; |
| | | box-shadow: 0 12px 24px rgba(239, 47, 99, 0.2); |
| | | } |
| | | |
| | | .login-form { |
| | |
| | | background: transparent; |
| | | box-shadow: none; |
| | | border-radius: 0; |
| | | color: #333; |
| | | border-bottom: 1px solid rgb(235, 237, 242); |
| | | color: #1a1f2b; |
| | | border-bottom: 1px solid rgba(78, 155, 255, 0.4); |
| | | transition: border-color 0.2s ease; |
| | | } |
| | | |
| | | .el-input__prefix { |
| | |
| | | margin-top: 2px; |
| | | width: 100px; |
| | | height: 38px; |
| | | background-color: #fdfdfd; |
| | | border: 1px solid #f0f0f0; |
| | | color: #333; |
| | | background-color: rgba(255, 255, 255, 0.85); |
| | | border: 1px solid rgba(78, 155, 255, 0.3); |
| | | color: #1a1f2b; |
| | | font-size: 14px; |
| | | font-weight: bold; |
| | | letter-spacing: 5px; |
| | |
| | | margin-top: 2px; |
| | | width: 100px; |
| | | height: 38px; |
| | | background-color: #fdfdfd; |
| | | border: 1px solid #f0f0f0; |
| | | color: #7c7979; |
| | | background-color: rgba(255, 255, 255, 0.85); |
| | | border: 1px solid rgba(78, 155, 255, 0.3); |
| | | color: #4b5563; |
| | | font-size: 12px; |
| | | letter-spacing: 1px; |
| | | line-height: 38px; |
| | |
| | | text-align: center; |
| | | cursor: pointer !important; |
| | | } |
| | | |
| | | .btn-submit:hover, |
| | | .login-submit:hover { |
| | | border-color: rgba(78, 155, 255, 0.9); |
| | | background: linear-gradient(120deg, rgba(78, 155, 255, 0.35), rgba(95, 228, 255, 0.45)); |
| | | color: #0e2d56; |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .register-submit:hover { |
| | | border-color: rgba(239, 47, 99, 0.9); |
| | | background: linear-gradient(120deg, rgba(239, 47, 99, 0.3), rgba(255, 120, 160, 0.45)); |
| | | color: #8f173a; |
| | | transform: translateY(-1px); |
| | | } |
| | | |
| | | .el-input__wrapper.is-focus { |
| | | border-bottom-color: rgba(78, 155, 255, 0.9); |
| | | } |
| | | |
| | | @keyframes float-sheen { |
| | | 0% { |
| | | transform: translate3d(0, 0, 0) scale(1); |
| | | } |
| | | 50% { |
| | | transform: translate3d(2%, -2%, 0) scale(1.02); |
| | | } |
| | | 100% { |
| | | transform: translate3d(0, 0, 0) scale(1); |
| | | } |
| | | } |
| | | |
| | | @keyframes pop-in { |
| | | 0% { |
| | | opacity: 0; |
| | | transform: translateY(20px) scale(0.98); |
| | | } |
| | | 100% { |
| | | opacity: 1; |
| | | transform: translateY(0) scale(1); |
| | | } |
| | | } |