Compare commits

..

No commits in common. "0dca401b1c9acda368de1e4aabf6bc735dd2e460" and "49e963e196af76f2510b70c4deaa1209f9dea143" have entirely different histories.

12 changed files with 601 additions and 840 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -108,7 +108,7 @@ const emit = defineEmits(['view-plan', 'start-dispatch'])
*/
const getLevelText = (level) => {
const item = RESPONSE_LEVELS.find(l => l.value === level)
return item ? `${item.label}` : '级'
return item ? `${item.label}` : '级'
}
/**

View File

@ -5,7 +5,7 @@
<!-- 触发器 -->
<div class="dropdown-trigger" @click="toggleDropdown">
<span class="trigger-text"
>距离灾害点{{ forcePreset.searchRadius - 20 }}km范围内</span
>距离灾害点{{ forcePreset.searchRadius }}km范围内</span
>
<div class="trigger-icon" :class="{ 'is-open': isDropdownOpen }">
<img
@ -63,8 +63,6 @@
<div class="stat-group">
<div class="stat-card stat-card--base">
<span class="stat-label flexBox"
@click="handleJump"
>应急基地与预置点
<img
@ -135,28 +133,14 @@ import { ref, inject } from "vue";
const { forcePreset } = inject("disasterData");
const onDistanceChange = inject("onDistanceChange");
const viewer = inject('cesiumViewer')
const triggerJump = inject('triggerJump')
//
const handleJump = () => {
console.log('viewer',viewer.value);
console.log('triggerJump',triggerJump);
if (viewer.value && triggerJump) {
triggerJump(5, 10) // 510
}
}
//
const isDropdownOpen = ref(false);
//
const distanceOptions = [
// { value: 10, label: "10km" },
{ value: 30, label: "距离灾害点10km范围内" },
{ value: 50, label: "距离灾害点30km范围内" },
{ value: 30, label: "距离灾害点30km范围内" },
{ value: 50, label: "距离灾害点50km范围内" },
];
/**

View File

@ -18,8 +18,7 @@
<span
class="info-source"
:style="{ color: getSourceColor(info.source) }"
>[{{ info.source }}]</span
>
>[{{ info.source }}]</span>
<span class="info-content">{{ info.content }}</span>
</div>
</div>
@ -27,61 +26,55 @@
</template>
<script setup>
import { inject, ref, computed } from "vue";
import { inject, ref, computed } from 'vue'
const { collaborationInfo } = inject("disasterData");
const { collaborationInfo } = inject('disasterData')
//
const isCollapsed = ref(false);
const isCollapsed = ref(false)
//
const toggleCollapse = () => {
isCollapsed.value = !isCollapsed.value;
};
isCollapsed.value = !isCollapsed.value
}
//
const displayedInfo = computed(() => {
if (isCollapsed.value && collaborationInfo.value.length > 0) {
//
return [collaborationInfo.value[0]];
return [collaborationInfo.value[0]]
}
//
return collaborationInfo.value;
});
return collaborationInfo.value
})
//
const getSourceColor = (source) => {
const colorMap = {
气象预警: "#4A9EFF", //
气象部门: "#4A9EFF", //
公安部门: "#FF5555", //
交警部门: "#FF5555", //
应急管理: "#FF5555", //
融媒体中心: "#00D68F", // 绿
新闻中心: "#00D68F", // 绿
宣传部门: "#00D68F", // 绿
};
'气象预警': '#4A9EFF', //
'气象部门': '#4A9EFF', //
'公安部门': '#FF5555', //
'交警部门': '#FF5555', //
'应急管理': '#FF5555', //
'融媒体中心': '#00D68F', // 绿
'新闻中心': '#00D68F', // 绿
'宣传部门': '#00D68F' // 绿
}
return colorMap[source] || "#4A9EFF"; //
};
return colorMap[source] || '#4A9EFF' //
}
</script>
<style scoped lang="scss">
@use "@/styles/mixins.scss" as *;
@use "../../assets/styles/common.scss" as *;
@use '@/styles/mixins.scss' as *;
@use '../../assets/styles/common.scss' as *;
.collaboration-info {
position: relative;
margin-top: vh(12);
background: url("../../assets/images/信息面板bg.png") no-repeat center center;
background: url('../../assets/images/信息面板bg.png') no-repeat center center;
background-size: 100% 100%;
padding: 2px;
border: 2px solid transparent;
&:hover {
border: 2px solid rgba(255, 80, 80, 0.6);
box-shadow: 0 0 10px rgba(255, 80, 80, 0.6), 0 0 20px rgba(255, 0, 0, 0.4),
0 0 35px rgba(255, 0, 0, 0.25);
}
&__header {
position: absolute;
@ -138,7 +131,7 @@ const getSourceColor = (source) => {
font-size: fs(13);
font-family: SourceHanSansCN-Bold, sans-serif;
font-weight: 700;
margin-right: vw(4); //
margin-right: vw(4); //
}
.info-content {

View File

@ -143,14 +143,6 @@ onUnmounted(() => {
// border-radius: vw(8);
overflow: hidden;
transition: all 0.3s ease;
border: 2px solid transparent;
&:hover {
border: 2px solid rgba(255, 80, 80, 0.6);
box-shadow: 0 0 10px rgba(255, 80, 80, 0.6),
0 0 20px rgba(255, 0, 0, 0.4),
0 0 35px rgba(255, 0, 0, 0.25);
}
//
&.is-collapsed {

View File

@ -1,9 +1,9 @@
<template>
<section
class="collapsible-panel"
:class="{ 'collapsible-panel--collapsed': isCollapsed }"
>
<div class="collapsible-panel__header" @click="handleHeaderClick">
<section class="collapsible-panel" :class="{ 'collapsible-panel--collapsed': isCollapsed }">
<div
class="collapsible-panel__header"
@click="handleHeaderClick"
>
<PanelHeader :title="title" :subtitle="subtitle">
<template #title-icon>
<slot name="title-icon" />
@ -48,20 +48,20 @@
</template>
<script setup>
import { computed, ref } from "vue";
import PanelHeader from "./PanelHeader.vue";
import toggleIcon from "../../assets/images/展开icon.png";
import { computed, ref } from 'vue'
import PanelHeader from './PanelHeader.vue'
import toggleIcon from '../../assets/images/展开icon.png'
const props = defineProps({
/** 面板标题 */
title: {
type: String,
required: true,
required: true
},
/** 面板副标题 */
subtitle: {
type: String,
default: "",
default: ''
},
/**
* v-model:collapsed 控制收起状态
@ -70,98 +70,97 @@ const props = defineProps({
*/
collapsed: {
type: Boolean,
default: undefined,
default: undefined
},
/** 是否可收缩 */
collapsible: {
type: Boolean,
default: true,
default: true
},
/** 初始是否收起(仅在非受控模式下生效) */
startCollapsed: {
type: Boolean,
default: false,
},
});
default: false
}
})
const emit = defineEmits(["update:collapsed", "toggle", "title-click"]);
const emit = defineEmits(['update:collapsed', 'toggle', 'title-click'])
//
const isControlled = computed(() => props.collapsed !== undefined);
const isControlled = computed(() => props.collapsed !== undefined)
//
const internalCollapsed = ref(!!props.startCollapsed);
const internalCollapsed = ref(!!props.startCollapsed)
//
const isCollapsed = computed({
get() {
return isControlled.value ? !!props.collapsed : internalCollapsed.value;
return isControlled.value ? !!props.collapsed : internalCollapsed.value
},
set(value) {
if (isControlled.value) {
emit("update:collapsed", value);
emit('update:collapsed', value)
} else {
internalCollapsed.value = value;
internalCollapsed.value = value
}
emit("toggle", value);
},
});
emit('toggle', value)
}
})
//
function handleHeaderClick() {
emit("title-click");
emit('title-click')
}
// /
function toggle() {
if (!props.collapsible) return;
isCollapsed.value = !isCollapsed.value;
if (!props.collapsible) return
isCollapsed.value = !isCollapsed.value
}
//
function onBeforeEnter(el) {
el.style.height = "0";
el.style.overflow = "hidden";
el.style.height = '0'
el.style.overflow = 'hidden'
}
function onEnter(el) {
// 使 requestAnimationFrame
requestAnimationFrame(() => {
el.style.height = `${el.scrollHeight}px`;
});
el.style.height = `${el.scrollHeight}px`
})
}
function onAfterEnter(el) {
el.style.height = "";
el.style.overflow = "";
el.style.height = ''
el.style.overflow = ''
}
function onBeforeLeave(el) {
el.style.height = `${el.scrollHeight}px`;
el.style.overflow = "hidden";
el.style.height = `${el.scrollHeight}px`
el.style.overflow = 'hidden'
}
function onLeave(el) {
//
el.offsetHeight; // eslint-disable-line no-unused-expressions
el.style.height = "0";
el.offsetHeight // eslint-disable-line no-unused-expressions
el.style.height = '0'
}
function onAfterLeave(el) {
el.style.height = "";
el.style.overflow = "";
el.style.height = ''
el.style.overflow = ''
}
</script>
<style scoped lang="scss">
@use "@/styles/mixins.scss" as *;
@use "../../assets/styles/common.scss" as *;
@use '@/styles/mixins.scss' as *;
@use '../../assets/styles/common.scss' as *;
.collapsible-panel {
display: flex;
flex-direction: column;
margin-bottom: vh(17);
border: 2px solid transparent; /* 防止 hover 时抖动 */
margin-bottom: vh(20);
&__header {
position: relative;
@ -213,24 +212,24 @@ function onAfterLeave(el) {
// 使
&::before {
content: "";
content: '';
position: absolute;
inset: 0;
border-style: solid;
border-width: vh(25) vw(30);
border-image-source: url("../../assets/images/通用卡片bg.png");
border-image-source: url('../../assets/images/通用卡片bg.png');
border-image-slice: 25 30 25 30 fill;
border-image-width: vh(25) vw(30);
border-image-repeat: stretch;
box-sizing: border-box;
pointer-events: none; //
z-index: -1; //
pointer-events: none; //
z-index: -1; //
}
}
&__content {
position: relative;
padding: vh(5) vw(20) vh(5); //
padding: vh(5) vw(20) vh(5); //
z-index: 1;
//
@ -245,14 +244,8 @@ function onAfterLeave(el) {
&--collapsed {
.collapsible-panel__body {
height: 0;
border-width: 0; // border
border-width: 0; // border
}
}
}
.collapsible-panel:hover {
border: 2px solid rgba(255, 80, 80, 0.6); /* 柔和的渐变红基色 */
box-shadow: 0 0 10px rgba(255, 80, 80, 0.6), 0 0 20px rgba(255, 0, 0, 0.4),
0 0 35px rgba(255, 0, 0, 0.25); /* 多层柔光渐变效果 */
}
</style>

View File

@ -7,11 +7,11 @@ export function useDisasterData() {
// 灾害基本信息
const disasterInfo = ref({
type: '边坡垮塌',
volume: '1220',
volume: '10022',
volumeUnit: 'm³',
length: '33',
length: '13',
lengthUnit: 'm',
width: '15',
width: '5',
widthUnit: 'm',
casualties: '0',
vehicles: '0',
@ -43,7 +43,7 @@ export function useDisasterData() {
// 力量调度信息
const forceDispatch = ref({
responseLevel: 4, // 四
responseLevel: 3, // 三
estimatedClearTime: getCurrentDateTime('24:00'),
plan: {
name: '智能应急方案',

View File

@ -1,7 +1,6 @@
import { ref, onUnmounted } from 'vue'
import * as Cesium from 'cesium'
import { use3DTiles } from './use3DTiles'
import { useMapMarkers } from '../composables/useMapMarkers'
/**
* 双地图对比模式
@ -30,8 +29,6 @@ export function useDualMapCompare() {
const { load3DTileset } = use3DTiles()
const { drawCollapseBoundaryLeft } = useMapMarkers()
/**
* 初始化左侧Viewer灾前场景
* @param {HTMLElement} container - 容器元素
@ -602,13 +599,6 @@ export function useDualMapCompare() {
}
await enableCompareMode(rightViewerInstance, options)
const leftContainer = document.getElementById('leftCesiumContainer')
if (leftContainer) {
console.log('[index.vue] 绘制左侧坍塌边界红色虚线...')
drawCollapseBoundaryLeft(leftContainer)
console.log('[index.vue] 左侧坍塌边界红色虚线已添加')
}
} else {
disableCompareMode()
}

View File

@ -107,12 +107,11 @@ export function useMapMarkers() {
const resolveBillboardHeightReference = (samplingSucceeded) => {
// 统一使用 CLAMP_TO_GROUND 让 Cesium 自动贴地
// 配合禁用的 3D Tiles 动态细化,标记位置会保持稳定
// return Cesium.HeightReference.CLAMP_TO_GROUND
return Cesium.HeightReference.RELATIVE_TO_GROUND
return Cesium.HeightReference.CLAMP_TO_GROUND
}
/**
* 绘制坍塌边界色实线右侧地图
* 绘制坍塌边界色实线右侧地图
* 在右侧地图上显示灾后坍塌的边界轮廓
* @param {Cesium.Viewer} viewer
* @returns {Cesium.Entity | null} 返回创建的边界线实体
@ -144,12 +143,12 @@ export function useMapMarkers() {
console.log('[useMapMarkers] 右侧坍塌边界点位数量:', positions.length)
// 创建色实线边界
// 创建色实线边界
const boundaryEntity = viewer.entities.add({
polyline: {
positions: positions,
width: 4, // 增加线宽,更明显
material: Cesium.Color.fromCssColorString('#FF0000').withAlpha(1.0), // 纯色实线
material: Cesium.Color.fromCssColorString('#1CA1FF').withAlpha(1.0), // 纯色实线
clampToGround: true,
classificationType: Cesium.ClassificationType.BOTH, // 同时分类到地形和3D Tiles
zIndex: 1 // 提高层级
@ -162,7 +161,7 @@ export function useMapMarkers() {
})
collapseBoundaryEntity.value = boundaryEntity
console.log('[useMapMarkers] 右侧坍塌边界色实线绘制完成, entityId:', boundaryEntity.id)
console.log('[useMapMarkers] 右侧坍塌边界色实线绘制完成, entityId:', boundaryEntity.id)
// 强制渲染场景
viewer.scene.requestRender()
@ -216,7 +215,7 @@ export function useMapMarkers() {
positions: positions,
width: 4,
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.fromCssColorString('#FF0000').withAlpha(1.0), //
color: Cesium.Color.fromCssColorString('#1CA1FF').withAlpha(1.0), //
dashLength: 16.0, // 虚线段长度
dashPattern: 255.0 // 虚线模式
}),
@ -579,18 +578,19 @@ export function useMapMarkers() {
},
properties: isSoldier
? {
type: 'soldier',
name: soldierNames[Math.floor(Math.random() * soldierNames.length)],
department: soldierDepartments[Math.floor(Math.random() * soldierDepartments.length)],
location: `目前为止距离现场${locationDistance}公里`
}
type: 'soldier',
name: soldierNames[Math.floor(Math.random() * soldierNames.length)],
department: soldierDepartments[Math.floor(Math.random() * soldierDepartments.length)],
location: `目前为止距离现场${locationDistance}公里`
}
: {
type: 'device',
name: deviceNames[Math.floor(Math.random() * deviceNames.length)],
deviceType: deviceTypes[Math.floor(Math.random() * deviceTypes.length)],
location: `目前为止距离现场${locationDistance}公里`
}
type: 'device',
name: deviceNames[Math.floor(Math.random() * deviceNames.length)],
deviceType: deviceTypes[Math.floor(Math.random() * deviceTypes.length)],
location: `目前为止距离现场${locationDistance}公里`
}
})
entities.push(entity)
}
@ -849,7 +849,6 @@ export function useMapMarkers() {
icon = otherEmergencyIcon
type = 'other'
levelName = '其他'
// return;
}
console.log(`[useMapMarkers] 添加标记: ${item.gl1Yjllmc}, 级别: ${levelString} (${levelName})`)
@ -858,7 +857,7 @@ export function useMapMarkers() {
position: result.position,
billboard: {
image: icon,
width: 32,
width: 29,
height: 32,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
heightReference: resolveBillboardHeightReference(result.samplingSucceeded),
@ -973,96 +972,6 @@ export function useMapMarkers() {
}
}
/**
* 触发实体像素级跳动动画
* @param {Cesium.Viewer} viewer
* @param {number} duration 动画持续时间
* @param {number} pixelJumpHeight 像素跳动高度
*/
const triggerJumpAnimation = (viewer, duration = 5, pixelJumpHeight = 30) => {
console.log(`[useMapMarkers] 触发像素跳动动画,持续时间 ${duration} 秒,像素高度 ${pixelJumpHeight}`)
if (!viewer) {
console.warn('[useMapMarkers] triggerJumpAnimation: viewer 为空')
return
}
// 收集所有需要动画的实体(包含所有类型)
const allEntities = [
...markerEntities.value,
...emergencyResourceEntities.value,
...reserveCenterEntities.value
].filter(e => e.billboard) // 只需要有billboard的实体
// 存储原始像素偏移量
const entitiesData = allEntities.map(entity => ({
entity,
originalOffset: entity.billboard.pixelOffset
? new Cesium.Cartesian2(
entity.billboard.pixelOffset.x,
entity.billboard.pixelOffset.y
)
: new Cesium.Cartesian2(0, 0)
}))
// 动画参数
const startTime = Date.now()
const jumpDuration = 800 // 单次跳动周期(毫秒)
let animationActive = true
// 定义动画函数
const animate = () => {
if (!animationActive) return
const elapsed = Date.now() - startTime
const progress = (elapsed % jumpDuration) / jumpDuration
// 使用正弦波计算当前像素偏移
const currentOffset = Math.sin(progress * Math.PI * 2) * pixelJumpHeight
// 应用像素偏移到所有实体
entitiesData.forEach(({ entity, originalOffset }) => {
try {
entity.billboard.pixelOffset = new Cesium.Cartesian2(
originalOffset.x,
originalOffset.y - currentOffset // Y轴负方向为上移
)
} catch (error) {
console.warn('[useMapMarkers] 更新实体像素偏移失败:', error)
}
})
// 继续动画直到持续时间结束
if (elapsed < duration * 1000) {
requestAnimationFrame(animate)
} else {
// 恢复原始像素偏移
entitiesData.forEach(({ entity, originalOffset }) => {
entity.billboard.pixelOffset = originalOffset
})
animationActive = false
console.log('[useMapMarkers] 像素跳动动画结束')
}
}
// 启动动画
animate()
// 提供手动停止方法
return () => {
if (animationActive) {
animationActive = false
entitiesData.forEach(({ entity, originalOffset }) => {
entity.billboard.pixelOffset = originalOffset
})
console.log('[useMapMarkers] 像素跳动动画已手动停止')
}
}
}
return {
collapseAreaEntities,
markerEntities,
@ -1087,8 +996,7 @@ export function useMapMarkers() {
clearEmergencyResourceMarkers,
addReserveCenterMarkers,
clearReserveCenterMarkers,
getCollapseCenter: calculateCollapseCenter, // 提前获取中心点,不依赖标记初始化
triggerJumpAnimation,
getCollapseCenter: calculateCollapseCenter // 提前获取中心点,不依赖标记初始化
}
}

View File

@ -64,20 +64,6 @@ export function useRangeCircle() {
outlineWidth: style.outlineWidth,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
label: {
text: `${radiusKm.toFixed(1) - 20} km`, // 实际是30 / 50 km 临时修改减少20km 后续调整
font: 'bold 24px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
pixelOffset: new Cesium.Cartesian2(0, 0),
backgroundColor: Cesium.Color.fromCssColorString('#333').withAlpha(0.7),
backgroundPadding: new Cesium.Cartesian2(18, 12),
showBackground: true
}
})
// 禁用范围圈的鼠标交互,让点击可以穿透到下面的标记点