Compare commits
3 Commits
main
..
bf0e8f2e26
| Author | SHA1 | Date | |
|---|---|---|---|
| bf0e8f2e26 | |||
| 73c2fc2ffa | |||
| 32d95f323f |
+1
-1
@@ -5,7 +5,7 @@ VITE_BACKEND_BASE_URL=http://localhost:8081
|
||||
START_PORT=81
|
||||
|
||||
# geoserver地址
|
||||
VITE_GEOSERVER_BASE_URL=http://47.92.216.173:4020/geoserver/xian
|
||||
VITE_GEOSERVER_BASE_URL=http://47.92.216.173:4019/geoserver/xian
|
||||
|
||||
# WebSocket地址
|
||||
VITE_WEBSOCKET_URL=http://localhost:8081/websocket
|
||||
|
||||
+5
-2
@@ -17,18 +17,21 @@
|
||||
"@types/spark-md5": "^3.0.5",
|
||||
"axios": "^1.12.2",
|
||||
"cesium": "1.101.0",
|
||||
"docx": "^9.7.1",
|
||||
"element-plus": "^2.13.6",
|
||||
"file-saver": "^2.0.5",
|
||||
"gm-crypto": "^0.1.12",
|
||||
"pinia": "^3.0.3",
|
||||
"proj4": "^2.20.8",
|
||||
"sockjs-client": "^1.6.1",
|
||||
"spark-md5": "^3.0.2",
|
||||
"vite-plugin-cesium": "^1.2.22",
|
||||
"vue": "^3.5.32",
|
||||
"vue": "^3.5.35",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/proj4": "^2.19.0",
|
||||
"@types/sockjs-client": "^1.5.4",
|
||||
@@ -49,4 +52,4 @@
|
||||
"vite-plugin-vue-devtools": "^8.0.3",
|
||||
"vue-tsc": "^3.2.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-25
@@ -55,9 +55,6 @@ import type { XianSchool } from '@/types/base/XianSchool';
|
||||
import type { XianBridge } from '@/types/base/XianBridge.ts';
|
||||
import type { XianReservoirList } from '@/types/base/XianReservoirList';
|
||||
import type { XianSubwayStations } from '@/types/base/XianSubwayStations';
|
||||
import { modelDeduction as rainfallModelDeduction } from './rainfall';
|
||||
import type { RainPredictResponse } from '@/types/rainstorm/RainPredictResponse';
|
||||
import type { RainPredictRequest } from '@/types/rainstorm/RainPredictRequest';
|
||||
|
||||
/**
|
||||
* API接口统一导出对象
|
||||
@@ -87,14 +84,12 @@ export const $api = {
|
||||
/**
|
||||
* 根据id获取隐患点详情
|
||||
* @param id - 隐患点id
|
||||
* @param simulationId - 模拟id
|
||||
* @returns 隐患点详情
|
||||
*/
|
||||
getPointDetailById: (
|
||||
id: number,
|
||||
simulationId: number
|
||||
id: number
|
||||
): Promise<ApiResponse<XianHiddenDangerSpots>> =>
|
||||
getHiddenDangerPointDetailById(id, simulationId),
|
||||
getHiddenDangerPointDetailById(id),
|
||||
},
|
||||
|
||||
// 风险点信息
|
||||
@@ -109,14 +104,10 @@ export const $api = {
|
||||
/**
|
||||
* 根据id获取风险点详情
|
||||
* @param id - 风险点id
|
||||
* @param simulationId - 模拟id
|
||||
* @returns 风险点详情
|
||||
*/
|
||||
getPointDetailById: (
|
||||
id: number,
|
||||
simulationId: number
|
||||
): Promise<ApiResponse<XianRiskSpots>> =>
|
||||
getRiskPointDetailById(id, simulationId),
|
||||
getPointDetailById: (id: number): Promise<ApiResponse<XianRiskSpots>> =>
|
||||
getRiskPointDetailById(id),
|
||||
},
|
||||
|
||||
// 医院信息
|
||||
@@ -286,16 +277,4 @@ export const $api = {
|
||||
): Promise<ApiResponse<XianSubwayStations>> =>
|
||||
getSubwayStationsPointDetailById(id),
|
||||
},
|
||||
|
||||
// 暴雨推演
|
||||
rainfall: {
|
||||
/**
|
||||
* 进行模型推演
|
||||
* @param req 请求体
|
||||
* @returns 推演点的概率
|
||||
*/
|
||||
modelDeduction: (
|
||||
req: RainPredictRequest
|
||||
): Promise<ApiResponse<RainPredictResponse>> => rainfallModelDeduction(req),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,33 +1,25 @@
|
||||
import type { ApiResponse } from '@/types/ApiResponse';
|
||||
import type { XianHiddenDangerSpots } from '@/types/base/XianHiddenDangerSpots';
|
||||
import httpInstance from '@/utils/request/http';
|
||||
import type { ApiResponse } from "@/types/ApiResponse"
|
||||
import type { XianHiddenDangerSpots } from "@/types/base/XianHiddenDangerSpots"
|
||||
import httpInstance from "@/utils/request/http"
|
||||
|
||||
/**
|
||||
* 获取隐患点基础数据
|
||||
* @param disasterType - 灾害类型(landslide, debris_flow, water_logging, flash_flood)
|
||||
* @returns 隐患点数据数组
|
||||
*/
|
||||
export const getBasePoints = (
|
||||
disasterType: string
|
||||
): Promise<ApiResponse<XianHiddenDangerSpots[]>> => {
|
||||
return httpInstance.get('/hidden-danger-spots/base-points', {
|
||||
params: {
|
||||
disasterType,
|
||||
},
|
||||
});
|
||||
};
|
||||
export const getBasePoints = (disasterType: string): Promise<ApiResponse<XianHiddenDangerSpots[]>> => {
|
||||
return httpInstance.get('/hidden-danger-spots/base-points', {
|
||||
params: {
|
||||
disasterType
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id获取隐患点详情
|
||||
* @param id - 隐患点id
|
||||
* @param simulationId - 模拟id
|
||||
* @returns 隐患点详情
|
||||
*/
|
||||
export const getPointDetailById = (
|
||||
id: number,
|
||||
simulationId: number
|
||||
): Promise<ApiResponse<XianHiddenDangerSpots>> => {
|
||||
return httpInstance.get(
|
||||
`/hidden-danger-spots/point-detail/${id}/${simulationId}`
|
||||
);
|
||||
};
|
||||
export const getPointDetailById = (id: number): Promise<ApiResponse<XianHiddenDangerSpots>> => {
|
||||
return httpInstance.get(`/hidden-danger-spots/point-detail/${id}`)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { ApiResponse } from '@/types/ApiResponse';
|
||||
import type { RainPredictRequest } from '@/types/rainstorm/RainPredictRequest';
|
||||
import type { RainPredictResponse } from '@/types/rainstorm/RainPredictResponse';
|
||||
import httpInstance from '@/utils/request/http';
|
||||
|
||||
/**
|
||||
* 进行模型推演
|
||||
* @param req 请求体
|
||||
* @returns 推演点的概率
|
||||
*/
|
||||
export const modelDeduction = (
|
||||
req: RainPredictRequest
|
||||
): Promise<ApiResponse<RainPredictResponse>> => {
|
||||
return httpInstance.post('/algorithm-api/rainfall/predict', req);
|
||||
};
|
||||
+8
-12
@@ -1,24 +1,20 @@
|
||||
import type { ApiResponse } from '@/types/ApiResponse';
|
||||
import type { XianRiskSpots } from '@/types/base/XianRiskSpots';
|
||||
import httpInstance from '@/utils/request/http';
|
||||
import type { ApiResponse } from "@/types/ApiResponse"
|
||||
import type { XianRiskSpots } from "@/types/base/XianRiskSpots"
|
||||
import httpInstance from "@/utils/request/http"
|
||||
|
||||
/**
|
||||
* 获取风险点基础数据
|
||||
* @returns 风险点数据数组
|
||||
*/
|
||||
export const getBasePoints = (): Promise<ApiResponse<XianRiskSpots[]>> => {
|
||||
return httpInstance.get('/risk-spots/base-points');
|
||||
};
|
||||
return httpInstance.get('/risk-spots/base-points')
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id获取风险点详情
|
||||
* @param id - 风险点id
|
||||
* @param simulationId - 模拟id
|
||||
* @returns 风险点详情
|
||||
*/
|
||||
export const getPointDetailById = (
|
||||
id: number,
|
||||
simulationId: number
|
||||
): Promise<ApiResponse<XianRiskSpots>> => {
|
||||
return httpInstance.get(`/risk-spots/point-detail/${id}/${simulationId}`);
|
||||
};
|
||||
export const getPointDetailById = (id: number): Promise<ApiResponse<XianRiskSpots>> => {
|
||||
return httpInstance.get(`/risk-spots/point-detail/${id}`)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -2,7 +2,6 @@
|
||||
|
||||
// 图标
|
||||
export { default as landslideIcon } from '@/assets/images/icon/landslide.png';
|
||||
export { default as collapseIcon } from '@/assets/images/icon/collapse.png';
|
||||
export { default as waterLoggingIcon } from '@/assets/images/icon/waterlogging.png';
|
||||
export { default as debrisFlowIcon } from '@/assets/images/icon/debris-flow.png';
|
||||
export { default as flashFloodIcon } from '@/assets/images/icon/flash-flood.png';
|
||||
|
||||
@@ -16,11 +16,7 @@
|
||||
</header>
|
||||
<div class="content">
|
||||
<table class="table">
|
||||
<tr
|
||||
v-for="(tableData, index) in tableDatas"
|
||||
:key="index"
|
||||
:style="tableData.styles || {}"
|
||||
>
|
||||
<tr v-for="(tableData, index) in tableDatas" :key="index">
|
||||
<td class="label">{{ tableData.title }}</td>
|
||||
<td>{{ tableData.content }}</td>
|
||||
</tr>
|
||||
@@ -37,7 +33,6 @@
|
||||
title: string;
|
||||
data: Record<string, string>;
|
||||
field: Record<string, string>;
|
||||
style?: Record<string, Record<string, string>>;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
}>();
|
||||
@@ -48,8 +43,7 @@
|
||||
const newOffsetX = ref(props.offsetX);
|
||||
const newOffsetY = ref(props.offsetY);
|
||||
|
||||
const tableDatas: Ref<{ title: string; content: string; styles?: Record<string, string> }[]> =
|
||||
ref([]);
|
||||
const tableDatas: Ref<{ title: string; content: string }[]> = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
// 判断是否超出屏幕,超出就重新定位
|
||||
@@ -64,14 +58,10 @@
|
||||
Object.entries(props.data).forEach(([key, value]) => {
|
||||
// 判断key是不是存在field中,存在就添加到表格数据,不存在则不添加
|
||||
if (Object.hasOwn(props.field, key) && value) {
|
||||
const rowData: { title: string; content: string; styles?: Record<string, string> } = {
|
||||
tableDatas.value.push({
|
||||
title: props.field[key],
|
||||
content: value,
|
||||
};
|
||||
if (props.style && Object.hasOwn(props.style, key)) {
|
||||
rowData.styles = props.style[key];
|
||||
}
|
||||
tableDatas.value.push(rowData);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -40,9 +40,7 @@
|
||||
polygonStyle: {
|
||||
fill: true,
|
||||
fillColor: areasColor[index].withAlpha(areaTransparency),
|
||||
outline: true,
|
||||
outlineColor: Color.WHITE,
|
||||
outlineWidth: 3,
|
||||
outline: false,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -14,14 +14,6 @@
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- 崩塌隐患点 -->
|
||||
<CollapseComponent
|
||||
v-if="
|
||||
statusStore.appLoadingCompleted &&
|
||||
statusStore.poiLayers.showCollapseHiddenPoint.loading
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- 泥石流隐患点 -->
|
||||
<DebrisFlowComponent
|
||||
v-if="
|
||||
@@ -65,14 +57,6 @@
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- 崩塌隐患点 -->
|
||||
<CollapseComponent
|
||||
v-if="
|
||||
statusStore.appLoadingCompleted &&
|
||||
statusStore.poiLayers.showCollapseHiddenPoint.loading
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- 泥石流隐患点 -->
|
||||
<DebrisFlowComponent
|
||||
v-if="
|
||||
@@ -97,11 +81,10 @@
|
||||
import { DisasterType } from '@/types/common/DisasterType.ts';
|
||||
import RiskPointComponent from '@/component/rain-earthquake/basic/RiskPointComponent.vue';
|
||||
import LandslideComponent from '@/component/rain-earthquake/basic/LandslideComponent.vue';
|
||||
import CollapseComponent from '@/component/rain-earthquake/basic/CollapseComponent.vue';
|
||||
import DebrisFlowComponent from '@/component/rain-earthquake/basic/DebrisFlowComponent.vue';
|
||||
import WaterLoggingComponent from '@/component/rain-earthquake/basic/WaterLoggingComponent.vue';
|
||||
import FlashFloodComponent from '@/component/rain-earthquake/basic/FlashFloodComponent.vue';
|
||||
import RainfallGridComponent from '@/component/rain/RainfallAutomaticSimulationComponent.vue';
|
||||
import RainfallGridComponent from '@/component/rain-earthquake/detail-panels/RainfallGridComponent.vue';
|
||||
import { useStatusStore } from '@/stores/useStatusStore';
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
<!-- 崩塌隐患点组件 -->
|
||||
<template>
|
||||
<div>
|
||||
<!-- 加载崩塌隐患点 -->
|
||||
<LoadingPoints
|
||||
v-if="statusStore.appLoadingCompleted && collapsePoints.length > 0"
|
||||
:base-points="collapsePoints"
|
||||
:get-disaster-icon="getDisasterIcon"
|
||||
:prefix="config.prefix.collapseHiddenPointId"
|
||||
:is-default="true"
|
||||
:loading-resource-field="LoadingResource.COLLAPSE_HIDDEN_POINT"
|
||||
/>
|
||||
|
||||
<!-- 显示信息框 -->
|
||||
<InformationBox
|
||||
:data="collapsePointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="style"
|
||||
v-if="loadingInformationStore.collapseHiddenPoint.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
:offset-y="offsetY"
|
||||
:key="loadingInformationStore.collapseHiddenPoint.id"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { $api } from '@/api/api.ts';
|
||||
import type { Point } from '@/types/base/Point.ts';
|
||||
import LoadingPoints from '@/component/common/LoadingPoints.vue';
|
||||
import config from '@/config/config.json';
|
||||
import InformationBox from '@/component/common/InformationBox.vue';
|
||||
import { useStatusStore } from '@/stores/useStatusStore.ts';
|
||||
import { useLoadingInformationStore } from '@/stores/useLoadingInformation.ts';
|
||||
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils.ts';
|
||||
import { LoadingResource } from '@/types/common/LoadingResourceType.ts';
|
||||
import { useHiddenPoint } from '@/hooks/rain-earthquake/useHiddenPoint.ts';
|
||||
import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore.ts';
|
||||
import {
|
||||
PointType,
|
||||
HiddenDangerPointTypeMap,
|
||||
} from '@/types/common/DisasterType.ts';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore';
|
||||
|
||||
const collapsePoints = ref<Point[]>([]);
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
const loadingInformationStore = useLoadingInformationStore();
|
||||
const loadingResourceStore = useLoadingResourceStore();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
const { field, style, getDisasterIcon } = useHiddenPoint();
|
||||
|
||||
// 信息框相关配置
|
||||
const offsetX = ref(0);
|
||||
const offsetY = ref(0);
|
||||
const collapsePointDetail = ref<Point>();
|
||||
const informationBoxTitle = ref('');
|
||||
|
||||
// 加载崩塌隐患点数据
|
||||
$api.hiddenDangerSpots
|
||||
.getBasePoints(HiddenDangerPointTypeMap[PointType.COLLAPSE])
|
||||
.then((res) => {
|
||||
collapsePoints.value = res.data;
|
||||
});
|
||||
|
||||
// 监听id变化
|
||||
watch(
|
||||
() => loadingInformationStore.collapseHiddenPoint.id,
|
||||
async (newId: number) => {
|
||||
if (newId === -1) {
|
||||
return;
|
||||
}
|
||||
// 获取崩塌隐患点数据
|
||||
const clickObject = loadingInformationStore.clickObject;
|
||||
|
||||
if (!clickObject || !clickObject.primitive) {
|
||||
console.warn('点击对象或图元不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await $api.hiddenDangerSpots.getPointDetailById(
|
||||
loadingInformationStore.collapseHiddenPoint.id,
|
||||
simulationIdStore.status ? simulationIdStore.id : -1
|
||||
);
|
||||
|
||||
// 更新数据
|
||||
collapsePointDetail.value = res.data;
|
||||
informationBoxTitle.value = res.data.disasterName || '崩塌隐患点信息';
|
||||
|
||||
try {
|
||||
// 将坐标转换为偏移量
|
||||
const screenPos = CesiumUtilsSingleton.convertScreenPosition(
|
||||
clickObject.primitive.position
|
||||
);
|
||||
offsetX.value = screenPos.x;
|
||||
offsetY.value = screenPos.y;
|
||||
|
||||
// 显示新的信息框
|
||||
loadingInformationStore.collapseHiddenPoint.loading = true;
|
||||
} catch (error) {
|
||||
throw new Error(`坐标转换失败:${error}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听显示隐藏
|
||||
watch(
|
||||
() => statusStore.poiLayers.showCollapseHiddenPoint.show,
|
||||
(newValue: boolean) => {
|
||||
if (newValue) {
|
||||
// 显示崩塌隐患点
|
||||
CesiumUtilsSingleton.batchShowPrimitives(
|
||||
loadingResourceStore.getLoadingResource(
|
||||
LoadingResource.COLLAPSE_HIDDEN_POINT
|
||||
).ids
|
||||
);
|
||||
} else {
|
||||
// 隐藏崩塌隐患点
|
||||
CesiumUtilsSingleton.batchHidePrimitives(
|
||||
loadingResourceStore.getLoadingResource(
|
||||
LoadingResource.COLLAPSE_HIDDEN_POINT
|
||||
).ids
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="debrisFlowPointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="style"
|
||||
v-if="loadingInformationStore.debrisFlowHiddenPoint.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
@@ -42,16 +41,14 @@
|
||||
PointType,
|
||||
HiddenDangerPointTypeMap,
|
||||
} from '@/types/common/DisasterType.ts';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore';
|
||||
|
||||
const debrisFlowPoints = ref<Point[]>([]);
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
const loadingInformationStore = useLoadingInformationStore();
|
||||
const loadingResourceStore = useLoadingResourceStore();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
const { field, style, getDisasterIcon } = useHiddenPoint();
|
||||
const { field, getDisasterIcon } = useHiddenPoint();
|
||||
|
||||
// 信息框相关配置
|
||||
const offsetX = ref(0);
|
||||
@@ -82,8 +79,7 @@
|
||||
}
|
||||
|
||||
const res = await $api.hiddenDangerSpots.getPointDetailById(
|
||||
loadingInformationStore.debrisFlowHiddenPoint.id,
|
||||
simulationIdStore.status ? simulationIdStore.id : -1
|
||||
loadingInformationStore.debrisFlowHiddenPoint.id
|
||||
);
|
||||
|
||||
// 更新数据
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="flashFloodPointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="style"
|
||||
v-if="loadingInformationStore.flashFloodHiddenPoint.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
@@ -42,16 +41,14 @@
|
||||
PointType,
|
||||
HiddenDangerPointTypeMap,
|
||||
} from '@/types/common/DisasterType.ts';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore';
|
||||
|
||||
const flashFloodPoints = ref<Point[]>([]);
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
const loadingInformationStore = useLoadingInformationStore();
|
||||
const loadingResourceStore = useLoadingResourceStore();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
const { field, style, getDisasterIcon } = useHiddenPoint();
|
||||
const { field, getDisasterIcon } = useHiddenPoint();
|
||||
|
||||
// 信息框相关配置
|
||||
const offsetX = ref(0);
|
||||
@@ -82,8 +79,7 @@
|
||||
}
|
||||
|
||||
const res = await $api.hiddenDangerSpots.getPointDetailById(
|
||||
loadingInformationStore.flashFloodHiddenPoint.id,
|
||||
simulationIdStore.status ? simulationIdStore.id : -1
|
||||
loadingInformationStore.flashFloodHiddenPoint.id
|
||||
);
|
||||
|
||||
// 更新数据
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="landslidePointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="style"
|
||||
v-if="loadingInformationStore.landslideHiddenPoint.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
@@ -42,16 +41,14 @@
|
||||
PointType,
|
||||
HiddenDangerPointTypeMap,
|
||||
} from '@/types/common/DisasterType.ts';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore';
|
||||
|
||||
const landslidePoints = ref<Point[]>([]);
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
const loadingInformationStore = useLoadingInformationStore();
|
||||
const loadingResourceStore = useLoadingResourceStore();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
const { field, style, getDisasterIcon } = useHiddenPoint();
|
||||
const { field, getDisasterIcon } = useHiddenPoint();
|
||||
|
||||
// 信息框相关配置
|
||||
const offsetX = ref(0);
|
||||
@@ -82,8 +79,7 @@
|
||||
}
|
||||
|
||||
const res = await $api.hiddenDangerSpots.getPointDetailById(
|
||||
loadingInformationStore.landslideHiddenPoint.id,
|
||||
simulationIdStore.status ? simulationIdStore.id : -1
|
||||
loadingInformationStore.landslideHiddenPoint.id
|
||||
);
|
||||
|
||||
// 更新数据
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="riskPointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="style"
|
||||
v-if="loadingInformationStore.riskPoint.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
@@ -38,14 +37,12 @@
|
||||
import { useRiskPoint } from '@/hooks/rain-earthquake/useRiskPoint.ts';
|
||||
import { LoadingResource } from '@/types/common/LoadingResourceType.ts';
|
||||
import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore.ts';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore';
|
||||
|
||||
const riskPoints = ref<Point[]>([]);
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
const loadingInformationStore = useLoadingInformationStore();
|
||||
const loadingResourceStore = useLoadingResourceStore();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
// 信息框相关配置
|
||||
const offsetX = ref(0);
|
||||
@@ -53,7 +50,7 @@
|
||||
const riskPointDetail = ref<Point>();
|
||||
|
||||
// 获取钩子函数
|
||||
const { informationBoxTitle, field, style, getDisasterIcon } = useRiskPoint();
|
||||
const { informationBoxTitle, field, getDisasterIcon } = useRiskPoint();
|
||||
|
||||
$api.riskSpots.getBasePoints().then((res) => {
|
||||
riskPoints.value = res.data;
|
||||
@@ -75,8 +72,7 @@
|
||||
}
|
||||
|
||||
const res = await $api.riskSpots.getPointDetailById(
|
||||
loadingInformationStore.riskPoint.id,
|
||||
simulationIdStore.status ? simulationIdStore.id : -1
|
||||
loadingInformationStore.riskPoint.id
|
||||
);
|
||||
|
||||
// 更新数据
|
||||
@@ -104,13 +100,15 @@
|
||||
(newValue: boolean) => {
|
||||
if (newValue) {
|
||||
CesiumUtilsSingleton.batchShowPrimitives(
|
||||
loadingResourceStore.getLoadingResource(LoadingResource.RISK_POINT)
|
||||
.ids
|
||||
loadingResourceStore.getLoadingResource(
|
||||
LoadingResource.RISK_POINT
|
||||
).ids
|
||||
);
|
||||
} else {
|
||||
CesiumUtilsSingleton.batchHidePrimitives(
|
||||
loadingResourceStore.getLoadingResource(LoadingResource.RISK_POINT)
|
||||
.ids
|
||||
loadingResourceStore.getLoadingResource(
|
||||
LoadingResource.RISK_POINT
|
||||
).ids
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<div>
|
||||
<!-- 加载内涝隐患点 -->
|
||||
<LoadingPoints
|
||||
v-if="statusStore.appLoadingCompleted && waterLoggingPoints.length > 0"
|
||||
v-if="
|
||||
statusStore.appLoadingCompleted && waterLoggingPoints.length > 0
|
||||
"
|
||||
:base-points="waterLoggingPoints"
|
||||
:get-disaster-icon="getDisasterIcon"
|
||||
:prefix="config.prefix.waterLoggingHiddenPointId"
|
||||
@@ -15,7 +17,6 @@
|
||||
<InformationBox
|
||||
:data="waterLoggingPointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="style"
|
||||
v-if="loadingInformationStore.waterLoggingHiddenPoint.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
@@ -42,16 +43,14 @@
|
||||
PointType,
|
||||
HiddenDangerPointTypeMap,
|
||||
} from '@/types/common/DisasterType.ts';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore';
|
||||
|
||||
const waterLoggingPoints = ref<Point[]>([]);
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
const loadingInformationStore = useLoadingInformationStore();
|
||||
const loadingResourceStore = useLoadingResourceStore();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
const { field, style, getDisasterIcon } = useHiddenPoint();
|
||||
const { field, getDisasterIcon } = useHiddenPoint();
|
||||
|
||||
// 信息框相关配置
|
||||
const offsetX = ref(0);
|
||||
@@ -82,8 +81,7 @@
|
||||
}
|
||||
|
||||
const res = await $api.hiddenDangerSpots.getPointDetailById(
|
||||
loadingInformationStore.waterLoggingHiddenPoint.id,
|
||||
simulationIdStore.status ? simulationIdStore.id : -1
|
||||
loadingInformationStore.waterLoggingHiddenPoint.id
|
||||
);
|
||||
|
||||
// 更新数据
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="storePointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.bridge.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="dangerousSourcePointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.dangerousSource.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="emergencyShelterPointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.emergencyShelter.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="fireStationPointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.fireStation.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="hospitalPointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.hospital.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
+6
-33
@@ -4,23 +4,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { $api } from '@/api/api.ts';
|
||||
import { useRainstormDeduction } from '@/hooks/rainstorm/useRainstormDeduction.ts';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore.ts';
|
||||
import { useStatusStore } from '@/stores/useStatusStore.ts';
|
||||
import { useStepStore } from '@/stores/useStepStore.ts';
|
||||
import type { ApiResponse } from '@/types/ApiResponse.ts';
|
||||
import type { RainfallGridResponse } from '@/types/rainstorm/RainfallGridResponse.ts';
|
||||
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils.ts';
|
||||
import { WebSocketService } from '@/utils/request/websocket.ts';
|
||||
import { Utils } from '@/utils/utils.ts';
|
||||
import { useRainstormDeduction } from '@/hooks/rainstorm/useRainstormDeduction';
|
||||
import { useStatusStore } from '@/stores/useStatusStore';
|
||||
import { useStepStore } from '@/stores/useStepStore';
|
||||
import type { ApiResponse } from '@/types/ApiResponse';
|
||||
import type { RainfallGridResponse } from '@/types/rainstorm/RainfallGridResponse';
|
||||
import { WebSocketService } from '@/utils/request/websocket';
|
||||
import { onMounted, onUnmounted, watch } from 'vue';
|
||||
|
||||
let rainfallWsService: WebSocketService | null = null;
|
||||
const { triggerLayerShowStatus, addGridLayer } = useRainstormDeduction();
|
||||
const statusStore = useStatusStore();
|
||||
const stepStore = useStepStore();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
// 请求降雨栅格数据
|
||||
const requestRainfallData = () => {
|
||||
@@ -43,33 +38,11 @@
|
||||
'/topic/rainfall/grid/messages',
|
||||
(response) => {
|
||||
if (response.code === 200 && response.data) {
|
||||
// 设置步骤为第一步
|
||||
stepStore.currentStep = 0;
|
||||
|
||||
// 显示图层
|
||||
addGridLayer(response.data);
|
||||
|
||||
// 推进到下一步
|
||||
stepStore.nextStep();
|
||||
|
||||
// 进行模型计算
|
||||
$api.rainfall
|
||||
.modelDeduction({
|
||||
disaster_name: `${Utils.formatDate('YYYYMMDDHHmmss')}暴雨自动推演`,
|
||||
operation_type: '暴雨灾害链自动推演',
|
||||
})
|
||||
.then((res) => {
|
||||
// 进行预警
|
||||
CesiumUtilsSingleton.addPulseEffect(res.data.list);
|
||||
// 设置事件id
|
||||
simulationIdStore.setId(res.data.record_id);
|
||||
|
||||
// 推进到下一步
|
||||
stepStore.nextStep();
|
||||
|
||||
// 产出报告
|
||||
console.log(res);
|
||||
});
|
||||
} else {
|
||||
console.warn('响应错误:', response.message);
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="reservoirDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.reservoir.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="schoolDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.school.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="storePointDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.storePoints.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<InformationBox
|
||||
:data="subwayStationDetail as Record<string, any>"
|
||||
:field="field"
|
||||
:style="{}"
|
||||
v-if="loadingInformationStore.subwayStation.loading"
|
||||
:title="informationBoxTitle"
|
||||
:offset-x="offsetX"
|
||||
|
||||
@@ -11,16 +11,29 @@
|
||||
|
||||
<!-- 具体功能组件 -->
|
||||
<AroundAnalysisDetailComponent />
|
||||
|
||||
<!-- 脉冲点列表组件 -->
|
||||
<PulsePointListComponent />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useStatusStore } from '@/stores/useStatusStore';
|
||||
import AroundAnalysisDetailComponent from './around-analysis/AroundAnalysisDetailComponent.vue';
|
||||
import ButtonComponent from './around-analysis/ButtonComponent.vue';
|
||||
import SearchComponent from './around-analysis/SearchComponent.vue';
|
||||
import { provide } from 'vue';//从Vue导入provide函数,用于依赖注入
|
||||
import { useStatusStore } from '@/stores/useStatusStore';
|
||||
import { useAnalysisButton } from '@/hooks/rain-earthquake/useAnalysisButton';
|
||||
import AroundAnalysisDetailComponent from './around-analysis/AroundAnalysisDetailComponent.vue';
|
||||
import ButtonComponent from './around-analysis/ButtonComponent.vue';
|
||||
import SearchComponent from './around-analysis/SearchComponent.vue';
|
||||
import PulsePointListComponent from './around-analysis/PulsePointListComponent.vue';
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
|
||||
const statusStore = useStatusStore();
|
||||
|
||||
// 在父组件中创建唯一的 Hook 实例,包含所有周边分析相关的状态和方法
|
||||
const analysisButtonState = useAnalysisButton();
|
||||
|
||||
// 通过 provide 共享给所有子组件(让 TypeScript 自动推断类型)
|
||||
provide('analysisButtonState', analysisButtonState);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
+138
-3
@@ -1,7 +1,142 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<div v-if="showAreaDialog" class="area-dialog" :style="{ left: dialogPosition.x + 'px', top: dialogPosition.y + 'px' }">
|
||||
<div class="dialog-header">选择区域</div>
|
||||
<div class="dialog-content">
|
||||
<div class="radius-input-group">
|
||||
<span class="label">半径:</span>
|
||||
<input
|
||||
v-model.number="radius"
|
||||
type="number"
|
||||
class="radius-input"
|
||||
min="1"
|
||||
max="100"
|
||||
/>
|
||||
<span class="unit">公里</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button class="confirm-btn" @click="handleConfirm">确认添加</button>
|
||||
<button class="cancel-btn" @click="handleCancel">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import { inject } from 'vue';//从Vue导入inject函数,用于依赖注入
|
||||
import type { AnalysisButtonState } from '@/types/common/useAroundAnalysisType';
|
||||
|
||||
<style scoped></style>
|
||||
// 从父组件注入共享状态,明确指定类型
|
||||
const analysisButtonState = inject<AnalysisButtonState>('analysisButtonState');
|
||||
|
||||
const {
|
||||
showAreaDialog,
|
||||
radius,
|
||||
dialogPosition,
|
||||
handleConfirm,
|
||||
handleCancel
|
||||
} = analysisButtonState!;//使用非空断言操作符,告诉TypeScript该值一定存在
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 对话框容器样式 */
|
||||
.area-dialog {
|
||||
position: absolute;
|
||||
z-index: 100001;
|
||||
min-width: 200px;
|
||||
padding: 0;
|
||||
background: linear-gradient(180deg, rgba(0, 60, 120, 0.95), rgba(0, 40, 80, 0.95));
|
||||
border: 2px solid #00b4ff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 180, 255, 0.3);
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 对话框标题栏样式 */
|
||||
.dialog-header {
|
||||
padding: 8px 12px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: white;
|
||||
background: linear-gradient(90deg, #00b4ff, #0080cc);
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 10px 6px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.radius-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
/* 标签和单位文字的样式 */
|
||||
.radius-input-group .label,
|
||||
.radius-input-group .unit {
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.radius-input {
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
padding: 2px 5px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
background: rgba(0, 100, 180, 0.6);
|
||||
border: 1px solid #00b4ff;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
}
|
||||
/* 隐藏数字输入框的上下调节按钮(针对WebKit浏览器) */
|
||||
.radius-input::-webkit-inner-spin-button,
|
||||
.radius-input::-webkit-outer-spin-button {
|
||||
opacity: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px 10px 8px;
|
||||
}
|
||||
|
||||
.confirm-btn,
|
||||
.cancel-btn {
|
||||
padding: 6px 15px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: linear-gradient(180deg, #2d8a4e, #1e6b3a);
|
||||
border: 1px solid #3da862;
|
||||
}
|
||||
|
||||
.confirm-btn:hover {
|
||||
background: linear-gradient(180deg, #3da862, #2d8a4e);
|
||||
box-shadow: 0 2px 8px rgba(45, 138, 78, 0.5);
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background: linear-gradient(180deg, #c0392b, #96281b);
|
||||
border: 1px solid #e74c3c;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background: linear-gradient(180deg, #e74c3c, #c0392b);
|
||||
box-shadow: 0 2px 8px rgba(192, 57, 43, 0.5);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,76 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<div class="analysis-button-box">
|
||||
<ul class="analysis-button-ul">
|
||||
<li v-for="(buttonItem, index) in analysisButtons" :key="index">
|
||||
<button
|
||||
@click="handleButtonClick(index, buttonItem.callback)"
|
||||
:style="{
|
||||
'background-image': `url(${selectedButtonIndex === index ? rightOrangeButton : rightBlueButton})`,
|
||||
}"
|
||||
>
|
||||
{{ selectedButtonIndex === index && buttonItem.activeName ? buttonItem.activeName : buttonItem.name }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import { inject } from 'vue';
|
||||
import { rightBlueButton, rightOrangeButton } from '@/assets';
|
||||
import type { AnalysisButtonState } from '@/types/common/useAroundAnalysisType';
|
||||
|
||||
<style scoped></style>
|
||||
// 从父组件注入共享状态,明确指定类型
|
||||
const analysisButtonState = inject<AnalysisButtonState>('analysisButtonState');
|
||||
|
||||
const {
|
||||
selectedButtonIndex,
|
||||
analysisButtons,
|
||||
handleButtonClick
|
||||
} = analysisButtonState!;
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.analysis-button-box {
|
||||
position: absolute;
|
||||
top: 95px;
|
||||
right: 40px;
|
||||
z-index: 1000;
|
||||
width: 180px;
|
||||
padding: 15px 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.analysis-button-ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.analysis-button-ul li {
|
||||
margin: 15px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.analysis-button-ul li button {
|
||||
width: 190px;
|
||||
height: 30px;
|
||||
padding: 5px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
min-width: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 1;
|
||||
background-color: transparent;
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 20px center;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="showPulsePointList"
|
||||
ref="listRef"
|
||||
class="pulse-point-list"
|
||||
:style="{ right: position.right + 'px', bottom: position.bottom + 'px' }"
|
||||
>
|
||||
<div class="list-header" @mousedown="startDrag">
|
||||
<span>脉冲点列表 ({{ pulsePoints.length }})</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="exportToWord"
|
||||
class="export-btn"
|
||||
:icon="Download"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索点位..."
|
||||
clearable
|
||||
size="small"
|
||||
prefix-icon="Search"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="point-list-content">
|
||||
<el-table
|
||||
:data="filteredPoints"
|
||||
border
|
||||
stripe
|
||||
max-height="300"
|
||||
style="width: 100%"
|
||||
empty-text="暂无数据"
|
||||
>
|
||||
<!-- 名称列 - 悬浮时文字换行展开 -->
|
||||
<el-table-column
|
||||
prop="value"
|
||||
label="名称"
|
||||
width="150"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="name-cell">
|
||||
{{ (row as any).value || '未命名' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="category" label="类型" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="getCategoryType(row.category)">
|
||||
{{ getCategoryName((row as any).category) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="坐标" width="135" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="coordinate">
|
||||
{{ (row as any).lon?.toFixed(4) || '-' }}, {{ (row as any).lat?.toFixed(4) || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, inject, reactive, watch, nextTick } from 'vue';
|
||||
import { Download } from '@element-plus/icons-vue';
|
||||
import { Document, Packer, Paragraph, Table, TableRow, TableCell, WidthType, AlignmentType, TextRun, HeadingLevel } from 'docx';
|
||||
import { saveAs } from 'file-saver';
|
||||
import type { AnalysisButtonState } from '@/types/common/useAroundAnalysisType';
|
||||
|
||||
const analysisButtonState = inject<AnalysisButtonState>('analysisButtonState');
|
||||
|
||||
const pulsePoints = computed(() => analysisButtonState?.pulsePoints.value || []);
|
||||
const showPulsePointList = computed(() => analysisButtonState?.showPulsePointList.value || false);
|
||||
|
||||
const searchKeyword = ref('');
|
||||
const listRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// 使用 right 和 bottom 定位(距离右下角的偏移量)
|
||||
const position = reactive({ right: 20, bottom: 20 });
|
||||
const isDragging = ref(false);
|
||||
const dragStart = reactive({ x: 0, y: 0 });
|
||||
const initialPosition = reactive({ right: 0, bottom: 0 });
|
||||
const listSize = reactive({ width: 375, height: 420 });
|
||||
|
||||
// 获取列表的实际尺寸
|
||||
const updateListSize = () => {
|
||||
if (listRef.value) {
|
||||
const rect = listRef.value.getBoundingClientRect();
|
||||
listSize.width = rect.width;
|
||||
listSize.height = rect.height;
|
||||
}
|
||||
};
|
||||
|
||||
// 边界限制函数
|
||||
const constrainPosition = (right: number, bottom: number) => {
|
||||
updateListSize();
|
||||
|
||||
const actualLeft = window.innerWidth - listSize.width - right;
|
||||
const actualTop = window.innerHeight - listSize.height - bottom;
|
||||
|
||||
let constrainedRight = right;
|
||||
let constrainedBottom = bottom;
|
||||
|
||||
if (actualLeft < 0) {
|
||||
constrainedRight = window.innerWidth - listSize.width;
|
||||
}
|
||||
if (actualLeft > window.innerWidth - listSize.width) {
|
||||
constrainedRight = 0;
|
||||
}
|
||||
if (actualTop < 100) {
|
||||
constrainedBottom = window.innerHeight - listSize.height - 100;
|
||||
}
|
||||
if (actualTop > window.innerHeight - listSize.height) {
|
||||
constrainedBottom = 0;
|
||||
}
|
||||
|
||||
constrainedRight = Math.max(0, Math.min(constrainedRight, window.innerWidth - listSize.width));
|
||||
constrainedBottom = Math.max(0, Math.min(constrainedBottom, window.innerHeight - listSize.height));
|
||||
|
||||
return {
|
||||
right: constrainedRight,
|
||||
bottom: constrainedBottom
|
||||
};
|
||||
};
|
||||
|
||||
// 重置位置到右下角
|
||||
const resetPosition = () => {
|
||||
nextTick(() => {
|
||||
updateListSize();
|
||||
position.right = 20;
|
||||
position.bottom = 20;
|
||||
});
|
||||
};
|
||||
|
||||
// 监听列表显示状态
|
||||
watch(showPulsePointList, async (newValue) => {
|
||||
if (newValue) {
|
||||
await nextTick();
|
||||
resetPosition();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听窗口大小变化
|
||||
watch(() => window.innerWidth, () => {
|
||||
if (showPulsePointList.value) {
|
||||
resetPosition();
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => window.innerHeight, () => {
|
||||
if (showPulsePointList.value) {
|
||||
resetPosition();
|
||||
}
|
||||
});
|
||||
|
||||
const filteredPoints = computed(() => {
|
||||
if (!searchKeyword.value) return pulsePoints.value;
|
||||
|
||||
const keyword = searchKeyword.value.toLowerCase();
|
||||
return pulsePoints.value.filter(point =>
|
||||
point.value?.toLowerCase().includes(keyword) ||
|
||||
point.category?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
// 导出为 Word 文档
|
||||
const exportToWord = async () => {
|
||||
try {
|
||||
const points = filteredPoints.value;
|
||||
|
||||
// 统计各类别数量
|
||||
const categoryCount: Record<string, number> = {};
|
||||
points.forEach(point => {
|
||||
const category = getCategoryName(point.category);
|
||||
categoryCount[category] = (categoryCount[category] || 0) + 1;
|
||||
});
|
||||
|
||||
// 创建表格行数据
|
||||
const tableRows = [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: '序号', alignment: AlignmentType.CENTER })],
|
||||
width: { size: 8, type: WidthType.PERCENTAGE },
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: '名称', alignment: AlignmentType.CENTER })],
|
||||
width: { size: 35, type: WidthType.PERCENTAGE },
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: '类型', alignment: AlignmentType.CENTER })],
|
||||
width: { size: 15, type: WidthType.PERCENTAGE },
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: '坐标', alignment: AlignmentType.CENTER })],
|
||||
width: { size: 42, type: WidthType.PERCENTAGE },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
// 添加数据行
|
||||
points.forEach((point, index) => {
|
||||
tableRows.push(
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: String(index + 1), alignment: AlignmentType.CENTER })],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: point.value || '未命名' })],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: getCategoryName(point.category), alignment: AlignmentType.CENTER })],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph({
|
||||
text: `${point.lon?.toFixed(4) || '-'}, ${point.lat?.toFixed(4) || '-'}`,
|
||||
alignment: AlignmentType.CENTER
|
||||
})],
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// 创建统计数据段落
|
||||
const statsParagraphs = Object.entries(categoryCount).map(([category, count]) => {
|
||||
return new Paragraph({
|
||||
children: [
|
||||
new TextRun({ text: `${category}: `, bold: true }),
|
||||
new TextRun({ text: `${count} 个` }),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// 创建文档
|
||||
const doc = new Document({
|
||||
sections: [
|
||||
{
|
||||
properties: {},
|
||||
children: [
|
||||
// 标题
|
||||
new Paragraph({
|
||||
text: '脉冲点列表导出报告',
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
alignment: AlignmentType.CENTER,
|
||||
}),
|
||||
|
||||
// 空行
|
||||
new Paragraph({}),
|
||||
|
||||
// 总数统计
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({ text: '总点数: ', bold: true }),
|
||||
new TextRun({ text: `${points.length} 个` }),
|
||||
],
|
||||
}),
|
||||
|
||||
// 空行
|
||||
new Paragraph({}),
|
||||
|
||||
// 分类统计标题
|
||||
new Paragraph({
|
||||
text: '分类统计:',
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
}),
|
||||
|
||||
// 分类统计数据
|
||||
...statsParagraphs,
|
||||
|
||||
// 空行
|
||||
new Paragraph({}),
|
||||
|
||||
// 详细数据标题
|
||||
new Paragraph({
|
||||
text: '详细数据:',
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
}),
|
||||
|
||||
// 空行
|
||||
new Paragraph({}),
|
||||
|
||||
// 表格
|
||||
new Table({
|
||||
rows: tableRows,
|
||||
width: { size: 100, type: WidthType.PERCENTAGE },
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 生成并下载文档
|
||||
const blob = await Packer.toBlob(doc);
|
||||
const fileName = `脉冲点列表_${new Date().toISOString().slice(0, 10)}.docx`;
|
||||
saveAs(blob, fileName);
|
||||
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error);
|
||||
alert('导出失败,请重试');
|
||||
}
|
||||
};
|
||||
// 开始拖动
|
||||
const startDrag = (e: MouseEvent) => {
|
||||
// Deleted:if ((e.target as HTMLElement).closest('.close-btn')) return;
|
||||
|
||||
isDragging.value = true;
|
||||
dragStart.x = e.clientX;
|
||||
dragStart.y = e.clientY;
|
||||
initialPosition.right = position.right;
|
||||
initialPosition.bottom = position.bottom;
|
||||
|
||||
document.addEventListener('mousemove', onDrag);
|
||||
document.addEventListener('mouseup', stopDrag);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// 拖动中
|
||||
const onDrag = (e: MouseEvent) => {
|
||||
if (!isDragging.value) return;
|
||||
|
||||
const deltaX = e.clientX - dragStart.x;
|
||||
const deltaY = e.clientY - dragStart.y;
|
||||
|
||||
updateListSize();
|
||||
|
||||
const currentLeft = window.innerWidth - listSize.width - position.right;
|
||||
const currentTop = window.innerHeight - listSize.height - position.bottom;
|
||||
|
||||
let newLeft = currentLeft + deltaX;
|
||||
let newTop = currentTop + deltaY;
|
||||
|
||||
newLeft = Math.max(0, newLeft);
|
||||
newLeft = Math.min(newLeft, window.innerWidth - listSize.width);
|
||||
newTop = Math.max(100, newTop);
|
||||
newTop = Math.min(newTop, window.innerHeight - listSize.height);
|
||||
|
||||
const newRight = window.innerWidth - listSize.width - newLeft;
|
||||
const newBottom = window.innerHeight - listSize.height - newTop;
|
||||
|
||||
const constrained = constrainPosition(newRight, newBottom);
|
||||
position.right = constrained.right;
|
||||
position.bottom = constrained.bottom;
|
||||
|
||||
dragStart.x = e.clientX;
|
||||
dragStart.y = e.clientY;
|
||||
initialPosition.right = position.right;
|
||||
initialPosition.bottom = position.bottom;
|
||||
};
|
||||
|
||||
// 停止拖动
|
||||
const stopDrag = () => {
|
||||
isDragging.value = false;
|
||||
document.removeEventListener('mousemove', onDrag);
|
||||
document.removeEventListener('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
const getCategoryName = (category?: string): string => {
|
||||
const nameMap: Record<string, string> = {
|
||||
'school': '学校',
|
||||
'hospital': '医院',
|
||||
'danger': '危险源',
|
||||
'shelter': '避难所',
|
||||
'fire': '消防站',
|
||||
'store': '储备点',
|
||||
'subway': '地铁站',
|
||||
'hidden-danger': '隐患点',
|
||||
'risk-point': '风险点',
|
||||
'bridge': '桥梁',
|
||||
'reservoir': '水库'
|
||||
};
|
||||
return nameMap[category || ''] || category || '未知';
|
||||
};
|
||||
|
||||
const getCategoryType = (category?: string): string => {
|
||||
const typeMap: Record<string, string> = {
|
||||
'school': 'primary',
|
||||
'hospital': 'success',
|
||||
'danger': 'danger',
|
||||
'shelter': 'warning',
|
||||
'fire': 'danger',
|
||||
'store': 'info',
|
||||
'subway': '',
|
||||
'hidden-danger': 'warning',
|
||||
'risk-point': 'danger',
|
||||
'bridge': 'info',
|
||||
'reservoir': 'success'
|
||||
};
|
||||
return typeMap[category || ''] || '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pulse-point-list {
|
||||
position: fixed;
|
||||
z-index: 100000;
|
||||
background: rgba(14, 52, 98, 0.95);
|
||||
border: 1px solid rgba(0, 225, 255, 0.5);
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
width: auto;
|
||||
min-width: 350px;
|
||||
max-width: 450px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(0, 225, 255, 0.3);
|
||||
cursor: move;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.list-header span {
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.export-btn {
|
||||
background-color: rgba(0, 225, 255, 0.3);
|
||||
border-color: rgba(0, 225, 255, 0.5);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.export-btn:hover {
|
||||
background-color: rgba(0, 225, 255, 0.5);
|
||||
border-color: rgba(0, 225, 255, 0.8);
|
||||
}
|
||||
|
||||
|
||||
.search-box {
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.point-list-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.coordinate {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
white-space: nowrap;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
:deep(.el-table) {
|
||||
background-color: transparent;
|
||||
--el-table-tr-bg-color: transparent;
|
||||
--el-table-header-bg-color: rgba(86, 204, 242, 0.3);
|
||||
--el-table-row-hover-bg-color: rgba(0, 225, 255, 0.2);
|
||||
--el-table-border-color: rgba(255, 255, 255, 0.2);
|
||||
--el-table-text-color: white;
|
||||
--el-table-header-text-color: white;
|
||||
}
|
||||
|
||||
:deep(.el-table th.el-table__cell) {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgb(86, 204, 242) 0%,
|
||||
rgb(47, 128, 237) 100%
|
||||
);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 8px 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table td.el-table__cell) {
|
||||
background-color: rgba(15, 61, 118, 0.4);
|
||||
color: white;
|
||||
transition: background-color 0.2s ease;
|
||||
padding: 6px 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||
background-color: rgba(15, 61, 118, 0.6);
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr:hover) > td {
|
||||
background-color: rgba(0, 225, 255, 0.15) !important;
|
||||
}
|
||||
|
||||
|
||||
/* 单元格内容样式 */
|
||||
:deep(.el-table .cell) {
|
||||
padding-left: 8px !important;
|
||||
padding-right: 8px !important;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background-color: rgba(15, 61, 118, 0.6);
|
||||
border: 1px solid rgba(0, 225, 255, 0.3);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper.is-focus) {
|
||||
box-shadow: 0 0 0 1px rgba(0, 225, 255, 0.8) inset;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 225, 255, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 225, 255, 0.5);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(15, 61, 118, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* 隐藏外层容器的滚动条 */
|
||||
.pulse-point-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 表格内部滚动条样式 */
|
||||
.point-list-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.point-list-content::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.point-list-content::-webkit-scrollbar-thumb:hover {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.point-list-content::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 名称单元格样式 - 默认截断,悬浮时换行展开 */
|
||||
.name-cell {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 悬浮时展开文字,换行显示完整内容 */
|
||||
.name-cell:hover {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 强制表格头部文字居中 */
|
||||
:deep(.el-table th.el-table__cell .cell) {
|
||||
justify-content: center !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,105 @@
|
||||
<!-- 搜索组件 -->
|
||||
<template>
|
||||
<div>
|
||||
搜索
|
||||
<div class="search-component-box">
|
||||
<el-autocomplete
|
||||
v-model="state"
|
||||
:fetch-suggestions="querySearch"
|
||||
popper-class="my-autocomplete"
|
||||
placeholder="搜索地点"
|
||||
@select="handleSelect"
|
||||
@focus="handleFocus"
|
||||
clearable
|
||||
:disabled="!canSearch"
|
||||
:teleported="false"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon class="el-input__icon">
|
||||
<edit />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default="{ item }">
|
||||
<div class="value">{{ item.value }}</div>
|
||||
<span class="link">{{ item.lon?.toFixed(4) }}, {{ item.lat?.toFixed(4) }}</span>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts"></script>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { Edit } from '@element-plus/icons-vue';
|
||||
import { useAroundAnalysis } from '@/hooks/rain-earthquake/useAroundAnalysis';
|
||||
|
||||
<style scoped></style>
|
||||
const {
|
||||
state,
|
||||
canSearch,
|
||||
querySearch,
|
||||
handleSelect,
|
||||
handleFocus,
|
||||
loadAllPointData
|
||||
} = useAroundAnalysis();
|
||||
|
||||
onMounted(() => {
|
||||
loadAllPointData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-component-box {
|
||||
position: absolute;
|
||||
top: 125px;
|
||||
right: 210px;
|
||||
z-index: 10000;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.search-component-box :deep(.el-input__wrapper) {
|
||||
background: rgba(60, 99, 147, 0.9) ;
|
||||
box-shadow: 0 0 0 1px rgba(160, 173, 192, 0.5) ;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.search-component-box :deep(.el-input__inner) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.search-component-box :deep(.el-input__inner::placeholder) {
|
||||
color: #9ca9b9;
|
||||
}
|
||||
|
||||
.search-component-box :deep(.el-input__wrapper.is-focus) {
|
||||
box-shadow: 0 0 0 2px rgba(79, 131, 194, 0.9);
|
||||
}
|
||||
|
||||
.search-component-box :deep(.my-autocomplete) {
|
||||
background: rgba(26, 58, 95, 0.95);
|
||||
border: 1px solid #2a3d58;
|
||||
}
|
||||
|
||||
.search-component-box :deep(.my-autocomplete li) {
|
||||
padding: 2px 6px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
color: #e6edf3;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.search-component-box :deep(.my-autocomplete li .value) {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.search-component-box :deep(.my-autocomplete li .link) {
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.search-component-box :deep(.my-autocomplete li:hover),
|
||||
.search-component-box :deep(.my-autocomplete li.highlighted) {
|
||||
background: rgba(58, 112, 169, 0.7);
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
},
|
||||
"prefix": {
|
||||
"landslideHiddenPointId": "landslide-hidden-point-",
|
||||
"collapseHiddenPointId": "collapse-hidden-point-",
|
||||
"debrisFlowHiddenPointId": "debris-flow-hidden-point-",
|
||||
"waterLoggingHiddenPointId": "water-logging-hidden-point-",
|
||||
"flashFloodHiddenPointId": "flash-flood-hidden-point-",
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useLayerControl } from '../rain-earthquake/useLayerControl.ts';
|
||||
import {
|
||||
debrisFlowIcon,
|
||||
landslideIcon,
|
||||
collapseIcon,
|
||||
riskAreaIcon,
|
||||
earthquakeLineIcon,
|
||||
hospitalIcon,
|
||||
@@ -147,18 +146,6 @@ export const useEarthquakeDisasterChain = () => {
|
||||
LoadingResource.DEBRIS_FLOW_HIDDEN_POINT
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '崩塌隐患点',
|
||||
statusStore: statusStore.poiLayers,
|
||||
statusKey: 'showCollapseHiddenPoint' as const,
|
||||
callback: layerControl.clickCollapseHiddenPoint,
|
||||
link: collapseIcon,
|
||||
category: ControlPanelCategory.DISASTER_HAZARD,
|
||||
count: () =>
|
||||
resourceStore.getResourceCount(
|
||||
LoadingResource.COLLAPSE_HIDDEN_POINT
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '风险点',
|
||||
statusStore: statusStore.mapLayers,
|
||||
|
||||
@@ -25,7 +25,6 @@ export const useMap = () => {
|
||||
// 当id改变时候,重置状态
|
||||
if (
|
||||
loadingInfoStore.landslideHiddenPoint.id !== id &&
|
||||
loadingInfoStore.collapseHiddenPoint.id !== id &&
|
||||
loadingInfoStore.debrisFlowHiddenPoint.id !== id &&
|
||||
loadingInfoStore.waterLoggingHiddenPoint.id !== id &&
|
||||
loadingInfoStore.flashFloodHiddenPoint.id !== id &&
|
||||
@@ -44,11 +43,6 @@ export const useMap = () => {
|
||||
loadingInfoStore.landslideHiddenPoint.id = id;
|
||||
}
|
||||
|
||||
// 崩塌隐患点
|
||||
else if (pickedObject.id.startsWith(config.prefix.collapseHiddenPointId)) {
|
||||
loadingInfoStore.collapseHiddenPoint.id = id;
|
||||
}
|
||||
|
||||
// 泥石流隐患点
|
||||
else if (pickedObject.id.startsWith(config.prefix.debrisFlowHiddenPointId)) {
|
||||
loadingInfoStore.debrisFlowHiddenPoint.id = id;
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
import { ref, reactive, onUnmounted, watch, computed } from 'vue';
|
||||
import { useStatusStore } from '@/stores/useStatusStore';
|
||||
import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore';
|
||||
import { LoadingResource } from '@/types/common/LoadingResourceType';
|
||||
import type {
|
||||
PointResource,
|
||||
PointResourceCategory,
|
||||
ButtonResourceConfig,
|
||||
AnalysisButtonConfig,
|
||||
DialogPosition,
|
||||
AnalysisButtonState
|
||||
} from '@/types/common/useAroundAnalysisType';
|
||||
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils';
|
||||
import {
|
||||
ScreenSpaceEventHandler,
|
||||
ScreenSpaceEventType,
|
||||
Cartesian2,
|
||||
Cartographic,
|
||||
Cartesian3,
|
||||
} from 'cesium';
|
||||
import { useCircleDrawer } from './useCircleDrawer';
|
||||
import { usePulseEffect } from './usePulseEffect';
|
||||
import { useMarkerManager } from './useMarkerManager';
|
||||
|
||||
// ==================== 常量配置 ====================
|
||||
const DIALOG_WIDTH = 280;
|
||||
const DIALOG_HEIGHT = 150;
|
||||
const DIALOG_PADDING = 10;
|
||||
const DIALOG_OFFSET = 20;
|
||||
const EARTH_RADIUS = 6371000;
|
||||
const MIN_FLY_HEIGHT = 10000;
|
||||
const FLY_HEIGHT_MULTIPLIER = 6000;
|
||||
const FLY_DURATION = 2;
|
||||
|
||||
// ==================== Store 实例 ====================
|
||||
const statusStore = useStatusStore();
|
||||
const loadingResourceStore = useLoadingResourceStore();
|
||||
|
||||
const poi = computed(() => statusStore.poiLayers);
|
||||
const map = computed(() => statusStore.mapLayers);
|
||||
const infra = computed(() => statusStore.infrastructureLayers);
|
||||
|
||||
// ==================== 资源配置 ====================
|
||||
const RESOURCE_CONFIGS: ButtonResourceConfig[] = [
|
||||
{ key: LoadingResource.SCHOOL, category: 'school' },
|
||||
{ key: LoadingResource.HOSPITAL, category: 'hospital' },
|
||||
{ key: LoadingResource.DANGEROUS_SOURCE, category: 'danger' },
|
||||
{ key: LoadingResource.EMERGENCY_SHELTER, category: 'shelter' },
|
||||
{ key: LoadingResource.FIRE_STATION, category: 'fire' },
|
||||
{ key: LoadingResource.STORE_POINTS, category: 'store' },
|
||||
{ key: LoadingResource.SUBWAY_STATION, category: 'subway' },
|
||||
{ key: LoadingResource.LANDSLIDE_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'landslide' },
|
||||
{ key: LoadingResource.DEBRIS_FLOW_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'debris_flow' },
|
||||
{ key: LoadingResource.WATER_LOGGING_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'water_logging' },
|
||||
{ key: LoadingResource.FLASH_FLOOD_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'flash_flood' },
|
||||
{ key: LoadingResource.RISK_POINT, category: 'risk-point' },
|
||||
{ key: LoadingResource.BRIDGE, category: 'bridge' },
|
||||
{ key: LoadingResource.RESERVOIR, category: 'reservoir' },
|
||||
];
|
||||
|
||||
// ==================== 图层可见性判断 ====================
|
||||
const isCategoryVisible = (category: PointResourceCategory, originalType?: string): boolean => {
|
||||
const visibilityMap: Record<string, () => boolean> = {
|
||||
school: () => poi.value.showSchool.show,
|
||||
hospital: () => poi.value.showHospital.show,
|
||||
danger: () => poi.value.showDangerSource.show,
|
||||
shelter: () => poi.value.showRefugeeShelter.show,
|
||||
fire: () => poi.value.showFireStation.show,
|
||||
store: () => poi.value.showReservePoint.show,
|
||||
subway: () => poi.value.showSubwayStation.show,
|
||||
'risk-point': () => map.value.riskPointShow.show,
|
||||
bridge: () => infra.value.showBridge.show,
|
||||
reservoir: () => infra.value.showReservoir.show,
|
||||
'hidden-danger': () => {
|
||||
const hiddenMap: Record<string, () => boolean> = {
|
||||
landslide: () => poi.value.showLandslideHiddenPoint.show,
|
||||
debris_flow: () => poi.value.showDebrisFlowHiddenPoint.show,
|
||||
water_logging: () => poi.value.showWaterLoggingHiddenPoint.show,
|
||||
flash_flood: () => poi.value.showFlashFloodHiddenPoint.show,
|
||||
};
|
||||
return hiddenMap[originalType || '']?.() ?? false;
|
||||
},
|
||||
};
|
||||
return visibilityMap[category]?.() ?? false;
|
||||
};
|
||||
|
||||
// ==================== 响应式状态 ====================
|
||||
export const useAnalysisButton = (): AnalysisButtonState => {
|
||||
const selectedButtonIndex = ref<number>(-1);
|
||||
const showAreaDialog = ref(false);
|
||||
const radius = ref(10);
|
||||
const dialogPosition = reactive<DialogPosition>({ x: 0, y: 0 });
|
||||
const pulsePoints = ref<PointResource[]>([]);
|
||||
const showPulsePointList = ref(false);
|
||||
|
||||
let clickHandler: ScreenSpaceEventHandler | null = null;
|
||||
let currentCenterPosition: Cartesian3 | null = null;
|
||||
|
||||
// ==================== 组合子 Hook ====================
|
||||
const { drawCircle, clearCircle } = useCircleDrawer();
|
||||
const { addPulseEffectToPoints, removePulseEffect } = usePulseEffect();
|
||||
const { addMarker, removeMarker } = useMarkerManager();
|
||||
|
||||
// ==================== 数据加载与计算 ====================
|
||||
|
||||
const loadAllPointData = (): PointResource[] => {
|
||||
const resources: PointResource[] = [];
|
||||
|
||||
RESOURCE_CONFIGS.forEach(config => {
|
||||
const data = loadingResourceStore.getLoadingResource(config.key).info;
|
||||
if (Array.isArray(data)) {
|
||||
const convertedData = data.map((item: Record<string, unknown>) => {
|
||||
const id = item.id || item._id || item.uuid || 'unknown_id';
|
||||
const safeId = typeof id === 'string' ? id : typeof id === 'number' ? id : 'unknown_id';
|
||||
const value = (item.name && String(item.name).trim() !== '')
|
||||
? String(item.name)
|
||||
: String(safeId);
|
||||
|
||||
return {
|
||||
...item,
|
||||
id: safeId,
|
||||
value,
|
||||
category: config.category,
|
||||
originalType: (config.forcedType || (item.type as string) || (item.disasterType as string))?.toLowerCase()
|
||||
};
|
||||
});
|
||||
resources.push(...convertedData);
|
||||
}
|
||||
});
|
||||
|
||||
const seenIds = new Map<string | number, PointResource>();
|
||||
for (const item of resources) {
|
||||
if (!seenIds.has(item.id)) {
|
||||
seenIds.set(item.id, item);
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueResources = Array.from(seenIds.values());
|
||||
console.log('加载的点数据总数:', uniqueResources.length);
|
||||
return uniqueResources;
|
||||
};
|
||||
|
||||
const calculateDistance = (
|
||||
centerLon: number,
|
||||
centerLat: number,
|
||||
pointLon: unknown,
|
||||
pointLat: unknown
|
||||
): number => {
|
||||
const pLon = Number(pointLon);
|
||||
const pLat = Number(pointLat);
|
||||
|
||||
if (isNaN(pLon) || isNaN(pLat)) return Infinity;
|
||||
|
||||
const dLat = (pLat - centerLat) * Math.PI / 180;
|
||||
const dLon = (pLon - centerLon) * Math.PI / 180;
|
||||
const a = Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos(centerLat * Math.PI / 180) * Math.cos(pLat * Math.PI / 180) *
|
||||
Math.sin(dLon / 2) ** 2;
|
||||
return EARTH_RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
};
|
||||
|
||||
const getPointsInCircle = (centerPosition: Cartesian3, radiusKm: number): PointResource[] => {
|
||||
const cartographic = Cartographic.fromCartesian(centerPosition);
|
||||
const centerLon = cartographic.longitude * (180 / Math.PI);
|
||||
const centerLat = cartographic.latitude * (180 / Math.PI);
|
||||
|
||||
const allPoints = loadAllPointData();
|
||||
const radiusMeters = radiusKm * 1000;
|
||||
|
||||
return allPoints.filter(point => {
|
||||
if (point.lon === undefined || point.lat === undefined) return false;
|
||||
|
||||
const distance = calculateDistance(centerLon, centerLat, point.lon, point.lat);
|
||||
return distance <= radiusMeters && isCategoryVisible(point.category as PointResourceCategory, point.originalType);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新脉冲效果
|
||||
*/
|
||||
const refreshPulseEffect = () => {
|
||||
if (!currentCenterPosition) return;
|
||||
|
||||
console.log('刷新脉冲效果...');
|
||||
removePulseEffect();
|
||||
|
||||
const pointsInCircle = getPointsInCircle(currentCenterPosition, radius.value);
|
||||
addPulseEffectToPoints(pointsInCircle);
|
||||
pulsePoints.value = pointsInCircle;
|
||||
showPulsePointList.value = true;
|
||||
};
|
||||
|
||||
// ==================== 地图事件处理 ====================
|
||||
|
||||
const registerMapClickHandler = () => {
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
clickHandler = new ScreenSpaceEventHandler(viewer.scene.canvas);
|
||||
clickHandler.setInputAction((clickEvent: { position: Cartesian2 }) => {
|
||||
const cartesian = viewer.camera.pickEllipsoid(clickEvent.position, viewer.scene.globe.ellipsoid);
|
||||
if (cartesian) {
|
||||
currentCenterPosition = cartesian;
|
||||
const cartographic = Cartographic.fromCartesian(cartesian);
|
||||
const longitude = cartographic.longitude * (180 / Math.PI);
|
||||
const latitude = cartographic.latitude * (180 / Math.PI);
|
||||
|
||||
console.log('点击位置:', { longitude, latitude });
|
||||
addMarker(cartesian);
|
||||
showAreaDialog.value = true;
|
||||
calculateDialogPosition(clickEvent.position);
|
||||
}
|
||||
}, ScreenSpaceEventType.LEFT_CLICK);
|
||||
};
|
||||
|
||||
const removeMapClickHandler = () => {
|
||||
if (clickHandler) {
|
||||
clickHandler.destroy();
|
||||
clickHandler = null;
|
||||
}
|
||||
};
|
||||
|
||||
const calculateDialogPosition = (clickPosition: Cartesian2) => {
|
||||
const screenWidth = window.innerWidth;
|
||||
const screenHeight = window.innerHeight;
|
||||
|
||||
let x = clickPosition.x + DIALOG_OFFSET;
|
||||
let y = clickPosition.y + DIALOG_OFFSET;
|
||||
|
||||
if (x + DIALOG_WIDTH > screenWidth - DIALOG_PADDING) {
|
||||
x = clickPosition.x - DIALOG_WIDTH - DIALOG_OFFSET;
|
||||
}
|
||||
|
||||
if (y + DIALOG_HEIGHT > screenHeight - DIALOG_PADDING) {
|
||||
y = clickPosition.y - DIALOG_HEIGHT - DIALOG_OFFSET;
|
||||
}
|
||||
|
||||
dialogPosition.x = Math.max(DIALOG_PADDING, Math.min(x, screenWidth - DIALOG_WIDTH - DIALOG_PADDING));
|
||||
dialogPosition.y = Math.max(DIALOG_PADDING, Math.min(y, screenHeight - DIALOG_HEIGHT - DIALOG_PADDING));
|
||||
};
|
||||
|
||||
// ==================== 资源清理 ====================
|
||||
const clearAllAnalysisResources = () => {
|
||||
removeMarker();
|
||||
clearCircle();
|
||||
removePulseEffect();
|
||||
currentCenterPosition = null;
|
||||
pulsePoints.value = [];
|
||||
showPulsePointList.value = false;
|
||||
};
|
||||
/**
|
||||
* 仅清除视觉效果,保留标记点和中心点位置
|
||||
*/
|
||||
const clearVisualEffectsOnly = () => {
|
||||
clearCircle();
|
||||
removePulseEffect();
|
||||
pulsePoints.value = [];
|
||||
showPulsePointList.value = false;
|
||||
};
|
||||
// ==================== 事件处理 ====================
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!currentCenterPosition) {
|
||||
console.error('中心点位置不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('确认添加区域分析', {
|
||||
radius: radius.value,
|
||||
center: currentCenterPosition
|
||||
});
|
||||
// 先清除上一次的视觉效果(保留标记点)
|
||||
clearVisualEffectsOnly();
|
||||
|
||||
// 重新绘制当前选择的效果
|
||||
drawCircle(currentCenterPosition, radius.value);
|
||||
|
||||
const pointsInCircle = getPointsInCircle(currentCenterPosition, radius.value);
|
||||
addPulseEffectToPoints(pointsInCircle);
|
||||
pulsePoints.value = pointsInCircle;
|
||||
showPulsePointList.value = true;
|
||||
|
||||
const cartographic = Cartographic.fromCartesian(currentCenterPosition);
|
||||
const longitude = cartographic.longitude * (180 / Math.PI);
|
||||
const latitude = cartographic.latitude * (180 / Math.PI);
|
||||
|
||||
const flyHeight = Math.max(radius.value * FLY_HEIGHT_MULTIPLIER, MIN_FLY_HEIGHT);
|
||||
CesiumUtilsSingleton.flyToTarget([longitude, latitude, flyHeight], FLY_DURATION);
|
||||
|
||||
// 关闭对话框并清理资源(包括鼠标样式)
|
||||
showAreaDialog.value = false;
|
||||
|
||||
// 移除地图点击事件监听器,恢复鼠标默认样式
|
||||
if (clickHandler) {
|
||||
clickHandler.destroy();
|
||||
clickHandler = null;
|
||||
}
|
||||
|
||||
// 恢复鼠标默认样式
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (viewer?.canvas) {
|
||||
statusStore.cursorStyle = 'default';
|
||||
viewer.canvas.style.cursor = 'default';
|
||||
}
|
||||
};
|
||||
const handleCancel = () => {
|
||||
showAreaDialog.value = false;
|
||||
clearAllAnalysisResources();
|
||||
pulsePoints.value = [];
|
||||
showPulsePointList.value = false;
|
||||
};
|
||||
|
||||
const handleButtonClick = (index: number, callback: (status: boolean) => void) => {
|
||||
const isActive = selectedButtonIndex.value === index;
|
||||
|
||||
if (isActive) {
|
||||
selectedButtonIndex.value = -1;
|
||||
callback(false);
|
||||
} else {
|
||||
if (selectedButtonIndex.value !== -1) {
|
||||
clearAllAnalysisResources();
|
||||
showAreaDialog.value = false;
|
||||
}
|
||||
selectedButtonIndex.value = index;
|
||||
callback(true);
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 监听器 ====================
|
||||
|
||||
watch(
|
||||
() => loadingResourceStore.loadingResource,
|
||||
() => {
|
||||
console.log('检测到资源数据变化,刷新脉冲效果');
|
||||
refreshPulseEffect();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const layerVisibilityWatchers = [
|
||||
() => poi.value.showSchool.show,
|
||||
() => poi.value.showHospital.show,
|
||||
() => poi.value.showDangerSource.show,
|
||||
() => poi.value.showRefugeeShelter.show,
|
||||
() => poi.value.showFireStation.show,
|
||||
() => poi.value.showReservePoint.show,
|
||||
() => poi.value.showSubwayStation.show,
|
||||
() => poi.value.showLandslideHiddenPoint.show,
|
||||
() => poi.value.showDebrisFlowHiddenPoint.show,
|
||||
() => poi.value.showWaterLoggingHiddenPoint.show,
|
||||
() => poi.value.showFlashFloodHiddenPoint.show,
|
||||
() => map.value.riskPointShow.show,
|
||||
() => infra.value.showBridge.show,
|
||||
() => infra.value.showReservoir.show,
|
||||
];
|
||||
|
||||
watch(layerVisibilityWatchers, () => {
|
||||
// 只有当有中心点位置且脉冲点列表正在显示时,才刷新脉冲效果
|
||||
if (currentCenterPosition && showPulsePointList.value) {
|
||||
console.log('检测到图层可见性变化,刷新脉冲效果');
|
||||
refreshPulseEffect();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearAllAnalysisResources();
|
||||
removeMapClickHandler();
|
||||
});
|
||||
|
||||
// ==================== 按钮配置 ====================
|
||||
const analysisButtons: AnalysisButtonConfig[] = [
|
||||
{
|
||||
name: '标记区域分析',
|
||||
activeName: '取消区域分析',
|
||||
callback: (status: boolean) => {
|
||||
console.log('标记区域分析', status);
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer?.canvas) return;
|
||||
|
||||
statusStore.cursorStyle = status ? 'crosshair' : 'default';
|
||||
viewer.canvas.style.cursor = status ? 'crosshair' : 'default';
|
||||
|
||||
if (status) {
|
||||
registerMapClickHandler();
|
||||
} else {
|
||||
removeMapClickHandler();
|
||||
clearAllAnalysisResources();
|
||||
showAreaDialog.value = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '隐藏行政区划',
|
||||
callback: (status: boolean) => {
|
||||
console.log('隐藏行政区划', status);
|
||||
useStatusStore().mapLayers.showAdministrativeDivision.show = !status;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
selectedButtonIndex,
|
||||
showAreaDialog,
|
||||
radius,
|
||||
dialogPosition,
|
||||
analysisButtons,
|
||||
handleButtonClick,
|
||||
handleConfirm,
|
||||
handleCancel,
|
||||
refreshPulseEffect,
|
||||
pulsePoints,
|
||||
showPulsePointList,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils';
|
||||
import { useStatusStore } from '@/stores/useStatusStore';
|
||||
import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore';
|
||||
import { LoadingResource } from '@/types/common/LoadingResourceType';
|
||||
import type { PointResource, ResourceConfig } from '@/types/common/useAroundAnalysisType';
|
||||
|
||||
/**
|
||||
* 周边分析搜索组件钩子函数
|
||||
* @returns 搜索相关的状态和方法
|
||||
*/
|
||||
export const useAroundAnalysis = () => {
|
||||
const statusStore = useStatusStore();//用于访问图层显示状态
|
||||
const loadingResourceStore = useLoadingResourceStore();//用于访问各类点位数据
|
||||
// 计算属性:获取图层的显示状态
|
||||
const poi = computed(() => statusStore.poiLayers);
|
||||
const map = computed(() => statusStore.mapLayers);
|
||||
const infra = computed(() => statusStore.infrastructureLayers);
|
||||
|
||||
/**
|
||||
* 资源配置列表
|
||||
*/
|
||||
const RESOURCE_CONFIGS: ResourceConfig[] = [
|
||||
{ key: LoadingResource.SCHOOL, category: 'school', isVisible: () => poi.value.showSchool.show },
|
||||
{ key: LoadingResource.HOSPITAL, category: 'hospital', isVisible: () => poi.value.showHospital.show },
|
||||
{ key: LoadingResource.DANGEROUS_SOURCE, category: 'danger', isVisible: () => poi.value.showDangerSource.show },
|
||||
{ key: LoadingResource.EMERGENCY_SHELTER, category: 'shelter', isVisible: () => poi.value.showRefugeeShelter.show },
|
||||
{ key: LoadingResource.FIRE_STATION, category: 'fire', isVisible: () => poi.value.showFireStation.show },
|
||||
{ key: LoadingResource.STORE_POINTS, category: 'store', isVisible: () => poi.value.showReservePoint.show },
|
||||
{ key: LoadingResource.SUBWAY_STATION, category: 'subway', isVisible: () => poi.value.showSubwayStation.show },
|
||||
{ key: LoadingResource.LANDSLIDE_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'landslide', isVisible: () => poi.value.showLandslideHiddenPoint.show },
|
||||
{ key: LoadingResource.DEBRIS_FLOW_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'debris_flow', isVisible: () => poi.value.showDebrisFlowHiddenPoint.show },
|
||||
{ key: LoadingResource.WATER_LOGGING_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'water_logging', isVisible: () => poi.value.showWaterLoggingHiddenPoint.show },
|
||||
{ key: LoadingResource.FLASH_FLOOD_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'flash_flood', isVisible: () => poi.value.showFlashFloodHiddenPoint.show },
|
||||
{ key: LoadingResource.RISK_POINT, category: 'risk-point', isVisible: () => map.value.riskPointShow.show },
|
||||
{ key: LoadingResource.BRIDGE, category: 'bridge', isVisible: () => infra.value.showBridge.show },
|
||||
{ key: LoadingResource.RESERVOIR, category: 'reservoir', isVisible: () => infra.value.showReservoir.show },
|
||||
];
|
||||
|
||||
/**
|
||||
* 所有资源数据
|
||||
*/
|
||||
const allResources = ref<PointResource[]>([]);
|
||||
|
||||
/**
|
||||
* 计算属性:判断是否允许搜索
|
||||
*/
|
||||
const canSearch = computed(() => {
|
||||
return RESOURCE_CONFIGS.some(config => config.isVisible());
|
||||
});
|
||||
|
||||
/**
|
||||
* 查询建议回调
|
||||
* @param queryString - 搜索字符串
|
||||
* @param cb - 回调函数
|
||||
*/
|
||||
function querySearch(queryString: string, cb: (results: PointResource[]) => void) {
|
||||
if (!canSearch.value) {
|
||||
cb([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const lowerQuery = queryString.toLowerCase();
|
||||
const filteredResults = allResources.value.filter(item => {
|
||||
const config = RESOURCE_CONFIGS.find(c => c.category === item.category);
|
||||
let isVisible = false;
|
||||
if (config) {
|
||||
if (item.category === 'hidden-danger') {
|
||||
const type = (item.originalType as string)?.toLowerCase();
|
||||
isVisible = (type === config.forcedType?.toLowerCase()) && config.isVisible();
|
||||
} else {
|
||||
isVisible = config.isVisible();
|
||||
}
|
||||
}
|
||||
|
||||
if (!isVisible) return false;
|
||||
if (!queryString) return true;
|
||||
const matchStr = (item.value || '').toLowerCase();
|
||||
return matchStr.includes(lowerQuery);
|
||||
});
|
||||
|
||||
cb(filteredResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择建议回调
|
||||
* @param item - 选中的点资源
|
||||
*/
|
||||
function handleSelect(item: PointResource) {
|
||||
if (item.lon != null && item.lat != null) {
|
||||
CesiumUtilsSingleton.flyToTarget([item.lon, item.lat, 6000]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理聚焦事件,重新加载数据
|
||||
*/
|
||||
function handleFocus() {
|
||||
loadAllPointData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据处理:将 Store 数据转换为资源格式
|
||||
* @param infoList - 原始数据列表
|
||||
* @param category - 资源分类
|
||||
* @param forcedType - 强制类型
|
||||
* @returns 转换后的点资源数组
|
||||
*/
|
||||
function convertStoreDataToResources(
|
||||
infoList: Record<string, unknown>[],
|
||||
category: PointResource['category'],
|
||||
forcedType?: string,
|
||||
): PointResource[] {
|
||||
if (!Array.isArray(infoList)) return [];
|
||||
|
||||
return infoList.map((item: Record<string, unknown>) => {
|
||||
const id = item.id || item._id || item.uuid || 'unknown_id';
|
||||
const safeId = typeof id === 'string' ? id : typeof id === 'number' ? id : 'unknown_id';
|
||||
|
||||
const value = (item.name && String(item.name).trim() !== '')
|
||||
? String(item.name)
|
||||
: String(safeId);
|
||||
|
||||
return {
|
||||
...item,
|
||||
id: safeId,
|
||||
value: value,
|
||||
category: category,
|
||||
originalType: (forcedType || (item.type as string) || (item.disasterType as string))?.toLowerCase(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载所有点类数据
|
||||
*/
|
||||
function loadAllPointData() {
|
||||
const resources: PointResource[] = [];
|
||||
|
||||
RESOURCE_CONFIGS.forEach(config => {
|
||||
const data = loadingResourceStore.getLoadingResource(config.key).info;
|
||||
resources.push(...convertStoreDataToResources(data, config.category, config.forcedType));
|
||||
});
|
||||
|
||||
const seenIds = new Map<string | number, PointResource>();
|
||||
const uniqueResources: PointResource[] = [];
|
||||
for (const item of resources) {
|
||||
if (!seenIds.has(item.id)) {
|
||||
seenIds.set(item.id, item);
|
||||
uniqueResources.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
allResources.value = uniqueResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索框的值
|
||||
*/
|
||||
const state = ref('');
|
||||
|
||||
return { state, allResources, canSearch, querySearch, handleSelect, handleFocus, loadAllPointData };
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Cartesian3, Color, HeightReference } from 'cesium';
|
||||
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils';
|
||||
import type { CircleAnalysisOptions } from '@/types/cesium/EntityOptions';
|
||||
|
||||
/**
|
||||
* 圆形区域绘制管理 Hook
|
||||
* @returns 圆形绘制相关方法
|
||||
*/
|
||||
export const useCircleDrawer = () => {
|
||||
/**
|
||||
* 绘制圆形区域
|
||||
* @param centerPosition - 中心点位置
|
||||
* @param radiusKm - 半径(公里)
|
||||
* @param options - 可选配置
|
||||
*/
|
||||
const drawCircle = (
|
||||
centerPosition: Cartesian3,
|
||||
radiusKm: number,
|
||||
options?: Partial<CircleAnalysisOptions>
|
||||
): void => {
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
const radiusMeters = radiusKm * 1000;
|
||||
|
||||
// 默认配置
|
||||
const fillColor = options?.fillColor || Color.RED;
|
||||
const fillAlpha = options?.fillAlpha ?? 0.1;
|
||||
const outlineColor = options?.outlineColor || Color.RED;
|
||||
const outlineAlpha = options?.outlineAlpha ?? 0.9;
|
||||
const outlineWidth = options?.outlineWidth ?? 3;
|
||||
const height = options?.height ?? 0;
|
||||
const heightReference = options?.heightReference ?? HeightReference.CLAMP_TO_GROUND;
|
||||
|
||||
const circleConfig = {
|
||||
position: centerPosition,
|
||||
ellipse: {
|
||||
semiMajorAxis: radiusMeters,
|
||||
semiMinorAxis: radiusMeters,
|
||||
height,
|
||||
heightReference,
|
||||
},
|
||||
};
|
||||
|
||||
const circleFillEntity = viewer.entities.add({
|
||||
...circleConfig,
|
||||
ellipse: {
|
||||
...circleConfig.ellipse,
|
||||
material: fillColor.withAlpha(fillAlpha),
|
||||
},
|
||||
});
|
||||
|
||||
const circleOutlineEntity = viewer.entities.add({
|
||||
...circleConfig,
|
||||
ellipse: {
|
||||
...circleConfig.ellipse,
|
||||
material: Color.TRANSPARENT,
|
||||
outline: true,
|
||||
outlineColor: outlineColor.withAlpha(outlineAlpha),
|
||||
outlineWidth,
|
||||
},
|
||||
});
|
||||
|
||||
circleFillEntity ._isAnalysisCircle = true;
|
||||
circleOutlineEntity._isAnalysisCircle = true;
|
||||
|
||||
console.log(`已添加圆形图层, 半径: ${radiusKm}公里`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除所有圆形区域
|
||||
*/
|
||||
const clearCircle = (): void => {
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
viewer.entities.values
|
||||
.filter(entity => entity._isAnalysisCircle)
|
||||
.forEach(entity => viewer.entities.remove(entity));
|
||||
};
|
||||
|
||||
return {
|
||||
drawCircle,
|
||||
clearCircle,
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
collapseIcon,
|
||||
debrisFlowIcon,
|
||||
flashFloodIcon,
|
||||
landslideIcon,
|
||||
@@ -15,7 +14,6 @@ export const useHiddenPoint = () => {
|
||||
* 字段映射配置
|
||||
*/
|
||||
const field = {
|
||||
probability: '预测概率',
|
||||
fieldCode: '野外编号',
|
||||
disasterName: '灾害点名称',
|
||||
position: '位置',
|
||||
@@ -24,13 +22,6 @@ export const useHiddenPoint = () => {
|
||||
riskGrade: '风险等级',
|
||||
};
|
||||
|
||||
/**
|
||||
* 样式
|
||||
*/
|
||||
const style = {
|
||||
probability: { background: '#888888' },
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据灾害类型获取对应图标
|
||||
* @param disasterType - 灾害类型(支持中英文)
|
||||
@@ -42,9 +33,6 @@ export const useHiddenPoint = () => {
|
||||
case 'landslide':
|
||||
case '滑坡':
|
||||
return landslideIcon;
|
||||
case 'collapse':
|
||||
case '崩塌':
|
||||
return collapseIcon;
|
||||
case 'debris_flow':
|
||||
case '泥石流':
|
||||
return debrisFlowIcon;
|
||||
@@ -59,5 +47,5 @@ export const useHiddenPoint = () => {
|
||||
}
|
||||
}
|
||||
|
||||
return { field, style, getDisasterIcon };
|
||||
return { field, getDisasterIcon };
|
||||
};
|
||||
|
||||
@@ -161,13 +161,6 @@ export const useLayerControl = () => {
|
||||
statusStore.poiLayers.showLandslideHiddenPoint.loading = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示崩塌隐患点
|
||||
*/
|
||||
const clickCollapseHiddenPoint = () => {
|
||||
statusStore.poiLayers.showCollapseHiddenPoint.loading = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示泥石流隐患点
|
||||
*/
|
||||
@@ -228,7 +221,6 @@ export const useLayerControl = () => {
|
||||
clickReservoir,
|
||||
clickSubwayStation,
|
||||
clickLandslideHiddenPoint,
|
||||
clickCollapseHiddenPoint,
|
||||
clickDebrisFlowHiddenPoint,
|
||||
clickWaterLoggingHiddenPoint,
|
||||
clickFlashFloodHiddenPoint,
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Cartesian3, Entity, VerticalOrigin, HorizontalOrigin, HeightReference } from 'cesium';
|
||||
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils';
|
||||
|
||||
// 十字准心标记 SVG
|
||||
const MARKER_SVG = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80">
|
||||
<circle cx="40" cy="40" r="6" fill="#FF0000" stroke="#FFFFFF" stroke-width="2"/>
|
||||
<path d="M 15 20 L 15 15 L 20 15" stroke="#00FF00" stroke-width="4" fill="none" stroke-linecap="round"/>
|
||||
<path d="M 65 20 L 65 15 L 60 15" stroke="#00FF00" stroke-width="4" fill="none" stroke-linecap="round"/>
|
||||
<path d="M 15 60 L 15 65 L 20 65" stroke="#00FF00" stroke-width="4" fill="none" stroke-linecap="round"/>
|
||||
<path d="M 65 60 L 65 65 L 60 65" stroke="#00FF00" stroke-width="4" fill="none" stroke-linecap="round"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
/**
|
||||
* 标记管理 Hook
|
||||
* @returns 标记相关方法
|
||||
*/
|
||||
export const useMarkerManager = () => {
|
||||
let currentMarkerEntity: Entity | null = null;
|
||||
|
||||
/**
|
||||
* 添加标记
|
||||
* @param position - 标记位置
|
||||
*/
|
||||
const addMarker = (position: Cartesian3): void => {
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
// 移除旧标记
|
||||
if (currentMarkerEntity) {
|
||||
viewer.entities.remove(currentMarkerEntity);
|
||||
}
|
||||
|
||||
const markerDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(MARKER_SVG)}`;
|
||||
currentMarkerEntity = viewer.entities.add({
|
||||
position,
|
||||
billboard: {
|
||||
image: markerDataUrl,
|
||||
scale: 1.0,
|
||||
verticalOrigin: VerticalOrigin.CENTER,
|
||||
horizontalOrigin: HorizontalOrigin.CENTER,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
heightReference: HeightReference.CLAMP_TO_GROUND,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 移除标记
|
||||
*/
|
||||
const removeMarker = (): void => {
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer || !currentMarkerEntity) return;
|
||||
|
||||
viewer.entities.remove(currentMarkerEntity);
|
||||
currentMarkerEntity = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前标记实体
|
||||
*/
|
||||
const getCurrentMarker = (): Entity | null => {
|
||||
return currentMarkerEntity;
|
||||
};
|
||||
|
||||
return {
|
||||
addMarker,
|
||||
removeMarker,
|
||||
getCurrentMarker,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
Cartesian3,
|
||||
Color,
|
||||
Entity,
|
||||
VerticalOrigin,
|
||||
HorizontalOrigin,
|
||||
HeightReference,
|
||||
CallbackProperty,
|
||||
JulianDate
|
||||
} from 'cesium';
|
||||
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils';
|
||||
import type { PointResource } from '@/types/common/useAroundAnalysisType';
|
||||
|
||||
/**
|
||||
* 脉冲效果管理 Hook
|
||||
* @returns 脉冲效果相关方法
|
||||
*/
|
||||
export const usePulseEffect = () => {
|
||||
let pulseEntities: Entity[] = [];
|
||||
|
||||
/**
|
||||
* 创建红色圆形纹理
|
||||
* @param radius - 半径
|
||||
* @param lineWidth - 线宽
|
||||
* @param opacity - 透明度
|
||||
* @returns Base64 图片数据
|
||||
*/
|
||||
const createRedCircleTexture = (radius = 15, lineWidth = 2, opacity = 0.8): string => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const size = radius * 2 + 10;
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (!ctx) return '';
|
||||
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
ctx.beginPath();
|
||||
ctx.arc(size / 2, size / 2, radius, 0, 2 * Math.PI);
|
||||
ctx.strokeStyle = `rgba(255, 0, 0, ${opacity})`;
|
||||
ctx.lineWidth = lineWidth;
|
||||
ctx.stroke();
|
||||
|
||||
return canvas.toDataURL();
|
||||
};
|
||||
|
||||
/**
|
||||
* 为单个点添加脉冲效果
|
||||
* @param point - 点资源
|
||||
* @param startTime - 开始时间(秒)
|
||||
*/
|
||||
const addPulseEffectToPoint = (point: PointResource, startTime: number): void => {
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer || point.lon === undefined || point.lat === undefined) return;
|
||||
|
||||
const lon = Number(point.lon);
|
||||
const lat = Number(point.lat);
|
||||
if (isNaN(lon) || isNaN(lat)) return;
|
||||
|
||||
const position = Cartesian3.fromDegrees(lon, lat, 0);
|
||||
const baseTexture = createRedCircleTexture(10, 3, 1.0);
|
||||
|
||||
const dynamicScale = new CallbackProperty((time: JulianDate) => {
|
||||
const elapsed = ((time.secondsOfDay - startTime) % 1 + 1) % 1;
|
||||
return 1.1 + 0.2 * Math.sin(elapsed * Math.PI * 2);
|
||||
}, false);
|
||||
|
||||
const pulseEntity = viewer.entities.add({
|
||||
position,
|
||||
billboard: {
|
||||
image: baseTexture,
|
||||
scale: dynamicScale,
|
||||
color: new CallbackProperty((time: JulianDate) => {
|
||||
const elapsed = ((time.secondsOfDay - startTime) % 1 + 1) % 1;
|
||||
const alpha = 0.6 + 0.4 * Math.sin(elapsed * Math.PI * 2);
|
||||
return Color.fromBytes(255, 0, 0, Math.floor(alpha * 255));
|
||||
}, false),
|
||||
verticalOrigin: VerticalOrigin.CENTER,
|
||||
horizontalOrigin: HorizontalOrigin.CENTER,
|
||||
heightReference: HeightReference.CLAMP_TO_GROUND,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
});
|
||||
|
||||
pulseEntities.push(pulseEntity);
|
||||
};
|
||||
|
||||
/**
|
||||
* 为多个点添加脉冲效果
|
||||
* @param points - 点资源数组
|
||||
*/
|
||||
const addPulseEffectToPoints = (points: PointResource[]): void => {
|
||||
const startTime = JulianDate.now().secondsOfDay;
|
||||
points.forEach(point => addPulseEffectToPoint(point, startTime));
|
||||
console.log(`已为 ${points.length} 个点添加脉冲效果`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 移除所有脉冲效果
|
||||
*/
|
||||
const removePulseEffect = (): void => {
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
pulseEntities.forEach(entity => viewer.entities.remove(entity));
|
||||
pulseEntities = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前脉冲实体数量
|
||||
*/
|
||||
const getPulseCount = (): number => {
|
||||
return pulseEntities.length;
|
||||
};
|
||||
|
||||
return {
|
||||
addPulseEffectToPoint,
|
||||
addPulseEffectToPoints,
|
||||
removePulseEffect,
|
||||
getPulseCount,
|
||||
};
|
||||
};
|
||||
@@ -4,15 +4,12 @@ import config from '@/config/config.json';
|
||||
import { useLeftLegendStore } from '@/stores/useLeftLegendStore';
|
||||
import { useScene } from '../useScene';
|
||||
import { useRainstormDeduction } from '../rainstorm/useRainstormDeduction';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore';
|
||||
|
||||
export const useRightHandle = () => {
|
||||
const statusStore = useStatusStore();
|
||||
const leftLegendStore = useLeftLegendStore();
|
||||
const scene = useScene();
|
||||
const rainstormDeduction = useRainstormDeduction();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
/**
|
||||
* 暴雨模拟
|
||||
* @param status - 状态
|
||||
@@ -28,12 +25,6 @@ export const useRightHandle = () => {
|
||||
|
||||
// 添加图例
|
||||
rainstormDeduction.addLegend();
|
||||
|
||||
// 如果有脉冲,显示脉冲
|
||||
CesiumUtilsSingleton.showPulseEffects();
|
||||
|
||||
// 模拟id状态为true
|
||||
simulationIdStore.status = true;
|
||||
} else {
|
||||
// 关闭暴雨模拟:隐藏降雨栅格图层
|
||||
statusStore.weatherLayers.showRainfallGrid.show = false;
|
||||
@@ -43,12 +34,6 @@ export const useRightHandle = () => {
|
||||
|
||||
// 隐藏步骤条
|
||||
statusStore.uiComponents.stepBar.show = false;
|
||||
|
||||
// 隐藏脉冲
|
||||
CesiumUtilsSingleton.hidePulseEffects();
|
||||
|
||||
// 模拟id状态为false
|
||||
simulationIdStore.status = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ export const useRiskPoint = () => {
|
||||
* 字段映射配置
|
||||
*/
|
||||
const field = {
|
||||
probability: '预测概率',
|
||||
riskName: '风险区名称',
|
||||
unitCode: '统一编号',
|
||||
housing: '住房(间)',
|
||||
@@ -28,13 +27,6 @@ export const useRiskPoint = () => {
|
||||
lat: '纬度',
|
||||
};
|
||||
|
||||
/**
|
||||
* 样式
|
||||
*/
|
||||
const style = {
|
||||
probability: { background: '#888888' },
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取风险点图标
|
||||
* @returns 图标路径
|
||||
@@ -43,5 +35,5 @@ export const useRiskPoint = () => {
|
||||
return riskAreaIcon;
|
||||
}
|
||||
|
||||
return { informationBoxTitle, field, style, getDisasterIcon };
|
||||
return { informationBoxTitle, field, getDisasterIcon };
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
debrisFlowIcon,
|
||||
flashFloodIcon,
|
||||
landslideIcon,
|
||||
collapseIcon,
|
||||
riskAreaIcon,
|
||||
waterLoggingIcon,
|
||||
hospitalIcon,
|
||||
@@ -200,18 +199,6 @@ export const useRainDisasterChain = () => {
|
||||
LoadingResource.FLASH_FLOOD_HIDDEN_POINT
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '崩塌隐患点',
|
||||
statusStore: statusStore.poiLayers,
|
||||
statusKey: 'showCollapseHiddenPoint' as const,
|
||||
callback: layerControl.clickCollapseHiddenPoint,
|
||||
link: collapseIcon,
|
||||
category: ControlPanelCategory.DISASTER_HAZARD,
|
||||
count: () =>
|
||||
resourceStore.getResourceCount(
|
||||
LoadingResource.COLLAPSE_HIDDEN_POINT
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '风险点',
|
||||
statusStore: statusStore.mapLayers,
|
||||
|
||||
@@ -16,7 +16,7 @@ export const useIndex = () => {
|
||||
{ title: '多灾种灾害链分析', name: 'index', query: { identification: 3 } },
|
||||
{ title: '灾害链情景推演', name: 'index', query: { identification: 4 } },
|
||||
{ title: '数据管理', name: 'dataManagement', query: { identification: 5 } },
|
||||
{ title: '文件管理', name: 'fileManagement', query: { identification: 6 } },
|
||||
{ title: '文件管理', name: 'index', query: { identification: 6 } },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useButtonSelectedIdStore } from '@/stores/useButtonSelectedIdStore';
|
||||
import { useLeftLegendStore } from '@/stores/useLeftLegendStore';
|
||||
import { useLoadingInformationStore } from '@/stores/useLoadingInformation';
|
||||
import { useSimulationIdStore } from '@/stores/useSimulationIdStore';
|
||||
import { useStatusStore } from '@/stores/useStatusStore';
|
||||
|
||||
export const useScene = () => {
|
||||
@@ -9,7 +8,6 @@ export const useScene = () => {
|
||||
const loadingInformationStore = useLoadingInformationStore();
|
||||
const leftLegendStore = useLeftLegendStore();
|
||||
const buttonSelectedIdStore = useButtonSelectedIdStore();
|
||||
const simulationIdStore = useSimulationIdStore();
|
||||
|
||||
// 重置场景
|
||||
const resetScene = () => {
|
||||
@@ -24,9 +22,6 @@ export const useScene = () => {
|
||||
|
||||
// 重置左侧图例
|
||||
leftLegendStore.resetLegendListInfo();
|
||||
|
||||
// 重置模拟id
|
||||
simulationIdStore.resetSimulationId();
|
||||
};
|
||||
|
||||
return { resetScene };
|
||||
|
||||
@@ -25,12 +25,6 @@ const router = createRouter({
|
||||
component: () =>
|
||||
import('@/views/home/data-management/DataManagementView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'file-management',
|
||||
name: 'fileManagement',
|
||||
component: () =>
|
||||
import('@/views/home/file-management/FileManagementView.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -114,14 +114,6 @@ export const useLoadingInformationStore = defineStore(
|
||||
id: -1,
|
||||
});
|
||||
|
||||
// ============================== 崩塌隐患点状态 ================================
|
||||
const collapseHiddenPoint = reactive({
|
||||
/** 加载状态 */
|
||||
loading: false,
|
||||
/** 崩塌隐患点ID */
|
||||
id: -1,
|
||||
});
|
||||
|
||||
// ============================== 泥石流隐患点状态 ================================
|
||||
const debrisFlowHiddenPoint = reactive({
|
||||
/** 加载状态 */
|
||||
@@ -198,10 +190,6 @@ export const useLoadingInformationStore = defineStore(
|
||||
landslideHiddenPoint.loading = false;
|
||||
landslideHiddenPoint.id = -1;
|
||||
|
||||
// 崩塌隐患点状态重置
|
||||
collapseHiddenPoint.loading = false;
|
||||
collapseHiddenPoint.id = -1;
|
||||
|
||||
// 泥石流隐患点状态重置
|
||||
debrisFlowHiddenPoint.loading = false;
|
||||
debrisFlowHiddenPoint.id = -1;
|
||||
@@ -228,7 +216,6 @@ export const useLoadingInformationStore = defineStore(
|
||||
reservoir,
|
||||
subwayStation,
|
||||
landslideHiddenPoint,
|
||||
collapseHiddenPoint,
|
||||
debrisFlowHiddenPoint,
|
||||
waterLoggingHiddenPoint,
|
||||
flashFloodHiddenPoint,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { type Ref, ref } from 'vue';
|
||||
|
||||
/**
|
||||
* 模拟暴雨、地震灾害链id
|
||||
*/
|
||||
export const useSimulationIdStore = defineStore('simulationId', () => {
|
||||
const status: Ref<boolean> = ref(false);
|
||||
const id: Ref<number> = ref(-1);
|
||||
|
||||
/**
|
||||
* 重置模拟id
|
||||
*/
|
||||
const resetSimulationId = () => {
|
||||
status.value = false;
|
||||
id.value = -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置id
|
||||
*/
|
||||
const setId = (value: number) => {
|
||||
status.value = true;
|
||||
id.value = value;
|
||||
};
|
||||
|
||||
return { id, status, resetSimulationId, setId };
|
||||
});
|
||||
@@ -118,11 +118,6 @@ export const useStatusStore = defineStore('status', () => {
|
||||
show: true,
|
||||
loading: true,
|
||||
},
|
||||
/** 显示崩塌隐患点 */
|
||||
showCollapseHiddenPoint: {
|
||||
show: true,
|
||||
loading: true,
|
||||
},
|
||||
/** 显示泥石流隐患点 */
|
||||
showDebrisFlowHiddenPoint: {
|
||||
show: true,
|
||||
@@ -200,6 +195,11 @@ export const useStatusStore = defineStore('status', () => {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 鼠标样式状态
|
||||
*/
|
||||
const cursorStyle = ref<string>('default');
|
||||
|
||||
/**
|
||||
* 恢复默认值
|
||||
*/
|
||||
@@ -283,10 +283,6 @@ export const useStatusStore = defineStore('status', () => {
|
||||
show: true,
|
||||
loading: true,
|
||||
};
|
||||
poiLayers.showCollapseHiddenPoint = {
|
||||
show: true,
|
||||
loading: true,
|
||||
};
|
||||
poiLayers.showDebrisFlowHiddenPoint = {
|
||||
show: true,
|
||||
loading: true,
|
||||
@@ -347,6 +343,7 @@ export const useStatusStore = defineStore('status', () => {
|
||||
infrastructureLayers,
|
||||
weatherLayers,
|
||||
functionStatus,
|
||||
cursorStyle,
|
||||
reset,
|
||||
resetScene,
|
||||
};
|
||||
|
||||
@@ -14,6 +14,4 @@ export interface Point {
|
||||
disasterType?: string;
|
||||
/** 名称 */
|
||||
name?: string;
|
||||
/** 预测概率 */
|
||||
probability?: string;
|
||||
}
|
||||
|
||||
@@ -75,3 +75,70 @@ export interface EntityOptions {
|
||||
/** 自定义属性(用于存储额外信息) */
|
||||
attributes?: Record<string, unknown>;
|
||||
}
|
||||
/**
|
||||
* 椭圆配置选项
|
||||
* 用于绘制椭圆形区域(圆形是椭圆的特例:semiMajorAxis = semiMinorAxis)
|
||||
*/
|
||||
export interface EllipseOptions {
|
||||
/** 椭圆中心位置 */
|
||||
position: Cartesian3;
|
||||
/** 半长轴(米) */
|
||||
semiMajorAxis: number;
|
||||
/** 半短轴(米) */
|
||||
semiMinorAxis: number;
|
||||
/** 旋转角度(弧度),默认0 */
|
||||
rotation?: number;
|
||||
/** 高度,默认0 */
|
||||
height?: number;
|
||||
/** 拉伸高度,默认0 */
|
||||
extrudedHeight?: number;
|
||||
/** 高度参考,默认CLAMP_TO_GROUND */
|
||||
heightReference?: HeightReference;
|
||||
/** 填充材质,默认白色 */
|
||||
material?: MaterialProperty | Color;
|
||||
/** 是否显示轮廓,默认false */
|
||||
outline?: boolean;
|
||||
/** 轮廓颜色,默认黑色 */
|
||||
outlineColor?: Color;
|
||||
/** 轮廓宽度,默认1 */
|
||||
outlineWidth?: number;
|
||||
/** 分段数(控制平滑度),默认128 */
|
||||
granularity?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 圆形区域配置选项(基于椭圆配置)
|
||||
* 用于周边分析功能的圆形绘制
|
||||
*/
|
||||
export interface CircleAnalysisOptions {
|
||||
/** 圆心位置 */
|
||||
position: Cartesian3;
|
||||
/** 半径(公里) */
|
||||
radiusKm: number;
|
||||
/** 填充颜色,默认半透明红色 */
|
||||
fillColor?: Color;
|
||||
/** 填充透明度,默认0.1 */
|
||||
fillAlpha?: number;
|
||||
/** 轮廓颜色,默认红色 */
|
||||
outlineColor?: Color;
|
||||
/** 轮廓透明度,默认0.9 */
|
||||
outlineAlpha?: number;
|
||||
/** 轮廓宽度,默认3 */
|
||||
outlineWidth?: number;
|
||||
/** 高度,默认0 */
|
||||
height?: number;
|
||||
/** 高度参考,默认CLAMP_TO_GROUND */
|
||||
heightReference?: HeightReference;
|
||||
}
|
||||
// ==================== Cesium Entity 类型扩展 ====================
|
||||
|
||||
/**
|
||||
* Cesium Entity 类型扩展
|
||||
* 为周边分析功能添加自定义属性
|
||||
*/
|
||||
declare module 'cesium' {
|
||||
interface Entity {
|
||||
/** 是否为分析圆形标记 */
|
||||
_isAnalysisCircle?: boolean;
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,6 @@ export enum DisasterType {
|
||||
export enum PointType {
|
||||
/** 滑坡 */
|
||||
LANDSLIDE = '滑坡',
|
||||
/** 崩塌 */
|
||||
COLLAPSE = '崩塌',
|
||||
/** 泥石流 */
|
||||
DEBRIS_FLOW = '泥石流',
|
||||
/** 内涝 */
|
||||
@@ -31,7 +29,6 @@ export enum PointType {
|
||||
*/
|
||||
export const HiddenDangerPointTypeMap: Record<PointType, string> = {
|
||||
[PointType.LANDSLIDE]: 'landslide',
|
||||
[PointType.COLLAPSE]: 'collapse',
|
||||
[PointType.DEBRIS_FLOW]: 'debris_flow',
|
||||
[PointType.WATER_LOGGING]: 'water_logging',
|
||||
[PointType.FLASH_FLOOD]: 'flash_flood',
|
||||
|
||||
@@ -47,11 +47,6 @@ export enum LoadingResource {
|
||||
*/
|
||||
LANDSLIDE_HIDDEN_POINT = 'LANDSLIDE_HIDDEN_POINT',
|
||||
|
||||
/**
|
||||
* 崩塌隐患点
|
||||
*/
|
||||
COLLAPSE_HIDDEN_POINT = 'COLLAPSE_HIDDEN_POINT',
|
||||
|
||||
/**
|
||||
* 泥石流隐患点
|
||||
*/
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* 预警列表
|
||||
*/
|
||||
export interface WarningList {
|
||||
probability: number;
|
||||
lon: number;
|
||||
lat: number;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import type { LoadingResource } from './LoadingResourceType';
|
||||
import type { Ref } from 'vue';
|
||||
/**
|
||||
* 周边分析组件相关类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 点资源分类类型
|
||||
*/
|
||||
export type PointResourceCategory =
|
||||
| 'school'
|
||||
| 'hospital'
|
||||
| 'danger'
|
||||
| 'shelter'
|
||||
| 'fire'
|
||||
| 'store'
|
||||
| 'hidden-danger'
|
||||
| 'risk-point'
|
||||
| 'bridge'
|
||||
| 'reservoir'
|
||||
| 'subway';
|
||||
|
||||
/**
|
||||
* 点资源数据结构
|
||||
*/
|
||||
export interface PointResource {
|
||||
/** 点ID */
|
||||
id: string | number;
|
||||
/** 显示值(名称) */
|
||||
value: string;
|
||||
/** 经度 */
|
||||
lon?: number;
|
||||
/** 纬度 */
|
||||
lat?: number;
|
||||
/** 资源分类 */
|
||||
category?: PointResourceCategory;
|
||||
/** 原始类型(用于隐患点子类型区分) */
|
||||
originalType?: string;
|
||||
/** 其他任意属性 */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源配置接口(用于 SearchComponent)
|
||||
*/
|
||||
export interface ResourceConfig {
|
||||
/** 加载资源键 */
|
||||
key: LoadingResource;
|
||||
/** 资源分类 */
|
||||
category: PointResourceCategory;
|
||||
/** 强制类型(用于隐患点) */
|
||||
forcedType?: string;
|
||||
/** 是否可见的判断函数 */
|
||||
isVisible: () => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源配置接口(用于 ButtonComponent,不含 isVisible)
|
||||
*/
|
||||
export interface ButtonResourceConfig {
|
||||
/** 加载资源键 */
|
||||
key: LoadingResource;
|
||||
/** 资源分类 */
|
||||
category: PointResourceCategory;
|
||||
/** 强制类型(用于隐患点) */
|
||||
forcedType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析按钮配置接口
|
||||
*/
|
||||
export interface AnalysisButtonConfig {
|
||||
/** 按钮默认名称 */
|
||||
name: string;
|
||||
/** 按钮激活时的名称(可选) */
|
||||
activeName?: string;
|
||||
/** 按钮点击回调函数 */
|
||||
callback: (status: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框位置接口
|
||||
*/
|
||||
export interface DialogPosition {
|
||||
/** X 坐标 */
|
||||
x: number;
|
||||
/** Y 坐标 */
|
||||
y: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 周边分析按钮状态接口(用于 provide/inject 共享)
|
||||
* 注意:响应式属性使用 Ref 类型
|
||||
*/
|
||||
export interface AnalysisButtonState {
|
||||
/** 当前选中的按钮索引 */
|
||||
selectedButtonIndex: Ref<number>;
|
||||
/** 是否显示区域选择弹窗 */
|
||||
showAreaDialog: Ref<boolean>;
|
||||
/** 区域半径(公里) */
|
||||
radius: Ref<number>;
|
||||
/** 弹窗位置 */
|
||||
dialogPosition: DialogPosition;
|
||||
/** 分析按钮配置列表 */
|
||||
analysisButtons: AnalysisButtonConfig[];
|
||||
/** 按钮点击处理函数 */
|
||||
handleButtonClick: (index: number, callback: (status: boolean) => void) => void;
|
||||
/** 确认添加区域分析 */
|
||||
handleConfirm: () => void;
|
||||
/** 取消区域分析 */
|
||||
handleCancel: () => void;
|
||||
/** 刷新脉冲效果 */
|
||||
refreshPulseEffect: () => void;
|
||||
/** 脉冲点列表 */
|
||||
pulsePoints: Ref<PointResource[]>;
|
||||
/** 是否显示脉冲点列表 */
|
||||
showPulsePointList: Ref<boolean>;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 降雨预测请求值
|
||||
*/
|
||||
export interface RainPredictRequest {
|
||||
/** 灾害名称 */
|
||||
disaster_name: string;
|
||||
/** id列表 */
|
||||
point_ids?: number[];
|
||||
/** 行政区划代码 */
|
||||
region_code?: string;
|
||||
/** 累计降雨量 */
|
||||
rainfall?: number;
|
||||
/** 持续时间 */
|
||||
duration?: number;
|
||||
/** 发生时间 */
|
||||
occurred_time?: string;
|
||||
/** 操作类型 */
|
||||
operation_type?: string;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { WarningList } from '../common/WarningList';
|
||||
|
||||
export interface RainPredictResponse {
|
||||
record_id: number;
|
||||
list: Record<string, WarningList>;
|
||||
}
|
||||
@@ -20,12 +20,6 @@ import {
|
||||
SceneTransforms,
|
||||
Rectangle,
|
||||
Color,
|
||||
JulianDate,
|
||||
CallbackProperty,
|
||||
ConstantProperty,
|
||||
HeightReference,
|
||||
VerticalOrigin,
|
||||
HorizontalOrigin,
|
||||
} from 'cesium';
|
||||
import { CesiumViewerManager } from './CesiumViewerManager';
|
||||
import { EntityManager } from './EntityManager';
|
||||
@@ -35,7 +29,6 @@ import { GeoJsonManager, type ClearType } from './GeoJsonManager';
|
||||
import { CameraController } from './CameraController';
|
||||
import config from '@/config/config.json';
|
||||
import type { ClickObject } from '@/types/cesium/ClickObject';
|
||||
import type { WarningList } from '@/types/common/WarningList';
|
||||
|
||||
// 导出 ClearType 类型
|
||||
export type { ClearType };
|
||||
@@ -55,12 +48,6 @@ export class CesiumUtils {
|
||||
// 颜色缓存
|
||||
#colorCache = new Map<string, Color>();
|
||||
|
||||
// 脉冲相关状态
|
||||
#pulseMap: Record<string, { pulseId: string; probability: number }> = {};
|
||||
#maxPulseRadius = 30;
|
||||
#pulseDuration = 5;
|
||||
#pulseCircleImage: string | null = null;
|
||||
|
||||
constructor() {
|
||||
this.#viewerManager = new CesiumViewerManager();
|
||||
}
|
||||
@@ -509,11 +496,10 @@ export class CesiumUtils {
|
||||
clickLayer(callback: (pickedObject: ClickObject) => void) {
|
||||
const handler = new ScreenSpaceEventHandler(this.getViewer()?.scene.canvas);
|
||||
handler.setInputAction((clickEvent: { position: Cartesian2 }) => {
|
||||
const viewer = CesiumUtilsSingleton.getViewer();
|
||||
if (!viewer) return;
|
||||
const pickedList = viewer.scene.drillPick(clickEvent.position);
|
||||
// 跳过脉冲实体,取第一个非脉冲实体
|
||||
const pickedObject = pickedList.find((p) => !p.id?.properties?.pulseKey);
|
||||
// 在点击位置进行拾取
|
||||
const pickedObject = CesiumUtilsSingleton.getViewer()?.scene.pick(
|
||||
clickEvent.position
|
||||
);
|
||||
callback(pickedObject);
|
||||
}, ScreenSpaceEventType.LEFT_CLICK);
|
||||
}
|
||||
@@ -553,7 +539,6 @@ export class CesiumUtils {
|
||||
this.clearAllPrimitives(clearType);
|
||||
this.clearAllLayers(clearType);
|
||||
this.clearAllGeoJsonLayers(clearType);
|
||||
this.removeAllPulses();
|
||||
}
|
||||
|
||||
// ===================== getter 和 setter函数 =====================
|
||||
@@ -628,85 +613,6 @@ export class CesiumUtils {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加脉冲效果
|
||||
* @param list 点列表
|
||||
* @returns
|
||||
*/
|
||||
addPulseEffect(list: Record<string, WarningList>) {
|
||||
const viewer = this.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
// 移除已有脉冲
|
||||
this.removeAllPulses();
|
||||
|
||||
// 生成圆形贴图
|
||||
if (!this.#pulseCircleImage) {
|
||||
this.#pulseCircleImage = this.#createCircleImage(this.#maxPulseRadius);
|
||||
}
|
||||
|
||||
for (const [disasterType, warning] of Object.entries(list)) {
|
||||
const { lon, lat, probability } = warning;
|
||||
|
||||
// 根据概率确定颜色
|
||||
let color: Color;
|
||||
if (probability >= 0.7) {
|
||||
color = Color.RED;
|
||||
} else if (probability >= 0.5) {
|
||||
color = Color.YELLOW;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = `${disasterType}_${lon}_${lat}`;
|
||||
const pulseId = `PULSE_${key}_${Date.now()}`;
|
||||
|
||||
this.#createPulseCircle(pulseId, key, lon, lat, color);
|
||||
this.#pulseMap[key] = { pulseId, probability };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有脉冲实体
|
||||
*/
|
||||
removeAllPulses(): void {
|
||||
for (const key of Object.keys(this.#pulseMap)) {
|
||||
this.#deletePulseEntity(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏所有脉冲效果
|
||||
*/
|
||||
hidePulseEffects(): void {
|
||||
const viewer = this.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
for (const key of Object.keys(this.#pulseMap)) {
|
||||
const entry = this.#pulseMap[key];
|
||||
const entity = viewer.entities.getById(entry.pulseId);
|
||||
if (entity && entity.billboard) {
|
||||
entity.billboard.show = new ConstantProperty(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示所有脉冲效果
|
||||
*/
|
||||
showPulseEffects(): void {
|
||||
const viewer = this.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
for (const key of Object.keys(this.#pulseMap)) {
|
||||
const entry = this.#pulseMap[key];
|
||||
const entity = viewer.entities.getById(entry.pulseId);
|
||||
if (entity && entity.billboard) {
|
||||
entity.billboard.show = new ConstantProperty(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 私有方法 =====================
|
||||
|
||||
/**
|
||||
@@ -719,97 +625,6 @@ export class CesiumUtils {
|
||||
throw new Error(`${managerName} 未初始化,请先调用 initCesiumViewer()`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成脉冲圆形贴图
|
||||
*/
|
||||
#createCircleImage(maxRadius: number): string {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = maxRadius * 2;
|
||||
canvas.height = maxRadius * 2;
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.beginPath();
|
||||
ctx.arc(maxRadius, maxRadius, maxRadius, 0, Math.PI * 2, false);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.7)';
|
||||
ctx.fill();
|
||||
return canvas.toDataURL('image/png');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建脉冲实体
|
||||
* @param pulseId - 脉冲实体 ID
|
||||
* @param lon - 经度
|
||||
* @param lat - 纬度
|
||||
* @param color - 颜色
|
||||
*/
|
||||
#createPulseCircle(
|
||||
pulseId: string,
|
||||
key: string,
|
||||
lon: number,
|
||||
lat: number,
|
||||
color: Color
|
||||
): void {
|
||||
const viewer = this.getViewer();
|
||||
if (!viewer || !this.#pulseCircleImage) return;
|
||||
|
||||
const startTime = JulianDate.now();
|
||||
const maxRadius = this.#maxPulseRadius;
|
||||
const duration = this.#pulseDuration;
|
||||
|
||||
viewer.entities.add({
|
||||
id: pulseId,
|
||||
position: Cartesian3.fromDegrees(lon, lat),
|
||||
properties: { pulseKey: key },
|
||||
billboard: {
|
||||
image: this.#pulseCircleImage,
|
||||
width: new CallbackProperty((time) => {
|
||||
const elapsed =
|
||||
JulianDate.secondsDifference(time, startTime) % duration;
|
||||
const progress = elapsed / duration;
|
||||
return maxRadius * 2 * Math.abs(Math.sin(progress * Math.PI));
|
||||
}, false),
|
||||
height: new CallbackProperty((time) => {
|
||||
const elapsed =
|
||||
JulianDate.secondsDifference(time, startTime) % duration;
|
||||
const progress = elapsed / duration;
|
||||
return maxRadius * 2 * Math.abs(Math.sin(progress * Math.PI));
|
||||
}, false),
|
||||
color: new CallbackProperty((time) => {
|
||||
const elapsed =
|
||||
JulianDate.secondsDifference(time, startTime) % duration;
|
||||
const progress = elapsed / duration;
|
||||
const alpha = 0.7 * (1 - progress);
|
||||
return color.withAlpha(alpha);
|
||||
}, false),
|
||||
heightReference: HeightReference.CLAMP_TO_GROUND,
|
||||
verticalOrigin: VerticalOrigin.CENTER,
|
||||
horizontalOrigin: HorizontalOrigin.CENTER,
|
||||
eyeOffset: new Cartesian3(0.0, 0.0, -10.0),
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除单个脉冲实体
|
||||
* @param key - 脉冲映射 key
|
||||
*/
|
||||
#deletePulseEntity(key: string): void {
|
||||
const viewer = this.getViewer();
|
||||
if (!viewer) return;
|
||||
|
||||
const entry = this.#pulseMap[key];
|
||||
if (!entry) return;
|
||||
|
||||
const entity = viewer.entities.getById(entry.pulseId);
|
||||
if (entity) {
|
||||
viewer.entities.remove(entity);
|
||||
}
|
||||
delete this.#pulseMap[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<!-- 文件管理 -->
|
||||
<template>
|
||||
<!-- 搜索组件 -->
|
||||
<SearchComponent />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SearchComponent from '@/component/file-management/SearchComponent.vue';
|
||||
import { useStatusStore } from '@/stores/useStatusStore';
|
||||
|
||||
const statusStore = useStatusStore()
|
||||
|
||||
// 加载完成
|
||||
statusStore.appLoadingCompleted = true;
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user