From b69e2eb18eb476881836eaf87afbf89b66448367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=88=E5=85=86=E5=A2=9E?= <你的邮箱@example.com> Date: Wed, 13 May 2026 08:44:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E5=8D=95=E4=B8=AA=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=EF=BC=8C=E6=95=B0=E6=8D=AE=E5=A1=AB=E6=8A=A5bug?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/guoYuSheShiShuJuTianBao/index.ts | 3 +- frontend/src/components/gis/GisView.vue | 60 +- frontend/src/components/gis/map.class.ts | 221 ++-- frontend/src/components/gis/map.d.ts | 329 +++--- frontend/src/components/gis/map.ol.ts | 439 +++---- frontend/src/layout/components/AppMain.vue | 4 +- frontend/src/modules/jidiSelectorMod.vue | 9 +- frontend/src/store/modules/jidiSelectEvent.ts | 784 ++---------- .../shuJuTianBao/guoYuDaoShuTongJi/index.vue | 29 +- .../guoYuSheShiShuJuHistory/index.vue | 3 + .../guoYuSheShiShuJuTianBaoForm.vue | 6 +- .../guoYuSheShiShuJuTianBaoTable.vue | 7 +- .../guoYuSheShiShuJuTianBao/index.vue | 16 +- .../views/shuJuTianBao/shengPiJiLu/index.vue | 3 + frontend/src/views/system/dept/index.vue | 316 +++-- frontend/src/views/system/dict/index.vue | 442 ++++--- frontend/src/views/system/menu/index.vue | 1051 ++++++++++------- frontend/src/views/system/record/index.vue | 230 ++-- frontend/src/views/system/review/index.vue | 3 + frontend/src/views/system/role/index.vue | 463 +++++--- frontend/src/views/system/user/index.vue | 792 ++++++++----- 21 files changed, 2831 insertions(+), 2379 deletions(-) diff --git a/frontend/src/api/guoYuSheShiShuJuTianBao/index.ts b/frontend/src/api/guoYuSheShiShuJuTianBao/index.ts index 3e86214..0cb7537 100644 --- a/frontend/src/api/guoYuSheShiShuJuTianBao/index.ts +++ b/frontend/src/api/guoYuSheShiShuJuTianBao/index.ts @@ -69,7 +69,8 @@ export function importFishZip(data: FormData) { url: '/data/fishDraft/importZip', method: 'post', data, - headers: { 'Content-Type': 'multipart/form-data' } + headers: { 'Content-Type': 'multipart/form-data' }, + timeout: 1000 * 60 * 5 // 5分钟 }); } // 取消导入任务 diff --git a/frontend/src/components/gis/GisView.vue b/frontend/src/components/gis/GisView.vue index 2437d04..5ec6466 100644 --- a/frontend/src/components/gis/GisView.vue +++ b/frontend/src/components/gis/GisView.vue @@ -56138,7 +56138,30 @@ const fetchPointData = _.debounce(async () => { matrixIds_index: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"], tileMatrixSetID: "EPSG:3857_qgc_sx_gjjdx_arcgistiles_l13", }); - + //基地 + mapClass.addBaseDataLayer({ + id: "hydropBase", + key: "hydropBase", + urlType: "gisurl", + url: + "https://211.99.26.225:18085/geoserver/gwc/service/tms/1.0.0/qgc%3AstationEra1117@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf", + geojson_url: + "https://211.99.26.225:18085/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:stationEra1117&maxFeatures=50&outputFormat=application/json&token=bearer a9a0f227-1df3-4e68-b380-2eca5bb49bd1", + url_3d: "https://211.99.26.225:18085/geoserver/qgc/wms", + _layer: "stationEra1117", + layers: "qgc:stationEra1117", + rasteropacity: 0.5, + visible: true, + minZoom: 0, + maxZoom: 20, + type: "vector", + layerType: "line", + paint: { + "line-color": "#C5C6F3", + "line-width": 1, + "line-opacity": 1, + }, + }); // 梯级流域图 // mapClass.addBaseDataLayer( // { @@ -56352,34 +56375,12 @@ watch( watch( () => JidiSelectEventStore.selectedItem, (newVal) => { - console.log(newVal); if (newVal) { - // if (newVal.wbsCode == "all") { - // } else { - // mapClass.addBaseDataLayer({ - // id: "hydropBase", - // key: "hydropBase", - // urlType: "gisurl", - // url: - // "https://211.99.26.225:18085/geoserver/gwc/service/tms/1.0.0/qgc%3AstationEra1117@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf", - // geojson_url: - // "https://211.99.26.225:18085/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:stationEra1117&maxFeatures=50&outputFormat=application/json&token=bearer a9a0f227-1df3-4e68-b380-2eca5bb49bd1", - // url_3d: "https://211.99.26.225:18085/geoserver/qgc/wms", - // _layer: "stationEra1117", - // layers: "qgc:stationEra1117", - // rasteropacity: 0.5, - // visible: true, - // minZoom: 0, - // maxZoom: 20, - // type: "vector", - // layerType: "line", - // paint: { - // "line-color": "#C5C6F3", - // "line-width": 1, - // "line-opacity": 1, - // }, - // }); - // } + if (newVal.wbsCode == "all") { + mapClass.jdPanelControlShowAndHidden(newVal.wbsCode, false); + } else { + mapClass.jdPanelControlShowAndHidden(newVal.wbsCode, true); + } } }, { deep: true } @@ -56401,8 +56402,6 @@ onUnmounted(() => { width: 100%; height: 100%; position: absolute; - left: 0; - top: 0; visibility: visible; } #mapContainer { @@ -56410,6 +56409,7 @@ onUnmounted(() => { height: 100%; position: absolute; background-color: #fff; + cursor: grab; z-index: 1; } /* 消除瓦片之间的缝隙和默认边框 */ diff --git a/frontend/src/components/gis/map.class.ts b/frontend/src/components/gis/map.class.ts index 5cb375c..edbd096 100644 --- a/frontend/src/components/gis/map.class.ts +++ b/frontend/src/components/gis/map.class.ts @@ -1,10 +1,10 @@ -import type { layer, MapInterface } from "./map.d"; +import type { layer, MapInterface } from './map.d'; // import { MapLeaflet } from "./map.leaflet"; -import { MapOl } from "./map.ol"; +import { MapOl } from './map.ol'; interface MapClassInterface extends MapInterface { - layers: Map; - view: any; + layers: Map; + view: any; } //描点参数 @@ -16,114 +16,123 @@ export type MDOptions = { isRemove?: boolean; // 是否移除标签 }; -export const mapServerBaseUrl = localStorage.getItem("gisurl") || "http://210.72.227.199:18084/"; +export const mapServerBaseUrl = + localStorage.getItem('gisurl') || 'http://210.72.227.199:18084/'; export class MapClass implements MapClassInterface { - layers: Map; - view: any; - private static instance: MapClass; - private service: MapInterface; + layers: Map; + view: any; + private static instance: MapClass; + private service: MapInterface; - constructor() { - this.layers = new Map(); - this.view = null; - // this.service = new MapLeaflet(); - this.service = new MapOl(); - } - static getInstance(): MapClass { - if (!this.instance) { - this.instance = new MapClass(); - } - return this.instance; - } - // 地图初始化 - init(container: HTMLElement, rectangle?: any): Promise { - return this.service.init(container, rectangle).then((map) => { - this.view = map; - return map; - }); - } - // 基地面板控制 - jdPanelControlShowAndHidden(baseid: String, isAll: boolean): void { - this.service.jdPanelControlShowAndHidden(baseid, isAll) - } - mdLayerShowOrHidden(): void { - // layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean - // this.service.mdLayerShowOrHidden(layerType, key, baseid, checked, isAll) - } - // 添加基础数据图层 - addBaseDataLayer(layer: any): void { - return this.service.addBaseDataLayer(layer) - } - // 基础图层显示影隐藏方法 - controlBaseLayerTreeShowAndHidden(layerType: String, key: String, checked: boolean) { - this.service.controlBaseLayerTreeShowAndHidden(layerType, key, checked) - } - // 图层树控制描点数据显示隐藏方法 - mdLayerTreeShowOrHidden(layerType: String, checked?: boolean) { - this.service.mdLayerTreeShowOrHidden(layerType, checked) - } - // 初始化加载描点数据 - addInitDataLayer = (pointData: any[], layerType: any, mdoptions?: any) => { - return this.service.addInitDataLayer(pointData, layerType, mdoptions) - } - //切换底图 - baseLayerSwitcher(key: string): void { - this.service.baseLayerSwitcher(key) + constructor() { + this.layers = new Map(); + this.view = null; + // this.service = new MapLeaflet(); + this.service = new MapOl(); + } + static getInstance(): MapClass { + if (!this.instance) { + this.instance = new MapClass(); } + return this.instance; + } + // 地图初始化 + init(container: HTMLElement, rectangle?: any): Promise { + return this.service.init(container, rectangle).then(map => { + this.view = map; + return map; + }); + } + // 基地面板控制 + jdPanelControlShowAndHidden(baseid: String, isAll: boolean): void { + this.service.jdPanelControlShowAndHidden(baseid, isAll); + } + mdLayerShowOrHidden(): void { + // layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean + // this.service.mdLayerShowOrHidden(layerType, key, baseid, checked, isAll) + } + // 添加基础数据图层 + addBaseDataLayer(layer: any): void { + return this.service.addBaseDataLayer(layer); + } + // 基础图层显示影隐藏方法 + controlBaseLayerTreeShowAndHidden( + layerType: String, + key: String, + checked: boolean + ) { + this.service.controlBaseLayerTreeShowAndHidden(layerType, key, checked); + } + // 图层树控制描点数据显示隐藏方法 + mdLayerTreeShowOrHidden(layerType: String, checked?: boolean) { + this.service.mdLayerTreeShowOrHidden(layerType, checked); + } + // 初始化加载描点数据 + addInitDataLayer = (pointData: any[], layerType: any, mdoptions?: any) => { + return this.service.addInitDataLayer(pointData, layerType, mdoptions); + }; + //切换底图 + baseLayerSwitcher(key: string): void { + this.service.baseLayerSwitcher(key); + } - // 添加梯级流域图 - addTertiarybasinLayer(layer: layer, fillcolor: any, outlineColor: any, datas: any): void { - this.service.addTertiarybasinLayer(layer, fillcolor, outlineColor, datas) - } - // 移除梯级流域图 - removeTertiarybasinLayer(layer: layer): void { - this.service.removeTertiarybasinLayer?.(layer) - } - // 缩放 - zoomToggle(type: 'out' | 'in') { - this.service.zoomToggle(type) - } - /** - * 长度量算 - */ - lengthCalculate(): void { - this.service.lengthCalculate() - } - /** - * 面积量算 - */ - areCalculate(): void { - this.service.areCalculate() - } - /** - * 移除量算结果 - */ - removeQueryLayer(): void { - this.service.removeQueryLayer() - } - // 地图打印 - mapOutPut() { - this.service.mapOutPut() - } - // 销毁地图 - destroy(): void { - this.service.destroy() - } + // 添加梯级流域图 + addTertiarybasinLayer( + layer: layer, + fillcolor: any, + outlineColor: any, + datas: any + ): void { + this.service.addTertiarybasinLayer(layer, fillcolor, outlineColor, datas); + } + // 移除梯级流域图 + removeTertiarybasinLayer(layer: layer): void { + this.service.removeTertiarybasinLayer?.(layer); + } + // 缩放 + zoomToggle(type: 'out' | 'in') { + this.service.zoomToggle(type); + } + /** + * 长度量算 + */ + lengthCalculate(): void { + this.service.lengthCalculate(); + } + /** + * 面积量算 + */ + areCalculate(): void { + this.service.areCalculate(); + } + /** + * 移除量算结果 + */ + removeQueryLayer(): void { + this.service.removeQueryLayer(); + } + // 地图打印 + mapOutPut() { + this.service.mapOutPut(); + } + // 销毁地图 + destroy(): void { + this.service.destroy(); + } - // 切换地图视图 - switchView(): void { - // this.service.switchView() - } + // 切换地图视图 + switchView(): void { + // this.service.switchView() + } - // 飞行到指定的点 - fitBounds(): void { - // this.service.fitBounds(bounds) - } - - // 飞行到指定的点 - flyTopanto(): void { - // this.service.flyTopanto(point) - } + // 飞行到指定的点 + fitBounds(): void { + // this.service.fitBounds(bounds) + } + // 飞行到指定的点 + flyTopanto(position: number[], zoom: number): void { + this.service.flyTopanto(position, zoom); + } } diff --git a/frontend/src/components/gis/map.d.ts b/frontend/src/components/gis/map.d.ts index a1c5de7..0c33b4a 100644 --- a/frontend/src/components/gis/map.d.ts +++ b/frontend/src/components/gis/map.d.ts @@ -1,15 +1,14 @@ - -import { MDOptions } from './map.class' +import { MDOptions } from './map.class'; export type layerType = - | "markers" - | "tiledMap" - | "tiledMapQuery" - | "geoJson" - | "arcgisFeature" - | "dynamicMapLayer" - | "arcgisMap" - | "label" - | "vector"; + | 'markers' + | 'tiledMap' + | 'tiledMapQuery' + | 'geoJson' + | 'arcgisFeature' + | 'dynamicMapLayer' + | 'arcgisMap' + | 'label' + | 'vector'; export type layerOption = { opacity?: number; data?: Array; @@ -18,170 +17,190 @@ export type layerOption = { hoverEvent?: Function; legendImages?: Array | null | undefined; geoJsonLegend?: Array | null | undefined; - tiledMapType?: undefined | "superMap"; + tiledMapType?: undefined | 'superMap'; }; export interface layer { - id: string - key: string - _layer?: any - type?: layerType - url?: string - url_3d?: string - geojson_url: string - label?: string - thumbnail?: string - visible?: boolean - option?: layerOption - tempobj?: any - layers?: any - rasteropacity?: any - imgUrl?: any - minZoom?: any - maxZoom?: any - minHeight?: number - maxHeight?: number - /** layer 类型 */ - layerType?: string - matrixIds_index?: string[] - tileMatrixSetID?: string - urlType: string + id: string; + key: string; + _layer?: any; + type?: layerType; + url?: string; + url_3d?: string; + geojson_url: string; + label?: string; + thumbnail?: string; + visible?: boolean; + option?: layerOption; + tempobj?: any; + layers?: any; + rasteropacity?: any; + imgUrl?: any; + minZoom?: any; + maxZoom?: any; + minHeight?: number; + maxHeight?: number; + /** layer 类型 */ + layerType?: string; + matrixIds_index?: string[]; + tileMatrixSetID?: string; + urlType: string; } export interface MapInterface { - /** - * 地图初始化 - * @param container DOM容器 - * @return any 地图视图 - */ - init(container: HTMLElement, rectangle?: any, center?: any, altitude?: number, bearing?: number): Promise + /** + * 地图初始化 + * @param container DOM容器 + * @return any 地图视图 + */ + init( + container: HTMLElement, + rectangle?: any, + center?: any, + altitude?: number, + bearing?: number + ): Promise; - /** - * 初始化加载描点数据 - * @param pointData - * @param layerType - */ - addInitDataLayer(pointData: any[], layerType: any, mdoptions: MDOptions): void + /** + * 初始化加载描点数据 + * @param pointData + * @param layerType + */ + addInitDataLayer( + pointData: any[], + layerType: any, + mdoptions: MDOptions + ): void; - /** - * 初始化加载基础图层 - * @param layer - */ - addBaseDataLayer(layer: any): void + /** + * 初始化加载基础图层 + * @param layer + */ + addBaseDataLayer(layer: any): void; - /** - * 切换底图 - * @param layer - */ - baseLayerSwitcher(key: string): void + /** + * 切换底图 + * @param layer + */ + baseLayerSwitcher(key: string): void; - /** - * 切换2D或者3D视图 - * @param type '2D' | '3D' - */ - switchView(type: '2D' | '3D'): any | null + /** + * 切换2D或者3D视图 + * @param type '2D' | '3D' + */ + switchView(type: '2D' | '3D'): any | null; - /** - * 缩放 - * out 缩小 - * in 放大 - */ - zoomToggle(type: 'out' | 'in'): void + /** + * 缩放 + * out 缩小 + * in 放大 + */ + zoomToggle(type: 'out' | 'in'): void; - /** - * 地图输出打印 - */ - mapOutPut(): void + /** + * 地图输出打印 + */ + mapOutPut(): void; - //飞行到指定的点 - fitBounds(bbox,bearing):void - flyTopanto(positon,zoom,stcd):void - // /** - // * 加载倾斜摄影数据 - // * @param HH3DUrlArray - // */ - // addQxsyLayer(HH3DUrlArray: siteItem): void + //飞行到指定的点 + fitBounds(bbox, bearing): void; - // /** - // * 移除倾斜摄影数据 - // * @param HH3DUrlArray - // */ - // removeQxsyLayer(HH3DUrlArray: siteItem): void + flyTopanto(positon, zoom): void; + // /** + // * 加载倾斜摄影数据 + // * @param HH3DUrlArray + // */ + // addQxsyLayer(HH3DUrlArray: siteItem): void - // /** - // * 倾斜摄影定位 - // * @param HH3DUrlArray - // */ - // qxsyToPosition(HH3DUrlArray: siteItem): void + // /** + // * 移除倾斜摄影数据 + // * @param HH3DUrlArray + // */ + // removeQxsyLayer(HH3DUrlArray: siteItem): void - // /** - // * 倾斜摄影裁剪 - // * @param HH3DUrlArray - // */ - // qxsyClipBoundary(HH3DUrlArray: siteItem): void + // /** + // * 倾斜摄影定位 + // * @param HH3DUrlArray + // */ + // qxsyToPosition(HH3DUrlArray: siteItem): void - // /** - // * 移除倾斜摄影裁剪 - // * @param HH3DUrlArray - // */ - // removeQxsyClipBoundary(HH3DUrlArray: siteItem): void + // /** + // * 倾斜摄影裁剪 + // * @param HH3DUrlArray + // */ + // qxsyClipBoundary(HH3DUrlArray: siteItem): void - /** - * 基地面板控制 - * @param baseid - * @param isAll - */ - jdPanelControlShowAndHidden(baseid: String, isAll: boolean): void - /** - * 图例和基地面板控制描点数据显示隐藏方法 - * @param layerType - * @param key - * @param checked - * @param isAll - */ - mdLayerShowOrHidden(): void - // layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean - /** - * 基础图层显示影隐藏方法 - * @param layerType - * @param checked - */ - controlBaseLayerTreeShowAndHidden(layerType: String, key: String, checked: boolean): void + // /** + // * 移除倾斜摄影裁剪 + // * @param HH3DUrlArray + // */ + // removeQxsyClipBoundary(HH3DUrlArray: siteItem): void - /** - * 图层树控制描点数据显示隐藏方法 - * @param layerType - * @param checked - */ - mdLayerTreeShowOrHidden(layerType: String, checked?: boolean): void + /** + * 基地面板控制 + * @param baseid + * @param isAll + */ + jdPanelControlShowAndHidden(baseid: String, isAll: boolean): void; + /** + * 图例和基地面板控制描点数据显示隐藏方法 + * @param layerType + * @param key + * @param checked + * @param isAll + */ + mdLayerShowOrHidden(): void; + // layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean + /** + * 基础图层显示影隐藏方法 + * @param layerType + * @param checked + */ + controlBaseLayerTreeShowAndHidden( + layerType: String, + key: String, + checked: boolean + ): void; - /** - * 长度量算 - */ - lengthCalculate(): void - /** - * 面积量算 - */ - areCalculate(): void + /** + * 图层树控制描点数据显示隐藏方法 + * @param layerType + * @param checked + */ + mdLayerTreeShowOrHidden(layerType: String, checked?: boolean): void; - /** - * 移除量算结果 - */ - removeQueryLayer(): void - /** - * 添加梯级流域图 - * @param layer - * @param fillcolor - */ - addTertiarybasinLayer(layer: layer, fillcolor: any, outlineColor: any, datas: any): void + /** + * 长度量算 + */ + lengthCalculate(): void; + /** + * 面积量算 + */ + areCalculate(): void; - /** - * 移除梯级流域图 - * @param layer - */ - removeTertiarybasinLayer(layer: layer): void - /** - * 移除地图对象 - */ - destroy(): void + /** + * 移除量算结果 + */ + removeQueryLayer(): void; + /** + * 添加梯级流域图 + * @param layer + * @param fillcolor + */ + addTertiarybasinLayer( + layer: layer, + fillcolor: any, + outlineColor: any, + datas: any + ): void; + + /** + * 移除梯级流域图 + * @param layer + */ + removeTertiarybasinLayer(layer: layer): void; + /** + * 移除地图对象 + */ + destroy(): void; } diff --git a/frontend/src/components/gis/map.ol.ts b/frontend/src/components/gis/map.ol.ts index 4a0b46d..cea4e04 100644 --- a/frontend/src/components/gis/map.ol.ts +++ b/frontend/src/components/gis/map.ol.ts @@ -55,8 +55,10 @@ export class MapOl implements MapInterface { // ✅ 新增:存储 key -> layer 实例,用于控制显示隐藏和切换 private layerRegistry: Map = new Map(); // ✅ 新增:记录当前正在显示的“切换 Key”,用于判断是否需要重新加载或只是切换显隐 + // 基础图层配置 private baseLayerConfig: TileLayer | null = null; - // private hydropBaseConfig: TileLayer | null = null; + // 基地配置 + private hydropBaseConfig: any | null = null; private REGISTRY_KEY = 'customBaseLayer'; // ✅ 新增:量算相关属性 @@ -70,9 +72,9 @@ export class MapOl implements MapInterface { // ✅ 新增:声明用于存储 mask 事件监听器的引用,以便后续移除 private _maskPrerender: any = null; private _maskPostrender: any = null; - // private BASEID: string = '01'; + private BASEID: string = ''; constructor() { - this.loadGeoJsonData(); + // this.loadGeoJsonData(); this.loadGeoJsonData1(); } private async loadGeoJsonData(): Promise { @@ -83,6 +85,15 @@ export class MapOl implements MapInterface { console.error('配置加载失败:', error); } } + private async loadGeoJsonData2(url): Promise { + try { + const response = await fetch(url); + const data = await response.json(); + return data; + } catch (error) { + console.error('配置加载失败:', error); + } + } private async loadGeoJsonData1(): Promise { try { const response = await fetch('/data/geoJson1.json'); @@ -402,8 +413,6 @@ export class MapOl implements MapInterface { matrixIds[z] = `${matrixSetName}:${z}`; } - console.log('Using Matrix IDs sample:', matrixIds[0], matrixIds[1]); - // 4. 创建 WMTS 图层 const wmtsLayer = new TileLayer({ source: new WMTS({ @@ -428,6 +437,7 @@ export class MapOl implements MapInterface { } else { wmtsLayer.setZIndex(-99); } + console.log('基础图层', wmtsLayer); // 5. 注册与添加 this.layerRegistry.set(layer.key, wmtsLayer); this.map.addLayer(wmtsLayer); @@ -445,141 +455,11 @@ export class MapOl implements MapInterface { this.map.addLayer(tileLayer); } else if (layer.type === 'vector') { if (layer.key === 'hydropBase') { - // this.hydropBaseConfig = layer; + this.hydropBaseConfig = layer; } - // ✅ 1. 创建矢量源,关键是要配置投影转换 - const vectorSource = new VectorSource({ - features: new GeoJSON().readFeatures(this.geoJsonData1, { - dataProjection: 'EPSG:4326', - featureProjection: 'EPSG:3857' // 确保转换到地图使用的投影 - }) - // url: layer.geojson_url +`&BASEID=BASEID'${this.BASEID}'` , // 远程 GeoJSON 地址 - // format: new GeoJSON({ - // featureProjection: 'EPSG:3857' - // }) - }); - - // ✅ 2. 创建矢量图层,添加默认样式防止透明 - const vectorLayer = new VectorLayer({ - source: vectorSource, - style: new Style({ - stroke: new Stroke({ - color: '#3399CC', // 默认蓝色边框 - width: 2 - }), - fill: new Fill({ - color: 'rgba(51, 153, 204, 0.4)' // 默认半透明填充 - }) - }), - // ✅ 3. 调整 zIndex,确保在底图之上可见 - zIndex: 101, - visible: true - }); - // 监听数据加载完成事件 - vectorSource.on('featuresloadend', () => { - // 获取所有加载的要素 - const features = vectorSource.getFeatures(); - const feature = features[0]; - const geometry = feature.getGeometry(); - console.log(geometry); - // this.addClipToRasterLayer(this.layerRegistry.get(this.REGISTRY_KEY), geometry); - // this.applyMapMask(geometry); - }); - // 4. 注册与添加 - this.layerRegistry.set(layer.key, vectorLayer); - // this.map.addLayer(vectorLayer); - layer._layer = vectorLayer; - const targetBaseLayer = this.layerRegistry.get(this.REGISTRY_KEY); - this.applyMapMask(targetBaseLayer as TileLayer, this.geoJsonData1); - console.log(`矢量图层 [${layer.key}] 已加载: ${layer.url}`); } } - /** - * 为栅格图层添加裁切效果 - * @param layer 栅格图层(TileLayer) - * @param clipGeometry 裁切几何(Polygon 或 MultiPolygon,EPSG:3857 投影) - */ - // private addClipToRasterLayer(layer: TileLayer, clipGeometry: Geometry): void { - // if (!layer || !clipGeometry) return; - - // // 处理几何类型,统一为多边形数组 - // let polygons: Polygon[] = []; - // const type = clipGeometry.getType(); - - // if (type === 'Polygon') { - // polygons = [clipGeometry as Polygon]; - // } else if (type === 'MultiPolygon') { - // const multiPolygon = clipGeometry as MultiPolygon; - // const coords = multiPolygon.getCoordinates(); - // polygons = coords.map(coord => new Polygon(coord)); - // } else { - // console.error('不支持的几何类型:', type); - // return; - // } - - // // 预存储所有外环坐标 - // const allRings: number[][][] = []; - // for (const polygon of polygons) { - // const coords = polygon.getCoordinates(); - // if (coords && coords[0] && coords[0].length > 0) { - // allRings.push(coords[0]); - // } - // } - - // // 移除旧事件 - // layer.removeEventListener('prerender'); - // layer.removeEventListener('postrender'); - - // // 渲染前:设置裁切区域 - // layer.on('prerender', (event) => { - // const context = event.context; - // const frameState = event.frameState; - - // if (!context || !frameState) return; - - // // 获取坐标转像素函数 - // const toPixel = frameState.coordinateToPixel; - // if (!toPixel) return; - - // context.save(); - // context.beginPath(); - - // let hasPath = false; - - // for (const ring of allRings) { - // if (!ring || ring.length === 0) continue; - - // for (let i = 0; i < ring.length; i++) { - // const coord = ring[i]; - // const pixel = toPixel(coord); - - // if (!pixel || pixel.length < 2) continue; - - // if (i === 0) { - // context.moveTo(pixel[0], pixel[1]); - // } else { - // context.lineTo(pixel[0], pixel[1]); - // } - // hasPath = true; - // } - // context.closePath(); - // } - - // if (hasPath) { - // context.clip(); - // } else { - // context.restore(); - // } - // }); - - // // 渲染后:恢复状态 - // layer.on('postrender', (event) => { - // if (event.context) { - // event.context.restore(); - // } - // }); - // } // 在 addBaseDataLayer 方法的 vector 分支末尾,或者专门提供一个方法来激活遮罩 enableNortheastMask(): void { // 1. 获取全量底图图层 @@ -603,12 +483,52 @@ export class MapOl implements MapInterface { * @param regionId 区域ID (例如 "hebei", "13", "01" 等,需与图层 Key 或属性对应) * @param isAll 是否显示所有 (true: 显示所有图层; false: 仅显示匹配 regionId 的图层,隐藏其他) */ - jdPanelControlShowAndHidden(_regionId: string): void { - // this.BASEID = regionId; - // console.log(this.layerRegistry); - // console.log(this.layerRegistry.keys()); - // this.addBaseDataLayer(this.hydropBaseConfig); - // this.controlBaseLayerTreeShowAndHidden(this.REGISTRY_KEY,this.REGISTRY_KEY,false) + async jdPanelControlShowAndHidden( + _regionId: string, + isAll: boolean + ): Promise { + if (!this.map || !this.hydropBaseConfig) { + console.warn('地图未初始化或 hydropBaseConfig 未配置'); + return; + } + + // 1. 更新当前选中的区域 ID + this.BASEID = _regionId; + console.log(`切换区域至: ${this.BASEID}`); + + if (!isAll) { + // ✅ 情况 A: isAll == false -> 清空裁切 + this.clearMapMask(); + return; + } + + // 2. 获取底图图层 (用于应用遮罩的目标) + const baseLayer = this.layerRegistry.get(this.REGISTRY_KEY) as TileLayer; + if (!baseLayer) { + console.warn('未找到底图图层,无法应用遮罩'); + return; + } + try { + const url = + this.hydropBaseConfig.geojson_url + + `&cql_filter=BASEID='${this.BASEID}'`; + console.log('正在请求裁切数据:', url); + + // 等待数据加载 + const geoJsonData = await this.loadGeoJsonData2(url); + console.log('获取到的原始 GeoJSON:', geoJsonData); + + // ✅ 新增:在应用遮罩前,先让地图视角聚焦到该区域 + this.fitViewToGeoJson(geoJsonData); + + console.log('基础图层', baseLayer); + // 应用遮罩 + this.applyMapMask(baseLayer, geoJsonData); + } catch (error) { + console.error('加载裁切数据失败:', error); + // 出错时也清空遮罩,保证用户体验 + this.clearMapMask(); + } } /** * 基础图层显示影隐藏方法 @@ -1224,38 +1144,18 @@ export class MapOl implements MapInterface { private applyMapMask(rasterLayer: TileLayer, clipGeoJson: any): void { if (!rasterLayer || !clipGeoJson) return; - // 1. 解析 GeoJSON + // 1. 解析 GeoJSON 为 OL Features (EPSG:3857) const features = new GeoJSON().readFeatures(clipGeoJson, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }); - // if (!features || features.length === 0) { - // console.warn('裁切数据为空'); - // return; - // } + if (!features || features.length === 0) { + console.warn('裁切数据为空,无法应用遮罩'); + return; + } - // // ✅ 调试:添加绿线图层 - // const debugVectorSource = new VectorSource({ features: features }); - // let debugLayer = this.map!.getLayers() - // .getArray() - // .find((l: any) => l.get('isDebugMask')); - // if (debugLayer) { - // this.map!.removeLayer(debugLayer); - // } - // debugLayer = new VectorLayer({ - // source: debugVectorSource, - // style: new Style({ - // stroke: new Stroke({ color: '#00FF00', width: 2 }), - // fill: new Fill({ color: 'rgba(0, 255, 0, 0.1)' }) - // }), - // zIndex: 9999, - // visible: true - // }); - // debugLayer.set('isDebugMask', true); - // this.map!.addLayer(debugLayer); - - // 移除旧监听器 + // 2. 移除旧的监听器,防止重复绑定 if (this._maskPrerender) { rasterLayer.un('prerender', this._maskPrerender); } @@ -1263,108 +1163,209 @@ export class MapOl implements MapInterface { rasterLayer.un('postrender', this._maskPostrender); } + // 3. 定义 prerender 事件处理函数 + // OpenLayers 在 prerender 时,context 已经包含了当前帧的状态 const maskPrerender = (event: any) => { const context = event.context; - if (!context || !this.map) return; - const frameState = event.frameState; - if (!frameState) return; + if (!context || !frameState || !this.map) return; + + // ✅ 关键步骤 1: 保存当前 Canvas 状态 context.save(); + + // ✅ 关键步骤 2: 重置变换矩阵,确保我们操作的是屏幕像素坐标 + // OpenLayers 默认可能应用了平移/缩放,我们需要手动控制 context.setTransform(1, 0, 0, 1, 0, 0); + context.beginPath(); - let hasPath = false; + let hasValidPath = false; + // 遍历所有特征,绘制路径 for (const feature of features) { - const geom: any = feature.getGeometry(); + const geom = feature.getGeometry(); if (!geom) continue; const type = geom.getType(); + + // 处理 Polygon if (type === 'Polygon') { - const rings = geom.getCoordinates() as number[][][]; + const rings = (geom as any).getCoordinates() as number[][][]; for (const ring of rings) { - // ✅ 传递 frameState 给 drawRing - this.drawRing(context, ring, frameState); - hasPath = true; + if (this.drawRingToContext(context, ring, frameState)) { + hasValidPath = true; + } } - } else if (type === 'MultiPolygon') { - const polygons = geom.getCoordinates() as number[][][][]; + } + // 处理 MultiPolygon + else if (type === 'MultiPolygon') { + const polygons = (geom as any).getCoordinates() as number[][][][]; for (const polygonRings of polygons) { for (const ring of polygonRings) { - // ✅ 传递 frameState 给 drawRing - this.drawRing(context, ring, frameState); - hasPath = true; + if (this.drawRingToContext(context, ring, frameState)) { + hasValidPath = true; + } } } } } - if (hasPath) { - context.strokeStyle = 'red'; - context.lineWidth = 2; + + // ✅ 关键步骤 3: 如果绘制了有效路径,则执行裁剪 + if (hasValidPath) { + // 闭合路径(虽然 drawRing 里做了 closePath,但为了保险) + + context.save(); + // 先用外描边创建裁剪区域 + context.lineWidth = 6; + context.strokeStyle = '#6D64DF'; context.stroke(); - context.fillStyle = 'rgba(255, 255, 0, 0.2)'; - context.fill(); - // context.clip(); + context.clip(); // 裁剪到外描边的内部区域 + context.strokeStyle = '#CCC9F4'; + context.lineWidth = 5; + context.stroke(); + // 执行裁剪!此后所有绘制操作仅在此路径内可见 + context.clip(); + + // 可选:如果你希望裁剪区域外有半透明黑色遮罩,可以在这里绘制一个全屏矩形 + // 但通常我们只希望“隐藏”外部,所以只需 clip 即可,OL 会自动绘制瓦片 } else { + console.warn('未能生成有效的裁切路径'); } - context.restore(); }; + // 4. 定义 postrender 事件处理函数 + // 用于恢复 Canvas 状态,避免影响其他图层或后续帧 const maskPostrender = (event: any) => { if (event.context) { event.context.restore(); } }; + // 5. 绑定新的事件监听器 this._maskPrerender = maskPrerender; this._maskPostrender = maskPostrender; rasterLayer.on('prerender', this._maskPrerender); rasterLayer.on('postrender', this._maskPostrender); + // 6. 强制触发重绘 rasterLayer.changed(); + + console.log('地图遮罩已应用'); } - private drawRing( + /** + * 将单个环(Ring)绘制到 Canvas 上下文 + * @returns boolean 是否成功绘制了有效路径 + */ + private drawRingToContext( context: CanvasRenderingContext2D, ring: number[][], frameState: any - ) { - if (!ring || ring.length === 0) return; + ): boolean { + if (!ring || ring.length < 3) return false; // 至少需要3个点构成面 - // ✅ 关键:获取当前 Canvas 的像素比(高清屏通常为 2) - // frameState.pixelRatio 是 OL 内部使用的像素比 const pixelRatio = frameState.pixelRatio || window.devicePixelRatio || 1; + let moved = false; for (let i = 0; i < ring.length; i++) { const coord = ring[i]; - if (!this.map || !Array.isArray(coord) || typeof coord[0] !== 'number') - continue; + // 坐标有效性检查 + if (!coord || coord.length < 2 || typeof coord[0] !== 'number') continue; - // ✅ 1. 获取相对于地图容器的 CSS 像素坐标 - const cssPixel = this.map.getPixelFromCoordinate( + // 获取 CSS 像素坐标 + const cssPixel = this.map?.getPixelFromCoordinate( coord as [number, number] ); if (!cssPixel) continue; - // ✅ 2. 转换为 Canvas 内部物理像素坐标 - // 因为我们在 maskPrerender 中执行了 context.setTransform(1, 0, 0, 1, 0, 0); - // 此时 Canvas 的原点是左上角,单位是物理像素。 - // 而 getPixelFromCoordinate 返回的是 CSS 像素。 - // 所以必须乘以 pixelRatio。 + // 转换为物理像素坐标 const canvasX = cssPixel[0] * pixelRatio; const canvasY = cssPixel[1] * pixelRatio; - if (i === 0) { + if (!moved) { context.moveTo(canvasX, canvasY); + moved = true; } else { context.lineTo(canvasX, canvasY); } } - context.closePath(); + + if (moved) { + context.closePath(); + return true; + } + return false; + } + /** + * 根据 GeoJSON 数据调整地图视野,使其居中并适配大小 + * @param geoJson GeoJSON 对象 + */ + private fitViewToGeoJson(geoJson: any): void { + if (!this.map || !this.view) return; + + try { + // 1. 将 GeoJSON 转换为 OpenLayers 的 Feature 数组 + // 注意:这里假设输入的是 EPSG:4326,需要转换到地图使用的 EPSG:3857 + const features = new GeoJSON().readFeatures(geoJson, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857' + }); + + if (!features || features.length === 0) return; + + // 2. 创建一个临时的矢量源来计算所有要素的包围盒 + const source = new VectorSource({ + features: features + }); + + // 3. 获取包围盒 [minX, minY, maxX, maxY] + const extent = source.getExtent(); + + // 检查包围盒是否有效 + if (extent[0] === Infinity || extent[0] === -Infinity) { + console.warn('无法计算有效的地图包围盒'); + return; + } + + // 4. 调整视图 + // padding: 设置四周留白,防止图形贴边 + // duration: 动画持续时间 + // maxZoom: 限制最大缩放级别,防止放大过度导致像素化 + this.view.fit(extent, { + padding: [50, 50, 50, 50], // 上右下左留白 + duration: 1000, // 1秒动画 + maxZoom: 15 // 根据底图精度调整,避免过度放大 + }); + + console.log('地图视野已调整至裁切区域'); + } catch (e) { + console.error('调整地图视野失败:', e); + } + } + /** + * 清除地图遮罩(恢复底图正常显示) + */ + private clearMapMask(): void { + const baseLayer = this.layerRegistry.get(this.REGISTRY_KEY) as TileLayer; + if (!baseLayer) return; + + console.log('正在清除地图遮罩...'); + + if (this._maskPrerender) { + baseLayer.un('prerender', this._maskPrerender); + this._maskPrerender = null; + } + if (this._maskPostrender) { + baseLayer.un('postrender', this._maskPostrender); + this._maskPostrender = null; + } + + // 强制重绘以移除遮罩效果 + baseLayer.changed(); } /** * 格式化长度输出 (米) @@ -1386,7 +1387,33 @@ export class MapOl implements MapInterface { mdLayerShowOrHidden(): void {} switchView(): void {} fitBounds(): void {} - flyTopanto(): void {} + /** + * 飞行定位到指定经纬度和缩放级别 + * @param position [经度, 纬度] (EPSG:4326) + * @param zoom 缩放级别 + */ + flyTopanto(position: number[], zoom: number): void { + if (!this.map || !this.view) { + console.warn('地图未初始化,无法执行飞行定位'); + return; + } + + // 1. 将经纬度 [lon, lat] 转换为地图投影坐标 (EPSG:3857) + const targetCenter = fromLonLat(position); + + // 2. 执行动画 + // duration: 动画持续时间,单位毫秒,例如 1000ms (1秒) + this.view.animate( + { + center: targetCenter, + duration: 1000 + }, + { + zoom: zoom, + duration: 1000 + } + ); + } /** * 移除地图对象,释放资源 */ diff --git a/frontend/src/layout/components/AppMain.vue b/frontend/src/layout/components/AppMain.vue index 98e64ab..c587ac0 100644 --- a/frontend/src/layout/components/AppMain.vue +++ b/frontend/src/layout/components/AppMain.vue @@ -16,7 +16,7 @@ const routeKey = computed(() => router.path + Math.random());