From e17a73537cbd11ae641b58b651f832eecdbbc46c Mon Sep 17 00:00:00 2001 From: wzy-warehouse <18135009705@163.com> Date: Sat, 11 Apr 2026 10:09:40 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9A=90=E6=82=A3=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 5 + package.json | 2 +- pnpm-lock.yaml | 2 +- src/App.vue | 15 ++- src/api/api.ts | 13 ++ src/api/crypto.ts | 4 +- src/api/hidden-danger-spots.ts | 26 ++++ src/assets/images/icon/debris-flow.png | Bin 0 -> 4715 bytes src/assets/images/icon/flash-flood.png | Bin 0 -> 4164 bytes src/assets/images/icon/landslide.png | Bin 0 -> 3627 bytes src/assets/images/icon/waterlogging.png | Bin 0 -> 3246 bytes src/component/common/InformationBox.vue | 110 ++++++++++++++++ src/component/map/Map.vue | 20 ++- .../rain-earthquake/BasicComponent.vue | 26 ++++ .../rain-earthquake/HiddenPointComponent.vue | 120 ++++++++++++++++++ .../rain-earthquake/LoadingPoints.vue | 26 ++++ src/config/config.json | 7 +- src/hooks/usePointsHandle.ts | 65 ++++++++++ src/stores/useViewerStore.ts | 15 +++ src/types/{Response.ts => ApiResponse.ts} | 2 +- src/types/base/Point.ts | 12 ++ src/types/base/XianHiddenDangerSpots.ts | 33 +++++ src/types/cesium/PrimitiveOptions.ts | 4 +- src/types/common/DisasterType.ts | 4 + src/utils/cesium/CesiumUtils.ts | 28 +++- src/utils/cesium/PrimitiveManager.ts | 16 ++- src/utils/utils.ts | 25 ++++ src/views/home/earthquake/Earthquake.vue | 9 +- src/views/home/rainstorm/Rainstorm.vue | 9 +- 29 files changed, 573 insertions(+), 25 deletions(-) create mode 100644 src/api/hidden-danger-spots.ts create mode 100644 src/assets/images/icon/debris-flow.png create mode 100644 src/assets/images/icon/flash-flood.png create mode 100644 src/assets/images/icon/landslide.png create mode 100644 src/assets/images/icon/waterlogging.png create mode 100644 src/component/common/InformationBox.vue create mode 100644 src/component/rain-earthquake/BasicComponent.vue create mode 100644 src/component/rain-earthquake/HiddenPointComponent.vue create mode 100644 src/component/rain-earthquake/LoadingPoints.vue create mode 100644 src/hooks/usePointsHandle.ts create mode 100644 src/stores/useViewerStore.ts rename src/types/{Response.ts => ApiResponse.ts} (50%) create mode 100644 src/types/base/Point.ts create mode 100644 src/types/base/XianHiddenDangerSpots.ts create mode 100644 src/types/common/DisasterType.ts diff --git a/components.d.ts b/components.d.ts index 227ab48..65dc19f 100644 --- a/components.d.ts +++ b/components.d.ts @@ -11,6 +11,11 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + ElButton: typeof import('element-plus/es')['ElButton'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElIcon: typeof import('element-plus/es')['ElIcon'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] } diff --git a/package.json b/package.json index 12b5401..70d8acb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@types/spark-md5": "^3.0.5", "axios": "^1.12.2", "cesium": "1.101.0", - "element-plus": "^2.11.7", + "element-plus": "^2.13.6", "gm-crypto": "^0.1.12", "pinia": "^3.0.3", "spark-md5": "^3.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bea220..88e70bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,7 +21,7 @@ importers: specifier: 1.101.0 version: 1.101.0 element-plus: - specifier: ^2.11.7 + specifier: ^2.13.6 version: 2.13.6(typescript@6.0.2)(vue@3.5.32(typescript@6.0.2)) gm-crypto: specifier: ^0.1.12 diff --git a/src/App.vue b/src/App.vue index 6078adf..c3b7c92 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,8 +2,21 @@ - \ No newline at end of file diff --git a/src/component/map/Map.vue b/src/component/map/Map.vue index 18caa67..932dbdf 100644 --- a/src/component/map/Map.vue +++ b/src/component/map/Map.vue @@ -2,23 +2,33 @@
- + + + diff --git a/src/component/rain-earthquake/HiddenPointComponent.vue b/src/component/rain-earthquake/HiddenPointComponent.vue new file mode 100644 index 0000000..ce3d734 --- /dev/null +++ b/src/component/rain-earthquake/HiddenPointComponent.vue @@ -0,0 +1,120 @@ + + + + + + + + diff --git a/src/component/rain-earthquake/LoadingPoints.vue b/src/component/rain-earthquake/LoadingPoints.vue new file mode 100644 index 0000000..d0b0602 --- /dev/null +++ b/src/component/rain-earthquake/LoadingPoints.vue @@ -0,0 +1,26 @@ + + + + + + diff --git a/src/config/config.json b/src/config/config.json index 4f00fdf..2625359 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -12,5 +12,10 @@ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1ZDBjZjAxOS0wMDhhLTRmZjEtYjNmOC1iNmM2ZmY2ZmQ1N2IiLCJpZCI6MjAxMDI1LCJpYXQiOjE3MTAxNTgxNjJ9.mdbJYEzXQkBnHNqpozz7MvZjJ_X9a3JZRGPA-ytGhLI", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiNjczZTVlMy1kNDEwLTRhZWItYWM0NS1mNjYxMzJjODMwYTQiLCJpZCI6MzIxMzI2LCJpYXQiOjE3NzU2NDU1OTd9._MPcZQsxK1dGPl8IMVhKHV3PIPu4-TaOUgzsUUOP6WE" ], - "defaultPosition": [108.948024, 34.263161, 200000] + "defaultPosition": [108.948024, 34.263161, 300000], + + "prefix": { + "hiddenDangerPointId": "hidden-danger-point-", + "riskPointId": "risk-point-" + } } diff --git a/src/hooks/usePointsHandle.ts b/src/hooks/usePointsHandle.ts new file mode 100644 index 0000000..3375f77 --- /dev/null +++ b/src/hooks/usePointsHandle.ts @@ -0,0 +1,65 @@ +import type { Point } from "@/types/base/Point"; +import type { PrimitiveOptions } from "@/types/cesium/PrimitiveOptions"; +import { CesiumUtilsSingleton } from "@/utils/cesium/CesiumUtils"; +import { Cartesian3, HorizontalOrigin, NearFarScalar, VerticalOrigin } from "cesium"; +import config from '@/config/config.json' + +/** + * 公共批量处理点钩子函数 + */ +export const usePointsHandle = () => { + + + /** + * 添加点 + * @param points - 点数据 + * @param getDisasterIcon - 获取灾害图标的函数 + * @returns 点的ID列表 + */ + function addPoints(points: Point[], getDisasterIcon: (disasterType: string) => string): string[] { + // 设置加载配置 + const options: PrimitiveOptions[] = []; + + // 存放id + const ids: string[] = []; + + points.forEach(point => { + try { + + if (point.lon === undefined || point.lat === undefined || point.disasterType === undefined) { + throw new Error(`点位数据缺少经纬度或者灾害类型:${point.id}`); + } + // 将经纬度转换为笛卡尔坐标 + const position = Cartesian3.fromDegrees(point.lon, point.lat, 0); + + const id = `${config.prefix.hiddenDangerPointId}${point.id}` + ids.push(id) + + options.push({ + id: id, + type: 'billboard', + positions: [position], + image: getDisasterIcon(point.disasterType), + scale: 0.8, + width: 40, + isDefault: false, + scaleByDistance: new NearFarScalar(500, 1, 5e5, 0.3), + customProperties: { + verticalOrigin: VerticalOrigin.BOTTOM, + horizontalOrigin: HorizontalOrigin.CENTER, + height: 40 + }, + }); + } catch (error) { + throw new Error(`处理点位失败:${point.id}`); + } + }) + + // 批量创建图层 + CesiumUtilsSingleton.addPrimitivesBatch(options); + + return ids + } + + return { addPoints} +} \ No newline at end of file diff --git a/src/stores/useViewerStore.ts b/src/stores/useViewerStore.ts new file mode 100644 index 0000000..ee25c03 --- /dev/null +++ b/src/stores/useViewerStore.ts @@ -0,0 +1,15 @@ +import { defineStore } from 'pinia' +import { type Ref, ref } from 'vue' + +export const useViewerStore = defineStore('viewer', () => { + // viewer完成状态 + const viewerLoadingCompleted: Ref = ref(false) + + // get/set方法 + const getViewerLoadingCompleted = () => viewerLoadingCompleted.value + const setViewerLoadingCompleted = (value: boolean) => { + viewerLoadingCompleted.value = value + } + + return { getViewerLoadingCompleted, setViewerLoadingCompleted } +}) diff --git a/src/types/Response.ts b/src/types/ApiResponse.ts similarity index 50% rename from src/types/Response.ts rename to src/types/ApiResponse.ts index 499be55..3e0e56b 100644 --- a/src/types/Response.ts +++ b/src/types/ApiResponse.ts @@ -1,4 +1,4 @@ -export interface Response { +export interface ApiResponse { code: number message: string data: T diff --git a/src/types/base/Point.ts b/src/types/base/Point.ts new file mode 100644 index 0000000..b7624e8 --- /dev/null +++ b/src/types/base/Point.ts @@ -0,0 +1,12 @@ +export interface Point { + /** 序号 */ + id?: number; + /** 经度 */ + lon?: number; + /** 纬度 */ + lat?: number; + /** 空间 */ + geom?: string; + /** 灾害类型 */ + disasterType?: string; +} \ No newline at end of file diff --git a/src/types/base/XianHiddenDangerSpots.ts b/src/types/base/XianHiddenDangerSpots.ts new file mode 100644 index 0000000..53789f4 --- /dev/null +++ b/src/types/base/XianHiddenDangerSpots.ts @@ -0,0 +1,33 @@ +import type { Point } from "./Point"; + +/** + * 地质灾害隐患点 + */ +export interface XianHiddenDangerSpots extends Point{ + /** 野外编号 */ + fieldCode?: string; + /** 省 */ + province?: string; + /** 省编号 */ + provinceId?: string; + /** 市 */ + city?: string; + /** 市编号 */ + cityId?: string; + /** 县 */ + county?: string; + /** 县编号 */ + countyId?: string; + /** 乡镇 */ + village?: string; + /** 灾害点名称 */ + disasterName?: string; + /** 位置 */ + position?: string; + /** 规模等级 */ + scaleGrade?: string; + /** 险情等级 */ + riskGrade?: string; + /** 逻辑删除标识,0未删除,1已删除 */ + isDelete?: 0 | 1; +} \ No newline at end of file diff --git a/src/types/cesium/PrimitiveOptions.ts b/src/types/cesium/PrimitiveOptions.ts index be6fa42..0056e5f 100644 --- a/src/types/cesium/PrimitiveOptions.ts +++ b/src/types/cesium/PrimitiveOptions.ts @@ -1,4 +1,4 @@ -import type { Cartesian3, Color } from 'cesium' +import type { Cartesian3, Color, NearFarScalar } from 'cesium' export interface PrimitiveOptions { id: string @@ -10,4 +10,6 @@ export interface PrimitiveOptions { width?: number // 线宽 image?: string // 广告牌图片 scale?: number // 广告牌缩放 + scaleByDistance?: NearFarScalar // 广告牌距离衰减缩放 + customProperties?: Record // 自定义属性对象 } diff --git a/src/types/common/DisasterType.ts b/src/types/common/DisasterType.ts new file mode 100644 index 0000000..f4cbe01 --- /dev/null +++ b/src/types/common/DisasterType.ts @@ -0,0 +1,4 @@ +export enum DisasterType { + RAINSTORM = 'rainstorm', + EARTHQUAKE= 'earthquake' +} \ No newline at end of file diff --git a/src/utils/cesium/CesiumUtils.ts b/src/utils/cesium/CesiumUtils.ts index c99c3e6..f7e371c 100644 --- a/src/utils/cesium/CesiumUtils.ts +++ b/src/utils/cesium/CesiumUtils.ts @@ -3,7 +3,7 @@ import type { EntityOptions } from '@/types/cesium/EntityOptions' import type { PrimitiveOptions } from '@/types/cesium/PrimitiveOptions' import type { LayerConfig } from '@/types/cesium/LayerConfig' import type { CustomizeGeoJsonDataSource, GeoJsonOptions } from '@/types/cesium/GeoJsonOptions' -import { Viewer, Entity, DataSource, ImageryLayer, Primitive, BillboardCollection, Cartesian3 } from 'cesium' +import { Viewer, Entity, DataSource, ImageryLayer, Primitive, BillboardCollection, Cartesian3, ScreenSpaceEventHandler, ScreenSpaceEventType, Cartesian2, SceneTransforms } from 'cesium' import { CesiumViewerManager } from './CesiumViewerManager' import { EntityManager } from './EntityManager' import { PrimitiveManager } from './PrimitiveManager' @@ -38,7 +38,7 @@ export class CesiumUtils { */ initCesiumViewer(options: CesiumInitOptions, tdMapToken?: string[], type: number = 0): void { this.#viewerManager.initCesiumViewer(options, tdMapToken, type) - + const viewer = this.#viewerManager.getViewer() if (viewer) { this.#entityManager = new EntityManager(viewer) @@ -401,6 +401,20 @@ export class CesiumUtils { return this.#geoJsonManager!.getGeoJsonLayerIds(clearType) } + // ===================== 图层操作 ===================== + /** + * 监听点击事件 + */ + clickLayer(callback: (pickedObject: object) => void) { + const handler = new ScreenSpaceEventHandler(this.getViewer()?.scene.canvas); + handler.setInputAction((clickEvent: {position: Cartesian2}) => { + // 在点击位置进行拾取 + const pickedObject = CesiumUtilsSingleton.getViewer()?.scene.pick(clickEvent.position); + + callback(pickedObject); + }, ScreenSpaceEventType.LEFT_CLICK); + } + // ===================== 视角控制 ===================== /** @@ -465,6 +479,16 @@ export class CesiumUtils { return positions.map((pos) => this.convertPosition(pos)) } + /** + * 将Cartesian3坐标转换为屏幕坐标 + * @param pos 坐标 + * @returns 偏移量 + */ + convertScreenPosition(pos: Cartesian3): {x: number, y: number} { + const windowCoord = SceneTransforms.wgs84ToWindowCoordinates(this.getViewer()!.scene, pos); + return {x: windowCoord.x, y: windowCoord.y} + } + // ===================== 私有方法 ===================== /** diff --git a/src/utils/cesium/PrimitiveManager.ts b/src/utils/cesium/PrimitiveManager.ts index 14a7379..fa1feb7 100644 --- a/src/utils/cesium/PrimitiveManager.ts +++ b/src/utils/cesium/PrimitiveManager.ts @@ -14,6 +14,7 @@ import { BillboardGraphics, VerticalOrigin, HorizontalOrigin, + NearFarScalar, } from 'cesium' import type { PrimitiveOptions } from '@/types/cesium/PrimitiveOptions' import type { Viewer } from 'cesium' @@ -259,13 +260,24 @@ export class PrimitiveManager { options.forEach((option) => { const position = this.#convertPosition(option.positions[0]!) - collection.add({ + + const billboardConfig: any = { id: option.id, position, image: option.image, scale: option.scale || 1, color: option.color || Color.WHITE, - }) + } + + if (option.scaleByDistance) { + billboardConfig.scaleByDistance = option.scaleByDistance + } + + if (option.customProperties) { + Object.assign(billboardConfig, option.customProperties) + } + + collection.add(billboardConfig) }) this.#viewer.scene.primitives.add(collection) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 3dd5342..37befa9 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -80,6 +80,7 @@ export const Utils = { return result.replace(regex, String(value ?? '')) }, format) }, + /** * 深拷贝函数 * 支持类型:原始类型、数组、对象、Date、RegExp、Map、Set、ArrayBuffer等 @@ -200,4 +201,28 @@ export const Utils = { // 对于其他无法处理的情况,返回原值 return source }, + + /** + * 调整元素位置,确保其不超出屏幕可视区域边界 + * @param {number} offsetX - 元素左上角原 X 坐标(相对于视口) + * @param {number} offsetY - 元素左上角原 Y 坐标(相对于视口) + * @param {number} width - 元素的宽度 + * @param {number} height - 元素的高度 + * @returns {[number, number]} 调整后的 [newX, newY] + */ + keepWithinScreen: (offsetX: number, offsetY: number, width: number, height: number) => { + const viewportW = window.innerWidth; + const viewportH = window.innerHeight; + + // 计算允许的 X 范围:最小 0,最大 (视口宽度 - 元素宽度) + // 如果元素宽度大于视口宽度,允许范围为 [0, 0](即只能左对齐) + const maxX = Math.max(0, viewportW - width); + const newX = Math.min(Math.max(offsetX, 0), maxX); + + // 计算允许的 Y 范围 + const maxY = Math.max(0, viewportH - height); + const newY = Math.min(Math.max(offsetY, 0), maxY); + + return [newX, newY]; + } } diff --git a/src/views/home/earthquake/Earthquake.vue b/src/views/home/earthquake/Earthquake.vue index 3eaa73f..c006cab 100644 --- a/src/views/home/earthquake/Earthquake.vue +++ b/src/views/home/earthquake/Earthquake.vue @@ -1,9 +1,12 @@ + - + + diff --git a/src/views/home/rainstorm/Rainstorm.vue b/src/views/home/rainstorm/Rainstorm.vue index 9561cf3..5a3d38a 100644 --- a/src/views/home/rainstorm/Rainstorm.vue +++ b/src/views/home/rainstorm/Rainstorm.vue @@ -1,13 +1,12 @@ - +