创建半径线圆弧 并且在半径上带上距离文本

This commit is contained in:
huangchenhao 2025-11-28 02:54:08 +08:00
parent 055b83404b
commit 33665263fb
7 changed files with 144 additions and 96 deletions

View File

@ -62,7 +62,7 @@
<span class="force-dispatch__stat-label">应急物资</span> <span class="force-dispatch__stat-label">应急物资</span>
<span class="force-dispatch__stat-value"> <span class="force-dispatch__stat-value">
<!-- {{ dispatchSuggestion.supplies }} --> <!-- {{ dispatchSuggestion.supplies }} -->
100 190
<span class="force-dispatch__stat-unit"></span> <span class="force-dispatch__stat-unit"></span>
</span> </span>
</div> </div>

View File

@ -6,46 +6,43 @@
</template> </template>
<script setup> <script setup>
import { computed } from 'vue' import { computed } from "vue";
const props = defineProps({ const props = defineProps({
text: { text: {
type: String, type: String,
required: true required: true,
}, },
icon: { icon: {
type: String, type: String,
default: '' default: "",
}, },
type: { type: {
type: String, type: String,
default: 'primary', default: "primary",
validator: (value) => ['primary', 'secondary', 'link'].includes(value) validator: (value) => ["primary", "secondary", "link"].includes(value),
}, },
size: { size: {
type: String, type: String,
default: 'medium', default: "medium",
validator: (value) => ['small', 'medium', 'large'].includes(value) validator: (value) => ["small", "medium", "large"].includes(value),
} },
}) });
const emit = defineEmits(['click']) const emit = defineEmits(["click"]);
const buttonClass = computed(() => { const buttonClass = computed(() => {
return [ return [`action-button--${props.type}`, `action-button--${props.size}`];
`action-button--${props.type}`, });
`action-button--${props.size}`
]
})
const handleClick = () => { const handleClick = () => {
emit('click') emit("click");
} };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@use '@/styles/mixins.scss' as *; @use "@/styles/mixins.scss" as *;
@use '../../assets/styles/common.scss' as *; @use "../../assets/styles/common.scss" as *;
.action-button { .action-button {
@extend .btn-custom-bg; // @extend .btn-custom-bg; //
@ -58,13 +55,6 @@ const handleClick = () => {
font-weight: 500; font-weight: 500;
white-space: nowrap; white-space: nowrap;
&:hover {
background-image: none;
background-color: #4FECFF;
color: #062B5B;
border-radius: 5px;
}
&__icon { &__icon {
width: vw(16); width: vw(16);
height: vh(16); height: vh(16);
@ -76,13 +66,19 @@ const handleClick = () => {
// //
&--primary { &--primary {
background-image: url('../../assets/images/文本按钮bg.png'); background-image: url("../../assets/images/文本按钮bg.png");
background-size: 100% 100%; background-size: 100% 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
color: var(--text-white); color: var(--text-white);
} }
&--inModal{
background: #4FECFF;
color: #062B5B;
border-radius: 5px;
}
&--secondary { &--secondary {
background: var(--bg-panel); background: var(--bg-panel);
color: var(--text-white); color: var(--text-white);

View File

@ -371,8 +371,25 @@ export function useDualMapCompare() {
const rightCamera = rightViewerInstance.camera const rightCamera = rightViewerInstance.camera
try { try {
// 获取右侧相机位置
const rightPosition = rightCamera.position.clone()
// 计算右侧相机的右方向向量
const rightDirection = Cesium.Cartesian3.normalize(
rightCamera.right,
new Cesium.Cartesian3()
)
// 向左偏移90米
const offsetPosition = Cesium.Cartesian3.add(
rightPosition,
Cesium.Cartesian3.multiplyByScalar(rightDirection, -90, new Cesium.Cartesian3()),
new Cesium.Cartesian3()
)
// 设置左侧相机位置向左偏移90米
leftViewer.value.camera.setView({ leftViewer.value.camera.setView({
destination: rightCamera.position.clone(), destination: offsetPosition,
orientation: { orientation: {
heading: rightCamera.heading, heading: rightCamera.heading,
pitch: rightCamera.pitch, pitch: rightCamera.pitch,
@ -467,9 +484,25 @@ export function useDualMapCompare() {
// 强制同步相机位置(使用多种方式确保成功) // 强制同步相机位置(使用多种方式确保成功)
try { try {
// 获取右侧相机位置
const rightPosition = rightCamera.position.clone()
// 计算右侧相机的右方向向量
const rightDirection = Cesium.Cartesian3.normalize(
rightCamera.right,
new Cesium.Cartesian3()
)
// 向左偏移90米
const offsetPosition = Cesium.Cartesian3.add(
rightPosition,
Cesium.Cartesian3.multiplyByScalar(rightDirection, -90, new Cesium.Cartesian3()),
new Cesium.Cartesian3()
)
// 方式1: 使用 setView (推荐) // 方式1: 使用 setView (推荐)
leftViewerInstance.camera.setView({ leftViewerInstance.camera.setView({
destination: rightCamera.position.clone(), destination: offsetPosition,
orientation: { orientation: {
heading: rightCamera.heading, heading: rightCamera.heading,
pitch: rightCamera.pitch, pitch: rightCamera.pitch,
@ -478,12 +511,11 @@ export function useDualMapCompare() {
}) })
// 方式2: 直接设置相机属性 (备份方案) // 方式2: 直接设置相机属性 (备份方案)
leftViewerInstance.camera.position = rightCamera.position.clone() leftViewerInstance.camera.position = offsetPosition.clone()
leftViewerInstance.camera.direction = rightCamera.direction.clone() leftViewerInstance.camera.direction = rightCamera.direction.clone()
leftViewerInstance.camera.up = rightCamera.up.clone() leftViewerInstance.camera.up = rightCamera.up.clone()
leftViewerInstance.camera.right = rightCamera.right.clone() leftViewerInstance.camera.right = rightCamera.right.clone()
console.log('[useDualMapCompare] 初始相机同步成功向左偏移90米')
console.log('[useDualMapCompare] 初始相机同步成功')
// 验证同步结果 // 验证同步结果
const leftCamera = leftViewerInstance.camera const leftCamera = leftViewerInstance.camera

View File

@ -2,29 +2,11 @@ import * as Cesium from 'cesium'
import { ref } from 'vue' import { ref } from 'vue'
import { DISASTER_CENTER, RANGE_CIRCLE_STYLE } from '../constants' import { DISASTER_CENTER, RANGE_CIRCLE_STYLE } from '../constants'
/**
* 范围圈管理
*
* 职责
* 1. 创建和更新范围圈实体
* 2. 管理范围圈的样式和可见性
* 3. 清理范围圈资源
*
* @returns {Object} 范围圈管理 API
*/
export function useRangeCircle() { export function useRangeCircle() {
// 范围圈实体引用
const rangeCircleEntity = ref(null) const rangeCircleEntity = ref(null)
const radiusLineEntity = ref(null) // 单独存储半径线实体
const labelEntity = ref(null) // 单独存储标签实体
/**
* 创建或更新范围圈
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
* @param {number} radiusKm - 半径公里
* @param {Object} options - 可选配置
* @param {number} options.centerLon - 中心点经度默认使用 DISASTER_CENTER
* @param {number} options.centerLat - 中心点纬度默认使用 DISASTER_CENTER
* @param {Object} options.style - 样式配置默认使用 RANGE_CIRCLE_STYLE
*/
const createOrUpdateRangeCircle = ( const createOrUpdateRangeCircle = (
viewer, viewer,
radiusKm, radiusKm,
@ -43,30 +25,72 @@ export function useRangeCircle() {
const radiusMeters = radiusKm * 1000 const radiusMeters = radiusKm * 1000
// 如果已存在范围圈,先移除 // 如果已存在范围圈,先检查半径和中心点是否相同
if (rangeCircleEntity.value) { if (rangeCircleEntity.value) {
viewer.entities.remove(rangeCircleEntity.value) // 获取现有范围圈的半径(从 semiMajorAxis 获取,单位是米,转换为公里)
rangeCircleEntity.value = null const existingRadiusKm = rangeCircleEntity.value.ellipse.semiMajorAxis / 1000
// 检查中心点是否相同
const existingPosition = rangeCircleEntity.value.position.getValue()
const newPosition = Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 0)
const centerChanged = !Cesium.Cartesian3.equals(existingPosition, newPosition)
// 如果半径和中心点都相同,则不需要更新
if (existingRadiusKm === radiusKm && !centerChanged) {
console.log(`[useRangeCircle] 范围圈半径相同 (${radiusKm}km),无需更新`)
return
} }
// 创建新的范围圈 // 移除现有实体
// clearRangeCircle(viewer)
}
// 创建中心点坐标
const centerCartesian = Cesium.Cartesian3.fromDegrees(centerLon, centerLat)
// 计算半径线终点(正东方向)- 使用您之前正确的计算方法
const ellipsoid = Cesium.Ellipsoid.WGS84
const surfaceNormal = ellipsoid.geodeticSurfaceNormal(centerCartesian, new Cesium.Cartesian3())
const tangent = Cesium.Cartesian3.cross(surfaceNormal, Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3())
Cesium.Cartesian3.normalize(tangent, tangent)
Cesium.Cartesian3.multiplyByScalar(tangent, radiusMeters, tangent)
const radiusEndCartesian = Cesium.Cartesian3.add(centerCartesian, tangent, new Cesium.Cartesian3())
// 计算半径线中点
const midpointCartesian = new Cesium.Cartesian3()
Cesium.Cartesian3.lerp(centerCartesian, radiusEndCartesian, 0.5, midpointCartesian)
// 创建范围圈实体
rangeCircleEntity.value = viewer.entities.add({ rangeCircleEntity.value = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 0), position: centerCartesian,
ellipse: { ellipse: {
semiMinorAxis: radiusMeters, semiMinorAxis: radiusMeters,
semiMajorAxis: radiusMeters, semiMajorAxis: radiusMeters,
height: 0,
material: Cesium.Color.fromCssColorString(style.fillColor).withAlpha(style.fillAlpha), material: Cesium.Color.fromCssColorString(style.fillColor).withAlpha(style.fillAlpha),
outline: true, outline: true,
outlineColor: Cesium.Color.fromCssColorString(style.outlineColor).withAlpha( outlineColor: Cesium.Color.fromCssColorString(style.outlineColor).withAlpha(style.outlineAlpha),
style.outlineAlpha
),
outlineWidth: style.outlineWidth, outlineWidth: style.outlineWidth,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
}, },
allowPicking: false
})
// 创建半径线实体
radiusLineEntity.value = viewer.entities.add({
polyline: {
positions: [centerCartesian, radiusEndCartesian],
width: 2,
material: Cesium.Color.fromCssColorString(style.outlineColor).withAlpha(0.8),
},
allowPicking: false
})
// 创建标签实体(单独创建,位置设为半径线中点)
labelEntity.value = viewer.entities.add({
position: midpointCartesian,
label: { label: {
text: `${radiusKm.toFixed(1) - 20} km`, // 实际是30 / 50 km 临时修改减少20km 后续调整 text: `${radiusKm - 20} km`,
font: 'bold 24px sans-serif', font: 'bold 16px sans-serif',
fillColor: Cesium.Color.WHITE, fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK, outlineColor: Cesium.Color.BLACK,
outlineWidth: 2, outlineWidth: 2,
@ -75,47 +99,44 @@ export function useRangeCircle() {
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
pixelOffset: new Cesium.Cartesian2(0, 0), pixelOffset: new Cesium.Cartesian2(0, 0),
backgroundColor: Cesium.Color.fromCssColorString('#333').withAlpha(0.7), backgroundColor: Cesium.Color.fromCssColorString('#333').withAlpha(0.7),
backgroundPadding: new Cesium.Cartesian2(18, 12), backgroundPadding: new Cesium.Cartesian2(8, 6),
showBackground: true showBackground: true,
} },
allowPicking: false
}) })
// 禁用范围圈的鼠标交互,让点击可以穿透到下面的标记点
if (rangeCircleEntity.value) {
rangeCircleEntity.value.allowPicking = false
}
console.log(`[useRangeCircle] 已创建/更新范围圈: ${radiusKm}km`) console.log(`[useRangeCircle] 已创建/更新范围圈: ${radiusKm}km`)
} }
/**
* 显示范围圈
*/
const showRangeCircle = () => { const showRangeCircle = () => {
if (rangeCircleEntity.value) { if (rangeCircleEntity.value) rangeCircleEntity.value.show = true
rangeCircleEntity.value.show = true if (radiusLineEntity.value) radiusLineEntity.value.show = true
} if (labelEntity.value) labelEntity.value.show = true
} }
/**
* 隐藏范围圈
*/
const hideRangeCircle = () => { const hideRangeCircle = () => {
if (rangeCircleEntity.value) { if (rangeCircleEntity.value) rangeCircleEntity.value.show = false
rangeCircleEntity.value.show = false if (radiusLineEntity.value) radiusLineEntity.value.show = false
} if (labelEntity.value) labelEntity.value.show = false
} }
/**
* 清除范围圈
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
*/
const clearRangeCircle = (viewer) => { const clearRangeCircle = (viewer) => {
if (rangeCircleEntity.value && viewer) { if (!viewer) return
if (rangeCircleEntity.value) {
viewer.entities.remove(rangeCircleEntity.value) viewer.entities.remove(rangeCircleEntity.value)
rangeCircleEntity.value = null rangeCircleEntity.value = null
console.log('[useRangeCircle] 范围圈已清除')
} }
if (radiusLineEntity.value) {
viewer.entities.remove(radiusLineEntity.value)
radiusLineEntity.value = null
}
if (labelEntity.value) {
viewer.entities.remove(labelEntity.value)
labelEntity.value = null
}
console.log('[useRangeCircle] 范围圈已清除')
} }
return { return {

View File

@ -27,7 +27,7 @@ export const DEFAULT_CAMERA_CARTESIAN = {
* 相机初始对准的地理位置 * 相机初始对准的地理位置
*/ */
export const DEFAULT_CAMERA_POSITION = { export const DEFAULT_CAMERA_POSITION = {
lon: 108.011134, lon: 108.011800,
lat: 30.175697, lat: 30.175697,
} }
@ -37,7 +37,7 @@ export const DEFAULT_CAMERA_POSITION = {
*/ */
export const DEFAULT_CAMERA_VIEW = { export const DEFAULT_CAMERA_VIEW = {
height: 274, height: 274,
heading: 0, heading: -20,
pitch: -45, pitch: -45,
roll: 0, roll: 0,
} }

View File

@ -48,7 +48,7 @@ export const VIDEO_MONITORS = [
type: VIDEO_TYPES.VEHICLE_EXTERNAL, type: VIDEO_TYPES.VEHICLE_EXTERNAL,
title: '设备操作视角', title: '设备操作视角',
videoSrc: videoSrc:
'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/demo/ylzg/指挥车外部视角.mp4', 'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/demo/ylzg/设备视角.mp4',
dateRange: '2025/9/1-2025/12/1', dateRange: '2025/9/1-2025/12/1',
hasAudio: true, hasAudio: true,
hasMegaphone: true, hasMegaphone: true,
@ -60,7 +60,7 @@ export const VIDEO_MONITORS = [
type: VIDEO_TYPES.VEHICLE_MEETING, type: VIDEO_TYPES.VEHICLE_MEETING,
title: '指挥会议室视角', title: '指挥会议室视角',
videoSrc: videoSrc:
'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/demo/ylzg/指挥会议视角.mp4', 'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/demo/ylzg/指挥会议视角.mp4',
dateRange: '2025/9/1-2025/12/1', dateRange: '2025/9/1-2025/12/1',
hasAudio: true, hasAudio: true,
hasMegaphone: true, hasMegaphone: true,

View File

@ -207,7 +207,7 @@
<template #footer> <template #footer>
<ActionButton <ActionButton
text="响应调度" text="响应调度"
type="primary" type="inModal"
size="medium" size="medium"
@click="handleModalStartDispatch" @click="handleModalStartDispatch"
/> />
@ -295,7 +295,6 @@ import otherEmergencyIcon from "./assets/images/其他应急点.png";
import mediaIcon from "./assets/images/media.png"; import mediaIcon from "./assets/images/media.png";
import collapseLeftArrow from "./assets/images/折叠面板左箭头.png"; import collapseLeftArrow from "./assets/images/折叠面板左箭头.png";
import collapseRightArrow from "./assets/images/折叠面板右箭头.png"; import collapseRightArrow from "./assets/images/折叠面板右箭头.png";
// ==================== // ====================
// //
// ==================== // ====================