2 Commits

Author SHA1 Message Date
wzy-warehouse c5621acdce 添加文件管理基础组件和路由 2026-06-24 09:53:17 +08:00
wzy-warehouse 747d836ba5 测试迁移效果 2026-06-17 20:54:27 +08:00
29 changed files with 122 additions and 2018 deletions
+1
View File
@@ -1,5 +1,6 @@
# xian_vue_new # xian_vue_new
西安项目前端 西安项目前端
# xian_vue_new # xian_vue_new
## 开发工具说明 ## 开发工具说明
+2 -5
View File
@@ -17,21 +17,18 @@
"@types/spark-md5": "^3.0.5", "@types/spark-md5": "^3.0.5",
"axios": "^1.12.2", "axios": "^1.12.2",
"cesium": "1.101.0", "cesium": "1.101.0",
"docx": "^9.7.1",
"element-plus": "^2.13.6", "element-plus": "^2.13.6",
"file-saver": "^2.0.5",
"gm-crypto": "^0.1.12", "gm-crypto": "^0.1.12",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"proj4": "^2.20.8", "proj4": "^2.20.8",
"sockjs-client": "^1.6.1", "sockjs-client": "^1.6.1",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"vite-plugin-cesium": "^1.2.22", "vite-plugin-cesium": "^1.2.22",
"vue": "^3.5.35", "vue": "^3.5.32",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/node22": "^22.0.2", "@tsconfig/node22": "^22.0.2",
"@types/file-saver": "^2.0.7",
"@types/node": "^24.12.2", "@types/node": "^24.12.2",
"@types/proj4": "^2.19.0", "@types/proj4": "^2.19.0",
"@types/sockjs-client": "^1.5.4", "@types/sockjs-client": "^1.5.4",
@@ -52,4 +49,4 @@
"vite-plugin-vue-devtools": "^8.0.3", "vite-plugin-vue-devtools": "^8.0.3",
"vue-tsc": "^3.2.6" "vue-tsc": "^3.2.6"
} }
} }
@@ -0,0 +1,9 @@
<template>
<div></div>
</template>
<script lang="ts" setup></script>
<style scoped>
</style>
@@ -101,7 +101,7 @@
import DebrisFlowComponent from '@/component/rain-earthquake/basic/DebrisFlowComponent.vue'; import DebrisFlowComponent from '@/component/rain-earthquake/basic/DebrisFlowComponent.vue';
import WaterLoggingComponent from '@/component/rain-earthquake/basic/WaterLoggingComponent.vue'; import WaterLoggingComponent from '@/component/rain-earthquake/basic/WaterLoggingComponent.vue';
import FlashFloodComponent from '@/component/rain-earthquake/basic/FlashFloodComponent.vue'; import FlashFloodComponent from '@/component/rain-earthquake/basic/FlashFloodComponent.vue';
import RainfallGridComponent from '@/component/rain-earthquake/detail-panels/RainfallGridComponent.vue'; import RainfallGridComponent from '@/component/rain/RainfallAutomaticSimulationComponent.vue';
import { useStatusStore } from '@/stores/useStatusStore'; import { useStatusStore } from '@/stores/useStatusStore';
const statusStore = useStatusStore(); const statusStore = useStatusStore();
@@ -17,7 +17,7 @@
v-show="statusStore.uiComponents.disasterChainPointShow.show" v-show="statusStore.uiComponents.disasterChainPointShow.show"
> >
<header class="table-title"> <header class="table-title">
<span>灾害链影响点列表({{ props.totalDataCount ?? props.tableDataList.length }})</span> <span>灾害链影响点列表</span>
</header> </header>
<!-- 搜索 --> <!-- 搜索 -->
@@ -29,36 +29,29 @@
/> />
<el-select v-model="conditions.hiddenPoint" class="search-hidden-point"> <el-select v-model="conditions.hiddenPoint" class="search-hidden-point">
<el-option <el-option
v-for="(opt, index) in selectOptions" v-for="(opt, index) in selectOptions"
:key="index" :key="index"
:label="opt.value === PointType.RISK_AREA || Object.values(InfrastructurePointType).includes(opt.value as InfrastructurePointType) ? opt.label : `${opt.label}预警点`" :label="`${opt.label}预警点`"
:value="opt.value" :value="opt.value"
/> />
</el-select> </el-select>
</div> </div>
<!-- 表格 --> <!-- 表格 -->
<div class="table-box"> <div class="table-box">
<el-table <el-table
:data="paginatedData" :data="tableDataList"
border border
style="width: 100%" style="width: 100%"
empty-text="暂无数据" empty-text="暂无数据"
> >
<el-table-column <el-table-column
v-for="(row, index) in tableColumns" v-for="(row, index) in tableColumns"
:key="index" :key="index"
:prop="row.key" :prop="row.key"
:label=" :label="
index == 0 ? `${conditions.hiddenPoint}${row.title}` : row.title index == 0 ? `${conditions.hiddenPoint}${row.title}` : row.title
" "
> />
<template #default="scope" v-if="row.key === 'position'">
<div class="position-cell">
<div>{{ scope.row.lon?.toFixed(4) }},</div>
<div>{{ scope.row.lat?.toFixed(4) }}</div>
</div>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
@@ -88,7 +81,6 @@
import { useStatusStore } from '@/stores/useStatusStore'; import { useStatusStore } from '@/stores/useStatusStore';
import type { Point } from '@/types/base/Point'; import type { Point } from '@/types/base/Point';
import { PointType } from '@/types/common/DisasterType'; import { PointType } from '@/types/common/DisasterType';
import { InfrastructurePointType } from '@/types/common/InfrastructurePointType';
import type { PaginationType } from '@/types/common/PaginationType'; import type { PaginationType } from '@/types/common/PaginationType';
import { ref, watch, computed, type Ref } from 'vue'; import { ref, watch, computed, type Ref } from 'vue';
@@ -96,37 +88,29 @@
// 接收父组件的参数 // 接收父组件的参数
const props = defineProps<{ const props = defineProps<{
selectOptions: { label: string; value: PointType | InfrastructurePointType }[]; selectOptions: { label: string; value: PointType }[];
tableDataList: Point[]; tableDataList: Point[];
tableColumns: { title: string; key: string }[]; tableColumns: { title: string; key: string }[];
pageOption: PaginationType; pageOption: PaginationType;
totalDataCount?: number; // 所有数据的总数
}>(); }>();
// 接收父组件方法 // 接收父组件方法
const emits = defineEmits<{ const emits = defineEmits<{
( (
e: 'changeConditions', e: 'changeConditions',
value: { tableData: string; hiddenPoint: PointType | InfrastructurePointType } value: { tableData: string; hiddenPoint: PointType }
): void; ): void;
(e: 'changeCurrentPage', value: number): void; (e: 'changeCurrentPage', value: number): void;
}>(); }>();
// 搜索条件 // 搜索条件
const conditions: Ref<{ tableData: string; hiddenPoint: PointType | InfrastructurePointType }> = ref({ const conditions: Ref<{ tableData: string; hiddenPoint: PointType }> = ref({
tableData: '', tableData: '',
hiddenPoint: PointType.LANDSLIDE, hiddenPoint: PointType.LANDSLIDE,
}); });
// ==================== 计算属性 ==================== // ==================== 计算属性 ====================
// 分页后的数据
const paginatedData = computed(() => {
const start = (props.pageOption.currentPage - 1) * props.pageOption.pageSize;
const end = start + props.pageOption.pageSize;
return props.tableDataList.slice(start, end);
});
// 是否显示分页 // 是否显示分页
const showPagination = computed(() => props.pageOption.totalPage !== 0); const showPagination = computed(() => props.pageOption.totalPage !== 0);
@@ -232,10 +216,7 @@
:deep(.el-select__selected-item) { :deep(.el-select__selected-item) {
color: white; color: white;
} }
/* 减小表格行的上下间距 */
:deep(.el-table .el-table__cell) {
padding: 6px 0;
}
:deep(.el-table--border th) { :deep(.el-table--border th) {
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
@@ -246,7 +227,7 @@
border: 1px solid rgba(255, 255, 255, 0.3); border: 1px solid rgba(255, 255, 255, 0.3);
padding: 10px 12px; padding: 10px 12px;
text-align: center; text-align: center;
font-size: 15px; font-size: 14px;
font-weight: bold; font-weight: bold;
} }
:deep(.el-table tr), :deep(.el-table tr),
@@ -286,27 +267,4 @@
cursor: pointer; cursor: pointer;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
/* 名称列样式:一行显示,超出隐藏 */ </style>
:deep(.el-table .el-table__row td:first-child .cell) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
/* 鼠标悬浮时显示完整名称 */
:deep(.el-table .el-table__row td:first-child:hover .cell) {
white-space: normal;
overflow: visible;
text-overflow: unset;
word-break: break-all;
}
/* 位置列样式:经纬度两行显示 */
.position-cell {
display: flex;
flex-direction: column;
align-items: center;
line-height: 1.5;
}
</style>
@@ -1,151 +1,26 @@
<template> <template>
<div <div
class="around-analysis-box" class="around-analysis-box"
v-show="statusStore.functionStatus.aroundAnalysis.show" v-show="statusStore.functionStatus.aroundAnalysis.show"
> >
<!-- 搜索组件 --> <!-- 搜索组件 -->
<SearchComponent /> <SearchComponent />
<!-- 按钮组件 --> <!-- 按钮组件 -->
<ButtonComponent /> <ButtonComponent />
</div>
<!-- 区域选择对话框fixed定位相对于视口 --> <!-- 具体功能组件 -->
<div v-if="aroundAnalysisState.showAreaDialog.value" class="search-area-dialog" :style="{ left: aroundAnalysisState.dialogPosition.x + 'px', top: aroundAnalysisState.dialogPosition.y + 'px' }"> <AroundAnalysisDetailComponent />
<div class="dialog-header">选择区域</div>
<div class="dialog-content">
<div class="radius-input-group">
<span class="label">半径</span>
<input
v-model.number="aroundAnalysisState.radius.value"
type="number"
class="radius-input"
min="1"
max="100"
/>
<span class="unit">公里</span>
</div>
</div>
<div class="dialog-footer">
<button class="confirm-btn" @click="aroundAnalysisState.handleConfirm">确认添加</button>
<button class="cancel-btn" @click="aroundAnalysisState.handleCancel">取消</button>
</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject } from 'vue'; import { useStatusStore } from '@/stores/useStatusStore';
import { useStatusStore } from '@/stores/useStatusStore'; import AroundAnalysisDetailComponent from './around-analysis/AroundAnalysisDetailComponent.vue';
import type { AroundAnalysisState } from '@/types/common/useAroundAnalysisType'; import ButtonComponent from './around-analysis/ButtonComponent.vue';
import ButtonComponent from './around-analysis/ButtonComponent.vue'; import SearchComponent from './around-analysis/SearchComponent.vue';
import SearchComponent from './around-analysis/SearchComponent.vue';
const statusStore = useStatusStore(); const statusStore = useStatusStore();
// 通过 inject 获取父组件(RainstormView.vue)提供的实例
const aroundAnalysisState = inject<AroundAnalysisState>('aroundAnalysisState')!;
</script> </script>
<style scoped> <style scoped></style>
/* 搜索触发的区域选择对话框样式(fixed定位,相对于视口) */
.search-area-dialog {
position: fixed;
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;
}
.search-area-dialog .dialog-header {
padding: 8px 12px;
font-size: 16px;
font-weight: bold;
text-align: center;
color: white;
background: linear-gradient(90deg, #00b4ff, #0080cc);
}
.search-area-dialog .dialog-content {
padding: 10px 6px;
display: flex;
justify-content: center;
align-items: center;
}
.search-area-dialog .radius-input-group {
display: flex;
align-items: center;
gap: 6px;
}
.search-area-dialog .radius-input-group .label,
.search-area-dialog .radius-input-group .unit {
font-size: 14px;
color: white;
}
.search-area-dialog .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;
}
.search-area-dialog .radius-input::-webkit-inner-spin-button,
.search-area-dialog .radius-input::-webkit-outer-spin-button {
opacity: 0;
-webkit-appearance: none;
appearance: none;
margin: 0;
}
.search-area-dialog .dialog-footer {
display: flex;
justify-content: center;
gap: 8px;
padding: 10px 10px 8px;
}
.search-area-dialog .confirm-btn,
.search-area-dialog .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;
}
.search-area-dialog .confirm-btn {
background: linear-gradient(180deg, #2d8a4e, #1e6b3a);
border: 1px solid #3da862;
}
.search-area-dialog .confirm-btn:hover {
background: linear-gradient(180deg, #3da862, #2d8a4e);
box-shadow: 0 2px 8px rgba(45, 138, 78, 0.5);
}
.search-area-dialog .cancel-btn {
background: linear-gradient(180deg, #c0392b, #96281b);
border: 1px solid #e74c3c;
}
.search-area-dialog .cancel-btn:hover {
background: linear-gradient(180deg, #e74c3c, #c0392b);
box-shadow: 0 2px 8px rgba(192, 57, 43, 0.5);
}
</style>
@@ -0,0 +1,7 @@
<template>
<div></div>
</template>
<script lang="ts" setup></script>
<style scoped></style>
@@ -1,71 +1,7 @@
<!-- 按钮组件 -->
<template> <template>
<div class="analysis-button-box"> <div></div>
<ul class="analysis-button-ul">
<li v-for="(buttonItem, index) in aroundAnalysisState.analysisButtons" :key="index">
<button
@click="aroundAnalysisState.handleButtonClick(index, buttonItem.callback)"
:style="{
'background-image': `url(${aroundAnalysisState.selectedButtonIndex.value === index ? rightOrangeButton : rightBlueButton})`,
}"
>
{{ aroundAnalysisState.selectedButtonIndex.value === index && buttonItem.activeName ? buttonItem.activeName : buttonItem.name }}
</button>
</li>
</ul>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup></script>
import { inject } from 'vue';
import { rightBlueButton, rightOrangeButton } from '@/assets';
import type { AroundAnalysisState } from '@/types/common/useAroundAnalysisType';
// 从父组件注入共享的 Hook 实例 <style scoped></style>
const aroundAnalysisState = inject<AroundAnalysisState>('aroundAnalysisState')!;
</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>
@@ -1,96 +1,10 @@
<!-- 搜索组件 --> <!-- 搜索组件 -->
<template> <template>
<div class="search-component-box"> <div>
<el-autocomplete 搜索
v-model="aroundAnalysisState.searchState.value"
:fetch-suggestions="aroundAnalysisState.querySearch"
popper-class="my-autocomplete"
placeholder="搜索地点"
@select="aroundAnalysisState.handleSelect"
@focus="aroundAnalysisState.handleFocus"
clearable
:disabled="!aroundAnalysisState.canSearch.value"
: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> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts"></script>
import { inject } from 'vue';
import { Edit } from '@element-plus/icons-vue';
import type { AroundAnalysisState } from '@/types/common/useAroundAnalysisType';
// 从父组件注入共享的 Hook 实例 <style scoped></style>
const aroundAnalysisState = inject<AroundAnalysisState>('aroundAnalysisState')!;
</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>
@@ -4,16 +4,16 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { $api } from '@/api/api'; import { $api } from '@/api/api.ts';
import { useRainstormDeduction } from '@/hooks/rainstorm/useRainstormDeduction'; import { useRainstormDeduction } from '@/hooks/rainstorm/useRainstormDeduction.ts';
import { useSimulationIdStore } from '@/stores/useSimulationIdStore'; import { useSimulationIdStore } from '@/stores/useSimulationIdStore.ts';
import { useStatusStore } from '@/stores/useStatusStore'; import { useStatusStore } from '@/stores/useStatusStore.ts';
import { useStepStore } from '@/stores/useStepStore'; import { useStepStore } from '@/stores/useStepStore.ts';
import type { ApiResponse } from '@/types/ApiResponse'; import type { ApiResponse } from '@/types/ApiResponse.ts';
import type { RainfallGridResponse } from '@/types/rainstorm/RainfallGridResponse'; import type { RainfallGridResponse } from '@/types/rainstorm/RainfallGridResponse.ts';
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils'; import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils.ts';
import { WebSocketService } from '@/utils/request/websocket'; import { WebSocketService } from '@/utils/request/websocket.ts';
import { Utils } from '@/utils/utils'; import { Utils } from '@/utils/utils.ts';
import { onMounted, onUnmounted, watch } from 'vue'; import { onMounted, onUnmounted, watch } from 'vue';
let rainfallWsService: WebSocketService | null = null; let rainfallWsService: WebSocketService | null = null;
@@ -1,348 +0,0 @@
import { ref, reactive, onUnmounted, watch, computed } from 'vue';
import { useStatusStore } from '@/stores/useStatusStore';
import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore';
import { useAroundAnalysisConfig } from './useAroundAnalysisConfig';
import type { PointResource, PointResourceCategory, AnalysisButtonConfig, AroundAnalysisState } from '@/types/common/useAroundAnalysisType';
import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils';
import { isCategoryVisible, loadAllPointData, calculateDistance } from '@/utils/aroundAnalysisUtils';
import {
ScreenSpaceEventHandler,
ScreenSpaceEventType,
Cartesian2,
Cartographic,
Cartesian3,
} from 'cesium';
import { useCircleDrawer } from './useCircleDrawer';
import { usePulseEffect } from './usePulseEffect';
import { useMarkerManager } from './useMarkerManager';
/**
* 周边分析统一 Hook(合并按钮和搜索逻辑)
*/
export const useAroundAnalysis = (): AroundAnalysisState => {
const statusStore = useStatusStore();
const { resourceConfigs, MIN_FLY_HEIGHT, FLY_HEIGHT_MULTIPLIER, FLY_DURATION } = useAroundAnalysisConfig();
// ==================== 响应式状态 ====================
const selectedButtonIndex = ref<number>(-1);
const showAreaDialog = ref(false);
const radius = ref(10);
const dialogPosition = reactive({ x: 0, y: 0 });
const pulsePoints = ref<PointResource[]>([]);
const showPulsePointList = ref(false);
const searchState = ref('');
const canSearch = computed(() => {
return resourceConfigs.value.some(config => isCategoryVisible(config.category, config.forcedType));
});
let clickHandler: ScreenSpaceEventHandler | null = null;
let currentCenterPosition: Cartesian3 | null = null;
// ==================== 组合子 Hook ====================
const { drawCircle, clearCircle } = useCircleDrawer();
const { addPulseEffectToPoints, removePulseEffect } = usePulseEffect();
const { addMarker, removeMarker } = useMarkerManager();
// ==================== 核心功能 ====================
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(resourceConfigs.value);
const radiusMeters = radiusKm * 1000;
const filteredPoints = 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 coordMap = new Map<string, PointResource>();
filteredPoints.forEach(point => {
const coordKey = `${point.lon},${point.lat}`;
if (!coordMap.has(coordKey)) {
coordMap.set(coordKey, point);
}
});
return Array.from(coordMap.values());
};
const refreshPulseEffect = () => {
if (!currentCenterPosition) return;
removePulseEffect();
const pointsInCircle = getPointsInCircle(currentCenterPosition, radius.value);
addPulseEffectToPoints(pointsInCircle);
pulsePoints.value = pointsInCircle;
showPulsePointList.value = true;
};
const clearAllAnalysisResources = () => {
removeMarker();
clearCircle();
removePulseEffect();
currentCenterPosition = null;
pulsePoints.value = [];
showPulsePointList.value = false;
};
const clearVisualEffectsOnly = () => {
clearCircle();
removePulseEffect();
pulsePoints.value = [];
showPulsePointList.value = false;
};
// ==================== 地图事件 ====================
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);
console.log('点击位置:', {
longitude: cartographic.longitude * (180 / Math.PI),
latitude: cartographic.latitude * (180 / Math.PI)
});
addMarker(cartesian);
showAreaDialog.value = true;
calculateDialogPosition(clickEvent.position);
}
}, ScreenSpaceEventType.LEFT_CLICK);
};
const removeMapClickHandler = () => {
if (clickHandler) {
clickHandler.destroy();
clickHandler = null;
}
};
const calculateDialogPosition = (clickPosition: Cartesian2) => {
const { innerWidth: screenWidth, innerHeight: screenHeight } = window;
const { DIALOG_WIDTH: dialogWidth, DIALOG_HEIGHT: dialogHeight, DIALOG_PADDING: dialogPadding, DIALOG_OFFSET: dialogOffset } = useAroundAnalysisConfig().getConstants();
let x = clickPosition.x + dialogOffset;
let y = clickPosition.y + dialogOffset;
if (x + dialogWidth > screenWidth - dialogPadding) {
x = clickPosition.x - dialogWidth - dialogOffset;
}
if (y + dialogHeight > screenHeight - dialogPadding) {
y = clickPosition.y - dialogHeight - dialogOffset;
}
dialogPosition.x = Math.max(dialogPadding, Math.min(x, screenWidth - dialogWidth - dialogPadding));
dialogPosition.y = Math.max(dialogPadding, Math.min(y, screenHeight - dialogHeight - dialogPadding));
};
// ==================== 事件处理 ====================
const handleConfirm = () => {
if (!currentCenterPosition) {
console.error('中心点位置不存在');
return;
}
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;
removeMapClickHandler();
const viewer = CesiumUtilsSingleton.getViewer();
if (viewer?.canvas) {
statusStore.cursorStyle = 'default';
viewer.canvas.style.cursor = 'default';
}
};
const handleCancel = () => {
showAreaDialog.value = false;
clearAllAnalysisResources();
};
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);
}
};
const startAreaAnalysisFromSearch = (point: PointResource) => {
if (point.lon == null || point.lat == null) return;
clearAllAnalysisResources();
currentCenterPosition = Cartesian3.fromDegrees(point.lon, point.lat, 0);
selectedButtonIndex.value = 0;
const viewer = CesiumUtilsSingleton.getViewer();
if (viewer?.canvas) {
statusStore.cursorStyle = 'crosshair';
viewer.canvas.style.cursor = 'crosshair';
}
addMarker(currentCenterPosition);
showAreaDialog.value = true;
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const { DIALOG_WIDTH: dialogWidth, DIALOG_HEIGHT: dialogHeight, DIALOG_PADDING: dialogPadding, DIALOG_OFFSET: dialogOffset } = useAroundAnalysisConfig().getConstants();
dialogPosition.x = Math.max(dialogPadding, Math.min(centerX + dialogOffset, window.innerWidth - dialogWidth - dialogPadding));
dialogPosition.y = Math.max(dialogPadding, Math.min(centerY + dialogOffset, window.innerHeight - dialogHeight - dialogPadding));
};
// ==================== 搜索功能 ====================
const querySearch = (queryString: string, cb: (results: PointResource[]) => void) => {
if (!canSearch.value) {
cb([]);
return;
}
const lowerQuery = queryString.toLowerCase();
const allResources = loadAllPointData(resourceConfigs.value);
const filteredResults = allResources.filter(item => {
const config = resourceConfigs.value.find(c => c.category === item.category);
let isVisible = false;
if (config) {
isVisible = isCategoryVisible(config.category, config.forcedType || item.originalType);
}
if (!isVisible) return false;
if (!queryString) return true;
return (item.value || '').toLowerCase().includes(lowerQuery);
});
cb(filteredResults);
};
const handleSelect = async (item: PointResource) => {
if (item.lon == null || item.lat == null) return;
await CesiumUtilsSingleton.flyToTarget([item.lon, item.lat, 6000]);
startAreaAnalysisFromSearch(item);
};
const handleFocus = () => {
// 触发数据刷新(如果需要)
};
// ==================== 监听器 ====================
watch(
() => useLoadingResourceStore().loadingResource,
() => {
if (currentCenterPosition && showPulsePointList.value) {
refreshPulseEffect();
}
},
{ deep: true }
);
const poi = computed(() => statusStore.poiLayers);
const map = computed(() => statusStore.mapLayers);
const infra = computed(() => statusStore.infrastructureLayers);
watch([
() => 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,
], () => {
if (currentCenterPosition && showPulsePointList.value) {
refreshPulseEffect();
}
});
onUnmounted(() => {
clearAllAnalysisResources();
removeMapClickHandler();
});
// ==================== 按钮配置 ====================
const analysisButtons: AnalysisButtonConfig[] = [
{
name: '标记区域分析',
activeName: '取消区域分析',
callback: (status: boolean) => {
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) => {
statusStore.mapLayers.showAdministrativeDivision.show = !status;
},
},
];
return {
selectedButtonIndex,
showAreaDialog,
radius,
dialogPosition,
analysisButtons,
searchState,
canSearch,
pulsePoints,
showPulsePointList,
handleButtonClick,
handleConfirm,
handleCancel,
refreshPulseEffect,
startAreaAnalysisFromSearch,
querySearch,
handleSelect,
handleFocus,
};
};
@@ -1,146 +0,0 @@
import { ref, computed } from 'vue';
import { LoadingResource } from '@/types/common/LoadingResourceType';
import type { ResourceConfig, PointResourceCategory } from '@/types/common/useAroundAnalysisType';
/**
* 周边分析配置 Hook
* 将配置文件改为 hook 形式,提供响应式配置管理
*/
export const useAroundAnalysisConfig = () => {
// ==================== 资源配置 ====================
/** 周边分析资源配置列表 */
const resourceConfigs = ref<ResourceConfig[]>([
{ 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.COLLAPSE_HIDDEN_POINT, category: 'hidden-danger', forcedType: 'collapse' },
{ key: LoadingResource.RISK_POINT, category: 'risk-point' },
{ key: LoadingResource.BRIDGE, category: 'bridge' },
{ key: LoadingResource.RESERVOIR, category: 'reservoir' },
]);
// ==================== 常量配置 ====================
/** 对话框宽度(像素) */
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;
// ==================== 计算属性 ====================
/** 根据分类获取资源配置 */
const getConfigByCategory = (category: PointResourceCategory) => {
return resourceConfigs.value.find(config => config.category === category);
};
/** 根据 key 获取资源配置 */
const getConfigByKey = (key: LoadingResource) => {
return resourceConfigs.value.find(config => config.key === key);
};
/** 获取所有隐患点配置 */
const hiddenDangerConfigs = computed(() => {
return resourceConfigs.value.filter(config => config.category === 'hidden-danger');
});
/** 获取所有资源配置的 key 列表 */
const resourceKeys = computed(() => {
return resourceConfigs.value.map(config => config.key);
});
/** 获取所有资源配置的 category 列表 */
const resourceCategories = computed(() => {
return resourceConfigs.value.map(config => config.category);
});
// ==================== 方法 ====================
/** 添加资源配置 */
const addResourceConfig = (config: ResourceConfig) => {
resourceConfigs.value.push(config);
};
/** 移除资源配置 */
const removeResourceConfig = (key: LoadingResource) => {
const index = resourceConfigs.value.findIndex(config => config.key === key);
if (index !== -1) {
resourceConfigs.value.splice(index, 1);
}
};
/** 更新资源配置 */
const updateResourceConfig = (key: LoadingResource, updates: Partial<ResourceConfig>) => {
const config = resourceConfigs.value.find(c => c.key === key);
if (config) {
Object.assign(config, updates);
}
};
/** 获取常量配置对象 */
const getConstants = () => ({
DIALOG_WIDTH,
DIALOG_HEIGHT,
DIALOG_PADDING,
DIALOG_OFFSET,
EARTH_RADIUS,
MIN_FLY_HEIGHT,
FLY_HEIGHT_MULTIPLIER,
FLY_DURATION,
});
return {
// 状态
resourceConfigs,
// 常量
DIALOG_WIDTH,
DIALOG_HEIGHT,
DIALOG_PADDING,
DIALOG_OFFSET,
EARTH_RADIUS,
MIN_FLY_HEIGHT,
FLY_HEIGHT_MULTIPLIER,
FLY_DURATION,
// 计算属性
hiddenDangerConfigs,
resourceKeys,
resourceCategories,
// 方法
getConfigByCategory,
getConfigByKey,
addResourceConfig,
removeResourceConfig,
updateResourceConfig,
getConstants,
};
};
@@ -1,86 +0,0 @@
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,72 +0,0 @@
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,
};
};
-141
View File
@@ -1,141 +0,0 @@
import {
Cartesian3,
Color,
Entity,
VerticalOrigin,
HorizontalOrigin,
HeightReference,
CallbackProperty,
JulianDate,
LabelStyle,
NearFarScalar
} 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,
},
label: {
text: point.value || String(point.id),
font: 'bold 16px Microsoft YaHei, sans-serif',
fillColor: Color.WHITE,
outlineColor: Color.BLACK,
outlineWidth: 5,
style: LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: VerticalOrigin.BOTTOM,
horizontalOrigin: HorizontalOrigin.CENTER,
pixelOffset: new Cartesian3(0, -20),
eyeOffset: new Cartesian3(0, 0, -100),
heightReference: HeightReference.NONE,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
showBackground: false,
scaleByDistance: new NearFarScalar(1000, 1.2, 50000, 0.8),
translucencyByDistance: new NearFarScalar(1000, 1.0, 30000, 0.9),
},
});
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,
};
};
+5 -39
View File
@@ -1,24 +1,13 @@
import { ref } from 'vue'; import { ref } from 'vue';
import type { XianHiddenDangerSpots } from '@/types/base/XianHiddenDangerSpots'; import type { XianHiddenDangerSpots } from '@/types/base/XianHiddenDangerSpots';
import type { XianRiskSpots } from '@/types/base/XianRiskSpots';
import type { XianHospitals } from '@/types/base/XianHospitals';
import type { XianDangerousSource } from '@/types/base/XianDangerousSource';
import type { XianEmergencyShelter } from '@/types/base/XianEmergencyShelter';
import type { XianFirefighter } from '@/types/base/XianFirefighter';
import type { XianStorePoints } from '@/types/base/XianStorePoints';
import type { XianSchool } from '@/types/base/XianSchool';
import type { XianBridge } from '@/types/base/XianBridge';
import type { XianReservoirList } from '@/types/base/XianReservoirList';
import type { XianSubwayStations } from '@/types/base/XianSubwayStations';
import type { PaginationType } from '@/types/common/PaginationType'; import type { PaginationType } from '@/types/common/PaginationType';
import { PointType } from '@/types/common/DisasterType'; import { PointType } from '@/types/common/DisasterType';
import { InfrastructurePointType } from '@/types/common/InfrastructurePointType';
/** /**
* 灾害链表格数据选项 * 灾害链表格数据选项
*/ */
export interface SelectOption { export interface SelectOption {
value: PointType | InfrastructurePointType; value: PointType;
label: string; label: string;
} }
@@ -35,7 +24,7 @@ export interface TableColumn {
*/ */
export interface SearchConditions { export interface SearchConditions {
tableData: string; tableData: string;
hiddenPoint: PointType | InfrastructurePointType; hiddenPoint: PointType;
} }
/** /**
@@ -67,19 +56,8 @@ export const useDisasterChainTable = () => {
/** /**
* 表格数据 * 表格数据
*/ */
const tableDatas = ref<( const tableDatas = ref<XianHiddenDangerSpots[]>([]);
| XianHiddenDangerSpots
| XianRiskSpots
| XianHospitals
| XianDangerousSource
| XianEmergencyShelter
| XianFirefighter
| XianStorePoints
| XianSchool
| XianBridge
| XianReservoirList
| XianSubwayStations
)[]>([]);
/** /**
* 分页配置 * 分页配置
*/ */
@@ -141,17 +119,6 @@ export const useDisasterChainTable = () => {
tableColumns.value = columns; tableColumns.value = columns;
}; };
/**
* 设置表格数据
* @param datas - 表格数据数组
*/
const setTableDatas = (datas: (XianHiddenDangerSpots | XianRiskSpots | XianHospitals)[]) => {
tableDatas.value = datas;
paginationConfig.value.total = datas.length;
paginationConfig.value.totalPage = Math.ceil(datas.length / paginationConfig.value.pageSize);
paginationConfig.value.currentPage = 1;
};
// ==================== 返回 ==================== // ==================== 返回 ====================
return { return {
@@ -168,6 +135,5 @@ export const useDisasterChainTable = () => {
changeCurrentPage, changeCurrentPage,
setSelectOptions, setSelectOptions,
setTableColumns, setTableColumns,
setTableDatas,
}; };
}; };
+1 -1
View File
@@ -16,7 +16,7 @@ export const useIndex = () => {
{ title: '多灾种灾害链分析', name: 'index', query: { identification: 3 } }, { title: '多灾种灾害链分析', name: 'index', query: { identification: 3 } },
{ title: '灾害链情景推演', name: 'index', query: { identification: 4 } }, { title: '灾害链情景推演', name: 'index', query: { identification: 4 } },
{ title: '数据管理', name: 'dataManagement', query: { identification: 5 } }, { title: '数据管理', name: 'dataManagement', query: { identification: 5 } },
{ title: '文件管理', name: 'index', query: { identification: 6 } }, { title: '文件管理', name: 'fileManagement', query: { identification: 6 } },
]; ];
/** /**
+2 -24
View File
@@ -44,30 +44,8 @@ export const usePointsHandle = () => {
const id = `${prefix}${point.id}`; const id = `${prefix}${point.id}`;
ids.push(id); ids.push(id);
info.push({ info.push({ id, name: point.name!, lon: point.lon, lat: point.lat });
id,
name: point.name!,
lon: point.lon,
lat: point.lat,
scale_grade: point.scale_grade,
risk_grade: point.risk_grade,
risk_level: point.risk_level,
level: point.level,
safe_product_level: point.safe_product_level,
type: point.type,
construction_category: point.construction_category,
team_type: point.team_type,
fire_type: point.fire_type,
department: point.department,
school_type: point.school_type,
school_creater: point.school_creater,
bridge_type: point.bridge_type,
build_time: point.build_time,
safety_status: point.safety_status,
net_flood_capacity: point.net_flood_capacity,
line: point.line,
depth_of_accumulated_water: point.depth_of_accumulated_water,
});
options.push({ options.push({
id: id, id: id,
type: 'billboard', type: 'billboard',
+6
View File
@@ -25,6 +25,12 @@ const router = createRouter({
component: () => component: () =>
import('@/views/home/data-management/DataManagementView.vue'), import('@/views/home/data-management/DataManagementView.vue'),
}, },
{
path: 'file-management',
name: 'fileManagement',
component: () =>
import('@/views/home/file-management/FileManagementView.vue'),
},
], ],
}, },
], ],
-6
View File
@@ -200,11 +200,6 @@ export const useStatusStore = defineStore('status', () => {
}, },
}); });
/**
* 鼠标样式状态
*/
const cursorStyle = ref<string>('default');
/** /**
* 恢复默认值 * 恢复默认值
*/ */
@@ -352,7 +347,6 @@ export const useStatusStore = defineStore('status', () => {
infrastructureLayers, infrastructureLayers,
weatherLayers, weatherLayers,
functionStatus, functionStatus,
cursorStyle,
reset, reset,
resetScene, resetScene,
}; };
+1 -37
View File
@@ -16,40 +16,4 @@ export interface Point {
name?: string; name?: string;
/** 预测概率 */ /** 预测概率 */
probability?: string; probability?: string;
/** 规模等级(隐患点专用) */ }
scale_grade?: string;
/** 险情等级(隐患点专用) */
risk_grade?: string;
/** 风险区等级(风险点专用) */
risk_level?: string;
/** 医院等级(医院专用) */
level?: string;
/** 安全生产标准化等级(危险源专用) */
safe_product_level?: string;
/** 避难所类型(避难所专用) */
type?: string;
/** 避难所性质(避难所专用) */
construction_category?: string;
/** 队伍类型(消防站专用) */
team_type?: string;
/** 消防站类型(消防站专用) */
fire_type?: string;
/** 所属部门(储备点/学校专用) */
department?: string;
/** 学校类型(学校专用) */
school_type?: string;
/** 所属部门(学校专用) */
school_creater?: string;
/** 桥梁类型(桥梁专用) */
bridge_type?: string;
/** 建成时间(桥梁专用) */
build_time?: string;
/** 水库安全状态编码(水库专用) */
safety_status?: string;
/** 净防洪库容(水库专用) */
net_flood_capacity?: string;
/** 地铁线路(地铁站专用) */
line?: string;
/** 积水深度(地铁站专用) */
depth_of_accumulated_water?: string;
}
+1 -5
View File
@@ -10,12 +10,8 @@ export interface XianReservoirList extends Point {
location?: string; location?: string;
/** 安全评定结果 */ /** 安全评定结果 */
safetyAssessResult?: string; safetyAssessResult?: string;
/** 水库安全状态编码 */
safetyStatus?: string;
/** 净防洪库容(万m³) */
netFloodCapacity?: string;
/** 经度 */ /** 经度 */
lon?: number; lon?: number;
/** 纬度 */ /** 纬度 */
lat?: number; lat?: number;
} }
+1 -3
View File
@@ -6,12 +6,10 @@ import type { Point } from './Point';
export interface XianSubwayStations extends Point { export interface XianSubwayStations extends Point {
/** 站点名称 */ /** 站点名称 */
stationName?: string; stationName?: string;
/** 地铁线路 */
line?: string;
/** 参照积水点 */ /** 参照积水点 */
referToTheWaterAccumulationPoint?: string; referToTheWaterAccumulationPoint?: string;
/** 积水深度 */ /** 积水深度 */
depthOfAccumulatedWater?: string; depthOfAccumulatedWater?: string;
/** 核算后积水深度 */ /** 核算后积水深度 */
accumulatedWaterAfterAccounting?: string; accumulatedWaterAfterAccounting?: string;
} }
-67
View File
@@ -75,70 +75,3 @@ export interface EntityOptions {
/** 自定义属性(用于存储额外信息) */ /** 自定义属性(用于存储额外信息) */
attributes?: Record<string, unknown>; 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;
}
}
@@ -1,38 +0,0 @@
/**
* 基础设施点类型枚举
*/
export enum InfrastructurePointType {
/** 医院 */
HOSPITAL = '医院',
/** 学校 */
SCHOOL = '学校',
/** 危险源 */
DANGEROUS_SOURCE = '危险源',
/** 避难所 */
REFUGEE_SHELTER = '避难所',
/** 消防站 */
FIRE_STATION = '消防站',
/** 储备点 */
STORE_POINTS = '储备点',
/** 桥梁 */
BRIDGE = '桥梁',
/** 水库 */
RESERVOIR = '水库',
/** 地铁站 */
SUBWAY = '地铁站',
}
/**
* 基础设施点类型映射(中文 -> 后端英文参数)
*/
export const InfrastructurePointTypeMap: Record<InfrastructurePointType, string> = {
[InfrastructurePointType.HOSPITAL]: 'hospital',
[InfrastructurePointType.SCHOOL]: 'school',
[InfrastructurePointType.DANGEROUS_SOURCE]: 'dangerous_source',
[InfrastructurePointType.REFUGEE_SHELTER]: 'refugee_shelter',
[InfrastructurePointType.FIRE_STATION]: 'fire_station',
[InfrastructurePointType.STORE_POINTS]: 'store_points',
[InfrastructurePointType.BRIDGE]: 'bridge',
[InfrastructurePointType.RESERVOIR]: 'reservoir',
[InfrastructurePointType.SUBWAY]: 'subway',
};
-118
View File
@@ -1,118 +0,0 @@
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;
}
/**
* 资源配置接口(统一版本)
*/
export interface ResourceConfig {
/** 加载资源键 */
key: LoadingResource;
/** 资源分类 */
category: PointResourceCategory;
/** 强制类型(用于隐患点) */
forcedType?: string;
/** 是否可见的判断函数 */
isVisible?: () => boolean;
}
/**
* 分析按钮配置接口
*/
export interface AnalysisButtonConfig {
/** 按钮默认名称 */
name: string;
/** 按钮激活时的名称(可选) */
activeName?: string;
/** 按钮点击回调函数 */
callback: (status: boolean) => void;
}
/**
* 对话框位置接口
*/
export interface DialogPosition {
/** X 坐标 */
x: number;
/** Y 坐标 */
y: number;
}
/**
* 周边分析统一状态接口
*/
export interface AroundAnalysisState {
/** 当前选中的按钮索引 */
selectedButtonIndex: Ref<number>;
/** 是否显示区域选择弹窗 */
showAreaDialog: Ref<boolean>;
/** 区域半径(公里) */
radius: Ref<number>;
/** 弹窗位置 */
dialogPosition: DialogPosition;
/** 分析按钮配置列表 */
analysisButtons: AnalysisButtonConfig[];
/** 搜索框状态 */
searchState: Ref<string>;
/** 是否允许搜索 */
canSearch: Ref<boolean>;
/** 脉冲点列表 */
pulsePoints: Ref<PointResource[]>;
/** 是否显示脉冲点列表 */
showPulsePointList: Ref<boolean>;
/** 按钮点击处理函数 */
handleButtonClick: (index: number, callback: (status: boolean) => void) => void;
/** 确认添加区域分析 */
handleConfirm: () => void;
/** 取消区域分析 */
handleCancel: () => void;
/** 刷新脉冲效果 */
refreshPulseEffect: () => void;
/** 从搜索结果启动区域分析 */
startAreaAnalysisFromSearch: (point: PointResource) => void;
/** 查询建议 */
querySearch: (queryString: string, cb: (results: PointResource[]) => void) => void;
/** 选择建议回调 */
handleSelect: (item: PointResource) => void;
/** 聚焦事件处理 */
handleFocus: () => void;
}
-103
View File
@@ -1,103 +0,0 @@
import { useStatusStore } from '@/stores/useStatusStore';
import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore';
import type { PointResource, PointResourceCategory, ResourceConfig } from '@/types/common/useAroundAnalysisType';
/**
* 判断资源分类是否可见
*/
export const isCategoryVisible = (
category: PointResourceCategory,
originalType?: string
): boolean => {
const statusStore = useStatusStore();
const poi = statusStore.poiLayers;
const map = statusStore.mapLayers;
const infra = statusStore.infrastructureLayers;
const visibilityMap: Record<string, () => boolean> = {
school: () => poi.showSchool.show,
hospital: () => poi.showHospital.show,
danger: () => poi.showDangerSource.show,
shelter: () => poi.showRefugeeShelter.show,
fire: () => poi.showFireStation.show,
store: () => poi.showReservePoint.show,
subway: () => poi.showSubwayStation.show,
'risk-point': () => map.riskPointShow.show,
bridge: () => infra.showBridge.show,
reservoir: () => infra.showReservoir.show,
'hidden-danger': () => {
const hiddenMap: Record<string, () => boolean> = {
landslide: () => poi.showLandslideHiddenPoint.show,
debris_flow: () => poi.showDebrisFlowHiddenPoint.show,
water_logging: () => poi.showWaterLoggingHiddenPoint.show,
flash_flood: () => poi.showFlashFloodHiddenPoint.show,
collapse: () => poi.showCollapseHiddenPoint.show,
};
return hiddenMap[originalType || '']?.() ?? false;
},
};
return visibilityMap[category]?.() ?? false;
};
/**
* 转换 Store 数据为资源格式
*/
export const convertStoreDataToResources = (
infoList: Record<string, unknown>[],
category: PointResource['category'],
forcedType?: string
): PointResource[] => {
if (!Array.isArray(infoList)) return [];
return infoList.map(item => {
const id = item.id || item._id || item.uuid || 'unknown_id';
const safeId = typeof id === 'string' || typeof id === 'number' ? id : 'unknown_id';
const name = item.name && String(item.name).trim() !== '' ? String(item.name) : String(safeId);
return {
...item,
id: safeId,
value: name,
category,
originalType: (forcedType || item.type || item.disasterType)?.toString().toLowerCase(),
};
});
};
/**
* 加载所有点数据
*/
export const loadAllPointData = (configs: ResourceConfig[]): PointResource[] => {
const loadingResourceStore = useLoadingResourceStore();
const resources = configs.flatMap(config =>
convertStoreDataToResources(
loadingResourceStore.getLoadingResource(config.key).info,
config.category,
config.forcedType
)
);
const uniqueMap = new Map<string | number, PointResource>();
resources.forEach(item => uniqueMap.set(item.id, item));
return Array.from(uniqueMap.values());
};
/**
* 计算两点间距离(Haversine 公式)
*/
export 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 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};
@@ -0,0 +1,19 @@
<!-- 文件管理 -->
<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>
+19 -414
View File
@@ -8,17 +8,16 @@
<!-- 灾害链影响列表组件 --> <!-- 灾害链影响列表组件 -->
<DisasterChainPointComponent <DisasterChainPointComponent
v-if=" v-if="
statusStore.appLoadingCompleted && statusStore.appLoadingCompleted &&
statusStore.uiComponents.disasterChainPointShow.loading statusStore.uiComponents.disasterChainPointShow.loading
" "
:select-options="selectOptions" :select-options="selectOptions"
:table-data-list="tableDatas" :table-data-list="tableDatas"
:table-columns="tableColumns" :table-columns="tableColumns"
:page-option="paginationConfig" :page-option="paginationConfig"
:total-data-count="allDataCount" @change-conditions="changeConditions"
@change-conditions="changeConditions" @change-current-page="changeCurrentPage"
@change-current-page="changeCurrentPage"
/> />
<!-- 左侧按钮组件 --> <!-- 左侧按钮组件 -->
@@ -72,32 +71,13 @@
import RightButtonComponent from '@/component/rain-earthquake/RightButtonComponent.vue'; import RightButtonComponent from '@/component/rain-earthquake/RightButtonComponent.vue';
import StepComponent from '@/component/rain-earthquake/StepComponent.vue'; import StepComponent from '@/component/rain-earthquake/StepComponent.vue';
import { useRainDisasterChain } from '@/hooks/rainstorm/useRainDisasterChain'; import { useRainDisasterChain } from '@/hooks/rainstorm/useRainDisasterChain';
import { useAroundAnalysis } from '@/hooks/rain-earthquake/useAroundAnalysis';
import { InfrastructurePointType } from '@/types/common/InfrastructurePointType';
import type { AroundAnalysisState } from '@/types/common/useAroundAnalysisType';
import type { PointResource } from '@/types/common/useAroundAnalysisType';
import type { XianHiddenDangerSpots } from '@/types/base/XianHiddenDangerSpots';
import type { XianRiskSpots } from '@/types/base/XianRiskSpots';
import type { XianHospitals } from '@/types/base/XianHospitals';
import type { XianDangerousSource } from '@/types/base/XianDangerousSource';
import type { XianEmergencyShelter } from '@/types/base/XianEmergencyShelter';
import type { XianFirefighter } from '@/types/base/XianFirefighter';
import type { XianStorePoints } from '@/types/base/XianStorePoints';
import type { XianSchool } from '@/types/base/XianSchool';
import type { XianBridge } from '@/types/base/XianBridge';
import type { XianReservoirList } from '@/types/base/XianReservoirList';
import type { XianSubwayStations } from '@/types/base/XianSubwayStations';
import { import {
useDisasterChainTable, useDisasterChainTable,
type SearchConditions, type SearchConditions,
} from '@/hooks/useDisasterChainTable'; } from '@/hooks/useDisasterChainTable';
import { useStatusStore } from '@/stores/useStatusStore'; import { useStatusStore } from '@/stores/useStatusStore';
import { import { DisasterType, PointType } from '@/types/common/DisasterType.ts';
DisasterType, import { onBeforeMount } from 'vue';
PointType,
HiddenDangerPointTypeMap,
} from '@/types/common/DisasterType.ts';
import {onBeforeMount, watch, provide, computed} from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
const route = useRoute(); const route = useRoute();
@@ -107,55 +87,33 @@
const statusStore = useStatusStore(); const statusStore = useStatusStore();
// 在父组件中创建 useAroundAnalysis 实例并通过 provide 提供给子组件
const aroundAnalysisState = useAroundAnalysis();
provide<AroundAnalysisState>('aroundAnalysisState', aroundAnalysisState);
const { const {
selectOptions, selectOptions,
tableColumns, tableColumns,
tableDatas, tableDatas,
paginationConfig, paginationConfig,
conditions,
changeConditions, changeConditions,
setConditions, setConditions,
changeCurrentPage, changeCurrentPage,
setSelectOptions, setSelectOptions,
setTableColumns, setTableColumns,
setTableDatas,
} = useDisasterChainTable(); } = useDisasterChainTable();
// 所有数据的总数(所有类型的脉冲点总和)
const allDataCount = computed(() => {
return aroundAnalysisState.pulsePoints.value?.length || 0;
});
onBeforeMount(() => { onBeforeMount(() => {
// 设置下拉选项 // 设置下拉选项
setSelectOptions([ setSelectOptions([
{value: PointType.LANDSLIDE, label: '滑坡'}, { value: PointType.LANDSLIDE, label: '滑坡' },
{value: PointType.DEBRIS_FLOW, label: '泥石流'}, { value: PointType.DEBRIS_FLOW, label: '泥石流' },
{value: PointType.WATER_LOGGING, label: '内涝'}, { value: PointType.FLASH_FLOOD, label: '山洪' },
{value: PointType.FLASH_FLOOD, label: '山洪'}, { value: PointType.WATER_LOGGING, label: '内涝' },
{value: PointType.COLLAPSE, label: '崩塌'},
{value: PointType.RISK_AREA, label: '风险区'},
{value: InfrastructurePointType.HOSPITAL, label: '医院'},
{value: InfrastructurePointType.DANGEROUS_SOURCE, label: '危险源'},
{value: InfrastructurePointType.REFUGEE_SHELTER, label: '避难所'},
{value: InfrastructurePointType.FIRE_STATION, label: '消防站'},
{value: InfrastructurePointType.STORE_POINTS, label: '储备点'},
{value: InfrastructurePointType.SCHOOL, label: '学校'},
{value: InfrastructurePointType.BRIDGE, label: '桥梁'},
{value: InfrastructurePointType.RESERVOIR, label: '水库'},
{value: InfrastructurePointType.SUBWAY, label: '地铁站'},
]); ]);
// 设置表格列配置(默认显示隐患点的列) // 设置表格列配置
setTableColumns([ setTableColumns([
{title: '名称', key: 'disasterName'}, { title: '名称', key: 'disasterName' },
{title: '位置', key: 'position'}, { title: '位置', key: 'position' },
{title: '规模等级', key: 'scaleGrade'}, { title: '规模等级', key: 'scaleGrade' },
{title: '险情等级', key: 'riskGrade'}, { title: '险情等级', key: 'riskGrade' },
]); ]);
/** /**
@@ -164,361 +122,8 @@
*/ */
changeConditions.value = (value: SearchConditions) => { changeConditions.value = (value: SearchConditions) => {
setConditions(value); setConditions(value);
// 根据选择的类型动态改变表格列
if (value.hiddenPoint === PointType.RISK_AREA) {
// 风险区:只显示风险区等级
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '风险区等级', key: 'riskLevel'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.HOSPITAL) {
// 医院:只显示医院等级
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '医院等级', key: 'level'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.DANGEROUS_SOURCE) {
// 危险源:显示危险源等级和安全生产标准化等级
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '危险源等级', key: 'level'},
{title: '安全生产标准化等级', key: 'safeProductLevel'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.REFUGEE_SHELTER) {
// 避难所:显示避难所类型和避难所性质
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '避难所类型', key: 'type'},
{title: '避难所性质', key: 'constructionCategory'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.FIRE_STATION) {
// 消防站:显示队伍类型和消防站类型
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '队伍类型', key: 'teamType'},
{title: '消防站类型', key: 'fireType'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.STORE_POINTS) {
// 储备点:显示分级和所属部门
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '分级', key: 'level'},
{title: '所属部门', key: 'department'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.SCHOOL) {
// 学校:显示学校类型和所属部门
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '学校类型', key: 'schoolType'},
{title: '所属部门', key: 'schoolCreater'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.BRIDGE) {
// 桥梁:显示类型和建成时间
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '类型', key: 'bridgeType'},
{title: '建成时间', key: 'buildTime'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.RESERVOIR) {
// 水库:显示安全状态和净防洪库容
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '安全状态', key: 'safetyStatus'},
{title: '净防洪库容(万m³)', key: 'netFloodCapacity'},
]);
} else if (value.hiddenPoint === InfrastructurePointType.SUBWAY) {
// 地铁站:显示地铁线路和积水深度
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '地铁线路', key: 'line'},
{title: '积水深度', key: 'depthOfAccumulatedWater'},
]);
} else {
// 隐患点:显示规模等级和险情等级
setTableColumns([
{title: '名称', key: 'disasterName'},
{title: '位置', key: 'position'},
{title: '规模等级', key: 'scaleGrade'},
{title: '险情等级', key: 'riskGrade'},
]);
}
}; };
// 监听脉冲点变化和下拉选项变化,过滤并更新表格数据
watch(
[
() => aroundAnalysisState.pulsePoints.value,
() => conditions.value.hiddenPoint,
],
([newPulsePoints, hiddenPointType]: [PointResource[], PointType | InfrastructurePointType]) => {
console.log('=== 脉冲点变化 ===');
console.log('newPulsePoints:', newPulsePoints);
console.log('newPulsePoints.length:', newPulsePoints?.length);
console.log('hiddenPointType:', hiddenPointType);
if (newPulsePoints && newPulsePoints.length > 0) {
// 根据选中的类型过滤
const filteredPoints = newPulsePoints.filter(point => {
// 隐患点过滤
if (point.category === 'hidden-danger') {
const englishType = HiddenDangerPointTypeMap[hiddenPointType as PointType];
return englishType ? point.originalType === englishType : false;
}
// 风险点过滤
if (
hiddenPointType === PointType.RISK_AREA &&
point.category === 'risk-point'
) {
return true;
}
// 基础设施点过滤
const infrastructureMap: Record<string, string> = {
[InfrastructurePointType.HOSPITAL]: 'hospital',
[InfrastructurePointType.DANGEROUS_SOURCE]: 'danger',
[InfrastructurePointType.REFUGEE_SHELTER]: 'shelter',
[InfrastructurePointType.FIRE_STATION]: 'fire',
[InfrastructurePointType.STORE_POINTS]: 'store',
[InfrastructurePointType.SCHOOL]: 'school',
[InfrastructurePointType.BRIDGE]: 'bridge',
[InfrastructurePointType.RESERVOIR]: 'reservoir',
[InfrastructurePointType.SUBWAY]: 'subway',
};
const infraCategory = infrastructureMap[hiddenPointType as InfrastructurePointType];
if (infraCategory && point.category === infraCategory) {
return true;
}
return false;
});
// 根据类型转换数据
const convertedData = filteredPoints.map(point => {
if (point.category === 'risk-point') {
// 风险点数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
riskLevel: String(point.risk_level || '一般'),
lon: point.lon,
lat: point.lat,
} as XianRiskSpots;
} else if (point.category === 'hospital') {
// 医院数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
level: String(point.level || '未知'),
lon: point.lon,
lat: point.lat,
} as XianHospitals;
} else if (point.category === 'danger') {
// 危险源数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
level: String(point.level || '未知'),
safeProductLevel: String(point.safe_product_level || '未知'),
lon: point.lon,
lat: point.lat,
} as XianDangerousSource;
} else if (point.category === 'shelter') {
// 避难所数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
type: String(point.type || '未知'),
constructionCategory: String(point.construction_category || '未知'),
lon: point.lon,
lat: point.lat,
} as XianEmergencyShelter;
} else if (point.category === 'fire') {
// 消防站数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
teamType: String(point.team_type || '未知'),
fireType: String(point.fire_type || '未知'),
lon: point.lon,
lat: point.lat,
} as XianFirefighter;
} else if (point.category === 'store') {
// 储备点数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
level: String(point.level || '未知'),
department: String(point.department || '未知'),
lon: point.lon,
lat: point.lat,
} as XianStorePoints;
} else if (point.category === 'school') {
// 学校数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
schoolType: String(point.school_type || '未知'),
schoolCreater: String(point.school_creater || '未知'),
lon: point.lon,
lat: point.lat,
} as XianSchool;
} else if (point.category === 'bridge') {
// 桥梁数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
bridgeType: String(point.bridge_type || '未知'),
buildTime: String(point.build_time || '未知'),
lon: point.lon,
lat: point.lat,
} as XianBridge;
} else if (point.category === 'reservoir') {
// 水库数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
safetyStatus: String(point.safety_status || '未知'),
netFloodCapacity: String(point.net_flood_capacity || '未知'),
lon: point.lon,
lat: point.lat,
} as XianReservoirList;
} else if (point.category === 'subway') {
// 地铁站数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
line: String(point.line || '未知'),
depthOfAccumulatedWater: String(point.depth_of_accumulated_water || '未知'),
lon: point.lon,
lat: point.lat,
} as XianSubwayStations;
} else {
// 隐患点数据
return {
id:
typeof point.id === 'number'
? point.id
: parseInt(String(point.id), 10),
name: point.value,
disasterName: point.value,
position:
point.lon !== undefined && point.lat !== undefined
? `${point.lon.toFixed(4)}, ${point.lat.toFixed(4)}`
: '未知位置',
scaleGrade: String(point.scale_grade || '未知'),
riskGrade: String(point.risk_grade || '一般'),
lon: point.lon,
lat: point.lat,
fieldCode: String(point.id),
province: '陕西省',
city: '西安市',
county: '',
village: '',
isDelete: 0,
} as XianHiddenDangerSpots;
}
});
console.log('convertedData:', convertedData);
setTableDatas(convertedData);
} else {
// 如果没有脉冲点,则清空表格数据
console.log('清空表格数据');
setTableDatas([]);
}
},
{ immediate: true, deep: true }
);
}); });
</script> </script>
<style scoped></style> <style scoped></style>