数据查询修改
This commit is contained in:
parent
78eb7f9b87
commit
0398be3d83
340
frontend/docs/地图模块-OpenLayers抽吸与碰撞分析.md
Normal file
340
frontend/docs/地图模块-OpenLayers抽吸与碰撞分析.md
Normal file
@ -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<VectorSource>`;
|
||||||
|
- 图层上开启了 `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 的碰撞判断。
|
||||||
|
|
||||||
|
在这个基础上,再进入代码修改会更稳。
|
||||||
622
frontend/docs/地图模块-详细说明.md
Normal file
622
frontend/docs/地图模块-详细说明.md
Normal file
@ -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<Point>`
|
||||||
|
- 建立图例字段索引
|
||||||
|
- 控制整层显隐
|
||||||
|
- 控制图例项对应点位显隐
|
||||||
|
- 删除点图层
|
||||||
|
- 获取当前可视范围内点位
|
||||||
|
|
||||||
|
### 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 共同维护配置、数据和视图状态
|
||||||
|
- 图层树、图例、筛选器、底图切换器作为前台交互入口
|
||||||
|
|
||||||
|
如果后续继续扩展专题图层、复杂筛选、点位布局和高缩放交互,建议都优先沿着这条主干扩展,而不是再回到页面组件里直接堆业务逻辑。
|
||||||
@ -2,35 +2,37 @@
|
|||||||
|
|
||||||
> 本记录用于跟踪项目开发过程中发现的问题,按日期倒序排列,并标注每项问题的发现人及处理状态。
|
> 本记录用于跟踪项目开发过程中发现的问题,按日期倒序排列,并标注每项问题的发现人及处理状态。
|
||||||
|
|
||||||
---
|
***
|
||||||
|
|
||||||
## 问题列表(2026-06-26)
|
## 问题列表(2026-06-26)
|
||||||
|
|
||||||
| 发现日期 | 解决人 | 编号 | 问题描述 | 状态 | 备注 |
|
| 发现日期 | 解决人 | 编号 | 问题描述 | 状态 | 备注 |
|
||||||
|----------|--------|------|----------|------|------|
|
| ---------- | ------ | -- | ------------------------------------- | -- | ------ |
|
||||||
| 2026-06-26 | 扈 | 1 | 基础信息图片展示是什么逻辑 | ✅ | |
|
| 2026-06-26 | 扈 | 1 | 基础信息图片展示是什么逻辑 | ✅ | <br /> |
|
||||||
| 2026-06-26 | 扈 | 2 | 电站专题展示逻辑 | ✅ | |
|
| 2026-06-26 | 扈 | 2 | 电站专题展示逻辑 | ✅ | <br /> |
|
||||||
| 2026-06-26 | | 3 | 实时视频回放 少接口 | ⚠ | 缺少接口 |
|
| 2026-06-26 | <br /> | 3 | 实时视频回放 少接口 | ⚠ | 缺少接口 |
|
||||||
| 2026-06-26 | | 4 | 预警提示 少接口 | ⚠ | 缺少接口 |
|
| 2026-06-26 | <br /> | 4 | 预警提示 少接口 | ⚠ | 缺少接口 |
|
||||||
| 2026-06-26 | 扈 | 5 | 生态流量 达标率查询不对 | ✅ | |
|
| 2026-06-26 | 扈 | 5 | 生态流量 达标率查询不对 | ✅ | <br /> |
|
||||||
| 2026-06-26 | 王 | 6 | 鱼类适应性繁殖同期对比NAN | ⚠ | |
|
| 2026-06-26 | 王 | 6 | 鱼类适应性繁殖同期对比 NAN | ⚠ | <br /> |
|
||||||
| 2026-06-26 | | 7 | 出库水温 综合分析 导出没做 | ⚠ | |
|
| 2026-06-26 | 扈 | 7 | 出库水温 综合分析 导出没做 | ✅ | <br /> |
|
||||||
| 2026-06-26 | | 8 | 栖息地-流量监测 没有水位视频和流量视频 字段没数据 | ⚠ | |
|
| 2026-06-26 | <br /> | 8 | 栖息地-流量监测 没有水位视频和流量视频 字段没数据 | ⚠ | <br /> |
|
||||||
| 2026-06-26 | | 9 | 栖息地 水温、水质、流量没有数据没法测 | ⚠ | |
|
| 2026-06-26 | <br /> | 9 | 栖息地 水温、水质、流量没有数据没法测 | ⚠ | <br /> |
|
||||||
| 2026-06-26 | | 10 | 鱼类增殖站 - 运行数据 要添加字典 | ⚠ | |
|
| 2026-06-26 | 扈 | 10 | 鱼类增殖站 - 运行数据 要添加字典 | ✅ | <br /> |
|
||||||
| 2026-06-26 | 扈 | 11 | 珍惜植物园 - 种植要求字段不知道 | ✅ | |
|
| 2026-06-26 | 扈 | 11 | 珍惜植物园 - 种植要求字段不知道 | ✅ | <br /> |
|
||||||
| 2026-06-26 | 扈 | 12 | 水生调查断面 - 监测数据 不知道电导率,性腺发育期,早期资源量和种类 | ✅ | |
|
| 2026-06-26 | 扈 | 12 | 水生调查断面 - 监测数据 不知道电导率,性腺发育期,早期资源量和种类 | ✅ | <br /> |
|
||||||
| 2026-06-26 | 扈 | 13 | 水电告警情况 生态流量 不知道字段 接口传参有问题 | ✅ | |
|
| 2026-06-26 | 扈 | 13 | 水电告警情况 生态流量 不知道字段 接口传参有问题 | ✅ | <br /> |
|
||||||
| 2026-06-26 | | 14 | 地图抽吸 碰撞检测 放大到具体层级锚点抽吸了但是popup弹框还在显示 | ⚠ | |
|
| 2026-06-26 | <br /> | 14 | 地图抽吸 碰撞检测 放大到具体层级锚点抽吸了但是 popup 弹框还在显示 | ⚠ | <br /> |
|
||||||
| 2026-06-26 | | 15 | 地图比如积石峡 放大到层级被公伯峡隐藏掉了,然后在放大才显示 抽吸的问题 | ⚠ | |
|
| 2026-06-26 | <br /> | 15 | 地图比如积石峡 放大到层级被公伯峡隐藏掉了,然后在放大才显示 抽吸的问题 | ⚠ | <br /> |
|
||||||
| 2026-06-26 | | 16 | 运行情况 计划开始运行时间 接口没接 | ⚠ | |
|
| 2026-06-26 | <br /> | 16 | 运行情况 计划开始运行时间 接口没接 | ⚠ | <br /> |
|
||||||
|
|
||||||
---
|
***
|
||||||
|
|
||||||
## 状态说明
|
## 状态说明
|
||||||
|
|
||||||
- ✅ – 已解决 / 已确认
|
- ✅ – 已解决 / 已确认
|
||||||
- ⚠ – 处理中 / 待解决
|
- ⚠ – 处理中 / 待解决
|
||||||
- ❌ – 阻塞 / 未开始
|
- ❌ – 阻塞 / 未开始
|
||||||
|
|
||||||
---
|
***
|
||||||
|
|
||||||
、
|
、
|
||||||
@ -7,7 +7,6 @@ const appStore = useAppStore();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 1. 数据查询 电站信息 编辑删除 自定义筛选列 没做-->
|
|
||||||
<el-config-provider :locale="appStore.locale" :size="appStore.size">
|
<el-config-provider :locale="appStore.locale" :size="appStore.size">
|
||||||
<a-config-provider :theme="usetTheme" :locale="locale">
|
<a-config-provider :theme="usetTheme" :locale="locale">
|
||||||
<router-view />
|
<router-view />
|
||||||
|
|||||||
@ -119,6 +119,22 @@ export function getWaterDataMonthList(data) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 水质监测数据 编辑
|
||||||
|
export function updateWaterDataInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wq/updateWqRsData',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 水质监测数据 删除
|
||||||
|
export function deleteWaterDataInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wq/removeKendoByIds',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
// 垂向 - 列表
|
// 垂向 - 列表
|
||||||
export function getVerticalList(data) {
|
export function getVerticalList(data) {
|
||||||
return request({
|
return request({
|
||||||
@ -127,6 +143,22 @@ export function getVerticalList(data) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 垂向 - 编辑
|
||||||
|
export function updateVerticalInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wt/cxDetail/updateWtvtRData',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 垂向 - 删除
|
||||||
|
export function deleteVerticalInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wt/cxDetail/removeKendoByIds',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
// 表层水温 - 断面
|
// 表层水温 - 断面
|
||||||
export function getSurfaceTempSectionList(data) {
|
export function getSurfaceTempSectionList(data) {
|
||||||
return request({
|
return request({
|
||||||
@ -159,6 +191,23 @@ export function getSurfaceTempMonthList(data) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 表层水温 - 编辑
|
||||||
|
export function updateSurfaceTempInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wt/alongDetail/updateWtrvRData',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 表层水温 - 删除
|
||||||
|
export function deleteSurfaceTempInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wt/alongDetail/drtp/removeKendoByIds',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
// 表层水温 - 数据分析 - 断面
|
// 表层水温 - 数据分析 - 断面
|
||||||
export function getSurfaceTempAnalysisList(data) {
|
export function getSurfaceTempAnalysisList(data) {
|
||||||
return request({
|
return request({
|
||||||
@ -242,3 +291,20 @@ export function getFlowStationMonthList(data) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 流量站 - 编辑
|
||||||
|
export function updateFlowStationInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/zq/river/updateRiverRData',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 流量站 - 删除
|
||||||
|
export function deleteFlowStationInfo(data) {
|
||||||
|
return request({
|
||||||
|
url: '/zq/river/removeKendoByIds',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export function postIdUrl(data: any) {
|
|||||||
// 查询站点建设状态
|
// 查询站点建设状态
|
||||||
export function getBuildState(data: any) {
|
export function getBuildState(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/dec-lygk-base-server/base/vmsstbprpt/GetKendoList',
|
url: '/wt/vmsstbprpt/GetKendoList',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: {
|
data: {
|
||||||
filter: data.filter,
|
filter: data.filter,
|
||||||
|
|||||||
@ -40,7 +40,10 @@
|
|||||||
import { ref, computed, onMounted, watch, nextTick, h, useSlots } from 'vue';
|
import { ref, computed, onMounted, watch, nextTick, h, useSlots } from 'vue';
|
||||||
import { message, Tooltip } from 'ant-design-vue';
|
import { message, Tooltip } from 'ant-design-vue';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
import { exportTable as exportTableUtil } from '@/utils/exportExcel';
|
import {
|
||||||
|
exportTable as exportTableUtil,
|
||||||
|
type ExportColumn
|
||||||
|
} from '@/utils/exportExcel';
|
||||||
|
|
||||||
// --- Types ---
|
// --- Types ---
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -328,6 +331,13 @@ interface ExportTableParams {
|
|||||||
callback?: (message: string | boolean) => void;
|
callback?: (message: string | boolean) => void;
|
||||||
tbtitle?: string;
|
tbtitle?: string;
|
||||||
tbsub?: string;
|
tbsub?: string;
|
||||||
|
summary?: {
|
||||||
|
sheetName: string;
|
||||||
|
data: any[];
|
||||||
|
columns: ExportColumn[];
|
||||||
|
tbtitle?: string;
|
||||||
|
tbsub?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultExportCallback = (result: string | boolean) => {
|
const defaultExportCallback = (result: string | boolean) => {
|
||||||
@ -341,7 +351,7 @@ const defaultExportCallback = (result: string | boolean) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const exportTable = async (params: ExportTableParams): Promise<void> => {
|
const exportTable = async (params: ExportTableParams): Promise<void> => {
|
||||||
const { fileName, callback, tbtitle, tbsub } = params;
|
const { fileName, callback, tbtitle, tbsub, summary } = params;
|
||||||
const cb = callback || defaultExportCallback;
|
const cb = callback || defaultExportCallback;
|
||||||
|
|
||||||
// 如果没有 listUrl 且没有内部数据,则无法导出
|
// 如果没有 listUrl 且没有内部数据,则无法导出
|
||||||
@ -433,6 +443,7 @@ const exportTable = async (params: ExportTableParams): Promise<void> => {
|
|||||||
data: records,
|
data: records,
|
||||||
columns: props.columns,
|
columns: props.columns,
|
||||||
fileName,
|
fileName,
|
||||||
|
appendTables: summary ? [summary] : [],
|
||||||
callback: result => {
|
callback: result => {
|
||||||
cb(result);
|
cb(result);
|
||||||
resolve();
|
resolve();
|
||||||
|
|||||||
@ -90,9 +90,9 @@ import {
|
|||||||
getEcologyList1,
|
getEcologyList1,
|
||||||
getEcologyList2,
|
getEcologyList2,
|
||||||
getEcologyList3,
|
getEcologyList3,
|
||||||
getEcologyProtectType,
|
|
||||||
getEcologyYear
|
getEcologyYear
|
||||||
} from '@/api/mapModal';
|
} from '@/api/mapModal';
|
||||||
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
import { useModelStore } from '@/store/modules/model';
|
import { useModelStore } from '@/store/modules/model';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import CommonAttachmentModal from './CommonAttachmentModal.vue';
|
import CommonAttachmentModal from './CommonAttachmentModal.vue';
|
||||||
@ -473,7 +473,7 @@ const loadBatchOptions = async () => {
|
|||||||
const loadProtectTypeOptions = async () => {
|
const loadProtectTypeOptions = async () => {
|
||||||
protectTypeLoading.value = true;
|
protectTypeLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const res: any = await getEcologyProtectType({
|
const res: any = await getDictItemsByCode({
|
||||||
dictCode: 'FISHTY'
|
dictCode: 'FISHTY'
|
||||||
});
|
});
|
||||||
const records = res?.data?.data || res?.data?.records || res?.data || [];
|
const records = res?.data?.data || res?.data?.records || res?.data || [];
|
||||||
@ -486,8 +486,8 @@ const loadProtectTypeOptions = async () => {
|
|||||||
...records
|
...records
|
||||||
.filter((item: any) => String(item?.level) !== '1')
|
.filter((item: any) => String(item?.level) !== '1')
|
||||||
.map((item: any) => ({
|
.map((item: any) => ({
|
||||||
label: item?.dictMeaning,
|
label: item?.dictName,
|
||||||
value: item?.dictValue
|
value: item?.itemCode
|
||||||
}))
|
}))
|
||||||
.filter((item: any) => item.label && item.value)
|
.filter((item: any) => item.label && item.value)
|
||||||
];
|
];
|
||||||
|
|||||||
@ -301,12 +301,10 @@ const buildFishOptionItem = (item: any): OperationFilterOption => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const buildDictOptionItem = (item: any): OperationFilterOption => ({
|
const buildDictOptionItem = (item: any): OperationFilterOption => ({
|
||||||
label:
|
label: item?.dictName || item?.label || item?.text || item?.dictLabel || '-',
|
||||||
item?.dictMeaning || item?.label || item?.text || item?.dictLabel || '-',
|
value: item?.itemCode ?? item?.value ?? item?.code ?? '',
|
||||||
value: item?.dictValue ?? item?.value ?? item?.code ?? '',
|
name: item?.dictName || item?.label || item?.text || item?.dictLabel || '-',
|
||||||
name:
|
id: item?.itemCode ?? item?.value ?? item?.code ?? ''
|
||||||
item?.dictMeaning || item?.label || item?.text || item?.dictLabel || '-',
|
|
||||||
id: item?.dictValue ?? item?.value ?? item?.code ?? ''
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getFieldOptions = (field: OperationFilterField) => {
|
const getFieldOptions = (field: OperationFilterField) => {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
yuLeiShouJin,
|
yuLeiShouJin,
|
||||||
yuleifuhua
|
yuleifuhua
|
||||||
} from './proliferationColumns';
|
} from './proliferationColumns';
|
||||||
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
|
|
||||||
export interface OperationFilterOption {
|
export interface OperationFilterOption {
|
||||||
label: string;
|
label: string;
|
||||||
@ -152,7 +153,7 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'ctmo',
|
name: 'ctmo',
|
||||||
label: '培育方式',
|
label: '培育方式',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/system/dictionary/getDictItemsByCode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'CTMO'
|
dictCode: 'CTMO'
|
||||||
@ -218,7 +219,7 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'ftype',
|
name: 'ftype',
|
||||||
label: '饲料种类',
|
label: '饲料种类',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/system/dictionary/getDictItemsByCode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'FEEDTP'
|
dictCode: 'FEEDTP'
|
||||||
@ -312,7 +313,7 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'fpitocintype',
|
name: 'fpitocintype',
|
||||||
label: '催产剂种类',
|
label: '催产剂种类',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/system/dictionary/getDictItemsByCode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'FPITOCINTYPE'
|
dictCode: 'FPITOCINTYPE'
|
||||||
@ -361,7 +362,7 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'fishsrc',
|
name: 'fishsrc',
|
||||||
label: '亲鱼来源',
|
label: '亲鱼来源',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/system/dictionary/getDictItemsByCode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'BSSR'
|
dictCode: 'BSSR'
|
||||||
@ -430,7 +431,7 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'ftype',
|
name: 'ftype',
|
||||||
label: '饲料种类',
|
label: '饲料种类',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/system/dictionary/getDictItemsByCode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'FEEDTP'
|
dictCode: 'FEEDTP'
|
||||||
@ -478,7 +479,7 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'method',
|
name: 'method',
|
||||||
label: '用药方式',
|
label: '用药方式',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/system/dictionary/getDictItemsByCode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'method'
|
dictCode: 'method'
|
||||||
@ -488,7 +489,7 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'treateffect',
|
name: 'treateffect',
|
||||||
label: '治疗效果',
|
label: '治疗效果',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/system/dictionary/getDictItemsByCode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'TREATEFFECT'
|
dictCode: 'TREATEFFECT'
|
||||||
@ -498,7 +499,7 @@ export const operationStepList: OperationStepItem[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'fishdrugtype',
|
name: 'fishdrugtype',
|
||||||
label: '鱼药种类',
|
label: '鱼药种类',
|
||||||
url: '/api/dec-modules-usm-springcloud-starter/usm/v1/dict/getRemoteDictTreeValue',
|
url: '/system/dictionary/getDictItemsByCode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
dictCode: 'FISHDRUGTYPE'
|
dictCode: 'FISHDRUGTYPE'
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
v-if="isSummaryMode"
|
v-if="isSummaryMode"
|
||||||
class="search-btn"
|
class="search-btn"
|
||||||
@click="handleExport"
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
>
|
>
|
||||||
导出
|
导出
|
||||||
</a-button>
|
</a-button>
|
||||||
@ -49,6 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<BasicTable
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
:scrollY="480"
|
:scrollY="480"
|
||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:columns="currentColumns"
|
:columns="currentColumns"
|
||||||
@ -82,6 +84,8 @@ import BasicTable from '@/components/BasicTable/index.vue';
|
|||||||
import { DateSetting } from '@/utils/enumeration';
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
|
const exportLoading = ref(false);
|
||||||
|
const tableRef = ref<any>();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isActive: { type: Boolean, default: false },
|
isActive: { type: Boolean, default: false },
|
||||||
@ -617,8 +621,17 @@ const fetchData = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => fetchData();
|
const handleSearch = () => fetchData();
|
||||||
|
/* 导出逻辑 */
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
/* 导出逻辑 */
|
exportLoading.value = true;
|
||||||
|
|
||||||
|
tableRef.value
|
||||||
|
.exportTable({
|
||||||
|
fileName: `综合分析_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
exportLoading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
v-if="isSummaryMode"
|
v-if="isSummaryMode"
|
||||||
class="search-btn"
|
class="search-btn"
|
||||||
@click="handleExport"
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
>
|
>
|
||||||
导出
|
导出
|
||||||
</a-button>
|
</a-button>
|
||||||
@ -49,6 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<BasicTable
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
:scrollY="360"
|
:scrollY="360"
|
||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:columns="currentColumns"
|
:columns="currentColumns"
|
||||||
@ -140,6 +142,8 @@ const showTimeConfig = {
|
|||||||
minuteStep: 5,
|
minuteStep: 5,
|
||||||
secondStep: 60
|
secondStep: 60
|
||||||
};
|
};
|
||||||
|
const tableRef = ref<any>();
|
||||||
|
const exportLoading = ref(false);
|
||||||
const disabledDate = (current: Dayjs) =>
|
const disabledDate = (current: Dayjs) =>
|
||||||
current && current.isAfter(dayjs(), 'day');
|
current && current.isAfter(dayjs(), 'day');
|
||||||
|
|
||||||
@ -446,6 +450,42 @@ const summaryRows = computed(() => {
|
|||||||
return rows;
|
return rows;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const formatSummaryExportText = (cell?: { value: string; time: string }) => {
|
||||||
|
if (!cell) return '-';
|
||||||
|
const value = cell.value ?? '-';
|
||||||
|
if (!cell.time) return value;
|
||||||
|
|
||||||
|
const formattedTime = dayjs(cell.time).isValid()
|
||||||
|
? dayjs(cell.time).format('YYYY-MM-DD HH:mm')
|
||||||
|
: cell.time;
|
||||||
|
return `${value}\n时间:${formattedTime}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const summaryExportColumns = computed(() => [
|
||||||
|
{
|
||||||
|
title: '统计项',
|
||||||
|
dataIndex: 'label',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
...summaryColumns.value.map((col: any) => ({
|
||||||
|
title: col.title,
|
||||||
|
dataIndex: col.dataIndex,
|
||||||
|
width: col.width
|
||||||
|
}))
|
||||||
|
]);
|
||||||
|
|
||||||
|
const summaryExportData = computed(() =>
|
||||||
|
summaryRows.value.map((row: any) => {
|
||||||
|
const exportRow: Record<string, string> = { label: row.label };
|
||||||
|
summaryColumns.value.forEach((col: any) => {
|
||||||
|
exportRow[col.dataIndex] = formatSummaryExportText(
|
||||||
|
row.cells[col.dataIndex]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return exportRow;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// ==================== 图表 ====================
|
// ==================== 图表 ====================
|
||||||
function shouldStagger(width: number, dataLen: number) {
|
function shouldStagger(width: number, dataLen: number) {
|
||||||
return dataLen > Math.floor(width / 100);
|
return dataLen > Math.floor(width / 100);
|
||||||
@ -897,8 +937,25 @@ const fetchData = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => fetchData();
|
const handleSearch = () => fetchData();
|
||||||
|
// 导出
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
/* 导出逻辑 */
|
exportLoading.value = true;
|
||||||
|
|
||||||
|
tableRef.value
|
||||||
|
.exportTable({
|
||||||
|
fileName: `综合分析_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`,
|
||||||
|
summary:
|
||||||
|
summaryExportData.value.length > 0
|
||||||
|
? {
|
||||||
|
sheetName: '综合分析汇总',
|
||||||
|
data: summaryExportData.value,
|
||||||
|
columns: summaryExportColumns.value
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
exportLoading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
|
|||||||
@ -4,14 +4,7 @@
|
|||||||
<div ref="popupRef" class="map-popup-container" style="display: none"></div>
|
<div ref="popupRef" class="map-popup-container" style="display: none"></div>
|
||||||
<!-- - 1. 切换菜单的时候图层没有切换 ,是因为接口的原因,现在没有接口
|
<!-- - 1. 切换菜单的时候图层没有切换 ,是因为接口的原因,现在没有接口
|
||||||
2. 切换菜单的时候图例现在是不对的,默认选中的数据他们没有做处理
|
2. 切换菜单的时候图例现在是不对的,默认选中的数据他们没有做处理
|
||||||
|
-->
|
||||||
1. 图层切换显示(完成)
|
|
||||||
2. 鼠标悬停,点击(完成)
|
|
||||||
3. 文字重叠(完成)
|
|
||||||
4. 搜索 (完成)
|
|
||||||
5. 公共弹框基础信息是eng的时候才显示电站专题
|
|
||||||
6.图层加载的时候如果接口报错,loading 一直加载(完成)
|
|
||||||
popup 隐藏还是有问题 (完成) -->
|
|
||||||
<!-- 地图图例 -->
|
<!-- 地图图例 -->
|
||||||
<MapLegend />
|
<MapLegend />
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
|
|
||||||
export interface UseTimeScaleOptions {
|
export interface UseTimeScaleOptions {
|
||||||
defaultTimeScale?: string;
|
defaultTimeScale?: string;
|
||||||
@ -7,6 +8,11 @@ export interface UseTimeScaleOptions {
|
|||||||
onReset?: (values: any) => void;
|
onReset?: (values: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TimeShortcutItem {
|
||||||
|
label: string;
|
||||||
|
value: [Dayjs, Dayjs];
|
||||||
|
}
|
||||||
|
|
||||||
export function useTimeScale(options: UseTimeScaleOptions = {}) {
|
export function useTimeScale(options: UseTimeScaleOptions = {}) {
|
||||||
const defaultTimeScale = options.defaultTimeScale || 'tm';
|
const defaultTimeScale = options.defaultTimeScale || 'tm';
|
||||||
const currentTimeScale = ref(defaultTimeScale);
|
const currentTimeScale = ref(defaultTimeScale);
|
||||||
@ -36,6 +42,70 @@ export function useTimeScale(options: UseTimeScaleOptions = {}) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getTmShortcutList = (): TimeShortcutItem[] => {
|
||||||
|
const getStartTime = () => dayjs().startOf('day').startOf('hour');
|
||||||
|
const getStartHourTime = () => dayjs().startOf('hour');
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: '此刻',
|
||||||
|
value: [getStartTime(), getStartTime()]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '今天',
|
||||||
|
value: [getStartTime().startOf('day'), getStartTime().endOf('day')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '昨天',
|
||||||
|
value: [
|
||||||
|
getStartTime().subtract(1, 'day'),
|
||||||
|
getStartTime().subtract(1, 'day').endOf('day')
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '最近7小时',
|
||||||
|
value: [getStartHourTime().subtract(7, 'hour'), getStartHourTime()]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '本月',
|
||||||
|
value: [getStartTime().startOf('month'), getStartTime().endOf('month')]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMonthShortcutList = (): TimeShortcutItem[] => {
|
||||||
|
const now = dayjs();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: '本月',
|
||||||
|
value: [now.startOf('month'), now.endOf('month')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '最近三个月',
|
||||||
|
value: [now.subtract(3, 'month').startOf('month'), now.endOf('month')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '今年',
|
||||||
|
value: [now.startOf('year'), now.endOf('year')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '最近半年',
|
||||||
|
value: [now.subtract(5, 'month').startOf('month'), now.endOf('month')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '最近一年',
|
||||||
|
value: [now.subtract(12, 'month').startOf('month'), now.endOf('month')]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeShortcutList = computed<TimeShortcutItem[]>(() => {
|
||||||
|
if (currentTimeScale.value === 'tm') return getTmShortcutList();
|
||||||
|
if (currentTimeScale.value === 'dt') return DateSetting.RangeButton.days1;
|
||||||
|
return getMonthShortcutList();
|
||||||
|
});
|
||||||
|
|
||||||
// 设置默认时间范围
|
// 设置默认时间范围
|
||||||
const setDefaultTimeRange = (timeScale: string) => {
|
const setDefaultTimeRange = (timeScale: string) => {
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
@ -125,6 +195,7 @@ export function useTimeScale(options: UseTimeScaleOptions = {}) {
|
|||||||
timePicker,
|
timePicker,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
timeShowTime,
|
timeShowTime,
|
||||||
|
timeShortcutList,
|
||||||
handleRangeClick,
|
handleRangeClick,
|
||||||
disabledEndDate,
|
disabledEndDate,
|
||||||
handleSearchFinish,
|
handleSearchFinish,
|
||||||
|
|||||||
@ -25,6 +25,46 @@ export interface ExportParams {
|
|||||||
tbsub?: string;
|
tbsub?: string;
|
||||||
/** 导出回调 */
|
/** 导出回调 */
|
||||||
callback?: (message: string | boolean) => void;
|
callback?: (message: string | boolean) => void;
|
||||||
|
/** 附加导出表格,默认追加到主表末尾 */
|
||||||
|
appendTables?: ExportAppendTableConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportAppendTableConfig {
|
||||||
|
/** 追加表格数据 */
|
||||||
|
data: any[];
|
||||||
|
/** 追加表格列配置 */
|
||||||
|
columns: ExportColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeExportValue(value: any): any {
|
||||||
|
if (value === null || value === undefined || value === '') return value;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof value === 'string' ||
|
||||||
|
typeof value === 'number' ||
|
||||||
|
typeof value === 'boolean'
|
||||||
|
) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const normalized = value
|
||||||
|
.map(item => normalizeExportValue(item))
|
||||||
|
.filter(item => item !== null && item !== undefined && item !== '');
|
||||||
|
return normalized.length > 0 ? normalized.join('') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if ('children' in value) {
|
||||||
|
return normalizeExportValue(value.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('value' in value) {
|
||||||
|
return normalizeExportValue(value.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,63 +88,84 @@ function downloadExcel({
|
|||||||
try {
|
try {
|
||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
|
||||||
for (const sheet of sheets) {
|
const headerStyle: Partial<ExcelJS.Style> = {
|
||||||
const { columns, dataSource, tbtitle, tbsub, sheetName } = sheet;
|
font: { bold: true, size: 14, name: '微软雅黑' },
|
||||||
|
alignment: {
|
||||||
|
wrapText: true,
|
||||||
|
vertical: 'middle',
|
||||||
|
horizontal: 'left'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 添加 sheet
|
const dataStyle: Partial<ExcelJS.Style> = {
|
||||||
const worksheet = workbook.addWorksheet(sheetName || 'Sheet1', {
|
font: { size: 12, name: '微软雅黑' },
|
||||||
properties: { tabColor: { argb: 'FFC0000' } }
|
alignment: {
|
||||||
});
|
wrapText: true,
|
||||||
|
vertical: 'middle',
|
||||||
|
horizontal: 'left'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 固定列宽(根据定义宽度计算导出宽度)
|
const setWorksheetColumns = (
|
||||||
worksheet.columns = columns.map(col => {
|
worksheet: ExcelJS.Worksheet,
|
||||||
const definedWidth = col.width || 150;
|
columnGroups: ExportColumn[][]
|
||||||
// < 200: 29.22, 200-300: 39.22, 300-400: 59.22
|
) => {
|
||||||
|
const maxColumnCount = Math.max(
|
||||||
|
...columnGroups.map(cols => cols.length),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
worksheet.columns = Array.from({ length: maxColumnCount }, (_, index) => {
|
||||||
|
const definedWidth = Math.max(
|
||||||
|
...columnGroups.map(cols => cols[index]?.width || 150),
|
||||||
|
150
|
||||||
|
);
|
||||||
const exportWidth = 29.22 + Math.max(0, (definedWidth - 150) / 50) * 10;
|
const exportWidth = 29.22 + Math.max(0, (definedWidth - 150) / 50) * 10;
|
||||||
return { width: exportWidth };
|
return { width: exportWidth };
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 样式定义
|
const writeTableBlock = ({
|
||||||
const headerStyle: Partial<ExcelJS.Style> = {
|
worksheet,
|
||||||
font: { bold: true, size: 14, name: '微软雅黑' },
|
columns,
|
||||||
alignment: {
|
dataSource,
|
||||||
wrapText: true,
|
startRow,
|
||||||
vertical: 'middle',
|
title,
|
||||||
horizontal: 'left'
|
subTitle,
|
||||||
}
|
freezeHeader = false,
|
||||||
};
|
showHeader = true
|
||||||
|
}: {
|
||||||
|
worksheet: ExcelJS.Worksheet;
|
||||||
|
columns: ExportColumn[];
|
||||||
|
dataSource: any[];
|
||||||
|
startRow: number;
|
||||||
|
title?: string;
|
||||||
|
subTitle?: string;
|
||||||
|
freezeHeader?: boolean;
|
||||||
|
showHeader?: boolean;
|
||||||
|
}) => {
|
||||||
|
let currentRow = startRow;
|
||||||
|
|
||||||
const dataStyle: Partial<ExcelJS.Style> = {
|
if (title) {
|
||||||
font: { size: 12, name: '微软雅黑' },
|
|
||||||
alignment: {
|
|
||||||
wrapText: true,
|
|
||||||
vertical: 'middle',
|
|
||||||
horizontal: 'left'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 当前行号
|
|
||||||
let currentRow = 1;
|
|
||||||
|
|
||||||
// 写入表头标题(如果有)
|
|
||||||
if (tbtitle) {
|
|
||||||
const titleRow = worksheet.getRow(currentRow);
|
const titleRow = worksheet.getRow(currentRow);
|
||||||
titleRow.getCell(1).value = tbtitle;
|
titleRow.getCell(1).value = title;
|
||||||
titleRow.getCell(2).value = tbsub || '';
|
titleRow.getCell(2).value = subTitle || '';
|
||||||
currentRow++;
|
currentRow++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入表头行
|
if (showHeader) {
|
||||||
const headerRow = worksheet.getRow(currentRow);
|
const headerRow = worksheet.getRow(currentRow);
|
||||||
columns.forEach((col, index) => {
|
columns.forEach((col, index) => {
|
||||||
headerRow.getCell(index + 1).value = col.title;
|
headerRow.getCell(index + 1).value = col.title;
|
||||||
headerRow.getCell(index + 1).style = headerStyle;
|
headerRow.getCell(index + 1).style = headerStyle;
|
||||||
});
|
});
|
||||||
// 冻结表头行
|
|
||||||
worksheet.views = [{ state: 'frozen', ySplit: currentRow }];
|
if (freezeHeader) {
|
||||||
currentRow++;
|
worksheet.views = [{ state: 'frozen', ySplit: currentRow }];
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRow++;
|
||||||
|
}
|
||||||
|
|
||||||
// 写入数据行
|
|
||||||
dataSource.forEach(record => {
|
dataSource.forEach(record => {
|
||||||
const row = worksheet.getRow(currentRow);
|
const row = worksheet.getRow(currentRow);
|
||||||
columns.forEach((col, index) => {
|
columns.forEach((col, index) => {
|
||||||
@ -113,15 +174,18 @@ function downloadExcel({
|
|||||||
|
|
||||||
let value = record[dataIndex];
|
let value = record[dataIndex];
|
||||||
|
|
||||||
// 如果有 customRender,优先使用它处理后的值
|
|
||||||
if (customRender) {
|
if (customRender) {
|
||||||
try {
|
try {
|
||||||
const renderResult = customRender({
|
const renderResult = customRender({
|
||||||
text: value,
|
text: value,
|
||||||
record,
|
record,
|
||||||
index: currentRow - (tbtitle ? 3 : 2)
|
index:
|
||||||
|
currentRow - startRow - (title ? 1 : 0) - (showHeader ? 1 : 0)
|
||||||
});
|
});
|
||||||
value = renderResult !== undefined ? renderResult : value;
|
value =
|
||||||
|
renderResult !== undefined
|
||||||
|
? normalizeExportValue(renderResult)
|
||||||
|
: value;
|
||||||
} catch {
|
} catch {
|
||||||
// customRender 执行失败,使用原始值
|
// customRender 执行失败,使用原始值
|
||||||
}
|
}
|
||||||
@ -133,12 +197,60 @@ function downloadExcel({
|
|||||||
|
|
||||||
const cell = row.getCell(index + 1);
|
const cell = row.getCell(index + 1);
|
||||||
cell.value = value;
|
cell.value = value;
|
||||||
|
|
||||||
// 所有单元格统一应用样式,确保 - 占位符也居中对齐
|
|
||||||
cell.style = dataStyle;
|
cell.style = dataStyle;
|
||||||
});
|
});
|
||||||
currentRow++;
|
currentRow++;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return currentRow;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const sheet of sheets) {
|
||||||
|
const {
|
||||||
|
columns,
|
||||||
|
dataSource,
|
||||||
|
tbtitle,
|
||||||
|
tbsub,
|
||||||
|
sheetName,
|
||||||
|
appendTables = []
|
||||||
|
} = sheet as {
|
||||||
|
sheetName: string;
|
||||||
|
columns: ExportColumn[];
|
||||||
|
dataSource: any[];
|
||||||
|
tbtitle?: string;
|
||||||
|
tbsub?: string;
|
||||||
|
appendTables?: ExportAppendTableConfig[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加 sheet
|
||||||
|
const worksheet = workbook.addWorksheet(sheetName || 'Sheet1', {
|
||||||
|
properties: { tabColor: { argb: 'FFC0000' } }
|
||||||
|
});
|
||||||
|
|
||||||
|
setWorksheetColumns(worksheet, [
|
||||||
|
columns,
|
||||||
|
...appendTables.map(table => table.columns)
|
||||||
|
]);
|
||||||
|
|
||||||
|
let currentRow = writeTableBlock({
|
||||||
|
worksheet,
|
||||||
|
columns,
|
||||||
|
dataSource,
|
||||||
|
startRow: 1,
|
||||||
|
title: tbtitle,
|
||||||
|
subTitle: tbsub,
|
||||||
|
freezeHeader: true
|
||||||
|
});
|
||||||
|
|
||||||
|
appendTables.forEach(table => {
|
||||||
|
currentRow = writeTableBlock({
|
||||||
|
worksheet,
|
||||||
|
columns: table.columns,
|
||||||
|
dataSource: table.data,
|
||||||
|
startRow: currentRow,
|
||||||
|
showHeader: false
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 Excel 文件并下载
|
// 生成 Excel 文件并下载
|
||||||
@ -190,6 +302,24 @@ function resolveColumnTitle(col: ExportColumn): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveExportColumns(columns: ExportColumn[]): ExportColumn[] {
|
||||||
|
const exportColumns: ExportColumn[] = [];
|
||||||
|
for (const col of columns) {
|
||||||
|
if (!col.dataIndex || col.dataIndex === 'action' || col.excludeFromExport) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const resolvedTitle = resolveColumnTitle(col);
|
||||||
|
if (!resolvedTitle) continue;
|
||||||
|
|
||||||
|
exportColumns.push({
|
||||||
|
...col,
|
||||||
|
title: resolvedTitle
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportColumns;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取嵌套属性值
|
* 获取嵌套属性值
|
||||||
*/
|
*/
|
||||||
@ -207,40 +337,34 @@ export function exportTable({
|
|||||||
fileName = '导出数据',
|
fileName = '导出数据',
|
||||||
tbtitle,
|
tbtitle,
|
||||||
tbsub,
|
tbsub,
|
||||||
callback
|
callback,
|
||||||
|
appendTables = []
|
||||||
}: ExportParams) {
|
}: ExportParams) {
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
callback?.('没有可导出的数据');
|
callback?.('没有可导出的数据');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤掉不需要导出的列,并解析函数类型的 title
|
const exportColumns = resolveExportColumns(columns);
|
||||||
const exportColumns: ExportColumn[] = [];
|
const sheets = [
|
||||||
for (const col of columns) {
|
{
|
||||||
if (!col.dataIndex || col.dataIndex === 'action' || col.excludeFromExport) {
|
sheetName: fileName,
|
||||||
continue;
|
columns: exportColumns,
|
||||||
|
dataSource: data,
|
||||||
|
tbtitle,
|
||||||
|
tbsub,
|
||||||
|
appendTables: appendTables
|
||||||
|
.filter(table => Array.isArray(table.data) && table.data.length > 0)
|
||||||
|
.map(table => ({
|
||||||
|
data: table.data,
|
||||||
|
columns: resolveExportColumns(table.columns)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
const resolvedTitle = resolveColumnTitle(col);
|
];
|
||||||
// 如果没有解析出标题,跳过该列
|
|
||||||
if (!resolvedTitle) continue;
|
|
||||||
|
|
||||||
exportColumns.push({
|
|
||||||
...col,
|
|
||||||
title: resolvedTitle
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadExcel({
|
downloadExcel({
|
||||||
filename: fileName,
|
filename: fileName,
|
||||||
sheets: [
|
sheets,
|
||||||
{
|
|
||||||
sheetName: fileName,
|
|
||||||
columns: exportColumns,
|
|
||||||
dataSource: data,
|
|
||||||
tbtitle,
|
|
||||||
tbsub
|
|
||||||
}
|
|
||||||
],
|
|
||||||
callback
|
callback
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,14 @@
|
|||||||
:scrollX="tableScrollX"
|
:scrollX="tableScrollX"
|
||||||
:scrollY="tableScrollY"
|
:scrollY="tableScrollY"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
:search-params="{
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'orderIndex',
|
||||||
|
order: 'asc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"
|
||||||
:list-url="getApprovalStatusList"
|
:list-url="getApprovalStatusList"
|
||||||
>
|
>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<!-- 第一步:输入修改依据 -->
|
<!-- 第一步:输入修改依据 -->
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:open="dataSourceVisible"
|
v-model:open="dataSourceVisible"
|
||||||
title="删除电站"
|
:title="title"
|
||||||
ok-text="确定"
|
ok-text="确定"
|
||||||
cancel-text="取消"
|
cancel-text="取消"
|
||||||
@ok="handleDataSourceConfirm"
|
@ok="handleDataSourceConfirm"
|
||||||
@ -24,13 +24,14 @@
|
|||||||
ok-text="确认删除"
|
ok-text="确认删除"
|
||||||
cancel-text="取消"
|
cancel-text="取消"
|
||||||
:ok-button-props="{ danger: true }"
|
:ok-button-props="{ danger: true }"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
@ok="handleFinalDelete"
|
@ok="handleFinalDelete"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p style="color: red; font-weight: bold">请慎重操作!</p>
|
<p style="color: red; font-weight: bold">请慎重操作!</p>
|
||||||
<p>电站名称:{{ deleteRecord?.stnm }}</p>
|
<p>{{ label }}名称:{{ deleteRecord?.stnm }}</p>
|
||||||
<p>修改依据:{{ dataSource || '无' }}</p>
|
<p>修改依据:{{ dataSource || '无' }}</p>
|
||||||
<p>确定要删除该电站吗?</p>
|
<p>确定要删除该{{ label }}吗?</p>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
@ -40,10 +41,26 @@ import { ref } from 'vue';
|
|||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { deletePowerInfo } from '@/api/DataQueryMenuModule';
|
import { deletePowerInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
/** 自定义删除函数,接收 (record, reason) 参数 */
|
||||||
|
deleteFn?: (record: any, reason: string) => Promise<any>;
|
||||||
|
/** 弹窗标题 */
|
||||||
|
title?: string;
|
||||||
|
/** 显示标签(如"电站"、"数据") */
|
||||||
|
label?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
title: '删除电站',
|
||||||
|
label: '电站'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
const dataSourceVisible = ref(false);
|
const dataSourceVisible = ref(false);
|
||||||
const confirmVisible = ref(false);
|
const confirmVisible = ref(false);
|
||||||
|
const confirmLoading = ref(false);
|
||||||
const dataSource = ref('');
|
const dataSource = ref('');
|
||||||
const deleteRecord = ref<any>(null);
|
const deleteRecord = ref<any>(null);
|
||||||
const onSuccess = ref<Function>(() => {});
|
const onSuccess = ref<Function>(() => {});
|
||||||
@ -64,14 +81,21 @@ const handleDataSourceConfirm = () => {
|
|||||||
|
|
||||||
// 最终删除
|
// 最终删除
|
||||||
const handleFinalDelete = async () => {
|
const handleFinalDelete = async () => {
|
||||||
|
confirmLoading.value = true; // 开始加载
|
||||||
try {
|
try {
|
||||||
const params = {
|
let res: any;
|
||||||
ids: [deleteRecord.value.stcd],
|
if (props.deleteFn) {
|
||||||
source: dataSource.value
|
// 使用外部传入的删除函数
|
||||||
};
|
res = await props.deleteFn(deleteRecord.value, dataSource.value);
|
||||||
const res = await deletePowerInfo(params);
|
} else {
|
||||||
console.log(res);
|
// 默认使用电站删除接口
|
||||||
if (res?.code == 0) {
|
const params = {
|
||||||
|
ids: [deleteRecord.value.stcd],
|
||||||
|
source: dataSource.value
|
||||||
|
};
|
||||||
|
res = await deletePowerInfo(params);
|
||||||
|
}
|
||||||
|
if (res?.code == 0 || res?.success) {
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
confirmVisible.value = false;
|
confirmVisible.value = false;
|
||||||
onSuccess.value();
|
onSuccess.value();
|
||||||
@ -80,6 +104,8 @@ const handleFinalDelete = async () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败,请重试');
|
message.error('删除失败,请重试');
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false; // 结束加载
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -149,14 +149,6 @@ const tableScrollX = computed(() =>
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
const columns: any = [
|
const columns: any = [
|
||||||
{
|
|
||||||
key: 'index',
|
|
||||||
title: '序号',
|
|
||||||
dataIndex: 'index',
|
|
||||||
visible: true,
|
|
||||||
width: 80,
|
|
||||||
fixed: 'left'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
title: '中文名称',
|
title: '中文名称',
|
||||||
@ -165,6 +157,7 @@ const columns: any = [
|
|||||||
width: 150,
|
width: 150,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
|
sort: true,
|
||||||
customRender: ({ text }: any) => {
|
customRender: ({ text }: any) => {
|
||||||
return text
|
return text
|
||||||
? h('span', { style: { color: '#2f6b98', cursor: 'pointer' } }, text)
|
? h('span', { style: { color: '#2f6b98', cursor: 'pointer' } }, text)
|
||||||
@ -178,6 +171,7 @@ const columns: any = [
|
|||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
sort: true,
|
||||||
fixed: 'left'
|
fixed: 'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -187,6 +181,7 @@ const columns: any = [
|
|||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
sort: true,
|
||||||
fixed: 'left'
|
fixed: 'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -194,6 +189,7 @@ const columns: any = [
|
|||||||
title: '分类',
|
title: '分类',
|
||||||
dataIndex: 'typeName',
|
dataIndex: 'typeName',
|
||||||
visible: true,
|
visible: true,
|
||||||
|
sort: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
@ -202,6 +198,7 @@ const columns: any = [
|
|||||||
title: '种',
|
title: '种',
|
||||||
dataIndex: 'species',
|
dataIndex: 'species',
|
||||||
visible: true,
|
visible: true,
|
||||||
|
sort: true,
|
||||||
width: 150,
|
width: 150,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
@ -210,6 +207,7 @@ const columns: any = [
|
|||||||
title: '属',
|
title: '属',
|
||||||
dataIndex: 'genus',
|
dataIndex: 'genus',
|
||||||
visible: true,
|
visible: true,
|
||||||
|
sort: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
@ -218,6 +216,7 @@ const columns: any = [
|
|||||||
title: '科',
|
title: '科',
|
||||||
dataIndex: 'family',
|
dataIndex: 'family',
|
||||||
visible: true,
|
visible: true,
|
||||||
|
sort: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
@ -227,6 +226,7 @@ const columns: any = [
|
|||||||
dataIndex: 'orders',
|
dataIndex: 'orders',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 150,
|
width: 150,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
@ -244,6 +244,7 @@ const columns: any = [
|
|||||||
dataIndex: 'alias',
|
dataIndex: 'alias',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -252,6 +253,7 @@ const columns: any = [
|
|||||||
dataIndex: 'fsz',
|
dataIndex: 'fsz',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -260,6 +262,7 @@ const columns: any = [
|
|||||||
dataIndex: 'shapedesc',
|
dataIndex: 'shapedesc',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 300,
|
width: 300,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -268,6 +271,7 @@ const columns: any = [
|
|||||||
dataIndex: 'habitation',
|
dataIndex: 'habitation',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -276,6 +280,7 @@ const columns: any = [
|
|||||||
dataIndex: 'habitMigrat',
|
dataIndex: 'habitMigrat',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -284,6 +289,7 @@ const columns: any = [
|
|||||||
dataIndex: 'feedingHabit',
|
dataIndex: 'feedingHabit',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -292,6 +298,7 @@ const columns: any = [
|
|||||||
dataIndex: 'food',
|
dataIndex: 'food',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -300,6 +307,7 @@ const columns: any = [
|
|||||||
dataIndex: 'timeFeed',
|
dataIndex: 'timeFeed',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -308,6 +316,7 @@ const columns: any = [
|
|||||||
dataIndex: 'spawnMonth',
|
dataIndex: 'spawnMonth',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -316,6 +325,7 @@ const columns: any = [
|
|||||||
dataIndex: 'orignDate',
|
dataIndex: 'orignDate',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 300,
|
width: 300,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -324,6 +334,7 @@ const columns: any = [
|
|||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -332,6 +343,7 @@ const columns: any = [
|
|||||||
dataIndex: 'pretemp',
|
dataIndex: 'pretemp',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -340,6 +352,7 @@ const columns: any = [
|
|||||||
dataIndex: 'flowRate',
|
dataIndex: 'flowRate',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -348,6 +361,7 @@ const columns: any = [
|
|||||||
dataIndex: 'depth',
|
dataIndex: 'depth',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -356,6 +370,7 @@ const columns: any = [
|
|||||||
dataIndex: 'wqtq',
|
dataIndex: 'wqtq',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -364,6 +379,7 @@ const columns: any = [
|
|||||||
dataIndex: 'spawnCharact',
|
dataIndex: 'spawnCharact',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -372,6 +388,7 @@ const columns: any = [
|
|||||||
dataIndex: 'botmMater',
|
dataIndex: 'botmMater',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -380,6 +397,7 @@ const columns: any = [
|
|||||||
dataIndex: 'ptypeName',
|
dataIndex: 'ptypeName',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -388,6 +406,7 @@ const columns: any = [
|
|||||||
dataIndex: 'specOriginName',
|
dataIndex: 'specOriginName',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -396,6 +415,7 @@ const columns: any = [
|
|||||||
dataIndex: 'vlsr',
|
dataIndex: 'vlsr',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 150,
|
width: 150,
|
||||||
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
}
|
}
|
||||||
// {
|
// {
|
||||||
|
|||||||
@ -84,7 +84,7 @@ const props = defineProps<{
|
|||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
rstcd: null,
|
rstcd: null,
|
||||||
stcd: null,
|
stcd: '0086601020VP000006',
|
||||||
mway: '1'
|
mway: '1'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeDateList"
|
||||||
:key="item"
|
:key="item"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
@ -72,6 +72,27 @@ import BasicSearch from '@/components/BasicSearch/index.vue';
|
|||||||
import { DateSetting } from '@/utils/enumeration';
|
import { DateSetting } from '@/utils/enumeration';
|
||||||
import { useTimeScale } from '@/store/composables/useTimeScale';
|
import { useTimeScale } from '@/store/composables/useTimeScale';
|
||||||
|
|
||||||
|
const timeDateList = ref([
|
||||||
|
{
|
||||||
|
label: '一年',
|
||||||
|
value: [dayjs().startOf('year'), dayjs().endOf('year')] // 范围
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '两年',
|
||||||
|
value: [dayjs().subtract(1, 'year').startOf('year'), dayjs().endOf('year')] // 范围
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '三年',
|
||||||
|
value: [dayjs().subtract(2, 'year').startOf('year'), dayjs().endOf('year')] // 范围
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '五年',
|
||||||
|
value: [
|
||||||
|
dayjs().subtract(4, 'year').startOf('year'),
|
||||||
|
dayjs().startOf('year')
|
||||||
|
] // 范围
|
||||||
|
}
|
||||||
|
]);
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'export-btn'): void;
|
(e: 'export-btn'): void;
|
||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
|
|||||||
@ -18,12 +18,13 @@
|
|||||||
:showTime="timeShowTime"
|
:showTime="timeShowTime"
|
||||||
placeholder="起始时间"
|
placeholder="起始时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -43,12 +44,13 @@
|
|||||||
:disabledDate="disabledEndDate"
|
:disabledDate="disabledEndDate"
|
||||||
placeholder="结束时间"
|
placeholder="结束时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -68,7 +70,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import BasicSearch from '@/components/BasicSearch/index.vue';
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
import { DateSetting } from '@/utils/enumeration';
|
|
||||||
import { useTimeScale } from '@/store/composables/useTimeScale';
|
import { useTimeScale } from '@/store/composables/useTimeScale';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -76,7 +77,6 @@ const emit = defineEmits<{
|
|||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
rstcd: null,
|
rstcd: '008660306300000001',
|
||||||
basin: null,
|
basin: null,
|
||||||
ruleType: null,
|
ruleType: null,
|
||||||
timeScale: 'tm'
|
timeScale: 'tm'
|
||||||
@ -100,6 +100,7 @@ const {
|
|||||||
timePicker,
|
timePicker,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
timeShowTime,
|
timeShowTime,
|
||||||
|
timeShortcutList,
|
||||||
handleRangeClick,
|
handleRangeClick,
|
||||||
disabledEndDate,
|
disabledEndDate,
|
||||||
handleSearchFinish,
|
handleSearchFinish,
|
||||||
|
|||||||
@ -37,36 +37,29 @@ import {
|
|||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
||||||
|
|
||||||
const sort = ref<any>([
|
const currentSearchParams = ref<any>({});
|
||||||
{
|
const sort = computed(() => {
|
||||||
field: 'qecLimit',
|
return [
|
||||||
dir: 'asc'
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
},
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
{
|
{ field: 'siteStepSort', dir: 'asc' },
|
||||||
field: 'baseStepSort',
|
currentSearchParams.value.timeScale == 'tm'
|
||||||
dir: 'asc'
|
? { field: 'tm', dir: 'desc' }
|
||||||
},
|
: null,
|
||||||
{
|
currentSearchParams.value.timeScale == 'dt'
|
||||||
field: 'rvcdStepSort',
|
? { field: 'dt', dir: 'desc' }
|
||||||
dir: 'asc'
|
: null,
|
||||||
},
|
currentSearchParams.value.timeScale == 'month'
|
||||||
{
|
? { field: 'year', dir: 'desc' }
|
||||||
field: 'siteStepSort',
|
: null,
|
||||||
dir: 'asc'
|
currentSearchParams.value.timeScale == 'month'
|
||||||
},
|
? { field: 'month', dir: 'desc' }
|
||||||
{
|
: null
|
||||||
field: 'year',
|
].filter(Boolean);
|
||||||
dir: 'desc'
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'month',
|
|
||||||
dir: 'desc'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const searchRef = ref();
|
const searchRef = ref();
|
||||||
const tableScrollY = ref<string | number>(0);
|
const tableScrollY = ref<string | number>(0);
|
||||||
const currentSearchParams = ref<any>({});
|
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
|
|
||||||
// 根据 timeScale 动态切换 API
|
// 根据 timeScale 动态切换 API
|
||||||
@ -214,7 +207,7 @@ const ruleColumnsMap: any = {
|
|||||||
dataIndex: 'mwrSfdbName',
|
dataIndex: 'mwrSfdbName',
|
||||||
visible: true,
|
visible: true,
|
||||||
sort: true,
|
sort: true,
|
||||||
width: 170,
|
width: 180,
|
||||||
title: () =>
|
title: () =>
|
||||||
h('span', { style: 'display:flex;align-items:center;gap:4px' }, [
|
h('span', { style: 'display:flex;align-items:center;gap:4px' }, [
|
||||||
'水利部要求是否达标',
|
'水利部要求是否达标',
|
||||||
@ -256,7 +249,7 @@ const ruleColumnsMap: any = {
|
|||||||
dataIndex: 'avqLimit',
|
dataIndex: 'avqLimit',
|
||||||
visible: true,
|
visible: true,
|
||||||
sort: true,
|
sort: true,
|
||||||
width: 150
|
width: 160
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'avqSfdb',
|
key: 'avqSfdb',
|
||||||
@ -264,7 +257,7 @@ const ruleColumnsMap: any = {
|
|||||||
dataIndex: 'avqSfdbName',
|
dataIndex: 'avqSfdbName',
|
||||||
visible: true,
|
visible: true,
|
||||||
sort: true,
|
sort: true,
|
||||||
width: 210,
|
width: 230,
|
||||||
title: () =>
|
title: () =>
|
||||||
h('span', { style: 'display:flex;align-items:center;gap:4px' }, [
|
h('span', { style: 'display:flex;align-items:center;gap:4px' }, [
|
||||||
'多年平均流量*10%是否达标',
|
'多年平均流量*10%是否达标',
|
||||||
|
|||||||
@ -0,0 +1,327 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="编辑流量站数据"
|
||||||
|
width="60vw"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:label-col="{ span: 7 }"
|
||||||
|
:wrapper-col="{ span: 17 }"
|
||||||
|
>
|
||||||
|
<div class="form-scroll-area">
|
||||||
|
<div class="form-section">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="24"
|
||||||
|
><div class="form-group-title">基本信息</div></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="站点名称">
|
||||||
|
<a-input :value="recordData.stnm || '-'" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="时间">
|
||||||
|
<a-input :value="displayTime" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="所属基地">
|
||||||
|
<a-input :value="recordData.baseName || '-'" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="所属电站">
|
||||||
|
<a-input :value="recordData.ennm || '-'" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="24"
|
||||||
|
><div class="form-group-title">监测数据</div></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="水位(m)">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.z"
|
||||||
|
placeholder="请输入水位"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="流量(m³/s)">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.q"
|
||||||
|
placeholder="请输入流量"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="流速(m/s)">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.v"
|
||||||
|
placeholder="请输入流速"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:open="confirmModalVisible"
|
||||||
|
title="确认修改"
|
||||||
|
width="700px"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleConfirmSubmit"
|
||||||
|
@cancel="handleConfirmCancel"
|
||||||
|
>
|
||||||
|
<div style="max-height: 50vh; overflow-y: auto; margin-bottom: 16px">
|
||||||
|
<a-descriptions bordered :column="1" size="small">
|
||||||
|
<a-descriptions-item
|
||||||
|
v-for="item in changedFieldList"
|
||||||
|
:key="item.key"
|
||||||
|
:label="item.label"
|
||||||
|
>
|
||||||
|
<span style="color: #ff4d4f; text-decoration: line-through">{{
|
||||||
|
item.oldValue
|
||||||
|
}}</span>
|
||||||
|
<span style="margin: 0 8px">→</span>
|
||||||
|
<span style="color: #52c41a">{{ item.newValue }}</span>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
<a-form-item label="修改依据">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="sourceValue"
|
||||||
|
placeholder="请输入修改依据"
|
||||||
|
:rows="5"
|
||||||
|
:maxlength="500"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { updateFlowStationInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
record?: any;
|
||||||
|
timeScale?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:open', 'success']);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: val => emit('update:open', val)
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
const confirmModalVisible = ref(false);
|
||||||
|
const sourceValue = ref('');
|
||||||
|
const recordData = ref<any>({});
|
||||||
|
const formData = ref<{
|
||||||
|
z: number | null;
|
||||||
|
q: number | null;
|
||||||
|
v: number | null;
|
||||||
|
}>({
|
||||||
|
z: null,
|
||||||
|
q: null,
|
||||||
|
v: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizeValue = (value: any) => {
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
return Number(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatValue = (value: any) => {
|
||||||
|
const normalized = normalizeValue(value);
|
||||||
|
return normalized === null ? '空' : String(normalized);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldLabelMap: Record<string, string> = {
|
||||||
|
z: '水位(m)',
|
||||||
|
q: '流量(m³/s)',
|
||||||
|
v: '流速(m/s)'
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayTime = computed(() => {
|
||||||
|
const timeScale = props.timeScale;
|
||||||
|
if (timeScale === 'month') {
|
||||||
|
const year = recordData.value?.year ?? '-';
|
||||||
|
const month = recordData.value?.month ?? '-';
|
||||||
|
return `${year}-${String(month).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeValue = recordData.value?.dt ?? recordData.value?.tm;
|
||||||
|
if (!timeValue) return '-';
|
||||||
|
if (timeScale === 'dt') {
|
||||||
|
return dayjs(timeValue).format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
return dayjs(timeValue).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
});
|
||||||
|
|
||||||
|
const changedFieldList = computed(() => {
|
||||||
|
return (['z', 'q', 'v'] as const)
|
||||||
|
.map(key => {
|
||||||
|
const oldValue = normalizeValue(recordData.value?.[key]);
|
||||||
|
const newValue = normalizeValue(formData.value[key]);
|
||||||
|
if (oldValue === newValue) return null;
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label: fieldLabelMap[key],
|
||||||
|
oldValue: formatValue(oldValue),
|
||||||
|
newValue: formatValue(newValue)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean) as {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
oldValue: string;
|
||||||
|
newValue: string;
|
||||||
|
}[];
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.record,
|
||||||
|
newRecord => {
|
||||||
|
if (!newRecord) {
|
||||||
|
recordData.value = {};
|
||||||
|
formData.value = {
|
||||||
|
z: null,
|
||||||
|
q: null,
|
||||||
|
v: null
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordData.value = JSON.parse(JSON.stringify(newRecord));
|
||||||
|
formData.value = {
|
||||||
|
z: normalizeValue(newRecord.z),
|
||||||
|
q: normalizeValue(newRecord.q),
|
||||||
|
v: normalizeValue(newRecord.v)
|
||||||
|
};
|
||||||
|
sourceValue.value = '';
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
if (changedFieldList.value.length === 0) {
|
||||||
|
message.info('未检测到任何修改');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceValue.value = '';
|
||||||
|
confirmModalVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSubmit = async () => {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
const updateData = {
|
||||||
|
stcd: recordData.value?.stcd,
|
||||||
|
tm: recordData.value?.tm,
|
||||||
|
stnm: recordData.value?.stnm,
|
||||||
|
z:
|
||||||
|
formData.value.z === null || formData.value.z === undefined
|
||||||
|
? ''
|
||||||
|
: String(formData.value.z),
|
||||||
|
q:
|
||||||
|
formData.value.q === null || formData.value.q === undefined
|
||||||
|
? ''
|
||||||
|
: String(formData.value.q),
|
||||||
|
v:
|
||||||
|
formData.value.v === null || formData.value.v === undefined
|
||||||
|
? ''
|
||||||
|
: String(formData.value.v)
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await updateFlowStationInfo({
|
||||||
|
updateData,
|
||||||
|
source: sourceValue.value.trim()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.code == 0 || res?.success) {
|
||||||
|
message.success('编辑成功');
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
visible.value = false;
|
||||||
|
emit('success');
|
||||||
|
} else {
|
||||||
|
message.error(res?.msg || '编辑失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error);
|
||||||
|
message.error('提交失败,请重试');
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmCancel = () => {
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
sourceValue.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
sourceValue.value = '';
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.form-scroll-area {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
padding: 4px 4px 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section + .form-section {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
padding: 8px 0 12px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -18,12 +18,13 @@
|
|||||||
:showTime="timeShowTime"
|
:showTime="timeShowTime"
|
||||||
placeholder="起始时间"
|
placeholder="起始时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -43,12 +44,13 @@
|
|||||||
:disabledDate="disabledEndDate"
|
:disabledDate="disabledEndDate"
|
||||||
placeholder="结束时间"
|
placeholder="结束时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -68,7 +70,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import BasicSearch from '@/components/BasicSearch/index.vue';
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
import { DateSetting } from '@/utils/enumeration';
|
|
||||||
import { useTimeScale } from '@/store/composables/useTimeScale';
|
import { useTimeScale } from '@/store/composables/useTimeScale';
|
||||||
import { getFlowStationSectionList } from '@/api/DataQueryMenuModule';
|
import { getFlowStationSectionList } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ const props = defineProps<{
|
|||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
rstcd: null,
|
rstcd: null,
|
||||||
stcd: null,
|
stcd: '008640203500001067',
|
||||||
timeScale: 'tm'
|
timeScale: 'tm'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ const {
|
|||||||
timePicker,
|
timePicker,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
timeShowTime,
|
timeShowTime,
|
||||||
|
timeShortcutList,
|
||||||
handleRangeClick,
|
handleRangeClick,
|
||||||
disabledEndDate,
|
disabledEndDate,
|
||||||
handleSearchFinish,
|
handleSearchFinish,
|
||||||
|
|||||||
@ -19,59 +19,99 @@
|
|||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="text-[#2f6b98]"
|
||||||
|
size="small"
|
||||||
|
@click="handleEdit(record)"
|
||||||
|
>编辑</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
@click="handleDelete(record)"
|
||||||
|
>删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
|
|
||||||
|
<EditFlowStationModal
|
||||||
|
v-model:open="editVisible"
|
||||||
|
:record="editRecord"
|
||||||
|
:time-scale="currentSearchParams.timeScale"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeleteConfirmModal
|
||||||
|
ref="deleteModalRef"
|
||||||
|
:delete-fn="handleDeleteFn"
|
||||||
|
title="删除流量站数据"
|
||||||
|
label="数据"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
import { ref, computed, onMounted, nextTick } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import FlowStationSearch from './FlowStationSearch.vue';
|
import FlowStationSearch from './FlowStationSearch.vue';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import EditFlowStationModal from './EditFlowStationModal.vue';
|
||||||
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import {
|
import {
|
||||||
getFlowStationList,
|
getFlowStationList,
|
||||||
getFlowStationDayList,
|
getFlowStationDayList,
|
||||||
getFlowStationMonthList
|
getFlowStationMonthList,
|
||||||
|
deleteFlowStationInfo
|
||||||
} from '@/api/DataQueryMenuModule';
|
} from '@/api/DataQueryMenuModule';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
||||||
|
|
||||||
const sort = ref<any>([
|
const sort = computed(() => {
|
||||||
{
|
// 预定义排序数组
|
||||||
field: 'qecLimit',
|
const sortMap = {
|
||||||
dir: 'asc'
|
tm: [
|
||||||
},
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
{
|
{ field: 'rvcd', dir: 'asc' },
|
||||||
field: 'baseStepSort',
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
dir: 'asc'
|
{ field: 'tm', dir: 'desc' }
|
||||||
},
|
],
|
||||||
{
|
dt: [
|
||||||
field: 'rvcdStepSort',
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
dir: 'asc'
|
{ field: 'rvcd', dir: 'asc' },
|
||||||
},
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
{
|
{ field: 'stnm', dir: 'asc' },
|
||||||
field: 'siteStepSort',
|
{ field: 'dt', dir: 'desc' }
|
||||||
dir: 'asc'
|
],
|
||||||
},
|
month: [
|
||||||
{
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
field: 'year',
|
{ field: 'rvcd', dir: 'asc' },
|
||||||
dir: 'desc'
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
},
|
{ field: 'stnm', dir: 'asc' },
|
||||||
{
|
{ field: 'year', dir: 'desc' },
|
||||||
field: 'month',
|
{ field: 'month', dir: 'desc' }
|
||||||
dir: 'desc'
|
]
|
||||||
}
|
};
|
||||||
]);
|
|
||||||
|
// 取值(若 timeScale 不在映射中,返回空数组)
|
||||||
|
return sortMap[currentSearchParams.value.timeScale] || [];
|
||||||
|
});
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const searchRef = ref();
|
const searchRef = ref();
|
||||||
const tableScrollY = ref<string | number>(0);
|
const tableScrollY = ref<string | number>(0);
|
||||||
const currentSearchParams = ref<any>({});
|
const currentSearchParams = ref<any>({});
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const editRecord = ref<any>(null);
|
||||||
|
const deleteModalRef = ref();
|
||||||
|
|
||||||
// 根据 timeScale 动态切换 API
|
// 根据 timeScale 动态切换 API
|
||||||
const currentListUrl = computed(() => {
|
const currentListUrl = computed(() => {
|
||||||
const timeScale = currentSearchParams.value.timeScale;
|
const timeScale = currentSearchParams.value.timeScale;
|
||||||
console.log('timeScale:', timeScale);
|
|
||||||
if (timeScale === 'dt') return getFlowStationDayList;
|
if (timeScale === 'dt') return getFlowStationDayList;
|
||||||
if (timeScale === 'month') return getFlowStationMonthList;
|
if (timeScale === 'month') return getFlowStationMonthList;
|
||||||
return getFlowStationList; // 默认 tm(小时)
|
return getFlowStationList; // 默认 tm(小时)
|
||||||
@ -121,6 +161,7 @@ const allColumns: any[] = [
|
|||||||
title: '水位(m)',
|
title: '水位(m)',
|
||||||
dataIndex: 'z',
|
dataIndex: 'z',
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
customRender: ({ record }: any) => record.z?.toFixed(2) || '-'
|
customRender: ({ record }: any) => record.z?.toFixed(2) || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -128,6 +169,7 @@ const allColumns: any[] = [
|
|||||||
title: '流量(m³/s)',
|
title: '流量(m³/s)',
|
||||||
dataIndex: 'q',
|
dataIndex: 'q',
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
customRender: ({ record }: any) => record.q?.toFixed(2) || '-'
|
customRender: ({ record }: any) => record.q?.toFixed(2) || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -135,6 +177,7 @@ const allColumns: any[] = [
|
|||||||
title: '流速(m³/s)',
|
title: '流速(m³/s)',
|
||||||
dataIndex: 'v',
|
dataIndex: 'v',
|
||||||
width: 100,
|
width: 100,
|
||||||
|
sort: true,
|
||||||
customRender: ({ record }: any) => record.v?.toFixed(2) || '-'
|
customRender: ({ record }: any) => record.v?.toFixed(2) || '-'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -169,10 +212,19 @@ const timeColumn = computed(() => ({
|
|||||||
|
|
||||||
// 动态列(响应式)
|
// 动态列(响应式)
|
||||||
const Columns = computed(() => {
|
const Columns = computed(() => {
|
||||||
const result = [...allColumns];
|
const result = allColumns.map(col => ({ ...col }));
|
||||||
// 在基地列之后插入时间列
|
// 在基地列之后插入时间列
|
||||||
const baseIndex = result.findIndex(c => c.key === 'stlc');
|
const baseIndex = result.findIndex(c => c.key === 'ennm');
|
||||||
result.splice(baseIndex + 1, 0, timeColumn.value);
|
result.splice(baseIndex + 1, 0, timeColumn.value);
|
||||||
|
if (currentSearchParams.value.timeScale === 'tm') {
|
||||||
|
result.push({
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120
|
||||||
|
});
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -205,8 +257,35 @@ const exportBtn = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEdit = (record: any) => {
|
||||||
|
editRecord.value = { ...record };
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
return deleteFlowStationInfo({
|
||||||
|
dataList: [
|
||||||
|
{
|
||||||
|
id: record.stcd,
|
||||||
|
tm: record.tm
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataType: 'TIME',
|
||||||
|
source: reason
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
deleteModalRef.value?.open(record, () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
const initTable = (values: any) => {
|
const initTable = (values: any) => {
|
||||||
console.log(values);
|
|
||||||
const filters = [
|
const filters = [
|
||||||
values.rvcd && values.rvcd !== 'all'
|
values.rvcd && values.rvcd !== 'all'
|
||||||
? {
|
? {
|
||||||
|
|||||||
@ -0,0 +1,252 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="编辑表层水温数据"
|
||||||
|
width="60vw"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:label-col="{ span: 7 }"
|
||||||
|
:wrapper-col="{ span: 17 }"
|
||||||
|
>
|
||||||
|
<div class="form-scroll-area">
|
||||||
|
<div class="form-section">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="24"
|
||||||
|
><div class="form-group-title">基本信息</div></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="断面名称">
|
||||||
|
<a-input :value="recordData.stnm || '-'" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="时间">
|
||||||
|
<a-input :value="displayTime" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="24"
|
||||||
|
><div class="form-group-title">水温数据</div></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="水温(℃)">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.wt"
|
||||||
|
placeholder="请输入水温"
|
||||||
|
:precision="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:open="confirmModalVisible"
|
||||||
|
title="确认修改"
|
||||||
|
width="700px"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleConfirmSubmit"
|
||||||
|
@cancel="handleConfirmCancel"
|
||||||
|
>
|
||||||
|
<div style="max-height: 50vh; overflow-y: auto; margin-bottom: 16px">
|
||||||
|
<a-descriptions bordered :column="1" size="small">
|
||||||
|
<a-descriptions-item label="水温(℃)">
|
||||||
|
<span style="color: #ff4d4f; text-decoration: line-through">{{
|
||||||
|
originalWtText
|
||||||
|
}}</span>
|
||||||
|
<span style="margin: 0 8px">→</span>
|
||||||
|
<span style="color: #52c41a">{{ currentWtText }}</span>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
<a-form-item label="修改依据">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="sourceValue"
|
||||||
|
placeholder="请输入修改依据"
|
||||||
|
:rows="5"
|
||||||
|
:maxlength="500"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { updateSurfaceTempInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
record?: any;
|
||||||
|
timeScale?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:open', 'success']);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: val => emit('update:open', val)
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
const confirmModalVisible = ref(false);
|
||||||
|
const sourceValue = ref('');
|
||||||
|
const recordData = ref<any>({});
|
||||||
|
const formData = ref<{
|
||||||
|
wt: number | null;
|
||||||
|
}>({
|
||||||
|
wt: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizeValue = (value: any) => {
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
return Number(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatValue = (value: any) => {
|
||||||
|
const normalized = normalizeValue(value);
|
||||||
|
return normalized === null ? '空' : String(normalized);
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayTime = computed(() => {
|
||||||
|
const timeScale = props.timeScale;
|
||||||
|
if (timeScale === 'month') {
|
||||||
|
const year = recordData.value?.year ?? '-';
|
||||||
|
const month = recordData.value?.month ?? '-';
|
||||||
|
return `${year}-${String(month).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeValue = recordData.value?.dt ?? recordData.value?.tm;
|
||||||
|
if (!timeValue) return '-';
|
||||||
|
if (timeScale === 'dt') {
|
||||||
|
return dayjs(timeValue).format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
return dayjs(timeValue).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalWt = computed(() => normalizeValue(recordData.value?.wt));
|
||||||
|
const originalWtText = computed(() => formatValue(originalWt.value));
|
||||||
|
const currentWtText = computed(() => formatValue(formData.value.wt));
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.record,
|
||||||
|
newRecord => {
|
||||||
|
if (!newRecord) {
|
||||||
|
recordData.value = {};
|
||||||
|
formData.value = { wt: null };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordData.value = JSON.parse(JSON.stringify(newRecord));
|
||||||
|
formData.value = {
|
||||||
|
wt: normalizeValue(newRecord.wt)
|
||||||
|
};
|
||||||
|
sourceValue.value = '';
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
if (originalWt.value === formData.value.wt) {
|
||||||
|
message.info('未检测到任何修改');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceValue.value = '';
|
||||||
|
confirmModalVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSubmit = async () => {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
const updateData = {
|
||||||
|
stcd: recordData.value?.stcd,
|
||||||
|
dt: recordData.value?.tm,
|
||||||
|
stnm: recordData.value?.stnm,
|
||||||
|
wt:
|
||||||
|
formData.value.wt === null || formData.value.wt === undefined
|
||||||
|
? ''
|
||||||
|
: String(formData.value.wt)
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await updateSurfaceTempInfo({
|
||||||
|
updateData,
|
||||||
|
source: sourceValue.value.trim()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.code == 0 || res?.success) {
|
||||||
|
message.success('编辑成功');
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
visible.value = false;
|
||||||
|
emit('success');
|
||||||
|
} else {
|
||||||
|
message.error(res?.msg || '编辑失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error);
|
||||||
|
message.error('提交失败,请重试');
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmCancel = () => {
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
sourceValue.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
sourceValue.value = '';
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.form-scroll-area {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
padding: 4px 4px 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section + .form-section {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
padding: 8px 0 12px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -18,12 +18,13 @@
|
|||||||
:showTime="timeShowTime"
|
:showTime="timeShowTime"
|
||||||
placeholder="起始时间"
|
placeholder="起始时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -43,12 +44,13 @@
|
|||||||
:disabledDate="disabledEndDate"
|
:disabledDate="disabledEndDate"
|
||||||
placeholder="结束时间"
|
placeholder="结束时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -68,7 +70,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import BasicSearch from '@/components/BasicSearch/index.vue';
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
import { DateSetting } from '@/utils/enumeration';
|
|
||||||
import { useTimeScale } from '@/store/composables/useTimeScale';
|
import { useTimeScale } from '@/store/composables/useTimeScale';
|
||||||
import { getSurfaceTempSectionList } from '@/api/DataQueryMenuModule';
|
import { getSurfaceTempSectionList } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ const props = defineProps<{
|
|||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
rstcd: null,
|
rstcd: null,
|
||||||
stcd: null,
|
stcd: '008640202300001013',
|
||||||
timeScale: 'tm'
|
timeScale: 'tm'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ const {
|
|||||||
timePicker,
|
timePicker,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
timeShowTime,
|
timeShowTime,
|
||||||
|
timeShortcutList,
|
||||||
handleRangeClick,
|
handleRangeClick,
|
||||||
disabledEndDate,
|
disabledEndDate,
|
||||||
handleSearchFinish,
|
handleSearchFinish,
|
||||||
|
|||||||
@ -19,59 +19,97 @@
|
|||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="text-[#2f6b98]"
|
||||||
|
size="small"
|
||||||
|
@click="handleEdit(record)"
|
||||||
|
>编辑</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
@click="handleDelete(record)"
|
||||||
|
>删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
|
|
||||||
|
<EditSurfaceTempModal
|
||||||
|
v-model:open="editVisible"
|
||||||
|
:record="editRecord"
|
||||||
|
:time-scale="currentSearchParams.timeScale"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeleteConfirmModal
|
||||||
|
ref="deleteModalRef"
|
||||||
|
:delete-fn="handleDeleteFn"
|
||||||
|
title="删除表层水温数据"
|
||||||
|
label="数据"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
import { ref, computed, onMounted, nextTick } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import SurfaceTempSearch from './SurfaceTempSearch.vue';
|
import SurfaceTempSearch from './SurfaceTempSearch.vue';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import EditSurfaceTempModal from './EditSurfaceTempModal.vue';
|
||||||
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import {
|
import {
|
||||||
getSurfaceTempList,
|
getSurfaceTempList,
|
||||||
getSurfaceTempDayList,
|
getSurfaceTempDayList,
|
||||||
getSurfaceTempMonthList
|
getSurfaceTempMonthList,
|
||||||
|
deleteSurfaceTempInfo
|
||||||
} from '@/api/DataQueryMenuModule';
|
} from '@/api/DataQueryMenuModule';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
||||||
|
|
||||||
const sort = ref<any>([
|
const sort = computed(() => {
|
||||||
{
|
// 预定义排序数组
|
||||||
field: 'qecLimit',
|
const sortMap = {
|
||||||
dir: 'asc'
|
tm: [
|
||||||
},
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
{
|
{ field: 'rvcd', dir: 'asc' },
|
||||||
field: 'baseStepSort',
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
dir: 'asc'
|
{ field: 'ennm', dir: 'asc' },
|
||||||
},
|
{ field: 'tm', dir: 'desc' }
|
||||||
{
|
],
|
||||||
field: 'rvcdStepSort',
|
dt: [
|
||||||
dir: 'asc'
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
},
|
{ field: 'rvcd', dir: 'asc' },
|
||||||
{
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
field: 'siteStepSort',
|
{ field: 'stnm', dir: 'asc' },
|
||||||
dir: 'asc'
|
{ field: 'dt', dir: 'desc' }
|
||||||
},
|
],
|
||||||
{
|
month: [
|
||||||
field: 'year',
|
{ field: 'stnm', dir: 'desc' },
|
||||||
dir: 'desc'
|
{ field: 'year', dir: 'desc' },
|
||||||
},
|
{ field: 'month', dir: 'desc' }
|
||||||
{
|
]
|
||||||
field: 'month',
|
};
|
||||||
dir: 'desc'
|
|
||||||
}
|
// 取值(若 timeScale 不在映射中,返回空数组)
|
||||||
]);
|
return sortMap[currentSearchParams.value.timeScale] || [];
|
||||||
|
});
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const searchRef = ref();
|
const searchRef = ref();
|
||||||
const tableScrollY = ref<string | number>(0);
|
const tableScrollY = ref<string | number>(0);
|
||||||
const currentSearchParams = ref<any>({});
|
const currentSearchParams = ref<any>({});
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const editRecord = ref<any>(null);
|
||||||
|
const deleteModalRef = ref();
|
||||||
|
|
||||||
// 根据 timeScale 动态切换 API
|
// 根据 timeScale 动态切换 API
|
||||||
const currentListUrl = computed(() => {
|
const currentListUrl = computed(() => {
|
||||||
const timeScale = currentSearchParams.value.timeScale;
|
const timeScale = currentSearchParams.value.timeScale;
|
||||||
console.log('timeScale:', timeScale);
|
|
||||||
if (timeScale === 'dt') return getSurfaceTempDayList;
|
if (timeScale === 'dt') return getSurfaceTempDayList;
|
||||||
if (timeScale === 'month') return getSurfaceTempMonthList;
|
if (timeScale === 'month') return getSurfaceTempMonthList;
|
||||||
return getSurfaceTempList; // 默认 tm(小时)
|
return getSurfaceTempList; // 默认 tm(小时)
|
||||||
@ -94,7 +132,6 @@ const allColumns: any[] = [
|
|||||||
title: '断面名称',
|
title: '断面名称',
|
||||||
dataIndex: 'stnm',
|
dataIndex: 'stnm',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
|
||||||
sort: true,
|
sort: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
@ -103,7 +140,6 @@ const allColumns: any[] = [
|
|||||||
title: '水温(℃)',
|
title: '水温(℃)',
|
||||||
dataIndex: 'wt',
|
dataIndex: 'wt',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 200,
|
|
||||||
sort: true,
|
sort: true,
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
customRender: ({ record }: any) => record.wt?.toFixed(1) || '-',
|
customRender: ({ record }: any) => record.wt?.toFixed(1) || '-',
|
||||||
@ -122,7 +158,6 @@ const timeColumn = computed(() => ({
|
|||||||
},
|
},
|
||||||
dataIndex: 'tm',
|
dataIndex: 'tm',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 150,
|
|
||||||
sort: true,
|
sort: true,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
customRender: ({ record }: any) => {
|
customRender: ({ record }: any) => {
|
||||||
@ -141,10 +176,19 @@ const timeColumn = computed(() => ({
|
|||||||
|
|
||||||
// 动态列(响应式)
|
// 动态列(响应式)
|
||||||
const Columns = computed(() => {
|
const Columns = computed(() => {
|
||||||
const result = [...allColumns];
|
const result = allColumns.map(col => ({ ...col }));
|
||||||
// 在基地列之后插入时间列
|
// 在基地列之后插入时间列
|
||||||
const baseIndex = result.findIndex(c => c.key === 'stnm');
|
const baseIndex = result.findIndex(c => c.key === 'stnm');
|
||||||
result.splice(baseIndex + 1, 0, timeColumn.value);
|
result.splice(baseIndex + 1, 0, timeColumn.value);
|
||||||
|
if (currentSearchParams.value.timeScale === 'tm') {
|
||||||
|
result.push({
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120
|
||||||
|
});
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -177,8 +221,35 @@ const exportBtn = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEdit = (record: any) => {
|
||||||
|
editRecord.value = { ...record };
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
return deleteSurfaceTempInfo({
|
||||||
|
dataList: [
|
||||||
|
{
|
||||||
|
id: record.stcd,
|
||||||
|
dt: record.tm
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataType: 'TIME',
|
||||||
|
source: reason
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
deleteModalRef.value?.open(record, () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
const initTable = (values: any) => {
|
const initTable = (values: any) => {
|
||||||
console.log(values);
|
|
||||||
const filters = [
|
const filters = [
|
||||||
values.rvcd && values.rvcd !== 'all'
|
values.rvcd && values.rvcd !== 'all'
|
||||||
? {
|
? {
|
||||||
|
|||||||
@ -0,0 +1,288 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="编辑垂向水温数据"
|
||||||
|
width="60vw"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:label-col="{ span: 7 }"
|
||||||
|
:wrapper-col="{ span: 17 }"
|
||||||
|
>
|
||||||
|
<div class="form-scroll-area">
|
||||||
|
<div class="form-section">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="24"
|
||||||
|
><div class="form-group-title">基本信息</div></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="测站名称">
|
||||||
|
<a-input :value="recordData.stnm || '-'" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="时间">
|
||||||
|
<a-input :value="displayTime" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="24"
|
||||||
|
><div class="form-group-title">水深数据</div></a-col
|
||||||
|
>
|
||||||
|
<a-col v-for="item in depthFieldList" :key="item.key" :span="12">
|
||||||
|
<a-form-item :label="`水深${item.key}m(℃)`">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.dataList[item.key]"
|
||||||
|
placeholder="请输入水温"
|
||||||
|
:precision="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:open="confirmModalVisible"
|
||||||
|
title="确认修改"
|
||||||
|
width="700px"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleConfirmSubmit"
|
||||||
|
@cancel="handleConfirmCancel"
|
||||||
|
>
|
||||||
|
<div style="max-height: 50vh; overflow-y: auto; margin-bottom: 16px">
|
||||||
|
<a-descriptions bordered :column="1" size="small">
|
||||||
|
<a-descriptions-item
|
||||||
|
v-for="item in changedDepthList"
|
||||||
|
:key="item.key"
|
||||||
|
:label="`水深${item.key}m(℃)`"
|
||||||
|
>
|
||||||
|
<span style="color: #ff4d4f; text-decoration: line-through">{{
|
||||||
|
item.oldValue
|
||||||
|
}}</span>
|
||||||
|
<span style="margin: 0 8px">→</span>
|
||||||
|
<span style="color: #52c41a">{{ item.newValue }}</span>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
<a-form-item label="修改依据">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="sourceValue"
|
||||||
|
placeholder="请输入修改依据"
|
||||||
|
:rows="5"
|
||||||
|
:maxlength="500"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { updateVerticalInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
record?: any;
|
||||||
|
timeScale?: string;
|
||||||
|
visibleDepths?: string[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:open', 'success']);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: val => emit('update:open', val)
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
const confirmModalVisible = ref(false);
|
||||||
|
const sourceValue = ref('');
|
||||||
|
const recordData = ref<any>({});
|
||||||
|
const formData = ref<{
|
||||||
|
dataList: Record<string, number | null>;
|
||||||
|
}>({
|
||||||
|
dataList: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const depthFieldList = computed(() => {
|
||||||
|
const dataList = recordData.value?.dataList || {};
|
||||||
|
const visibleDepths = props.visibleDepths || [];
|
||||||
|
return Object.keys(dataList)
|
||||||
|
.filter(key => visibleDepths.includes(key))
|
||||||
|
.sort((a, b) => Number(a) - Number(b))
|
||||||
|
.map(key => ({
|
||||||
|
key
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayTime = computed(() => {
|
||||||
|
const timeValue = recordData.value?.dt ?? recordData.value?.tm;
|
||||||
|
if (!timeValue) return '-';
|
||||||
|
if (props.timeScale === 'month') {
|
||||||
|
return dayjs(timeValue).format('YYYY-MM');
|
||||||
|
}
|
||||||
|
if (props.timeScale === 'dt') {
|
||||||
|
return dayjs(timeValue).format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
return dayjs(timeValue).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizeValue = (value: any) => {
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
return Number(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatValue = (value: any) => {
|
||||||
|
const normalized = normalizeValue(value);
|
||||||
|
return normalized === null ? '空' : String(normalized);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changedDepthList = computed(() => {
|
||||||
|
return depthFieldList.value
|
||||||
|
.map(item => {
|
||||||
|
const oldValue = normalizeValue(recordData.value?.dataList?.[item.key]);
|
||||||
|
const newValue = normalizeValue(formData.value.dataList?.[item.key]);
|
||||||
|
if (oldValue === newValue) return null;
|
||||||
|
return {
|
||||||
|
key: item.key,
|
||||||
|
oldValue: formatValue(oldValue),
|
||||||
|
newValue: formatValue(newValue)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean) as { key: string; oldValue: string; newValue: string }[];
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.record,
|
||||||
|
newRecord => {
|
||||||
|
if (!newRecord) {
|
||||||
|
recordData.value = {};
|
||||||
|
formData.value = {
|
||||||
|
dataList: {}
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordData.value = JSON.parse(JSON.stringify(newRecord));
|
||||||
|
const dataList = recordData.value?.dataList || {};
|
||||||
|
const nextDataList: Record<string, number | null> = {};
|
||||||
|
Object.keys(dataList)
|
||||||
|
.sort((a, b) => Number(a) - Number(b))
|
||||||
|
.forEach(key => {
|
||||||
|
nextDataList[key] = normalizeValue(dataList[key]);
|
||||||
|
});
|
||||||
|
formData.value = {
|
||||||
|
dataList: nextDataList
|
||||||
|
};
|
||||||
|
sourceValue.value = '';
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
if (changedDepthList.value.length === 0) {
|
||||||
|
message.info('未检测到任何修改');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceValue.value = '';
|
||||||
|
confirmModalVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSubmit = async () => {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
const updateData = {
|
||||||
|
stcd: recordData.value?.stcd,
|
||||||
|
dt: recordData.value?.dt,
|
||||||
|
stnm: recordData.value?.stnm,
|
||||||
|
map: depthFieldList.value.reduce((result, item) => {
|
||||||
|
const value = formData.value.dataList?.[item.key];
|
||||||
|
result[item.key] =
|
||||||
|
value === null || value === undefined || value === ''
|
||||||
|
? ''
|
||||||
|
: String(value);
|
||||||
|
return result;
|
||||||
|
}, {} as Record<string, string>)
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await updateVerticalInfo({
|
||||||
|
updateData,
|
||||||
|
source: sourceValue.value.trim()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.code == 0 || res?.success) {
|
||||||
|
message.success('编辑成功');
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
visible.value = false;
|
||||||
|
emit('success');
|
||||||
|
} else {
|
||||||
|
message.error(res?.msg || '编辑失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error);
|
||||||
|
message.error('提交失败,请重试');
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmCancel = () => {
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
sourceValue.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
sourceValue.value = '';
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.form-scroll-area {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
padding: 0 4px 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section + .form-section {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
padding: 8px 0 12px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -18,12 +18,13 @@
|
|||||||
:showTime="timeShowTime"
|
:showTime="timeShowTime"
|
||||||
placeholder="起始时间"
|
placeholder="起始时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -43,12 +44,13 @@
|
|||||||
:disabledDate="disabledEndDate"
|
:disabledDate="disabledEndDate"
|
||||||
placeholder="结束时间"
|
placeholder="结束时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -68,7 +70,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import BasicSearch from '@/components/BasicSearch/index.vue';
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
import { DateSetting } from '@/utils/enumeration';
|
|
||||||
import { useTimeScale } from '@/store/composables/useTimeScale';
|
import { useTimeScale } from '@/store/composables/useTimeScale';
|
||||||
import { getSurfaceTempSectionList } from '@/api/DataQueryMenuModule';
|
import { getSurfaceTempSectionList } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ const props = defineProps<{
|
|||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
rstcd: null,
|
rstcd: null,
|
||||||
stcd: null,
|
stcd: '008640202300001012',
|
||||||
timeScale: 'tm'
|
timeScale: 'tm'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ const {
|
|||||||
timePicker,
|
timePicker,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
timeShowTime,
|
timeShowTime,
|
||||||
|
timeShortcutList,
|
||||||
handleRangeClick,
|
handleRangeClick,
|
||||||
disabledEndDate,
|
disabledEndDate,
|
||||||
handleSearchFinish,
|
handleSearchFinish,
|
||||||
|
|||||||
@ -58,9 +58,44 @@
|
|||||||
:enable-row-highlight="false"
|
:enable-row-highlight="false"
|
||||||
:min-selection-count="1"
|
:min-selection-count="1"
|
||||||
@selection-change="handleCxswSelectionChange"
|
@selection-change="handleCxswSelectionChange"
|
||||||
/>
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="text-[#2f6b98]"
|
||||||
|
size="small"
|
||||||
|
@click="handleEdit(record)"
|
||||||
|
>编辑</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
@click="handleDelete(record)"
|
||||||
|
>删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<EditVerticalTempModal
|
||||||
|
v-model:open="editVisible"
|
||||||
|
:record="editRecord"
|
||||||
|
:time-scale="currentSearchParams.timeScale"
|
||||||
|
:visible-depths="selectedColumns"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeleteConfirmModal
|
||||||
|
ref="deleteModalRef"
|
||||||
|
:delete-fn="handleDeleteFn"
|
||||||
|
title="删除垂向水温数据"
|
||||||
|
label="数据"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -76,9 +111,10 @@ import {
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import VerticalTempSearch from './VerticalTempSearch.vue';
|
import VerticalTempSearch from './VerticalTempSearch.vue';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import EditVerticalTempModal from './EditVerticalTempModal.vue';
|
||||||
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import { getVerticalList } from '@/api/DataQueryMenuModule';
|
import { getVerticalList, deleteVerticalInfo } from '@/api/DataQueryMenuModule';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
|
|
||||||
const sort = ref<any>([
|
const sort = ref<any>([
|
||||||
@ -93,6 +129,9 @@ const tableScrollY = ref<string | number>(0);
|
|||||||
const currentSearchParams = ref<any>({});
|
const currentSearchParams = ref<any>({});
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
const tableData = ref<any[]>([]);
|
const tableData = ref<any[]>([]);
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const editRecord = ref<any>(null);
|
||||||
|
const deleteModalRef = ref();
|
||||||
|
|
||||||
// 图表相关
|
// 图表相关
|
||||||
const chartRef = ref<HTMLElement>();
|
const chartRef = ref<HTMLElement>();
|
||||||
@ -154,7 +193,7 @@ const tableColumns = computed(() => {
|
|||||||
{
|
{
|
||||||
title: '时间',
|
title: '时间',
|
||||||
dataIndex: 'dt',
|
dataIndex: 'dt',
|
||||||
width: 140,
|
width: 160,
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
customRender: ({ text }: any) =>
|
customRender: ({ text }: any) =>
|
||||||
text
|
text
|
||||||
@ -183,7 +222,19 @@ const tableColumns = computed(() => {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return [...fixedCols, ...dataCols];
|
const actionCols =
|
||||||
|
currentSearchParams.value.timeScale === 'tm'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return [...fixedCols, ...dataCols, ...actionCols];
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableScrollX = computed(() => {
|
const tableScrollX = computed(() => {
|
||||||
@ -257,10 +308,6 @@ const updateChart = (selectedRows: any[]) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
|
||||||
text: '垂向水温分布',
|
|
||||||
left: 'center'
|
|
||||||
},
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
@ -387,6 +434,34 @@ const exportBtn = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEdit = (record: any) => {
|
||||||
|
editRecord.value = { ...record };
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
return deleteVerticalInfo({
|
||||||
|
dataList: [
|
||||||
|
{
|
||||||
|
id: record.stcd ?? record.id,
|
||||||
|
tm: record.tm ?? record.dt
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataType: 'TIME',
|
||||||
|
source: reason
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
deleteModalRef.value?.open(record, () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
const initTable = (values: any) => {
|
const initTable = (values: any) => {
|
||||||
const filters = [
|
const filters = [
|
||||||
values.rvcd && values.rvcd !== 'all'
|
values.rvcd && values.rvcd !== 'all'
|
||||||
@ -510,6 +585,7 @@ const transformData = (data: any) => {
|
|||||||
dataList[col.dataIndex] = row.dataList[col.dataIndex];
|
dataList[col.dataIndex] = row.dataList[col.dataIndex];
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
...row,
|
||||||
stnm: row.stnm,
|
stnm: row.stnm,
|
||||||
dt: row.dt,
|
dt: row.dt,
|
||||||
dataList
|
dataList
|
||||||
@ -534,7 +610,6 @@ const transformData = (data: any) => {
|
|||||||
chartData.value = [];
|
chartData.value = [];
|
||||||
if (chartInstance) chartInstance.clear();
|
if (chartInstance) chartInstance.clear();
|
||||||
}
|
}
|
||||||
console.log(formattedData);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
records: formattedData,
|
records: formattedData,
|
||||||
@ -613,10 +688,10 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
// 禁用表格行点击高亮样式
|
// 禁用表格行点击高亮样式
|
||||||
:deep(.ant-table-tbody .ant-table-row:hover > td) {
|
:deep(.ant-table-tbody .ant-table-row:hover > td) {
|
||||||
background-color: transparent !important;
|
background-color: #ffffff !important;
|
||||||
}
|
}
|
||||||
:deep(.ant-table-tbody .ant-table-row-selected > td) {
|
:deep(.ant-table-tbody .ant-table-row-selected > td) {
|
||||||
background-color: transparent !important;
|
background-color: #ffffff !important;
|
||||||
}
|
}
|
||||||
:deep(.ant-table-tbody .ant-table-row > td) {
|
:deep(.ant-table-tbody .ant-table-row > td) {
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
|
|||||||
@ -0,0 +1,405 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="visible"
|
||||||
|
title="编辑水质数据"
|
||||||
|
width="60vw"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:label-col="{ span: 8 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
:rules="formRules"
|
||||||
|
class="max-h-[70vh] overflow-y-auto pr-4"
|
||||||
|
>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<!-- 基本信息(只读展示) -->
|
||||||
|
<a-col :span="24"><div class="form-group-title">基本信息</div></a-col>
|
||||||
|
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="测站/断面名称">
|
||||||
|
<a-input :value="formData.stnm" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="所属电站">
|
||||||
|
<a-input :value="formData.ennm" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="基地">
|
||||||
|
<a-input :value="formData.baseName" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 地表水等级 -->
|
||||||
|
<a-col :span="24"><div class="form-group-title">地表水等级</div></a-col>
|
||||||
|
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="水质要求" name="wwqtg">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.wwqtgName"
|
||||||
|
disabled
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="水质等级" name="wqgrd">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.wqgrdName"
|
||||||
|
disabled
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 污染物监测指标 -->
|
||||||
|
<a-col :span="24"
|
||||||
|
><div class="form-group-title">污染物监测指标</div></a-col
|
||||||
|
>
|
||||||
|
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="化学需氧量(mg/L)" name="codcr">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.codcr"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="氨氮(mg/L)" name="nh3n">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.nh3n"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="总磷(mg/L)" name="tp">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.tp"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="总氮(mg/L)" name="tn">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.tn"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 五参数 -->
|
||||||
|
<a-col :span="24"><div class="form-group-title">五参数</div></a-col>
|
||||||
|
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="电导率(μS/cm)" name="cond">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.cond"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="溶解氧(mg/L)" name="dox">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.dox"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="pH值" name="ph">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.ph"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="浊度(NTU)" name="tu">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.tu"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="水温(℃)" name="wtmp">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.wtmp"
|
||||||
|
placeholder="请输入"
|
||||||
|
:precision="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 确认修改弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="confirmModalVisible"
|
||||||
|
title="确认修改"
|
||||||
|
width="800px"
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleConfirmSubmit"
|
||||||
|
@cancel="handleConfirmCancel"
|
||||||
|
>
|
||||||
|
<div style="max-height: 50vh; overflow-y: auto; margin-bottom: 16px">
|
||||||
|
<a-descriptions bordered :column="1" size="small">
|
||||||
|
<a-descriptions-item
|
||||||
|
v-for="item in diffList"
|
||||||
|
:key="item.field"
|
||||||
|
:label="item.label"
|
||||||
|
>
|
||||||
|
<span style="color: #ff4d4f; text-decoration: line-through">{{
|
||||||
|
item.oldValue
|
||||||
|
}}</span>
|
||||||
|
<span style="margin: 0 8px">→</span>
|
||||||
|
<span style="color: #52c41a">{{ item.newValue }}</span>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
<a-form-item label="修改依据">
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="sourceValue"
|
||||||
|
placeholder="请输入修改依据"
|
||||||
|
:rows="5"
|
||||||
|
:maxlength="500"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { updateWaterDataInfo } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
open: boolean;
|
||||||
|
record?: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:open', 'success']);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.open,
|
||||||
|
set: val => emit('update:open', val)
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
const formData = ref<any>({});
|
||||||
|
const originalRecord = ref<any>({});
|
||||||
|
const formRules = ref<any>({
|
||||||
|
stnm: [{ required: true, message: '请输入测站名称', trigger: 'blur' }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 确认弹框状态
|
||||||
|
const confirmModalVisible = ref(false);
|
||||||
|
const sourceValue = ref('');
|
||||||
|
|
||||||
|
// 字段名 → 中文名映射
|
||||||
|
const fieldLabelMap: Record<string, string> = {
|
||||||
|
codcr: '化学需氧量(mg/L)',
|
||||||
|
nh3n: '氨氮(mg/L)',
|
||||||
|
tp: '总磷(mg/L)',
|
||||||
|
tn: '总氮(mg/L)',
|
||||||
|
cond: '电导率(μS/cm)',
|
||||||
|
dox: '溶解氧(mg/L)',
|
||||||
|
ph: 'pH值',
|
||||||
|
tu: '浊度(NTU)',
|
||||||
|
wtmp: '水温(℃)'
|
||||||
|
};
|
||||||
|
|
||||||
|
// select 选项映射(用于在 diff 中显示中文名)
|
||||||
|
const selectOptionsMap: Record<string, () => any[]> = {};
|
||||||
|
|
||||||
|
const resolveSelectLabel = (field: string, value: any): string => {
|
||||||
|
if (value === null || value === undefined || value === '') return '空';
|
||||||
|
const getOptions = selectOptionsMap[field];
|
||||||
|
if (getOptions) {
|
||||||
|
const opt = getOptions().find(
|
||||||
|
(o: any) => String(o.value) === String(value)
|
||||||
|
);
|
||||||
|
if (opt) return opt.label;
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 按修改顺序记录变更
|
||||||
|
const changeOrder = ref<
|
||||||
|
{ field: string; label: string; oldValue: string; newValue: string }[]
|
||||||
|
>([]);
|
||||||
|
const diffList = changeOrder;
|
||||||
|
|
||||||
|
// 将值中的"-"转换为null
|
||||||
|
const convertDashToNull = (obj: any) => {
|
||||||
|
const result: any = {};
|
||||||
|
for (const key in obj) {
|
||||||
|
if (obj[key] === '-' || obj[key] === '') {
|
||||||
|
result[key] = null;
|
||||||
|
} else {
|
||||||
|
result[key] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 formData 变化,按顺序记录修改
|
||||||
|
watch(
|
||||||
|
() => formData.value,
|
||||||
|
newData => {
|
||||||
|
const original = originalRecord.value;
|
||||||
|
for (const key in newData) {
|
||||||
|
const oldVal = original[key];
|
||||||
|
const newVal = newData[key];
|
||||||
|
if (oldVal !== newVal) {
|
||||||
|
const existingIdx = changeOrder.value.findIndex(c => c.field === key);
|
||||||
|
const oldNorm =
|
||||||
|
oldVal === null || oldVal === undefined || oldVal === '-'
|
||||||
|
? null
|
||||||
|
: oldVal;
|
||||||
|
const newNorm = newVal === null || newVal === undefined ? null : newVal;
|
||||||
|
if (oldNorm === newNorm) continue;
|
||||||
|
if (
|
||||||
|
oldNorm !== null &&
|
||||||
|
newNorm !== null &&
|
||||||
|
!isNaN(Number(oldNorm)) &&
|
||||||
|
!isNaN(Number(newNorm)) &&
|
||||||
|
Number(oldNorm) === Number(newNorm)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
const isSelect = !!selectOptionsMap[key];
|
||||||
|
const oldDisplay = isSelect
|
||||||
|
? resolveSelectLabel(key, oldNorm)
|
||||||
|
: oldNorm === null
|
||||||
|
? '空'
|
||||||
|
: String(oldNorm);
|
||||||
|
const newDisplay = isSelect
|
||||||
|
? resolveSelectLabel(key, newNorm)
|
||||||
|
: newNorm === null
|
||||||
|
? '空'
|
||||||
|
: String(newNorm);
|
||||||
|
const entry = {
|
||||||
|
field: key,
|
||||||
|
label: fieldLabelMap[key] || key,
|
||||||
|
oldValue: oldDisplay,
|
||||||
|
newValue: newDisplay
|
||||||
|
};
|
||||||
|
if (existingIdx >= 0) {
|
||||||
|
changeOrder.value[existingIdx] = entry;
|
||||||
|
} else {
|
||||||
|
changeOrder.value.push(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changeOrder.value = changeOrder.value.filter(c => c.field !== key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听 record 变化,填充表单
|
||||||
|
watch(
|
||||||
|
() => props.record,
|
||||||
|
newRecord => {
|
||||||
|
if (newRecord) {
|
||||||
|
const converted = convertDashToNull({ ...newRecord });
|
||||||
|
originalRecord.value = { ...converted };
|
||||||
|
formData.value = { ...converted };
|
||||||
|
changeOrder.value = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validateFields();
|
||||||
|
|
||||||
|
if (changeOrder.value.length === 0) {
|
||||||
|
message.info('未检测到任何修改');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示确认弹框
|
||||||
|
sourceValue.value = '';
|
||||||
|
confirmModalVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSubmit = async () => {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await updateWaterDataInfo({
|
||||||
|
updateData: { ...formData.value },
|
||||||
|
source: sourceValue.value.trim()
|
||||||
|
});
|
||||||
|
if (res?.code == 0 || res?.success) {
|
||||||
|
message.success('编辑成功');
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
visible.value = false;
|
||||||
|
emit('success');
|
||||||
|
} else {
|
||||||
|
message.error(res?.msg || '编辑失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error);
|
||||||
|
message.error('提交失败,请重试');
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmCancel = () => {
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
sourceValue.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
confirmModalVisible.value = false;
|
||||||
|
sourceValue.value = '';
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.form-group-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -18,12 +18,13 @@
|
|||||||
:showTime="timeShowTime"
|
:showTime="timeShowTime"
|
||||||
placeholder="起始时间"
|
placeholder="起始时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -43,12 +44,13 @@
|
|||||||
:disabledDate="disabledEndDate"
|
:disabledDate="disabledEndDate"
|
||||||
placeholder="结束时间"
|
placeholder="结束时间"
|
||||||
:showToday="false"
|
:showToday="false"
|
||||||
|
:showNow="false"
|
||||||
>
|
>
|
||||||
<template #renderExtraFooter>
|
<template #renderExtraFooter>
|
||||||
<div class="flex items-center flex-wrap px-2">
|
<div class="flex items-center flex-wrap px-2">
|
||||||
<span
|
<span
|
||||||
v-for="item in DateSetting.RangeButton.days1"
|
v-for="item in timeShortcutList"
|
||||||
:key="item"
|
:key="item.label"
|
||||||
@click="handleRangeClick(item)"
|
@click="handleRangeClick(item)"
|
||||||
>
|
>
|
||||||
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
<a class="text-[#1890ff] mr-1"> {{ item.label }}</a>
|
||||||
@ -68,7 +70,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import BasicSearch from '@/components/BasicSearch/index.vue';
|
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||||
import { DateSetting } from '@/utils/enumeration';
|
|
||||||
import { useTimeScale } from '@/store/composables/useTimeScale';
|
import { useTimeScale } from '@/store/composables/useTimeScale';
|
||||||
import { getWaterDataSectionList } from '@/api/DataQueryMenuModule';
|
import { getWaterDataSectionList } from '@/api/DataQueryMenuModule';
|
||||||
|
|
||||||
@ -77,7 +78,6 @@ const emit = defineEmits<{
|
|||||||
(e: 'reset', values: any): void;
|
(e: 'reset', values: any): void;
|
||||||
(e: 'search-finish', values: any): void;
|
(e: 'search-finish', values: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const basicSearchRef = ref<any>();
|
const basicSearchRef = ref<any>();
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
rvcd: 'all',
|
rvcd: 'all',
|
||||||
rstcd: null,
|
rstcd: '008660306300000001',
|
||||||
stcd: null,
|
stcd: null,
|
||||||
mway: null,
|
mway: null,
|
||||||
timeScale: 'tm'
|
timeScale: 'tm'
|
||||||
@ -101,6 +101,7 @@ const {
|
|||||||
timePicker,
|
timePicker,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
timeShowTime,
|
timeShowTime,
|
||||||
|
timeShortcutList,
|
||||||
handleRangeClick,
|
handleRangeClick,
|
||||||
disabledEndDate,
|
disabledEndDate,
|
||||||
handleSearchFinish,
|
handleSearchFinish,
|
||||||
|
|||||||
@ -20,55 +20,98 @@
|
|||||||
sort: sort
|
sort: sort
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<template #action="{ record }">
|
||||||
|
<template v-if="currentSearchParams.timeScale === 'tm'">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
class="text-[#2f6b98]"
|
||||||
|
size="small"
|
||||||
|
@click="handleEdit(record)"
|
||||||
|
>编辑</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
@click="handleDelete(record)"
|
||||||
|
>删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
|
|
||||||
|
<!-- 编辑水质数据 Modal -->
|
||||||
|
<EditWaterDataModal
|
||||||
|
v-model:open="editVisible"
|
||||||
|
:record="editRecord"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 删除确认 Modal -->
|
||||||
|
<DeleteConfirmModal
|
||||||
|
ref="deleteModalRef"
|
||||||
|
:delete-fn="handleDeleteFn"
|
||||||
|
title="删除水质数据"
|
||||||
|
label="数据"
|
||||||
|
@success="handleEditSuccess"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
import { ref, computed, onMounted, nextTick, h } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import WaterDataSearch from './WaterDataSearch.vue';
|
import WaterDataSearch from './WaterDataSearch.vue';
|
||||||
|
import EditWaterDataModal from './EditWaterDataModal.vue';
|
||||||
|
import DeleteConfirmModal from '@/views/DataQueryMenuModule/components/conventionalHydropower/BasicData/DeleteConfirmModal.vue';
|
||||||
import { Tooltip } from 'ant-design-vue';
|
import { Tooltip } from 'ant-design-vue';
|
||||||
import BasicTable from '@/components/BasicTable/index.vue';
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
import {
|
import {
|
||||||
getWaterDataList,
|
getWaterDataList,
|
||||||
getWaterDataDayList,
|
getWaterDataDayList,
|
||||||
getWaterDataMonthList
|
getWaterDataMonthList,
|
||||||
|
deleteWaterDataInfo
|
||||||
} from '@/api/DataQueryMenuModule';
|
} from '@/api/DataQueryMenuModule';
|
||||||
import { calcTableScrollY } from '@/utils/index';
|
import { calcTableScrollY } from '@/utils/index';
|
||||||
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
import { buildTimeFilters } from '@/utils/buildTimeFilters';
|
||||||
|
|
||||||
const sort = ref<any>([
|
const sort = computed(() => {
|
||||||
{
|
// 预定义排序数组
|
||||||
field: 'qecLimit',
|
const sortMap = {
|
||||||
dir: 'asc'
|
tm: [
|
||||||
},
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
{
|
{ field: 'rvcd', dir: 'asc' },
|
||||||
field: 'baseStepSort',
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
dir: 'asc'
|
{ field: 'ennm', dir: 'asc' },
|
||||||
},
|
{ field: 'tm', dir: 'desc' }
|
||||||
{
|
],
|
||||||
field: 'rvcdStepSort',
|
dt: [
|
||||||
dir: 'asc'
|
{ field: 'baseStepSort', dir: 'asc' },
|
||||||
},
|
{ field: 'rvcd', dir: 'asc' },
|
||||||
{
|
{ field: 'rvcdStepSort', dir: 'asc' },
|
||||||
field: 'siteStepSort',
|
{ field: 'stnm', dir: 'asc' },
|
||||||
dir: 'asc'
|
{ field: 'dt', dir: 'desc' }
|
||||||
},
|
],
|
||||||
{
|
month: [
|
||||||
field: 'year',
|
{ field: 'stnm', dir: 'desc' },
|
||||||
dir: 'desc'
|
{ field: 'year', dir: 'desc' },
|
||||||
},
|
{ field: 'month', dir: 'desc' }
|
||||||
{
|
]
|
||||||
field: 'month',
|
};
|
||||||
dir: 'desc'
|
|
||||||
}
|
// 取值(若 timeScale 不在映射中,返回空数组)
|
||||||
]);
|
return sortMap[currentSearchParams.value.timeScale] || [];
|
||||||
|
});
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
const searchRef = ref();
|
const searchRef = ref();
|
||||||
const tableScrollY = ref<string | number>(0);
|
const tableScrollY = ref<string | number>(0);
|
||||||
const currentSearchParams = ref<any>({});
|
const currentSearchParams = ref<any>({});
|
||||||
const exportLoading = ref(false);
|
const exportLoading = ref(false);
|
||||||
|
|
||||||
|
// 编辑相关
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const editRecord = ref<any>(null);
|
||||||
|
const deleteModalRef = ref();
|
||||||
|
|
||||||
// 根据 timeScale 动态切换 API
|
// 根据 timeScale 动态切换 API
|
||||||
const currentListUrl = computed(() => {
|
const currentListUrl = computed(() => {
|
||||||
const timeScale = currentSearchParams.value.timeScale;
|
const timeScale = currentSearchParams.value.timeScale;
|
||||||
@ -97,6 +140,7 @@ const allColumns: any[] = [
|
|||||||
width: 200,
|
width: 200,
|
||||||
sort: true,
|
sort: true,
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
|
merge: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -105,6 +149,7 @@ const allColumns: any[] = [
|
|||||||
dataIndex: 'ennm',
|
dataIndex: 'ennm',
|
||||||
visible: true,
|
visible: true,
|
||||||
width: 120,
|
width: 120,
|
||||||
|
merge: true,
|
||||||
ellipsis: true
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -119,7 +164,7 @@ const allColumns: any[] = [
|
|||||||
title: '地表水等级',
|
title: '地表水等级',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: 'wwqtgName',
|
key: 'wwqtg',
|
||||||
title: '水质要求',
|
title: '水质要求',
|
||||||
dataIndex: 'wwqtgName',
|
dataIndex: 'wwqtgName',
|
||||||
width: 100,
|
width: 100,
|
||||||
@ -128,7 +173,7 @@ const allColumns: any[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'wqgrdName',
|
key: 'wqgrd',
|
||||||
title: '水质等级',
|
title: '水质等级',
|
||||||
dataIndex: 'wqgrdName',
|
dataIndex: 'wqgrdName',
|
||||||
width: 100,
|
width: 100,
|
||||||
@ -235,6 +280,13 @@ const allColumns: any[] = [
|
|||||||
customRender: ({ record }: any) => {
|
customRender: ({ record }: any) => {
|
||||||
return record.wtmp || '-';
|
return record.wtmp || '-';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'action',
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -270,10 +322,19 @@ const timeColumn = computed(() => ({
|
|||||||
|
|
||||||
// 动态列(响应式)
|
// 动态列(响应式)
|
||||||
const Columns = computed(() => {
|
const Columns = computed(() => {
|
||||||
const result = [...allColumns];
|
const result = allColumns.map(col => ({ ...col }));
|
||||||
// 在基地列之后插入时间列
|
// 在基地列之后插入时间列
|
||||||
const baseIndex = result.findIndex(c => c.key === 'baseName');
|
const baseIndex = result.findIndex(c => c.key === 'baseName');
|
||||||
result.splice(baseIndex + 1, 0, timeColumn.value);
|
result.splice(baseIndex + 1, 0, timeColumn.value);
|
||||||
|
// 仅 timeScale === 'tm' 时显示操作列
|
||||||
|
if (currentSearchParams.value.timeScale !== 'tm') {
|
||||||
|
const wcsGroup = result.find(c => c.title === '五参数');
|
||||||
|
if (wcsGroup && wcsGroup.children) {
|
||||||
|
wcsGroup.children = wcsGroup.children.filter(
|
||||||
|
(c: any) => c.key !== 'action'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -349,6 +410,38 @@ const initTable = (values: any) => {
|
|||||||
tableRef.value.getList(params);
|
tableRef.value.getList(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const handleEdit = (record: any) => {
|
||||||
|
editRecord.value = { ...record };
|
||||||
|
editVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDeleteFn = async (record: any, reason: string) => {
|
||||||
|
console.log(record, reason);
|
||||||
|
return deleteWaterDataInfo({
|
||||||
|
dataList: [
|
||||||
|
{
|
||||||
|
id: record.stcd,
|
||||||
|
tm: record.tm
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataType: 'TIME',
|
||||||
|
source: reason
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: any) => {
|
||||||
|
deleteModalRef.value?.open(record, () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑/删除成功回调
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
initTable(currentSearchParams.value);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
tableScrollY.value = calcTableScrollY();
|
tableScrollY.value = calcTableScrollY();
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: "dept",
|
name: 'dept'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, reactive, ref, nextTick } from "vue";
|
import { onMounted, reactive, ref, nextTick } from 'vue';
|
||||||
import { ElMessage, ElMessageBox, FormRules } from "element-plus";
|
import { ElMessage, ElMessageBox, FormRules } from 'element-plus';
|
||||||
import Sortable from "sortablejs";
|
import Sortable from 'sortablejs';
|
||||||
import { useAppStore } from "@/store/modules/app";
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import Page from "@/components/Pagination/page.vue";
|
import Page from '@/components/Pagination/page.vue';
|
||||||
import {
|
import {
|
||||||
getTreelist,
|
getTreelist,
|
||||||
addDict,
|
addDict,
|
||||||
@ -21,38 +21,38 @@ import {
|
|||||||
updateDictionaryItem,
|
updateDictionaryItem,
|
||||||
deleteDictItemById,
|
deleteDictItemById,
|
||||||
deleteDictItemByIds,
|
deleteDictItemByIds,
|
||||||
changeItemOrder,
|
changeItemOrder
|
||||||
} from "@/api/dict";
|
} from '@/api/dict';
|
||||||
|
|
||||||
const treeloading = ref(false);
|
const treeloading = ref(false);
|
||||||
const activeName = ref("00");
|
const activeName = ref('00');
|
||||||
// 左侧树 - 数据
|
// 左侧树 - 数据
|
||||||
const treedata: any = ref([]);
|
const treedata: any = ref([]);
|
||||||
const treeRef = ref();
|
const treeRef = ref();
|
||||||
const treeId = ref("");
|
const treeId = ref('');
|
||||||
const defaultProps = { label: "dictName" };
|
const defaultProps = { label: 'dictName' };
|
||||||
// 字典弹框
|
// 字典弹框
|
||||||
const title = ref("");
|
const title = ref('');
|
||||||
const dialogdict = ref(false);
|
const dialogdict = ref(false);
|
||||||
const dictInfoRef = ref();
|
const dictInfoRef = ref();
|
||||||
const dictInfo = ref({
|
const dictInfo = ref({
|
||||||
id: "",
|
id: '',
|
||||||
dictCode: "",
|
dictCode: '',
|
||||||
dictName: "",
|
dictName: '',
|
||||||
dictType: "",
|
dictType: ''
|
||||||
});
|
});
|
||||||
// 字典弹框规则
|
// 字典弹框规则
|
||||||
const rules = reactive<FormRules>({
|
const rules = reactive<FormRules>({
|
||||||
dictName: [{ required: true, message: "请输入字典名称", trigger: "blur" }],
|
dictName: [{ required: true, message: '请输入字典名称', trigger: 'blur' }],
|
||||||
dictCode: [{ required: true, message: "请输入字典编码", trigger: "blur" }],
|
dictCode: [{ required: true, message: '请输入字典编码', trigger: 'blur' }]
|
||||||
});
|
});
|
||||||
|
|
||||||
// 右侧查询
|
// 右侧查询
|
||||||
const querystr = ref({
|
const querystr = ref({
|
||||||
dictId: "",
|
dictId: '',
|
||||||
dictName: "",
|
dictName: '',
|
||||||
size: 10,
|
size: 20,
|
||||||
current: 1,
|
current: 1
|
||||||
});
|
});
|
||||||
// 右侧表格
|
// 右侧表格
|
||||||
const tableData = ref([]) as any;
|
const tableData = ref([]) as any;
|
||||||
@ -60,21 +60,21 @@ const multipleSelection = ref([]);
|
|||||||
// const total = ref(0);
|
// const total = ref(0);
|
||||||
const tableloading = ref(false);
|
const tableloading = ref(false);
|
||||||
// 右侧新增/修改弹框弹框
|
// 右侧新增/修改弹框弹框
|
||||||
const titleItem = ref("");
|
const titleItem = ref('');
|
||||||
const dialogdictItem = ref(false);
|
const dialogdictItem = ref(false);
|
||||||
const dictInfoRefItem = ref();
|
const dictInfoRefItem = ref();
|
||||||
const dictInfoItem = ref({
|
const dictInfoItem = ref({
|
||||||
id: "",
|
id: '',
|
||||||
dictId: "",
|
dictId: '',
|
||||||
itemCode: "",
|
itemCode: '',
|
||||||
dictName: "",
|
dictName: '',
|
||||||
parentCode: "",
|
parentCode: '',
|
||||||
custom1: "",
|
custom1: ''
|
||||||
});
|
});
|
||||||
// 字典弹框规则
|
// 字典弹框规则
|
||||||
const dictItemrules = reactive<FormRules>({
|
const dictItemrules = reactive<FormRules>({
|
||||||
dictName: [{ required: true, message: "请输入项名称", trigger: "blur" }],
|
dictName: [{ required: true, message: '请输入项名称', trigger: 'blur' }],
|
||||||
itemCode: [{ required: true, message: "请输入项编码", trigger: "blur" }],
|
itemCode: [{ required: true, message: '请输入项编码', trigger: 'blur' }]
|
||||||
});
|
});
|
||||||
// 初始加载
|
// 初始加载
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -86,23 +86,25 @@ const vMove = {
|
|||||||
mounted(el: any) {
|
mounted(el: any) {
|
||||||
el.onmousedown = function (e: any) {
|
el.onmousedown = function (e: any) {
|
||||||
var init = e.clientX;
|
var init = e.clientX;
|
||||||
var parent: any = document.getElementById("silderLeft");
|
var parent: any = document.getElementById('silderLeft');
|
||||||
const initWidth: any = parent.offsetWidth;
|
const initWidth: any = parent.offsetWidth;
|
||||||
document.onmousemove = function (e) {
|
document.onmousemove = function (e) {
|
||||||
var end = e.clientX;
|
var end = e.clientX;
|
||||||
var newWidth = end - init + initWidth;
|
var newWidth = end - init + initWidth;
|
||||||
parent.style.width = newWidth + "px";
|
parent.style.width = newWidth + 'px';
|
||||||
};
|
};
|
||||||
document.onmouseup = function () {
|
document.onmouseup = function () {
|
||||||
document.onmousemove = document.onmouseup = null;
|
document.onmousemove = document.onmouseup = null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
function rowDrop() {
|
function rowDrop() {
|
||||||
const tbody = document.querySelector(".draggable .el-table__body-wrapper tbody");
|
const tbody = document.querySelector(
|
||||||
|
'.draggable .el-table__body-wrapper tbody'
|
||||||
|
);
|
||||||
Sortable.create(tbody, {
|
Sortable.create(tbody, {
|
||||||
draggable: ".draggable .el-table__row",
|
draggable: '.draggable .el-table__row',
|
||||||
onEnd(data: any) {
|
onEnd(data: any) {
|
||||||
const newIndex = data.newIndex;
|
const newIndex = data.newIndex;
|
||||||
const oldIndex = data.oldIndex;
|
const oldIndex = data.oldIndex;
|
||||||
@ -111,31 +113,31 @@ function rowDrop() {
|
|||||||
}
|
}
|
||||||
const params = {
|
const params = {
|
||||||
fromID: tableData.value[newIndex].id,
|
fromID: tableData.value[newIndex].id,
|
||||||
toID: tableData.value[oldIndex].id,
|
toID: tableData.value[oldIndex].id
|
||||||
};
|
};
|
||||||
changeItemOrder(params).then(() => {
|
changeItemOrder(params).then(() => {
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 查询左侧树
|
// 查询左侧树
|
||||||
function getTree() {
|
function getTree() {
|
||||||
const params = {
|
const params = {
|
||||||
dictType: activeName.value,
|
dictType: activeName.value
|
||||||
};
|
};
|
||||||
treeloading.value = true;
|
treeloading.value = true;
|
||||||
getTreelist(params)
|
getTreelist(params)
|
||||||
.then((res) => {
|
.then(res => {
|
||||||
treedata.value = res.data;
|
treedata.value = res.data;
|
||||||
treeloading.value = false;
|
treeloading.value = false;
|
||||||
if (treeId.value == "") {
|
if (treeId.value == '') {
|
||||||
treeId.value = res.data[0].id;
|
treeId.value = res.data[0].id;
|
||||||
}
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
treeRef.value?.setCurrentKey(treeId.value);
|
treeRef.value?.setCurrentKey(treeId.value);
|
||||||
});
|
});
|
||||||
if (res.data.length == 0) treeId.value = "";
|
if (res.data.length == 0) treeId.value = '';
|
||||||
init();
|
init();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -155,37 +157,37 @@ function activeNameChange(name: any) {
|
|||||||
|
|
||||||
// 新增字典
|
// 新增字典
|
||||||
function dictAdd() {
|
function dictAdd() {
|
||||||
title.value = "新增字典";
|
title.value = '新增字典';
|
||||||
dialogdict.value = true;
|
dialogdict.value = true;
|
||||||
const info = ref({
|
const info = ref({
|
||||||
id: "",
|
id: '',
|
||||||
dictCode: "",
|
dictCode: '',
|
||||||
dictName: "",
|
dictName: '',
|
||||||
dictType: activeName.value,
|
dictType: activeName.value
|
||||||
});
|
});
|
||||||
dictInfo.value = info.value;
|
dictInfo.value = info.value;
|
||||||
}
|
}
|
||||||
//修改字典
|
//修改字典
|
||||||
function dictEdit(data: any) {
|
function dictEdit(data: any) {
|
||||||
title.value = "修改字典";
|
title.value = '修改字典';
|
||||||
dictInfo.value = JSON.parse(JSON.stringify(data));
|
dictInfo.value = JSON.parse(JSON.stringify(data));
|
||||||
dialogdict.value = true;
|
dialogdict.value = true;
|
||||||
}
|
}
|
||||||
//删除字典
|
//删除字典
|
||||||
function dictDel(data: any) {
|
function dictDel(data: any) {
|
||||||
ElMessageBox.confirm("确定删除该字典及该字典下的所有项吗?", "删除提示", {
|
ElMessageBox.confirm('确定删除该字典及该字典下的所有项吗?', '删除提示', {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: '取消',
|
||||||
type: "warning",
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
let params = {
|
let params = {
|
||||||
id: data.id,
|
id: data.id
|
||||||
};
|
};
|
||||||
deleteById(params).then(() => {
|
deleteById(params).then(() => {
|
||||||
getTree();
|
getTree();
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: "删除成功",
|
message: '删除成功',
|
||||||
type: "success",
|
type: 'success'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -198,14 +200,14 @@ function dictSave(formEl: any) {
|
|||||||
const params = {
|
const params = {
|
||||||
dictCode: dictInfo.value.dictCode,
|
dictCode: dictInfo.value.dictCode,
|
||||||
dictName: dictInfo.value.dictName,
|
dictName: dictInfo.value.dictName,
|
||||||
dictType: dictInfo.value.dictType,
|
dictType: dictInfo.value.dictType
|
||||||
};
|
};
|
||||||
addDict(params).then(() => {
|
addDict(params).then(() => {
|
||||||
getTree();
|
getTree();
|
||||||
dialogdict.value = false;
|
dialogdict.value = false;
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: "success",
|
type: 'success',
|
||||||
message: "新增成功",
|
message: '新增成功'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (dictInfo.value.id) {
|
} else if (dictInfo.value.id) {
|
||||||
@ -213,14 +215,14 @@ function dictSave(formEl: any) {
|
|||||||
dictCode: dictInfo.value.dictCode,
|
dictCode: dictInfo.value.dictCode,
|
||||||
dictName: dictInfo.value.dictName,
|
dictName: dictInfo.value.dictName,
|
||||||
dictType: dictInfo.value.dictType,
|
dictType: dictInfo.value.dictType,
|
||||||
id: dictInfo.value.id,
|
id: dictInfo.value.id
|
||||||
};
|
};
|
||||||
updateDict(params).then(() => {
|
updateDict(params).then(() => {
|
||||||
getTree();
|
getTree();
|
||||||
dialogdict.value = false;
|
dialogdict.value = false;
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: "success",
|
type: 'success',
|
||||||
message: "修改成功",
|
message: '修改成功'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -233,7 +235,7 @@ function dictSave(formEl: any) {
|
|||||||
function treenodeDrop(before: any, after: any) {
|
function treenodeDrop(before: any, after: any) {
|
||||||
const params = {
|
const params = {
|
||||||
fromID: before.data.id,
|
fromID: before.data.id,
|
||||||
toID: after.data.id,
|
toID: after.data.id
|
||||||
};
|
};
|
||||||
changeDictOrder(params).then(() => {
|
changeDictOrder(params).then(() => {
|
||||||
getTree();
|
getTree();
|
||||||
@ -241,15 +243,15 @@ function treenodeDrop(before: any, after: any) {
|
|||||||
}
|
}
|
||||||
const allowDrop: any = (draggingNode: any, dropNode: any, type: any) => {
|
const allowDrop: any = (draggingNode: any, dropNode: any, type: any) => {
|
||||||
// 不能拖拽到级别里面
|
// 不能拖拽到级别里面
|
||||||
if (type === "inner" || Number(dropNode.data.pid) === 0) return;
|
if (type === 'inner' || Number(dropNode.data.pid) === 0) return;
|
||||||
if (draggingNode.nextSibling === undefined) {
|
if (draggingNode.nextSibling === undefined) {
|
||||||
return type === "prev";
|
return type === 'prev';
|
||||||
} else if (dropNode.nextSibling === undefined) {
|
} else if (dropNode.nextSibling === undefined) {
|
||||||
return type === "next";
|
return type === 'next';
|
||||||
} else if (draggingNode.nextSibling.id !== dropNode.id) {
|
} else if (draggingNode.nextSibling.id !== dropNode.id) {
|
||||||
return type === "prev";
|
return type === 'prev';
|
||||||
} else {
|
} else {
|
||||||
return type === "next";
|
return type === 'next';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 关闭字典弹框
|
// 关闭字典弹框
|
||||||
@ -281,62 +283,62 @@ function init() {
|
|||||||
}
|
}
|
||||||
// 新增字典项
|
// 新增字典项
|
||||||
function addClick() {
|
function addClick() {
|
||||||
titleItem.value = "新增项";
|
titleItem.value = '新增项';
|
||||||
dialogdictItem.value = true;
|
dialogdictItem.value = true;
|
||||||
const info = ref({
|
const info = ref({
|
||||||
id: "",
|
id: '',
|
||||||
dictId: querystr.value.dictId,
|
dictId: querystr.value.dictId,
|
||||||
itemCode: "",
|
itemCode: '',
|
||||||
dictName: "",
|
dictName: '',
|
||||||
parentCode: "",
|
parentCode: '',
|
||||||
custom1: "",
|
custom1: ''
|
||||||
});
|
});
|
||||||
dictInfoItem.value = info.value;
|
dictInfoItem.value = info.value;
|
||||||
}
|
}
|
||||||
// 修改字典项
|
// 修改字典项
|
||||||
function editClick(data: any) {
|
function editClick(data: any) {
|
||||||
titleItem.value = "修改项";
|
titleItem.value = '修改项';
|
||||||
dictInfoItem.value = JSON.parse(JSON.stringify(data));
|
dictInfoItem.value = JSON.parse(JSON.stringify(data));
|
||||||
dialogdictItem.value = true;
|
dialogdictItem.value = true;
|
||||||
}
|
}
|
||||||
// 删除字典项
|
// 删除字典项
|
||||||
function delClick(data: any) {
|
function delClick(data: any) {
|
||||||
ElMessageBox.confirm("确定删除此项吗?", "删除提示", {
|
ElMessageBox.confirm('确定删除此项吗?', '删除提示', {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: '取消',
|
||||||
type: "warning",
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
let params = {
|
let params = {
|
||||||
id: data.id,
|
id: data.id
|
||||||
};
|
};
|
||||||
deleteDictItemById(params).then(() => {
|
deleteDictItemById(params).then(() => {
|
||||||
init();
|
init();
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: "删除成功",
|
message: '删除成功',
|
||||||
type: "success",
|
type: 'success'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 多选删除字典项
|
// 多选删除字典项
|
||||||
function delsClick() {
|
function delsClick() {
|
||||||
ElMessageBox.confirm("确定删除已选择的项吗?", "删除提示", {
|
ElMessageBox.confirm('确定删除已选择的项吗?', '删除提示', {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: '取消',
|
||||||
type: "warning",
|
type: 'warning'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
let id = [] as any[];
|
let id = [] as any[];
|
||||||
multipleSelection.value.forEach((item: any) => {
|
multipleSelection.value.forEach((item: any) => {
|
||||||
id.push(item.id);
|
id.push(item.id);
|
||||||
});
|
});
|
||||||
let params = {
|
let params = {
|
||||||
id: id.join(","),
|
id: id.join(',')
|
||||||
};
|
};
|
||||||
deleteDictItemByIds(params).then(() => {
|
deleteDictItemByIds(params).then(() => {
|
||||||
init();
|
init();
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: "删除成功",
|
message: '删除成功',
|
||||||
type: "success",
|
type: 'success'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -356,14 +358,14 @@ function dictItemSave(formEl: any) {
|
|||||||
itemCode: dictInfoItem.value.itemCode,
|
itemCode: dictInfoItem.value.itemCode,
|
||||||
dictName: dictInfoItem.value.dictName,
|
dictName: dictInfoItem.value.dictName,
|
||||||
parentCode: dictInfoItem.value.parentCode,
|
parentCode: dictInfoItem.value.parentCode,
|
||||||
custom1: dictInfoItem.value.custom1,
|
custom1: dictInfoItem.value.custom1
|
||||||
};
|
};
|
||||||
addDictionaryItem(params).then(() => {
|
addDictionaryItem(params).then(() => {
|
||||||
init();
|
init();
|
||||||
dialogdictItem.value = false;
|
dialogdictItem.value = false;
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: "success",
|
type: 'success',
|
||||||
message: "新增字典项成功",
|
message: '新增字典项成功'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (dictInfoItem.value.id) {
|
} else if (dictInfoItem.value.id) {
|
||||||
@ -373,14 +375,14 @@ function dictItemSave(formEl: any) {
|
|||||||
dictName: dictInfoItem.value.dictName,
|
dictName: dictInfoItem.value.dictName,
|
||||||
parentCode: dictInfoItem.value.parentCode,
|
parentCode: dictInfoItem.value.parentCode,
|
||||||
custom1: dictInfoItem.value.custom1,
|
custom1: dictInfoItem.value.custom1,
|
||||||
id: dictInfoItem.value.id,
|
id: dictInfoItem.value.id
|
||||||
};
|
};
|
||||||
updateDictionaryItem(params).then(() => {
|
updateDictionaryItem(params).then(() => {
|
||||||
init();
|
init();
|
||||||
dialogdictItem.value = false;
|
dialogdictItem.value = false;
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: "success",
|
type: 'success',
|
||||||
message: "修改字典项成功",
|
message: '修改字典项成功'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -402,7 +404,9 @@ const total = ref();
|
|||||||
<aside id="silderLeft">
|
<aside id="silderLeft">
|
||||||
<el-tabs
|
<el-tabs
|
||||||
:class="
|
:class="
|
||||||
useAppStore().size === 'default' ? 'silderLeft-large' : 'silderLeft-default'
|
useAppStore().size === 'default'
|
||||||
|
? 'silderLeft-large'
|
||||||
|
: 'silderLeft-default'
|
||||||
"
|
"
|
||||||
v-model="activeName"
|
v-model="activeName"
|
||||||
class="demo-tabs"
|
class="demo-tabs"
|
||||||
@ -432,7 +436,9 @@ const total = ref();
|
|||||||
v-loading="treeloading"
|
v-loading="treeloading"
|
||||||
ref="treeRef"
|
ref="treeRef"
|
||||||
:class="
|
:class="
|
||||||
useAppStore().size === 'default' ? 'silderLeft-large' : 'silderLeft-default'
|
useAppStore().size === 'default'
|
||||||
|
? 'silderLeft-large'
|
||||||
|
: 'silderLeft-default'
|
||||||
"
|
"
|
||||||
node-key="id"
|
node-key="id"
|
||||||
:allow-drop="allowDrop"
|
:allow-drop="allowDrop"
|
||||||
@ -451,10 +457,18 @@ const total = ref();
|
|||||||
<span>{{ node.label }}</span>
|
<span>{{ node.label }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<el-icon class="treeediticon" title="修改" @click="() => dictEdit(data)">
|
<el-icon
|
||||||
|
class="treeediticon"
|
||||||
|
title="修改"
|
||||||
|
@click="() => dictEdit(data)"
|
||||||
|
>
|
||||||
<EditPen />
|
<EditPen />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<el-icon class="treedelicon" title="删除" @click="() => dictDel(data)">
|
<el-icon
|
||||||
|
class="treedelicon"
|
||||||
|
title="删除"
|
||||||
|
@click="() => dictDel(data)"
|
||||||
|
>
|
||||||
<Delete />
|
<Delete />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</span>
|
</span>
|
||||||
@ -510,7 +524,10 @@ const total = ref();
|
|||||||
border
|
border
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
default-expand-all
|
default-expand-all
|
||||||
:header-cell-style="{ background: 'rgb(250 250 250)', height: '50px' }"
|
:header-cell-style="{
|
||||||
|
background: 'rgb(250 250 250)',
|
||||||
|
height: '50px'
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="50" align="center" />
|
<el-table-column type="selection" width="50" align="center" />
|
||||||
<el-table-column prop="orderNo" label="序号" width="80">
|
<el-table-column prop="orderNo" label="序号" width="80">
|
||||||
@ -521,8 +538,16 @@ const total = ref();
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="itemCode" label="项编码" width="160"></el-table-column>
|
<el-table-column
|
||||||
<el-table-column prop="dictName" label="项名称" width="140"></el-table-column>
|
prop="itemCode"
|
||||||
|
label="项编码"
|
||||||
|
width="160"
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="dictName"
|
||||||
|
label="项名称"
|
||||||
|
width="140"
|
||||||
|
></el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="parentCode"
|
prop="parentCode"
|
||||||
label="父项编码"
|
label="父项编码"
|
||||||
@ -633,7 +658,9 @@ const total = ref();
|
|||||||
-webkit-justify-content: flex-end;
|
-webkit-justify-content: flex-end;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<el-button style="padding: 10px 15px" @click="dictItemClose">取 消</el-button>
|
<el-button style="padding: 10px 15px" @click="dictItemClose"
|
||||||
|
>取 消</el-button
|
||||||
|
>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
style="padding: 10px 15px"
|
style="padding: 10px 15px"
|
||||||
@ -689,7 +716,9 @@ const total = ref();
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<el-button @click="dictClose">取 消</el-button>
|
<el-button @click="dictClose">取 消</el-button>
|
||||||
<el-button type="primary" @click="dictSave(dictInfoRef)">确定</el-button>
|
<el-button type="primary" @click="dictSave(dictInfoRef)"
|
||||||
|
>确定</el-button
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user