From 5eb71642d21433f4f5c113c3e7ad4cee14b4cd0a Mon Sep 17 00:00:00 2001 From: wzy-warehouse <18135009705@163.com> Date: Mon, 13 Apr 2026 10:30:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=E8=A7=86=E8=A7=92=E9=AB=98=E5=BA=A6?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E4=BB=A5=E5=8F=8A=E8=A7=86=E8=A7=92=E8=8C=83?= =?UTF-8?q?=E5=9B=B4=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc | 6 + src/App.vue | 46 +-- src/component/common/InformationBox.vue | 129 +++---- src/component/map/AdministrativeDivision.vue | 149 +++++---- src/component/map/Map.vue | 90 ----- src/component/map/MapComponent.vue | 118 +++++++ .../rain-earthquake/BasicComponent.vue | 25 +- .../rain-earthquake/HiddenPointComponent.vue | 180 +++++----- .../rain-earthquake/LoadingPoints.vue | 34 +- .../rain-earthquake/RiskPointComponent.vue | 115 ++++--- src/config/config.json | 30 +- src/hooks/usePointsHandle.ts | 112 ++++--- src/main.ts | 22 +- src/router/index.ts | 18 +- src/stores/useCryptStore.ts | 10 +- src/stores/useLoadingInformation.ts | 82 +++-- src/stores/useViewerStore.ts | 22 +- src/types/ApiResponse.ts | 6 +- src/types/base/Point.ts | 22 +- src/types/base/XianHiddenDangerSpots.ts | 58 ++-- src/types/base/XianRiskSpots.ts | 80 ++--- src/types/cesium/CesiumInitOptions.ts | 50 +-- src/types/cesium/ClickObject.ts | 7 + src/types/cesium/EntityOptions.ts | 70 ++-- src/types/cesium/GeoJsonFileType.ts | 4 +- src/types/cesium/GeoJsonOptions.ts | 10 +- src/types/cesium/LabelConfig.ts | 27 +- src/types/cesium/LayerConfig.ts | 23 +- src/types/cesium/PrimitiveOptions.ts | 24 +- src/types/common/DisasterType.ts | 6 +- src/types/crypto/Sm2PublicKeyResponse.ts | 2 +- src/utils/cesium/CameraController.ts | 162 ++++++++- src/utils/cesium/CesiumUtils.ts | 316 +++++++++++------- src/utils/cesium/CesiumViewerManager.ts | 162 +++++---- src/utils/cesium/EntityManager.ts | 196 ++++++----- src/utils/cesium/GeoJsonManager.ts | 284 +++++++++------- src/utils/cesium/LayerManager.ts | 120 +++---- src/utils/cesium/PrimitiveManager.ts | 254 ++++++++------ src/utils/request/http.ts | 140 ++++---- src/utils/safety/SafetyUtils.ts | 96 +++--- src/utils/utils.ts | 145 ++++---- src/views/{Index.vue => IndexView.vue} | 100 +++--- src/views/home/earthquake/Earthquake.vue | 16 - src/views/home/earthquake/EarthquakeView.vue | 18 + src/views/home/rainstorm/Rainstorm.vue | 15 - src/views/home/rainstorm/RainstormView.vue | 18 + 46 files changed, 2084 insertions(+), 1535 deletions(-) create mode 100644 .prettierrc delete mode 100644 src/component/map/Map.vue create mode 100644 src/component/map/MapComponent.vue create mode 100644 src/types/cesium/ClickObject.ts rename src/views/{Index.vue => IndexView.vue} (53%) delete mode 100644 src/views/home/earthquake/Earthquake.vue create mode 100644 src/views/home/earthquake/EarthquakeView.vue delete mode 100644 src/views/home/rainstorm/Rainstorm.vue create mode 100644 src/views/home/rainstorm/RainstormView.vue diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..02057e5 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "vueIndentScriptAndStyle": true +} \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 9e9adbd..da0ff03 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,32 +3,34 @@ diff --git a/src/component/common/InformationBox.vue b/src/component/common/InformationBox.vue index 43b3682..ee0ca4e 100644 --- a/src/component/common/InformationBox.vue +++ b/src/component/common/InformationBox.vue @@ -1,68 +1,81 @@ \ No newline at end of file + } + diff --git a/src/component/map/AdministrativeDivision.vue b/src/component/map/AdministrativeDivision.vue index 0bd3aba..4a25aef 100644 --- a/src/component/map/AdministrativeDivision.vue +++ b/src/component/map/AdministrativeDivision.vue @@ -1,75 +1,102 @@ - \ No newline at end of file + diff --git a/src/component/map/Map.vue b/src/component/map/Map.vue deleted file mode 100644 index 4e4297c..0000000 --- a/src/component/map/Map.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/component/map/MapComponent.vue b/src/component/map/MapComponent.vue new file mode 100644 index 0000000..1128b32 --- /dev/null +++ b/src/component/map/MapComponent.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/src/component/rain-earthquake/BasicComponent.vue b/src/component/rain-earthquake/BasicComponent.vue index 72c8fcf..2736ba1 100644 --- a/src/component/rain-earthquake/BasicComponent.vue +++ b/src/component/rain-earthquake/BasicComponent.vue @@ -5,24 +5,27 @@ - + - + diff --git a/src/component/rain-earthquake/HiddenPointComponent.vue b/src/component/rain-earthquake/HiddenPointComponent.vue index 7796a45..c7b901a 100644 --- a/src/component/rain-earthquake/HiddenPointComponent.vue +++ b/src/component/rain-earthquake/HiddenPointComponent.vue @@ -2,107 +2,129 @@ diff --git a/src/component/rain-earthquake/LoadingPoints.vue b/src/component/rain-earthquake/LoadingPoints.vue index ce6850f..aa453f2 100644 --- a/src/component/rain-earthquake/LoadingPoints.vue +++ b/src/component/rain-earthquake/LoadingPoints.vue @@ -1,27 +1,31 @@ diff --git a/src/component/rain-earthquake/RiskPointComponent.vue b/src/component/rain-earthquake/RiskPointComponent.vue index f36f77e..5617e6b 100644 --- a/src/component/rain-earthquake/RiskPointComponent.vue +++ b/src/component/rain-earthquake/RiskPointComponent.vue @@ -1,37 +1,49 @@ diff --git a/src/config/config.json b/src/config/config.json index d400b08..11182b6 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -1,10 +1,12 @@ { "backendBaseUrl": "__BACKEND_BASE_URL__", "apiBaseUrl": "/api", - "noEncryptUrls": ["/crypto/sm2/public-key"], + "noEncryptUrls": [ + "/crypto/sm2/public-key" + ], "tdMapToken": [ - "fc6cb1139b8eed4f79439130eb34eb00", - "78234e018ed03fe3bb28de976dcfa6d3", + "fc6cb1139b8eed4f79439130eb34eb00", + "78234e018ed03fe3bb28de976dcfa6d3", "2e8111f9bc84149cbf24f562ed4e9229", "88055d3d7f13f8f7e6e8eeb67cf6d78a" ], @@ -12,9 +14,27 @@ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1ZDBjZjAxOS0wMDhhLTRmZjEtYjNmOC1iNmM2ZmY2ZmQ1N2IiLCJpZCI6MjAxMDI1LCJpYXQiOjE3MTAxNTgxNjJ9.mdbJYEzXQkBnHNqpozz7MvZjJ_X9a3JZRGPA-ytGhLI", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiNjczZTVlMy1kNDEwLTRhZWItYWM0NS1mNjYxMzJjODMwYTQiLCJpZCI6MzIxMzI2LCJpYXQiOjE3NzU2NDU1OTd9._MPcZQsxK1dGPl8IMVhKHV3PIPu4-TaOUgzsUUOP6WE" ], - "defaultPosition": [108.948024, 34.263161, 250000], + "defaultPosition": [ + 108.948024, + 34.263161, + 250000 + ], + "defauleRectangleRange": [ + [ + 107.6, + 33.7 + ], + [ + 109.8, + 34.8 + ] + ], + "camera": { + "max": 400000, + "min": 1000 + }, "prefix": { "hiddenDangerPointId": "hidden-danger-point-", "riskPointId": "risk-point-" } -} +} \ No newline at end of file diff --git a/src/hooks/usePointsHandle.ts b/src/hooks/usePointsHandle.ts index 7cfd1e9..b89cb82 100644 --- a/src/hooks/usePointsHandle.ts +++ b/src/hooks/usePointsHandle.ts @@ -1,63 +1,69 @@ -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 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'; /** * 公共批量处理点钩子函数 */ export const usePointsHandle = () => { + /** + * 添加点 + * @param points - 点数据 + * @param getDisasterIcon - 获取灾害图标的函数 + * @param prefix - 前缀 + * @returns 点的ID列表 + */ + function addPoints( + points: Point[], + getDisasterIcon: (disasterType?: string) => string, + prefix: string + ): string[] { + // 设置加载配置 + const options: PrimitiveOptions[] = []; + // 存放id + const ids: string[] = []; - /** - * 添加点 - * @param points - 点数据 - * @param getDisasterIcon - 获取灾害图标的函数 - * @param prefix - 前缀 - * @returns 点的ID列表 - */ - function addPoints(points: Point[], getDisasterIcon: (disasterType?: string) => string, prefix: string): string[] { - // 设置加载配置 - const options: PrimitiveOptions[] = []; + points.forEach((point) => { + try { + if (point.lon === undefined || point.lat === undefined) { + throw new Error(`点位数据缺少经纬度:${point.id}`); + } + // 将经纬度转换为笛卡尔坐标,太高一点高度,方便点击 + const position = Cartesian3.fromDegrees(point.lon, point.lat, 10); - // 存放id - const ids: string[] = []; + const id = `${prefix}${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(`处理点位失败:${error}`); + } + }); + // 批量创建图层 + CesiumUtilsSingleton.addPrimitivesBatch(options); - points.forEach(point => { - try { + return ids; + } - if (point.lon === undefined || point.lat === undefined) { - throw new Error(`点位数据缺少经纬度:${point.id}`); - } - // 将经纬度转换为笛卡尔坐标,太高一点高度,方便点击 - const position = Cartesian3.fromDegrees(point.lon, point.lat, 10); - - const id = `${prefix}${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(`处理点位失败:${error}`); - } - }) - // 批量创建图层 - CesiumUtilsSingleton.addPrimitivesBatch(options); - - return ids - } - - return { addPoints} -} \ No newline at end of file + return { addPoints }; +}; diff --git a/src/main.ts b/src/main.ts index b7e9be7..e34e51b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,18 +1,18 @@ -import { createApp } from 'vue' -import { createPinia } from 'pinia' +import { createApp } from 'vue'; +import { createPinia } from 'pinia'; -import App from './App.vue' -import router from './router' -import * as ElementPlusIconsVue from '@element-plus/icons-vue' -import 'element-plus/dist/index.css' +import App from './App.vue'; +import router from './router'; +import * as ElementPlusIconsVue from '@element-plus/icons-vue'; +import 'element-plus/dist/index.css'; -const app = createApp(App) +const app = createApp(App); for (const [key, component] of Object.entries(ElementPlusIconsVue)) { - app.component(key, component) + app.component(key, component); } -app.use(createPinia()) -app.use(router) +app.use(createPinia()); +app.use(router); -app.mount('#app') +app.mount('#app'); diff --git a/src/router/index.ts b/src/router/index.ts index 23f33fd..367ab81 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,4 +1,4 @@ -import { createRouter, createWebHashHistory } from 'vue-router' +import { createRouter, createWebHashHistory } from 'vue-router'; const router = createRouter({ history: createWebHashHistory(), @@ -6,22 +6,22 @@ const router = createRouter({ { path: '/', name: 'index', - component: () => import('@/views/Index.vue'), + component: () => import('@/views/IndexView.vue'), redirect: 'rainstorm', children: [ { path: 'rainstorm', name: 'rainstorm', - component: () => import('@/views/home/rainstorm/Rainstorm.vue'), + component: () => import('@/views/home/rainstorm/RainstormView.vue'), }, { path: 'earthquake', name: 'earthquake', - component: () => import('@/views/home/earthquake/Earthquake.vue'), - } - ] - } + component: () => import('@/views/home/earthquake/EarthquakeView.vue'), + }, + ], + }, ], -}) +}); -export default router +export default router; diff --git a/src/stores/useCryptStore.ts b/src/stores/useCryptStore.ts index f5d98e1..1d37bb1 100644 --- a/src/stores/useCryptStore.ts +++ b/src/stores/useCryptStore.ts @@ -1,9 +1,9 @@ -import { defineStore } from 'pinia' -import { type Ref, ref } from 'vue' +import { defineStore } from 'pinia'; +import { type Ref, ref } from 'vue'; export const useCryptStore = defineStore('crypt', () => { // sm2公钥 - const sm2PublicKey: Ref = ref('') + const sm2PublicKey: Ref = ref(''); - return { sm2PublicKey } -}) + return { sm2PublicKey }; +}); diff --git a/src/stores/useLoadingInformation.ts b/src/stores/useLoadingInformation.ts index cfbe97b..072001c 100644 --- a/src/stores/useLoadingInformation.ts +++ b/src/stores/useLoadingInformation.ts @@ -1,52 +1,68 @@ -import { Billboard } from 'cesium' -import { defineStore } from 'pinia' -import { type Ref, ref } from 'vue' +import type { ClickObject } from '@/types/cesium/ClickObject'; +import { defineStore } from 'pinia'; +import { type Ref, ref } from 'vue'; /** * 加载信息弹窗相关参数 */ -export const useLoadingInformationStore = defineStore('loadingInformation', () => { - +export const useLoadingInformationStore = defineStore( + 'loadingInformation', + () => { // 点击的对象 - const clickObject: Ref<{ primitive: Billboard | null }> = ref({ primitive: null }) + const clickObject: Ref = ref({ id: '', primitive: null }); // 隐患点 - const loadingHiddenPointInformationStatus: Ref = ref(false) - const hiddenPointId: Ref = ref(-1) + const loadingHiddenPointInformationStatus: Ref = ref(false); + const hiddenPointId: Ref = ref(-1); // 风险点 - const loadingRiskPointInformationStatus: Ref = ref(false) - const riskPointId: Ref = ref(-1) + const loadingRiskPointInformationStatus: Ref = ref(false); + const riskPointId: Ref = ref(-1); // 重置状态 const resetStatue = () => { - loadingHiddenPointInformationStatus.value = false - hiddenPointId.value = -1 - loadingRiskPointInformationStatus.value = false - riskPointId.value = -1 - } + loadingHiddenPointInformationStatus.value = false; + hiddenPointId.value = -1; + loadingRiskPointInformationStatus.value = false; + riskPointId.value = -1; + }; // get/set方法 - const getClickObject = () => clickObject.value - const setClickObject = (value: {primitive: Billboard}) => { - clickObject.value = value - } - const getLoadingHiddenPointInformationStatus = () => loadingHiddenPointInformationStatus.value + const getClickObject = () => clickObject.value; + const setClickObject = (value: ClickObject) => { + clickObject.value = value; + }; + const getLoadingHiddenPointInformationStatus = () => + loadingHiddenPointInformationStatus.value; const setLoadingHiddenPointInformationStatus = (value: boolean) => { - loadingHiddenPointInformationStatus.value = value - } - const getLoadingRiskPointInformationStatus = () => loadingRiskPointInformationStatus.value + loadingHiddenPointInformationStatus.value = value; + }; + const getLoadingRiskPointInformationStatus = () => + loadingRiskPointInformationStatus.value; const setLoadingRiskPointInformationStatus = (value: boolean) => { - loadingRiskPointInformationStatus.value = value - } - const getHiddenPointId = () => hiddenPointId.value + loadingRiskPointInformationStatus.value = value; + }; + const getHiddenPointId = () => hiddenPointId.value; const setHiddenPointId = (value: number) => { - hiddenPointId.value = value - } - const getRiskPointId = () => riskPointId.value + hiddenPointId.value = value; + }; + const getRiskPointId = () => riskPointId.value; const setRiskPointId = (value: number) => { - riskPointId.value = value - } + riskPointId.value = value; + }; - return { resetStatue, getClickObject, setClickObject, getLoadingHiddenPointInformationStatus, setLoadingHiddenPointInformationStatus, getLoadingRiskPointInformationStatus, setLoadingRiskPointInformationStatus, getHiddenPointId, setHiddenPointId, getRiskPointId, setRiskPointId } -}) + return { + resetStatue, + getClickObject, + setClickObject, + getLoadingHiddenPointInformationStatus, + setLoadingHiddenPointInformationStatus, + getLoadingRiskPointInformationStatus, + setLoadingRiskPointInformationStatus, + getHiddenPointId, + setHiddenPointId, + getRiskPointId, + setRiskPointId, + }; + } +); diff --git a/src/stores/useViewerStore.ts b/src/stores/useViewerStore.ts index ee25c03..37075f9 100644 --- a/src/stores/useViewerStore.ts +++ b/src/stores/useViewerStore.ts @@ -1,15 +1,15 @@ -import { defineStore } from 'pinia' -import { type Ref, ref } from 'vue' +import { defineStore } from 'pinia'; +import { type Ref, ref } from 'vue'; export const useViewerStore = defineStore('viewer', () => { - // viewer完成状态 - const viewerLoadingCompleted: Ref = ref(false) + // viewer完成状态 + const viewerLoadingCompleted: Ref = ref(false); - // get/set方法 - const getViewerLoadingCompleted = () => viewerLoadingCompleted.value - const setViewerLoadingCompleted = (value: boolean) => { - viewerLoadingCompleted.value = value - } + // get/set方法 + const getViewerLoadingCompleted = () => viewerLoadingCompleted.value; + const setViewerLoadingCompleted = (value: boolean) => { + viewerLoadingCompleted.value = value; + }; - return { getViewerLoadingCompleted, setViewerLoadingCompleted } -}) + return { getViewerLoadingCompleted, setViewerLoadingCompleted }; +}); diff --git a/src/types/ApiResponse.ts b/src/types/ApiResponse.ts index 3e0e56b..f33828d 100644 --- a/src/types/ApiResponse.ts +++ b/src/types/ApiResponse.ts @@ -1,5 +1,5 @@ export interface ApiResponse { - code: number - message: string - data: T + code: number; + message: string; + data: T; } diff --git a/src/types/base/Point.ts b/src/types/base/Point.ts index b7624e8..a1afdc2 100644 --- a/src/types/base/Point.ts +++ b/src/types/base/Point.ts @@ -1,12 +1,12 @@ export interface Point { - /** 序号 */ - id?: number; - /** 经度 */ - lon?: number; - /** 纬度 */ - lat?: number; - /** 空间 */ - geom?: string; - /** 灾害类型 */ - disasterType?: string; -} \ No newline at end of file + /** 序号 */ + id?: number; + /** 经度 */ + lon?: number; + /** 纬度 */ + lat?: number; + /** 空间 */ + geom?: string; + /** 灾害类型 */ + disasterType?: string; +} diff --git a/src/types/base/XianHiddenDangerSpots.ts b/src/types/base/XianHiddenDangerSpots.ts index 53789f4..0756e97 100644 --- a/src/types/base/XianHiddenDangerSpots.ts +++ b/src/types/base/XianHiddenDangerSpots.ts @@ -1,33 +1,33 @@ -import type { Point } from "./Point"; +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 +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; +} diff --git a/src/types/base/XianRiskSpots.ts b/src/types/base/XianRiskSpots.ts index 8756101..ae795dd 100644 --- a/src/types/base/XianRiskSpots.ts +++ b/src/types/base/XianRiskSpots.ts @@ -1,42 +1,42 @@ -import type { Point } from "./Point"; +import type { Point } from './Point'; export interface XianRiskSpots extends Point { - /** 风险区名称 */ - riskName?: string; - /** 统一编号 */ - unitCode?: string; - /** 风险区等级 */ - riskLevel?: string; - /** 面积 */ - area?: number; - /** 省 */ - province?: string; - /** 市 */ - city?: string; - /** 县 */ - county?: string; - /** 乡 */ - country?: string; - /** 村 */ - village?: string; - /** 位置 */ - position?: string; - /** 居民户数(户) */ - residentCounts?: number; - /** 居民人口(人) */ - addressPopulation?: number; - /** 威胁财产(万元) */ - riskProperty?: number; - /** 常住人口(人) */ - permanentPopulation?: number; - /** 住房(间) */ - housing?: number; - /** 巡查员姓名 */ - inspectorName?: string; - /** 巡查员电话 */ - inspectorTele?: string; - /** 空间 */ - geom?: any; - /** 逻辑删除标识,0未删除,1已删除 */ - isDelete?: number; -} \ No newline at end of file + /** 风险区名称 */ + riskName?: string; + /** 统一编号 */ + unitCode?: string; + /** 风险区等级 */ + riskLevel?: string; + /** 面积 */ + area?: number; + /** 省 */ + province?: string; + /** 市 */ + city?: string; + /** 县 */ + county?: string; + /** 乡 */ + country?: string; + /** 村 */ + village?: string; + /** 位置 */ + position?: string; + /** 居民户数(户) */ + residentCounts?: number; + /** 居民人口(人) */ + addressPopulation?: number; + /** 威胁财产(万元) */ + riskProperty?: number; + /** 常住人口(人) */ + permanentPopulation?: number; + /** 住房(间) */ + housing?: number; + /** 巡查员姓名 */ + inspectorName?: string; + /** 巡查员电话 */ + inspectorTele?: string; + /** 空间 */ + geom?: string; + /** 逻辑删除标识,0未删除,1已删除 */ + isDelete?: number; +} diff --git a/src/types/cesium/CesiumInitOptions.ts b/src/types/cesium/CesiumInitOptions.ts index 11ebb54..d129ed9 100644 --- a/src/types/cesium/CesiumInitOptions.ts +++ b/src/types/cesium/CesiumInitOptions.ts @@ -1,46 +1,46 @@ -import type { Color } from "cesium" -import type { GeoJsonFileType } from "./GeoJsonFileType" +import type { Color } from 'cesium'; +import type { GeoJsonFileType } from './GeoJsonFileType'; /** * Cesium 公共配置选项 * 用于初始化时统一配置 Viewer 参数 */ export interface CesiumInitOptions { - containerId: string // 容器 DOM ID - terrain?: string // 地形服务地址(默认:Cesium 内置地形) + containerId: string; // 容器 DOM ID + terrain?: string; // 地形服务地址(默认:Cesium 内置地形) - shouldAnimate?: boolean // 是否自动播放动画(默认:true) - baseLayerPicker?: boolean // 是否显示图层选择器(默认:false) - timeline?: boolean // 是否显示时间轴(默认:false) - animation?: boolean // 是否显示动画控件(默认:false) - infoBox?: boolean // 是否显示信息框(默认:false) - navigationHelpButton?: boolean // 是否显示导航帮助按钮(默认:false) - fullscreenButton?: boolean // 是否显示全屏按钮(默认:false) - homeButton?: boolean // 是否显示主页按钮(默认:false) - scene3DOnly?: boolean // 是否3D场景(默认:false) - sceneModePicker?: boolean // 场景模式选择器(默认:false) - geocoder?: boolean // 搜索(默认:false) + shouldAnimate?: boolean; // 是否自动播放动画(默认:true) + baseLayerPicker?: boolean; // 是否显示图层选择器(默认:false) + timeline?: boolean; // 是否显示时间轴(默认:false) + animation?: boolean; // 是否显示动画控件(默认:false) + infoBox?: boolean; // 是否显示信息框(默认:false) + navigationHelpButton?: boolean; // 是否显示导航帮助按钮(默认:false) + fullscreenButton?: boolean; // 是否显示全屏按钮(默认:false) + homeButton?: boolean; // 是否显示主页按钮(默认:false) + scene3DOnly?: boolean; // 是否3D场景(默认:false) + sceneModePicker?: boolean; // 场景模式选择器(默认:false) + geocoder?: boolean; // 搜索(默认:false) - sceneMode?: number // 初始场景模式(默认:3D,可选:2D=1, COLUMBUS_VIEW=2) + sceneMode?: number; // 初始场景模式(默认:3D,可选:2D=1, COLUMBUS_VIEW=2) // 遮罩配置 mark?: { // 是否包含遮罩,默认false - include?: boolean + include?: boolean; // GeoJSON 数据,如果要突出显示某一区域,就传递改值 - geoJson?: GeoJsonFileType + geoJson?: GeoJsonFileType; // 孔属于半球,默认东半球 - belongingHemisphere?: 'east' | 'west' + belongingHemisphere?: 'east' | 'west'; // 遮罩颜色,默认黑色 - color?: Color + color?: Color; // 边框 border?: { // 是否显示边框,默认true - show?: boolean + show?: boolean; // 边框颜色,默认白色 - color?: Color + color?: Color; // 边框宽度,默认1 - width?: number - } - } + width?: number; + }; + }; } diff --git a/src/types/cesium/ClickObject.ts b/src/types/cesium/ClickObject.ts new file mode 100644 index 0000000..de6e325 --- /dev/null +++ b/src/types/cesium/ClickObject.ts @@ -0,0 +1,7 @@ +import type { Billboard } from 'cesium'; + +export interface ClickObject { + id: string; + [key: string]: unknown; + primitive: Billboard | null; +} diff --git a/src/types/cesium/EntityOptions.ts b/src/types/cesium/EntityOptions.ts index 6f6c85a..654844c 100644 --- a/src/types/cesium/EntityOptions.ts +++ b/src/types/cesium/EntityOptions.ts @@ -1,50 +1,50 @@ -import type { Cartesian3, Color } from "cesium" -import { HeightReference, MaterialProperty } from 'cesium' +import type { Cartesian3, Color } from 'cesium'; +import { HeightReference, MaterialProperty } from 'cesium'; /** * 实体配置通用类型 * 支持点、线、面、Billboard 等基础实体 */ export interface EntityOptions { - id: string // 实体唯一标识(必填,用于后续查询/删除) - position: Cartesian3 | [number, number, number] // 位置(经纬度高程数组 或 Cartesian3) - type: 'point' | 'polyline' | 'billboard' | 'polygon' // 实体类型 - isDefault?: boolean // 是否为默认实体,默认值false + id: string; // 实体唯一标识(必填,用于后续查询/删除) + position: Cartesian3 | [number, number, number]; // 位置(经纬度高程数组 或 Cartesian3) + type: 'point' | 'polyline' | 'billboard' | 'polygon'; // 实体类型 + isDefault?: boolean; // 是否为默认实体,默认值false // 点配置(type='point' 时必填) pointOptions?: { - color?: Color // 颜色(默认:红色) - pixelSize?: number // 像素大小(默认:8) - outlineColor?: Color // 轮廓颜色(默认:白色) - outlineWidth?: number // 轮廓宽度(默认:1) - heightReference?: HeightReference // 高度参考(默认:CLAMP_TO_GROUND) - } + color?: Color; // 颜色(默认:红色) + pixelSize?: number; // 像素大小(默认:8) + outlineColor?: Color; // 轮廓颜色(默认:白色) + outlineWidth?: number; // 轮廓宽度(默认:1) + heightReference?: HeightReference; // 高度参考(默认:CLAMP_TO_GROUND) + }; // 线配置(type='polyline' 时必填) polylineOptions?: { - positions: Cartesian3[] | [number, number, number][] // 线顶点数组 - color?: Color // 颜色(默认:蓝色) - width?: number // 线宽(默认:3) - clampToGround?: boolean // 是否贴地(默认:false) - } + positions: Cartesian3[] | [number, number, number][]; // 线顶点数组 + color?: Color; // 颜色(默认:蓝色) + width?: number; // 线宽(默认:3) + clampToGround?: boolean; // 是否贴地(默认:false) + }; // Billboard 配置(type='billboard' 时必填) billboardOptions?: { - image: string // 图片地址 - scale?: number // 缩放比例(默认:1) - color?: Color // 颜色(默认:白色) - verticalOrigin?: number // 垂直对齐方式(默认:CENTER) - horizontalOrigin?: number // 水平对齐方式(默认:CENTER) - heightReference?: HeightReference // 高度参考(默认:CLAMP_TO_GROUND) - } + image: string; // 图片地址 + scale?: number; // 缩放比例(默认:1) + color?: Color; // 颜色(默认:白色) + verticalOrigin?: number; // 垂直对齐方式(默认:CENTER) + horizontalOrigin?: number; // 水平对齐方式(默认:CENTER) + heightReference?: HeightReference; // 高度参考(默认:CLAMP_TO_GROUND) + }; // 面配置(type='polygon' 时必填) polygonOptions?: { - hierarchy: Cartesian3[] | [number, number, number][] // 面顶点数组 + hierarchy: Cartesian3[] | [number, number, number][]; // 面顶点数组 // color?: Color // 颜色(默认:绿色) - outline?: boolean // 是否显示轮廓(默认:true) - outlineColor?: Color // 轮廓颜色(默认:黑色) - outlineWidth?: number // 轮廓宽度(默认:1) - height?: number // 高度(默认:0) - extrudedHeight?: number // extrudedHeight 高度(默认:0) - heightReference?: HeightReference // 高度参考(默认:CLAMP_TO_GROUND) - material?: MaterialProperty // 材质(默认:Color.WHITE) - } - attributes?: Record // 自定义属性(用于存储额外信息) -} \ No newline at end of file + outline?: boolean; // 是否显示轮廓(默认:true) + outlineColor?: Color; // 轮廓颜色(默认:黑色) + outlineWidth?: number; // 轮廓宽度(默认:1) + height?: number; // 高度(默认:0) + extrudedHeight?: number; // extrudedHeight 高度(默认:0) + heightReference?: HeightReference; // 高度参考(默认:CLAMP_TO_GROUND) + material?: MaterialProperty; // 材质(默认:Color.WHITE) + }; + attributes?: Record; // 自定义属性(用于存储额外信息) +} diff --git a/src/types/cesium/GeoJsonFileType.ts b/src/types/cesium/GeoJsonFileType.ts index 034b34e..2484cfe 100644 --- a/src/types/cesium/GeoJsonFileType.ts +++ b/src/types/cesium/GeoJsonFileType.ts @@ -1,8 +1,8 @@ export interface GeoJsonFileType { - type: "FeatureCollection"; + type: 'FeatureCollection'; features: { geometry: { coordinates: number[][][][]; }; }[]; -} \ No newline at end of file +} diff --git a/src/types/cesium/GeoJsonOptions.ts b/src/types/cesium/GeoJsonOptions.ts index 0a0f1f0..6405d18 100644 --- a/src/types/cesium/GeoJsonOptions.ts +++ b/src/types/cesium/GeoJsonOptions.ts @@ -1,9 +1,5 @@ -import type { - Cartesian3, - Color, - DataSource, -} from "cesium"; -import type { LabelConfig } from "./LabelConfig"; +import type { Cartesian3, Color, DataSource } from 'cesium'; +import type { LabelConfig } from './LabelConfig'; // 数据源:字符串路径/URL | GeoJSON对象 export type CustomizeGeoJsonDataSource = string | object; @@ -33,4 +29,4 @@ export interface GeoJsonOptions { outlineWidth?: number; }; onComplete?: (dataSource: DataSource) => void; -} \ No newline at end of file +} diff --git a/src/types/cesium/LabelConfig.ts b/src/types/cesium/LabelConfig.ts index be47860..26e36a8 100644 --- a/src/types/cesium/LabelConfig.ts +++ b/src/types/cesium/LabelConfig.ts @@ -1,13 +1,18 @@ -import type { Cartesian3, Color, HorizontalOrigin, VerticalOrigin } from 'cesium' +import type { + Cartesian3, + Color, + HorizontalOrigin, + VerticalOrigin, +} from 'cesium'; export interface LabelConfig { - labelText?: string // 文本,默认空白 - labelFont?: string // 字体样式,默认16px "微软雅黑" - labelColor?: Color // 标签颜色, 默认白色 - labelSize?: number // 字体大小,默认16 - labelOffset?: { x: number; y: number } // 标签偏移,默认0,0 - horizontalOrigin?: HorizontalOrigin // 水平位置,默认居中 - verticalOrigin?: VerticalOrigin // 垂直位置,默认居中 - backgroundColor?: Color // 背景颜色,默认透明 - center?: Cartesian3 | [number, number, number] -} \ No newline at end of file + labelText?: string; // 文本,默认空白 + labelFont?: string; // 字体样式,默认16px "微软雅黑" + labelColor?: Color; // 标签颜色, 默认白色 + labelSize?: number; // 字体大小,默认16 + labelOffset?: { x: number; y: number }; // 标签偏移,默认0,0 + horizontalOrigin?: HorizontalOrigin; // 水平位置,默认居中 + verticalOrigin?: VerticalOrigin; // 垂直位置,默认居中 + backgroundColor?: Color; // 背景颜色,默认透明 + center?: Cartesian3 | [number, number, number]; +} diff --git a/src/types/cesium/LayerConfig.ts b/src/types/cesium/LayerConfig.ts index ec999b2..59580ef 100644 --- a/src/types/cesium/LayerConfig.ts +++ b/src/types/cesium/LayerConfig.ts @@ -1,21 +1,20 @@ export interface LayerConfig { - id: string // 唯一id + id: string; // 唯一id - type: 'imagery' | 'wms' | 'wmts' // 图层类型,支持iimagery, Geoserver WMS, Geoserver WMTS - provider: string // 图层提供者 - url: string // 图层地址 - layers: string // 图层名称 + type: 'imagery' | 'wms' | 'wmts'; // 图层类型,支持iimagery, Geoserver WMS, Geoserver WMTS + provider: string; // 图层提供者 + url: string; // 图层地址 + layers: string; // 图层名称 - isDefault?: boolean // 是否为默认图层,默认值false + isDefault?: boolean; // 是否为默认图层,默认值false /** * WMTS 图层参数 */ - style?: string // 图层样式,默认为default - format?: string // 图层格式,默认为image/png - tileMatrixSetID?: string // 瓦片矩阵集ID,默认为EPSG:4326 - credit?: string // 图层版权信息,默认为空 + style?: string; // 图层样式,默认为default + format?: string; // 图层格式,默认为image/png + tileMatrixSetID?: string; // 瓦片矩阵集ID,默认为EPSG:4326 + credit?: string; // 图层版权信息,默认为空 - - parameters?: Record // 图层参数 + parameters?: Record; // 图层参数 } diff --git a/src/types/cesium/PrimitiveOptions.ts b/src/types/cesium/PrimitiveOptions.ts index 0056e5f..e41c4ea 100644 --- a/src/types/cesium/PrimitiveOptions.ts +++ b/src/types/cesium/PrimitiveOptions.ts @@ -1,15 +1,15 @@ -import type { Cartesian3, Color, NearFarScalar } from 'cesium' +import type { Cartesian3, Color, NearFarScalar } from 'cesium'; export interface PrimitiveOptions { - id: string - type: 'point' | 'polyline' | 'polygon' | 'billboard' - positions: [number, number, number][] | Cartesian3[] // 点集合,线和面需要多个点 - isDefault?: boolean // 是否为默认图元,默认值false - color?: Color - pixelSize?: number // 点大小 - width?: number // 线宽 - image?: string // 广告牌图片 - scale?: number // 广告牌缩放 - scaleByDistance?: NearFarScalar // 广告牌距离衰减缩放 - customProperties?: Record // 自定义属性对象 + id: string; + type: 'point' | 'polyline' | 'polygon' | 'billboard'; + positions: [number, number, number][] | Cartesian3[]; // 点集合,线和面需要多个点 + isDefault?: boolean; // 是否为默认图元,默认值false + color?: Color; + pixelSize?: number; // 点大小 + width?: number; // 线宽 + image?: string; // 广告牌图片 + scale?: number; // 广告牌缩放 + scaleByDistance?: NearFarScalar; // 广告牌距离衰减缩放 + customProperties?: Record; // 自定义属性对象 } diff --git a/src/types/common/DisasterType.ts b/src/types/common/DisasterType.ts index f4cbe01..3ea6033 100644 --- a/src/types/common/DisasterType.ts +++ b/src/types/common/DisasterType.ts @@ -1,4 +1,4 @@ export enum DisasterType { - RAINSTORM = 'rainstorm', - EARTHQUAKE= 'earthquake' -} \ No newline at end of file + RAINSTORM = 'rainstorm', + EARTHQUAKE = 'earthquake', +} diff --git a/src/types/crypto/Sm2PublicKeyResponse.ts b/src/types/crypto/Sm2PublicKeyResponse.ts index 7f60e8d..b3bab4a 100644 --- a/src/types/crypto/Sm2PublicKeyResponse.ts +++ b/src/types/crypto/Sm2PublicKeyResponse.ts @@ -2,5 +2,5 @@ export interface Sm2PublicKeyResponse { /** * 公钥 */ - publicKey: string + publicKey: string; } diff --git a/src/utils/cesium/CameraController.ts b/src/utils/cesium/CameraController.ts index 2d16db0..f484438 100644 --- a/src/utils/cesium/CameraController.ts +++ b/src/utils/cesium/CameraController.ts @@ -1,18 +1,19 @@ -import { - Cartesian3, - Cartographic, - Math as CesiumMath, -} from 'cesium' -import type { Viewer } from 'cesium' +import { Cartesian3, Cartographic, Math as CesiumMath } from 'cesium'; +import { Rectangle, type Viewer } from 'cesium'; /** * 相机控制器 */ export class CameraController { - #viewer: Viewer + #viewer: Viewer; + + // 设置相机相关参数 + #minHeight: number = 0; + #maxHeight: number = Number.MAX_VALUE; + #oldListener: (() => void) | null = null; constructor(viewer: Viewer) { - this.#viewer = viewer + this.#viewer = viewer; } /** @@ -20,17 +21,20 @@ export class CameraController { * @param target - 目标位置 [经度, 纬度, 高度] 或 Cartesian3 * @param duration - 飞行持续时间(秒,默认 2) */ - flyToTarget(target: [number, number, number] | Cartesian3, duration = 2): void { - const position = this.#convertPosition(target) - const cartographic = Cartographic.fromCartesian(position) + flyToTarget( + target: [number, number, number] | Cartesian3, + duration = 2 + ): void { + const position = this.#convertPosition(target); + const cartographic = Cartographic.fromCartesian(position); this.#viewer.camera.flyTo({ destination: Cartesian3.fromDegrees( CesiumMath.toDegrees(cartographic.longitude), CesiumMath.toDegrees(cartographic.latitude), - cartographic.height, + cartographic.height ), duration, - }) + }); } /** @@ -38,7 +42,7 @@ export class CameraController { * @param target - 目标位置 [经度, 纬度, 高度] 或 Cartesian3 */ viewToTarget(target: [number, number, number] | Cartesian3): void { - const position = this.#convertPosition(target) + const position = this.#convertPosition(target); this.#viewer?.camera.setView({ destination: position, orientation: { @@ -46,12 +50,138 @@ export class CameraController { pitch: CesiumMath.toRadians(-90), roll: 0.0, }, - }) + }); + } + + /** + * 滚轮事件处理 + * @param event - 滚轮事件 + * @param minHeight - 最小高度 + * @param maxHeight - 最大高度 + */ + setHeightLimits(minHeight: number, maxHeight: number): void { + this.#minHeight = minHeight; + this.#maxHeight = maxHeight; + + // 移除旧的监听器 + if (this.#oldListener) { + this.#removeOldListener(); + } + + // 添加相机移动结束监听器,约束高度 + this.#oldListener = this.#viewer.camera.moveEnd.addEventListener(() => { + const currentHeight = this.#getCameraHeight(); + + if (currentHeight < this.#minHeight) { + // 低于最小高度,调整到最小高度 + this.#setCameraHeight(this.#minHeight); + } else if (currentHeight > this.#maxHeight) { + // 高于最大高度,调整到最大高度 + this.#setCameraHeight(this.#maxHeight); + } + }); + } + + /** + * 清除高度限制 + */ + clearHeightLimits(): void { + if (this.#oldListener) { + this.#removeOldListener(); + this.#oldListener = null; + } + this.#minHeight = 0; + this.#maxHeight = Number.MAX_VALUE; + } + + /** + * 监听相机移动结束事件,并判断相机是否超出指定矩形范围 + * @param rectangle - 矩形范围 + * @param duration - 飞行持续时间(秒) + */ + outOverView(rectangle: Rectangle, duration: number): void { + // 监听相机移动结束事件 + this.#viewer.scene.camera.moveEnd.addEventListener(() => { + if (this.#isOutOverView(rectangle)) { + // 执行飞行动画,飞回西安范围 + this.#viewer.camera.flyTo({ + destination: rectangle, + duration: duration, + }); + } + }); } // ===================== 私有方法 ===================== #convertPosition(pos: Cartesian3 | [number, number, number]): Cartesian3 { - return Array.isArray(pos) ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) : pos + return Array.isArray(pos) + ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) + : pos; + } + + /** + * 获取相机高度 + * @returns 相机高度 + */ + #getCameraHeight(): number { + const cartographic = Cartographic.fromCartesian( + this.#viewer.camera.position + ); + return cartographic.height; + } + + /** + * 设置相机高度 + * @param height - 目标高度(米) + */ + #setCameraHeight(height: number): void { + const cartographic = Cartographic.fromCartesian( + this.#viewer.camera.position + ); + const newPosition = Cartesian3.fromRadians( + cartographic.longitude, + cartographic.latitude, + height + ); + this.#viewer.camera.setView({ + destination: newPosition, + }); + } + + /** + * 移除旧的监听器 + */ + #removeOldListener(): void { + if (this.#oldListener) { + this.#viewer.camera.moveEnd.removeEventListener(this.#oldListener); + this.#oldListener = null; + } + } + + /** + * 判断相机是否超出指定矩形范围 + * @param rectangle - 矩形范围 + * @returns 是否超出 + */ + #isOutOverView(rectangle: Rectangle): boolean { + // 获取当前相机的可视范围 + const currentViewRect = this.#viewer.camera.computeViewRectangle(); + + // 如果无法获取可视范围,直接返回 false,不做处理 + if (!currentViewRect) return false; + + // 判断两个矩形是否相交 + // 如果相交,返回false, 如果不相交,返回true + const intersectionResult = Rectangle.intersection( + currentViewRect, + rectangle + ); + + // 如果不相交,则说明已经超出视角 + if (!intersectionResult) { + return true; + } + return false; } } diff --git a/src/utils/cesium/CesiumUtils.ts b/src/utils/cesium/CesiumUtils.ts index f0fab9e..670c486 100644 --- a/src/utils/cesium/CesiumUtils.ts +++ b/src/utils/cesium/CesiumUtils.ts @@ -1,34 +1,51 @@ -import type { CesiumInitOptions } from '@/types/cesium/CesiumInitOptions' -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, ScreenSpaceEventHandler, ScreenSpaceEventType, Cartesian2, SceneTransforms, Color } from 'cesium' -import { CesiumViewerManager } from './CesiumViewerManager' -import { EntityManager } from './EntityManager' -import { PrimitiveManager } from './PrimitiveManager' -import { LayerManager } from './LayerManager' -import { GeoJsonManager, type ClearType } from './GeoJsonManager' -import { CameraController } from './CameraController' -import type { GeoJsonFileType } from '@/types/cesium/GeoJsonFileType' +import type { CesiumInitOptions } from '@/types/cesium/CesiumInitOptions'; +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, + ScreenSpaceEventHandler, + ScreenSpaceEventType, + Cartesian2, + SceneTransforms, + Rectangle, +} from 'cesium'; +import { CesiumViewerManager } from './CesiumViewerManager'; +import { EntityManager } from './EntityManager'; +import { PrimitiveManager } from './PrimitiveManager'; +import { LayerManager } from './LayerManager'; +import { GeoJsonManager, type ClearType } from './GeoJsonManager'; +import { CameraController } from './CameraController'; +import config from '@/config/config.json'; +import type { ClickObject } from '@/types/cesium/ClickObject'; // 导出 ClearType 类型 -export type { ClearType } +export type { ClearType }; /** * Cesium 工具类(重构版 - 委托模式) */ export class CesiumUtils { // 管理器实例 - #viewerManager: CesiumViewerManager - #entityManager: EntityManager | null = null - #primitiveManager: PrimitiveManager | null = null - #layerManager: LayerManager | null = null - #geoJsonManager: GeoJsonManager | null = null - #cameraController: CameraController | null = null + #viewerManager: CesiumViewerManager; + #entityManager: EntityManager | null = null; + #primitiveManager: PrimitiveManager | null = null; + #layerManager: LayerManager | null = null; + #geoJsonManager: GeoJsonManager | null = null; + #cameraController: CameraController | null = null; constructor() { - this.#viewerManager = new CesiumViewerManager() + this.#viewerManager = new CesiumViewerManager(); } /** @@ -37,24 +54,65 @@ export class CesiumUtils { * @param type - 底图类型:0=影像图,1=矢量图(默认 0) * @param tdMapToken - 天地图 Token 数组(可选) */ - async initCesiumViewer(options: CesiumInitOptions, type: number = 0, tdMapToken?: string[]): Promise { - await this.#viewerManager.initCesiumViewer(options, type, tdMapToken) + async initCesiumViewer( + options: CesiumInitOptions, + type: number = 0, + tdMapToken?: string[] + ): Promise { + await this.#viewerManager.initCesiumViewer(options, type, tdMapToken); - const viewer = this.#viewerManager.getViewer() + const viewer = this.#viewerManager.getViewer(); if (viewer) { - this.#entityManager = new EntityManager(viewer) - this.#primitiveManager = new PrimitiveManager(viewer) - this.#layerManager = new LayerManager(viewer) - this.#geoJsonManager = new GeoJsonManager(viewer) - this.#cameraController = new CameraController(viewer) + this.#entityManager = new EntityManager(viewer); + this.#primitiveManager = new PrimitiveManager(viewer); + this.#layerManager = new LayerManager(viewer); + this.#geoJsonManager = new GeoJsonManager(viewer); + this.#cameraController = new CameraController(viewer); } } + /** + * 设置相机高度限制 + * @param minHeight - 最小高度 + * @param maxHeight - 最大高度 + */ + setHeightLimits( + minHeight: number = config.camera.min, + maxHeight: number = config.camera.max + ) { + this.#cameraController!.setHeightLimits(minHeight, maxHeight); + } + + /** + * 清除相机高度限制 + */ + clearHightLimits(): void { + this.#cameraController!.clearHeightLimits(); + } + + /** + * 监听相机移动结束事件,并判断相机是否超出指定矩形范围 + * @param range - 矩形范围[左下角经纬度,右上角经纬度] + * @param duration - 飞行持续时间(秒),默认1秒 + */ + outOverView( + range: [ + [number, number], + [number, number], + ] = config.defauleRectangleRange as [[number, number], [number, number]], + duration: number = 1 + ) { + const rectangle = Rectangle.fromDegrees(...range[0], ...range[1]); + this.#cameraController!.outOverView(rectangle, duration); + } + /** * 销毁 Cesium Viewer */ destroyCesiumViewer(): void { - this.#viewerManager.destroyCesiumViewer(() => this.clearAllResources('all')) + this.#viewerManager.destroyCesiumViewer(() => + this.clearAllResources('all') + ); } // ===================== 实体管理 ===================== @@ -65,8 +123,8 @@ export class CesiumUtils { * @returns 创建的 Entity 实例 */ addCesiumEntity(entityOptions: EntityOptions): Entity { - this.#checkManager(this.#entityManager, 'EntityManager') - return this.#entityManager!.addCesiumEntity(entityOptions) + this.#checkManager(this.#entityManager, 'EntityManager'); + return this.#entityManager!.addCesiumEntity(entityOptions); } /** @@ -75,8 +133,8 @@ export class CesiumUtils { * @returns 创建的 Entity 实例数组 */ addCesiumEntitiesBatch(entityOptionsList: EntityOptions[]): Entity[] { - this.#checkManager(this.#entityManager, 'EntityManager') - return this.#entityManager!.addCesiumEntitiesBatch(entityOptionsList) + this.#checkManager(this.#entityManager, 'EntityManager'); + return this.#entityManager!.addCesiumEntitiesBatch(entityOptionsList); } /** @@ -85,8 +143,8 @@ export class CesiumUtils { * @returns Entity 实例,不存在则返回 null */ getCesiumEntityById(entityId: string): Entity | null { - this.#checkManager(this.#entityManager, 'EntityManager') - return this.#entityManager!.getCesiumEntityById(entityId) + this.#checkManager(this.#entityManager, 'EntityManager'); + return this.#entityManager!.getCesiumEntityById(entityId); } /** @@ -95,8 +153,8 @@ export class CesiumUtils { * @returns 是否删除成功 */ removeCesiumEntity(entityId: string): boolean { - this.#checkManager(this.#entityManager, 'EntityManager') - return this.#entityManager!.removeCesiumEntity(entityId) + this.#checkManager(this.#entityManager, 'EntityManager'); + return this.#entityManager!.removeCesiumEntity(entityId); } /** @@ -104,8 +162,8 @@ export class CesiumUtils { * @param entityIds - 实体 ID 数组 */ batchRemoveCesiumEntities(entityIds: string[]): void { - this.#checkManager(this.#entityManager, 'EntityManager') - this.#entityManager!.batchRemoveCesiumEntities(entityIds) + this.#checkManager(this.#entityManager, 'EntityManager'); + this.#entityManager!.batchRemoveCesiumEntities(entityIds); } /** @@ -113,8 +171,8 @@ export class CesiumUtils { * @param clearType - 清除类型:'default'=默认实体,'custom'=自定义实体,'all'=所有实体(默认 'custom') */ clearAllEntities(clearType: ClearType = 'custom'): void { - this.#checkManager(this.#entityManager, 'EntityManager') - this.#entityManager!.clearAllEntities(clearType) + this.#checkManager(this.#entityManager, 'EntityManager'); + this.#entityManager!.clearAllEntities(clearType); } /** @@ -123,8 +181,8 @@ export class CesiumUtils { * @returns 实体 ID 集合 */ getEntityIds(clearType: ClearType = 'all'): Set { - this.#checkManager(this.#entityManager, 'EntityManager') - return this.#entityManager!.getEntityIds(clearType) + this.#checkManager(this.#entityManager, 'EntityManager'); + return this.#entityManager!.getEntityIds(clearType); } // ===================== Primitive 管理 ===================== @@ -134,8 +192,8 @@ export class CesiumUtils { * @param primitive - Primitive 配置选项 */ addPrimitive(primitive: PrimitiveOptions): void { - this.#checkManager(this.#primitiveManager, 'PrimitiveManager') - this.#primitiveManager!.addPrimitive(primitive) + this.#checkManager(this.#primitiveManager, 'PrimitiveManager'); + this.#primitiveManager!.addPrimitive(primitive); } /** @@ -145,8 +203,8 @@ export class CesiumUtils { * @param primitives - Primitive 配置选项数组 */ addPrimitivesBatch(primitives: PrimitiveOptions[]): void { - this.#checkManager(this.#primitiveManager, 'PrimitiveManager') - this.#primitiveManager!.addPrimitivesBatch(primitives) + this.#checkManager(this.#primitiveManager, 'PrimitiveManager'); + this.#primitiveManager!.addPrimitivesBatch(primitives); } /** @@ -155,8 +213,8 @@ export class CesiumUtils { * @returns Primitive 或 BillboardCollection 实例,不存在则返回 undefined */ getPrimitiveById(id: string): Primitive | BillboardCollection | undefined { - this.#checkManager(this.#primitiveManager, 'PrimitiveManager') - return this.#primitiveManager!.getPrimitiveById(id) + this.#checkManager(this.#primitiveManager, 'PrimitiveManager'); + return this.#primitiveManager!.getPrimitiveById(id); } /** @@ -165,8 +223,8 @@ export class CesiumUtils { * @returns 是否删除成功 */ removePrimitiveById(id: string): boolean { - this.#checkManager(this.#primitiveManager, 'PrimitiveManager') - return this.#primitiveManager!.removePrimitiveById(id) + this.#checkManager(this.#primitiveManager, 'PrimitiveManager'); + return this.#primitiveManager!.removePrimitiveById(id); } /** @@ -174,8 +232,8 @@ export class CesiumUtils { * @param clearType - 清除类型:'default'=默认 Primitive,'custom'=自定义 Primitive,'all'=所有 Primitive(默认 'custom') */ clearAllPrimitives(clearType: ClearType = 'custom'): void { - this.#checkManager(this.#primitiveManager, 'PrimitiveManager') - this.#primitiveManager!.clearAllPrimitives(clearType) + this.#checkManager(this.#primitiveManager, 'PrimitiveManager'); + this.#primitiveManager!.clearAllPrimitives(clearType); } /** @@ -184,8 +242,8 @@ export class CesiumUtils { * @returns Primitive ID 集合 */ getPrimitiveIds(clearType: ClearType = 'all'): Set { - this.#checkManager(this.#primitiveManager, 'PrimitiveManager') - return this.#primitiveManager!.getPrimitiveIds(clearType) + this.#checkManager(this.#primitiveManager, 'PrimitiveManager'); + return this.#primitiveManager!.getPrimitiveIds(clearType); } // ===================== 图层管理 ===================== @@ -196,8 +254,8 @@ export class CesiumUtils { * @returns 创建的 ImageryLayer 实例数组(失败的为 null) */ createLayersBatch(layerConfigs: LayerConfig[]): (ImageryLayer | null)[] { - this.#checkManager(this.#layerManager, 'LayerManager') - return this.#layerManager!.createLayersBatch(layerConfigs) + this.#checkManager(this.#layerManager, 'LayerManager'); + return this.#layerManager!.createLayersBatch(layerConfigs); } /** @@ -206,8 +264,8 @@ export class CesiumUtils { * @returns 创建的 ImageryLayer 实例,失败则返回 null */ createLayer(layerConfig: LayerConfig): ImageryLayer | null { - this.#checkManager(this.#layerManager, 'LayerManager') - return this.#layerManager!.createLayer(layerConfig) + this.#checkManager(this.#layerManager, 'LayerManager'); + return this.#layerManager!.createLayer(layerConfig); } /** @@ -216,8 +274,8 @@ export class CesiumUtils { * @returns ImageryLayer 实例,不存在则返回 undefined */ getLayerByKey(key: string): ImageryLayer | undefined { - this.#checkManager(this.#layerManager, 'LayerManager') - return this.#layerManager!.getLayerByKey(key) + this.#checkManager(this.#layerManager, 'LayerManager'); + return this.#layerManager!.getLayerByKey(key); } /** @@ -226,8 +284,8 @@ export class CesiumUtils { * @returns 是否删除成功 */ removeLayerByKey(key: string): boolean { - this.#checkManager(this.#layerManager, 'LayerManager') - return this.#layerManager!.removeLayerByKey(key) + this.#checkManager(this.#layerManager, 'LayerManager'); + return this.#layerManager!.removeLayerByKey(key); } /** @@ -235,8 +293,8 @@ export class CesiumUtils { * @param layerIds - 图层 ID 数组 */ batchRemoveLayers(layerIds: string[]): void { - this.#checkManager(this.#layerManager, 'LayerManager') - this.#layerManager!.batchRemoveLayers(layerIds) + this.#checkManager(this.#layerManager, 'LayerManager'); + this.#layerManager!.batchRemoveLayers(layerIds); } /** @@ -244,8 +302,8 @@ export class CesiumUtils { * @param clearType - 清除类型:'default'=默认图层,'custom'=自定义图层,'all'=所有图层(默认 'custom') */ clearAllLayers(clearType: ClearType = 'custom'): void { - this.#checkManager(this.#layerManager, 'LayerManager') - this.#layerManager!.clearAllLayers(clearType) + this.#checkManager(this.#layerManager, 'LayerManager'); + this.#layerManager!.clearAllLayers(clearType); } /** @@ -254,8 +312,8 @@ export class CesiumUtils { * @returns 图层 Key 集合 */ getLayerKeys(clearType: ClearType = 'all'): Set { - this.#checkManager(this.#layerManager, 'LayerManager') - return this.#layerManager!.getLayerKeys(clearType) + this.#checkManager(this.#layerManager, 'LayerManager'); + return this.#layerManager!.getLayerKeys(clearType); } // ===================== GeoJSON 图层管理 ===================== @@ -264,18 +322,16 @@ export class CesiumUtils { * 添加 GeoJSON 图层 * @param layerId - 图层唯一标识 * @param geojsonData - GeoJSON 数据(路径、URL 或对象) - * @param isDefault - 是否为默认图层(默认 false) * @param options - 配置选项(样式、标签等) * @returns Promise 数据源实例 */ async addGeoJsonLayer( layerId: string, geojsonData: CustomizeGeoJsonDataSource, - isDefault: boolean = false, options?: GeoJsonOptions ): Promise { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.addGeoJsonLayer(layerId, geojsonData, isDefault, options) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.addGeoJsonLayer(layerId, geojsonData, options); } /** @@ -284,8 +340,8 @@ export class CesiumUtils { * @returns DataSource 实例,不存在则返回 undefined */ getGeoJsonLayerById(layerId: string): DataSource | undefined { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.getGeoJsonLayerById(layerId) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.getGeoJsonLayerById(layerId); } /** @@ -294,8 +350,8 @@ export class CesiumUtils { * @returns 是否删除成功 */ removeGeoJsonLayer(layerId: string): boolean { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.removeGeoJsonLayer(layerId) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.removeGeoJsonLayer(layerId); } /** @@ -304,14 +360,14 @@ export class CesiumUtils { */ async batchAddGeoJsonLayers( layerConfigs: Array<{ - layerId: string - geojsonData: CustomizeGeoJsonDataSource - isDefault?: boolean - options?: GeoJsonOptions + layerId: string; + geojsonData: CustomizeGeoJsonDataSource; + isDefault?: boolean; + options?: GeoJsonOptions; }> ): Promise { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - await this.#geoJsonManager!.batchAddGeoJsonLayers(layerConfigs) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + await this.#geoJsonManager!.batchAddGeoJsonLayers(layerConfigs); } /** @@ -319,8 +375,8 @@ export class CesiumUtils { * @param layerIds - 图层 ID 数组 */ batchRemoveGeoJsonLayers(layerIds: string[]): void { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - this.#geoJsonManager!.batchRemoveGeoJsonLayers(layerIds) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + this.#geoJsonManager!.batchRemoveGeoJsonLayers(layerIds); } /** @@ -328,8 +384,8 @@ export class CesiumUtils { * @param clearType - 清除类型:'default'=默认图层,'custom'=自定义图层,'all'=所有图层(默认 'custom') */ clearAllGeoJsonLayers(clearType: ClearType = 'custom'): void { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - this.#geoJsonManager!.clearAllGeoJsonLayers(clearType) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + this.#geoJsonManager!.clearAllGeoJsonLayers(clearType); } /** @@ -338,8 +394,8 @@ export class CesiumUtils { * @returns 是否操作成功 */ showGeoJsonLayer(layerId: string): boolean { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.showGeoJsonLayer(layerId) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.showGeoJsonLayer(layerId); } /** @@ -348,8 +404,8 @@ export class CesiumUtils { * @returns 是否操作成功 */ hideGeoJsonLayer(layerId: string): boolean { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.hideGeoJsonLayer(layerId) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.hideGeoJsonLayer(layerId); } /** @@ -358,8 +414,8 @@ export class CesiumUtils { * @returns 切换后的显示状态,图层不存在则返回 null */ toggleGeoJsonLayer(layerId: string): boolean | null { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.toggleGeoJsonLayer(layerId) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.toggleGeoJsonLayer(layerId); } /** @@ -368,8 +424,8 @@ export class CesiumUtils { * @returns 成功显示的图层数量 */ batchShowGeoJsonLayers(layerIds: string[]): number { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.batchShowGeoJsonLayers(layerIds) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.batchShowGeoJsonLayers(layerIds); } /** @@ -378,8 +434,8 @@ export class CesiumUtils { * @returns 成功隐藏的图层数量 */ batchHideGeoJsonLayers(layerIds: string[]): number { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.batchHideGeoJsonLayers(layerIds) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.batchHideGeoJsonLayers(layerIds); } /** @@ -388,8 +444,8 @@ export class CesiumUtils { * @returns 显示状态,图层不存在则返回 null */ getGeoJsonLayerVisibility(layerId: string): boolean | null { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.getGeoJsonLayerVisibility(layerId) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.getGeoJsonLayerVisibility(layerId); } /** @@ -398,19 +454,21 @@ export class CesiumUtils { * @returns GeoJSON 图层 ID 集合 */ getGeoJsonLayerIds(clearType: ClearType = 'all'): Set { - this.#checkManager(this.#geoJsonManager, 'GeoJsonManager') - return this.#geoJsonManager!.getGeoJsonLayerIds(clearType) + this.#checkManager(this.#geoJsonManager, 'GeoJsonManager'); + return this.#geoJsonManager!.getGeoJsonLayerIds(clearType); } // ===================== 图层操作 ===================== /** * 监听点击事件 */ - clickLayer(callback: (pickedObject: object) => void) { + clickLayer(callback: (pickedObject: ClickObject) => void) { const handler = new ScreenSpaceEventHandler(this.getViewer()?.scene.canvas); - handler.setInputAction((clickEvent: {position: Cartesian2}) => { + handler.setInputAction((clickEvent: { position: Cartesian2 }) => { // 在点击位置进行拾取 - const pickedObject = CesiumUtilsSingleton.getViewer()?.scene.pick(clickEvent.position); + const pickedObject = CesiumUtilsSingleton.getViewer()?.scene.pick( + clickEvent.position + ); callback(pickedObject); }, ScreenSpaceEventType.LEFT_CLICK); } @@ -422,9 +480,12 @@ export class CesiumUtils { * @param target - 目标位置 [经度, 纬度, 高度] 或 Cartesian3 * @param duration - 飞行持续时间(秒,默认 2) */ - flyToTarget(target: [number, number, number] | Cartesian3, duration = 2): void { - this.#checkManager(this.#cameraController, 'CameraController') - this.#cameraController!.flyToTarget(target, duration) + flyToTarget( + target: [number, number, number] | Cartesian3, + duration = 2 + ): void { + this.#checkManager(this.#cameraController, 'CameraController'); + this.#cameraController!.flyToTarget(target, duration); } /** @@ -432,8 +493,8 @@ export class CesiumUtils { * @param target - 目标位置 [经度, 纬度, 高度] 或 Cartesian3 */ viewToTarget(target: [number, number, number] | Cartesian3): void { - this.#checkManager(this.#cameraController, 'CameraController') - this.#cameraController!.viewToTarget(target) + this.#checkManager(this.#cameraController, 'CameraController'); + this.#cameraController!.viewToTarget(target); } // ===================== 清除与资源管理 ===================== @@ -443,10 +504,10 @@ export class CesiumUtils { * @param clearType - 清除类型:'default'=默认资源,'custom'=自定义资源,'all'=所有资源(默认 'custom') */ clearAllResources(clearType: ClearType = 'custom'): void { - this.clearAllEntities(clearType) - this.clearAllPrimitives(clearType) - this.clearAllLayers(clearType) - this.clearAllGeoJsonLayers(clearType) + this.clearAllEntities(clearType); + this.clearAllPrimitives(clearType); + this.clearAllLayers(clearType); + this.clearAllGeoJsonLayers(clearType); } // ===================== getter 和 setter函数 ===================== @@ -456,7 +517,7 @@ export class CesiumUtils { * @returns Viewer 实例,如果未初始化则返回 null */ getViewer(): Viewer | null { - return this.#viewerManager.getViewer() + return this.#viewerManager.getViewer(); } // ===================== 辅助函数 ===================== @@ -467,7 +528,9 @@ export class CesiumUtils { * @returns Cartesian3 坐标 */ convertPosition(pos: Cartesian3 | [number, number, number]): Cartesian3 { - return Array.isArray(pos) ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) : pos + return Array.isArray(pos) + ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) + : pos; } /** @@ -475,8 +538,10 @@ export class CesiumUtils { * @param positions - 位置坐标数组 * @returns Cartesian3 坐标数组 */ - convertPositionArray(positions: (Cartesian3 | [number, number, number])[]): Cartesian3[] { - return positions.map((pos) => this.convertPosition(pos)) + convertPositionArray( + positions: (Cartesian3 | [number, number, number])[] + ): Cartesian3[] { + return positions.map((pos) => this.convertPosition(pos)); } /** @@ -484,9 +549,12 @@ export class CesiumUtils { * @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} + convertScreenPosition(pos: Cartesian3): { x: number; y: number } { + const windowCoord = SceneTransforms.wgs84ToWindowCoordinates( + this.getViewer()!.scene, + pos + ); + return { x: windowCoord.x, y: windowCoord.y }; } // ===================== 私有方法 ===================== @@ -496,9 +564,9 @@ export class CesiumUtils { * @param manager - 管理器实例 * @param managerName - 管理器名称(用于错误提示) */ - #checkManager(manager: any, managerName: string): void { + #checkManager(manager: unknown, managerName: string): void { if (!manager) { - throw new Error(`${managerName} 未初始化,请先调用 initCesiumViewer()`) + throw new Error(`${managerName} 未初始化,请先调用 initCesiumViewer()`); } } } @@ -506,4 +574,4 @@ export class CesiumUtils { /** * CesiumUtils 单例实例 */ -export const CesiumUtilsSingleton = new CesiumUtils() +export const CesiumUtilsSingleton = new CesiumUtils(); diff --git a/src/utils/cesium/CesiumViewerManager.ts b/src/utils/cesium/CesiumViewerManager.ts index 332a9b3..8735e67 100644 --- a/src/utils/cesium/CesiumViewerManager.ts +++ b/src/utils/cesium/CesiumViewerManager.ts @@ -13,33 +13,33 @@ import { Material, MaterialAppearance, GroundPrimitive, -} from 'cesium' -import type { CesiumInitOptions } from '@/types/cesium/CesiumInitOptions' -import config from '@/config/config.json' +} from 'cesium'; +import type { CesiumInitOptions } from '@/types/cesium/CesiumInitOptions'; +import config from '@/config/config.json'; /** * Cesium Viewer 管理器 */ export class CesiumViewerManager { - #viewer: Viewer | null = null - #currentTokenIndex: number = 0 - #failedTokens: Set = new Set() + #viewer: Viewer | null = null; + #currentTokenIndex: number = 0; + #failedTokens: Set = new Set(); constructor() { - this.#initializeToken() + this.#initializeToken(); } /** * 初始化并设置有效的 Token */ #initializeToken(): void { - const tokens = config.cesiumIonDefaultAccessToken + const tokens = config.cesiumIonDefaultAccessToken; if (!Array.isArray(tokens) || tokens.length === 0) { - console.warn('Cesium Ion Token 配置为空') - return + console.warn('Cesium Ion Token 配置为空'); + return; } - Ion.defaultAccessToken = tokens[this.#currentTokenIndex] + Ion.defaultAccessToken = tokens[this.#currentTokenIndex]; } /** @@ -47,24 +47,24 @@ export class CesiumViewerManager { * @returns 是否成功切换 */ #switchToNextToken(): boolean { - const tokens = config.cesiumIonDefaultAccessToken + const tokens = config.cesiumIonDefaultAccessToken; if (!Array.isArray(tokens) || tokens.length <= 1) { - return false + return false; } - this.#failedTokens.add(this.#currentTokenIndex) + this.#failedTokens.add(this.#currentTokenIndex); for (let i = 1; i < tokens.length; i++) { - const nextIndex = (this.#currentTokenIndex + i) % tokens.length + const nextIndex = (this.#currentTokenIndex + i) % tokens.length; if (!this.#failedTokens.has(nextIndex)) { - this.#currentTokenIndex = nextIndex - Ion.defaultAccessToken = tokens[nextIndex] - return true + this.#currentTokenIndex = nextIndex; + Ion.defaultAccessToken = tokens[nextIndex]; + return true; } } - console.warn('所有 Cesium Ion Token 均已失效') - return false + console.warn('所有 Cesium Ion Token 均已失效'); + return false; } /** * 初始化 Cesium Viewer @@ -73,7 +73,11 @@ export class CesiumViewerManager { * @param type - 底图类型:0=影像图,1=矢量图(默认 0) * @param tdMapToken - 天地图 Token 数组(可选) */ - async initCesiumViewer(options: CesiumInitOptions, type: number = 0, tdMapToken?: string[]): Promise { + async initCesiumViewer( + options: CesiumInitOptions, + type: number = 0, + tdMapToken?: string[] + ): Promise { const defaultOptions: CesiumInitOptions = { containerId: options.containerId, shouldAnimate: true, @@ -95,29 +99,33 @@ export class CesiumViewerManager { border: { show: true, color: Color.WHITE, - width: 1 - } - } - } + width: 1, + }, + }, + }; // 合并选项 const finalOptions: CesiumInitOptions = { ...defaultOptions, ...options, - mark: options.mark ? { - ...defaultOptions.mark, - ...options.mark, - border: options.mark.border ? { - ...defaultOptions.mark!.border, - ...options.mark.border - } : defaultOptions.mark!.border - } : defaultOptions.mark - } + mark: options.mark + ? { + ...defaultOptions.mark, + ...options.mark, + border: options.mark.border + ? { + ...defaultOptions.mark!.border, + ...options.mark.border, + } + : defaultOptions.mark!.border, + } + : defaultOptions.mark, + }; - const container = document.getElementById(finalOptions.containerId) + const container = document.getElementById(finalOptions.containerId); if (!container) { - throw new Error(`Cesium 容器 #${finalOptions.containerId} 不存在`) + throw new Error(`Cesium 容器 #${finalOptions.containerId} 不存在`); } const viewer = new Viewer(container, { @@ -138,28 +146,30 @@ export class CesiumViewerManager { }, allowTextureFilterAnisotropic: true, }, - }) + }); // 性能优化配置 - viewer.scene.globe.depthTestAgainstTerrain = false - viewer.scene.fog.enabled = false - viewer.scene.globe.enableLighting = false - viewer.shadows = false - viewer.scene.globe.showGroundAtmosphere = false - viewer.scene.skyBox.show = false - const creditContainer = viewer.cesiumWidget.creditContainer as HTMLElement - creditContainer.style.display = 'none' + viewer.scene.globe.depthTestAgainstTerrain = false; + viewer.scene.fog.enabled = false; + viewer.scene.globe.enableLighting = false; + viewer.shadows = false; + viewer.scene.globe.showGroundAtmosphere = false; + viewer.scene.skyBox.show = false; + const creditContainer = viewer.cesiumWidget.creditContainer as HTMLElement; + creditContainer.style.display = 'none'; // 添加底图 - this.#createImageryProviders(type, tdMapToken || config.tdMapToken).forEach((provider) => { - viewer.imageryLayers.addImageryProvider(provider) - }) + this.#createImageryProviders(type, tdMapToken || config.tdMapToken).forEach( + (provider) => { + viewer.imageryLayers.addImageryProvider(provider); + } + ); - this.#viewer = viewer + this.#viewer = viewer; // 是否突出显示指定区域 if (options.mark?.include) { - await this.#highlight(finalOptions) + await this.#highlight(finalOptions); } } @@ -169,9 +179,9 @@ export class CesiumViewerManager { */ destroyCesiumViewer(clearAllResources: () => void): void { if (this.#viewer) { - clearAllResources() - this.#viewer.destroy() - this.#viewer = null + clearAllResources(); + this.#viewer.destroy(); + this.#viewer = null; } } @@ -180,13 +190,16 @@ export class CesiumViewerManager { * @returns Viewer 实例,如果未初始化则返回 null */ getViewer(): Viewer | null { - return this.#viewer + return this.#viewer; } /** * 创建底图 ImageryProvider */ - #createImageryProviders(type: number, tdMapToken: string[]): ImageryProvider[] { + #createImageryProviders( + type: number, + tdMapToken: string[] + ): ImageryProvider[] { const option = { tileMatrixSetID: 'w', format: 'tiles', @@ -194,30 +207,30 @@ export class CesiumViewerManager { minimumLevel: 0, maximumLevel: 18, credit: 'Tianditu', - subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'] - } + subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'], + }; - const token = tdMapToken[Math.floor(Math.random() * tdMapToken.length)] + const token = tdMapToken[Math.floor(Math.random() * tdMapToken.length)]; if (type === 0) { const imageryProvider = new WebMapTileServiceImageryProvider({ url: `https://{s}.tianditu.gov.cn/img_w/wmts?tk=${token}`, layer: 'img', ...option, - }) + }); const annotationProvider = new WebMapTileServiceImageryProvider({ url: `https://{s}.tianditu.gov.cn/cia_w/wmts?tk=${token}`, layer: 'cia', ...option, - }) - return [imageryProvider, annotationProvider] + }); + return [imageryProvider, annotationProvider]; } else { const vectorProvider = new WebMapTileServiceImageryProvider({ url: `https://{s}.tianditu.gov.cn/vec_w/wmts?tk=${token}`, layer: 'vec', ...option, - }) - return [vectorProvider] + }); + return [vectorProvider]; } } @@ -226,13 +239,12 @@ export class CesiumViewerManager { * @param options - 高亮选项 */ async #highlight(options: CesiumInitOptions): Promise { - if (!this.#viewer) { - throw new Error('请先初始化 Cesium Viewer') + throw new Error('请先初始化 Cesium Viewer'); } - if(!options.mark || !options.mark.geoJson) { - throw new Error('请提供 GeoJSON 数据') + if (!options.mark || !options.mark.geoJson) { + throw new Error('请提供 GeoJSON 数据'); } // 解析边界坐标和孔洞位置 @@ -285,7 +297,7 @@ export class CesiumViewerManager { const eastOption = { polygonHierarchy: new PolygonHierarchy(eastPositions), arcType: ArcType.GEODESIC, - } + }; if (options.mark.belongingHemisphere === 'east') { eastOption.polygonHierarchy = new PolygonHierarchy(eastPositions, holes); } @@ -295,7 +307,7 @@ export class CesiumViewerManager { // 添加遮罩 const maskMaterial = new Material({ fabric: { - type: "Color", + type: 'Color', uniforms: { color: options.mark.color, }, @@ -333,12 +345,14 @@ export class CesiumViewerManager { // 等待完成渲染 return new Promise((resolve) => { - const removeListener = this.#viewer!.scene.postRender.addEventListener(() => { - if (globalMask.ready) { - removeListener(); - resolve(); + const removeListener = this.#viewer!.scene.postRender.addEventListener( + () => { + if (globalMask.ready) { + removeListener(); + resolve(); + } } - }); + ); // 设置超时保护,避免无限等待 setTimeout(() => { diff --git a/src/utils/cesium/EntityManager.ts b/src/utils/cesium/EntityManager.ts index aa26543..0414b66 100644 --- a/src/utils/cesium/EntityManager.ts +++ b/src/utils/cesium/EntityManager.ts @@ -11,20 +11,20 @@ import { PolygonHierarchy, PolygonGraphics, GridMaterialProperty, -} from 'cesium' -import type { EntityOptions } from '@/types/cesium/EntityOptions' -import type { Viewer } from 'cesium' +} from 'cesium'; +import type { EntityOptions } from '@/types/cesium/EntityOptions'; +import type { Viewer } from 'cesium'; /** * 实体管理器 */ export class EntityManager { - #viewer: Viewer - #defaultEntityIds = new Set() - #customEntityIds = new Set() + #viewer: Viewer; + #defaultEntityIds = new Set(); + #customEntityIds = new Set(); constructor(viewer: Viewer) { - this.#viewer = viewer + this.#viewer = viewer; } /** @@ -33,33 +33,38 @@ export class EntityManager { * @returns 创建的 Entity 实例数组 */ addCesiumEntitiesBatch(entityOptionsList: EntityOptions[]): Entity[] { - const entities: Entity[] = [] - + const entities: Entity[] = []; + // 预验证所有ID的唯一性 entityOptionsList.forEach(({ id }) => { - if (!id) throw new Error('实体 id 为必填项') - this.#validateUniqueId(id) - }) + if (!id) throw new Error('实体 id 为必填项'); + this.#validateUniqueId(id); + }); // 批量创建并添加实体 entityOptionsList.forEach((entityOptions) => { - const { id, position, attributes = {}, isDefault = false } = entityOptions - - if (!position) throw new Error(`实体 ${id} 的 position 为必填项`) + const { + id, + position, + attributes = {}, + isDefault = false, + } = entityOptions; + + if (!position) throw new Error(`实体 ${id} 的 position 为必填项`); const entity = new Entity({ id, position: this.#convertPosition(position), ...attributes, - }) + }); - this.#configureEntityGraphics(entity, entityOptions) - this.#viewer.entities.add(entity) - this.#storeEntityId(id, isDefault) - entities.push(entity) - }) + this.#configureEntityGraphics(entity, entityOptions); + this.#viewer.entities.add(entity); + this.#storeEntityId(id, isDefault); + entities.push(entity); + }); - return entities + return entities; } /** @@ -68,23 +73,23 @@ export class EntityManager { * @returns 创建的 Entity 实例 */ addCesiumEntity(entityOptions: EntityOptions): Entity { - const { id, position, attributes = {}, isDefault = false } = entityOptions + const { id, position, attributes = {}, isDefault = false } = entityOptions; - if (!id) throw new Error('实体 id 为必填项') - if (!position) throw new Error('实体 position 为必填项') - this.#validateUniqueId(id) + if (!id) throw new Error('实体 id 为必填项'); + if (!position) throw new Error('实体 position 为必填项'); + this.#validateUniqueId(id); const entity = new Entity({ id, position: this.#convertPosition(position), ...attributes, - }) + }); - this.#configureEntityGraphics(entity, entityOptions) + this.#configureEntityGraphics(entity, entityOptions); - this.#viewer.entities.add(entity) - this.#storeEntityId(id, isDefault) - return entity + this.#viewer.entities.add(entity); + this.#storeEntityId(id, isDefault); + return entity; } /** @@ -93,8 +98,8 @@ export class EntityManager { * @returns Entity 实例,不存在则返回 null */ getCesiumEntityById(entityId: string): Entity | null { - if (!this.#entityExists(entityId)) return null - return this.#viewer.entities.getById(entityId) || null + if (!this.#entityExists(entityId)) return null; + return this.#viewer.entities.getById(entityId) || null; } /** @@ -104,17 +109,17 @@ export class EntityManager { */ removeCesiumEntity(entityId: string): boolean { if (!this.#entityExists(entityId)) { - console.warn(`实体 ID ${entityId} 不存在`) - return false + console.warn(`实体 ID ${entityId} 不存在`); + return false; } - const entity = this.#viewer.entities.getById(entityId) + const entity = this.#viewer.entities.getById(entityId); if (entity) { - this.#viewer.entities.remove(entity) - this.#removeEntityId(entityId) - return true + this.#viewer.entities.remove(entity); + this.#removeEntityId(entityId); + return true; } - return false + return false; } /** @@ -122,7 +127,7 @@ export class EntityManager { * @param entityIds - 实体 ID 数组 */ batchRemoveCesiumEntities(entityIds: string[]): void { - entityIds.forEach((id) => this.removeCesiumEntity(id)) + entityIds.forEach((id) => this.removeCesiumEntity(id)); } /** @@ -130,14 +135,14 @@ export class EntityManager { * @param clearType - 清除类型:'default'=默认实体,'custom'=自定义实体,'all'=所有实体(默认 'custom') */ clearAllEntities(clearType: 'default' | 'custom' | 'all' = 'custom'): void { - const targetIds = this.#getTargetIdsByType(clearType) + const targetIds = this.#getTargetIdsByType(clearType); targetIds.forEach((id) => { - const entity = this.#viewer.entities.getById(id) - if (entity) this.#viewer.entities.remove(entity) - }) + const entity = this.#viewer.entities.getById(id); + if (entity) this.#viewer.entities.remove(entity); + }); - this.#clearCollectionsByType(clearType) + this.#clearCollectionsByType(clearType); } /** @@ -146,7 +151,7 @@ export class EntityManager { * @returns 实体 ID 集合 */ getEntityIds(clearType: 'default' | 'custom' | 'all' = 'all'): Set { - return this.#getTargetIdsByType(clearType) + return this.#getTargetIdsByType(clearType); } // ===================== 私有方法 ===================== @@ -160,15 +165,15 @@ export class EntityManager { outlineColor = Color.WHITE, outlineWidth = 1, heightReference = HeightReference.CLAMP_TO_GROUND, - } = options.pointOptions || {} + } = options.pointOptions || {}; entity.point = new PointGraphics({ color, pixelSize, outlineColor, outlineWidth, heightReference, - }) - break + }); + break; } case 'polyline': { const { @@ -176,16 +181,17 @@ export class EntityManager { color = Color.BLUE, width = 3, clampToGround = false, - } = options.polylineOptions || {} - if (!positions) throw new Error('线实体必须传入 polylineOptions.positions') + } = options.polylineOptions || {}; + if (!positions) + throw new Error('线实体必须传入 polylineOptions.positions'); entity.polyline = new PolylineGraphics({ positions: this.#convertPositionArray(positions), material: color, width, clampToGround, - }) - break + }); + break; } case 'billboard': { const { @@ -195,8 +201,9 @@ export class EntityManager { verticalOrigin = VerticalOrigin.CENTER, horizontalOrigin = HorizontalOrigin.CENTER, heightReference = HeightReference.CLAMP_TO_GROUND, - } = options.billboardOptions || {} - if (!image) throw new Error('Billboard 实体必须传入 billboardOptions.image') + } = options.billboardOptions || {}; + if (!image) + throw new Error('Billboard 实体必须传入 billboardOptions.image'); entity.billboard = new BillboardGraphics({ image, @@ -205,8 +212,8 @@ export class EntityManager { verticalOrigin, horizontalOrigin, heightReference, - }) - break + }); + break; } case 'polygon': { const { @@ -220,12 +227,13 @@ export class EntityManager { material = new GridMaterialProperty({ color: Color.GREEN.withAlpha(0.3), cellAlpha: 0.2, - lineCount: new Cartesian3(8, 8, 0) as any, - lineThickness: new Cartesian3(2.0, 2.0, 0) as any, + lineCount: new Cartesian3(8, 8, 0), + lineThickness: new Cartesian3(2.0, 2.0, 0), }), - } = options.polygonOptions || {} + } = options.polygonOptions || {}; - if (!hierarchy) throw new Error('多边形实体必须传入 polygonOptions.hierarchy') + if (!hierarchy) + throw new Error('多边形实体必须传入 polygonOptions.hierarchy'); entity.polygon = new PolygonGraphics({ hierarchy: this.#processHierarchy(hierarchy), @@ -236,77 +244,87 @@ export class EntityManager { height, extrudedHeight, heightReference, - }) - break + }); + break; } default: - throw new Error(`不支持的实体类型:${options.type}`) + throw new Error(`不支持的实体类型:${options.type}`); } } #processHierarchy( - hier: PolygonHierarchy | Cartesian3[] | [number, number][] | [number, number, number][], + hier: + | PolygonHierarchy + | Cartesian3[] + | [number, number][] + | [number, number, number][] ): PolygonHierarchy { - if (hier instanceof PolygonHierarchy) return hier + if (hier instanceof PolygonHierarchy) return hier; if (!Array.isArray(hier) || hier.length < 3) { - throw new Error('多边形层级必须是非空数组且至少 3 个顶点') + throw new Error('多边形层级必须是非空数组且至少 3 个顶点'); } const positions = hier.map((pos) => { - if (pos instanceof Cartesian3) return pos + if (pos instanceof Cartesian3) return pos; if (Array.isArray(pos) && pos.length >= 2) { - return Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) + return Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0); } throw new Error( - `无效坐标格式:${JSON.stringify(pos)},应为 [经, 纬] 或 [经, 纬, 高] 或 Cartesian3`, - ) - }) + `无效坐标格式:${JSON.stringify(pos)},应为 [经, 纬] 或 [经, 纬, 高] 或 Cartesian3` + ); + }); - return new PolygonHierarchy(positions) + return new PolygonHierarchy(positions); } #validateUniqueId(id: string): void { if (this.#defaultEntityIds.has(id) || this.#customEntityIds.has(id)) { - throw new Error(`实体 ID ${id} 已存在`) + throw new Error(`实体 ID ${id} 已存在`); } } #entityExists(id: string): boolean { - return this.#defaultEntityIds.has(id) || this.#customEntityIds.has(id) + return this.#defaultEntityIds.has(id) || this.#customEntityIds.has(id); } #storeEntityId(id: string, isDefault: boolean): void { if (isDefault) { - this.#defaultEntityIds.add(id) + this.#defaultEntityIds.add(id); } else { - this.#customEntityIds.add(id) + this.#customEntityIds.add(id); } } #removeEntityId(id: string): void { - this.#defaultEntityIds.delete(id) - this.#customEntityIds.delete(id) + this.#defaultEntityIds.delete(id); + this.#customEntityIds.delete(id); } #getTargetIdsByType(clearType: 'default' | 'custom' | 'all'): Set { - const targetIds = new Set() + const targetIds = new Set(); if (clearType === 'default' || clearType === 'all') - this.#defaultEntityIds.forEach((id) => targetIds.add(id)) + this.#defaultEntityIds.forEach((id) => targetIds.add(id)); if (clearType === 'custom' || clearType === 'all') - this.#customEntityIds.forEach((id) => targetIds.add(id)) - return targetIds + this.#customEntityIds.forEach((id) => targetIds.add(id)); + return targetIds; } #clearCollectionsByType(clearType: 'default' | 'custom' | 'all'): void { - if (clearType === 'default' || clearType === 'all') this.#defaultEntityIds.clear() - if (clearType === 'custom' || clearType === 'all') this.#customEntityIds.clear() + if (clearType === 'default' || clearType === 'all') + this.#defaultEntityIds.clear(); + if (clearType === 'custom' || clearType === 'all') + this.#customEntityIds.clear(); } #convertPosition(pos: Cartesian3 | [number, number, number]): Cartesian3 { - return Array.isArray(pos) ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) : pos + return Array.isArray(pos) + ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) + : pos; } - #convertPositionArray(positions: (Cartesian3 | [number, number, number])[]): Cartesian3[] { - return positions.map((pos) => this.#convertPosition(pos)) + #convertPositionArray( + positions: (Cartesian3 | [number, number, number])[] + ): Cartesian3[] { + return positions.map((pos) => this.#convertPosition(pos)); } } diff --git a/src/utils/cesium/GeoJsonManager.ts b/src/utils/cesium/GeoJsonManager.ts index fee8639..b6800ea 100644 --- a/src/utils/cesium/GeoJsonManager.ts +++ b/src/utils/cesium/GeoJsonManager.ts @@ -14,21 +14,24 @@ import { Cartographic, JulianDate, NearFarScalar, -} from 'cesium' -import type { CustomizeGeoJsonDataSource, GeoJsonOptions } from '@/types/cesium/GeoJsonOptions' -import type { LabelConfig } from '@/types/cesium/LabelConfig' -import type { Viewer, Entity } from 'cesium' +} from 'cesium'; +import type { + CustomizeGeoJsonDataSource, + GeoJsonOptions, +} from '@/types/cesium/GeoJsonOptions'; +import type { LabelConfig } from '@/types/cesium/LabelConfig'; +import type { Viewer, Entity } from 'cesium'; // 定义清除类型枚举 -export type ClearType = 'default' | 'custom' | 'all' +export type ClearType = 'default' | 'custom' | 'all'; /** * GeoJSON 图层管理器 */ export class GeoJsonManager { - #viewer: Viewer - #defaultGeoJsonMap = new Map() - #customGeoJsonMap = new Map() + #viewer: Viewer; + #defaultGeoJsonMap = new Map(); + #customGeoJsonMap = new Map(); // 默认配置 static readonly DEFAULT_OPTIONS: Required = { @@ -62,41 +65,38 @@ export class GeoJsonManager { outlineWidth: 2, }, onComplete: () => {}, - } + }; constructor(viewer: Viewer) { - this.#viewer = viewer + this.#viewer = viewer; } /** * 添加 GeoJSON 图层 * @param layerId - 图层唯一标识 * @param geojsonData - GeoJSON 数据(路径、URL 或对象) - * @param isDefault - 是否为默认图层(默认 false,优先级高于 options.default) * @param options - 配置选项(样式、标签等) * @returns Promise 数据源实例 */ async addGeoJsonLayer( layerId: string, geojsonData: CustomizeGeoJsonDataSource, - isDefault: boolean = false, - options?: GeoJsonOptions, + options?: GeoJsonOptions ): Promise { - if (this.#exists(layerId)) throw new Error(`图层 ${layerId} 已存在`) - const opt = this.#mergeOptions(options) - - const finalIsDefault = isDefault || opt.isDefault + if (this.#exists(layerId)) throw new Error(`图层 ${layerId} 已存在`); + const opt = this.#mergeOptions(options); // 加载并应用样式 - const dataSource = await GeoJsonDataSource.load(geojsonData) - dataSource.entities.values.forEach((e) => this.#applyStyle(e, opt)) + const dataSource = await GeoJsonDataSource.load(geojsonData); + dataSource.entities.values.forEach((e) => this.#applyStyle(e, opt)); // 添加到地图 - await this.#viewer.dataSources.add(dataSource) - finalIsDefault - ? this.#defaultGeoJsonMap.set(layerId, dataSource) - : this.#customGeoJsonMap.set(layerId, dataSource) - + await this.#viewer.dataSources.add(dataSource); + if (opt.isDefault) { + this.#defaultGeoJsonMap.set(layerId, dataSource); + } else { + this.#customGeoJsonMap.set(layerId, dataSource); + } // 如果需要显示标签,调用 addLabelsToDataSource if (opt.showName && opt.labelStyle) { this.#addLabelsToDataSource(dataSource, { @@ -109,11 +109,11 @@ export class GeoJsonManager { verticalOrigin: opt.labelStyle.verticalOrigin, backgroundColor: opt.labelStyle.backgroundColor, center: opt.labelStyle.center, - }) + }); } - opt.onComplete(dataSource) - return dataSource + opt.onComplete(dataSource); + return dataSource; } /** @@ -122,7 +122,7 @@ export class GeoJsonManager { * @returns DataSource 实例,不存在则返回 undefined */ getGeoJsonLayerById(layerId: string): DataSource | undefined { - return this.#getGeoJsonLayer(layerId).ds + return this.#getGeoJsonLayer(layerId).ds; } /** @@ -131,34 +131,36 @@ export class GeoJsonManager { * @returns 是否删除成功 */ removeGeoJsonLayer(layerId: string): boolean { - const { isDefault, ds } = this.#getGeoJsonLayer(layerId) - if (!ds) return false + const { isDefault, ds } = this.#getGeoJsonLayer(layerId); + if (!ds) return false; - const removed = this.#viewer.dataSources.remove(ds, true) - removed && - (isDefault - ? this.#defaultGeoJsonMap.delete(layerId) - : this.#customGeoJsonMap.delete(layerId)) - return removed + const removed = this.#viewer.dataSources.remove(ds, true); + if (removed) { + if (isDefault) { + this.#defaultGeoJsonMap.delete(layerId); + } else { + this.#customGeoJsonMap.delete(layerId); + } + } + return removed; } /** * 批量添加GeoJSON图层 - * @param layerConfigs - 图层配置数组,每个元素包含 layerId、geojsonData、isDefault 和 options + * @param layerConfigs - 图层配置数组,每个元素包含 layerId、geojsonData 和 options */ async batchAddGeoJsonLayers( layerConfigs: Array<{ - layerId: string - geojsonData: CustomizeGeoJsonDataSource - isDefault?: boolean - options?: GeoJsonOptions - }>, + layerId: string; + geojsonData: CustomizeGeoJsonDataSource; + options?: GeoJsonOptions; + }> ): Promise { await Promise.all( - layerConfigs.map(({ layerId, geojsonData, isDefault = false, options }) => - this.addGeoJsonLayer(layerId, geojsonData, isDefault, options) + layerConfigs.map(({ layerId, geojsonData, options }) => + this.addGeoJsonLayer(layerId, geojsonData, options) ) - ) + ); } /** @@ -166,7 +168,7 @@ export class GeoJsonManager { * @param layerIds - 图层 ID 数组 */ batchRemoveGeoJsonLayers(layerIds: string[]): void { - layerIds.forEach((id) => this.removeGeoJsonLayer(id)) + layerIds.forEach((id) => this.removeGeoJsonLayer(id)); } /** @@ -178,12 +180,15 @@ export class GeoJsonManager { default: this.#defaultGeoJsonMap, custom: this.#customGeoJsonMap, all: new Map([...this.#defaultGeoJsonMap, ...this.#customGeoJsonMap]), - } + }; - maps[clearType].forEach((ds) => this.#viewer.dataSources.remove(ds, true)) - clearType === 'all' - ? (this.#defaultGeoJsonMap.clear(), this.#customGeoJsonMap.clear()) - : maps[clearType].clear() + maps[clearType].forEach((ds) => this.#viewer.dataSources.remove(ds, true)); + if (clearType === 'all') { + this.#defaultGeoJsonMap.clear(); + this.#customGeoJsonMap.clear(); + } else { + maps[clearType].clear(); + } } /** @@ -192,13 +197,13 @@ export class GeoJsonManager { * @returns 是否操作成功 */ showGeoJsonLayer(layerId: string): boolean { - const ds = this.getGeoJsonLayerById(layerId) - if (!ds) return false - ds.show = true + const ds = this.getGeoJsonLayerById(layerId); + if (!ds) return false; + ds.show = true; ds.entities.values.forEach( - (e) => e.label && (e.label.show = new ConstantProperty(true)), - ) - return true + (e) => e.label && (e.label.show = new ConstantProperty(true)) + ); + return true; } /** @@ -207,13 +212,13 @@ export class GeoJsonManager { * @returns 是否操作成功 */ hideGeoJsonLayer(layerId: string): boolean { - const ds = this.getGeoJsonLayerById(layerId) - if (!ds) return false - ds.show = false + const ds = this.getGeoJsonLayerById(layerId); + if (!ds) return false; + ds.show = false; ds.entities.values.forEach( - (e) => e.label && (e.label.show = new ConstantProperty(false)), - ) - return true + (e) => e.label && (e.label.show = new ConstantProperty(false)) + ); + return true; } /** @@ -222,14 +227,14 @@ export class GeoJsonManager { * @returns 切换后的显示状态,图层不存在则返回 null */ toggleGeoJsonLayer(layerId: string): boolean | null { - const ds = this.getGeoJsonLayerById(layerId) - if (!ds) return null - const state = !ds.show - ds.show = state + const ds = this.getGeoJsonLayerById(layerId); + if (!ds) return null; + const state = !ds.show; + ds.show = state; ds.entities.values.forEach( - (e) => e.label && (e.label.show = new ConstantProperty(state)), - ) - return state + (e) => e.label && (e.label.show = new ConstantProperty(state)) + ); + return state; } /** @@ -238,7 +243,10 @@ export class GeoJsonManager { * @returns 成功显示的图层数量 */ batchShowGeoJsonLayers(layerIds: string[]): number { - return layerIds.reduce((n, id) => n + (this.showGeoJsonLayer(id) ? 1 : 0), 0) + return layerIds.reduce( + (n, id) => n + (this.showGeoJsonLayer(id) ? 1 : 0), + 0 + ); } /** @@ -247,7 +255,10 @@ export class GeoJsonManager { * @returns 成功隐藏的图层数量 */ batchHideGeoJsonLayers(layerIds: string[]): number { - return layerIds.reduce((n, id) => n + (this.hideGeoJsonLayer(id) ? 1 : 0), 0) + return layerIds.reduce( + (n, id) => n + (this.hideGeoJsonLayer(id) ? 1 : 0), + 0 + ); } /** @@ -256,8 +267,8 @@ export class GeoJsonManager { * @returns 显示状态,图层不存在则返回 null */ getGeoJsonLayerVisibility(layerId: string): boolean | null { - const ds = this.getGeoJsonLayerById(layerId) - return ds ? ds.show : null + const ds = this.getGeoJsonLayerById(layerId); + return ds ? ds.show : null; } /** @@ -266,14 +277,17 @@ export class GeoJsonManager { * @returns GeoJSON 图层 ID 集合 */ getGeoJsonLayerIds(clearType: ClearType = 'all'): Set { - return this.#getTargetIdsByType(clearType) + return this.#getTargetIdsByType(clearType); } // ===================== 私有方法 ===================== /** 图层是否存在 */ #exists(layerId: string): boolean { - return this.#defaultGeoJsonMap.has(layerId) || this.#customGeoJsonMap.has(layerId) + return ( + this.#defaultGeoJsonMap.has(layerId) || + this.#customGeoJsonMap.has(layerId) + ); } /** 合并用户配置 + 默认配置 */ @@ -281,16 +295,28 @@ export class GeoJsonManager { return { ...GeoJsonManager.DEFAULT_OPTIONS, ...options, - labelStyle: { ...GeoJsonManager.DEFAULT_OPTIONS.labelStyle, ...options?.labelStyle }, - polygonStyle: { ...GeoJsonManager.DEFAULT_OPTIONS.polygonStyle, ...options?.polygonStyle }, - polylineStyle: { ...GeoJsonManager.DEFAULT_OPTIONS.polylineStyle, ...options?.polylineStyle }, - pointStyle: { ...GeoJsonManager.DEFAULT_OPTIONS.pointStyle, ...options?.pointStyle }, - } + labelStyle: { + ...GeoJsonManager.DEFAULT_OPTIONS.labelStyle, + ...options?.labelStyle, + }, + polygonStyle: { + ...GeoJsonManager.DEFAULT_OPTIONS.polygonStyle, + ...options?.polygonStyle, + }, + polylineStyle: { + ...GeoJsonManager.DEFAULT_OPTIONS.polylineStyle, + ...options?.polylineStyle, + }, + pointStyle: { + ...GeoJsonManager.DEFAULT_OPTIONS.pointStyle, + ...options?.pointStyle, + }, + }; } /** 统一应用样式到实体 */ #applyStyle(entity: Entity, options: Required): void { - const { polygonStyle, polylineStyle, pointStyle } = options + const { polygonStyle, polylineStyle, pointStyle } = options; if (entity.point) { Object.assign(entity.point, { @@ -298,7 +324,7 @@ export class GeoJsonManager { color: new ConstantProperty(pointStyle.color), outlineColor: new ConstantProperty(pointStyle.outlineColor), outlineWidth: new ConstantProperty(pointStyle.outlineWidth), - }) + }); } if (entity.polyline) { @@ -306,7 +332,7 @@ export class GeoJsonManager { width: new ConstantProperty(polylineStyle.width), material: new ColorMaterialProperty(polylineStyle.material as Color), clampToGround: new ConstantProperty(polylineStyle.clampToGround), - }) + }); } if (entity.polygon) { @@ -316,7 +342,7 @@ export class GeoJsonManager { outline: new ConstantProperty(polygonStyle.outline), outlineColor: new ConstantProperty(polygonStyle.outlineColor), outlineWidth: new ConstantProperty(polygonStyle.outlineWidth), - }) + }); } } @@ -326,35 +352,48 @@ export class GeoJsonManager { * - 添加距离衰减以减少远距离渲染负担 */ #addLabelsToDataSource(dataSource: DataSource, label: LabelConfig): void { - const entities = dataSource.entities.values + const entities = dataSource.entities.values; entities.forEach((entity) => { - const labelText = label?.labelText || 'name' + const labelText = label?.labelText || 'name'; const center: Cartesian3 | [number, number, number] = - label.center || this.#calculateTheCenterPositionOfTheSurface(entity) + label.center || this.#calculateTheCenterPositionOfTheSurface(entity); // 设置中心位置 - entity.position = new ConstantPositionProperty(this.#convertPosition(center)) + entity.position = new ConstantPositionProperty( + this.#convertPosition(center) + ); if (labelText && entity.position) { entity.label = new LabelGraphics({ text: new ConstantProperty(labelText), - font: new ConstantProperty(label?.labelFont || `${label?.labelSize || 16}px "微软雅黑"`), + font: new ConstantProperty( + label?.labelFont || `${label?.labelSize || 16}px "微软雅黑"` + ), fillColor: new ConstantProperty(label?.labelColor || Color.WHITE), // 性能优化:禁用描边 style: new ConstantProperty(LabelStyle.FILL), pixelOffset: new ConstantProperty( - new Cartesian2(label?.labelOffset?.x || 0, label?.labelOffset?.y || -20), + new Cartesian2( + label?.labelOffset?.x || 0, + label?.labelOffset?.y || -20 + ) + ), + verticalOrigin: new ConstantProperty( + label?.verticalOrigin || VerticalOrigin.CENTER ), - verticalOrigin: new ConstantProperty(label?.verticalOrigin || VerticalOrigin.CENTER), horizontalOrigin: new ConstantProperty( - label?.horizontalOrigin || HorizontalOrigin.CENTER, + label?.horizontalOrigin || HorizontalOrigin.CENTER ), showBackground: new ConstantProperty(true), - backgroundColor: new ConstantProperty(label?.backgroundColor || Color.TRANSPARENT), + backgroundColor: new ConstantProperty( + label?.backgroundColor || Color.TRANSPARENT + ), backgroundPadding: new ConstantProperty(new Cartesian2(5, 3)), - disableDepthTestDistance: new ConstantProperty(Number.POSITIVE_INFINITY), + disableDepthTestDistance: new ConstantProperty( + Number.POSITIVE_INFINITY + ), // 性能优化:添加距离衰减,减少远距离渲染负担 scaleByDistance: new ConstantProperty( new NearFarScalar(1.5e2, 1.0, 1.5e7, 0.5) @@ -362,17 +401,20 @@ export class GeoJsonManager { translucencyByDistance: new ConstantProperty( new NearFarScalar(1.5e2, 1.0, 1.5e7, 0.3) ), - }) + }); } - }) + }); } /** 获取图层信息 */ - #getGeoJsonLayer(layerId: string): { isDefault: boolean; ds: DataSource | undefined } { - const def = this.#defaultGeoJsonMap.get(layerId) - if (def) return { isDefault: true, ds: def } - const custom = this.#customGeoJsonMap.get(layerId) - return { isDefault: false, ds: custom } + #getGeoJsonLayer(layerId: string): { + isDefault: boolean; + ds: DataSource | undefined; + } { + const def = this.#defaultGeoJsonMap.get(layerId); + if (def) return { isDefault: true, ds: def }; + const custom = this.#customGeoJsonMap.get(layerId); + return { isDefault: false, ds: custom }; } /** @@ -380,39 +422,41 @@ export class GeoJsonManager { */ #calculateTheCenterPositionOfTheSurface(entity: Entity): Cartesian3 { if (entity.polygon) { - const hierarchy = entity.polygon.hierarchy?.getValue(JulianDate.now()) + const hierarchy = entity.polygon.hierarchy?.getValue(JulianDate.now()); if (hierarchy) { - const positions = hierarchy.positions + const positions = hierarchy.positions; if (positions && positions.length > 0) { let lonSum = 0, latSum = 0, - heightSum = 0 + heightSum = 0; positions.forEach((pos: Cartesian3) => { - const cartographic = Cartographic.fromCartesian(pos) - lonSum += cartographic.longitude - latSum += cartographic.latitude - heightSum += cartographic.height || 0 - }) - const centerLon = lonSum / positions.length - const centerLat = latSum / positions.length - const centerHeight = heightSum / positions.length + 100 - return Cartesian3.fromRadians(centerLon, centerLat, centerHeight) + const cartographic = Cartographic.fromCartesian(pos); + lonSum += cartographic.longitude; + latSum += cartographic.latitude; + heightSum += cartographic.height || 0; + }); + const centerLon = lonSum / positions.length; + const centerLat = latSum / positions.length; + const centerHeight = heightSum / positions.length + 100; + return Cartesian3.fromRadians(centerLon, centerLat, centerHeight); } } } - return Cartesian3.ZERO + return Cartesian3.ZERO; } #convertPosition(pos: Cartesian3 | [number, number, number]): Cartesian3 { - return Array.isArray(pos) ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) : pos + return Array.isArray(pos) + ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) + : pos; } #getTargetIdsByType(clearType: ClearType): Set { - const targetIds = new Set() + const targetIds = new Set(); if (clearType === 'default' || clearType === 'all') - this.#defaultGeoJsonMap.forEach((_, key) => targetIds.add(key)) + this.#defaultGeoJsonMap.forEach((_, key) => targetIds.add(key)); if (clearType === 'custom' || clearType === 'all') - this.#customGeoJsonMap.forEach((_, key) => targetIds.add(key)) - return targetIds + this.#customGeoJsonMap.forEach((_, key) => targetIds.add(key)); + return targetIds; } } diff --git a/src/utils/cesium/LayerManager.ts b/src/utils/cesium/LayerManager.ts index 50c2b7f..34ba7db 100644 --- a/src/utils/cesium/LayerManager.ts +++ b/src/utils/cesium/LayerManager.ts @@ -4,20 +4,20 @@ import { WebMapServiceImageryProvider, WebMapTileServiceImageryProvider, ImageryProvider, -} from 'cesium' -import type { LayerConfig } from '@/types/cesium/LayerConfig' -import type { Viewer } from 'cesium' +} from 'cesium'; +import type { LayerConfig } from '@/types/cesium/LayerConfig'; +import type { Viewer } from 'cesium'; /** * 图层管理器 */ export class LayerManager { - #viewer: Viewer - #defaultLayerMap = new Map() - #customLayerMap = new Map() + #viewer: Viewer; + #defaultLayerMap = new Map(); + #customLayerMap = new Map(); constructor(viewer: Viewer) { - this.#viewer = viewer + this.#viewer = viewer; } /** @@ -28,12 +28,12 @@ export class LayerManager { createLayersBatch(layerConfigs: LayerConfig[]): (ImageryLayer | null)[] { return layerConfigs.map((config) => { try { - return this.createLayer(config) + return this.createLayer(config); } catch (error) { - console.error(`创建图层 ${config.layers} 失败:`, error) - return null + console.error(`创建图层 ${config.layers} 失败:`, error); + return null; } - }) + }); } /** @@ -42,17 +42,17 @@ export class LayerManager { * @returns 创建的 ImageryLayer 实例,失败则返回 null */ createLayer(layerConfig: LayerConfig): ImageryLayer | null { - const { layers: layerKey, isDefault = false } = layerConfig + const { layers: layerKey, isDefault = false } = layerConfig; - if (!layerKey) throw new Error('layers 参数未定义') - this.#validateUniqueLayerKey(layerKey) + if (!layerKey) throw new Error('layers 参数未定义'); + this.#validateUniqueLayerKey(layerKey); - const provider = this.#createImageryProvider(layerConfig) - if (!provider) return null + const provider = this.#createImageryProvider(layerConfig); + if (!provider) return null; - const layer = this.#viewer.imageryLayers.addImageryProvider(provider) - this.#storeLayer(layerKey, layer!, isDefault) - return layer! + const layer = this.#viewer.imageryLayers.addImageryProvider(provider); + this.#storeLayer(layerKey, layer!, isDefault); + return layer!; } /** @@ -61,7 +61,7 @@ export class LayerManager { * @returns ImageryLayer 实例,不存在则返回 undefined */ getLayerByKey(key: string): ImageryLayer | undefined { - return this.#defaultLayerMap.get(key) || this.#customLayerMap.get(key) + return this.#defaultLayerMap.get(key) || this.#customLayerMap.get(key); } /** @@ -70,17 +70,17 @@ export class LayerManager { * @returns 是否删除成功 */ removeLayerByKey(key: string): boolean { - const { isDefault, layer } = this.#getLayerInfo(key) + const { isDefault, layer } = this.#getLayerInfo(key); if (!layer) { - console.warn(`图层 key ${key} 不存在`) - return false + console.warn(`图层 key ${key} 不存在`); + return false; } - const removed = this.#viewer.imageryLayers.remove(layer, true) + const removed = this.#viewer.imageryLayers.remove(layer, true); if (removed) { - this.#removeLayerKey(key, isDefault) + this.#removeLayerKey(key, isDefault); } - return removed! + return removed!; } /** @@ -88,7 +88,7 @@ export class LayerManager { * @param layerIds - 图层 ID 数组 */ batchRemoveLayers(layerIds: string[]): void { - layerIds.forEach((id) => this.removeLayerByKey(id)) + layerIds.forEach((id) => this.removeLayerByKey(id)); } /** @@ -96,13 +96,13 @@ export class LayerManager { * @param clearType - 清除类型:'default'=默认图层,'custom'=自定义图层,'all'=所有图层(默认 'custom') */ clearAllLayers(clearType: 'default' | 'custom' | 'all' = 'custom'): void { - const targetMap = this.#getTargetMapByType(clearType) + const targetMap = this.#getTargetMapByType(clearType); targetMap.forEach((layer) => { - this.#viewer.imageryLayers.remove(layer, true) - }) + this.#viewer.imageryLayers.remove(layer, true); + }); - this.#clearMapsByType(clearType) + this.#clearMapsByType(clearType); } /** @@ -111,28 +111,28 @@ export class LayerManager { * @returns 图层 Key 集合 */ getLayerKeys(clearType: 'default' | 'custom' | 'all' = 'all'): Set { - return this.#getTargetIdsByType(clearType) + return this.#getTargetIdsByType(clearType); } // ===================== 私有方法 ===================== #validateUniqueLayerKey(key: string): void { if (this.#defaultLayerMap.has(key) || this.#customLayerMap.has(key)) { - console.warn(`图层 ${key} 已存在,将覆盖原有图层`) - this.removeLayerByKey(key) + console.warn(`图层 ${key} 已存在,将覆盖原有图层`); + this.removeLayerByKey(key); } } #createImageryProvider(layerConfig: LayerConfig): ImageryProvider | null { switch (layerConfig.type) { case 'imagery': - return new ArcGisMapServerImageryProvider({ url: layerConfig.url }) + return new ArcGisMapServerImageryProvider({ url: layerConfig.url }); case 'wms': return new WebMapServiceImageryProvider({ url: layerConfig.url, layers: layerConfig.layers, parameters: layerConfig.parameters || { format: 'image/png' }, - }) + }); case 'wmts': return new WebMapTileServiceImageryProvider({ url: layerConfig.url, @@ -141,52 +141,58 @@ export class LayerManager { format: layerConfig.format || 'image/png', tileMatrixSetID: layerConfig.tileMatrixSetID || 'EPSG:4326', credit: '', - }) + }); default: - console.error(`不支持的图层类型:${layerConfig.type}`) - return null + console.error(`不支持的图层类型:${layerConfig.type}`); + return null; } } #storeLayer(key: string, layer: ImageryLayer, isDefault: boolean): void { if (isDefault) { - this.#defaultLayerMap.set(key, layer) + this.#defaultLayerMap.set(key, layer); } else { - this.#customLayerMap.set(key, layer) + this.#customLayerMap.set(key, layer); } } #getLayerInfo(key: string) { - const isDefault = this.#defaultLayerMap.has(key) - const layer = isDefault ? this.#defaultLayerMap.get(key) : this.#customLayerMap.get(key) - return { isDefault, layer } + const isDefault = this.#defaultLayerMap.has(key); + const layer = isDefault + ? this.#defaultLayerMap.get(key) + : this.#customLayerMap.get(key); + return { isDefault, layer }; } #removeLayerKey(key: string, isDefault: boolean): void { - if (isDefault) this.#defaultLayerMap.delete(key) - else this.#customLayerMap.delete(key) + if (isDefault) this.#defaultLayerMap.delete(key); + else this.#customLayerMap.delete(key); } - #getTargetMapByType(clearType: 'default' | 'custom' | 'all'): Map { - const targetMap = new Map() + #getTargetMapByType( + clearType: 'default' | 'custom' | 'all' + ): Map { + const targetMap = new Map(); if (clearType === 'default' || clearType === 'all') - this.#defaultLayerMap.forEach((value, key) => targetMap.set(key, value)) + this.#defaultLayerMap.forEach((value, key) => targetMap.set(key, value)); if (clearType === 'custom' || clearType === 'all') - this.#customLayerMap.forEach((value, key) => targetMap.set(key, value)) - return targetMap + this.#customLayerMap.forEach((value, key) => targetMap.set(key, value)); + return targetMap; } #clearMapsByType(clearType: 'default' | 'custom' | 'all'): void { - if (clearType === 'default' || clearType === 'all') this.#defaultLayerMap.clear() - if (clearType === 'custom' || clearType === 'all') this.#customLayerMap.clear() + if (clearType === 'default' || clearType === 'all') + this.#defaultLayerMap.clear(); + if (clearType === 'custom' || clearType === 'all') + this.#customLayerMap.clear(); } #getTargetIdsByType(clearType: 'default' | 'custom' | 'all'): Set { - const targetIds = new Set() + const targetIds = new Set(); if (clearType === 'default' || clearType === 'all') - this.#defaultLayerMap.forEach((_, key) => targetIds.add(key)) + this.#defaultLayerMap.forEach((_, key) => targetIds.add(key)); if (clearType === 'custom' || clearType === 'all') - this.#customLayerMap.forEach((_, key) => targetIds.add(key)) - return targetIds + this.#customLayerMap.forEach((_, key) => targetIds.add(key)); + return targetIds; } } diff --git a/src/utils/cesium/PrimitiveManager.ts b/src/utils/cesium/PrimitiveManager.ts index fa1feb7..6fe41e1 100644 --- a/src/utils/cesium/PrimitiveManager.ts +++ b/src/utils/cesium/PrimitiveManager.ts @@ -11,24 +11,20 @@ import { PolygonHierarchy, Cartesian3, Color, - BillboardGraphics, - VerticalOrigin, - HorizontalOrigin, - NearFarScalar, -} from 'cesium' -import type { PrimitiveOptions } from '@/types/cesium/PrimitiveOptions' -import type { Viewer } from 'cesium' +} from 'cesium'; +import type { PrimitiveOptions } from '@/types/cesium/PrimitiveOptions'; +import type { NearFarScalar, Viewer } from 'cesium'; /** * Primitive 管理器 */ export class PrimitiveManager { - #viewer: Viewer - #defaultPrimitiveMap = new Map() - #customPrimitiveMap = new Map() + #viewer: Viewer; + #defaultPrimitiveMap = new Map(); + #customPrimitiveMap = new Map(); constructor(viewer: Viewer) { - this.#viewer = viewer + this.#viewer = viewer; } /** @@ -36,7 +32,7 @@ export class PrimitiveManager { * @param primitive - Primitive 配置选项 */ addPrimitive(primitive: PrimitiveOptions): void { - this.addPrimitivesBatch([primitive]) + this.addPrimitivesBatch([primitive]); } /** @@ -46,24 +42,30 @@ export class PrimitiveManager { * @param primitives - Primitive 配置选项数组 */ addPrimitivesBatch(primitives: PrimitiveOptions[]): void { - if (primitives.length === 0) return + if (primitives.length === 0) return; - const grouped = this.#groupPrimitivesByType(primitives) + const grouped = this.#groupPrimitivesByType(primitives); // 并行添加不同类型的 Primitive - const promises: Promise[] = [] + const promises: Promise[] = []; if (grouped.points.length > 0) { - promises.push(Promise.resolve(this.#addPointPrimitives(grouped.points))) + promises.push(Promise.resolve(this.#addPointPrimitives(grouped.points))); } if (grouped.polylines.length > 0) { - promises.push(Promise.resolve(this.#addPolylinePrimitives(grouped.polylines))) + promises.push( + Promise.resolve(this.#addPolylinePrimitives(grouped.polylines)) + ); } if (grouped.polygons.length > 0) { - promises.push(Promise.resolve(this.#addPolygonPrimitives(grouped.polygons))) + promises.push( + Promise.resolve(this.#addPolygonPrimitives(grouped.polygons)) + ); } if (grouped.billboards.length > 0) { - promises.push(Promise.resolve(this.#addBillboardPrimitives(grouped.billboards))) + promises.push( + Promise.resolve(this.#addBillboardPrimitives(grouped.billboards)) + ); } } @@ -73,7 +75,9 @@ export class PrimitiveManager { * @returns Primitive 或 BillboardCollection 实例,不存在则返回 undefined */ getPrimitiveById(id: string): Primitive | BillboardCollection | undefined { - return this.#defaultPrimitiveMap.get(id) || this.#customPrimitiveMap.get(id) + return ( + this.#defaultPrimitiveMap.get(id) || this.#customPrimitiveMap.get(id) + ); } /** @@ -82,15 +86,15 @@ export class PrimitiveManager { * @returns 是否删除成功 */ removePrimitiveById(id: string): boolean { - const { isDefault, primitive } = this.#getPrimitiveInfo(id) + const { isDefault, primitive } = this.#getPrimitiveInfo(id); if (!primitive) { - console.warn(`Primitive ID ${id} 不存在`) - return false + console.warn(`Primitive ID ${id} 不存在`); + return false; } - this.#viewer.scene.primitives.remove(primitive) - this.#removePrimitiveId(id, isDefault) - return true + this.#viewer.scene.primitives.remove(primitive); + this.#removePrimitiveId(id, isDefault); + return true; } /** @@ -98,13 +102,13 @@ export class PrimitiveManager { * @param clearType - 清除类型:'default'=默认 Primitive,'custom'=自定义 Primitive,'all'=所有 Primitive(默认 'custom') */ clearAllPrimitives(clearType: 'default' | 'custom' | 'all' = 'custom'): void { - const targetMap = this.#getTargetMapByType(clearType) + const targetMap = this.#getTargetMapByType(clearType); targetMap.forEach((primitive) => { - this.#viewer.scene.primitives.remove(primitive) - }) + this.#viewer.scene.primitives.remove(primitive); + }); - this.#clearMapsByType(clearType) + this.#clearMapsByType(clearType); } /** @@ -112,73 +116,75 @@ export class PrimitiveManager { * @param clearType - 类型:'default'=默认 Primitive,'custom'=自定义 Primitive,'all'=所有 Primitive(默认 'all') * @returns Primitive ID 集合 */ - getPrimitiveIds(clearType: 'default' | 'custom' | 'all' = 'all'): Set { - return this.#getTargetIdsByType(clearType) + getPrimitiveIds( + clearType: 'default' | 'custom' | 'all' = 'all' + ): Set { + return this.#getTargetIdsByType(clearType); } // ===================== 私有方法 ===================== #groupPrimitivesByType(primitives: PrimitiveOptions[]) { const grouped: { - points: PrimitiveOptions[] - polylines: PrimitiveOptions[] - polygons: PrimitiveOptions[] - billboards: PrimitiveOptions[] + points: PrimitiveOptions[]; + polylines: PrimitiveOptions[]; + polygons: PrimitiveOptions[]; + billboards: PrimitiveOptions[]; } = { points: [], polylines: [], polygons: [], billboards: [], - } + }; primitives.forEach((option) => { - const { id } = option - this.#validatePrimitiveUniqueId(id) + const { id } = option; + this.#validatePrimitiveUniqueId(id); switch (option.type) { case 'point': - grouped.points.push(option) - break + grouped.points.push(option); + break; case 'polyline': - grouped.polylines.push(option) - break + grouped.polylines.push(option); + break; case 'polygon': - grouped.polygons.push(option) - break + grouped.polygons.push(option); + break; case 'billboard': - grouped.billboards.push(option) - break + grouped.billboards.push(option); + break; } - }) + }); - return grouped + return grouped; } #validatePrimitiveUniqueId(id: string): void { if (this.#defaultPrimitiveMap.has(id) || this.#customPrimitiveMap.has(id)) { - throw new Error(`Primitive ID ${id} 已存在`) + throw new Error(`Primitive ID ${id} 已存在`); } } #getPrimitiveInfo(id: string) { - const isDefault = this.#defaultPrimitiveMap.has(id) + const isDefault = this.#defaultPrimitiveMap.has(id); const primitive = isDefault ? this.#defaultPrimitiveMap.get(id) - : this.#customPrimitiveMap.get(id) - return { isDefault, primitive } + : this.#customPrimitiveMap.get(id); + return { isDefault, primitive }; } #removePrimitiveId(id: string, isDefault: boolean): void { if (isDefault) { - this.#defaultPrimitiveMap.delete(id) + this.#defaultPrimitiveMap.delete(id); } else { - this.#customPrimitiveMap.delete(id) + this.#customPrimitiveMap.delete(id); } } #addPointPrimitives(options: PrimitiveOptions[]): void { const instances = options.map((option) => { - const position = this.#convertPosition(option.positions[0]!) + const position = this.#convertPosition(option.positions[0]!); return new GeometryInstance({ id: option.id, geometry: new CircleGeometry({ @@ -187,24 +193,29 @@ export class PrimitiveManager { vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT, }), attributes: { - color: ColorGeometryInstanceAttribute.fromColor(option.color || Color.RED), + color: ColorGeometryInstanceAttribute.fromColor( + option.color || Color.RED + ), }, - }) - }) + }); + }); const primitive = new Primitive({ geometryInstances: instances, - appearance: new PerInstanceColorAppearance({ translucent: false, closed: true }), + appearance: new PerInstanceColorAppearance({ + translucent: false, + closed: true, + }), asynchronous: false, - }) + }); - this.#viewer.scene.primitives.add(primitive) - this.#storePrimitives(options, primitive) + this.#viewer.scene.primitives.add(primitive); + this.#storePrimitives(options, primitive); } #addPolylinePrimitives(options: PrimitiveOptions[]): void { const instances = options.map((option) => { - const positions = this.#convertPositionArray(option.positions) + const positions = this.#convertPositionArray(option.positions); return new GeometryInstance({ id: option.id, geometry: new PolylineGeometry({ @@ -213,24 +224,26 @@ export class PrimitiveManager { vertexFormat: PolylineColorAppearance.VERTEX_FORMAT, }), attributes: { - color: ColorGeometryInstanceAttribute.fromColor(option.color || Color.BLUE), + color: ColorGeometryInstanceAttribute.fromColor( + option.color || Color.BLUE + ), }, - }) - }) + }); + }); const primitive = new Primitive({ geometryInstances: instances, appearance: new PolylineColorAppearance({ translucent: true }), asynchronous: false, - }) + }); - this.#viewer.scene.primitives.add(primitive) - this.#storePrimitives(options, primitive) + this.#viewer.scene.primitives.add(primitive); + this.#storePrimitives(options, primitive); } #addPolygonPrimitives(options: PrimitiveOptions[]): void { const instances = options.map((option) => { - const positions = this.#convertPositionArray(option.positions) + const positions = this.#convertPositionArray(option.positions); return new GeometryInstance({ id: option.id, geometry: new PolygonGeometry({ @@ -239,91 +252,118 @@ export class PrimitiveManager { }), attributes: { color: ColorGeometryInstanceAttribute.fromColor( - option.color || Color.GREEN.withAlpha(0.5), + option.color || Color.GREEN.withAlpha(0.5) ), }, - }) - }) + }); + }); const primitive = new Primitive({ geometryInstances: instances, - appearance: new PerInstanceColorAppearance({ translucent: true, closed: true }), + appearance: new PerInstanceColorAppearance({ + translucent: true, + closed: true, + }), asynchronous: false, - }) + }); - this.#viewer.scene.primitives.add(primitive) - this.#storePrimitives(options, primitive) + this.#viewer.scene.primitives.add(primitive); + this.#storePrimitives(options, primitive); } #addBillboardPrimitives(options: PrimitiveOptions[]): void { - const collection = new BillboardCollection() + // 定义类型,仅供临时使用 + type BillboardConfig = { + id: string; + position: Cartesian3; + image: string | undefined; + scale?: number; + color?: Color; + scaleByDistance?: NearFarScalar; + [key: string]: unknown; + }; + + const collection = new BillboardCollection(); options.forEach((option) => { - const position = this.#convertPosition(option.positions[0]!) - - const billboardConfig: any = { + const position = this.#convertPosition(option.positions[0]!); + + const billboardConfig: BillboardConfig = { id: option.id, position, image: option.image, scale: option.scale || 1, color: option.color || Color.WHITE, - } + }; if (option.scaleByDistance) { - billboardConfig.scaleByDistance = option.scaleByDistance + billboardConfig.scaleByDistance = option.scaleByDistance; } if (option.customProperties) { - Object.assign(billboardConfig, option.customProperties) + Object.assign(billboardConfig, option.customProperties); } - collection.add(billboardConfig) - }) + collection.add(billboardConfig); + }); - this.#viewer.scene.primitives.add(collection) - this.#storePrimitives(options, collection) + this.#viewer.scene.primitives.add(collection); + this.#storePrimitives(options, collection); } - #storePrimitives(options: PrimitiveOptions[], primitive: Primitive | BillboardCollection): void { + #storePrimitives( + options: PrimitiveOptions[], + primitive: Primitive | BillboardCollection + ): void { options.forEach((option) => { if (option.isDefault) { - this.#defaultPrimitiveMap.set(option.id, primitive) + this.#defaultPrimitiveMap.set(option.id, primitive); } else { - this.#customPrimitiveMap.set(option.id, primitive) + this.#customPrimitiveMap.set(option.id, primitive); } - }) + }); } #getTargetMapByType( - clearType: 'default' | 'custom' | 'all', + clearType: 'default' | 'custom' | 'all' ): Map { - const targetMap = new Map() + const targetMap = new Map(); if (clearType === 'default' || clearType === 'all') - this.#defaultPrimitiveMap.forEach((value, key) => targetMap.set(key, value)) + this.#defaultPrimitiveMap.forEach((value, key) => + targetMap.set(key, value) + ); if (clearType === 'custom' || clearType === 'all') - this.#customPrimitiveMap.forEach((value, key) => targetMap.set(key, value)) - return targetMap + this.#customPrimitiveMap.forEach((value, key) => + targetMap.set(key, value) + ); + return targetMap; } #clearMapsByType(clearType: 'default' | 'custom' | 'all'): void { - if (clearType === 'default' || clearType === 'all') this.#defaultPrimitiveMap.clear() - if (clearType === 'custom' || clearType === 'all') this.#customPrimitiveMap.clear() + if (clearType === 'default' || clearType === 'all') + this.#defaultPrimitiveMap.clear(); + if (clearType === 'custom' || clearType === 'all') + this.#customPrimitiveMap.clear(); } #getTargetIdsByType(clearType: 'default' | 'custom' | 'all'): Set { - const targetIds = new Set() + const targetIds = new Set(); if (clearType === 'default' || clearType === 'all') - this.#defaultPrimitiveMap.forEach((_, key) => targetIds.add(key)) + this.#defaultPrimitiveMap.forEach((_, key) => targetIds.add(key)); if (clearType === 'custom' || clearType === 'all') - this.#customPrimitiveMap.forEach((_, key) => targetIds.add(key)) - return targetIds + this.#customPrimitiveMap.forEach((_, key) => targetIds.add(key)); + return targetIds; } #convertPosition(pos: Cartesian3 | [number, number, number]): Cartesian3 { - return Array.isArray(pos) ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) : pos + return Array.isArray(pos) + ? Cartesian3.fromDegrees(pos[0], pos[1], pos[2] || 0) + : pos; } - #convertPositionArray(positions: (Cartesian3 | [number, number, number])[]): Cartesian3[] { - return positions.map((pos) => this.#convertPosition(pos)) + #convertPositionArray( + positions: (Cartesian3 | [number, number, number])[] + ): Cartesian3[] { + return positions.map((pos) => this.#convertPosition(pos)); } } diff --git a/src/utils/request/http.ts b/src/utils/request/http.ts index ff80f99..51023f3 100644 --- a/src/utils/request/http.ts +++ b/src/utils/request/http.ts @@ -1,14 +1,17 @@ -import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from 'axios' -import configJson from '@/config/config.json' -import { SafetyUtils } from '../safety/SafetyUtils.ts' -import { ElMessage } from 'element-plus' -import router from '@/router' +import axios, { + type InternalAxiosRequestConfig, + type AxiosResponse, +} from 'axios'; +import configJson from '@/config/config.json'; +import { SafetyUtils } from '../safety/SafetyUtils.ts'; +import { ElMessage } from 'element-plus'; +import router from '@/router'; // 扩展Axios内部配置类型 declare module 'axios' { interface InternalAxiosRequestConfig { - __sm4Key?: string - isNoEncryptUrl?: boolean + __sm4Key?: string; + isNoEncryptUrl?: boolean; } } @@ -16,133 +19,154 @@ const httpInstance = axios.create({ baseURL: configJson.apiBaseUrl, timeout: 15000, // 增加超时时间 withCredentials: true, -}) +}); // 请求拦截器 httpInstance.interceptors.request.use( - async (config: InternalAxiosRequestConfig): Promise => { - const { url, method } = config + async ( + config: InternalAxiosRequestConfig + ): Promise => { + const { url, method } = config; // 初始化headers - config.headers = config.headers || {} + config.headers = config.headers || {}; // 加密处理标记 - const isNoEncryptUrl = configJson.noEncryptUrls.some((path) => url?.includes(path)) - config.isNoEncryptUrl = isNoEncryptUrl + const isNoEncryptUrl = configJson.noEncryptUrls.some((path) => + url?.includes(path) + ); + config.isNoEncryptUrl = isNoEncryptUrl; if (!isNoEncryptUrl) { try { // 生成SM4密钥并加密,无论是否有业务参数 - const sm4Key = SafetyUtils.generateSm4Key() - config.__sm4Key = sm4Key - const sm4KeyEncrypted = await SafetyUtils.sm2Encrypt(sm4Key) + const sm4Key = SafetyUtils.generateSm4Key(); + config.__sm4Key = sm4Key; + const sm4KeyEncrypted = await SafetyUtils.sm2Encrypt(sm4Key); // GET请求:处理URL参数(无论是否有params,都要传递sm4KeyEncrypted) if (method?.toUpperCase() === 'GET') { // 有业务参数则加密,无参数则仅传递sm4KeyEncrypted - const encryptedParams = config.params ? SafetyUtils.sm4Encrypt(sm4Key, config.params) : '' // 无参数时encryptedData可为空 + const encryptedParams = config.params + ? SafetyUtils.sm4Encrypt(sm4Key, config.params) + : ''; // 无参数时encryptedData可为空 config.params = { encryptedData: encryptedParams, sm4KeyEncrypted: sm4KeyEncrypted, - } + }; } // POST/PUT/DELETE/PATCH请求:处理请求体(无论是否有data,都要传递sm4KeyEncrypted) - if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(method?.toUpperCase() || '')) { + if ( + ['POST', 'PUT', 'DELETE', 'PATCH'].includes( + method?.toUpperCase() || '' + ) + ) { if (config.data instanceof FormData) { // 加密表单数据 - const encryptedFormData = SafetyUtils.encryptFormData(sm4Key, config.data) + const encryptedFormData = SafetyUtils.encryptFormData( + sm4Key, + config.data + ); - const newFormData = new FormData() + const newFormData = new FormData(); // 复制加密后的业务字段 for (const [key, value] of encryptedFormData.entries()) { - newFormData.append(key, value) + newFormData.append(key, value); } // 强制添加sm4KeyEncrypted - newFormData.append('sm4KeyEncrypted', sm4KeyEncrypted) - config.data = newFormData + newFormData.append('sm4KeyEncrypted', sm4KeyEncrypted); + config.data = newFormData; } else { // 有业务数据则加密,无数据则encryptedData为空 - const encryptedData = config.data ? SafetyUtils.sm4Encrypt(sm4Key, config.data) : '' + const encryptedData = config.data + ? SafetyUtils.sm4Encrypt(sm4Key, config.data) + : ''; config.data = { encryptedData: encryptedData, sm4KeyEncrypted: sm4KeyEncrypted, - } + }; } } } catch (error) { - console.error('请求加密失败:', error) - return Promise.reject(new Error('请求数据加密失败')) + console.error('请求加密失败:', error); + return Promise.reject(new Error('请求数据加密失败')); } } - return config + return config; }, (error) => { - console.error('请求拦截器错误:', error) - return Promise.reject(error) - }, -) + console.error('请求拦截器错误:', error); + return Promise.reject(error); + } +); // 响应拦截器 httpInstance.interceptors.response.use( (response: AxiosResponse) => { - const { config, data } = response - let processedData // 用于存储处理后(原始或解密)的数据 + const { config, data } = response; + let processedData; // 用于存储处理后(原始或解密)的数据 // 处理非加密接口或无密钥的情况 if (config.isNoEncryptUrl || !config.__sm4Key) { - processedData = data + processedData = data; } else { // 处理加密接口的解密逻辑 try { if (typeof data === 'string') { // 解密字符串类型的加密数据 - processedData = SafetyUtils.sm4Decrypt(config.__sm4Key, data) + processedData = SafetyUtils.sm4Decrypt(config.__sm4Key, data); } else if (data && typeof data === 'object') { // 解密对象中包含的加密字段 - processedData = SafetyUtils.sm4Decrypt(config.__sm4Key, data.encryptedData || data) + processedData = SafetyUtils.sm4Decrypt( + config.__sm4Key, + data.encryptedData || data + ); } else { // 非预期数据格式直接使用原始数据 - processedData = data + processedData = data; } } catch (error) { - console.error('响应数据解密失败:', error) - ElMessage.error('数据解密失败,请重试') - return Promise.reject(new Error('数据解密失败,请重试')) + console.error('响应数据解密失败:', error); + ElMessage.error('数据解密失败,请重试'); + return Promise.reject(new Error('数据解密失败,请重试')); } } // 统一判断处理后的数据状态 if (processedData?.code === 200 || processedData?.code === 409) { - return processedData + return processedData; } else if (processedData?.code == 401) { - router.push(`/login?redirect=${router.currentRoute.value.fullPath}`) - ElMessage.error('请先登录') + router.push(`/login?redirect=${router.currentRoute.value.fullPath}`); + ElMessage.error('请先登录'); } else { - const errorMsg = processedData?.message || '操作失败,请稍后重试' - ElMessage.error(errorMsg) - return Promise.reject(new Error(errorMsg)) + const errorMsg = processedData?.message || '操作失败,请稍后重试'; + ElMessage.error(errorMsg); + return Promise.reject(new Error(errorMsg)); } }, (error) => { - console.error('响应拦截器错误:', error) - let errorMsg = '请求失败,请稍后重试' + console.error('响应拦截器错误:', error); + let errorMsg = '请求失败,请稍后重试'; // 处理服务器解密相关错误 - if (error.response?.status === 500 && error.response?.data?.msg?.includes('解密')) { - errorMsg = '服务器解密失败,请检查密钥配置' + if ( + error.response?.status === 500 && + error.response?.data?.msg?.includes('解密') + ) { + errorMsg = '服务器解密失败,请检查密钥配置'; } else if (error.message) { // 使用错误对象自带的消息 - errorMsg = error.message + errorMsg = error.message; } // 错误提示 - ElMessage.error(errorMsg) - return Promise.reject(error) - }, -) + ElMessage.error(errorMsg); + return Promise.reject(error); + } +); -export default httpInstance +export default httpInstance; diff --git a/src/utils/safety/SafetyUtils.ts b/src/utils/safety/SafetyUtils.ts index 6b9f530..78b7530 100644 --- a/src/utils/safety/SafetyUtils.ts +++ b/src/utils/safety/SafetyUtils.ts @@ -1,33 +1,33 @@ -import { $api } from '@/api/api' -import { useCryptStore } from '@/stores/useCryptStore' -import { SM2, SM4 } from 'gm-crypto' +import { $api } from '@/api/api'; +import { useCryptStore } from '@/stores/useCryptStore'; +import { SM2, SM4 } from 'gm-crypto'; // 与后端SM2Utils对应的模式配置 -const SM2_MODE = SM2.constants.C1C3C2 -const SM2_INPUT_ENCODING = 'utf8' -const SM2_OUTPUT_ENCODING = 'hex' +const SM2_MODE = SM2.constants.C1C3C2; +const SM2_INPUT_ENCODING = 'utf8'; +const SM2_OUTPUT_ENCODING = 'hex'; // 与后端SM4Utils对应的模式配置(ECB模式) -const SM4_MODE = SM4.constants.ECB -const SM4_INPUT_ENCODING = 'utf8' -const SM4_OUTPUT_ENCODING = 'hex' +const SM4_MODE = SM4.constants.ECB; +const SM4_INPUT_ENCODING = 'utf8'; +const SM4_OUTPUT_ENCODING = 'hex'; export const SafetyUtils = { /** * 获取SM2公钥 */ getSm2PublicKey: async (): Promise => { - const cryptStore = useCryptStore() - if (cryptStore.sm2PublicKey) return cryptStore.sm2PublicKey + const cryptStore = useCryptStore(); + if (cryptStore.sm2PublicKey) return cryptStore.sm2PublicKey; try { - const res = await $api.crypto.getSm2PublicKey() - const publicKey = res.data.publicKey - cryptStore.sm2PublicKey = publicKey - return publicKey + const res = await $api.crypto.getSm2PublicKey(); + const publicKey = res.data.publicKey; + cryptStore.sm2PublicKey = publicKey; + return publicKey; } catch (error) { - console.error('获取SM2公钥失败:', error) - throw new Error('获取加密公钥失败') + console.error('获取SM2公钥失败:', error); + throw new Error('获取加密公钥失败'); } }, @@ -35,16 +35,20 @@ export const SafetyUtils = { * 生成随机SM4密钥(16字节=32位十六进制字符串) */ generateSm4Key: (): string => { - return SafetyUtils._generateRandomHex(16) + return SafetyUtils._generateRandomHex(16); }, /** * SM2非对称加密(公钥加密) */ - sm2Encrypt: async (data: object | string, publicKey?: string): Promise => { + sm2Encrypt: async ( + data: object | string, + publicKey?: string + ): Promise => { try { - const targetPublicKey = publicKey || (await SafetyUtils.getSm2PublicKey()) - const plaintext = typeof data === 'string' ? data : JSON.stringify(data) + const targetPublicKey = + publicKey || (await SafetyUtils.getSm2PublicKey()); + const plaintext = typeof data === 'string' ? data : JSON.stringify(data); return ( '04' + SM2.encrypt(plaintext, targetPublicKey, { @@ -52,10 +56,10 @@ export const SafetyUtils = { inputEncoding: SM2_INPUT_ENCODING, outputEncoding: SM2_OUTPUT_ENCODING, }) - ) + ); } catch (error) { - console.error('SM2加密失败:', error) - throw new Error('数据加密失败') + console.error('SM2加密失败:', error); + throw new Error('数据加密失败'); } }, @@ -68,10 +72,10 @@ export const SafetyUtils = { inputEncoding: SM2_OUTPUT_ENCODING, outputEncoding: SM2_INPUT_ENCODING, mode: SM2.constants.C1C3C2, - }) + }); } catch (error) { - console.error('SM2加密失败:', error) - throw new Error('数据加密失败') + console.error('SM2加密失败:', error); + throw new Error('数据加密失败'); } }, @@ -80,15 +84,15 @@ export const SafetyUtils = { */ sm4Encrypt: (key: string, data: object | string): string => { try { - const plaintext = typeof data === 'string' ? data : JSON.stringify(data) + const plaintext = typeof data === 'string' ? data : JSON.stringify(data); return SM4.encrypt(plaintext, key, { mode: SM4_MODE, inputEncoding: SM4_INPUT_ENCODING, outputEncoding: SM4_OUTPUT_ENCODING, - }) + }); } catch (error) { - console.error('SM4加密失败:', error) - throw new Error('数据加密失败') + console.error('SM4加密失败:', error); + throw new Error('数据加密失败'); } }, @@ -101,17 +105,17 @@ export const SafetyUtils = { mode: SM4_MODE, inputEncoding: SM4_OUTPUT_ENCODING, outputEncoding: SM4_INPUT_ENCODING, - }) + }); // 尝试解析JSON,兼容对象和字符串 try { - return JSON.parse(plaintext) + return JSON.parse(plaintext); } catch { - return plaintext + return plaintext; } } catch (error) { - console.error('SM4解密失败:', error) - throw new Error('数据解密失败') + console.error('SM4解密失败:', error); + throw new Error('数据解密失败'); } }, @@ -119,28 +123,30 @@ export const SafetyUtils = { * 加密FormData中的普通字段 */ encryptFormData: (key: string, formData: FormData): FormData => { - const encryptedFormData = new FormData() + const encryptedFormData = new FormData(); for (const [fieldName, value] of formData.entries()) { if (value instanceof Blob) { // 保留文件字段 - encryptedFormData.append(fieldName, value, (value as File).name) + encryptedFormData.append(fieldName, value, (value as File).name); } else { // 加密普通字段 - const encryptedValue = SafetyUtils.sm4Encrypt(key, String(value)) - encryptedFormData.append(fieldName, encryptedValue) + const encryptedValue = SafetyUtils.sm4Encrypt(key, String(value)); + encryptedFormData.append(fieldName, encryptedValue); } } - return encryptedFormData + return encryptedFormData; }, /** * 生成指定长度的随机十六进制字符串 */ _generateRandomHex: (length: number): string => { - const uint8Array = new Uint8Array(length) - window.crypto.getRandomValues(uint8Array) - return Array.from(uint8Array, (byte) => byte.toString(16).padStart(2, '0')).join('') + const uint8Array = new Uint8Array(length); + window.crypto.getRandomValues(uint8Array); + return Array.from(uint8Array, (byte) => + byte.toString(16).padStart(2, '0') + ).join(''); }, -} +}; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 37befa9..88af070 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -13,46 +13,48 @@ export const Utils = { debounce: function ( func: (this: This, ...args: T) => R, delay: number = 500, - immediate: boolean = false, + immediate: boolean = false ) { - let timer: number | null = null + let timer: number | null = null; // 用泛型This指定this类型 return function (this: This, ...args: T): void { - if (timer) clearTimeout(timer) + if (timer) clearTimeout(timer); // 立即执行逻辑 if (immediate && !timer) { - func.apply(this, args) + func.apply(this, args); } // 重新设置定时器 timer = window.setTimeout(() => { if (!immediate) { - func.apply(this, args) + func.apply(this, args); } - timer = null - }, delay) - } + timer = null; + }, delay); + }; }, formatDate: (format: string, date: Date = new Date()): string => { // 基础时间数据 - const year = date.getFullYear() - const month = date.getMonth() + 1 // 月份0-11,需+1 - const day = date.getDate() - const hours24 = date.getHours() - const hours12 = hours24 % 12 || 12 // 12小时制处理(0→12) - const minutes = date.getMinutes() - const seconds = date.getSeconds() - const weekNum = date.getDay() // 星期0-6(0=周日) + const year = date.getFullYear(); + const month = date.getMonth() + 1; // 月份0-11,需+1 + const day = date.getDate(); + const hours24 = date.getHours(); + const hours12 = hours24 % 12 || 12; // 12小时制处理(0→12) + const minutes = date.getMinutes(); + const seconds = date.getSeconds(); + const weekNum = date.getDay(); // 星期0-6(0=周日) // 星期映射配置 const weekMaps = { - ddd: ['日', '一', '二', '三', '四', '五', '六'].map((day) => `星期${day}`), + ddd: ['日', '一', '二', '三', '四', '五', '六'].map( + (day) => `星期${day}` + ), dd: ['日', '一', '二', '三', '四', '五', '六'].map((day) => `周${day}`), d: [0, 1, 2, 3, 4, 5, 6], - } + }; // 占位符替换规则(顺序:长占位符优先,避免冲突) const replaceRules = [ @@ -73,12 +75,12 @@ export const Utils = { { regex: /ddd/g, value: weekMaps.ddd[weekNum] }, { regex: /dd/g, value: weekMaps.dd[weekNum] }, { regex: /d/g, value: weekMaps.d[weekNum] }, - ] + ]; // 执行替换 return replaceRules.reduce((result, { regex, value }) => { - return result.replace(regex, String(value ?? '')) - }, format) + return result.replace(regex, String(value ?? '')); + }, format); }, /** @@ -91,115 +93,121 @@ export const Utils = { deepClone: (source: T, hash = new WeakMap()): T => { // 处理 null 或 undefined if (source === null || source === undefined) { - return source + return source; } // 处理原始类型(string, number, boolean, symbol, bigint, function) if (typeof source !== 'object' && typeof source !== 'function') { - return source + return source; } // 处理函数 - 直接返回原函数引用(通常不需要克隆函数) if (typeof source === 'function') { - return source + return source; } // 解决循环引用 if (hash.has(source)) { - return hash.get(source) as T + return hash.get(source) as T; } // 处理 Date 对象 if (source instanceof Date) { - const cloned = new Date(source.getTime()) as T - hash.set(source, cloned) - return cloned + const cloned = new Date(source.getTime()) as T; + hash.set(source, cloned); + return cloned; } // 处理 RegExp 对象 if (source instanceof RegExp) { - const cloned = new RegExp(source.source, source.flags) - cloned.lastIndex = source.lastIndex // 安全访问 - return cloned as unknown as T + const cloned = new RegExp(source.source, source.flags); + cloned.lastIndex = source.lastIndex; // 安全访问 + return cloned as unknown as T; } // 处理 Map 对象 if (source instanceof Map) { - const cloned = new Map() - hash.set(source, cloned) + const cloned = new Map(); + hash.set(source, cloned); source.forEach((value, key) => { - cloned.set(Utils.deepClone(key, hash), Utils.deepClone(value, hash)) - }) - return cloned as T + cloned.set(Utils.deepClone(key, hash), Utils.deepClone(value, hash)); + }); + return cloned as T; } // 处理 Set 对象 if (source instanceof Set) { - const cloned = new Set() - hash.set(source, cloned) + const cloned = new Set(); + hash.set(source, cloned); for (const value of source.values()) { - cloned.add(Utils.deepClone(value, hash)) + cloned.add(Utils.deepClone(value, hash)); } - return cloned as T + return cloned as T; } // 处理 ArrayBuffer if (source instanceof ArrayBuffer) { - const cloned = source.slice(0) as T - hash.set(source, cloned) - return cloned + const cloned = source.slice(0) as T; + hash.set(source, cloned); + return cloned; } // 处理数组 if (Array.isArray(source)) { - const cloned: T[] = [] - hash.set(source, cloned) + const cloned: T[] = []; + hash.set(source, cloned); for (let i = 0; i < source.length; i++) { - cloned[i] = Utils.deepClone(source[i], hash) + cloned[i] = Utils.deepClone(source[i], hash); } - return cloned as T + return cloned as T; } // 处理普通对象 if (typeof source === 'object') { // 处理 Error 对象 if (source instanceof Error) { - const cloned = new Error(source.message) - cloned.stack = source.stack - cloned.name = source.name - hash.set(source, cloned) - return cloned as T + const cloned = new Error(source.message); + cloned.stack = source.stack; + cloned.name = source.name; + hash.set(source, cloned); + return cloned as T; } // 处理其他对象 - const cloned: { [key: string | symbol]: unknown } = {} - hash.set(source, cloned) + const cloned: { [key: string | symbol]: unknown } = {}; + hash.set(source, cloned); // 获取对象的所有属性(包括不可枚举的属性和 Symbol) - const keys = [...Object.getOwnPropertyNames(source), ...Object.getOwnPropertySymbols(source)] + const keys = [ + ...Object.getOwnPropertyNames(source), + ...Object.getOwnPropertySymbols(source), + ]; for (const key of keys) { - const descriptor = Object.getOwnPropertyDescriptor(source, key) + const descriptor = Object.getOwnPropertyDescriptor(source, key); // 如果是访问器属性 if (descriptor && descriptor.get) { - Object.defineProperty(cloned, key, descriptor) + Object.defineProperty(cloned, key, descriptor); } else { // 如果是数据属性 - cloned[key] = Utils.deepClone((source as { [key: string | symbol]: unknown })[key], hash) + cloned[key] = Utils.deepClone( + (source as { [key: string | symbol]: unknown })[key], + hash + ); } } // 处理原型链 - const proto = Object.getPrototypeOf(source) + const proto = Object.getPrototypeOf(source); if (proto && proto !== Object.prototype) { - Object.setPrototypeOf(cloned, proto) + Object.setPrototypeOf(cloned, proto); } - return cloned as T + return cloned as T; } // 对于其他无法处理的情况,返回原值 - return source + return source; }, /** @@ -209,8 +217,13 @@ export const Utils = { * @param {number} width - 元素的宽度 * @param {number} height - 元素的高度 * @returns {[number, number]} 调整后的 [newX, newY] - */ - keepWithinScreen: (offsetX: number, offsetY: number, width: number, height: number) => { + */ + keepWithinScreen: ( + offsetX: number, + offsetY: number, + width: number, + height: number + ) => { const viewportW = window.innerWidth; const viewportH = window.innerHeight; @@ -224,5 +237,5 @@ export const Utils = { const newY = Math.min(Math.max(offsetY, 0), maxY); return [newX, newY]; - } -} + }, +}; diff --git a/src/views/Index.vue b/src/views/IndexView.vue similarity index 53% rename from src/views/Index.vue rename to src/views/IndexView.vue index ce8769a..44933a0 100644 --- a/src/views/Index.vue +++ b/src/views/IndexView.vue @@ -1,64 +1,72 @@ \ No newline at end of file + } + diff --git a/src/views/home/earthquake/Earthquake.vue b/src/views/home/earthquake/Earthquake.vue deleted file mode 100644 index 26b92a5..0000000 --- a/src/views/home/earthquake/Earthquake.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/src/views/home/earthquake/EarthquakeView.vue b/src/views/home/earthquake/EarthquakeView.vue new file mode 100644 index 0000000..f681700 --- /dev/null +++ b/src/views/home/earthquake/EarthquakeView.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/views/home/rainstorm/Rainstorm.vue b/src/views/home/rainstorm/Rainstorm.vue deleted file mode 100644 index 63d9458..0000000 --- a/src/views/home/rainstorm/Rainstorm.vue +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/src/views/home/rainstorm/RainstormView.vue b/src/views/home/rainstorm/RainstormView.vue new file mode 100644 index 0000000..fc8d4f1 --- /dev/null +++ b/src/views/home/rainstorm/RainstormView.vue @@ -0,0 +1,18 @@ + + + + +