This commit is contained in:
huangchenhao 2025-11-19 17:15:55 +08:00
commit 95283058d6
14 changed files with 330 additions and 66 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 KiB

View File

@ -82,7 +82,7 @@ const locationInfo = [
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr));
gap: clamp(10px, vh(14), 18px) clamp(12px, vw(16), 20px); gap: clamp(4px, vh(6), 8px) clamp(4px, vw(6), 8px);
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
@ -91,7 +91,7 @@ const locationInfo = [
.info-card { .info-card {
display: flex; display: flex;
align-items: center; align-items: center;
gap: clamp(8px, vw(12), 14px); gap: 3px;
// padding: clamp(10px, vh(12), 16px) clamp(12px, vw(14), 18px); // padding: clamp(10px, vh(12), 16px) clamp(12px, vw(14), 18px);
// background: rgba(10, 95, 165, 0.15); // background: rgba(10, 95, 165, 0.15);
border-radius: clamp(6px, vw(8), 10px); border-radius: clamp(6px, vw(8), 10px);
@ -137,9 +137,9 @@ const locationInfo = [
// //
.info-value { .info-value {
margin: 0; margin: 0;
font-size: clamp(15px, vw(18), 22px); font-size: 12px;
color: #e8f4ff; color: #e8f4ff;
font-weight: 600; // font-weight: 600;
line-height: 1.2; line-height: 1.2;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;

View File

@ -2,7 +2,7 @@
<div class="left-panel-wrapper"> <div class="left-panel-wrapper">
<div class="left-panel"> <div class="left-panel">
<CollapsiblePanel title="快速感知" subtitle="「灾害分析」"> <CollapsiblePanel title="快速感知" subtitle="「灾害分析」">
<template #header-right> <template #title-icon>
<img <img
src="../../assets/images/摄像头.png" src="../../assets/images/摄像头.png"
alt="摄像头" alt="摄像头"
@ -96,10 +96,11 @@ const handleStartDispatch = (payload) => {
.left-panel { .left-panel {
// width: vw(464); // width: vw(464);
height: 100%; height: 100%;
background: url('../../assets/images/SketchPng7ba5c49d9f8f79e6b559d62cfb6b0b0c79616dd8b289f8b62b5cb8adc18c30e7.png') no-repeat; background: url('../../assets/images/框架左边.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
overflow-y: auto; overflow-y: auto;
overscroll-behavior: contain; overscroll-behavior: contain;
padding-left: vw(40); //
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: vw(6); width: vw(6);
@ -128,7 +129,7 @@ const handleStartDispatch = (payload) => {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: vh(12); gap: vh(12);
width: vw(368); width: vw(220);
z-index: 10; z-index: 10;
} }
@ -187,11 +188,12 @@ const handleStartDispatch = (payload) => {
} }
.camera-icon { .camera-icon {
width: vw(24); width: vw(20);
height: vw(24); height: vw(20);
margin-right: vw(8); margin-left: vw(6);
cursor: pointer; cursor: pointer;
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
vertical-align: middle;
&:hover { &:hover {
opacity: 0.8; opacity: 0.8;

View File

@ -31,9 +31,9 @@
<span class="col-name">{{ item.name }}</span> <span class="col-name">{{ item.name }}</span>
<span class="col-dept">{{ item.department || item.type || '-' }}</span> <span class="col-dept">{{ item.department || item.type || '-' }}</span>
<span class="col-location">{{ item.distance || item.altitude || '-' }}</span> <span class="col-location">{{ item.distance || item.altitude || '-' }}</span>
<button class="col-action" @click="linkToItem(item)"> <span class="col-action" @click="linkToItem(item)">
联动 联动
</button> </span>
</div> </div>
</div> </div>
</div> </div>
@ -127,7 +127,8 @@ const getColumnName = (type) => {
} }
.table-body { .table-body {
max-height: vh(200); min-height: vh(205);
max-height: vh(205);
overflow-y: auto; overflow-y: auto;
&::-webkit-scrollbar { &::-webkit-scrollbar {
@ -165,15 +166,15 @@ const getColumnName = (type) => {
.col-action { .col-action {
padding: vh(4) vw(12); padding: vh(4) vw(12);
background: transparent; background: transparent;
border: 1px solid var(--primary-color); // border: 1px solid var(--primary-color);
border-radius: vw(4); // border-radius: vw(4);
color: var(--primary-color); color: var(--primary-color);
font-size: fs(12); font-size: fs(12);
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.3s;
&:hover { &:hover {
background: var(--primary-color); // background: var(--primary-color);
color: var(--text-white); color: var(--text-white);
} }
} }

View File

@ -1,15 +1,17 @@
<template> <template>
<div class="video-monitor-item"> <div class="video-monitor-item" :class="{ 'is-collapsed': isCollapsed }">
<div class="video-monitor-item__header"> <div class="video-monitor-item__header" @click="toggleCollapse">
<span class="video-title">{{ monitor.title }}</span> <span class="video-title">{{ monitor.title }}</span>
<img <img
src="../../assets/images/展开icon.png" src="../../assets/images/展开icon.png"
alt="collapse" alt="collapse"
class="collapse-icon" class="collapse-icon"
:class="{ 'is-collapsed': isCollapsed }"
/> />
</div> </div>
<div class="video-monitor-item__content"> <transition name="collapse">
<div v-show="!isCollapsed" class="video-monitor-item__content">
<div class="video-placeholder"> <div class="video-placeholder">
<!-- 视频播放器 --> <!-- 视频播放器 -->
<video <video
@ -77,6 +79,7 @@
</div> </div>
</div> </div>
</div> </div>
</transition>
</div> </div>
</template> </template>
@ -92,6 +95,16 @@ const props = defineProps({
defineEmits(['megaphone', 'audio', 'zoom']) defineEmits(['megaphone', 'audio', 'zoom'])
// /
const isCollapsed = ref(false)
/**
* 切换收起/展开状态
*/
const toggleCollapse = () => {
isCollapsed.value = !isCollapsed.value
}
const currentTime = ref('') const currentTime = ref('')
const updateTime = () => { const updateTime = () => {
@ -132,6 +145,13 @@ onUnmounted(() => {
padding: 2px; padding: 2px;
// border-radius: vw(8); // border-radius: vw(8);
overflow: hidden; overflow: hidden;
transition: all 0.3s ease;
//
&.is-collapsed {
background: none;
padding: 0;
}
&__header { &__header {
display: flex; display: flex;
@ -140,6 +160,7 @@ onUnmounted(() => {
padding: vh(10) vw(16); padding: vh(10) vw(16);
background: rgba(20, 53, 118, 0.5); background: rgba(20, 53, 118, 0.5);
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
transition: all 0.3s ease;
.video-title { .video-title {
color: var(--text-white); color: var(--text-white);
@ -152,11 +173,30 @@ onUnmounted(() => {
width: vw(26); width: vw(26);
height: vw(26); height: vw(26);
cursor: pointer; cursor: pointer;
transition: transform 0.3s; transition: transform 0.3s ease;
// &:hover { &.is-collapsed {
// transform: rotate(180deg); // transform: rotate(180deg);
// } }
&:hover {
opacity: 0.8;
}
}
}
// header
&.is-collapsed &__header {
border-bottom: none;
border-radius: vw(8);
}
&__header {
cursor: pointer;
user-select: none;
&:hover {
background: rgba(20, 53, 118, 0.7);
} }
} }
@ -266,4 +306,23 @@ onUnmounted(() => {
} }
} }
} }
//
.collapse-enter-active,
.collapse-leave-active {
transition: all 0.3s ease;
overflow: hidden;
}
.collapse-enter-from,
.collapse-leave-to {
max-height: 0;
opacity: 0;
}
.collapse-enter-to,
.collapse-leave-from {
max-height: 500px;
opacity: 1;
}
</style> </style>

View File

@ -36,10 +36,11 @@ import CollaborationInfo from './CollaborationInfo.vue'
.right-panel { .right-panel {
// width: vw(486); // width: vw(486);
height: 100%; height: 100%;
background: url('../../assets/images/SketchPngab2bc23b7e477ddbee76b880e28c1c97d6afb9261784dec29ed08c4e0a34d5b3.png') no-repeat; background: url('../../assets/images/框架右边.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
overflow-y: auto; overflow-y: auto;
overscroll-behavior: contain; overscroll-behavior: contain;
padding-right: vw(40); //
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: vw(6); width: vw(6);

View File

@ -6,6 +6,9 @@
@click="collapsible && toggle()" @click="collapsible && toggle()"
> >
<PanelHeader :title="title" :subtitle="subtitle"> <PanelHeader :title="title" :subtitle="subtitle">
<template #title-icon>
<slot name="title-icon" />
</template>
<template #extra> <template #extra>
<slot name="header-right" /> <slot name="header-right" />
<button <button
@ -16,20 +19,12 @@
:aria-label="isCollapsed ? '展开面板' : '收起面板'" :aria-label="isCollapsed ? '展开面板' : '收起面板'"
@click.stop="toggle" @click.stop="toggle"
> >
<svg <img
:src="toggleIcon"
alt=""
class="collapsible-panel__toggle-icon" class="collapsible-panel__toggle-icon"
:class="{ 'is-collapsed': isCollapsed }" :class="{ 'is-collapsed': isCollapsed }"
viewBox="0 0 12 12" />
fill="none"
>
<path
d="M3 5L6 8L9 5"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button> </button>
</template> </template>
</PanelHeader> </PanelHeader>
@ -56,6 +51,7 @@
<script setup> <script setup>
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import PanelHeader from './PanelHeader.vue' import PanelHeader from './PanelHeader.vue'
import toggleIcon from '../../assets/images/展开icon.png'
const props = defineProps({ const props = defineProps({
/** 面板标题 */ /** 面板标题 */
@ -196,13 +192,12 @@ function onAfterLeave(el) {
} }
&__toggle-icon { &__toggle-icon {
width: vw(12); width: vw(24);
height: vh(12); height: auto;
color: var(--primary-color);
transition: transform 0.3s ease; transition: transform 0.3s ease;
&.is-collapsed { &.is-collapsed {
transform: rotate(-90deg); // transform: rotate(-90deg);
} }
} }

View File

@ -3,6 +3,7 @@
<div class="panel-header__content"> <div class="panel-header__content">
<span class="panel-header__title">{{ title }}</span> <span class="panel-header__title">{{ title }}</span>
<span v-if="subtitle" class="panel-header__subtitle">{{ subtitle }}</span> <span v-if="subtitle" class="panel-header__subtitle">{{ subtitle }}</span>
<slot name="title-icon"></slot>
</div> </div>
<slot name="extra"></slot> <slot name="extra"></slot>
</div> </div>

View File

@ -69,7 +69,8 @@ export const BEFORE_IMAGERY_CONFIG = {
// 影像服务URL // 影像服务URL
// 格式支持标准瓦片服务的URL模板{z}/{x}/{y} 为瓦片坐标占位符 // 格式支持标准瓦片服务的URL模板{z}/{x}/{y} 为瓦片坐标占位符
// url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', // url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', // url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
url: 'http://222.212.85.86:9000/300bdf2b-a150-406e-be63-d28bd29b409f/model/ylzg/zxyj1119/terra_b3dms/tileset.json',
// 图层类型 // 图层类型
type: 'UrlTemplate', type: 'UrlTemplate',

View File

@ -26,8 +26,13 @@
<div class="situational-awareness__right-map"> <div class="situational-awareness__right-map">
<MapViewer @tool-change="handleMapToolChange" /> <MapViewer @tool-change="handleMapToolChange" />
</div> </div>
</div>
<!-- 场景标签层 --> <!-- 地图遮罩层 -->
<div class="situational-awareness__map-mask" aria-hidden="true"></div>
<!-- 场景标签层 - 独立层级显示在遮罩层之上 -->
<div class="situational-awareness__scene-labels-layer">
<!-- 灾前现场实景标签 - 在中间分割线左侧 --> <!-- 灾前现场实景标签 - 在中间分割线左侧 -->
<SceneLabel <SceneLabel
v-if="isCompareMode" v-if="isCompareMode"
@ -42,25 +47,56 @@
/> />
</div> </div>
<!-- 地图遮罩层 -->
<div class="situational-awareness__map-mask" aria-hidden="true"></div>
<!-- 浮动面板层 --> <!-- 浮动面板层 -->
<div class="situational-awareness__panels-layer"> <div class="situational-awareness__panels-layer">
<div <Transition name="panel-slide-left">
class="situational-awareness__panel-column situational-awareness__panel-column--left" <div
v-show="!isLeftPanelCollapsed"
class="situational-awareness__panel-column situational-awareness__panel-column--left"
>
<LeftPanel @start-dispatch="handleStartDispatch" />
</div>
</Transition>
<Transition name="panel-slide-right">
<div
v-show="!isRightPanelCollapsed"
class="situational-awareness__panel-column situational-awareness__panel-column--right"
>
<RightPanel />
</div>
</Transition>
</div>
<!-- 折叠按钮层 -->
<div class="situational-awareness__collapse-buttons-layer">
<!-- 左侧折叠按钮 -->
<button
class="situational-awareness__collapse-btn situational-awareness__collapse-btn--left"
:class="{ 'is-collapsed': isLeftPanelCollapsed }"
@click="toggleLeftPanel"
:aria-label="isLeftPanelCollapsed ? '展开左侧面板' : '收起左侧面板'"
> >
<LeftPanel @start-dispatch="handleStartDispatch" /> <img
</div> :src="isLeftPanelCollapsed ? collapseRightArrow : collapseLeftArrow"
<div alt=""
class="situational-awareness__center-spacer" class="collapse-arrow"
aria-hidden="true" />
></div> </button>
<div
class="situational-awareness__panel-column situational-awareness__panel-column--right" <!-- 右侧折叠按钮 -->
<button
class="situational-awareness__collapse-btn situational-awareness__collapse-btn--right"
:class="{ 'is-collapsed': isRightPanelCollapsed }"
@click="toggleRightPanel"
:aria-label="isRightPanelCollapsed ? '展开右侧面板' : '收起右侧面板'"
> >
<RightPanel /> <img
</div> :src="isRightPanelCollapsed ? collapseLeftArrow : collapseRightArrow"
alt=""
class="collapse-arrow"
/>
</button>
</div> </div>
<!-- 地图控件层 - 高于遮罩和面板 --> <!-- 地图控件层 - 高于遮罩和面板 -->
@ -146,6 +182,10 @@ import soldierIcon from "./assets/images/SketchPngfbec927027ff9e49207749ebaafd22
import deviceIcon from "./assets/images/SketchPng860d54f2a31f5f441fc6a88081224f1e98534bf6d5ca1246e420983bdf690380.png"; import deviceIcon from "./assets/images/SketchPng860d54f2a31f5f441fc6a88081224f1e98534bf6d5ca1246e420983bdf690380.png";
import emergencyBaseIcon from "./assets/images/应急基地.png"; import emergencyBaseIcon from "./assets/images/应急基地.png";
//
import collapseLeftArrow from "./assets/images/折叠面板左箭头.png";
import collapseRightArrow from "./assets/images/折叠面板右箭头.png";
// 使 // 使
const disasterData = useDisasterData(); const disasterData = useDisasterData();
@ -196,6 +236,20 @@ const showLoading = ref(false);
// //
const rangeCircleEntity = ref(null); const rangeCircleEntity = ref(null);
//
const isLeftPanelCollapsed = ref(false);
const isRightPanelCollapsed = ref(false);
//
const toggleLeftPanel = () => {
isLeftPanelCollapsed.value = !isLeftPanelCollapsed.value;
};
//
const toggleRightPanel = () => {
isRightPanelCollapsed.value = !isRightPanelCollapsed.value;
};
// 3D Tiles // 3D Tiles
const { load3DTileset, waitForTilesetReady } = use3DTiles(); const { load3DTileset, waitForTilesetReady } = use3DTiles();
@ -216,6 +270,48 @@ const setupMapClickHandler = (viewer) => {
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) { if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entity = pickedObject.id; const entity = pickedObject.id;
//
if (entity === rangeCircleEntity.value) {
console.log('[index.vue] 点击了范围圈,忽略');
// 穿
const drillPickedObjects = viewer.scene.drillPick(click.position);
let foundMarker = false;
for (const pickedObj of drillPickedObjects) {
if (Cesium.defined(pickedObj.id) && pickedObj.id !== rangeCircleEntity.value) {
const markerEntity = pickedObj.id;
if (markerEntity.properties) {
const type = markerEntity.properties.type?.getValue();
// Tooltip
if (type === 'soldier') {
showMarkerTooltip(viewer, markerEntity, click.position, soldierIcon);
foundMarker = true;
break;
} else if (type === 'device') {
showMarkerTooltip(viewer, markerEntity, click.position, deviceIcon);
foundMarker = true;
break;
} else if (type === 'emergencyBase' || type === 'station') {
const stationName = markerEntity.properties.name?.getValue() || '';
const icon = stationName === '忠县公路交通应急物资储备中心'
? emergencyCenterIcon
: emergencyBaseIcon;
showMarkerTooltip(viewer, markerEntity, click.position, icon);
foundMarker = true;
break;
}
}
}
}
if (!foundMarker) {
hideTooltip();
currentTooltipEntity.value = null;
}
return;
}
// properties // properties
if (entity.properties) { if (entity.properties) {
const type = entity.properties.type?.getValue(); const type = entity.properties.type?.getValue();
@ -769,6 +865,11 @@ const createOrUpdateRangeCircle = (viewer, radiusKm) => {
} }
}); });
// 穿
if (rangeCircleEntity.value) {
rangeCircleEntity.value.allowPicking = false;
}
console.log(`[index.vue] 已创建/更新范围圈: ${radiusKm}km`); console.log(`[index.vue] 已创建/更新范围圈: ${radiusKm}km`);
}; };
@ -950,7 +1051,9 @@ const showMapTooltip = ({ x, y, title = "", icon = "", data = null }) => {
.situational-awareness__left-map { .situational-awareness__left-map {
width: 0; width: 0;
visibility: hidden; opacity: 0;
pointer-events: none;
overflow: hidden;
} }
} }
@ -958,8 +1061,9 @@ const showMapTooltip = ({ x, y, title = "", icon = "", data = null }) => {
&.is-compare-mode { &.is-compare-mode {
.situational-awareness__left-map { .situational-awareness__left-map {
width: 50%; width: 50%;
visibility: visible; opacity: 1;
transition: width 0.3s ease; pointer-events: auto;
transition: width 0.3s ease, opacity 0.3s ease;
} }
.situational-awareness__right-map { .situational-awareness__right-map {
@ -1010,28 +1114,46 @@ const showMapTooltip = ({ x, y, title = "", icon = "", data = null }) => {
no-repeat; no-repeat;
} }
// - grid pointer-events // -
&__scene-labels-layer {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none; //
}
// - 使
&__panels-layer { &__panels-layer {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 2; z-index: 2;
display: grid;
grid-template-columns: var(--sa-left-width) 1fr var(--sa-right-width);
grid-auto-rows: 1fr;
gap: var(--sa-gap); //
height: 100%; height: 100%;
padding-top: var(--sa-header-height); // Header
pointer-events: none; // pointer-events: none; //
} }
// - // -
&__panel-column { &__panel-column {
position: absolute;
top: var(--sa-header-height); // header
bottom: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--sa-gap); // gap: var(--sa-gap); //
min-width: 0; // min-width: 0; //
min-height: 0; // flex min-height: 0; // flex
pointer-events: auto; // pointer-events: auto; //
//
&--left {
left: 0;
width: var(--sa-left-width);
}
//
&--right {
right: 0;
width: var(--sa-right-width);
}
} }
// - 穿 // - 穿
@ -1039,6 +1161,88 @@ const showMapTooltip = ({ x, y, title = "", icon = "", data = null }) => {
pointer-events: none; pointer-events: none;
} }
// -
&__collapse-buttons-layer {
position: absolute;
inset: 0;
z-index: 11; //
pointer-events: none; //
padding-top: var(--sa-header-height); // Header
}
//
&__collapse-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: transparent;
border: none;
cursor: pointer;
pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
z-index: 11;
padding: 0;
&:hover {
opacity: 0.8;
}
&:active {
transform: translateY(-50%) scale(0.95);
}
.collapse-arrow {
width: vw(30);
height: auto;
transition: all 0.3s ease;
}
// -
&--left {
left: 0;
}
// -
&--right {
right: 0;
}
}
// -
.panel-slide-left-enter-active,
.panel-slide-left-leave-active {
transition: all 0.3s ease;
}
.panel-slide-left-enter-from {
transform: translateX(-100%);
opacity: 0;
}
.panel-slide-left-leave-to {
transform: translateX(-100%);
opacity: 0;
}
// -
.panel-slide-right-enter-active,
.panel-slide-right-leave-active {
transition: all 0.3s ease;
}
.panel-slide-right-enter-from {
transform: translateX(100%);
opacity: 0;
}
.panel-slide-right-leave-to {
transform: translateX(100%);
opacity: 0;
}
// - // -
&__controls-layer { &__controls-layer {
position: absolute; position: absolute;