diff --git a/frontend/docs/地图模块-OpenLayers抽吸与碰撞分析.md b/frontend/docs/地图模块-OpenLayers抽吸与碰撞分析.md new file mode 100644 index 00000000..e7bf6d02 --- /dev/null +++ b/frontend/docs/地图模块-OpenLayers抽吸与碰撞分析.md @@ -0,0 +1,340 @@ +# 地图模块-OpenLayers抽吸与碰撞分析 + +## 1. 背景 + +当前地图 2D 主引擎已经切换为 OpenLayers,项目依赖版本为 `ol@^10.8.0`。 +本次关注的问题有两个: + +- 点位“抽吸”逻辑不符合业务预期; +- 文字碰撞时把整个点位一起隐藏,而不是仅隐藏文字。 + +典型场景是两个相邻电站,例如“积石峡”和“公伯峡”: + +- 可用代号 `1`、`2` 表示两个相邻点; +- 默认缩放下只展示 `1`; +- 放大到某个层级后,`1` 隐藏,`2` 展示; +- 再继续放大后,`1` 和 `2` 都展示,但 `2` 需要“抽吸/拉开”显示,避免与 `1` 重叠; +- 如果只是文字发生碰撞,希望点图标保留,仅文字隐藏或延后显示。 + +本文件先整理当前代码现状和问题成因,不直接修改实现。 + +## 2. 当前代码结构 + +当前前端 GIS 运行链路如下: + +1. `src/components/gis/GisView.vue` +2. `src/modules/map/application/map-orchestrator.ts` +3. `src/components/gis/map.class.ts` +4. `src/components/gis/map.ol.ts` +5. `src/components/gis/ol/point-layer-manager.ts` +6. `src/components/gis/ol/popup-manager.ts` + +各层职责简述: + +- `GisView.vue` + - 地图入口组件; + - 挂载地图容器、图例、筛选器、控制器; + - 通过 `mapOrchestrator.mountView()` 初始化地图; + - 菜单切换时通过 `handlePageChange()` 触发页面地图重载。 + +- `map.class.ts` + - 地图门面类; + - 当前 2D 实现实际绑定的是 `MapOl`; + - 3D 实现才会切到 `MapCesium`。 + +- `map.ol.ts` + - OpenLayers 主实现; + - 负责地图初始化、底图加载、点位样式、Popup、区域裁切、量算等; + - 当前“点位渲染规则”和“碰撞相关逻辑”主要集中在这个文件。 + +- `point-layer-manager.ts` + - 负责点图层创建、Feature 灌入、按图层控制显隐; + - 点位图层统一使用 `VectorLayer + VectorSource`。 + +- `popup-manager.ts` + - 管理 hover popup 和高缩放下的批量 popup; + - 内部额外做了一套基于边界框的碰撞判断。 + +## 3. 当前点位渲染链路 + +### 3.1 点图层创建 + +`point-layer-manager.ts` 中的 `ensureLayer()` 会统一创建点图层: + +- 图层类型为 `VectorLayer`; +- 图层上开启了 `declutter: true`; +- 样式函数统一走 `map.ol.ts` 中的 `createPointStyle()`。 + +也就是说,当前所有点位的“是否显示”和“图标/文字如何避让”,都收敛到 OpenLayers 的 declutter 机制和样式函数里。 + +### 3.2 点样式生成 + +`map.ol.ts` 的 `createPointStyle()` 当前做了这些事: + +- 读取 `_iconUrl` 和 `_labelText`; +- 如果图例不可见、区域不可见,则直接返回 `null`; +- 如果 `distance` 不满足当前缩放级别要求,也直接返回 `null`; +- 图标使用 `Icon`; +- 文字使用 `Text`; +- 图标和文字都设置了 `declutterMode: 'declutter'`; +- 最终把图标和文字一起放进同一个 `Style` 返回。 + +这意味着: + +- 点图标和文字不是两个独立的渲染对象; +- 它们在当前实现中属于同一个样式结果; +- 只要这组样式参与碰撞判定,图标和文字的命运就是绑定的。 + +### 3.3 当前已有的“抽稀”逻辑 + +当前项目中确实有一层“缩放越小,显示越少”的处理,但它不是精细的“抽吸”。 + +`map.ol.ts` 的 `shouldRenderFeatureByDistance()` 逻辑是: + +- 小缩放下要求 `distance` 更大才允许显示; +- 缩放越大,允许显示的点逐渐增多; +- 本质上是基于后端数据字段 `distance` 做全局抽稀。 + +这个逻辑的特点是: + +- 是全局阈值,不是成对点位或同组点位的专属规则; +- 只能决定“这个点显示还是不显示”; +- 不能表达“1 和 2 是近邻关系,并且在不同缩放阶段按业务优先级切换显示”。 + +### 3.4 当前批量 Popup 的碰撞检测 + +`popup-manager.ts` 在高缩放批量 popup 模式下,又额外做了一层碰撞判断: + +- 先估算图标和文字合并后的锚点边界框; +- 如果边界框碰撞,则当前要素直接不参与 popup 渲染; +- 随后再判断 popup 矩形之间是否碰撞。 + +这说明当前“图标 + 文字”被视为一个整体,不仅体现在 OpenLayers 的 declutter 里,也体现在 popup 的辅助判断里。 + +## 4. 当前仓库里“抽吸”相关代码现状 + +仓库中确实存在历史上的“抽吸”实现,但不在当前 OpenLayers 主链路里。 + +相关文件: + +- `src/utils/leaflet/leaflet.inflatable-markers-group.js` +- `src/components/gis/map.leaflet.ts` + +现状判断: + +- `leaflet.inflatable-markers-group.js` 是旧 Leaflet 时代的抽吸插件; +- 它内部有 `inflateAsManyAsPossible()`、`_zoomend()` 等典型抽吸逻辑; +- 但当前 `map.class.ts` 实际实例化的是 `new MapOl()`; +- `map.leaflet.ts` 已经不在主运行路径上。 + +结论是: + +- 当前页面上看到的点位行为,不是 Leaflet 抽吸插件在工作; +- 现在的显示结果主要来自 OpenLayers 的 `declutter`、当前样式函数和 `distance` 抽稀逻辑; +- 因此“已经有抽吸但是不太对”,更准确地说是“历史上有抽吸代码,但现在主链路没有真正使用那套机制”。 + +## 5. 问题分析 + +### 5.1 为什么当前实现做不到你要的分阶段抽吸 + +你描述的目标并不是简单“避让”,而是一个明确的分阶段显示策略: + +1. 默认只显示 `1`; +2. 缩放到阶段 A 时,隐藏 `1`,改为显示 `2`; +3. 缩放到阶段 B 时,`1` 和 `2` 都显示; +4. 阶段 B 下,`2` 还要相对 `1` 做抽吸/偏移,避免完全重叠。 + +而当前代码缺少以下能力: + +- 没有“近邻点关系”配置 + - 代码里没有看到类似 `groupId`、`neighborIds`、`priority`、`displayLevel` 这样的结构; + - 系统不知道“积石峡”和“公伯峡”是一组特殊近邻点。 + +- 没有“分阶段显示规则”配置 + - 当前只有全局的 `distance` 阈值判断; + - 没有按缩放阶段表达“谁替代谁显示、谁先隐藏、谁后展示”。 + +- 没有“抽吸偏移”计算 + - 当前点要素坐标就是原始经纬度转换结果; + - 没有根据缩放级别和邻近关系生成偏移坐标; + - 也没有“展开后沿圆周/沿固定方向偏移”的逻辑。 + +- 当前 declutter 是被动避让,不是主动编排 + - declutter 只会尽量避免重叠; + - 它不会理解业务上的“1 是主点、2 是附点”; + - 也不会自动实现“先 1,后 2,再两个都展示且 2 偏移”的规则。 + +所以当前看到的结果不稳定,本质原因不是某一个参数不对,而是“现有机制和目标能力不是一类问题”。 + +### 5.2 为什么文字碰撞会把整个点隐藏 + +这是当前实现里最关键的原因: + +- 点图标和文字是在同一个 `Style` 里返回的; +- 图层开启了 `declutter: true`; +- 图标和文字又都设置了 `declutterMode: 'declutter'`。 + +在这种组织方式下,当前要素的碰撞判定是按“一个整体渲染单元”处理的,而不是“图标一套规则,文字一套规则”。 + +因此会出现下面的现象: + +- 图标本身其实不冲突; +- 但文字边界框发生碰撞; +- 最终被隐藏的是整条样式结果; +- 用户视觉上就会觉得“明明只是字挤了,为什么点也没了”。 + +这和你现在观察到的问题是一致的。 + +### 5.3 为什么当前逻辑还会进一步放大这个问题 + +除了 OpenLayers 自身的 declutter 组织方式,代码里还有两层因素会继续放大问题: + +- `shouldRenderFeatureByDistance()` 会先根据 `distance` 直接过滤点位; +- `popup-manager.ts` 在批量 popup 模式下,也把“图标 + 文字”当作合并边界框做碰撞模拟。 + +也就是说,当前系统对近邻点位的处理是: + +- 先用全局 `distance` 规则筛掉一部分; +- 再用 OpenLayers declutter 继续过滤; +- 高缩放时 popup 层再做一轮碰撞跳过。 + +这三层叠加后,显示行为更偏向“谁先撞谁消失”,而不是“按业务规则分阶段展开”。 + +## 6. 结合你的案例做具体映射 + +如果以近邻点 `1`、`2` 为例,当前代码里实际上缺的是下面这些业务语义: + +- `1` 和 `2` 属于同一近邻组; +- 组内有主次优先级; +- 在不同 zoom 区间内有不同展示策略; +- 在最大放大阶段需要给 `2` 一个偏移位; +- 偏移后图标和文字还要分别控制碰撞策略。 + +当前实现只能表达: + +- 某个点在当前 zoom 下是否允许渲染; +- 某个点图例是否可见; +- 某个点是否被区域裁切隐藏; +- 某个点的图标和文字整体是否参与 declutter。 + +它并不能表达: + +- “1 替代 2 展示”; +- “2 替代 1 展示”; +- “1 和 2 一起展示,但 2 抽吸展开”; +- “只隐藏文字,不隐藏图标”。 + +所以你现在遇到的问题是结构性问题,不是简单调一个 `declutter` 参数就能彻底解决。 + +## 7. 后续修改时建议优先动的层 + +如果下一步要真正改这块,建议优先从下面几层入手: + +### 7.1 先补“近邻点显示规则” + +建议不要直接把业务规则写死在样式函数里,而是先增加一层近邻点编排规则,例如: + +- `groupId` +- `priority` +- `showZoomMin` +- `showZoomMax` +- `replaceAtZoom` +- `expandAtZoom` +- `expandOffset` + +这样才能表达: + +- 默认只显示主点; +- 中间层级切换成副点; +- 更大层级两个都显示; +- 副点在展示时做偏移。 + +### 7.2 抽吸不要依赖 declutter 自动完成 + +抽吸本质上是“主动布局”问题,不是“被动避让”问题。 + +建议后续把近邻点抽吸拆成单独逻辑: + +- 先根据原始点数据识别近邻组; +- 再根据当前 zoom 产出“本次真正参与渲染的点集合”; +- 对需要展开的点,生成偏移后的渲染坐标; +- 最后再交给图层渲染。 + +这样会比直接指望 OpenLayers 的 declutter 更可控。 + +### 7.3 图标层和文字层要分开控制 + +要实现“文字碰撞时只隐藏文字,不隐藏点图标”,后续最重要的一点是把图标和文字的渲染职责拆开。 + +至少要做到下面二选一: + +- 图标层和文字层拆成两个独立图层; +- 或者把图标与文字拆成可独立决策的渲染单元。 + +目标是: + +- 图标优先保留; +- 文字单独避让; +- 文本碰撞时只压文字,不压图标。 + +如果仍然维持“同一个样式对象里同时放 image 和 text”的组织方式,这个问题大概率还会持续存在。 + +### 7.4 Popup 碰撞策略也要和新规则同步 + +后续如果点位抽吸和文字单独避让改了,`popup-manager.ts` 里的边界框逻辑也要一起调整。 + +否则会出现: + +- 图标已经正常显示; +- 文字也按新规则显示了; +- 但 popup 层仍然沿用旧的合并边界框; +- 最终又在 popup 阶段把结果打回去。 + +## 8. 建议的改造顺序 + +为了降低风险,建议后续改造按以下顺序推进: + +1. 先梳理近邻点数据结构 + - 确定近邻点如何分组; + - 确定主次优先级和缩放阈值从哪里来。 + +2. 再实现“显示决策层” + - 在真正生成 Style 之前,先决定哪些点应该显示; + - 哪些点应该隐藏; + - 哪些点需要偏移展开。 + +3. 然后拆分图标和文字碰撞 + - 先保证点图标稳定显示; + - 再单独做文字避让。 + +4. 最后调整 popup 逻辑 + - 保证 popup 的碰撞判定和最终可见点保持一致。 + +## 9. 涉及文件清单 + +本次分析重点涉及以下文件: + +- `src/components/gis/GisView.vue` +- `src/components/gis/map.class.ts` +- `src/components/gis/map.ol.ts` +- `src/components/gis/ol/point-layer-manager.ts` +- `src/components/gis/ol/popup-manager.ts` +- `src/utils/leaflet/leaflet.inflatable-markers-group.js` +- `src/components/gis/map.leaflet.ts` +- `package.json` + +## 10. 当前结论 + +当前地图问题可以归纳为两点: + +1. 现在的 OpenLayers 主链路并没有真正使用旧 Leaflet 的抽吸机制; +2. 当前点图标和文字被作为一个整体参与 declutter,所以文字碰撞时会连点一起隐藏。 + +所以后续如果要改,方向不应该只是“调避让参数”,而应该是: + +- 增加近邻点分阶段显示规则; +- 增加主动抽吸/偏移布局; +- 把图标和文字拆成可独立控制的渲染单元; +- 同步修正 popup 的碰撞判断。 + +在这个基础上,再进入代码修改会更稳。 diff --git a/frontend/docs/地图模块-详细说明.md b/frontend/docs/地图模块-详细说明.md new file mode 100644 index 00000000..0b98ad2e --- /dev/null +++ b/frontend/docs/地图模块-详细说明.md @@ -0,0 +1,622 @@ +# 地图模块-详细说明 + +## 1. 文档目的 + +本文档用于梳理当前前端地图模块的整体结构、运行链路、核心文件职责、图层与点位渲染机制、图例与筛选联动方式,以及后台配置与前台运行时之间的关系。 + +本文档只描述当前地图模块整体实现,不包含抽吸和碰撞的专项分析。 +抽吸与碰撞问题单独整理在: + +- `docs/地图模块-OpenLayers抽吸与碰撞分析.md` + +## 2. 当前技术栈 + +当前地图模块涉及的核心技术如下: + +- 前端框架:Vue 3 + TypeScript + Ant Design Vue +- 状态管理:Pinia +- 2D 地图引擎:OpenLayers +- 3D 地图引擎:Cesium +- 地图服务:GeoServer +- 数据来源: + - 后端接口返回图层树配置 + - 后端接口返回图例配置 + - 后端接口返回各类锚点数据 + +项目依赖中当前地图引擎版本如下: + +- `ol@^10.8.0` +- `cesium@^1.141.0` +- `leaflet@^1.9.4` + +说明: + +- 当前 2D 主链路已经切换到 OpenLayers; +- Leaflet 依赖仍保留在项目中,但不是当前主运行路径。 + +## 3. 地图整体架构 + +当前地图模块可以分成 6 层: + +1. 页面入口层 +2. 应用编排层 +3. 地图能力门面层 +4. 地图引擎实现层 +5. 状态存储层 +6. 交互组件层 + +对应文件如下: + +- 页面入口层 + - `src/components/gis/GisView.vue` + +- 应用编排层 + - `src/modules/map/application/map-orchestrator.ts` + +- 地图能力门面层 + - `src/components/gis/map.class.ts` + +- 地图引擎实现层 + - `src/components/gis/map.ol.ts` + - `src/components/gis/map.cesium.ts` + +- 状态存储层 + - `src/store/modules/map.ts` + - `src/modules/map/stores/map-config.store.ts` + - `src/modules/map/stores/map-data.store.ts` + - `src/modules/map/stores/map-view.store.ts` + +- 交互组件层 + - `src/components/mapLegend/index.vue` + - `src/components/mapFilter/index.vue` + - `src/components/mapController/index.vue` + - `src/components/mapController/LayerController.vue` + - `src/components/BaseLayerSwitcher/index.vue` + +## 4. 页面入口层 + +### 4.1 全局挂载位置 + +地图视图最终由主布局中的 `GisView` 常驻挂载,入口位置在: + +- [AppMain.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/layout/components/AppMain.vue) + +这意味着地图并不是某一个业务页单独创建,而是作为全局地图容器持续存在,然后随着菜单切换切换图层和页面配置。 + +### 4.2 `GisView.vue` 职责 + +文件: + +- [GisView.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/GisView.vue) + +当前职责主要有: + +- 提供地图 DOM 容器 `#mapContainer` +- 挂载地图 popup 容器 +- 挂载地图图例、筛选器、控制器、底图切换器 +- 根据当前路由计算 `pageKey` +- 在组件挂载时触发地图初始化 +- 在页面切换时触发地图页面配置切换 +- 分发少量地图控制命令,例如 2D/3D 切换、梯级流域显示 + +它已经不再承担大部分地图业务拼装逻辑,更多是地图页面入口和组件装配层。 + +## 5. 应用编排层 + +文件: + +- [map-orchestrator.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/modules/map/application/map-orchestrator.ts) + +`map-orchestrator` 是当前地图运行时的调度中心,主要负责把页面、Store、地图实例串起来。 + +核心职责包括: + +- 初始化地图壳和 popup 容器 +- 根据 `pageKey` 加载当前页面地图配置 +- 拉取图层树和图例配置 +- 初始化基础底图 +- 触发点位数据加载 +- 处理图层树勾选 +- 处理图例显隐 +- 处理基地切换 +- 处理时间筛选和重新加载 +- 处理搜索定位 +- 处理某些菜单下的缩放联动图层 + +### 5.1 初始化主链路 + +大致流程如下: + +1. `GisView.vue` 调用 `mapOrchestrator.mountView()` +2. 编排器初始化地图实例 +3. 挂载 popup 容器 +4. 加载当前页面的图层树、图例和页面图例 +5. 初始化基础底图 +6. 加载点位图层数据 +7. 建立缩放监听、基地监听和图例联动 + +### 5.2 页面切换 + +当路由变化后: + +- `GisView.vue` 重新计算 `pageKey` +- 调用 `mapOrchestrator.handlePageChange()` +- 编排器重新加载该页面对应的地图配置和显示状态 + +这使得地图容器本身不销毁,只是页面配置和显示内容发生变化。 + +## 6. 地图能力门面层 + +文件: + +- [map.class.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/map.class.ts) + +`MapClass` 是地图能力统一门面,对外提供统一方法,屏蔽 2D 和 3D 的差异。 + +典型能力包括: + +- `init()` +- `addBaseDataLayer()` +- `addInitDataLayer()` +- `mdLayerTreeShowOrHidden()` +- `setLegendPointVisible()` +- `baseLayerSwitcher()` +- `flyTopanto()` +- `zoomToggle()` +- `lengthCalculate()` +- `areCalculate()` +- `mapOutPut()` +- `destroy()` +- `switchView()` + +当前 2D 主实现是: + +- `this.service = new MapOl()` + +说明当前前台主要还是 OpenLayers 在承担地图业务。 + +## 7. 地图引擎实现层 + +### 7.1 OpenLayers 主实现 + +文件: + +- [map.ol.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/map.ol.ts) + +这是当前地图模块最核心的执行层文件,承担了大量实际渲染与地图交互逻辑,包括: + +- OpenLayers 地图初始化 +- 视图和底图管理 +- 点图层管理 +- 点位样式生成 +- hover popup 与批量 popup +- 区域裁切 +- 基地过滤 +- 梯级流域图层 +- 测量、截图、定位 + +### 7.2 Cesium 实现 + +文件: + +- [map.cesium.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/map.cesium.ts) + +当前 3D 能力存在,但相对 2D 来说并没有完全对齐。 +目前更像是保留了切换入口和部分基础能力,例如: + +- viewer 初始化 +- 底图加载 +- 定位与飞行 +- 部分截图能力 + +多数业务点位和复杂运行逻辑仍然主要围绕 OpenLayers 设计。 + +## 8. 点图层管理 + +文件: + +- [point-layer-manager.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/ol/point-layer-manager.ts) + +这个文件负责 OpenLayers 点图层的生命周期管理。 + +核心职责包括: + +- 按图层 key 创建 `VectorLayer` +- 给每个点图层维护独立的 `VectorSource` +- 把后端点位数据转为 `Feature` +- 建立图例字段索引 +- 控制整层显隐 +- 控制图例项对应点位显隐 +- 删除点图层 +- 获取当前可视范围内点位 + +### 8.1 当前图层组织方式 + +当前点位图层的组织方式是: + +- 每个业务点图层对应一个 `VectorLayer` +- 每条后端点位数据被转换为一个 `Feature` +- Feature 上会额外挂载运行时字段,例如: + - `_iconUrl` + - `_labelText` + - `_legendVisible` + - `_regionVisible` + - `_layerKey` + +### 8.2 图层显隐 + +图层显隐主要分三类: + +- 图层树控制整层显隐 +- 图例项控制某一类点位显隐 +- 基地裁切控制某些点位在当前基地范围外隐藏 + +这些状态最终会体现在 Feature 属性和图层可见性上。 + +## 9. 点位样式与渲染 + +点位样式主逻辑在: + +- [map.ol.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/map.ol.ts) + +当前点位样式包含以下内容: + +- 图标 +- 文字标签 +- 字号随缩放动态变化 +- 图标大小随缩放动态变化 +- 图例显隐状态 +- 基地区域显隐状态 +- 按 `distance` 字段做缩放级别抽稀 + +### 9.1 当前样式生成逻辑 + +当前渲染时会综合判断: + +- 是否有图标 URL +- 图例是否可见 +- 区域是否可见 +- 是否满足当前缩放级别下的距离阈值 + +满足后才会返回 `Style`。 + +### 9.2 当前标签处理 + +当前标签会做以下处理: + +- 去掉括号 +- 最多显示两行 +- 每行长度受限 +- 超长文本截断并加省略号 +- 根据单行或多行情况设置不同偏移 + +这部分属于当前地图的统一标签显示策略。 + +## 10. Popup 机制 + +文件: + +- [popup-manager.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/ol/popup-manager.ts) + +Popup 分为两类: + +- hover 时显示的单点 popup +- 高缩放下批量显示的 popup + +### 10.1 Hover Popup + +基础 hover popup 由地图鼠标移动事件驱动: + +- 检测当前 hover 的要素 +- 更新悬停状态 +- 在对应坐标显示 popup + +### 10.2 批量 Popup + +在较高缩放级别下,会进入批量 popup 模式: + +- 获取当前视口内所有可见点位 +- 逐个测量 popup 尺寸 +- 做边界框碰撞判断 +- 只渲染不冲突的 popup + +这个机制的主要目的是避免高缩放下 popup 过多时完全覆盖地图。 + +## 11. 基础底图与 GIS 图层 + +基础底图和 GIS 叠加图层相关能力主要集中在: + +- `map.ol.ts` +- `mapurlManage.ts` +- `gisUtils.ts` + +### 11.1 `mapurlManage.ts` + +文件: + +- [mapurlManage.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/mapurlManage.ts) + +主要维护: + +- GeoServer 服务地址 +- WMTS 配置 +- XYZ 配置 +- 矢量图层相关配置 +- 某些专题图层和图例显示参数 + +### 11.2 `gisUtils.ts` + +文件: + +- [gisUtils.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/gis/gisUtils.ts) + +主要负责: + +- 图层树拍平 +- 地图配置补全 +- 底图配置转换 +- 部分标签/偏移相关工具逻辑 + +## 12. 图层树与图例 + +### 12.1 图层树 UI + +文件: + +- [LayerController.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/mapController/LayerController.vue) + +主要职责: + +- 展示右侧图层树 +- 响应勾选变化 +- 把勾选结果转发给编排器和 Store + +当前图层树里还包含若干业务互斥规则,例如: + +- 视频站和 AI 视频站互斥 +- 环保设施和环保设施在建互斥 +- 珍稀鱼类和沿程鱼类互斥 + +### 12.2 图例 UI + +文件: + +- [mapLegend/index.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/mapLegend/index.vue) + +主要职责: + +- 展示当前页面和当前已选图层对应的图例 +- 维护图例项勾选状态 +- 把图例点击行为分发给编排器 + +图例不是固定写死的,而是由后端返回配置和当前已选图层共同决定。 + +## 13. 地图筛选与控制器 + +### 13.1 `MapFilter` + +文件: + +- [mapFilter/index.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/mapFilter/index.vue) + +当前承担的筛选功能包括: + +- 基地筛选 +- 时间筛选 +- 装机容量筛选 +- 关键字搜索 +- 个别专题下的额外筛选 + +### 13.2 `MapController` + +文件: + +- [mapController/index.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/mapController/index.vue) + +当前承担的地图操作包括: + +- 放大缩小 +- 图层树显隐 +- 2D / 3D 切换 +- 量算 +- 截图 +- 梯级流域专题显示 + +### 13.3 `BaseLayerSwitcher` + +文件: + +- [BaseLayerSwitcher/index.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/components/BaseLayerSwitcher/index.vue) + +用于切换: + +- 矢量底图 +- 地形底图 +- 影像底图 + +## 14. Store 分层 + +### 14.1 旧主 Store + +文件: + +- [map.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/store/modules/map.ts) + +当前仍然承担大量地图运行态逻辑,例如: + +- 图层选中状态 +- 图例选中状态 +- 图层数据加载 +- 图例联动 +- 点位缓存 +- 图层更新 + +### 14.2 新拆分 Store + +文件: + +- [map-config.store.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/modules/map/stores/map-config.store.ts) +- [map-data.store.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/modules/map/stores/map-data.store.ts) +- [map-view.store.ts](file:///d:/wordpack/WholeProcessPlatform/frontend/src/modules/map/stores/map-view.store.ts) + +当前已经开始按职责拆分: + +- `map-config.store.ts` + - 维护图层树和图例配置 + +- `map-data.store.ts` + - 维护图层数据缓存和点位数据 + +- `map-view.store.ts` + - 维护视图相关运行态,例如选中图层、图例勾选、缩放级别、基地状态 + +这说明地图模块已经在往“编排层 + Store 分层 + 引擎执行层”方向演进。 + +## 15. 基地裁切与区域过滤 + +基地逻辑是当前地图模块里的关键能力之一。 + +入口主要在: + +- `GisView.vue` +- `map-orchestrator.ts` +- `map.ol.ts` + +整体过程大致如下: + +1. 选择基地 +2. 编排器接收基地变化 +3. 调用地图实例更新基地范围 +4. 请求或读取基地边界 GeoJSON +5. 对点位执行区域内外判断 +6. 更新 Feature 的 `_regionVisible` +7. 地图样式函数据此决定是否显示 + +除了点位控制外,基地切换还会影响底图裁切和专题图层显示。 + +## 16. 后台配置与前台运行时关系 + +地图模块不仅依赖前台代码,也强依赖后台配置。 + +### 16.1 后台管理入口 + +主要页面位于: + +- [views/system/map/index.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/views/system/map/index.vue) +- [views/system/map/components/LayerManagement/index.vue](file:///d:/wordpack/WholeProcessPlatform/frontend/src/views/system/map/components/LayerManagement/index.vue) + +### 16.2 后台管理内容 + +后台可维护的内容主要包括: + +- 图层树结构 +- 图层是否启用 +- 图层接口地址 +- 图层基础参数 +- 图例元数据 + +### 16.3 前后台关系 + +运行时不是前端把所有图层写死,而是: + +- 后台维护元数据 +- 前端进入页面后加载配置 +- 再根据配置请求对应图层数据 +- 最后交给地图引擎渲染 + +因此地图模块本质上是“配置驱动 + 运行时渲染”的模式。 + +## 17. 当前调用链总结 + +### 17.1 页面初始化链路 + +整体调用链如下: + +1. `AppMain` 挂载 `GisView` +2. `GisView` 调用 `mapOrchestrator.mountView` +3. `mapOrchestrator` 调用 `MapClass.init` +4. `MapClass` 实际调用 `MapOl.init` +5. 编排器加载图层树和图例配置 +6. 编排器初始化底图 +7. 编排器加载点位数据 +8. `PointLayerManager` 创建点图层并写入 Feature +9. `MapOl` 通过样式函数完成渲染 + +### 17.2 图层勾选链路 + +整体调用链如下: + +1. 用户勾选 `LayerController` +2. 编排器接收图层选择结果 +3. Store 更新图层选中状态 +4. 地图实例更新图层显隐 +5. 图例重新派生 + +### 17.3 图例点击链路 + +整体调用链如下: + +1. 用户点击 `MapLegend` +2. 编排器接收图例切换 +3. Store 更新图例选中状态 +4. 地图实例更新图例对应点位显隐 + +### 17.4 筛选链路 + +整体调用链如下: + +1. 用户操作 `MapFilter` +2. 编排器接收搜索、时间、基地或容量变化 +3. Store 更新运行态 +4. 必要时删图层、清缓存、重新请求数据 +5. 地图引擎重新渲染 + +## 18. 当前模块特点 + +从现有实现看,地图模块有以下几个明显特点: + +- 地图容器是全局常驻的 +- 页面切换主要依赖 `pageKey` 重新编排 +- OpenLayers 是当前 2D 主引擎 +- 地图运行态是配置驱动的 +- 点位图层按业务图层分层组织 +- 图层树、图例、筛选、基地切换之间耦合较深 +- 编排层已经出现,但部分旧逻辑仍在 Store 和引擎层混合存在 + +## 19. 相关文件清单 + +地图模块当前重点文件如下: + +- `src/components/gis/GisView.vue` +- `src/components/gis/map.class.ts` +- `src/components/gis/map.ol.ts` +- `src/components/gis/map.cesium.ts` +- `src/components/gis/gisUtils.ts` +- `src/components/gis/mapurlManage.ts` +- `src/components/gis/ol/point-layer-manager.ts` +- `src/components/gis/ol/popup-manager.ts` +- `src/modules/map/application/map-orchestrator.ts` +- `src/modules/map/stores/map-config.store.ts` +- `src/modules/map/stores/map-data.store.ts` +- `src/modules/map/stores/map-view.store.ts` +- `src/store/modules/map.ts` +- `src/components/mapLegend/index.vue` +- `src/components/mapFilter/index.vue` +- `src/components/mapController/index.vue` +- `src/components/mapController/LayerController.vue` +- `src/components/BaseLayerSwitcher/index.vue` +- `src/views/system/map/index.vue` +- `src/views/system/map/components/LayerManagement/index.vue` + +## 20. 结论 + +当前地图模块已经形成了比较清晰的主干结构: + +- `GisView` 作为入口层 +- `map-orchestrator` 作为编排层 +- `MapClass` 作为统一门面 +- `MapOl` 作为 2D 主执行层 +- 多个 Store 共同维护配置、数据和视图状态 +- 图层树、图例、筛选器、底图切换器作为前台交互入口 + +如果后续继续扩展专题图层、复杂筛选、点位布局和高缩放交互,建议都优先沿着这条主干扩展,而不是再回到页面组件里直接堆业务逻辑。 diff --git a/frontend/docs/问题.md b/frontend/docs/问题.md index 68492e06..94206d91 100644 --- a/frontend/docs/问题.md +++ b/frontend/docs/问题.md @@ -2,35 +2,37 @@ > 本记录用于跟踪项目开发过程中发现的问题,按日期倒序排列,并标注每项问题的发现人及处理状态。 ---- +*** ## 问题列表(2026-06-26) -| 发现日期 | 解决人 | 编号 | 问题描述 | 状态 | 备注 | -|----------|--------|------|----------|------|------| -| 2026-06-26 | 扈 | 1 | 基础信息图片展示是什么逻辑 | ✅ | | -| 2026-06-26 | 扈 | 2 | 电站专题展示逻辑 | ✅ | | -| 2026-06-26 | | 3 | 实时视频回放 少接口 | ⚠ | 缺少接口 | -| 2026-06-26 | | 4 | 预警提示 少接口 | ⚠ | 缺少接口 | -| 2026-06-26 | 扈 | 5 | 生态流量 达标率查询不对 | ✅ | | -| 2026-06-26 | 王 | 6 | 鱼类适应性繁殖同期对比NAN | ⚠ | | -| 2026-06-26 | | 7 | 出库水温 综合分析 导出没做 | ⚠ | | -| 2026-06-26 | | 8 | 栖息地-流量监测 没有水位视频和流量视频 字段没数据 | ⚠ | | -| 2026-06-26 | | 9 | 栖息地 水温、水质、流量没有数据没法测 | ⚠ | | -| 2026-06-26 | | 10 | 鱼类增殖站 - 运行数据 要添加字典 | ⚠ | | -| 2026-06-26 | 扈 | 11 | 珍惜植物园 - 种植要求字段不知道 | ✅ | | -| 2026-06-26 | 扈 | 12 | 水生调查断面 - 监测数据 不知道电导率,性腺发育期,早期资源量和种类 | ✅ | | -| 2026-06-26 | 扈 | 13 | 水电告警情况 生态流量 不知道字段 接口传参有问题 | ✅ | | -| 2026-06-26 | | 14 | 地图抽吸 碰撞检测 放大到具体层级锚点抽吸了但是popup弹框还在显示 | ⚠ | | -| 2026-06-26 | | 15 | 地图比如积石峡 放大到层级被公伯峡隐藏掉了,然后在放大才显示 抽吸的问题 | ⚠ | | -| 2026-06-26 | | 16 | 运行情况 计划开始运行时间 接口没接 | ⚠ | | +| 发现日期 | 解决人 | 编号 | 问题描述 | 状态 | 备注 | +| ---------- | ------ | -- | ------------------------------------- | -- | ------ | +| 2026-06-26 | 扈 | 1 | 基础信息图片展示是什么逻辑 | ✅ |
| +| 2026-06-26 | 扈 | 2 | 电站专题展示逻辑 | ✅ |
| +| 2026-06-26 |
| 3 | 实时视频回放 少接口 | ⚠ | 缺少接口 | +| 2026-06-26 |
| 4 | 预警提示 少接口 | ⚠ | 缺少接口 | +| 2026-06-26 | 扈 | 5 | 生态流量 达标率查询不对 | ✅ |
| +| 2026-06-26 | 王 | 6 | 鱼类适应性繁殖同期对比 NAN | ⚠ |
| +| 2026-06-26 | 扈 | 7 | 出库水温 综合分析 导出没做 | ✅ |
| +| 2026-06-26 |
| 8 | 栖息地-流量监测 没有水位视频和流量视频 字段没数据 | ⚠ |
| +| 2026-06-26 |
| 9 | 栖息地 水温、水质、流量没有数据没法测 | ⚠ |
| +| 2026-06-26 | 扈 | 10 | 鱼类增殖站 - 运行数据 要添加字典 | ✅ |
| +| 2026-06-26 | 扈 | 11 | 珍惜植物园 - 种植要求字段不知道 | ✅ |
| +| 2026-06-26 | 扈 | 12 | 水生调查断面 - 监测数据 不知道电导率,性腺发育期,早期资源量和种类 | ✅ |
| +| 2026-06-26 | 扈 | 13 | 水电告警情况 生态流量 不知道字段 接口传参有问题 | ✅ |
| +| 2026-06-26 |
| 14 | 地图抽吸 碰撞检测 放大到具体层级锚点抽吸了但是 popup 弹框还在显示 | ⚠ |
| +| 2026-06-26 |
| 15 | 地图比如积石峡 放大到层级被公伯峡隐藏掉了,然后在放大才显示 抽吸的问题 | ⚠ |
| +| 2026-06-26 |
| 16 | 运行情况 计划开始运行时间 接口没接 | ⚠ |
| ---- +*** ## 状态说明 -- ✅ – 已解决 / 已确认 -- ⚠ – 处理中 / 待解决 -- ❌ – 阻塞 / 未开始 ---- -、 \ No newline at end of file +- ✅ – 已解决 / 已确认 +- ⚠ – 处理中 / 待解决 +- ❌ – 阻塞 / 未开始 + +*** + +、 diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 45aee3ea..01f126a5 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -7,7 +7,6 @@ const appStore = useAppStore(); @@ -40,10 +41,26 @@ import { ref } from 'vue'; import { message } from 'ant-design-vue'; import { deletePowerInfo } from '@/api/DataQueryMenuModule'; +const props = withDefaults( + defineProps<{ + /** 自定义删除函数,接收 (record, reason) 参数 */ + deleteFn?: (record: any, reason: string) => Promise; + /** 弹窗标题 */ + title?: string; + /** 显示标签(如"电站"、"数据") */ + label?: string; + }>(), + { + title: '删除电站', + label: '电站' + } +); + const emit = defineEmits(['success']); const dataSourceVisible = ref(false); const confirmVisible = ref(false); +const confirmLoading = ref(false); const dataSource = ref(''); const deleteRecord = ref(null); const onSuccess = ref(() => {}); @@ -64,14 +81,21 @@ const handleDataSourceConfirm = () => { // 最终删除 const handleFinalDelete = async () => { + confirmLoading.value = true; // 开始加载 try { - const params = { - ids: [deleteRecord.value.stcd], - source: dataSource.value - }; - const res = await deletePowerInfo(params); - console.log(res); - if (res?.code == 0) { + let res: any; + if (props.deleteFn) { + // 使用外部传入的删除函数 + res = await props.deleteFn(deleteRecord.value, dataSource.value); + } else { + // 默认使用电站删除接口 + const params = { + ids: [deleteRecord.value.stcd], + source: dataSource.value + }; + res = await deletePowerInfo(params); + } + if (res?.code == 0 || res?.success) { message.success('删除成功'); confirmVisible.value = false; onSuccess.value(); @@ -80,6 +104,8 @@ const handleFinalDelete = async () => { } } catch (error) { message.error('删除失败,请重试'); + } finally { + confirmLoading.value = false; // 结束加载 } }; diff --git a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/FishResource/index.vue b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/FishResource/index.vue index a8f8dbd6..f421a160 100644 --- a/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/FishResource/index.vue +++ b/frontend/src/views/DataQueryMenuModule/components/conventionalHydropower/FishResource/index.vue @@ -149,14 +149,6 @@ const tableScrollX = computed(() => ) ); const columns: any = [ - { - key: 'index', - title: '序号', - dataIndex: 'index', - visible: true, - width: 80, - fixed: 'left' - }, { key: 'name', title: '中文名称', @@ -165,6 +157,7 @@ const columns: any = [ width: 150, ellipsis: true, fixed: 'left', + sort: true, customRender: ({ text }: any) => { return text ? h('span', { style: { color: '#2f6b98', cursor: 'pointer' } }, text) @@ -178,6 +171,7 @@ const columns: any = [ visible: true, width: 200, ellipsis: true, + sort: true, fixed: 'left' }, { @@ -187,6 +181,7 @@ const columns: any = [ visible: true, width: 100, ellipsis: true, + sort: true, fixed: 'left' }, { @@ -194,6 +189,7 @@ const columns: any = [ title: '分类', dataIndex: 'typeName', visible: true, + sort: true, width: 100, ellipsis: true }, @@ -202,6 +198,7 @@ const columns: any = [ title: '种', dataIndex: 'species', visible: true, + sort: true, width: 150, ellipsis: true }, @@ -210,6 +207,7 @@ const columns: any = [ title: '属', dataIndex: 'genus', visible: true, + sort: true, width: 100, ellipsis: true }, @@ -218,6 +216,7 @@ const columns: any = [ title: '科', dataIndex: 'family', visible: true, + sort: true, width: 100, ellipsis: true }, @@ -227,6 +226,7 @@ const columns: any = [ dataIndex: 'orders', visible: true, width: 150, + sort: true, ellipsis: true }, // { @@ -244,6 +244,7 @@ const columns: any = [ dataIndex: 'alias', visible: true, width: 200, + sort: true, ellipsis: true }, { @@ -252,6 +253,7 @@ const columns: any = [ dataIndex: 'fsz', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -260,6 +262,7 @@ const columns: any = [ dataIndex: 'shapedesc', visible: true, width: 300, + sort: true, ellipsis: true }, { @@ -268,6 +271,7 @@ const columns: any = [ dataIndex: 'habitation', visible: true, width: 200, + sort: true, ellipsis: true }, { @@ -276,6 +280,7 @@ const columns: any = [ dataIndex: 'habitMigrat', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -284,6 +289,7 @@ const columns: any = [ dataIndex: 'feedingHabit', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -292,6 +298,7 @@ const columns: any = [ dataIndex: 'food', visible: true, width: 200, + sort: true, ellipsis: true }, { @@ -300,6 +307,7 @@ const columns: any = [ dataIndex: 'timeFeed', visible: true, width: 200, + sort: true, ellipsis: true }, { @@ -308,6 +316,7 @@ const columns: any = [ dataIndex: 'spawnMonth', visible: true, width: 200, + sort: true, ellipsis: true }, { @@ -316,6 +325,7 @@ const columns: any = [ dataIndex: 'orignDate', visible: true, width: 300, + sort: true, ellipsis: true }, { @@ -324,6 +334,7 @@ const columns: any = [ dataIndex: 'description', visible: true, width: 200, + sort: true, ellipsis: true }, { @@ -332,6 +343,7 @@ const columns: any = [ dataIndex: 'pretemp', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -340,6 +352,7 @@ const columns: any = [ dataIndex: 'flowRate', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -348,6 +361,7 @@ const columns: any = [ dataIndex: 'depth', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -356,6 +370,7 @@ const columns: any = [ dataIndex: 'wqtq', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -364,6 +379,7 @@ const columns: any = [ dataIndex: 'spawnCharact', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -372,6 +388,7 @@ const columns: any = [ dataIndex: 'botmMater', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -380,6 +397,7 @@ const columns: any = [ dataIndex: 'ptypeName', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -388,6 +406,7 @@ const columns: any = [ dataIndex: 'specOriginName', visible: true, width: 100, + sort: true, ellipsis: true }, { @@ -396,6 +415,7 @@ const columns: any = [ dataIndex: 'vlsr', visible: true, width: 150, + sort: true, ellipsis: true } // { diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/FishData/FishDataSearch.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/FishData/FishDataSearch.vue index 78fd5809..b33493e8 100644 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/FishData/FishDataSearch.vue +++ b/frontend/src/views/DataQueryMenuModule/components/monitorData/FishData/FishDataSearch.vue @@ -84,7 +84,7 @@ const props = defineProps<{ const initSearchData = { rvcd: 'all', rstcd: null, - stcd: null, + stcd: '0086601020VP000006', mway: '1' }; diff --git a/frontend/src/views/DataQueryMenuModule/components/monitorData/FishRelease/FishReleaseSearch.vue b/frontend/src/views/DataQueryMenuModule/components/monitorData/FishRelease/FishReleaseSearch.vue index 3a1742bb..53684e85 100644 --- a/frontend/src/views/DataQueryMenuModule/components/monitorData/FishRelease/FishReleaseSearch.vue +++ b/frontend/src/views/DataQueryMenuModule/components/monitorData/FishRelease/FishReleaseSearch.vue @@ -22,7 +22,7 @@