This commit is contained in:
严争鸣 2025-01-20 09:42:19 +08:00
parent f60eaefa37
commit ab89bed57d
33 changed files with 1006 additions and 212 deletions

2
components.d.ts vendored
View File

@ -22,6 +22,8 @@ declare module '@vue/runtime-core' {
Modal: typeof import('./src/components/Modal/index.vue')['default']
Nav: typeof import('./src/components/Nav/index.vue')['default']
NButton: typeof import('naive-ui')['NButton']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable']
NDatePicker: typeof import('naive-ui')['NDatePicker']

7
package-lock.json generated
View File

@ -28,6 +28,7 @@
"satellite.js": "^5.0.0",
"seemly": "^0.3.9",
"v-viewer": "^3.0.21",
"vanilla-js-wheel-zoom": "^9.0.4",
"viewerjs": "^1.11.7",
"vue": "^3.2.45",
"vue-draggable-plus": "^0.5.6",
@ -15120,6 +15121,12 @@
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/vanilla-js-wheel-zoom": {
"version": "9.0.4",
"resolved": "https://registry.npmmirror.com/vanilla-js-wheel-zoom/-/vanilla-js-wheel-zoom-9.0.4.tgz",
"integrity": "sha512-OjmS9ihEKBCRw2OQ7IiIdQGXdC5gTEEmtcAWZcPeNAJaYiS61KCd02Z72YMtIoXLGN5TZP+wliBMylLAsr6wow==",
"license": "MIT"
},
"node_modules/vdirs": {
"version": "0.1.8",
"resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz",

View File

@ -31,6 +31,7 @@
"satellite.js": "^5.0.0",
"seemly": "^0.3.9",
"v-viewer": "^3.0.21",
"vanilla-js-wheel-zoom": "^9.0.4",
"viewerjs": "^1.11.7",
"vue": "^3.2.45",
"vue-draggable-plus": "^0.5.6",

View File

@ -1,8 +1,16 @@
import { defAxios as request } from '@/utils/http'
const baseUrl = window.settings.apis
// export function getHangjing(data = {}) {
// return request({
// url: `${baseUrl}/hangjing/list`,
// method: 'post',
// data,
// })
// }
export function getHangjing(data = {}) {
return request({
url: `${baseUrl}/hangjing/list`,
url: `${baseUrl}/hangjing/shiJianTree`,
method: 'post',
data,
})

View File

@ -56,6 +56,9 @@
);
--color-bg: #1a222966;
--gradient-bg-title: linear-gradient(to right, #4fd2dd55, #4b877400);
--tw-from-opacity: 33%;
}
@media (prefers-color-scheme: dark) {

8
src/assets/detail.scss Normal file
View File

@ -0,0 +1,8 @@
.detail-container {
@apply flex flex-col gap-4;
.detail-item-title{
@apply font-bold text-xl w-[120px] h-[30px] leading-[30px];
background-image: linear-gradient(to right, #4fd2dd55 0%, transparent 100%);
}
}

View File

@ -1,5 +1,6 @@
@import './base.css';
@import './naiveui.css';
@import './detail.scss';
#app {
width: 100%;

View File

@ -1,3 +1,7 @@
.n-dialog .n-dialog__content {
padding-top: 1rem;
}
.n-base-select-option__content {
width: 100%;
}

View File

@ -120,7 +120,7 @@ $radius: 2rem;
.content-title {
@apply h-9 px-5 leading-9 tracking-[5px];
border-radius: $radius $radius 0 0;
background: linear-gradient(to right, #4fd2dd55, #4b877400);
background: var(--gradient-bg-title);
.title-text {
@apply font-bold italic;
color: transparent;

View File

@ -62,12 +62,12 @@ async function getHisTraj({
function getMBEntityOpt({
id,
name,
targetType,
name,
}: {
id: string
name?: string
targetType: string
name?: string
}) {
const mubiaoDict = window.settings.mbDict[targetType]
@ -102,6 +102,25 @@ function getMBEntityOpt({
scale: 1000,
minimumPixelSize: 50,
},
ellipsoid: {
show: true,
radii: new Cesium.Cartesian3(100000, 100000, 100000),
innerRadii: new Cesium.Cartesian3(1.0, 1.0, 1.0),
maximumCone: Cesium.Math.toRadians(90),
minimumCone: Cesium.Math.toRadians(40),
minimumClock: Cesium.Math.toRadians(20),
maximumClock: Cesium.Math.toRadians(90),
material: Cesium.Color.fromCssColorString('#00dcff44'),
outline: true,
outlineColor: Cesium.Color.fromCssColorString('#00dcff'),
outlineWidth: 1,
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
0.0,
10.5e8
),
slicePartitions: 24,
stackPartitions: 36,
},
}
}

142
src/js/Radar.js Normal file
View File

@ -0,0 +1,142 @@
export default class Radar {
// 创建雷达罩
constructor(options) {
this.radius = options.radius
// this.viewer = options.viewer
// this.id = options.id
// this.entity = null
// this.radius = options.radius
// ;(this.longitude = options.position[0]),
// (this.latitude = options.position[1]),
// (this.position = Cesium.Cartesian3.fromDegrees(
// options.position[0],
// options.position[1]
// ))
// this.heading = 0
// this.positionArr = this.calcPoints(
// options.position[0],
// options.position[1],
// options.radius,
// 0
// ) //储存脏数据
// this.addEntities()
}
cartesian32LonLat(cartesian3) {
const cartographic =
this.viewer.scene.globe.ellipsoid.cartesianToCartographic(
cartesian3._value
)
const lat = Cesium.Math.toDegrees(cartographic.latitude)
const lon = Cesium.Math.toDegrees(cartographic.longitude)
return [lon, lat]
}
getRadar() {
// this.entity = this.viewer.entities.add({
// id: this.id,
// position: this.position,
// wall: {
// positions: new Cesium.CallbackProperty(() => {
// return Cesium.Cartesian3.fromDegreesArrayHeights(this.positionArr)
// }, false),
// material: Cesium.Color.fromCssColorString('#00dcff82'),
// distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
// 0.0,
// 10.5e6
// ),
// },
// ellipsoid: {
// radii: new Cesium.Cartesian3(
// this.radius,
// this.radius,
// this.radius
// ),
// maximumCone: Cesium.Math.toRadians(90),
// material: Cesium.Color.fromCssColorString('#00dcff82'),
// outline: true,
// outlineColor: Cesium.Color.fromCssColorString('#00dcff'),
// outlineWidth: 1,
// distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
// 0.0,
// 10.5e8
// ),
// },
// })
return {
// wall: {
// positions: new Cesium.CallbackProperty(() => {
// return Cesium.Cartesian3.fromDegreesArrayHeights(this.positionArr)
// }, false),
// material: Cesium.Color.fromCssColorString('#00dcff82'),
// distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
// 0.0,
// 10.5e6
// ),
// },
ellipsoid: {
radii: new Cesium.Cartesian3(this.radius, this.radius, this.radius),
maximumCone: Cesium.Math.toRadians(90),
material: Cesium.Color.fromCssColorString('#00dcff82'),
outline: true,
outlineColor: Cesium.Color.fromCssColorString('#00dcff'),
outlineWidth: 1,
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
0.0,
10.5e8
),
},
}
// this.addPostRender()
}
addPostRender() {
this.viewer.clock.onTick.addEventListener(() => {
this.heading += 1.0 //可调节转动速度
this.positionArr = this.calcPoints(
this.longitude,
this.latitude,
this.radius,
this.heading
)
})
}
calcPoints(x1, y1, radius, heading) {
let m = Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(x1, y1)
)
let rx = radius * Math.cos((heading * Math.PI) / 180.0)
let ry = radius * Math.sin((heading * Math.PI) / 180.0)
let translation = Cesium.Cartesian3.fromElements(rx, ry, 0)
let d = Cesium.Matrix4.multiplyByPoint(
m,
translation,
new Cesium.Cartesian3()
)
let c = Cesium.Cartographic.fromCartesian(d)
let x2 = Cesium.Math.toDegrees(c.longitude)
let y2 = Cesium.Math.toDegrees(c.latitude)
return this.computeCirclularFlight(x1, y1, x2, y2, 0, 90)
}
computeCirclularFlight(x1, y1, x2, y2, fx, angle) {
let positionArr = []
positionArr.push(x1)
positionArr.push(y1)
positionArr.push(0)
let radius = Cesium.Cartesian3.distance(
Cesium.Cartesian3.fromDegrees(x1, y1),
Cesium.Cartesian3.fromDegrees(x2, y2)
)
for (let i = fx; i <= fx + angle; i++) {
let h = radius * Math.sin((i * Math.PI) / 180.0)
let r = Math.cos((i * Math.PI) / 180.0)
let x = (x2 - x1) * r + x1
let y = (y2 - y1) * r + y1
positionArr.push(x)
positionArr.push(y)
positionArr.push(h)
}
return positionArr
}
removeEntity() {
this.entity && this.viewer.entities.remove(this.entity)
this.entity = null
}
}

View File

@ -22,7 +22,7 @@ class SatelliteEntity {
this.entity = null
this._underPoint = false
this.underPointEntity = null
this._sensorType = 'rectangle'
this._sensorType = 'conic'
this._listener = null
}
get sensorType() {
@ -443,9 +443,8 @@ class SatelliteEntity {
},
polyline: {
positions: points,
width: 1,
material: Cesium.Color.RED,
material: color,
},
})
}

View File

@ -0,0 +1,70 @@
import { NImage } from 'naive-ui'
import WZoom from 'vanilla-js-wheel-zoom/dist/wheel-zoom.min.js'
export default defineComponent({
props: {
imageList: {
type: Array,
default: () => [],
},
},
setup(props) {
// const { images, activeIndex } = toRefs(props)
watch(
() => props.imageList,
() => {
if (props.imageList.length > 1) {
nextTick(() => {
props.imageList.map(image => {
const imageElement = document
.getElementById(`image-${image.id}`)
.querySelector('img')
const wz = WZoom.create(`#image-${image.id}`, {
type: 'html',
maxScale: 3,
minScale: 0.2,
// zoomOnDoubleClick: true,
width: imageElement.naturalWidth,
height: imageElement.naturalHeight,
})
})
})
}
}
)
return () => (
<div class="l-image-container flex h-full w-full gap-4">
{props.imageList.map(image => (
<div class="flex h-full flex-1 flex-col gap-2">
<div>{image.createTime}</div>
<div class="h-0 flex-1 overflow-hidden">
{props.imageList.length > 1 ? (
<div
id={`image-${image.id}`}
class="flex h-full w-full cursor-grab items-center justify-center"
>
<img
class="w-full"
src={image.imgPath}
draggable="false"
// alt="image"
/>
</div>
) : (
<NImage
class="flex justify-center object-contain w-h-full"
object-fit="contain"
src={image.imgPath}
/>
)}
</div>
<div class="line-clamp-3 pb-4">{image.detailContent}</div>
</div>
))}
</div>
)
},
})

View File

@ -4,6 +4,7 @@
import { ImageOutline } from '@vicons/ionicons5'
import { useHisImage } from './hooks/hisImage'
import Panel from '@/components/Panel/index.vue'
import LImage from './components/LImage'
const { sheshiData, showOrHideHisImage, getHisImages } = useHisImage()
@ -61,12 +62,47 @@ const getImage = async () => {
// })
imageList.value = new Array(10).fill(1).map((item, index) => {
return {
id: index,
imgPath: `https://picsum.photos/300/200?random=${index}`,
imgId: index,
}
})
// console.log(imageList.value, 'imageList')
}
const checkedImage = ref<null | string[]>(null)
const checkValue = e => {
e.stopPropagation()
if (checkedImage.value && checkedImage.value.length > 2) {
checkedImage.value.pop()
}
}
// watch(checkedImage, newCheck => {
// // console.log(newCheck, 'newCheck')
// })
const isCompare = ref(false)
const compareImages = () => {
if (!isCompare.value) {
if (checkedImage.value && checkedImage.value.length === 2) {
isCompare.value = true
}
} else {
isCompare.value = false
checkedImage.value = null
}
}
const largeImageList = computed(() => {
if (isCompare.value) {
return imageList.value.filter(img => checkedImage.value.includes(img.id))
} else {
return imageList.value[previewIndex.value - 1]
? [imageList.value[previewIndex.value - 1]]
: []
}
})
</script>
<template>
@ -79,23 +115,8 @@ const getImage = async () => {
>
<div class="flex flex-col w-h-full">
<div class="large-container">
<div>
{{ previewIndex > 0 ? imageList[previewIndex - 1].createTime : '' }}
</div>
<!-- <vue-viewer :images="imageList" :options="{ inline: true }"> -->
<div class="h-0 flex-1">
<n-image
class="flex justify-center object-contain w-h-full"
object-fit="contain"
:src="previewIndex > 0 ? imageList[previewIndex - 1].imgPath : ''"
/>
</div>
<l-image :imageList="largeImageList" />
<div class="line-clamp-3 pb-4">
{{
previewIndex > 0 ? imageList[previewIndex - 1].detailContent : ''
}}
</div>
<!-- </vue-viewer> -->
</div>
<div ref="scrollRef" class="flex h-[178px] flex-col">
@ -109,12 +130,19 @@ const getImage = async () => {
format="yyyy-MM-dd HH:mm:ss"
/>
<n-button type="primary" @click="getImage">搜索</n-button>
<n-button type="primary" @click="compareImages">{{
isCompare ? '取消对比' : '对比'
}}</n-button>
</div>
<div v-if="imageList.length === 0" class="m-auto flex">
<n-empty description="暂无数据"> </n-empty>
</div>
<n-scrollbar v-else x-scrollable>
<div class="flex h-full flex-nowrap">
<!-- <div> -->
<n-checkbox-group
class="flex h-full flex-nowrap"
v-model:value="checkedImage"
>
<div
class="flex h-full w-[140px] cursor-pointer p-2"
v-for="(i, index) in imageList"
@ -128,6 +156,12 @@ const getImage = async () => {
index + 1 === previewIndex,
}"
>
<n-checkbox
class="absolute"
:value="i.id"
label=""
@click.stop="checkValue"
/>
<n-image
class="m-auto"
object-fit="contain"
@ -143,7 +177,8 @@ const getImage = async () => {
</n-image>
</div>
</div>
</div>
</n-checkbox-group>
<!-- </div> -->
</n-scrollbar>
</div>
</div>
@ -154,6 +189,7 @@ const getImage = async () => {
<style lang="scss" scoped>
.pre-container {
transition: all 0.3s ease-in-out;
.large-container {
@apply flex h-0 flex-1 flex-col gap-2 overflow-hidden text-center;
//

View File

@ -43,6 +43,10 @@ const nodeProps = ({ option }: { option: TreeOption }) => {
},
}
}
const renderSuffix = ({ option }: { option: TreeOption }) => {
}
</script>
<template>

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import moment from 'moment-timezone'
import { useRoute } from 'vue-router'
import { time2FormatWithTimezone } from '@/utils/date'
import WidgetNav from '../WidgetNav'
@ -38,6 +39,8 @@ onMounted(() => {
getTime()
getLocalTime()
})
const route = useRoute()
</script>
<template>
@ -64,7 +67,7 @@ onMounted(() => {
{{ worldTime }}
</div>
</div>
<widget-nav />
<widget-nav v-if="route.path === '\/'" />
<div class="time-container bgc-animation">
<div class="bgc-animation">{{ localeTime }}</div>
<div class="time-title">作战时</div>

View File

@ -1,15 +1,13 @@
import { NIcon, NPopover } from 'naive-ui'
import { useEntity } from '@/hooks/entity'
import { useSatellite } from '@/views/Satellite/hooks/satellite'
export default defineComponent({
setup() {
const { satelliteMap } = useEntity()
const { showPoint, showPointUnderSat } = useSatellite()
const show = ref(false)
// const show = ref(false)
const showPointUnderSatellite = () => {
show.value = !show.value
;[...satelliteMap.values()].forEach(satellite => {
satellite.underPoint = show.value
})
showPoint.value = !showPoint.value
showPointUnderSat()
}
return () => (
<>
@ -18,7 +16,10 @@ export default defineComponent({
placement="bottom"
v-slots={{
trigger: () => (
<div class="btn-class" onClick={showPointUnderSatellite}>
<div
class={`btn-class ${showPoint.value ? 'checked' : ''}`}
onClick={showPointUnderSatellite}
>
<NIcon size="15">
<svg
t="1736493475776"

View File

@ -54,7 +54,7 @@ const types = [
{ name: 'XW', value: 'wzbXw' },
]
const showPanelName = ref('wx')
const showPanelName = ref('hj-2')
const panelList = [
// {
@ -122,17 +122,9 @@ const showOrHideTextReport = () => {
<div class="grid flex-1 grid-cols-[1.5fr_3fr_1.5fr] grid-rows-1 gap-1">
<div class="left-panel pl-8">
<div
class="radio-group absolute -left-4 top-[15%] z-30 flex w-12 translate-y-[-50%] transform flex-col"
>
<div class="radio-group absolute -left-4 top-[15%] z-30 flex w-12 translate-y-[-50%] transform flex-col">
<template v-for="panel in panelList" :key="panel.id">
<input
type="radio"
:id="panel.id"
name="selector"
v-model="showPanelName"
:value="panel.value"
/>
<input type="radio" :id="panel.id" name="selector" v-model="showPanelName" :value="panel.value" />
<label for="wx" @click="hidePanel($event, panel.value)">{{
panel.name
}}</label>
@ -187,20 +179,10 @@ const showOrHideTextReport = () => {
</div>
<!-- <div class="z-20 grid grid-cols-1 grid-rows-3 gap-1"> -->
<div>
<div
class="btn-transform z-20 w-h-full"
:class="showTextReport ? '' : 'btn-transform-pos'"
>
<n-button
class="absolute -left-[16px] top-5 z-30 border border-[#29baf1] bg-[var(--color-bg)]"
size="tiny"
@click="showOrHideTextReport"
>
<n-icon
class="btn-transform"
:class="showTextReport ? '' : 'icon-transform'"
><arrow-right
/></n-icon>
<div class="btn-transform z-20 w-h-full" :class="showTextReport ? '' : 'btn-transform-pos'">
<n-button class="absolute -left-[16px] top-5 z-30 border border-[#29baf1] bg-[var(--color-bg)]" size="tiny"
@click="showOrHideTextReport">
<n-icon class="btn-transform" :class="showTextReport ? '' : 'icon-transform'"><arrow-right /></n-icon>
</n-button>
<!-- <transition name="slide2">.slice(0, 3) -->
<panel title="文字报"><text-report :tabs="types" /></panel>
@ -220,14 +202,8 @@ const showOrHideTextReport = () => {
</div>
<div class="absolute bottom-0 flex h-full w-full flex-col justify-end">
<text-message class="absolute z-30 h-[200px]"></text-message>
<mubiao-his-trajectory
v-if="showHisTrajCom"
class="z-30 h-[260px]"
></mubiao-his-trajectory>
<multi-his-trajectory
v-if="showMultiHisTrajCom"
class="z-30 h-[260px]"
></multi-his-trajectory>
<mubiao-his-trajectory v-if="showHisTrajCom" class="z-30 h-[260px]"></mubiao-his-trajectory>
<multi-his-trajectory v-if="showMultiHisTrajCom" class="z-30 h-[260px]"></multi-his-trajectory>
<his-images v-if="showHisImageCom" class="z-30 h-[260px]"></his-images>
</div>
<details-modal></details-modal>
@ -239,9 +215,11 @@ const showOrHideTextReport = () => {
.time-bg {
background-color: rgba(26, 34, 41, 0.4);
.time-container {
@apply flex items-center justify-center gap-2;
font-family: Digital;
.time-title {
@apply h-6 border border-[#29baf1] px-2 text-sm leading-5 text-[#29baf1];
}
@ -256,31 +234,39 @@ const showOrHideTextReport = () => {
background-clip: text;
color: transparent;
}
.title-container {
@apply absolute left-[50%] top-3 z-20 h-20 text-5xl font-bold tracking-[18px];
transform: translateX(-50%);
}
@keyframes gradientAnimation {
0% {
background-position: 200% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.header-bg {
background: url('./header.svg') no-repeat;
background-size: 100% 100%;
}
.left-panel {
@apply relative z-20 w-h-full;
&-wrapper {
@apply absolute h-full;
width: calc(100% - 2rem);
.left-panel-content {
@apply absolute right-[-37px] top-[78px];
}
@ -312,6 +298,7 @@ const showOrHideTextReport = () => {
.btn-transform {
transition: transform 0.8s ease;
}
.btn-transform-pos {
transform: translateX(99%);
}
@ -386,5 +373,4 @@ const showOrHideTextReport = () => {
// // background: var(--gradient-bg);
// // background-clip: text;
// }
// }
</style>
// }</style>

View File

@ -1,4 +1,6 @@
import { difference } from 'lodash'
import { useWebSocket } from '@vueuse/core'
import { useTree } from '@/utils/tree'
import { useHjPolygon } from './hangjingPolygon'
interface IPolygonData {
id: string
@ -6,6 +8,8 @@ interface IPolygonData {
color?: string
}
const { filterTreeNodeByField } = useTree()
const hangjingMap: Map<string, any> = new Map()
const { addHangjingPolygon, removeHangjingPolygon } = useHjPolygon(hangjingMap)
@ -13,8 +17,8 @@ export const useHangjing = () => {
onMounted(() => {
initWebSocket()
})
const addHangjing = (data: Record<string, any>[]) => {
addHangjingPolygon(data)
const addHangjing = (ids, hangjingData) => {
addHangjingPolygon(ids, hangjingData)
}
const removeHangjing = (id: string) => {

View File

@ -0,0 +1,34 @@
import { useModal } from '@/views/Content/hooks/modal'
const { openDetailsModal } = useModal()
import { useHangjingStyle } from './hangjingStyle'
const { renderStyleContent } = useHangjingStyle()
export const useHangjingDetail = () => {
return { showDetailsModal }
}
function renderDetailsContent(data) {
return () => (
<div class="detail-container">
<div class="detail-item-title">基本信息</div>
<div>
{Object.keys(data)
.filter(key => key !== 'geom')
.map(key => (
<div>
{key}{data[key]}
</div>
))}
</div>
<div class="detail-item-title">样式配置</div>
{renderStyleContent(data)}
</div>
)
}
function showDetailsModal(title, data) {
openDetailsModal({
titleString: title,
contentSlot: renderDetailsContent(data),
})
}

View File

@ -1,22 +0,0 @@
import { h } from 'vue'
import { useModal } from '@/views/Content/hooks/modal'
const { openDetailsModal } = useModal()
export const useHangjingDetail = () => {
return { showDetailsModal }
}
function renderDetailsContent(data: Record<string, any>) {
return h(
'div',
{},
Object.keys(data).map(key => h('div', {}, `${key}${data[key]}`))
)
}
function showDetailsModal(title: string, data: Record<string, any>) {
openDetailsModal({
titleString: title,
contentSlot: renderDetailsContent(data),
})
}

View File

@ -5,37 +5,44 @@ import { difference } from 'lodash'
import { polygonGradient } from '@/js/polygonGradient'
import { polygonMaterial } from '@/js/polygon'
import { centerOfMass } from '@turf/turf'
import { useTree } from '@/utils/tree'
import { useHangjingPopup } from './hangjingPopup'
const { createPopup } = useHangjingPopup()
const { filterTreeNodeByField } = useTree()
export const useHjPolygon = (polygonMap: Map<string | number, any>) => {
let subscriber: Subscriber | null = null
const colors = new Map()
function addHangjingPolygon(data: Record<string, any>[]) {
function addHangjingPolygon(ids, data) {
subscriber = new Subscriber(viewer, {
pickResult: {
enable: true,
},
})
const ids = data.map(item => item.id)
// const ids = data.map(item => item.id)
const addIds = difference(ids, [...polygonMap.keys()])
const removeIds = difference([...polygonMap.keys()], ids)
// 添加
if (addIds.length > 0) {
addIds.forEach(id => {
const item = data.find(item => item.id === id)
if (item) {
if (item.zoneList.length > 0) {
item.zoneList.forEach(zone => {
const nodes = filterTreeNodeByField({
treeData: data.value,
params: addIds,
paramName: 'dataId',
})
nodes.forEach(({ data: hjData, dataId: id }) => {
// const item = data.find(item => item.id === id)
if (hjData) {
if (hjData.zoneList.length > 0) {
hjData.zoneList.forEach(zone => {
addPolygon(zone, id)
})
} else {
addPolygon(item)
addPolygon(hjData)
}
}
})
@ -51,12 +58,14 @@ export const useHjPolygon = (polygonMap: Map<string | number, any>) => {
}
function addPolygon(item, parentId: number | null = null) {
const { id, geom } = item
const { id, geom, title } = item
const feature = parseWKT(geom)
const position = feature.coordinates[0].map(pos => {
return Cesium.Cartesian3.fromDegrees(...pos)
})
const labels = addTextAlongCurve('Cesium中文垂直排列测试', position)
// console.log(item, id, position, 'id, position, color')
// const randomColor =
// '#' + Math.random().toString(16).substring(2, 8).padEnd(6, '0')
@ -68,6 +77,7 @@ export const useHjPolygon = (polygonMap: Map<string | number, any>) => {
// if (zoneId) {
// if()
// }
const curId = parentId || id
if (!colors.has(curId)) {
@ -214,3 +224,121 @@ export const useHjPolygon = (polygonMap: Map<string | number, any>) => {
removeHangjingPolygon,
}
}
function addTextAlongCurve(text, polygonPoints) {
// 创建 Billboard 集合
const billboardCollection = viewer.scene.primitives.add(
new Cesium.BillboardCollection()
)
// 创建文字绘制的辅助函数
function createTextTexture(text, angle) {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// 设置 Canvas 大小
canvas.width = 20
canvas.height = 20
// 设置文字样式
context.font = '20px sans-serif' // 支持中文
context.fillStyle = 'blue'
context.textAlign = 'center'
context.textBaseline = 'middle'
// 将文字绘制到 Canvas并进行旋转
context.clearRect(0, 0, canvas.width, canvas.height)
context.save()
// 旋转文字
context.translate(canvas.width / 2, canvas.height / 2)
context.rotate(angle)
context.fillText(text, 0, 0)
context.restore()
return canvas
}
// 动态生成字符标注
function generateLabels(cameraHeight) {
billboardCollection.removeAll() // 清除之前的标注
let charIndex = 0 // 当前字符索引
for (let i = 0; i < polygonPoints.length - 1; i++) {
const start = polygonPoints[i] // 当前边的起点
const end = polygonPoints[i + 1] // 当前边的终点
// 计算线段的方向向量
const direction = Cesium.Cartesian3.subtract(
end,
start,
new Cesium.Cartesian3()
)
Cesium.Cartesian3.normalize(direction, direction)
// 计算垂直于线段的方向向量
const perpendicular = Cesium.Cartesian3.cross(
direction,
Cesium.Cartesian3.UNIT_Z, // 使用 Z 轴(垂直地球表面)计算垂直方向
new Cesium.Cartesian3()
)
Cesium.Cartesian3.normalize(perpendicular, perpendicular)
// 计算线段的长度
const length = Cesium.Cartesian3.distance(start, end)
// 动态调整字符间隔,基于相机高度
const baseSpacing = 50 // 基础字符间隔
const charSpacing = Math.max((baseSpacing * cameraHeight) / 5000000, 30)
// 按字符间隔放置文字
let distance = 0
while (distance < length && charIndex < text.length) {
// 计算字符的位置
const fraction = distance / length // 当前字符在边上的位置比例
const position = Cesium.Cartesian3.lerp(
start,
end,
fraction,
new Cesium.Cartesian3()
)
// 计算旋转角度,使文字垂直于线段
const angle = Math.atan2(perpendicular.y, perpendicular.x)
// 创建带旋转的文字纹理
const canvas = createTextTexture(text[charIndex], angle)
// 添加 Billboard 显示文字
billboardCollection.add({
position: position,
image: canvas, // 使用生成的文字纹理
// pixelOffset: new Cesium.Cartesian2(10, 0),
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
})
// 更新字符索引和距离
charIndex++
console.log(charSpacing)
distance += charSpacing * 2000
// distance *= 2
console.log(distance)
}
// 如果字符已用完,跳出循环
if (charIndex >= text.length) {
break
}
}
}
// 初始化标注(根据初始相机高度)
generateLabels(viewer.camera.positionCartographic.height)
// 监听相机变化事件,动态更新字符间隔
viewer.camera.changed.addEventListener(() => {
const cameraHeight = viewer.camera.positionCartographic.height
generateLabels(cameraHeight)
})
}

View File

@ -0,0 +1,115 @@
import {
NForm,
NFormItem,
NInput,
NColorPicker,
NSelect,
NInputNumber,
NButton,
} from 'naive-ui'
import { useModal } from '@/views/Content/hooks/modal'
const { openDetailsModal } = useModal()
const styleForm = ref({
fontFamily: '微软雅黑',
fontSize: 14,
textColor: 'rgba(255,255,0,1)',
polygonColor: 'rgba(255,0,0,0.3)',
lineColor: 'rgba(255,0,0,1)',
lineWidth: 1,
lineType: 'solid',
})
export const useHangjingStyle = () => {
return { renderStyleContent, showStyleModal }
}
function renderStyleContent(data) {
return (
<div class="flex flex-col gap-2 pt-4">
<NForm
model={styleForm.value}
labelPlacement="left"
//labelWidth: 100,
rules={{
fontFamily: [{ required: true, message: '请选择文字字体' }],
fontSize: [{ required: true, message: '请选择文字大小' }],
textColor: [{ required: true, message: '请选择文字颜色' }],
polygonColor: [{ required: true, message: '请选择区域颜色' }],
lineColor: [{ required: true, message: '请选择边框颜色' }],
lineType: [{ required: true, message: '请选择边框类型' }],
lineWidth: [{ required: true, message: '请选择边框宽度' }],
}}
class="grid grid-cols-2 gap-4"
>
<NFormItem label="文字字体" path="fontFamily">
<NSelect
v-model:value={styleForm.value.fontFamily}
options={[
{ label: '微软雅黑', value: '微软雅黑' },
{ label: '楷体', value: '楷体' },
{ label: '宋体', value: '宋体' },
]}
></NSelect>
</NFormItem>
<NFormItem label="边框颜色" path="lineColor">
<NColorPicker
v-model:value={styleForm.value.lineColor}
></NColorPicker>
</NFormItem>
<NFormItem label="文字大小" path="fontSize">
<NInputNumber
v-model:value={styleForm.value.fontSize}
min={1}
max={100}
></NInputNumber>
</NFormItem>
<NFormItem label="边框宽度" path="lineWidth">
<NInputNumber
v-model:value={styleForm.value.lineWidth}
min={1}
max={100}
></NInputNumber>
</NFormItem>
<NFormItem label="文字颜色" path="textColor">
<NColorPicker
v-model:value={styleForm.value.textColor}
></NColorPicker>
</NFormItem>
<NFormItem label="边框类型" path="lineType">
<NSelect
v-model:value={styleForm.value.lineType}
options={[
{ label: '实线', value: 'solid' },
{ label: '虚线', value: 'dashed' },
]}
></NSelect>
</NFormItem>
<NFormItem label="区域颜色" path="polygonColor">
<NColorPicker
v-model:value={styleForm.value.polygonColor}
></NColorPicker>
</NFormItem>
</NForm>
<div class="flex justify-end gap-2">
<NButton type="primary" onClick={updateStyle}>
确定
</NButton>
<NButton onClick={updateStyle}>取消</NButton>
</div>
</div>
)
}
function showStyleModal(title, data) {
openDetailsModal({
titleString: title,
contentSlot: renderStyleContent(data),
})
}
function updateStyle() {
// TODO:
}

View File

@ -9,9 +9,10 @@ import { getHangjing } from '@/api/Hangjing'
import { useHangjing } from './hooks/hangjing'
import { convertToWKT } from '@/utils/parseWKT'
import { useHangjingDetail } from './hooks/hangjingDetail'
import { useHangjingStyle } from './hooks/hangjingStyle'
// import { useHangjingStyle } from './hooks/hangjingStyle'
import Tree from '@/components/Tree/index.vue'
const { addHangjing } = useHangjing()
const timeRange = ref(null)
@ -101,70 +102,45 @@ const drawArea = () => {
}
const { showDetailsModal } = useHangjingDetail()
const { showStyleModal } = useHangjingStyle()
const columns = [
{
type: 'selection',
// disabled(row) {
// return row.name === 'Edward King 3'
// },
},
{
title: 'Name',
key: 'title',
},
{
title: 'Type',
key: 'hjType',
},
{
title: 'Action',
key: 'actions',
render(row) {
return h('div', { class: 'flex gap-2' }, [
h(
NButton,
{
strong: true,
tertiary: true,
size: 'small',
onClick: () => showDetailsModal(`${row.title}详情`, row),
},
{ default: () => '详情' }
),
h(
NButton,
{
strong: true,
tertiary: true,
size: 'small',
onClick: () => showStyleModal(`${row.title}样式配置`, row),
},
{ default: () => '样式配置' }
),
])
},
},
]
// const { showStyleModal } = useHangjingStyle()
const data = ref([])
const paginationReactive = reactive({
page: 1,
pageSize: 50,
showSizePicker: true,
pageSizes: [20, 50, 100],
onChange: page => {
paginationReactive.page = page
},
onUpdatePageSize: pageSize => {
paginationReactive.pageSize = pageSize
paginationReactive.page = 1
},
const renderSuffix = ({ option }: { option: TreeOption }) => {
return option.data ? h('div', { class: 'flex items-center gap-2 pr-2' }, [
h(
NButton,
{
text: true,
size: 'tiny',
type: 'info',
onClick: () => showDetailsModal(`${option.data.title}详情`, option.data),
},
{ default: () => '详情' }
),
// h(
// NButton,
// {
// text: true,
// size: 'tiny',
// type: 'info',
// onClick: () => showStyleModal(`${option.data.title}`, option.data),
// },
// { default: () => '' }
// ),
]) : null
}
const checkedKeys = ref<Array<string | number>>([])
const { addHangjing } = useHangjing()
watch(checkedKeys, val => {
addHangjing(val, data)
})
const handleCheck = (rowKeys: DataTableRowKey[]) => {
const selectedList = data.value.filter(item => rowKeys.includes(item.id))
addHangjing(selectedList)
}
@ -174,7 +150,7 @@ const getHangjingData = async (params = {}) => {
const { code, data: resData } = await getHangjing(params)
if (code === '200') {
data.value = resData || []
data.value = [resData]
}
isLoading.value = false
}
@ -204,14 +180,8 @@ const clearSelected = () => {
<template>
<div class="flex flex-col gap-2 w-h-full" v-loading="isLoading">
<n-date-picker
v-model:formatted-value="timeRange"
clearable
type="datetimerange"
:shortcuts="rangeShortcuts"
:update-value-on-close="true"
format="yyyy-MM-dd HH:mm:ss"
/>
<n-date-picker v-model:formatted-value="timeRange" clearable type="datetimerange" :shortcuts="rangeShortcuts"
:update-value-on-close="true" format="yyyy-MM-dd HH:mm:ss" />
<div class="flex gap-2">
<n-input class="w-auto" v-model:value="searchTitle" />
<n-select class="w-40" v-model:value="type" :options="typeOptions" />
@ -239,15 +209,12 @@ const clearSelected = () => {
<n-button type="primary" @click="searchHangjing">检索</n-button>
</n-input-group>
</div>
<n-data-table
class="flex-1"
:columns="columns"
:data="data"
:row-key="(rowData: Record<string, any>) => rowData.id"
flex-height
@update:checked-row-keys="handleCheck"
/>
<!-- <n-data-table class="flex-1" :columns="columns" :data="data" :row-key="(rowData: Record<string, any>) => rowData.id"
flex-height @update:checked-row-keys="handleCheck" /> -->
<!-- :pagination="paginationReactive" -->
<tree :data="data" :key-field="'dataId'" :label-field="'nodeName'" v-model:checked="checkedKeys" showSearch
:renderSuffix="renderSuffix" />
</div>
</template>

View File

@ -60,7 +60,7 @@ export const useMubiao = () => {
// 获取目标坐标
const mbPos = await getMubiaoCurPos(targetIdList)
console.log('mbPos', mbPos)
// console.log('mbPos', mbPos)
nodes.forEach(({ data, dataId: id }: IMubiao) => {
const { target_time, target_geom } =
@ -105,6 +105,7 @@ export const useMubiao = () => {
id,
targetType,
})
const mubiaoEntity = viewer.entities.add({
name: id,
position: Cesium.Cartesian3.fromDegrees(...(position as number[])),

View File

@ -0,0 +1,127 @@
// import { h } from 'vue'
import { NDataTable, NInputNumber, NSwitch } from 'naive-ui'
import { useModal } from '@/views/Content/hooks/modal'
const { openDetailsModal } = useModal()
export const useMubiaoDetail = () => {
return { showDetailsMubiao }
}
const detectingLoadColumns = [
{
title: '垂直起始角',
key: 'minimumClock',
width: 180,
render(row) {
return (
<NInputNumber
v-model:value={row.minimumClock}
// disabled={['', ''].includes(row.type)}
></NInputNumber>
)
},
},
// {
// title: '',
// key: 'maximumClock',
// width: 180,
// render(row) {
// return (
// <NInputNumber
// v-model:value={row.maximumClock}
// // disabled={['', ''].includes(row.type)}
// ></NInputNumber>
// )
// },
// },
{
title: '水平起始角',
key: 'minimumCone',
width: 180,
render(row) {
return (
<NInputNumber
v-model:value={row.minimumCone}
// disabled={['', ''].includes(row.type)}
></NInputNumber>
)
},
},
{
title: '水平终止角',
key: 'maximumCone',
width: 180,
render(row) {
return (
<NInputNumber
v-model:value={row.maximumCone}
// disabled={['', ''].includes(row.type)}
></NInputNumber>
)
},
},
{
title: '半径',
key: 'radius',
width: 180,
render(row) {
return (
<NInputNumber
v-model:value={row.radius}
// disabled={['', ''].includes(row.type)}
></NInputNumber>
)
},
},
{
title: '是否开启',
key: 'status',
render(row) {
return <NSwitch v-model:value={row.status}></NSwitch>
},
},
]
const data = ref([
{
id: 3,
radius: 5000,
minimumClock: 20.0,
maximumClock: 110.0,
minimumCone: 20.0,
maximumCone: 90.0,
height: 100,
status: true,
},
])
function renderMubiaoDetailsContent(mbData) {
// return h(
// 'div',
// {},
// Object.keys(mbData).map(key => h('div', {}, `${key}${mbData[key]}`))
// )
return () => (
<div class="detail-container">
<div class="detail-item-title">基本信息</div>
<div>
{Object.keys(mbData).map(key => (
<div>
{key}{mbData[key]}
</div>
))}
</div>
<div class="detail-item-title">探测载荷</div>
<NDataTable
key={row => row.id}
columns={detectingLoadColumns}
data={data.value}
/>
</div>
)
}
function showDetailsMubiao(mbData) {
openDetailsModal({
titleString: 'zb详情',
contentSlot: renderMubiaoDetailsContent(mbData),
})
}

View File

@ -1,19 +0,0 @@
import { h } from 'vue'
import { useModal } from '@/views/Content/hooks/modal'
const { openDetailsModal } = useModal()
export const useMubiaoDetail = () => {
return { showDetailsMubiao }
}
function renderMubiaoDetailsContent(mbData: Record<string, any>) {
return h(
'div',
{},
Object.keys(mbData).map(key => h('div', {}, `${key}${mbData[key]}`))
)
}
function showDetailsMubiao(mbData: Record<string, any>) {
openDetailsModal({
titleString: 'zb详情',
contentSlot: renderMubiaoDetailsContent(mbData),
})
}

View File

@ -0,0 +1,18 @@
import { useModal } from '@/views/Content/hooks/modal'
const { openDetailsModal } = useModal()
export const useMubiaoDetail = () => {
return { showDetailsMubiao }
}
function renderMubiaoDetailsContent(mbData) {
return (
<>
<div></div>
</>
)
}
function showDetailsMubiao(mbData) {
openDetailsModal({
titleString: '载荷',
contentSlot: renderMubiaoDetailsContent(mbData),
})
}

View File

@ -51,6 +51,16 @@ const renderSuffix = ({ option }: { option: TreeOption }) => {
},
{ default: () => '详情' }
),
// h(
// NButton,
// {
// text: true,
// size: 'tiny',
// type: 'info',
// onClick: () => showDetailsMubiao(option.data),
// },
// { default: () => '' }
// ),
h(
NButton,
{
@ -89,14 +99,8 @@ const renderSuffix = ({ option }: { option: TreeOption }) => {
@update:checked-row-keys="handleCheck"
/> -->
<div class="h-0 flex-1">
<tree
:data="data"
:key-field="'dataId'"
:label-field="'nodeName'"
v-model:checked="checkedKeys"
showSearch
:renderSuffix="renderSuffix"
/>
<tree :data="data" :key-field="'dataId'" :label-field="'nodeName'" v-model:checked="checkedKeys" showSearch
:renderSuffix="renderSuffix" />
</div>
<!-- :nodeProps="nodeProps" -->

View File

@ -0,0 +1,127 @@
import { useModal } from '@/views/Content/hooks/modal'
import { NDataTable, NSelect, NInputNumber, NSwitch } from 'naive-ui'
const { openDetailsModal } = useModal()
const detectingLoadColumns = [
// {
// title: '',
// key: 'type',
// render(row) {
// return (
// <NSelect
// v-model:value={row.type}
// options={[
// { label: '', value: '' },
// { label: '', value: '' },
// { label: '', value: '' },
// ]}
// ></NSelect>
// )
// },
// },
{
title: '开合角',
key: 'angle',
width: 120,
render(row) {
return (
<NInputNumber
v-model:value={row.angle}
max={120}
min={0}
// disabled={['', ''].includes(row.type)}
></NInputNumber>
)
},
},
// {
// title: '',
// key: 'xHalfAngle',
// width: 120,
// render(row) {
// return (
// <NInputNumber
// v-model:value={row.angle}
// max={120}
// min={0}
// disabled={[''].includes(row.type)}
// ></NInputNumber>
// )
// },
// },
// {
// title: '',
// key: 'yHalfAngle',
// width: 120,
// render(row) {
// return (
// <NInputNumber
// v-model:value={row.angle}
// max={120}
// min={0}
// disabled={[''].includes(row.type)}
// ></NInputNumber>
// )
// },
// },
{
title: '是否开启',
key: 'status',
render(row) {
return <NSwitch v-model:value={row.status}></NSwitch>
},
},
]
const data = ref([
{
id: 3,
angle: 30,
// xHalfAngle: 20,
// yHalfAngle: 25,
// type: '',
status: true,
},
])
export function showDetailsSatellite(option) {
openDetailsModal({
titleString: '' + option.name + ' 详情',
contentSlot: () => (
<div class="detail-container">
<div class="detail-item-title">基本信息</div>
<div>
<div>卫星编号{option.name}</div>
<div class="flex">
<div>两行根数</div>
<div>
<p>{option.tle.split('\n')[0]}</p>
<p>{option.tle.split('\n')[1]}</p>
<p>{option.tle.split('\n')[2]}</p>
</div>
</div>
</div>
<div class="detail-item-title">探测载荷</div>
<NDataTable
key={row => row.id}
columns={detectingLoadColumns}
data={data.value}
/>
<div class="detail-item-title">通信载荷</div>
{/* <NDataTable
key={row => row.id}
columns={detectingLoadColumns}
data={data.value}
/> */}
</div>
),
})
}
// export default defineComponent({
// name: 'SatDetail',
// setup(props) {
// return () => <></>
// },
// })

View File

@ -2,11 +2,10 @@ import SatelliteEntity from '@/js/SatelliteEntity'
import { difference } from 'lodash'
import { useEntity } from '@/hooks/entity'
// import CreateFrustum from '@/js/Sensor'
import * as CesiumSensorVolumes from 'cesium-sensors-es6'
interface ISatellite {
name: string
id: number | string
id: string
tle: string
}
@ -18,6 +17,17 @@ interface IBaseFilterParam {
const satelliteList = ref<ISatellite[]>([])
const { satelliteMap } = useEntity()
const showPoint = ref(true)
function showPointUnderSat(id?: string) {
if (id) {
satelliteMap.get(id).underPoint = true
} else {
;[...satelliteMap.values()].forEach(satellite => {
satellite.underPoint = showPoint.value
})
}
}
export function useSatellite() {
function addSatellites(ids: Array<string | number>) {
const addIds = difference(ids, [...satelliteMap.keys()])
@ -33,6 +43,9 @@ export function useSatellite() {
nodes.forEach(node => {
const entity = addSatellite(node)
satelliteMap.set(node.id, entity)
if (showPoint.value) {
showPointUnderSat(node.id)
}
})
}
@ -47,9 +60,10 @@ export function useSatellite() {
// const result = viewer.entities.add(cesiumSateEntity)
setTimeout(() => {
satellite.sensorType = Math.random() > 0.5 ? 'conic' : 'rectangle'
// satellite.sensorType = Math.random() > 0.5 ? 'conic' : 'rectangle'
satellite.sensor = true
}, 1000)
// viewer.clock.multiplier = 100
return satellite
@ -85,7 +99,7 @@ export function useSatellite() {
}
}
return { satelliteList, addSatellites }
return { satelliteList, addSatellites, showPoint, showPointUnderSat }
}
function filterTreeNodeByField({

View File

@ -4,6 +4,7 @@ import { NButton } from 'naive-ui'
import Tree from '@/components/Tree/index.vue'
import { getSatellite } from '@/api/Satellite'
import { useSatellite } from './hooks/satellite'
import { showDetailsSatellite } from './components/SatDetail'
const { satelliteList, addSatellites } = useSatellite()
@ -35,7 +36,7 @@ const renderSuffix = ({ option }: { option: TreeOption }) => {
text: true,
size: 'tiny',
type: 'info',
// onClick: () => showDetailsSatellite(option),
onClick: () => showDetailsSatellite(option),
},
{ default: () => '详情' }
),
@ -67,5 +68,6 @@ const renderSuffix = ({ option }: { option: TreeOption }) => {
showSearch
:renderSuffix="renderSuffix"
/>
<!-- <sat-detail /> -->
</div>
</template>