Merge branch 'main' into dev-tw
This commit is contained in:
commit
2a7b8d3e3f
@ -24,17 +24,23 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
Date now = new Date();
|
||||
|
||||
// 自动填充创建时间
|
||||
// 创建时间
|
||||
this.strictInsertFill(metaObject, "createdAt", Date.class, now);
|
||||
|
||||
// 自动填充更新时间
|
||||
// 更新时间
|
||||
this.strictInsertFill(metaObject, "updatedAt", Date.class, now);
|
||||
// 更新时间
|
||||
this.strictInsertFill(metaObject, "modifyTime", Date.class, now);
|
||||
|
||||
try {
|
||||
String userId = SecurityUtils.getUserId();
|
||||
// 自动填充更新时间
|
||||
// 创建人
|
||||
this.strictInsertFill(metaObject, "createdBy", String.class, userId );
|
||||
|
||||
// 自动填充更新时间
|
||||
//创建人
|
||||
this.strictInsertFill(metaObject, "recordUser", String.class, userId );
|
||||
// 更新人
|
||||
this.strictInsertFill(metaObject, "modifyUser", String.class, userId);
|
||||
// 更新人
|
||||
this.strictInsertFill(metaObject, "updatedBy", String.class, userId);
|
||||
} catch (Exception e) {
|
||||
log.info("message{}",e.getMessage());
|
||||
@ -47,12 +53,21 @@ public class MyMetaObjectHandler implements MetaObjectHandler {
|
||||
*/
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
// 自动填充更新时间
|
||||
this.strictUpdateFill(metaObject, "updatedAt", Date.class, new Date());
|
||||
Date date = new Date();
|
||||
// 更新时间
|
||||
this.strictUpdateFill(metaObject, "updatedAt", Date.class, date);
|
||||
// 删除时间
|
||||
this.strictUpdateFill(metaObject, "deleteTime", Date.class, date);
|
||||
// 跟新时间
|
||||
this.strictInsertFill(metaObject, "modifyTime", Date.class, date);
|
||||
try {
|
||||
String userId = SecurityUtils.getUserId();
|
||||
// 自动填充更新人
|
||||
// 更新人
|
||||
this.strictInsertFill(metaObject, "updatedBy", String.class, userId);
|
||||
// 删除人
|
||||
this.strictInsertFill(metaObject, "deleteUser", String.class, userId);
|
||||
// 更新人
|
||||
this.strictInsertFill(metaObject, "modifyUser", String.class, userId);
|
||||
} catch (Exception e) {
|
||||
log.info("message{}",e.getMessage());
|
||||
}
|
||||
|
||||
@ -241,6 +241,14 @@ public class SwaggerConfig {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi groupSysOqmgApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("5.2 倾斜摄影管理")
|
||||
.packagesToScan("com.yfd.platform.qgc_sys.obliqueImage.controller")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi groupLygkApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.yfd.platform.qgc_base.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ -28,6 +29,7 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 数据时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date tm;
|
||||
|
||||
/**
|
||||
@ -77,6 +79,7 @@ public class SdEngInfoBH implements Serializable {
|
||||
* 数据接入时间
|
||||
*/
|
||||
@TableField("DTIN_TM")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date dtinTm;
|
||||
|
||||
/**
|
||||
@ -182,29 +185,34 @@ public class SdEngInfoBH implements Serializable {
|
||||
* 规划设计日期
|
||||
*/
|
||||
@TableField("STDSDT")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date stdsdt;
|
||||
|
||||
/**
|
||||
* 计划开工日期
|
||||
*/
|
||||
@TableField("PSTSTDT")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date pststdt;
|
||||
|
||||
/**
|
||||
* 计划完工日期
|
||||
*/
|
||||
@TableField("PESSTDT")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date pesstdt;
|
||||
|
||||
/**
|
||||
* 开工日期
|
||||
*/
|
||||
@TableField("SWDT")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date swdt;
|
||||
|
||||
/**
|
||||
* 建成日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@TableField("JCDT")
|
||||
private Date jcdt;
|
||||
|
||||
@ -212,12 +220,14 @@ public class SdEngInfoBH implements Serializable {
|
||||
* 退役/拆除日期
|
||||
*/
|
||||
@TableField("WDDT")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date wddt;
|
||||
|
||||
/**
|
||||
* 截流日期
|
||||
*/
|
||||
@TableField("JLDT")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date jldt;
|
||||
|
||||
/**
|
||||
@ -404,6 +414,7 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 编制时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date edttm;
|
||||
|
||||
/**
|
||||
@ -856,16 +867,19 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 蓄水验收日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date sswdt;
|
||||
|
||||
/**
|
||||
* 一期下闸蓄水日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date piodt;
|
||||
|
||||
/**
|
||||
* 二期下闸蓄水日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date aiodt;
|
||||
|
||||
/**
|
||||
@ -936,16 +950,19 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 水土保持竣工验收时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date styphpsp;
|
||||
|
||||
/**
|
||||
* 现行环境保护竣工验收时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date xsjdhbys;
|
||||
|
||||
/**
|
||||
* 申报水土保持方案日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date sbfascrq;
|
||||
|
||||
/**
|
||||
@ -957,21 +974,25 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 环境影响后评价时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date hpsprq;
|
||||
|
||||
/**
|
||||
* 竣工环境保护验收时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date jghbysrq;
|
||||
|
||||
/**
|
||||
* 截流阶段环境保护验收时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date jljdsbys;
|
||||
|
||||
/**
|
||||
* 竣工水土保持方案验收时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date jgjdsbys;
|
||||
|
||||
/**
|
||||
@ -1218,6 +1239,7 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 植被覆盖调查时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date vlsrTm;
|
||||
|
||||
/**
|
||||
@ -1228,6 +1250,7 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date recordTime;
|
||||
|
||||
/**
|
||||
@ -1238,6 +1261,7 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date modifyTime;
|
||||
|
||||
/**
|
||||
@ -1253,6 +1277,7 @@ public class SdEngInfoBH implements Serializable {
|
||||
/**
|
||||
* 删除时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date deleteTime;
|
||||
|
||||
/**
|
||||
|
||||
@ -10,6 +10,7 @@ import com.yfd.platform.config.ResponseResult;
|
||||
import com.yfd.platform.qgc_base.service.ISdHbrvDicService;
|
||||
import com.yfd.platform.qgc_env.wt.service.SdWtMonitorService;
|
||||
import com.yfd.platform.qgc_lygk.along.domain.MsAlongB;
|
||||
import com.yfd.platform.qgc_lygk.along.domain.PowerDto;
|
||||
import com.yfd.platform.qgc_lygk.along.service.IMsAlongBService;
|
||||
import com.yfd.platform.qgc_sys.mapcfg.service.IMapmoduleBService;
|
||||
import com.yfd.platform.utils.DataSourceRequestUtil;
|
||||
@ -90,4 +91,12 @@ public class MsAlongBController {
|
||||
public ResponseResult getWbsbList(@RequestParam String stcd) {
|
||||
return ResponseResult.successData(msAlongBService.getWbsbList(stcd));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/noAuth/GetKendoPage")
|
||||
@Operation(summary = "查询水电基地流域字典分页列表")
|
||||
public ResponseResult getKendoPage(@RequestBody DataSourceRequest dataSourceRequest) {
|
||||
DataSourceResult<PowerDto> result = msAlongBService.getKendoPage(dataSourceRequest);
|
||||
return ResponseResult.successData(result);
|
||||
}
|
||||
}
|
||||
@ -84,4 +84,12 @@ public class MsAlongB implements Serializable {
|
||||
/** 排序 */
|
||||
@TableField("ORDER_INDEX")
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 水电基地编码 */
|
||||
@TableField("BASEID")
|
||||
private String baseId;
|
||||
|
||||
/** 水电基地名称 */
|
||||
@TableField("BASENAME")
|
||||
private String baseName;
|
||||
}
|
||||
@ -82,4 +82,10 @@ public class MsAlongDetB implements Serializable {
|
||||
/** 删除时间 */
|
||||
@TableField("DELETE_TIME")
|
||||
private Date deleteTime;
|
||||
|
||||
|
||||
/** 出库入库标识 */
|
||||
@TableField("IO_DIRECTION")
|
||||
private String ioDirection;
|
||||
|
||||
}
|
||||
@ -14,5 +14,6 @@ public class MsAlongDetTreeDto {
|
||||
private String stnm; // 仅用于显示,不存表
|
||||
private String rstcd; // 仅用于显示,不存表
|
||||
private String parentId; // 前端用,不存表
|
||||
private String ioDirection; // 前端用,不存表
|
||||
private List<MsAlongDetTreeDto> children; // 子节点
|
||||
}
|
||||
|
||||
@ -1,26 +1,191 @@
|
||||
package com.yfd.platform.qgc_lygk.along.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@Data
|
||||
@TableName("V_MS_STBPRP_T")
|
||||
public class PowerDto {
|
||||
// 水电水利工程编码
|
||||
private String stcd;
|
||||
|
||||
// 水电水利工程名称
|
||||
private String stnm;
|
||||
// ==================== 基础编码与名称 ====================
|
||||
@TableField("STCD")
|
||||
private String stcd; // 水电水利工程编码
|
||||
|
||||
// 水电水利工程名称
|
||||
private String sttp;
|
||||
@TableField("STNM")
|
||||
private String stnm; // 水电水利工程名称(中文)
|
||||
|
||||
// 主表实体
|
||||
private String sttpCode;
|
||||
@TableField("ENNM")
|
||||
private String ennm; // 水电水利工程名称(英文/简称)
|
||||
|
||||
// 主表实体
|
||||
private String sttpName;
|
||||
// ==================== 测站类型(STTP) ====================
|
||||
@TableField("STTP")
|
||||
private String sttp; // 测站类型代码(如 WE、EQ_5)
|
||||
|
||||
// 主表实体
|
||||
private String rstcd;
|
||||
@TableField("STTP_CODE")
|
||||
private String sttpCode; // 测站类型编码(与 STTP 相同)
|
||||
|
||||
@TableField("STTP_NAME")
|
||||
private String sttpName; // 测站类型名称(如 水生生态调查断面、基荷发电)
|
||||
|
||||
@TableField("STTP_FULL_PATH")
|
||||
private String sttpFullPath; // 测站类型完整路径(如 ENV,ENVM,WE,)
|
||||
|
||||
@TableField("STTP_TREE_LEVEL")
|
||||
private Integer sttpTreeLevel; // 测站类型树层级(如 3)
|
||||
|
||||
// ==================== 关联上级 ====================
|
||||
@TableField("RSTCD")
|
||||
private String rstcd; // 关联上级测站编码
|
||||
|
||||
// ==================== 流域信息(BASE) ====================
|
||||
@TableField("BASE_ID")
|
||||
private String baseId; // 流域ID(如 02)
|
||||
|
||||
@TableField("BASE_NAME")
|
||||
private String baseName; // 流域名称(如 雅砻江干流)
|
||||
|
||||
// ==================== 流域水系(HBRVCD) ====================
|
||||
@TableField("HBRVCD")
|
||||
private String hbrvcd; // 流域水系编码
|
||||
|
||||
@TableField("HBRVCD_NAME")
|
||||
private String hbrvcdName; // 流域水系名称
|
||||
|
||||
// ==================== 河流/流域(RVCD) ====================
|
||||
@TableField("RVCD")
|
||||
private String rvcd; // 河流/流域编码(如 SJLY176)
|
||||
|
||||
@TableField("RVCD_NAME")
|
||||
private String rvcdName; // 河流/流域名称(如 雅砻江干流)
|
||||
|
||||
// ==================== 行政区划(ADDVCD) ====================
|
||||
@TableField("ADDVCD")
|
||||
private String addvcd; // 行政区划编码
|
||||
|
||||
@TableField("ADDVNM")
|
||||
private String addvnm; // 行政区划名称
|
||||
|
||||
// ==================== 河流水系(HYCD) ====================
|
||||
@TableField("HYCD")
|
||||
private String hycd; // 河流水系编码
|
||||
|
||||
@TableField("HYNM")
|
||||
private String hynm; // 河流水系名称
|
||||
|
||||
@TableField("TOP_HYCD")
|
||||
private String topHycd; // 顶级河流水系编码
|
||||
|
||||
@TableField("TOP_HYNM")
|
||||
private String topHynm; // 顶级河流水系名称
|
||||
|
||||
// ==================== 地理坐标 ====================
|
||||
@TableField("LGTD")
|
||||
private BigDecimal lgtd; // 经度(如 100.780252)
|
||||
|
||||
@TableField("LTTD")
|
||||
private BigDecimal lttd; // 纬度(如 30.26872)
|
||||
|
||||
// ==================== 站点属性 ====================
|
||||
@TableField("STLC")
|
||||
private String stlc; // 站点位置描述
|
||||
|
||||
@TableField("USFL")
|
||||
private Integer usfl; // 是否启用(1启用 0禁用)
|
||||
|
||||
@TableField("DTIN")
|
||||
private String dtin; // 数据类型代码
|
||||
|
||||
@TableField("DTIN_TM")
|
||||
private Date dtinTm; // 数据类型更新时间
|
||||
|
||||
@TableField("BLDSTT_CODE")
|
||||
private String bldsttCode; // 建设状态代码
|
||||
|
||||
// ==================== 描述与附件 ====================
|
||||
@TableField("INTRODUCE")
|
||||
private String introduce; // 介绍/描述
|
||||
|
||||
@TableField("LOGO")
|
||||
private String logo; // LOGO 图片
|
||||
|
||||
@TableField("INFFILE")
|
||||
private String inffile; // 附件文件
|
||||
|
||||
// ==================== 投资与建设 ====================
|
||||
@TableField("INV")
|
||||
private String inv; // 投资方/投资信息
|
||||
|
||||
@TableField("INVINMN")
|
||||
private String invinmn; // 投资单位/法人
|
||||
|
||||
@TableField("STDSDT")
|
||||
private Date stdsdt; // 开工日期
|
||||
|
||||
@TableField("PSTSTDT")
|
||||
private Date pststdt; // 计划竣工日期
|
||||
|
||||
@TableField("PESSTDT")
|
||||
private Date pesstdt; // 实际竣工日期
|
||||
|
||||
// ==================== 水文参数 ====================
|
||||
@TableField("SWDT")
|
||||
private String swdt; // 水文数据类型
|
||||
|
||||
@TableField("JCDT")
|
||||
private String jcdt; // 监测数据类型
|
||||
|
||||
@TableField("WDDT")
|
||||
private String wddt; // 温度数据类型
|
||||
|
||||
@TableField("ORDER_INDEX")
|
||||
private Integer orderIndex; // 排序序号
|
||||
|
||||
@TableField("REMARK")
|
||||
private String remark; // 备注
|
||||
|
||||
@TableField("VLSR")
|
||||
private String vlsr; // 流速
|
||||
|
||||
@TableField("VLSR_TM")
|
||||
private Date vlsrTm; // 流速时间
|
||||
|
||||
// ==================== 数据源与运行状态 ====================
|
||||
@TableField("DTIN_TYPE")
|
||||
private String dtinType; // 数据类型分类
|
||||
|
||||
@TableField("MWAY")
|
||||
private String mway; // 测量方式
|
||||
|
||||
@TableField("RUN_STATE")
|
||||
private String runState; // 运行状态(如 1-运行中)
|
||||
|
||||
// ==================== 审计字段(通用) ====================
|
||||
@TableField("RECORD_USER")
|
||||
private String recordUser; // 创建人
|
||||
|
||||
@TableField("RECORD_TIME")
|
||||
private Date recordTime; // 创建时间
|
||||
|
||||
@TableField("MODIFY_USER")
|
||||
private String modifyUser; // 修改人
|
||||
|
||||
@TableField("MODIFY_TIME")
|
||||
private Date modifyTime; // 修改时间
|
||||
|
||||
@TableField("IS_DELETED")
|
||||
private Integer isDeleted; // 软删除(0未删 1已删)
|
||||
|
||||
@TableField("DELETE_USER")
|
||||
private String deleteUser; // 删除人
|
||||
|
||||
@TableField("DELETE_TIME")
|
||||
private Date deleteTime; // 删除时间
|
||||
|
||||
// ==================== 数据来源 ====================
|
||||
@TableField("DATA_SOURCE")
|
||||
private String dataSource; // 数据来源(如 SD_WE_B_H)
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@ public interface IMsAlongBService extends IService<MsAlongB> {
|
||||
|
||||
List<PowerDto> getWbsbList(String stcd);
|
||||
|
||||
DataSourceResult<PowerDto> getKendoPage(DataSourceRequest dataSourceRequest);
|
||||
|
||||
// /**
|
||||
// * 保存或更新沿程配置
|
||||
// *
|
||||
|
||||
@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.yfd.platform.common.DataSourceLoadOptionsBase;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.common.DataSourceResult;
|
||||
import com.yfd.platform.common.MicroservicDynamicSQLMapper;
|
||||
import com.yfd.platform.qgc_base.domain.SdRvcdDic;
|
||||
import com.yfd.platform.qgc_base.mapper.SdRvcdDicMapper;
|
||||
import com.yfd.platform.qgc_lygk.along.domain.MsAlongB;
|
||||
@ -17,6 +18,9 @@ import com.yfd.platform.qgc_lygk.along.service.IMsAlongBService;
|
||||
import com.yfd.platform.utils.CodeToNameMetadataBo;
|
||||
import com.yfd.platform.utils.DataSourceRequestUtil;
|
||||
import com.yfd.platform.utils.DictCodeToNameConverter;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.common.DataSourceRequest.FilterDescriptor;
|
||||
import com.yfd.platform.common.DataSourceRequest.SortDescriptor;
|
||||
import com.yfd.platform.utils.QgcQueryWrapperUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -37,6 +41,9 @@ public class MsAlongBServiceImpl extends ServiceImpl<MsAlongBMapper, MsAlongB> i
|
||||
@Resource
|
||||
private SdRvcdDicMapper sdRvcdDicMapper;
|
||||
|
||||
@Resource
|
||||
private MicroservicDynamicSQLMapper microservicDynamicSQLMapper;
|
||||
|
||||
@Override
|
||||
public DataSourceResult<MsAlongB> getKendoList(DataSourceRequest dataSourceRequest) {
|
||||
// 1. 构建查询条件
|
||||
@ -101,6 +108,136 @@ public class MsAlongBServiceImpl extends ServiceImpl<MsAlongBMapper, MsAlongB> i
|
||||
return powerDtos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceResult<PowerDto> getKendoPage(DataSourceRequest dataSourceRequest) {
|
||||
// 1. 处理分页参数
|
||||
DataSourceLoadOptionsBase loadOptions = dataSourceRequest == null ? null : dataSourceRequest.toDevRequest();
|
||||
|
||||
// 2. 构建基础 SQL
|
||||
StringBuilder sql = new StringBuilder("SELECT * FROM V_MS_STBPRP_T WHERE 1=1");
|
||||
Map<String, Object> paramMap = new HashMap<>();
|
||||
|
||||
// 3. 解析过滤条件(利用你的 FilterDescriptor)
|
||||
if (dataSourceRequest != null && dataSourceRequest.getFilter() != null) {
|
||||
String filterSql = parseFilter(dataSourceRequest.getFilter(), paramMap, new int[]{0});
|
||||
if (StrUtil.isNotBlank(filterSql)) {
|
||||
sql.append(" AND ").append(filterSql);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 解析排序(利用你的 SortDescriptor)
|
||||
if (dataSourceRequest != null && CollUtil.isNotEmpty(dataSourceRequest.getSort())) {
|
||||
List<String> orderList = new ArrayList<>();
|
||||
for (SortDescriptor sort : dataSourceRequest.getSort()) {
|
||||
String field = sort.getField();
|
||||
if (StrUtil.isBlank(field) || !isSafeField(field)) continue;
|
||||
String direction = "asc".equalsIgnoreCase(sort.getDir()) ? "ASC" : "DESC";
|
||||
orderList.add(field + " " + direction);
|
||||
}
|
||||
if (!orderList.isEmpty()) {
|
||||
sql.append(" ORDER BY ").append(String.join(", ", orderList));
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 执行分页查询
|
||||
Page<?> page = loadOptions == null ? null :
|
||||
QgcQueryWrapperUtil.buildPage(loadOptions, loadOptions.getSkip(), loadOptions.getTake());
|
||||
List<PowerDto> list = microservicDynamicSQLMapper.pageAllListWithResultType(
|
||||
page, sql.toString(), paramMap, PowerDto.class
|
||||
);
|
||||
|
||||
// 6. 封装返回结果
|
||||
DataSourceResult<PowerDto> result = new DataSourceResult<>();
|
||||
result.setData(list);
|
||||
result.setTotal(page != null ? page.getTotal() : list.size());
|
||||
result.setAggregates(new HashMap<>());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归解析 FilterDescriptor(完美适配你的内部类结构)
|
||||
*/
|
||||
/**
|
||||
* 递归解析 FilterDescriptor
|
||||
*/
|
||||
/**
|
||||
* 递归解析 FilterDescriptor(Oracle 兼容版)
|
||||
*/
|
||||
private String parseFilter(FilterDescriptor filter, Map<String, Object> paramMap, int[] paramIndex) {
|
||||
if (filter == null) return "";
|
||||
|
||||
// 如果有嵌套条件(logic + filters)
|
||||
List<FilterDescriptor> childFilters = filter.getFilters();
|
||||
if (CollUtil.isNotEmpty(childFilters)) {
|
||||
List<String> childConditions = new ArrayList<>();
|
||||
for (FilterDescriptor child : childFilters) {
|
||||
String childSql = parseFilter(child, paramMap, paramIndex);
|
||||
if (StrUtil.isNotBlank(childSql)) {
|
||||
childConditions.add(childSql);
|
||||
}
|
||||
}
|
||||
if (childConditions.isEmpty()) return "";
|
||||
|
||||
String logic = StrUtil.isNotBlank(filter.getLogic()) ? filter.getLogic() : "and";
|
||||
String connector = "and".equalsIgnoreCase(logic) ? " AND " : " OR ";
|
||||
return "(" + String.join(connector, childConditions) + ")";
|
||||
}
|
||||
|
||||
// 叶子节点:单个条件
|
||||
String field = filter.getField();
|
||||
Object value = filter.getValue();
|
||||
String operator = filter.getOperator();
|
||||
|
||||
if (StrUtil.isBlank(field) || value == null) return "";
|
||||
if (!isSafeField(field)) return "";
|
||||
|
||||
String paramKey = "p" + paramIndex[0]++;
|
||||
paramMap.put(paramKey, value);
|
||||
|
||||
// 根据操作符生成 SQL(Oracle 使用 || 拼接)
|
||||
if ("eq".equalsIgnoreCase(operator)) {
|
||||
return field + " = #{map." + paramKey + "}";
|
||||
} else if ("neq".equalsIgnoreCase(operator)) {
|
||||
return field + " != #{map." + paramKey + "}";
|
||||
} else if ("contains".equalsIgnoreCase(operator)) {
|
||||
// Oracle 用 || 替代 CONCAT
|
||||
return field + " LIKE '%' || #{map." + paramKey + "} || '%'";
|
||||
} else if ("startswith".equalsIgnoreCase(operator)) {
|
||||
return field + " LIKE #{map." + paramKey + "} || '%'";
|
||||
} else if ("endswith".equalsIgnoreCase(operator)) {
|
||||
return field + " LIKE '%' || #{map." + paramKey + "}";
|
||||
} else if ("gt".equalsIgnoreCase(operator)) {
|
||||
return field + " > #{map." + paramKey + "}";
|
||||
} else if ("gte".equalsIgnoreCase(operator)) {
|
||||
return field + " >= #{map." + paramKey + "}";
|
||||
} else if ("lt".equalsIgnoreCase(operator)) {
|
||||
return field + " < #{map." + paramKey + "}";
|
||||
} else if ("lte".equalsIgnoreCase(operator)) {
|
||||
return field + " <= #{map." + paramKey + "}";
|
||||
} else if ("isnull".equalsIgnoreCase(operator)) {
|
||||
paramMap.remove(paramKey);
|
||||
return field + " IS NULL";
|
||||
} else if ("isnotnull".equalsIgnoreCase(operator)) {
|
||||
paramMap.remove(paramKey);
|
||||
return field + " IS NOT NULL";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全字段白名单(根据你的视图字段调整)
|
||||
*/
|
||||
private boolean isSafeField(String field) {
|
||||
if (StrUtil.isBlank(field)) return false;
|
||||
// 支持视图中的常用字段
|
||||
Set<String> validFields = new HashSet<>(Arrays.asList(
|
||||
"STCD", "STNM", "ENNM", "BASE_ID", "BASE_NAME", "RVCD", "RVCD_NAME",
|
||||
"ADDVCD", "ADDVNM", "HYCD", "HYNM", "STTP", "STTP_CODE", "STTP_NAME",
|
||||
"LGTD", "LTTD", "ORDER_INDEX", "RUN_STATE"
|
||||
));
|
||||
return validFields.contains(field.toUpperCase());
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean saveOrUpdate(MsAlongB msAlongB) {
|
||||
// super.saveOrUpdate(msAlongB);
|
||||
|
||||
@ -149,6 +149,7 @@ public class MsAlongDetBServiceImpl extends ServiceImpl<MsAlongDetBMapper, MsAlo
|
||||
entity.setAlongId(node.getAlongId());
|
||||
entity.setStcd(node.getStcd());
|
||||
entity.setSttp(node.getSttp());
|
||||
entity.setIoDirection(node.getIoDirection());
|
||||
entity.setRstcd(node.getRstcd());
|
||||
entity.setRecordUser(SecurityUtils.getUserId()); // 创建人
|
||||
//entity.setRecordUser(SecurityUtils.getCurrentUsername()); // 创建人
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
package com.yfd.platform.qgc_sys.obliqueImage.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.common.DataSourceResult;
|
||||
import com.yfd.platform.common.exception.BizException;
|
||||
import com.yfd.platform.config.ResponseResult;
|
||||
import com.yfd.platform.qgc_sys.obliqueImage.domain.MsObliqueImageB;
|
||||
import com.yfd.platform.qgc_sys.obliqueImage.service.IMsObliqueImageBService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 倾斜摄影管理控制器
|
||||
*
|
||||
* @author migration
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/sys/oblique")
|
||||
@Tag(name = "倾斜摄影管理控制器")
|
||||
public class MsObliqueImageBController {
|
||||
|
||||
@Resource
|
||||
private IMsObliqueImageBService msObliqueImageBService;
|
||||
|
||||
/**
|
||||
* 条件过滤数据列表
|
||||
*/
|
||||
@Operation(summary = "条件过滤数据列表")
|
||||
@PostMapping("/GetKendoListCust")
|
||||
public ResponseResult getKendoListCust(@RequestBody DataSourceRequest dataSourceRequest) {
|
||||
DataSourceResult<MsObliqueImageB> result = msObliqueImageBService.getKendoList(dataSourceRequest);
|
||||
return ResponseResult.successData(result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
*/
|
||||
@Operation(summary = "新增或修改")
|
||||
@PostMapping("/addOrUpdate")
|
||||
public ResponseResult addOrUpdate(@RequestBody MsObliqueImageB msObliqueImageB) {
|
||||
msObliqueImageBService.addOrUpdate(msObliqueImageB);
|
||||
return ResponseResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
@Operation(summary = "删除")
|
||||
@GetMapping("/delete")
|
||||
public ResponseResult delete(@RequestParam("id") String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
throw new BizException("ID不能为空");
|
||||
}
|
||||
msObliqueImageBService.delete(id);
|
||||
return ResponseResult.success();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
package com.yfd.platform.qgc_sys.obliqueImage.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 倾斜摄影管理实体类
|
||||
*
|
||||
* @author migration
|
||||
*/
|
||||
@Data
|
||||
@TableName("MS_OBL_PHOTO")
|
||||
@Schema(description = "倾斜摄影管理实体类")
|
||||
public class MsObliqueImageB implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String id;
|
||||
|
||||
/** 站码 */
|
||||
@TableField("STCD")
|
||||
private String stcd;
|
||||
|
||||
/** 站名 */
|
||||
@TableField("STNM")
|
||||
private String stnm;
|
||||
|
||||
/** 所属站类ID */
|
||||
@TableField("STTP")
|
||||
private String sttp;
|
||||
|
||||
/** 所属站类编码 */
|
||||
@TableField("STTP_CODE")
|
||||
private String sttpCode;
|
||||
|
||||
/** 所属站类名称 */
|
||||
@TableField("STTP_NAME")
|
||||
private String sttpName;
|
||||
|
||||
/** 所属站类全路径(非数据库字段) */
|
||||
@TableField(exist = false)
|
||||
private String sttpFullPath;
|
||||
|
||||
/** 所属站类层级(非数据库字段) */
|
||||
@TableField(exist = false)
|
||||
private Integer sttpTreeLevel;
|
||||
|
||||
/** 所属水电基地编码 */
|
||||
@TableField("BASE_ID")
|
||||
private String baseId;
|
||||
|
||||
/** 所属水电基地名称 */
|
||||
@TableField("BASE_NAME")
|
||||
private String baseName;
|
||||
|
||||
/** 所属流域编码 */
|
||||
@TableField("HBRVCD")
|
||||
private String hbrvcd;
|
||||
|
||||
/** 所属流域名称 */
|
||||
@TableField("HBRVCD_NAME")
|
||||
private String hbrvcdName;
|
||||
|
||||
/** 所属河段编码(非数据库字段,与HBRVCD同源) */
|
||||
@TableField(exist = false)
|
||||
private String rvcd;
|
||||
|
||||
/** 所属河段名称(非数据库字段,与HBRVCD_NAME同源) */
|
||||
@TableField(exist = false)
|
||||
private String rvcdName;
|
||||
|
||||
/** 在线地址 */
|
||||
@TableField("URL")
|
||||
private String url;
|
||||
|
||||
/** 所属电站编码 */
|
||||
@TableField("RSTCD")
|
||||
private String rstcd;
|
||||
|
||||
/** 所属电站名称 */
|
||||
@TableField("ENNM")
|
||||
private String ennm;
|
||||
|
||||
/** 电站经度(非数据库字段) */
|
||||
@TableField("LGTD")
|
||||
private Double lgtd;
|
||||
|
||||
/** 电站纬度(非数据库字段) */
|
||||
@TableField("LTTD")
|
||||
private Double lttd;
|
||||
|
||||
/** 电站坐标JSON串 */
|
||||
@TableField("LOCATION")
|
||||
private String location;
|
||||
|
||||
/** 模型精度 */
|
||||
@TableField("ACCURACY")
|
||||
private Integer accuracy;
|
||||
|
||||
/** 边界范围 */
|
||||
@TableField("BOUNDARY")
|
||||
private String boundary;
|
||||
|
||||
/** 自定义高程 */
|
||||
@TableField("HEIGHT")
|
||||
private Integer height;
|
||||
|
||||
/** 备注 */
|
||||
@TableField("COMMENTS")
|
||||
private String comments;
|
||||
|
||||
/** 创建人 */
|
||||
@TableField("RECORD_USER")
|
||||
private String recordUser;
|
||||
|
||||
/** 创建人名称(非数据库字段) */
|
||||
@TableField(exist = false)
|
||||
private String recordUserName;
|
||||
|
||||
/** 创建时间(非数据库字段) */
|
||||
@TableField(exist = false)
|
||||
private Date recordTime;
|
||||
|
||||
/** 更新人 */
|
||||
@TableField("MODIFY_USER")
|
||||
private String modifyUser;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField("MODIFY_TIME")
|
||||
private Date modifyTime;
|
||||
|
||||
/** 是否已删除:0=未删除 1=已删除 */
|
||||
@TableField("IS_DELETED")
|
||||
private Integer isDeleted;
|
||||
|
||||
/** 删除人 */
|
||||
@TableField("DELETE_USER")
|
||||
private String deleteUser;
|
||||
|
||||
/** 删除时间 */
|
||||
@TableField("DELETE_TIME")
|
||||
private Date deleteTime;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.yfd.platform.qgc_sys.obliqueImage.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.yfd.platform.qgc_sys.obliqueImage.domain.MsObliqueImageB;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 倾斜摄影管理 Mapper 接口
|
||||
*
|
||||
* @author migration
|
||||
*/
|
||||
@Mapper
|
||||
public interface MsObliqueImageBMapper extends BaseMapper<MsObliqueImageB> {
|
||||
|
||||
@Delete("DELETE FROM MS_OBL_PHOTO WHERE ID = #{id}")
|
||||
void deleteById(@Param("id") String id);
|
||||
|
||||
@Select("SELECT ID FROM SD_STTP_B WHERE STTP_CODE = 'OSGB'")
|
||||
String getSttp();
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.yfd.platform.qgc_sys.obliqueImage.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.common.DataSourceResult;
|
||||
import com.yfd.platform.qgc_sys.obliqueImage.domain.MsObliqueImageB;
|
||||
|
||||
/**
|
||||
* 倾斜摄影管理 Service 接口
|
||||
*
|
||||
* @author migration
|
||||
*/
|
||||
public interface IMsObliqueImageBService extends IService<MsObliqueImageB> {
|
||||
|
||||
/**
|
||||
* 条件过滤数据列表(Kendo 数据源)
|
||||
*
|
||||
* @param dataSourceRequest 数据源请求
|
||||
* @return 数据源结果
|
||||
*/
|
||||
DataSourceResult<MsObliqueImageB> getKendoList(DataSourceRequest dataSourceRequest);
|
||||
|
||||
/**
|
||||
* 新增或修改
|
||||
*
|
||||
* @param msObliqueImageB 倾斜摄影实体
|
||||
*/
|
||||
void addOrUpdate(MsObliqueImageB msObliqueImageB);
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param id 主键ID
|
||||
*/
|
||||
void delete(String id);
|
||||
}
|
||||
@ -0,0 +1,178 @@
|
||||
package com.yfd.platform.qgc_sys.obliqueImage.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.yfd.platform.common.DataSourceLoadOptionsBase;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.common.DataSourceResult;
|
||||
import com.yfd.platform.common.exception.BizException;
|
||||
import com.yfd.platform.qgc_base.domain.SdEngInfoBH;
|
||||
import com.yfd.platform.qgc_base.service.ISdEngInfoBHService;
|
||||
import com.yfd.platform.qgc_sys.obliqueImage.domain.MsObliqueImageB;
|
||||
import com.yfd.platform.qgc_sys.obliqueImage.mapper.MsObliqueImageBMapper;
|
||||
import com.yfd.platform.qgc_sys.obliqueImage.service.IMsObliqueImageBService;
|
||||
import com.yfd.platform.utils.DataSourceRequestUtil;
|
||||
import com.yfd.platform.utils.QgcQueryWrapperUtil;
|
||||
import com.yfd.platform.utils.QueryWrapperUtil;
|
||||
import com.yfd.platform.utils.SecurityUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.Data;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 倾斜摄影管理 Service 实现类
|
||||
*
|
||||
* @author migration
|
||||
*/
|
||||
@Service
|
||||
public class MsObliqueImageBServiceImpl extends ServiceImpl<MsObliqueImageBMapper, MsObliqueImageB> implements IMsObliqueImageBService {
|
||||
|
||||
@Resource
|
||||
private MsObliqueImageBMapper msObliqueImageBMapper;
|
||||
|
||||
@Resource
|
||||
private ISdEngInfoBHService engInfoBHService;
|
||||
|
||||
@Override
|
||||
public DataSourceResult<MsObliqueImageB> getKendoList(DataSourceRequest dataSourceRequest) {
|
||||
// 1. 构建查询条件
|
||||
DataSourceLoadOptionsBase loadOptions = dataSourceRequest == null ? null : dataSourceRequest.toDevRequest();
|
||||
|
||||
QueryWrapper<MsObliqueImageB> wrapper = DataSourceRequestUtil.buildQueryWrapper(
|
||||
dataSourceRequest, MsObliqueImageB.class);
|
||||
|
||||
// 固定条件:STTP_CODE = 'OSGB' AND IS_DELETED = 0 AND RSTCD IS NOT NULL
|
||||
wrapper.eq("STTP_CODE", "OSGB");
|
||||
wrapper.eq("IS_DELETED", 0);
|
||||
wrapper.isNotNull("RSTCD");
|
||||
|
||||
// 旧系统 rvcd 字段映射到 HBRVCD 列(前端传参保持 rvcd 不变)
|
||||
if (loadOptions != null) {
|
||||
String rvcd = QueryWrapperUtil.getFilterFieldValue(loadOptions, "rvcd");
|
||||
if (StrUtil.isNotBlank(rvcd)) {
|
||||
wrapper.eq("HBRVCD", rvcd);
|
||||
}
|
||||
}
|
||||
|
||||
// 默认按修改时间倒序
|
||||
wrapper.orderByDesc("MODIFY_TIME");
|
||||
|
||||
// 2. 分页参数处理
|
||||
Page<MsObliqueImageB> page = loadOptions == null ? null
|
||||
: (Page<MsObliqueImageB>) QgcQueryWrapperUtil.buildPage(loadOptions, loadOptions.getSkip(), loadOptions.getTake());
|
||||
|
||||
// 3. 执行查询
|
||||
Page<MsObliqueImageB> resultPage;
|
||||
if (page != null) {
|
||||
resultPage = this.page(page, wrapper);
|
||||
} else {
|
||||
List<MsObliqueImageB> list = this.list(wrapper);
|
||||
resultPage = new Page<>();
|
||||
resultPage.setRecords(list);
|
||||
resultPage.setTotal(list.size());
|
||||
}
|
||||
|
||||
// 4. 构建结果
|
||||
DataSourceResult<MsObliqueImageB> dataSourceResult = new DataSourceResult<>();
|
||||
dataSourceResult.setData(resultPage.getRecords());
|
||||
dataSourceResult.setTotal(resultPage.getTotal());
|
||||
return dataSourceResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOrUpdate(MsObliqueImageB msObliqueImageB) {
|
||||
if (StrUtil.isNotBlank(msObliqueImageB.getRstcd())){
|
||||
|
||||
LambdaQueryWrapper<SdEngInfoBH> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(StrUtil.isNotBlank(msObliqueImageB.getRstcd()), SdEngInfoBH::getStcd, msObliqueImageB.getRstcd())
|
||||
.eq(SdEngInfoBH::getIsDeleted, 0)
|
||||
.select(SdEngInfoBH::getLgtd, SdEngInfoBH::getLttd, SdEngInfoBH::getBaseId, SdEngInfoBH::getBaseName)
|
||||
.orderByAsc(SdEngInfoBH::getBaseId, SdEngInfoBH::getOrderIndex);
|
||||
SdEngInfoBH sdEngInfoBH = engInfoBHService.getOne(wrapper);
|
||||
if (sdEngInfoBH.getLgtd() != null){
|
||||
msObliqueImageB.setLgtd(sdEngInfoBH.getLgtd());
|
||||
}
|
||||
if (sdEngInfoBH.getLttd() != null){
|
||||
msObliqueImageB.setLttd(sdEngInfoBH.getLttd());
|
||||
}
|
||||
if (StrUtil.isNotBlank(sdEngInfoBH.getBaseName())){
|
||||
msObliqueImageB.setBaseName(sdEngInfoBH.getBaseName());
|
||||
}
|
||||
if (StrUtil.isNotBlank(sdEngInfoBH.getBaseId())){
|
||||
msObliqueImageB.setBaseId(sdEngInfoBH.getBaseId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(msObliqueImageB.getId())) {
|
||||
addMsObliqueImageB(msObliqueImageB);
|
||||
} else {
|
||||
updateMsObliqueImageB(msObliqueImageB);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String id) {
|
||||
msObliqueImageBMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void addMsObliqueImageB(MsObliqueImageB msObliqueImageB) {
|
||||
// 查询新增的倾斜摄影是否已存在
|
||||
String rstcd = msObliqueImageB.getRstcd();
|
||||
if (StrUtil.isBlank(rstcd)) {
|
||||
throw new BizException("电站不能为空");
|
||||
}
|
||||
String id = "OSGB_" + rstcd;
|
||||
MsObliqueImageB old = msObliqueImageBMapper.selectById(id);
|
||||
if (old != null) {
|
||||
throw new BizException("该电站已存在倾斜摄影");
|
||||
}
|
||||
msObliqueImageB.setSttpCode("OSGB");
|
||||
msObliqueImageB.setSttpName("倾斜摄影");
|
||||
if (StrUtil.isBlank(msObliqueImageB.getRvcd())) {
|
||||
msObliqueImageB.setRvcd(msObliqueImageB.getHbrvcd());
|
||||
}
|
||||
if (StrUtil.isBlank(msObliqueImageB.getRvcdName())) {
|
||||
msObliqueImageB.setRvcdName(msObliqueImageB.getHbrvcdName());
|
||||
}
|
||||
msObliqueImageB.setStcd("OSGB_" + msObliqueImageB.getRstcd());
|
||||
msObliqueImageB.setId("OSGB_" + msObliqueImageB.getRstcd());
|
||||
msObliqueImageB.setSttpFullPath("OSGB,");
|
||||
msObliqueImageB.setSttpTreeLevel(1);
|
||||
String sttp = msObliqueImageBMapper.getSttp();
|
||||
if (StrUtil.isBlank(sttp)) {
|
||||
throw new BizException("倾斜摄影对应的sttp在SD_STTP_B中找不到");
|
||||
}
|
||||
msObliqueImageB.setSttp(sttp);
|
||||
// 设置通用字段
|
||||
String userId = currentUserIdOrSystem();
|
||||
msObliqueImageB.setRecordUser(userId);
|
||||
Date now = new java.util.Date();
|
||||
msObliqueImageB.setModifyTime(now);
|
||||
msObliqueImageB.setModifyUser(userId);
|
||||
msObliqueImageB.setIsDeleted(0);
|
||||
msObliqueImageBMapper.insert(msObliqueImageB);
|
||||
}
|
||||
|
||||
private void updateMsObliqueImageB(MsObliqueImageB msObliqueImageB) {
|
||||
String userId = currentUserIdOrSystem();
|
||||
msObliqueImageB.setModifyUser(userId);
|
||||
msObliqueImageB.setModifyTime(new java.util.Date());
|
||||
this.updateById(msObliqueImageB);
|
||||
}
|
||||
|
||||
private String currentUserIdOrSystem() {
|
||||
try {
|
||||
return SecurityUtils.getUserId();
|
||||
} catch (Exception ex) {
|
||||
return "system";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,23 +4,24 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.yfd.platform.annotation.Log;
|
||||
import com.yfd.platform.common.DataSourceRequest;
|
||||
import com.yfd.platform.common.exception.BizException;
|
||||
import com.yfd.platform.config.ResponseResult;
|
||||
import com.yfd.platform.system.domain.PushConfig;
|
||||
import com.yfd.platform.system.domain.PushConfigTargetRequest;
|
||||
import com.yfd.platform.system.domain.PushHistory;
|
||||
import com.yfd.platform.system.domain.PushHistoryDetail;
|
||||
import com.yfd.platform.system.service.IPushConfigService;
|
||||
import com.yfd.platform.qgc_data.domain.ApprovalMain;
|
||||
import com.yfd.platform.system.domain.*;
|
||||
import com.yfd.platform.system.service.*;
|
||||
import com.yfd.platform.utils.DataSourceRequestUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.hutool.core.date.DateUtil.parseDate;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/system/pushConfig")
|
||||
@ -30,14 +31,32 @@ public class PushConfigController {
|
||||
@Resource
|
||||
private IPushConfigService pushConfigService;
|
||||
|
||||
@Resource
|
||||
private IUserService userService;
|
||||
|
||||
@Resource
|
||||
private ISysRoleService roleService;
|
||||
|
||||
@Resource
|
||||
private IPushHistoryService pushHistoryService;
|
||||
|
||||
@Resource
|
||||
private IPushHistoryDetailService pushHistoryDetailService;
|
||||
|
||||
|
||||
|
||||
@Operation(summary = "查询推送配置列表")
|
||||
@GetMapping("/getPushConfigList")
|
||||
public ResponseResult getPushConfigList(Page<PushConfig> page,
|
||||
String categoryName,
|
||||
String messageType,
|
||||
Integer targetType,
|
||||
Integer status) {
|
||||
return ResponseResult.successData(pushConfigService.getPushConfigPage(page, categoryName, messageType, targetType, status));
|
||||
@PostMapping("/getPushConfigList")
|
||||
public ResponseResult getPushConfigList(@RequestBody DataSourceRequest request) {
|
||||
|
||||
Page<PushConfig> pushConfigPage = DataSourceRequestUtil.executeQuery(request, PushConfig.class, pushConfigService);
|
||||
return ResponseResult.successData(pushConfigPage);
|
||||
|
||||
// String categoryName = null;
|
||||
// String messageType = null;
|
||||
// Integer targetType = null;
|
||||
// Integer status = null;
|
||||
// return ResponseResult.successData(pushConfigService.getPushConfigPage(page, categoryName, messageType, targetType, status));
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID查询推送配置")
|
||||
@ -63,7 +82,7 @@ public class PushConfigController {
|
||||
@Log(module = "推送配置", value = "删除推送配置")
|
||||
@Operation(summary = "删除推送配置")
|
||||
@PostMapping("/deletePushConfig")
|
||||
public ResponseResult deletePushConfig(@RequestParam String ids) {
|
||||
public ResponseResult deletePushConfig(@RequestBody List<String> ids) {
|
||||
return pushConfigService.deletePushConfig(ids) ? ResponseResult.success() : ResponseResult.error("删除失败");
|
||||
}
|
||||
|
||||
@ -102,23 +121,78 @@ public class PushConfigController {
|
||||
}
|
||||
|
||||
@Operation(summary = "查询推送历史主表")
|
||||
@GetMapping("/getPushHistoryList")
|
||||
public ResponseResult getPushHistoryList(Page<PushHistory> page,
|
||||
String configId,
|
||||
Integer status,
|
||||
String messageTypeCode,
|
||||
String startTime,
|
||||
String endTime) {
|
||||
Date start = StrUtil.isBlank(startTime) ? null : DateUtil.parse(startTime);
|
||||
Date end = StrUtil.isBlank(endTime) ? null : DateUtil.parse(endTime);
|
||||
return ResponseResult.successData(pushConfigService.getPushHistoryPage(page, configId, status, messageTypeCode, start, end));
|
||||
@PostMapping("/postPushHistoryList")
|
||||
public ResponseResult getPushHistoryList(@RequestBody DataSourceRequest request) {
|
||||
// 1. 预处理日期过滤器
|
||||
List<DataSourceRequest.FilterDescriptor> filters = request.getFilter().getFilters();
|
||||
for (DataSourceRequest.FilterDescriptor filter : filters) {
|
||||
String field = filter.getField();
|
||||
// 判断是否为日期字段(根据你的实体字段名)
|
||||
if (isDateField(field)) {
|
||||
Object value = filter.getValue();
|
||||
if (value != null) {
|
||||
String strValue = value.toString();
|
||||
if (StrUtil.isNotBlank(strValue)) {
|
||||
Date dateValue = parseDate(strValue);
|
||||
// 关键:将转换后的 Date 对象重新设置回 filter
|
||||
filter.setValue(dateValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 调用通用查询
|
||||
Page<PushHistory> pushHistoryPage = DataSourceRequestUtil.executeQuery(request, PushHistory.class, pushHistoryService);
|
||||
return ResponseResult.successData(pushHistoryPage);
|
||||
}
|
||||
|
||||
// 辅助方法:判断字段是否为日期类型(根据你的实体类字段名)
|
||||
private boolean isDateField(String field) {
|
||||
// 这些字段在 PushHistory 中都是 Date 类型
|
||||
return "planTime".equals(field)
|
||||
|| "createdAt".equals(field)
|
||||
|| "updatedAt".equals(field)
|
||||
|| "modifyTime".equals(field)
|
||||
|| "deleteTime".equals(field);
|
||||
}
|
||||
|
||||
// 辅助方法:解析日期字符串
|
||||
private Date parseDate(String dateStr) {
|
||||
try {
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateStr);
|
||||
} catch (ParseException e) {
|
||||
throw new BizException("日期格式错误,请使用 yyyy-MM-dd HH:mm:ss 格式: " + dateStr);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "查询推送历史明细")
|
||||
@GetMapping("/getPushHistoryDetailList")
|
||||
public ResponseResult getPushHistoryDetailList(Page<PushHistoryDetail> page,
|
||||
String historyId,
|
||||
Integer status) {
|
||||
return ResponseResult.successData(pushConfigService.getPushHistoryDetailPage(page, historyId, status));
|
||||
@PostMapping("/getPushHistoryDetailList")
|
||||
public ResponseResult getPushHistoryDetailList(@RequestBody DataSourceRequest request) {
|
||||
Page<PushHistoryDetail> pushHistoryDetailPage = DataSourceRequestUtil.executeQuery(request, PushHistoryDetail.class, pushHistoryDetailService);
|
||||
return ResponseResult.successData(pushHistoryDetailPage);
|
||||
}
|
||||
|
||||
@GetMapping("/queryUsersList")
|
||||
@Operation(summary = "查询状态正常的用户列表")
|
||||
@ResponseBody
|
||||
public ResponseResult queryUsersList( @RequestParam(value = "name", required = false) String name,@RequestHeader(value = "Tenant_id", required = false) String tenantId) {
|
||||
|
||||
List<SysUser> sysUserList = userService.queryUsersList(name,StrUtil.trimToNull(tenantId));
|
||||
return ResponseResult.successData(sysUserList);
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "根据historyId查推送历史明细详情")
|
||||
@GetMapping("/getPushHistoryDetailById")
|
||||
public ResponseResult getPushHistoryDetailById(@RequestParam String historyId) {
|
||||
return ResponseResult.successData(pushHistoryDetailService.getPushHistoryDetailById(historyId));
|
||||
}
|
||||
|
||||
@GetMapping("/queryRolesList")
|
||||
@Operation(summary = "查询角色类型列表")
|
||||
@ResponseBody
|
||||
public ResponseResult queryRolesList( @RequestParam(value = "roleName", required = false) String roleName,@RequestHeader(value = "Tenant_id", required = false) String tenantId) {
|
||||
List<SysRole> sysRoleList = roleService.queryRolesList(roleName,StrUtil.trimToNull(tenantId));
|
||||
return ResponseResult.successData(sysRoleList);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package com.yfd.platform.system.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -15,6 +13,7 @@ public class PushConfig implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
@TableField("ID")
|
||||
private String id;
|
||||
|
||||
private String categoryCode;
|
||||
@ -41,16 +40,20 @@ public class PushConfig implements Serializable {
|
||||
|
||||
private Integer status;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createdAt;
|
||||
|
||||
private Date updatedAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String recordUser;
|
||||
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private String modifyUser;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date modifyTime;
|
||||
|
||||
/** 是否已删除:0=未删除 1=已删除 */
|
||||
@TableField("IS_DELETED")
|
||||
private Integer isDeleted;
|
||||
|
||||
private String deleteUser;
|
||||
|
||||
@ -12,10 +12,16 @@ public class PushConfigTargetRequest implements Serializable {
|
||||
|
||||
private String configId;
|
||||
|
||||
// /**
|
||||
// * 0-用户,1-角色
|
||||
// */
|
||||
// private Integer type;
|
||||
|
||||
/**
|
||||
* 0-用户,1-角色
|
||||
* 1-所有人,2-按人,3-按角色
|
||||
*/
|
||||
private Integer type;
|
||||
private Integer targetType;
|
||||
|
||||
|
||||
private List<String> userRoleIds;
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package com.yfd.platform.system.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PushHistoryDetailStatistics {
|
||||
private Integer pendingCount; // 待推送(0)
|
||||
private Integer successCount; // 成功(1)
|
||||
private Integer failCount; // 失败(2)
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.yfd.platform.system.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PushTargetVo implements Serializable {
|
||||
|
||||
private Integer targetType;
|
||||
|
||||
private List<String> userRoleIds;
|
||||
private List<PushConfigTargetVo> pushConfigTargetVoList;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.yfd.platform.system.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
@Data
|
||||
public class SdWtbhVo implements Serializable{
|
||||
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String stcd;
|
||||
|
||||
private String stnm;
|
||||
|
||||
private String baseId;
|
||||
|
||||
private String baseName;
|
||||
|
||||
private String sttpCode;
|
||||
|
||||
private String sttpName;
|
||||
|
||||
private String rstcd;
|
||||
|
||||
private String warnState;
|
||||
}
|
||||
@ -2,6 +2,20 @@ package com.yfd.platform.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.yfd.platform.system.domain.PushHistoryDetail;
|
||||
import com.yfd.platform.system.domain.PushHistoryDetailStatistics;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@Mapper
|
||||
public interface PushHistoryDetailMapper extends BaseMapper<PushHistoryDetail> {
|
||||
@Select("SELECT " +
|
||||
"COUNT(CASE WHEN STATUS = 0 THEN 1 END) AS pendingCount, " +
|
||||
"COUNT(CASE WHEN STATUS = 1 THEN 1 END) AS successCount, " +
|
||||
"COUNT(CASE WHEN STATUS = 2 THEN 1 END) AS failCount " +
|
||||
"FROM PUSH_HISTORY_DETAIL " +
|
||||
"WHERE HISTORY_ID = #{historyId}")
|
||||
PushHistoryDetailStatistics countByHistoryId(@Param("historyId") String historyId);
|
||||
}
|
||||
|
||||
@ -2,11 +2,7 @@ package com.yfd.platform.system.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yfd.platform.system.domain.PushConfig;
|
||||
import com.yfd.platform.system.domain.PushConfigTargetRequest;
|
||||
import com.yfd.platform.system.domain.PushConfigTargetVo;
|
||||
import com.yfd.platform.system.domain.PushHistory;
|
||||
import com.yfd.platform.system.domain.PushHistoryDetail;
|
||||
import com.yfd.platform.system.domain.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -23,7 +19,7 @@ public interface IPushConfigService extends IService<PushConfig> {
|
||||
|
||||
boolean updatePushConfig(PushConfig pushConfig);
|
||||
|
||||
boolean deletePushConfig(String ids);
|
||||
boolean deletePushConfig(List<String> ids);
|
||||
|
||||
boolean setPushConfigStatus(String id, Integer status);
|
||||
|
||||
@ -31,7 +27,7 @@ public interface IPushConfigService extends IService<PushConfig> {
|
||||
|
||||
boolean savePushTargets(PushConfigTargetRequest request);
|
||||
|
||||
List<PushConfigTargetVo> getPushTargets(String configId);
|
||||
PushTargetVo getPushTargets(String configId);
|
||||
|
||||
Page<PushHistory> getPushHistoryPage(Page<PushHistory> page,
|
||||
String configId,
|
||||
|
||||
@ -4,4 +4,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yfd.platform.system.domain.PushHistoryDetail;
|
||||
|
||||
public interface IPushHistoryDetailService extends IService<PushHistoryDetail> {
|
||||
Object getPushHistoryDetailById(String historyId);
|
||||
}
|
||||
|
||||
@ -65,4 +65,6 @@ public interface ISysRoleService extends IService<SysRole> {
|
||||
boolean setMenuById(String id, String menuIds);
|
||||
|
||||
List<SysRole> selectRoleList(String rolename, String tenantId);
|
||||
|
||||
List<SysRole> queryRolesList(String roleName, String tenantId);
|
||||
}
|
||||
|
||||
@ -183,4 +183,5 @@ public interface IUserService extends IService<SysUser> {
|
||||
************************************/
|
||||
Page<SysUser> queryPendingAuditUsers(Page<SysUser> page, String name, String regStatus, String tenantId);
|
||||
|
||||
List<SysUser> queryUsersList(String name, String tenantId);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,10 +2,23 @@ package com.yfd.platform.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.yfd.platform.system.domain.PushHistoryDetail;
|
||||
import com.yfd.platform.system.domain.PushHistoryDetailStatistics;
|
||||
import com.yfd.platform.system.mapper.PushHistoryDetailMapper;
|
||||
import com.yfd.platform.system.service.IPushHistoryDetailService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class PushHistoryDetailServiceImpl extends ServiceImpl<PushHistoryDetailMapper, PushHistoryDetail> implements IPushHistoryDetailService {
|
||||
@Autowired
|
||||
private PushHistoryDetailMapper pushHistoryDetailMapper;
|
||||
|
||||
@Override
|
||||
public Object getPushHistoryDetailById(String historyId) {
|
||||
return pushHistoryDetailMapper.countByHistoryId(historyId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.yfd.platform.system.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.yfd.platform.config.ResponseResult;
|
||||
@ -172,4 +174,15 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
||||
return roleMapper.selectRoleList(rolename, tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysRole> queryRolesList(String roleName, String tenantId) {
|
||||
LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(StrUtil.isNotBlank(tenantId), SysRole::getTenantId, tenantId);
|
||||
queryWrapper.like(StrUtil.isNotBlank(roleName),SysRole::getRolename, roleName);
|
||||
queryWrapper.ne(SysRole::getLevel, 1);
|
||||
queryWrapper.orderByDesc(SysRole::getLevel);
|
||||
List<SysRole> sysRoleList = this.list( queryWrapper);
|
||||
return sysRoleList;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -904,4 +904,20 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SysUser> queryUsersList(String name, String tenantId) {
|
||||
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(StrUtil.isNotBlank(tenantId), SysUser::getTenantId, tenantId);
|
||||
queryWrapper.eq(SysUser::getStatus, 1);
|
||||
queryWrapper.and(StrUtil.isNotBlank(name), wrapper ->
|
||||
wrapper.like(SysUser::getNickname, name)
|
||||
.or()
|
||||
.like(SysUser::getRealName, name)
|
||||
);
|
||||
queryWrapper.orderByDesc(SysUser::getRegTime);
|
||||
List<SysUser> sysUserList = this.list( queryWrapper);
|
||||
return sysUserList;
|
||||
}
|
||||
}
|
||||
|
||||
28
backend/src/main/java/com/yfd/platform/utils/CronUtil.java
Normal file
28
backend/src/main/java/com/yfd/platform/utils/CronUtil.java
Normal file
@ -0,0 +1,28 @@
|
||||
package com.yfd.platform.utils;
|
||||
|
||||
import cn.hutool.cron.pattern.CronPattern;
|
||||
import cn.hutool.cron.pattern.CronPatternUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.DateField;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Cron 表达式工具类(基于 Hutool 6.x)
|
||||
* 支持 6 位或 7 位表达式(秒 分 时 日 月 星期 [年])
|
||||
*/
|
||||
public class CronUtil {
|
||||
|
||||
/**
|
||||
* 校验 Cron 表达式是否合法
|
||||
* @param expression Cron 表达式(支持 6 或 7 个字段)
|
||||
* @return true-合法,false-非法
|
||||
*/
|
||||
public static boolean isValid(String expression) {
|
||||
try {
|
||||
CronPattern.of(expression);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,14 +7,14 @@ VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
|
||||
VITE_APP_PORT = 3000
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
# 本地环境
|
||||
# VITE_APP_BASE_URL = 'http://10.84.121.127:8093'
|
||||
VITE_APP_BASE_URL = 'http://localhost:8093'
|
||||
# 测试环境
|
||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
|
||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
||||
# 汤伟
|
||||
# VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
|
||||
VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
|
||||
# 李林
|
||||
VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
|
||||
# VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
|
||||
|
||||
# 测试环境线上10.84.121.122:
|
||||
VITE_APP_TEST_ONLINE_URL = 'https://211.99.26.225:12122'
|
||||
|
||||
@ -132,7 +132,7 @@
|
||||
|
||||
## 5. 分步骤实施计划
|
||||
|
||||
### Step 1:修正 3D 初始化并默认定位中国
|
||||
### Step 1:修正 3D 初始化并默认定位中国 ✅️
|
||||
|
||||
目标:
|
||||
|
||||
@ -157,7 +157,7 @@
|
||||
- 首屏有加载过渡。
|
||||
- 进入后稳定落在中国。
|
||||
|
||||
### Step 2:展示 3D 基础底图
|
||||
### Step 2:展示 3D 基础底图 ✅️
|
||||
|
||||
目标:
|
||||
|
||||
@ -171,6 +171,10 @@
|
||||
- 实现 `addBaseDataLayer(layer, isShow)`:
|
||||
- 对 `customBaseLayer` 使用 `url_3d`
|
||||
- 对 `BASEMAP-img`、`BASEMAP-white` 预留兼容能力
|
||||
- **必须处理 `wmts` 类型图层**:2D 通过 `WMTS` source 加载,3D 使用 `Cesium.WebMapTileServiceImageryProvider`,从 URL 参数中提取 LAYER/TILEMATRIXSET
|
||||
- **必须提供 `tileMatrixLabels`**:Cesium 默认用纯数字作为 tileMatrix 参数值,但本系统 WMTS 服务器要求 `TileMatrixSet:zoom` 格式(如 `EPSG:3857_hbb_zrbhq_l13:4`)。需构建 `["TileMatrixSet:0", "TileMatrixSet:1", ...]` 传入 `tileMatrixLabels`。
|
||||
- 其他类型:`raster-dem`→XYZ、MapServer→ArcGIS、其余 →WMS
|
||||
- **注意**:还存在一个 `key = "powerBaseStation"` 的基地底图图层,该图层**初始化时不加载**(已在 `IGNORED_BASE_LAYER_KEYS` 中忽略),但后续可通过图层树手动勾选显示。
|
||||
- 实现 `controlBaseLayerTreeShowAndHidden()`:
|
||||
- 让图层树勾选能控制 3D 底图显隐
|
||||
- 根据你的要求,UI 层先不开放 `BaseLayerSwitcher` 的 3D 切换行为,只保留默认基础底图加载。
|
||||
@ -180,7 +184,7 @@
|
||||
- 切到 3D 后可稳定看到基础底图。
|
||||
- 图层初始化时 `addBaseDataLayer(this.baseLayerConfig, checked, true)` 的思路在 3D 可落地。
|
||||
|
||||
### Step 3:打通 3D 锚点展示
|
||||
### Step 3:打通 3D 锚点展示 ✅️
|
||||
|
||||
目标:
|
||||
|
||||
@ -189,13 +193,14 @@
|
||||
|
||||
计划:
|
||||
|
||||
- 在 `MapCesium` 内建立点位图层注册结构,建议按 `layerKey -> entity[]` 或 `CustomDataSource` 管理。
|
||||
- 在 `MapCesium` 内建立点位图层注册结构,按 `layerKey -> entity[]` 管理。
|
||||
- 实现 `addInitDataLayer(pointData, layerType)`:
|
||||
- 复用当前点位数据结构
|
||||
- 解析 `lgtd/lttd/iconCode/titleName/stnm/anchoPointState/popupHtml`
|
||||
- 复用现有图标资源路径
|
||||
- Entity ID 使用 `${layerKey}:${item._id || `${iconType || 'point'}_${stcd}`}`,与 2D 对齐,确保同 stcd 不同 sttpMap 的点不被覆盖
|
||||
- 确保 `eng_point` 仍按现有 `mapStore` 过滤后的数据展示,中型站点只在 `7.5` 以上层级出现
|
||||
- 为后续缩放驱动的增量显示预留运行态字段,不把所有点简单一次性硬渲染
|
||||
- 为后续缩放驱动的增量显示预留运行态字段(`_legendVisible`、`_regionVisible`、`_densityVisible`),不把所有点简单一次性硬渲染
|
||||
- 实现 `mdLayerTreeShowOrHidden(layerType, checked)`:
|
||||
- 图层树控制整个点图层显隐
|
||||
- 实现 `removePointLayer()` / `hasLayer()`
|
||||
@ -208,7 +213,34 @@
|
||||
- 取消勾选图层后,对应锚点消失。
|
||||
- `eng_point` 在低于 `7.5` 时不显示中型水电站,高于 `7.5` 时正常显示。
|
||||
|
||||
### Step 4:打通 3D popup 与 hover
|
||||
### Step 4:打通 3D 基地裁切(jdPanelControlShowAndHidden) ✅️
|
||||
|
||||
目标:
|
||||
|
||||
- 3D 点击基地面板后,与 2D 行为一致。
|
||||
- 加载基地边界 GeoJSON,只显示区域内锚点,隐藏区域外锚点。
|
||||
- 取消基地时恢复显示所有锚点。
|
||||
|
||||
计划:
|
||||
|
||||
- 在 `MapCesium` 中捕获 `hydropBase` 配置,与 2D 一样在 `addBaseDataLayer` 中记录。
|
||||
- 实现 `jdPanelControlShowAndHidden(baseid, isAll)`:
|
||||
- `isAll=false`:清除 3D 球面遮罩实体、恢复所有锚点区域可见性、清除裁切状态。
|
||||
- `isAll=true`:先隐藏所有锚点,异步加载基地边界 GeoJSON,执行点在面判断后仅显示区域内锚点,同时在球面绘制裁切边界多边形(`applyRegionMask`),最后飞行到区域视野。
|
||||
- 在 `createEntityPropertiesBag` 中新增 `_regionVisible` 属性,初始为 `true`。
|
||||
- 在 `applyPointVisibilityRefresh` 中跳过 `_regionVisible !== false` 的实体,确保裁切后碰撞检测不会把区域外锚点重新显示出来。
|
||||
- 复用 `extractPolygonCoords` / `isPointInRing` 射线法判断逻辑(与 `region-mask-manager.ts` 一致)。
|
||||
- 支持 `AbortController` 取消前一次未完成的请求,避免响应顺序错乱。
|
||||
- `applyRegionMask` 将 GeoJSON 多边形转换为 Cesium `PolygonHierarchy`,在球面绘制半透明填充 + 可见边界的区域多边形,模拟 2D 底图 Canvas 裁切遮罩的视觉效果。
|
||||
|
||||
验收:
|
||||
|
||||
- 点击基地后,3D 只显示该基地区域内的锚点,区域外锚点隐藏。
|
||||
- 取消基地后,所有锚点恢复正常显示。
|
||||
- 快速切换基地不会出现旧数据覆盖新数据的问题。
|
||||
- 多次切换 2D/3D 后基地裁切行为正常。
|
||||
|
||||
### Step 5:打通 3D popup 与 hover ✅️
|
||||
|
||||
目标:
|
||||
|
||||
@ -233,7 +265,7 @@
|
||||
- 样式和 2D 保持一致。
|
||||
- 移出后 popup 正常消失。
|
||||
|
||||
### Step 5:补齐缩放驱动显示与碰撞检测
|
||||
### Step 6:补齐缩放驱动显示与碰撞检测
|
||||
|
||||
目标:
|
||||
|
||||
@ -263,7 +295,7 @@
|
||||
- 放大到具体层级后会看到更多锚点逐步出现。
|
||||
- 标签和锚点不会大面积重叠到不可读。
|
||||
|
||||
### Step 6:打通 3D 锚点点击详情
|
||||
### Step 7:打通 3D 锚点点击详情 ✅️
|
||||
|
||||
目标:
|
||||
|
||||
@ -287,7 +319,7 @@
|
||||
- `ylfb` 和普通锚点分支行为一致。
|
||||
- 点击空白区域不会误触发弹框。
|
||||
|
||||
### Step 7:打通图例联动
|
||||
### Step 8:打通图例联动 ✅️
|
||||
|
||||
目标:
|
||||
|
||||
@ -305,7 +337,7 @@
|
||||
- 图例单项勾选/取消时,3D 对应类别锚点同步显隐。
|
||||
- 图例分组勾选/取消时,3D 行为与 2D 保持一致。
|
||||
|
||||
### Step 8:补足收口与稳定性
|
||||
### Step 9:补足收口与稳定性 ✅️
|
||||
|
||||
目标:
|
||||
|
||||
@ -329,7 +361,7 @@
|
||||
|
||||
1. 默认定位到中国 + 启动动画
|
||||
2. 展示基础底图
|
||||
3. 展示锚点
|
||||
3. 展示锚点 3.5. 补基地裁切(jdPanelControlShowAndHidden)
|
||||
4. 补 hover popup
|
||||
5. 补缩放驱动显示与碰撞检测
|
||||
6. 补锚点点击详情
|
||||
@ -337,64 +369,6 @@
|
||||
8. 补图例联动
|
||||
9. 做切换稳定性收口
|
||||
|
||||
## 9. 必须补齐的隐藏逻辑清单
|
||||
|
||||
下面这些逻辑不是表面接口能直接看出来的,但如果 `3D` 不补,最终效果就会和 `2D` 明显不一致。
|
||||
|
||||
### 9.1 图层与菜单切换
|
||||
|
||||
- 菜单切换时,只隐藏旧页点图层,不隐藏底图。
|
||||
- 图层树勾选前要先经过互斥规则归一化:
|
||||
- `rare_fish_point` 与 `fish_along_point` 互斥
|
||||
- `stinfo/stinfo_video_point` 与 `stinfo_ai_video_point` 互斥
|
||||
- `facilities` 与 `facilities_built` 两个分支互斥
|
||||
- 点图层取消勾选时只隐藏,不删除缓存;重新勾选时优先从缓存恢复。
|
||||
|
||||
### 9.2 图例与点内显隐
|
||||
|
||||
- 图例控制的是图层内部锚点是否可见,不是图层本身是否可见。
|
||||
- 图层恢复显示时,图例要先整层全关,再按当前 checked 状态逐项恢复。
|
||||
- 环保设施图例组当前是只展示不交互。
|
||||
|
||||
### 9.3 缩放驱动显示
|
||||
|
||||
- `eng_point` 的中型水电站只在 `ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5` 以上显示。
|
||||
- 水电开发页缩放跨过 `12` 级时,会自动补开一批动态图层;缩回去时再自动收口。
|
||||
- `2D` 存在“缩放越大显示越多”的行为,不是所有锚点在所有层级都等量显示。
|
||||
- `2D` 还存在 hover、批量 popup、密度控制等缩放阈值:
|
||||
- hover 低于一定缩放不触发
|
||||
- 高缩放会进入批量 popup 模式
|
||||
- 点位会随着缩放提升逐步放开显示限制
|
||||
|
||||
### 9.4 锚点碰撞与控量
|
||||
|
||||
- `2D` 中锚点是否可见,不只受图层开关控制,还受以下门闩共同影响:
|
||||
- 图例可见性
|
||||
- 区域裁切可见性
|
||||
- 密度阈值
|
||||
- 图标碰撞结果
|
||||
- 标签碰撞结果
|
||||
- 低缩放下,为避免堆叠,部分锚点或标签会被主动隐藏。
|
||||
- 高缩放下,会逐步放开图标/标签限制,显示更多内容。
|
||||
- 因此 `3D` 不能简单把所有 entity 永久全开,否则视觉密度会明显失控。
|
||||
|
||||
### 9.5 Popup 与点击
|
||||
|
||||
- hover popup 不是全时开启的,有缩放门槛。
|
||||
- 点击和 hover 的命中对象是图标本体,不是任意文字区域。
|
||||
- `ylfb` 与普通锚点的点击详情弹框走不同业务分支。
|
||||
|
||||
### 9.6 基地与区域裁切
|
||||
|
||||
- 基地切换不仅影响搜索候选,还会影响地图上的点位可见范围。
|
||||
- 区域裁切会直接把区域外点位隐藏,不是只改底图遮罩。
|
||||
|
||||
### 9.7 筛选器特殊逻辑
|
||||
|
||||
- 装机容量筛选本质是批量切 `eng_point` 图例状态,不是简单切图层。
|
||||
- 鱼类分布模式会接管 `ylfb_point` 的数据、缓存、图例与显隐。
|
||||
- 时间筛选当前只重载指定时间相关图层,不是全量重载。
|
||||
|
||||
## 7. 预计修改文件
|
||||
|
||||
- `src/components/gis/map.cesium.ts`
|
||||
@ -415,3 +389,91 @@
|
||||
- 默认飞到中国
|
||||
|
||||
这一步最小、最容易验证,也能先把“打开 3D 看起来不对”的核心问题解决掉。
|
||||
|
||||
## 9. 必须补齐的隐藏逻辑清单
|
||||
|
||||
下面这些逻辑不是表面接口能直接看出来的,但如果 `3D` 不补,最终效果就会和 `2D` 明显不一致。
|
||||
|
||||
### 9.1 图层与菜单切换 ✅️
|
||||
|
||||
- ✅ 菜单切换时,只隐藏旧页点图层,不隐藏底图。(编排器 `handlePageChange` 控制,Cesium 被动接收数据)
|
||||
- ✅ 图层树勾选前要先经过互斥规则归一化:(store 层 `updateLayerData` 处理,Cesium 不感知)
|
||||
- `rare_fish_point` 与 `fish_along_point` 互斥
|
||||
- `stinfo/stinfo_video_point` 与 `stinfo_ai_video_point` 互斥
|
||||
- `facilities` 与 `facilities_built` 两个分支互斥
|
||||
- ✅ 点图层取消勾选时只隐藏,不删除缓存;重新勾选时优先从缓存恢复。`mdLayerTreeShowOrHidden` 设置 `_layerVisible` 标志,不删除 `pointLayerRegistry` 条目。
|
||||
- ✅ **图层树勾选时按 `type` 字段走不同分支**(见 `updateLayerData`,store 层处理):
|
||||
- `type === 'pointMap'` → 走 `handlePointMapLayer` → 调用 `mdLayerTreeShowOrHidden`(已实现,L1624)
|
||||
- `type === 'GISMap'` → 走 `controlBaseLayerTreeShowAndHidden`(已实现,L1345)
|
||||
- 3D Cesium 已同时实现这两个方法。
|
||||
|
||||
### 9.2 图例与点内显隐 ✅️
|
||||
|
||||
- ✅ 图例控制的是图层内部锚点是否可见,不是图层本身是否可见。`setLegendPointVisible` 使用 `_legendVisible` 标志,与 `_layerVisible` 独立。
|
||||
- ✅ 图层恢复显示时,图例要先整层全关,再按当前 checked 状态逐项恢复。支持 `anchoPointState=''` 全关 → 逐项 `(state, true)` 恢复两步模式(L1764-1787)。
|
||||
- ✅ 环保设施图例组当前是只展示不交互。(编排器层面控制,Cesium 不感知)
|
||||
|
||||
### 9.3 缩放驱动显示 ✅️
|
||||
|
||||
- ✅ `eng_point` 的中型水电站只在 `ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5` 以上显示。(store `filterPointLayerDataForDisplay` 在数据进入 Cesium 前过滤)
|
||||
- ✅ 水电开发页缩放跨过 `12` 级时,会自动补开一批动态图层;缩回去时再自动收口。(编排器层面处理)
|
||||
- ✅ `2D` 存在"缩放越大显示越多"的行为。碰撞检测中密度门控逐级放开。
|
||||
- ✅ `2D` 还存在 hover、批量 popup、密度控制等缩放阈值:
|
||||
- hover 低于 ~zoom 4.5 不触发(`HOVER_POPUP_MAX_HEIGHT = 9_000_000`)
|
||||
- 高缩放进入批量 popup 模式(`BATCH_POPUP_MODE_ZOOM = 14.9`)
|
||||
- 点位随着缩放提升逐步放开密度限制(密度门控 + `DENSITY_ISOLATION_THRESHOLD`)
|
||||
- ✅ **图标动态缩放**:`CallbackProperty` 实现 `billboard.scale`,公式 `clamp(0.7 + (zoom - 4.5) * 0.035, 0.4, 1.2)`(L1522)。
|
||||
- ✅ **字体动态大小**:`CallbackProperty` 实现 `label.font`,钳位 `[10, 20]`(L1535)。
|
||||
- ✅ **文字在图标上方**:`CallbackProperty` 实现 `label.pixelOffset`,负 Y 偏移(L1549)。
|
||||
- ✅ **Cesium 中必须使用 `CallbackProperty` 实现动态缩放**:已使用 3 个 `CallbackProperty`(scale/font/offset),`isConstant=false` 每帧重算。
|
||||
|
||||
### 9.4 锚点碰撞与控量 ✅️
|
||||
|
||||
- ✅ `2D` 中锚点是否可见,不只受图层开关控制,还受以下门闩共同影响:
|
||||
- 图例可见性 `_legendVisible`
|
||||
- 区域裁切可见性 `_regionVisible`
|
||||
- 密度阈值(碰撞循环中 inline 计算,逐级放开)
|
||||
- 图标碰撞结果 `_iconCollisionVisible`
|
||||
- 标签碰撞结果 `_labelCollisionVisible`
|
||||
- ✅ 低缩放下,为避免堆叠,部分锚点或标签会被主动隐藏。(`refreshPointLabelVisibility` 碰撞检测,L2117-2455)
|
||||
- ✅ 高缩放下,会逐步放开图标/标签限制,显示更多内容。(密度门控 + 碰撞容差随 zoom 调整)
|
||||
- ✅ 因此 `3D` 不能简单把所有 entity 永久全开。所有 entity 初始 `_iconCollisionVisible = _labelCollisionVisible = true`,碰撞检测运行后动态调整。
|
||||
|
||||
### 9.5 Popup 与点击 ✅️
|
||||
|
||||
- ✅ hover popup 不是全时开启的,有缩放门槛。相机高度 > `HOVER_POPUP_MAX_HEIGHT` 时不触发 hover。
|
||||
- ✅ 点击和 hover 的命中对象是图标本体,不是任意文字区域。`ScreenSpaceEventHandler` 基于 `scene.pick` 命中 entity。
|
||||
- ✅ `ylfb` 与普通锚点的点击详情弹框走不同业务分支。`sttpMap === 'ylfb'` 打开 `ylfbModalVisible`,其他走 `modalVisible`(L366-375)。
|
||||
|
||||
### 9.6 基地与区域裁切 ✅️
|
||||
|
||||
- ✅ 基地切换不仅影响搜索候选,还会影响地图上的点位可见范围。`jdPanelControlShowAndHidden` 实现(L1684-1748)。
|
||||
- ✅ 区域裁切会直接把区域外点位隐藏,不是只改底图遮罩。`filterPointsByRegion` 设置 `_regionVisible = false`(L1824-1848),遮罩多边形 + 裁剪双重处理。
|
||||
|
||||
### 9.7 筛选器特殊逻辑 ✅️
|
||||
|
||||
- ✅ 装机容量筛选本质是批量切 `eng_point` 图例状态,不是简单切图层。(store 层处理,Cesium 通过 `setLegendPointVisible` 响应)
|
||||
- ✅ 鱼类分布模式会接管 `ylfb_point` 的数据、缓存、图例与显隐。(store 层处理)
|
||||
- ✅ 时间筛选当前只重载指定时间相关图层,不是全量重载。(store 层处理)
|
||||
|
||||
### 9.8 数据进入 addInitDataLayer 前的预处理 ✅️
|
||||
|
||||
- ✅ 所有点位数据在进入 `addInitDataLayer` 前会经过 `normalizePointLayerItems()` 过滤:(store 层 `map.ts` 执行,Cesium 接收时已过滤)
|
||||
- 没有 `iconCode` 且 `code !== 'colorLayer'` 的项会被抛弃
|
||||
- `baseId == 'all'` 且 `anchoPointState` 结尾为 `_nbuilt` 的项会被抛弃
|
||||
- ✅ `_id` 默认值生成规则:store 层生成 `${sttpMap || iconType || 'point'}_${stcd}`。
|
||||
- ✅ **stcd+经纬度重复锚点处理**:Cesium entity ID 使用 `${layerKey}:${item._id}`,与 2D 对齐,同 stcd 不同 sttpMap 的点不会互相覆盖。
|
||||
|
||||
### 9.9 图标渐进加载机制
|
||||
|
||||
- ❌ 2D 中图标不是一次性全加载完。有 `loading → loaded → error` 三种状态跟踪。
|
||||
- ❌ 只有 `loaded` 状态的图标才参与样式渲染,loader 失败的不渲染。
|
||||
- ❌ 如果在图标加载过程中就触发了样式刷新,需要通过 `requestAnimationFrame` 回刷。
|
||||
- ❌ 3D Cesium 中当前所有 entity 同步创建,billboard `image` URL 直接赋值,无渐进加载。Cesium 内部纹理加载无显式状态跟踪。
|
||||
|
||||
### 9.10 密度分档与近邻点元数据 ✅️
|
||||
|
||||
- ✅ 每个锚点的 `distance` 字段表示密度分档值(不是物理距离),值越大表示越稀疏。(已使用 `getEntityDensityValue` 从 `_rawData.distance` 读取,L2460)
|
||||
- ✅ 不同 density 值对应不同的 `minZoom` 显示门槛。`getEntityDensityMinZoom` 映射 `nearby-point-rules.ts` 的 `densityDisplayRules`(L2491)。
|
||||
- ✅ 全量数据加载后,store 会调用 `attachNearbyPointRuntimeMeta()` 给所有点打上 `_nearbyRule`、`_nearbyGroupId`、`_nearbyPriority` 等元数据。(store 层处理,Cesium 碰撞检测中读取)
|
||||
- ✅ **孤立点例外规则**:如果一个点在当前视口内 ~56px 范围内没有同 layerKey 的其他可见点,它可以绕过密度门槛直接显示。`DENSITY_ISOLATION_THRESHOLD = 56` + `shouldBypassDensityGateForIsolatedEntity`(L2506-2556)。
|
||||
|
||||
@ -79,6 +79,16 @@ export function vmsstbprptGetKendoList(data: any) {
|
||||
data: data
|
||||
});
|
||||
}
|
||||
// 环保自动监测工作开展情弹窗
|
||||
export function overviewvmsstbprptGetKendoList(data: any) {
|
||||
return request({
|
||||
url: '/overview/vmsstbprpt/GetKendoList',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 获取基本信息
|
||||
export function baseMsstbprptGetKendoList(data: any) {
|
||||
|
||||
@ -41,6 +41,14 @@ export function getEngInfoDropdown(data: any) {
|
||||
data
|
||||
});
|
||||
}
|
||||
// 根据河段获取电站
|
||||
export function getByRvcd(data: any) {
|
||||
return request({
|
||||
url: '/env/engInfo/getByRvcd',
|
||||
method: 'get',
|
||||
params:data
|
||||
});
|
||||
}
|
||||
// 根据id获取电站信息
|
||||
export function getEngInfoById(params: any) {
|
||||
return request({
|
||||
|
||||
@ -3,7 +3,7 @@ import request from '@/utils/request';
|
||||
// 获取所有倾斜摄影
|
||||
export function getAllTiltPhotoTree(data: any) {
|
||||
return request({
|
||||
url: '/api/wmp-sys-server/sys/oblique/GetKendoListCust',
|
||||
url: '/sys/oblique/GetKendoListCust',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
@ -11,7 +11,7 @@ export function getAllTiltPhotoTree(data: any) {
|
||||
// 保存倾斜摄影
|
||||
export function saveTiltPhoto(data: any) {
|
||||
return request({
|
||||
url: '/api/wmp-sys-server/sys/oblique/addOrUpdate',
|
||||
url: '/sys/oblique/addOrUpdate',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
@ -19,7 +19,7 @@ export function saveTiltPhoto(data: any) {
|
||||
// 删除倾斜摄影
|
||||
export function deleteTiltPhoto(data: any) {
|
||||
return request({
|
||||
url: '/api/wmp-sys-server/sys/oblique/delete',
|
||||
url: '/sys/oblique/delete',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
|
||||
@ -70,7 +70,7 @@ export function setPushConfigSmsSwitch(data: any) {
|
||||
return request({
|
||||
url: '/system/pushConfig/setPushConfigSmsSwitch',
|
||||
method: 'post',
|
||||
data
|
||||
params:data
|
||||
});
|
||||
}
|
||||
//获取推送历史列表
|
||||
|
||||
@ -468,7 +468,7 @@ const triggerManualValuesChange = (changedKey: string, newValue: any) => {
|
||||
emit('valuesChange', changedValues, { ...formData });
|
||||
};
|
||||
|
||||
const dataDimensionDataChange = (value: any, fieldName: string = 'baseId') => {
|
||||
const dataDimensionDataChange = (value: any, fieldName = 'baseId') => {
|
||||
formData[fieldName] = value;
|
||||
formData.rstcd = "";
|
||||
shuJuTianBaoStore.getEngOption(value,'baseId');
|
||||
|
||||
@ -192,6 +192,7 @@ const getList = async (filter?: Record<string, any>) => {
|
||||
total.value = 0;
|
||||
if (filter !== undefined) {
|
||||
lastFilter.value = filter;
|
||||
page.value = 1;
|
||||
}
|
||||
try {
|
||||
// 合并排序参数:优先使用用户手动排序,否则使用 searchParams 中的默认排序
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<div class="card_left">
|
||||
<div class="card_img">
|
||||
<div class="carousel-wrapper">
|
||||
<div class="carousel-inner">
|
||||
<div class="carousel-inner" ref="carouselInnerRef">
|
||||
<div class="carousel-track" :style="trackStyle">
|
||||
<div v-for="(item, index) in originalMediaData" :key="index" class="carousel-item">
|
||||
<a-image :src="item.url" />
|
||||
@ -43,7 +43,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
defineOptions({
|
||||
@ -67,6 +67,15 @@ interface MediaItem {
|
||||
|
||||
const originalMediaData = ref<MediaItem[]>([]);
|
||||
const currentIndex = ref(0);
|
||||
const carouselInnerRef = ref<HTMLElement | null>(null);
|
||||
const itemWidth = ref(0);
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
const updateItemWidth = () => {
|
||||
if (carouselInnerRef.value) {
|
||||
itemWidth.value = carouselInnerRef.value.clientWidth;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化数据
|
||||
const initOriginalData = () => {
|
||||
@ -89,10 +98,9 @@ const setCurrentIndexFromProps = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 计算轨道样式 - 完全按照 React 版本的逻辑
|
||||
const trackStyle = computed(() => {
|
||||
return {
|
||||
transform: `translateX(calc((68.8px - 48vw) * ${currentIndex.value}))`
|
||||
transform: `translateX(-${itemWidth.value * currentIndex.value}px)`
|
||||
};
|
||||
});
|
||||
|
||||
@ -122,12 +130,23 @@ const currentDescription = computed(() => {
|
||||
const currentTitle = computed(() => {
|
||||
return originalMediaData.value[currentIndex.value]?.title || '';
|
||||
});
|
||||
// 页面加载时执行
|
||||
onMounted(() => {
|
||||
console.log('ArtsDetail mounted - props.index:', props.index, 'props.dataSource.length:', props.dataSource?.length);
|
||||
initOriginalData();
|
||||
setCurrentIndexFromProps();
|
||||
console.log('After setCurrentIndexFromProps - currentIndex.value:', currentIndex.value);
|
||||
updateItemWidth();
|
||||
if (carouselInnerRef.value) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
updateItemWidth();
|
||||
});
|
||||
resizeObserver.observe(carouselInnerRef.value);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect();
|
||||
resizeObserver = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 只监听 dataSource 变化,不监听 index 变化
|
||||
@ -196,10 +215,9 @@ watch(
|
||||
transition: transform 0.3s ease-in-out;
|
||||
|
||||
.carousel-item {
|
||||
width: calc(48vw - 80.8px);
|
||||
flex: 0 0 calc(48vw - 80.8px);
|
||||
width: 100%;
|
||||
flex: 0 0 100%;
|
||||
height: 100%;
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@ -32,8 +32,10 @@ import { useMapOrchestrator } from '@/modules/map/application/map-orchestrator';
|
||||
import { MapClass } from './map.class';
|
||||
import { getQgcRvcd } from '@/api/map';
|
||||
import { servers } from './mapurlManage';
|
||||
import { useUiStore } from '@/store/modules/ui';
|
||||
|
||||
const mapOrchestrator = useMapOrchestrator();
|
||||
const uiStore = useUiStore();
|
||||
|
||||
const route = useRoute();
|
||||
const mapClass = MapClass.getInstance();
|
||||
@ -82,7 +84,11 @@ const init = async () => {
|
||||
const handleMapController = async (e: any, mapType: any) => {
|
||||
switch (e) {
|
||||
case 'dim':
|
||||
await mapClass.switchView(mapType);
|
||||
await mapOrchestrator.switchMapType(mapType, {
|
||||
popupContainer: popupRef.value,
|
||||
getIsHydroMenu: () => isShuiDianKaiFaMenu.value
|
||||
});
|
||||
uiStore.markMapSwitchCompleted();
|
||||
break;
|
||||
case 4: // 梯级流域显示
|
||||
tlyLayerVisible.value = !tlyLayerVisible.value;
|
||||
|
||||
@ -42,9 +42,6 @@ export const legendData2Obj = (data: any[]) => {
|
||||
if (item?.childrenList && item.childrenList?.length > 0) {
|
||||
f(item.childrenList);
|
||||
} else {
|
||||
if (item.nameEn.includes('_测试')) {
|
||||
item.nameEn = item.nameEn.split('_测试')[0];
|
||||
}
|
||||
_tempData[item.nameEn] = item;
|
||||
}
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -272,4 +272,8 @@ export class MapClass implements MapClassInterface {
|
||||
flyTopanto(position: number[], zoom: number): void {
|
||||
this.service.flyTopanto(position, zoom);
|
||||
}
|
||||
|
||||
getCurrentZoom(): number | undefined {
|
||||
return this.service.getCurrentZoom();
|
||||
}
|
||||
}
|
||||
|
||||
6
frontend/src/components/gis/map.d.ts
vendored
6
frontend/src/components/gis/map.d.ts
vendored
@ -124,6 +124,12 @@ export interface MapInterface {
|
||||
fitBounds(bbox, bearing): void;
|
||||
|
||||
flyTopanto(positon, zoom): void;
|
||||
|
||||
/**
|
||||
* 获取当前地图缩放级别语义
|
||||
* 2D 直接返回原生 zoom,3D 由相机高度换算后返回
|
||||
*/
|
||||
getCurrentZoom(): number | undefined;
|
||||
// /**
|
||||
// * 加载倾斜摄影数据
|
||||
// * @param HH3DUrlArray
|
||||
|
||||
@ -39,6 +39,7 @@ import { PointLayerManager } from './ol/point-layer-manager';
|
||||
import { PopupManager } from './ol/popup-manager';
|
||||
import { RegionMaskManager } from './ol/region-mask-manager';
|
||||
import { getNearbyPointDensityDisplayRules } from '@/modules/map/domain/nearby-point-rules';
|
||||
import router from '@/router';
|
||||
|
||||
const modelStore = useModelStore();
|
||||
const VITE_APP_MAP_URL = import.meta.env.VITE_APP_MAP_URL;
|
||||
@ -49,7 +50,7 @@ const MIN_ZOOM = 4.23;
|
||||
const MAX_ZOOM = 22;
|
||||
const INITIAL_ZOOM = 4.5;
|
||||
const BATCH_POPUP_MODE_ZOOM = 15;
|
||||
const FULL_DISPLAY_NO_COLLISION_ZOOM = 15;
|
||||
const FULL_DISPLAY_NO_COLLISION_ZOOM = Number.POSITIVE_INFINITY;
|
||||
|
||||
// 定义边界 [minX, minY, maxX, maxY] (Web Mercator 坐标)
|
||||
const BOUNDS_SW = [26.5, -9.99999999999929];
|
||||
@ -80,6 +81,8 @@ export class MapOl implements MapInterface {
|
||||
private regionMaskManager: RegionMaskManager;
|
||||
private isBatchPopupMode = false;
|
||||
private batchPopupRefreshFrameId: number | null = null;
|
||||
private batchPopupPendingRebuild = false;
|
||||
private batchPopupPostRenderListenerKey: any = null;
|
||||
private pointLayerStyleRefreshFrameId: number | null = null;
|
||||
private labelVisibilityRefreshFrameId: number | null = null;
|
||||
private labelVisibilityDebounceTimerId: number | null = null;
|
||||
@ -167,6 +170,13 @@ export class MapOl implements MapInterface {
|
||||
this.handleZoomChange();
|
||||
});
|
||||
this.map.on('click', evt => {
|
||||
if (
|
||||
router.currentRoute.value.path.includes(
|
||||
'shengTaiLiuLiangManZuQingKuangJiangJu'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.popupManager.showPopup(undefined, undefined);
|
||||
this.popupManager.handleMapClick(evt.pixel, detectedFeature => {
|
||||
if (detectedFeature.values_.sttpMap == 'ylfb') {
|
||||
@ -200,13 +210,14 @@ export class MapOl implements MapInterface {
|
||||
|
||||
this.map.on('pointerdrag', () => {
|
||||
if (this.isBatchPopupMode) {
|
||||
this.requestUpdateBatchPopups();
|
||||
this.ensureBatchPopupPostRenderSync();
|
||||
}
|
||||
});
|
||||
this.map.on('moveend', () => {
|
||||
this.clearBatchPopupPostRenderSync();
|
||||
this.requestRefreshPointLabelVisibility(false, 60);
|
||||
if (this.isBatchPopupMode) {
|
||||
this.requestUpdateBatchPopups();
|
||||
this.requestUpdateBatchPopups(false, true);
|
||||
}
|
||||
});
|
||||
|
||||
@ -291,8 +302,9 @@ export class MapOl implements MapInterface {
|
||||
*/
|
||||
private enableBatchPopupMode() {
|
||||
this.isBatchPopupMode = true;
|
||||
this.clearBatchPopupPostRenderSync();
|
||||
this.popupManager.clearBatchPopups();
|
||||
this.requestUpdateBatchPopups(true);
|
||||
this.requestUpdateBatchPopups(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,6 +312,8 @@ export class MapOl implements MapInterface {
|
||||
*/
|
||||
private disableBatchPopupMode() {
|
||||
this.isBatchPopupMode = false;
|
||||
this.batchPopupPendingRebuild = false;
|
||||
this.clearBatchPopupPostRenderSync();
|
||||
if (this.batchPopupRefreshFrameId !== null) {
|
||||
cancelAnimationFrame(this.batchPopupRefreshFrameId);
|
||||
this.batchPopupRefreshFrameId = null;
|
||||
@ -314,11 +328,40 @@ export class MapOl implements MapInterface {
|
||||
private updateBatchPopups() {
|
||||
const features = this.pointLayerManager.getFeaturesInViewport();
|
||||
// 传入样式检查器,过滤掉 declutter/距离过滤等隐藏的锚点
|
||||
this.popupManager.showPopupsForFeatures(features, feature => {
|
||||
const style = this.createPointStyle(feature);
|
||||
return style !== null;
|
||||
this.popupManager.showPopupsForFeatures(
|
||||
features,
|
||||
feature => {
|
||||
const style = this.createPointStyle(feature);
|
||||
return style !== null;
|
||||
},
|
||||
{
|
||||
rebuild: this.batchPopupPendingRebuild
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private ensureBatchPopupPostRenderSync() {
|
||||
if (!this.map || this.batchPopupPostRenderListenerKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.batchPopupPostRenderListenerKey = this.map.on('postrender', () => {
|
||||
if (!this.isBatchPopupMode) {
|
||||
return;
|
||||
}
|
||||
this.popupManager.updateBatchPopupPositions(feature => {
|
||||
const style = this.createPointStyle(feature);
|
||||
return style !== null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private clearBatchPopupPostRenderSync() {
|
||||
if (this.batchPopupPostRenderListenerKey) {
|
||||
unByKey(this.batchPopupPostRenderListenerKey);
|
||||
this.batchPopupPostRenderListenerKey = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 创建点样式 (模拟 Leaflet 的 DivIcon 效果,支持随缩放动态调整大小)
|
||||
*/
|
||||
@ -346,10 +389,11 @@ export class MapOl implements MapInterface {
|
||||
legendVisible !== false &&
|
||||
regionVisible !== false &&
|
||||
densityVisible &&
|
||||
this.shouldRenderNearbyFeature(feature, currentZoom);
|
||||
this.shouldRenderNearbyFeature();
|
||||
|
||||
const dynamicScale = this.getDynamicPointScale(currentZoom);
|
||||
const fontSize = this.getPointFontSize(dynamicScale);
|
||||
const engPriority = this.getFeatureEngRenderPriority(feature);
|
||||
const formattedLabelText = this.formatPointLabelText(labelText);
|
||||
const labelLineCount = formattedLabelText
|
||||
? formattedLabelText.split('\n').length
|
||||
@ -363,7 +407,10 @@ export class MapOl implements MapInterface {
|
||||
feature.get('_labelCollisionVisible') === true;
|
||||
const finalIconVisible = iconTargetVisible && iconCollisionVisible;
|
||||
const labelTargetVisible =
|
||||
finalIconVisible && !!formattedLabelText && labelCollisionVisible;
|
||||
!this.isBatchPopupMode &&
|
||||
finalIconVisible &&
|
||||
!!formattedLabelText &&
|
||||
labelCollisionVisible;
|
||||
|
||||
if (!finalIconVisible) {
|
||||
return null;
|
||||
@ -373,6 +420,7 @@ export class MapOl implements MapInterface {
|
||||
iconUrl,
|
||||
dynamicScale.toFixed(2),
|
||||
fontSize,
|
||||
engPriority,
|
||||
labelOffsetY,
|
||||
labelTargetVisible ? formattedLabelText : '',
|
||||
labelTargetVisible ? 1 : 0
|
||||
@ -384,6 +432,7 @@ export class MapOl implements MapInterface {
|
||||
|
||||
const styles: Style[] = [
|
||||
new Style({
|
||||
zIndex: engPriority,
|
||||
image: new Icon({
|
||||
src: iconUrl,
|
||||
scale: dynamicScale,
|
||||
@ -397,7 +446,7 @@ export class MapOl implements MapInterface {
|
||||
if (formattedLabelText && labelTargetVisible) {
|
||||
styles.push(
|
||||
new Style({
|
||||
zIndex: 101,
|
||||
zIndex: engPriority + 1,
|
||||
text: new Text({
|
||||
text: formattedLabelText,
|
||||
offsetY: labelOffsetY,
|
||||
@ -598,6 +647,7 @@ export class MapOl implements MapInterface {
|
||||
top: number;
|
||||
bottom: number;
|
||||
priority: number;
|
||||
engPriority: number;
|
||||
densityPriority: number;
|
||||
id: string;
|
||||
pixelX: number;
|
||||
@ -616,6 +666,7 @@ export class MapOl implements MapInterface {
|
||||
top: number;
|
||||
bottom: number;
|
||||
priority: number;
|
||||
engPriority: number;
|
||||
densityPriority: number;
|
||||
id: string;
|
||||
pixelX: number;
|
||||
@ -663,7 +714,7 @@ export class MapOl implements MapInterface {
|
||||
legendVisible: feature.get('_legendVisible') !== false,
|
||||
regionVisible: feature.get('_regionVisible') !== false,
|
||||
densityPriority: this.getFeatureDensityPriority(feature),
|
||||
nearbyVisible: this.shouldRenderNearbyFeature(feature, currentZoom),
|
||||
nearbyVisible: this.shouldRenderNearbyFeature(),
|
||||
labelText: this.formatPointLabelText(
|
||||
feature.get('_labelText') as string
|
||||
),
|
||||
@ -808,7 +859,7 @@ export class MapOl implements MapInterface {
|
||||
const dynamicScale = this.getDynamicPointScale(currentZoom);
|
||||
// 备注:图标碰撞盒适当小于视觉图标尺寸,允许相邻站点轻微贴近显示,
|
||||
// 避免像“小浪底/三门峡”这类近点被过早裁掉成只剩一个点。
|
||||
const iconCollisionSize = Math.max(12, 16 * dynamicScale);
|
||||
const iconCollisionSize = Math.max(14, 24 * dynamicScale);
|
||||
const iconPadding = 0;
|
||||
const iconLeft = entry.pixelX - iconCollisionSize / 2 - iconPadding;
|
||||
const iconRight = entry.pixelX + iconCollisionSize / 2 + iconPadding;
|
||||
@ -851,6 +902,7 @@ export class MapOl implements MapInterface {
|
||||
top: centerY - estimatedHeight / 2,
|
||||
bottom: centerY + estimatedHeight / 2,
|
||||
priority: Number(entry.feature.get('_nearbyPriority') || 9999),
|
||||
engPriority: this.getFeatureEngRenderPriority(entry.feature),
|
||||
densityPriority: entry.densityPriority,
|
||||
id: candidateId,
|
||||
pixelX: entry.pixelX,
|
||||
@ -863,6 +915,9 @@ export class MapOl implements MapInterface {
|
||||
});
|
||||
|
||||
candidates.sort((left, right) => {
|
||||
if (left.engPriority !== right.engPriority) {
|
||||
return right.engPriority - left.engPriority;
|
||||
}
|
||||
if (left.priority !== right.priority) {
|
||||
return left.priority - right.priority;
|
||||
}
|
||||
@ -951,6 +1006,9 @@ export class MapOl implements MapInterface {
|
||||
});
|
||||
|
||||
const labelCandidates = [...candidates].sort((left, right) => {
|
||||
if (left.engPriority !== right.engPriority) {
|
||||
return right.engPriority - left.engPriority;
|
||||
}
|
||||
if (left.priority !== right.priority) {
|
||||
return left.priority - right.priority;
|
||||
}
|
||||
@ -1149,10 +1207,11 @@ export class MapOl implements MapInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private requestUpdateBatchPopups(immediate = false) {
|
||||
private requestUpdateBatchPopups(immediate = false, rebuild = true) {
|
||||
if (!this.isBatchPopupMode) {
|
||||
return;
|
||||
}
|
||||
this.batchPopupPendingRebuild = this.batchPopupPendingRebuild || rebuild;
|
||||
|
||||
if (immediate) {
|
||||
if (this.batchPopupRefreshFrameId !== null) {
|
||||
@ -1160,6 +1219,7 @@ export class MapOl implements MapInterface {
|
||||
this.batchPopupRefreshFrameId = null;
|
||||
}
|
||||
this.updateBatchPopups();
|
||||
this.batchPopupPendingRebuild = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1173,6 +1233,7 @@ export class MapOl implements MapInterface {
|
||||
return;
|
||||
}
|
||||
this.updateBatchPopups();
|
||||
this.batchPopupPendingRebuild = false;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1326,6 +1387,47 @@ export class MapOl implements MapInterface {
|
||||
return densityDisplayRules.at(-1)?.minZoom ?? 0;
|
||||
}
|
||||
|
||||
private isFeatureEng(feature: Feature): boolean {
|
||||
const layerKey = String(feature.get('_layerKey') || '').toLowerCase();
|
||||
const sttpMap = String(
|
||||
feature.get('_sttpMap') || feature.get('sttpMap') || ''
|
||||
).toUpperCase();
|
||||
const sttpCode = String(
|
||||
feature.get('sttpCode') || feature.get('sttp') || ''
|
||||
).toUpperCase();
|
||||
|
||||
return (
|
||||
layerKey.includes('eng_point') ||
|
||||
sttpMap === 'ENG' ||
|
||||
sttpMap === 'ENG2' ||
|
||||
sttpCode === 'ENG'
|
||||
);
|
||||
}
|
||||
|
||||
private isFeatureEngAlarm(feature: Feature): boolean {
|
||||
const layerKey = String(
|
||||
feature.get('_layerKey') || feature.get('type') || ''
|
||||
).toLowerCase();
|
||||
const legendState = String(feature.get('anchoPointState') || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
return (
|
||||
layerKey.includes('eng_alarm_point') ||
|
||||
layerKey.includes('alarm_range') ||
|
||||
legendState.startsWith('alarm_range_') ||
|
||||
legendState.startsWith('large_eng_built_alarm_range_') ||
|
||||
legendState.startsWith('mid_eng_built_alarm_range_')
|
||||
);
|
||||
}
|
||||
|
||||
private getFeatureEngRenderPriority(feature: Feature): number {
|
||||
if (this.isFeatureEngAlarm(feature)) {
|
||||
return 300;
|
||||
}
|
||||
return this.isFeatureEng(feature) ? 200 : 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果点位在当前视图中没有近邻点,则允许跳过静态密度门槛直接参与显示。
|
||||
* 这样像“戈兰滩”这类 distance 档位偏密、但当前周围实际很空的点也能出现。
|
||||
@ -1398,53 +1500,10 @@ export class MapOl implements MapInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一根据近邻点分组和缩放阶段判断当前要素是否应该参与渲染。
|
||||
* 近邻点统一参与原位堆叠,具体谁显示在顶层交给碰撞排序和图层优先级控制。
|
||||
*/
|
||||
private shouldRenderNearbyFeature(
|
||||
feature: Feature,
|
||||
currentZoom: number
|
||||
): boolean {
|
||||
const nearbyGroupId = feature.get('_nearbyGroupId');
|
||||
const groupSize = Number(feature.get('_nearbyGroupSize'));
|
||||
const priority = Number(feature.get('_nearbyPriority'));
|
||||
const replaceAtZoom = Number(feature.get('_nearbyReplaceAtZoom'));
|
||||
const expandAtZoom = Number(feature.get('_nearbyExpandAtZoom'));
|
||||
const showZoomMin = feature.get('_nearbyShowZoomMin');
|
||||
const showZoomMax = feature.get('_nearbyShowZoomMax');
|
||||
|
||||
if (!nearbyGroupId || !Number.isFinite(groupSize) || groupSize < 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Number.isFinite(priority) || priority <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
showZoomMin !== null &&
|
||||
showZoomMin !== undefined &&
|
||||
currentZoom < Number(showZoomMin)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
showZoomMax !== null &&
|
||||
showZoomMax !== undefined &&
|
||||
currentZoom > Number(showZoomMax)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Number.isFinite(expandAtZoom) && currentZoom >= expandAtZoom) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Number.isFinite(replaceAtZoom) && currentZoom >= replaceAtZoom) {
|
||||
return priority <= Math.min(2, groupSize);
|
||||
}
|
||||
|
||||
return priority === 1;
|
||||
private shouldRenderNearbyFeature(): boolean {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 初始化加载基础图层
|
||||
@ -2436,6 +2495,10 @@ export class MapOl implements MapInterface {
|
||||
);
|
||||
}
|
||||
|
||||
getCurrentZoom(): number | undefined {
|
||||
return this.view?.getZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成带完整圆边框的上半圆 Canvas
|
||||
* @param radius 半径(像素)
|
||||
@ -2496,6 +2559,7 @@ export class MapOl implements MapInterface {
|
||||
window.clearTimeout(this.labelVisibilityDebounceTimerId);
|
||||
this.labelVisibilityDebounceTimerId = null;
|
||||
}
|
||||
this.clearBatchPopupPostRenderSync();
|
||||
this.removeQueryLayer();
|
||||
this.regionMaskManager.destroy();
|
||||
|
||||
|
||||
@ -5,8 +5,6 @@ import VectorLayer from 'ol/layer/Vector';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import { fromLonLat } from 'ol/proj';
|
||||
import { getIconPath } from '@/utils/index';
|
||||
import { getNearbyPointConfig } from '@/modules/map/domain/nearby-point-rules';
|
||||
|
||||
type PointLayerStyleFactory = (feature: Feature) => any;
|
||||
|
||||
type PointLayerManagerOptions = {
|
||||
@ -80,103 +78,12 @@ export class PointLayerManager {
|
||||
updateNearbyFeatureLayout(currentZoom?: number): void {
|
||||
if (!this.map) return;
|
||||
|
||||
const resolution = this.map.getView().getResolution();
|
||||
if (
|
||||
currentZoom === undefined ||
|
||||
!Number.isFinite(currentZoom) ||
|
||||
!resolution
|
||||
) {
|
||||
if (currentZoom === undefined || !Number.isFinite(currentZoom)) {
|
||||
return;
|
||||
}
|
||||
const nearbyPointConfig = getNearbyPointConfig();
|
||||
const { autoRule, layoutRule } = nearbyPointConfig;
|
||||
|
||||
const groupMap = new Map<string, Feature[]>();
|
||||
|
||||
this.forEachFeature(feature => {
|
||||
this.resetFeatureToBaseCoordinate(feature);
|
||||
|
||||
const groupId = feature.get('_nearbyGroupId');
|
||||
const groupSize = Number(feature.get('_nearbyGroupSize'));
|
||||
if (!groupId || !Number.isFinite(groupSize) || groupSize < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupFeatures = groupMap.get(groupId) || [];
|
||||
groupFeatures.push(feature);
|
||||
groupMap.set(groupId, groupFeatures);
|
||||
});
|
||||
|
||||
groupMap.forEach(features => {
|
||||
const expandAtZoom = Number(features[0]?.get('_nearbyExpandAtZoom'));
|
||||
if (!Number.isFinite(expandAtZoom) || currentZoom < expandAtZoom) {
|
||||
return;
|
||||
}
|
||||
const sortedFeatures = [...features].sort((left, right) => {
|
||||
return (
|
||||
Number(left.get('_nearbyPriority') || 0) -
|
||||
Number(right.get('_nearbyPriority') || 0)
|
||||
);
|
||||
});
|
||||
const secondaryFeatures = sortedFeatures.filter(feature => {
|
||||
const priority = Number(feature.get('_nearbyPriority'));
|
||||
return Number.isFinite(priority) && priority > 1;
|
||||
});
|
||||
|
||||
secondaryFeatures.forEach((feature, index) => {
|
||||
const baseCoordinates = feature.get('_baseCoordinates') as
|
||||
| number[]
|
||||
| undefined;
|
||||
const geometry = feature.getGeometry();
|
||||
if (
|
||||
!baseCoordinates ||
|
||||
baseCoordinates.length < 2 ||
|
||||
!geometry ||
|
||||
geometry.getType() !== 'Point'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupSecondaryCount = secondaryFeatures.length;
|
||||
const baseOffsetPx =
|
||||
Number(feature.get('_nearbyExpandOffsetPx')) ||
|
||||
autoRule.expandOffsetPx;
|
||||
const ringSize = Math.max(1, layoutRule.ringSize);
|
||||
const ringIndex = Math.floor(index / ringSize);
|
||||
const ringOffsetIndex = index % ringSize;
|
||||
const currentRingCount = Math.min(
|
||||
ringSize,
|
||||
groupSecondaryCount - ringIndex * ringSize
|
||||
);
|
||||
const fanAngleDeg =
|
||||
currentRingCount <= 1
|
||||
? 0
|
||||
: currentRingCount <= 2
|
||||
? layoutRule.fanAngleForTwoDeg
|
||||
: currentRingCount <= 4
|
||||
? layoutRule.fanAngleForFourDeg
|
||||
: layoutRule.fanAngleForManyDeg;
|
||||
const angleStepDeg =
|
||||
currentRingCount <= 1 ? 0 : fanAngleDeg / (currentRingCount - 1);
|
||||
const startAngleDeg = -90 - fanAngleDeg / 2;
|
||||
const expandAngleDeg = startAngleDeg + ringOffsetIndex * angleStepDeg;
|
||||
const expandAngleRad = (expandAngleDeg * Math.PI) / 180;
|
||||
const expandOffsetPx =
|
||||
baseOffsetPx +
|
||||
ringIndex *
|
||||
Math.max(
|
||||
layoutRule.ringGapPx,
|
||||
baseOffsetPx * layoutRule.ringGapFactor
|
||||
);
|
||||
const offsetX = Math.cos(expandAngleRad) * expandOffsetPx * resolution;
|
||||
const offsetY = Math.sin(expandAngleRad) * expandOffsetPx * resolution;
|
||||
|
||||
feature.set('_nearbyExpandAngleDeg', expandAngleDeg, true);
|
||||
(geometry as Point).setCoordinates([
|
||||
baseCoordinates[0] + offsetX,
|
||||
baseCoordinates[1] - offsetY
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -318,7 +225,7 @@ export class PointLayerManager {
|
||||
layerKey: string,
|
||||
matchValue: string,
|
||||
checked: boolean,
|
||||
fieldKey: string = 'anchoPointState'
|
||||
fieldKey = 'anchoPointState'
|
||||
): void {
|
||||
const vectorLayer = this.layerRegistry.get(layerKey);
|
||||
if (!vectorLayer) return;
|
||||
@ -392,7 +299,6 @@ export class PointLayerManager {
|
||||
if (!isFinite(coord[0]) || !isFinite(coord[1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let iconUrl = iconCode ? getIconPath(iconCode) : '';
|
||||
if (!iconUrl) {
|
||||
iconUrl = getIconPath('default') || '';
|
||||
@ -403,7 +309,9 @@ export class PointLayerManager {
|
||||
...item,
|
||||
_layerKey: targetLayerKey
|
||||
});
|
||||
feature.setId(item.stcd || item._id);
|
||||
const rawFeatureId =
|
||||
item.stcd || item._id || `${lon.toFixed(6)},${lat.toFixed(6)}`;
|
||||
feature.setId(`${targetLayerKey}:${rawFeatureId}`);
|
||||
feature.set('_iconUrl', iconUrl);
|
||||
feature.set(
|
||||
'_labelText',
|
||||
@ -457,7 +365,6 @@ export class PointLayerManager {
|
||||
|
||||
const valueMap = fieldIndexMap.get(fieldKey);
|
||||
if (!valueMap) return [];
|
||||
|
||||
return valueMap.get(String(matchValue)) || [];
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,10 @@ import OlMap from 'ol/Map';
|
||||
import Overlay from 'ol/Overlay';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import { generatePopupHtml } from '@/utils/popupHtmlGenerator';
|
||||
import {
|
||||
generatePopupHtml,
|
||||
shouldPreferEng2Popup
|
||||
} from '@/utils/popupHtmlGenerator';
|
||||
|
||||
type PopupManagerOptions = {
|
||||
map: OlMap | null;
|
||||
@ -27,6 +30,11 @@ export class PopupManager {
|
||||
private map: OlMap | null;
|
||||
private popupOverlay: Overlay | null = null;
|
||||
private popupElement: HTMLElement | null = null;
|
||||
private batchPopupContainer: HTMLDivElement | null = null;
|
||||
private batchPopupItems: Array<{
|
||||
feature: Feature;
|
||||
element: HTMLDivElement;
|
||||
}> = [];
|
||||
private lastHoveredId: string | number | null = null;
|
||||
private animationFrameId: number | null = null;
|
||||
private getPointLayers: () => VectorLayer<VectorSource>[];
|
||||
@ -49,6 +57,11 @@ export class PopupManager {
|
||||
// 备注:初始化 Popup Overlay 并挂载到地图实例。
|
||||
initPopupOverlay(container: HTMLElement) {
|
||||
this.popupElement = container;
|
||||
this.popupElement.style.display = 'none';
|
||||
this.popupElement.style.removeProperty('position');
|
||||
this.popupElement.style.removeProperty('transform');
|
||||
this.popupElement.style.removeProperty('left');
|
||||
this.popupElement.style.removeProperty('top');
|
||||
this.popupElement.style.setProperty('pointer-events', 'none', 'important');
|
||||
this.popupMouseEnterHandler = () => {
|
||||
this.forceHidePopup();
|
||||
@ -190,7 +203,7 @@ export class PopupManager {
|
||||
|
||||
if (feature && coordinate) {
|
||||
const props = feature.getProperties();
|
||||
const popupHtml = props.popupHtml || generatePopupHtml(props);
|
||||
const popupHtml = this.getPopupHtml(props);
|
||||
|
||||
if (popupHtml) {
|
||||
this.popupElement.innerHTML = popupHtml;
|
||||
@ -204,22 +217,37 @@ export class PopupManager {
|
||||
this.popupElement.style.display = 'none';
|
||||
}
|
||||
|
||||
private getPopupHtml(props: Record<string, any>) {
|
||||
return props.popupHtml || generatePopupHtml(props);
|
||||
}
|
||||
|
||||
// 备注:批量显示多个要素的 Popup,使用绝对定位的 div 而非 Overlay。
|
||||
showPopupsForFeatures(
|
||||
features: Feature[],
|
||||
styleChecker?: (feature: Feature) => boolean
|
||||
styleChecker?: (feature: Feature) => boolean,
|
||||
options?: {
|
||||
rebuild?: boolean;
|
||||
}
|
||||
) {
|
||||
if (!this.map || !this.popupElement) return;
|
||||
|
||||
// 清除之前批量显示的 popups
|
||||
this.clearBatchPopups();
|
||||
const shouldRebuild = options?.rebuild !== false;
|
||||
|
||||
// 过滤:只显示图例可见的要素
|
||||
const visibleFeatures = features.filter(
|
||||
f => f.get('_legendVisible') !== false
|
||||
);
|
||||
|
||||
if (visibleFeatures.length === 0) return;
|
||||
if (visibleFeatures.length === 0) {
|
||||
this.clearBatchPopups();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldRebuild && this.batchPopupItems.length > 0) {
|
||||
this.updateBatchPopupPositions(styleChecker);
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearBatchPopups();
|
||||
|
||||
const mapElement = this.map.getTargetElement();
|
||||
const batchContainer = document.createElement('div');
|
||||
@ -234,6 +262,7 @@ export class PopupManager {
|
||||
z-index: 1000;
|
||||
`;
|
||||
batchContainer.id = 'batch-popup-container';
|
||||
this.batchPopupContainer = batchContainer;
|
||||
|
||||
// 先添加容器到 DOM,以便后续元素能正确测量尺寸
|
||||
mapElement.appendChild(batchContainer);
|
||||
@ -245,12 +274,6 @@ export class PopupManager {
|
||||
top: number;
|
||||
bottom: number;
|
||||
}> = [];
|
||||
let styleHiddenCount = 0;
|
||||
let blockedCount = 0;
|
||||
let renderedCount = 0;
|
||||
const renderedIds: Array<string | number | null> = [];
|
||||
const blockedIds: Array<string | number | null> = [];
|
||||
|
||||
visibleFeatures.forEach(feature => {
|
||||
const geom = feature.getGeometry();
|
||||
if (!geom || geom.getType() !== 'Point') return;
|
||||
@ -261,12 +284,15 @@ export class PopupManager {
|
||||
|
||||
// 检查样式是否隐藏(declutter、距离过滤等)
|
||||
if (styleChecker && !styleChecker(feature)) {
|
||||
styleHiddenCount += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const props = feature.getProperties();
|
||||
const popupHtml = props.popupHtml || generatePopupHtml(props);
|
||||
const popupHtml = shouldPreferEng2Popup(props)
|
||||
? generatePopupHtml(props, { forceEng2: true }) ||
|
||||
props.popupHtml ||
|
||||
generatePopupHtml(props)
|
||||
: this.getPopupHtml(props);
|
||||
if (!popupHtml) return;
|
||||
|
||||
// 创建与原始 popupElement 完全相同的样式
|
||||
@ -314,8 +340,10 @@ export class PopupManager {
|
||||
popupEl.style.visibility = 'visible';
|
||||
popupEl.style.left = `${popupX}px`;
|
||||
popupEl.style.top = `${popupY}px`;
|
||||
renderedCount += 1;
|
||||
renderedIds.push((feature.getId?.() as string | number | null) ?? null);
|
||||
this.batchPopupItems.push({
|
||||
feature,
|
||||
element: popupEl
|
||||
});
|
||||
|
||||
placedPopups.push({
|
||||
left: popupRect.left,
|
||||
@ -325,15 +353,76 @@ export class PopupManager {
|
||||
});
|
||||
} else {
|
||||
// 有碰撞,移除该 popup
|
||||
blockedCount += 1;
|
||||
blockedIds.push((feature.getId?.() as string | number | null) ?? null);
|
||||
batchContainer.removeChild(popupEl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateBatchPopupPositions(styleChecker?: (feature: Feature) => boolean) {
|
||||
if (
|
||||
!this.map ||
|
||||
!this.batchPopupContainer ||
|
||||
this.batchPopupItems.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mapSize = this.map.getSize();
|
||||
const viewportWidth = mapSize?.[0] ?? 0;
|
||||
const viewportHeight = mapSize?.[1] ?? 0;
|
||||
const viewportPadding = 48;
|
||||
|
||||
this.batchPopupItems.forEach(({ feature, element }) => {
|
||||
if (feature.get('_legendVisible') === false) {
|
||||
element.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
if (styleChecker && !styleChecker(feature)) {
|
||||
element.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const geom = feature.getGeometry();
|
||||
if (!geom || geom.getType() !== 'Point') {
|
||||
element.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const coords = (geom as Point).getCoordinates();
|
||||
const pixel = this.map?.getPixelFromCoordinate(coords);
|
||||
if (!pixel) {
|
||||
element.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const popupX = pixel[0];
|
||||
const popupY = pixel[1] - 10;
|
||||
if (
|
||||
popupX < -viewportPadding ||
|
||||
popupY < -viewportPadding ||
|
||||
popupX > viewportWidth + viewportPadding ||
|
||||
popupY > viewportHeight + viewportPadding
|
||||
) {
|
||||
element.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
element.style.left = `${popupX}px`;
|
||||
element.style.top = `${popupY}px`;
|
||||
element.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
// 备注:清除批量显示的 popups。
|
||||
clearBatchPopups() {
|
||||
this.batchPopupItems = [];
|
||||
if (this.batchPopupContainer) {
|
||||
this.batchPopupContainer.remove();
|
||||
this.batchPopupContainer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = document.getElementById('batch-popup-container');
|
||||
if (existing) {
|
||||
existing.remove();
|
||||
|
||||
@ -209,7 +209,7 @@ export class RegionMaskManager {
|
||||
}
|
||||
|
||||
// 备注:清除当前底图遮罩并按需恢复默认视野,供切换基地和异常兜底复用。
|
||||
clearMapMask(resetView: boolean = true): void {
|
||||
clearMapMask(resetView = true): void {
|
||||
const maskedLayers = this.maskBindings.map(binding => binding.layer);
|
||||
this.clearMaskEvents();
|
||||
|
||||
|
||||
@ -110,7 +110,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch, nextTick } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { MapClass } from '@/components/gis/map.class';
|
||||
@ -118,6 +118,7 @@ import { useMapOrchestrator } from '@/modules/map/application/map-orchestrator';
|
||||
import { useMapDataStore } from '@/modules/map/stores/map-data.store';
|
||||
import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import { useUiStore } from '@/store/modules/ui';
|
||||
import { useMapStore } from '@/store/modules/map';
|
||||
import { DateSetting } from '@/utils/enumeration';
|
||||
import { getFishList, getFishPointList } from '@/api/map';
|
||||
@ -126,6 +127,7 @@ const route = useRoute();
|
||||
const mapOrchestrator = useMapOrchestrator();
|
||||
const mapDataStore = useMapDataStore();
|
||||
const mapViewStore = useMapViewStore();
|
||||
const uiStore = useUiStore();
|
||||
const jidiSelectEventStore = useJidiSelectEventStore();
|
||||
const mapStore = useMapStore();
|
||||
const mapClass = MapClass.getInstance();
|
||||
@ -156,6 +158,7 @@ const fishPointOptions = ref<any[]>([]);
|
||||
const fishSpeciesOptions = ref<{ label: string; value: string }[]>([]);
|
||||
const fishSpeciesLoading = ref(false);
|
||||
const fishPointLoading = ref(false);
|
||||
const refocusTimer = ref<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const searchTimeRange = ref([
|
||||
dayjs(mapViewStore.searchTimeRange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
||||
@ -623,15 +626,48 @@ const handleCapacityChange = (value: string) => {
|
||||
mapOrchestrator.changeEngPointCapacity(value);
|
||||
};
|
||||
|
||||
// 备注:搜索定位只分发定位命令,不在筛选组件里直接操作地图实例。
|
||||
const handleAnchorPointChange = (value: string) => {
|
||||
if (!value) return;
|
||||
const clearRefocusTimer = () => {
|
||||
if (!refocusTimer.value) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(refocusTimer.value);
|
||||
refocusTimer.value = null;
|
||||
};
|
||||
|
||||
const focusSelectedAnchorPoint = () => {
|
||||
const selectedValue = anchorPointSelectValue.value;
|
||||
if (!selectedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapOrchestrator.focusPoint(
|
||||
value,
|
||||
selectedValue,
|
||||
14,
|
||||
isFishSurveyMode.value ? anchorPointOptions.value : undefined
|
||||
);
|
||||
};
|
||||
|
||||
// 备注:搜索定位只分发定位命令,不在筛选组件里直接操作地图实例。
|
||||
const handleAnchorPointChange = (value: string) => {
|
||||
if (!value) return;
|
||||
focusSelectedAnchorPoint();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => uiStore.mapSwitchCompletedTick,
|
||||
async (newVal, oldVal) => {
|
||||
if (newVal === oldVal || !anchorPointSelectValue.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
clearRefocusTimer();
|
||||
refocusTimer.value = setTimeout(() => {
|
||||
refocusTimer.value = null;
|
||||
focusSelectedAnchorPoint();
|
||||
}, 120);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -88,9 +88,7 @@ const hasEnvFac = computed(() =>
|
||||
|
||||
const legendItemClick = (item: any) => {
|
||||
if (item.parentName == '环保设施') return;
|
||||
if (item.canBeChecked == 0) {
|
||||
return;
|
||||
}
|
||||
if (item.canBeChecked == 0) return;
|
||||
if (item.nameEn) {
|
||||
mapOrchestrator.toggleLegend(item.nameEn, item.checked == 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<a-form ref="formRef" :model="formState" layout="inline" class="search-form">
|
||||
<a-space wrap>
|
||||
<a-form-item label="所属基地" name="dataDimensionVal">
|
||||
<a-select v-model:value="formState.dataDimensionVal" placeholder="请选择" style="width: 120px"
|
||||
<a-select v-model:value="formState.dataDimensionVal" placeholder="请选择" style="width: 140px"
|
||||
show-search :filter-option="filterBaseOption" option-filter-prop="label"
|
||||
@change="handleBaseChange">
|
||||
<a-select-option v-for="item in baseList" :key="item.wbsCode" :value="item.wbsCode"
|
||||
@ -32,7 +32,7 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="达标状态" name="sfdb">
|
||||
<a-select v-model:value="formState.sfdb" placeholder="请选择" style="width: 100px" show-search
|
||||
<a-select v-model:value="formState.sfdb" placeholder="请选择" style="width: 160px" show-search
|
||||
:filter-option="filterOption" option-filter-prop="label">
|
||||
<a-select-option value="" label="全部">全部</a-select-option>
|
||||
<a-select-option value="0" label="不达标">不达标</a-select-option>
|
||||
@ -44,12 +44,12 @@
|
||||
<a-form-item label="时间" name="dateValues">
|
||||
<a-date-picker v-model:value="formState.dateValues"
|
||||
:format="formState.types === 'month' ? 'YYYY-MM' : 'YYYY'" :picker="formState.types"
|
||||
placeholder="请选择月份" style="width: 140px" :disabled-date="disabledMonthDate" />
|
||||
placeholder="请选择月份" style="width: 100px" :disabled-date="disabledMonthDate" />
|
||||
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="断面名称" name="stnm">
|
||||
<a-input v-model:value="formState.stnm" placeholder="请输入断面名称" style="width: 200px" allow-clear />
|
||||
<a-input v-model:value="formState.stnm" placeholder="请输入断面名称" style="width: 180px" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
@ -90,7 +90,7 @@
|
||||
<!-- 详情弹窗 -->
|
||||
<a-modal v-model:open="detailModalVisible" :title="`${detailData.stnm || ''} 详细信息`" width="1536px" :footer="null"
|
||||
@cancel="handleDetailModalClose">
|
||||
<HJMZDTwoLays :tc="{
|
||||
<HJMZDTwoLays v-if="detailModalVisible" :tc="{
|
||||
stcd: detailData.stcd || '',
|
||||
startTime: detailData.tm,
|
||||
endTime: (typeof detailData.tm === 'string' ? detailData.tm.split(' ')[0] : dayjs(detailData.tm).format('YYYY-MM-DD')) + ' 23:59:59',
|
||||
@ -231,7 +231,7 @@ const columns = [
|
||||
title: '断面名称',
|
||||
dataIndex: 'stnm',
|
||||
key: 'stnm',
|
||||
width: 180,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
customCell: (record: any, rowIndex: number) => {
|
||||
const tableData = tableRef.value?.tableData || []
|
||||
@ -243,13 +243,13 @@ const columns = [
|
||||
title: '测站类型',
|
||||
dataIndex: 'sttypeName',
|
||||
key: 'sttypeName',
|
||||
width: 120
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '达标状态',
|
||||
dataIndex: 'sfdbName',
|
||||
key: 'sfdbName',
|
||||
width: 120,
|
||||
width: 80,
|
||||
customRender: ({ record }: any) => {
|
||||
const sfdb = record.sfdbName
|
||||
let color = '#000000'
|
||||
@ -267,19 +267,19 @@ const columns = [
|
||||
title: '水质要求',
|
||||
dataIndex: 'wwqtgName',
|
||||
key: 'wwqtgName',
|
||||
width: 120
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '当前水质',
|
||||
dataIndex: 'wqGrdName',
|
||||
key: 'wqGrdName',
|
||||
width: 120
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '去年同期水质',
|
||||
dataIndex: 'beforeWqGrdName',
|
||||
key: 'beforeWqGrdName',
|
||||
width: 150
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@ -674,7 +674,7 @@ const handleReset = () => {
|
||||
formState.dtinType = ''
|
||||
formState.types = props.type || 'month'
|
||||
formState.sfdb = ''
|
||||
formState.dateValues = dayjs()
|
||||
formState.dateValues =props.dateValue ? (typeof props.dateValue === 'string' ? dayjs(props.dateValue, props.type === 'month' ? 'YYYY-MM' : 'YYYY') : props.dateValue) : dayjs(),
|
||||
formState.stnm = ''
|
||||
pieCode.value = []
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
v-model:value="formState.sfdb"
|
||||
placeholder="请选择"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
style="width: 160px"
|
||||
>
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option :value="'0'">不达标</a-select-option>
|
||||
@ -114,7 +114,8 @@ const columnList = [
|
||||
elementCode: '',
|
||||
disabledDate: true,
|
||||
sorter: true,
|
||||
multiLink: false
|
||||
multiLink: false,
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
dataIndexRaw: '',
|
||||
@ -558,18 +559,18 @@ const transUnit = (value: any, tbCode: string, ys: string) => {
|
||||
// ==================== 监听器 ====================
|
||||
|
||||
// 监听 tc 和 formState.sfdb 变化,重新获取列配置
|
||||
watch(
|
||||
() => [props.tc.stcd, props.tc.startTime, props.tc.endTime, formState.sfdb],
|
||||
([stcd, startTime, endTime]) => {
|
||||
if (!stcd) {
|
||||
return;
|
||||
}
|
||||
//
|
||||
tableRef.value.tableData = [];
|
||||
getCol({ stcd, startTime, endTime });
|
||||
},
|
||||
{ immediate: false } // 不在初始化时立即执行,由 onMounted 处理
|
||||
);
|
||||
// watch(
|
||||
// () => [props.tc.stcd, props.tc.startTime, props.tc.endTime, formState.sfdb],
|
||||
// ([stcd, startTime, endTime]) => {
|
||||
// if (!stcd) {
|
||||
// return;
|
||||
// }
|
||||
// //
|
||||
// tableRef.value.tableData = [];
|
||||
// getCol({ stcd, startTime, endTime });
|
||||
// },
|
||||
// { immediate: false } // 不在初始化时立即执行,由 onMounted 处理
|
||||
// );
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
|
||||
@ -167,6 +167,7 @@ const columns = [
|
||||
dataIndex: 'dtinTm',
|
||||
key: 'dtinTm',
|
||||
width: 120,
|
||||
sort: true, // 启用排序
|
||||
customRender: ({ record }: any) => {
|
||||
const date = record.dtinTm
|
||||
if (!date) return '-'
|
||||
@ -192,6 +193,7 @@ const columns = [
|
||||
dataIndex: 'ststdt',
|
||||
key: 'ststdt',
|
||||
width: 120,
|
||||
sort: true, // 启用排序
|
||||
customRender: ({ record }: any) => {
|
||||
const date = record.ststdt
|
||||
if (!date) return '-'
|
||||
@ -211,6 +213,7 @@ const columns = [
|
||||
dataIndex: 'jcdt',
|
||||
key: 'jcdt',
|
||||
width: 120,
|
||||
sort: true, // 启用排序
|
||||
customRender: ({ record }: any) => {
|
||||
const date = record.jcdt
|
||||
if (!date) return '-'
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
ref="tableRef"
|
||||
:scrollY="400"
|
||||
:columns="columns"
|
||||
:list-url="vmsstbprptGetKendoList"
|
||||
:list-url="overviewvmsstbprptGetKendoList"
|
||||
:search-params="searchParams"
|
||||
:transform-data="customTransform"
|
||||
>
|
||||
@ -73,8 +73,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { vmsstbprptGetKendoList } from '@/api/home';
|
||||
import { ref, computed, onMounted, h } from 'vue';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { vmsstbprptGetKendoList,overviewvmsstbprptGetKendoList } from '@/api/home';
|
||||
import BasicTable from '@/components/BasicTable/index.vue';
|
||||
import { useModelStore } from '@/store/modules/model';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
@ -144,10 +146,9 @@ const columns = computed(() => {
|
||||
},
|
||||
{
|
||||
key: 'coenvwState',
|
||||
title: '监测状态',
|
||||
title: h(Tooltip, { title: '最近1天有监测数据为在线,否则为离线' }, { default: () => h('span', null, ['监测状态', h(QuestionCircleOutlined, { style: { marginLeft: '4px', color: '#000' } })]) }),
|
||||
dataIndex: 'coenvwState',
|
||||
width: '120px',
|
||||
|
||||
slots: { customRender: 'coenvwState' }
|
||||
},
|
||||
{
|
||||
@ -203,7 +204,7 @@ const columns = computed(() => {
|
||||
if (tabIndex.value !== '4') {
|
||||
baseColumns.push({
|
||||
key: 'coenvwState',
|
||||
title: '监测状态',
|
||||
title: h(Tooltip, { title: '最近1天有监测数据为在线,否则为离线' }, { default: () => h('span', null, ['监测状态', h(QuestionCircleOutlined, { style: { marginLeft: '4px', color: '#999' } })]) }),
|
||||
dataIndex: 'coenvwState',
|
||||
width: '120px',
|
||||
slots: { customRender: 'coenvwState' }
|
||||
@ -313,7 +314,7 @@ const getRstcdData = async () => {
|
||||
],
|
||||
select: ['stcd', 'stnm']
|
||||
};
|
||||
const res = await vmsstbprptGetKendoList(parmas);
|
||||
const res = await overviewvmsstbprptGetKendoList(parmas);
|
||||
rstcdOptions.value = res.data.data.map((item) => {
|
||||
return {
|
||||
value: item.stcd,
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
const loading = ref(false);
|
||||
const isOpen = ref(true);
|
||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||
|
||||
|
||||
@ -317,7 +317,7 @@ import dayjs from 'dayjs';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import videoCover from '@/assets/images/videFm.jpg';
|
||||
import { useDraggable } from '@/utils/drag';
|
||||
;
|
||||
|
||||
const baseUrl = import.meta.env.VITE_APP_ATTACHMENT_URL;
|
||||
|
||||
// ==================== Props ====================
|
||||
|
||||
@ -33,9 +33,12 @@
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<!-- <a-button style="margin-left: 8px" @click="handleReset">重置</a-button> -->
|
||||
<a-tooltip title="导出(暂未实现)">
|
||||
<a-button style="margin-left: 8px">导出</a-button>
|
||||
</a-tooltip>
|
||||
<a-button
|
||||
style="margin-left: 8px"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
>导出</a-button
|
||||
>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@ -66,11 +69,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, reactive, watch,onMounted } from 'vue';
|
||||
import { ref, computed, reactive, watch, onMounted } from 'vue';
|
||||
import BasicTable from '@/components/BasicTable/index.vue';
|
||||
import { getStllGaojingDetail } from '@/api/zngj';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import { useModelStore } from '@/store/modules/model';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
defineOptions({
|
||||
name: 'ModeGaoJing'
|
||||
});
|
||||
@ -262,6 +266,19 @@ const opentan = (record: any) => {
|
||||
modelStore.title = record.ennm;
|
||||
modelStore.params.stcd = record.rstcd;
|
||||
};
|
||||
/* 导出逻辑 */
|
||||
const exportLoading = ref(false);
|
||||
const handleExport = () => {
|
||||
exportLoading.value = true;
|
||||
|
||||
tableRef.value
|
||||
.exportTable({
|
||||
fileName: `生态流量低于限值告警统计_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||
})
|
||||
.finally(() => {
|
||||
exportLoading.value = false;
|
||||
});
|
||||
};
|
||||
// 初始化加载数据
|
||||
// handleSearch();
|
||||
onMounted(() => {
|
||||
@ -283,7 +300,6 @@ onMounted(() => {
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -32,6 +32,12 @@
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
>导出</a-button
|
||||
>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@ -67,7 +73,7 @@ import BasicTable from '@/components/BasicTable/index.vue';
|
||||
import { getStllGaojingDetail } from '@/api/zngj';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import { useModelStore } from '@/store/modules/model';
|
||||
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
defineOptions({
|
||||
name: 'ModeGaoJing'
|
||||
});
|
||||
@ -266,6 +272,19 @@ const opentan = (record: any) => {
|
||||
modelStore.title = record.ennm;
|
||||
modelStore.params.stcd = record.rstcd;
|
||||
};
|
||||
/* 导出逻辑 */
|
||||
const exportLoading = ref(false);
|
||||
const handleExport = () => {
|
||||
exportLoading.value = true;
|
||||
|
||||
tableRef.value
|
||||
.exportTable({
|
||||
fileName: `生态流量低于限值告警统计_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||
})
|
||||
.finally(() => {
|
||||
exportLoading.value = false;
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
@ -1,47 +1,63 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<SidePanelItem title="水质监测数据告警情况" :select="select" @update-values="handlePanelChange">
|
||||
<a-spin :spinning="dataLoading" tip="加载中...">
|
||||
<div class="body_box">
|
||||
<div class="box" v-for="(item, index) in data" :key="index" @click="handleCardClick(item.warnState)" style="cursor: pointer;">
|
||||
<div>
|
||||
<div :class="item.icon" :style="{background:item.background}"></div>
|
||||
<div class="title_text">{{ item.name }}</div>
|
||||
</div>
|
||||
<div><span class="text_num">{{ item.num }}</span><span>座</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</SidePanelItem>
|
||||
<div>
|
||||
<SidePanelItem
|
||||
title="水质监测数据告警情况"
|
||||
:select="select"
|
||||
@update-values="handlePanelChange"
|
||||
>
|
||||
<a-spin :spinning="dataLoading" tip="加载中...">
|
||||
<div class="body_box">
|
||||
<div
|
||||
class="box"
|
||||
v-for="(item, index) in data"
|
||||
:key="index"
|
||||
@click="handleCardClick(item.warnState)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
:class="item.icon"
|
||||
:style="{ background: item.background }"
|
||||
></div>
|
||||
<div class="title_text">{{ item.name }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text_num">{{ item.num }}</span
|
||||
><span>座</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</SidePanelItem>
|
||||
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
title="水质监测告警统计"
|
||||
width="1536px"
|
||||
:footer="null"
|
||||
@cancel="handleModalClose"
|
||||
>
|
||||
<ModeGaoJing
|
||||
v-if="modalVisible"
|
||||
:baseId="baseid"
|
||||
:warnState="currentWarnState"
|
||||
:dtinType="defaultValue"
|
||||
/>
|
||||
</a-modal>
|
||||
</div>
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
title="水质监测告警统计"
|
||||
width="1536px"
|
||||
:footer="null"
|
||||
@cancel="handleModalClose"
|
||||
>
|
||||
<ModeGaoJing
|
||||
v-if="modalVisible"
|
||||
:baseId="baseid"
|
||||
:warnState="currentWarnState"
|
||||
:dtinType="defaultValue"
|
||||
/>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { ref, watch } from 'vue';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import SidePanelItem from "@/components/SidePanelItem/index.vue";
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
import { getShuizhiGaojingList } from '@/api/zngj';
|
||||
import ModeGaoJing from './ModeGaoJing.vue';
|
||||
import { useDraggable } from '@/utils/drag';
|
||||
|
||||
defineOptions({
|
||||
name: "shuizhijianceshuju",
|
||||
name: 'shuizhijianceshuju'
|
||||
});
|
||||
|
||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||
@ -54,222 +70,224 @@ const modalVisible = ref(false);
|
||||
const currentWarnState = ref('');
|
||||
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
||||
const handleCardClick = (warnState: string) => {
|
||||
currentWarnState.value = warnState;
|
||||
modalVisible.value = true;
|
||||
currentWarnState.value = warnState;
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
modalVisible.value = false;
|
||||
modalVisible.value = false;
|
||||
};
|
||||
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: '0',
|
||||
options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '国家站', value: '1' },
|
||||
{ label: '自建站', value: '0' },
|
||||
],
|
||||
width: '140px',
|
||||
minWidth: '140px',
|
||||
show: true,
|
||||
value: '0',
|
||||
options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '国家站', value: '1' },
|
||||
{ label: '自建站', value: '0' }
|
||||
],
|
||||
width: '140px',
|
||||
minWidth: '140px'
|
||||
});
|
||||
|
||||
const data = ref([
|
||||
{
|
||||
{
|
||||
name: '正常',
|
||||
num: '0',
|
||||
warnState: '1',
|
||||
icon: 'iconfont icon-map-rgszzDabiao zhuangji',
|
||||
background: '#78c300'
|
||||
},
|
||||
{
|
||||
name: '告警',
|
||||
num: '0',
|
||||
warnState: '2',
|
||||
icon: 'iconfont icon-map-rgszzDabiao shebeiZhengchang',
|
||||
background: '#e55555'
|
||||
},
|
||||
{
|
||||
name: '无数据',
|
||||
num: '0',
|
||||
warnState: '0',
|
||||
icon: 'iconfont icon-map-rgszzDabiao shebeiLixian',
|
||||
background: '#8d8d8d'
|
||||
}
|
||||
]);
|
||||
|
||||
const handlePanelChange = (payload: any) => {
|
||||
if (payload.select !== undefined) {
|
||||
defaultValue.value = payload.select;
|
||||
select.value.value = payload.select;
|
||||
}
|
||||
getCardData();
|
||||
};
|
||||
|
||||
const getCardData = async () => {
|
||||
if (!baseid.value) return;
|
||||
|
||||
dataLoading.value = true;
|
||||
|
||||
try {
|
||||
const filters: any[] = [
|
||||
{
|
||||
field: 'sttpCode',
|
||||
operator: 'in',
|
||||
dataType: 'string',
|
||||
value: ['WQ']
|
||||
},
|
||||
{
|
||||
field: 'mway',
|
||||
operator: 'eq',
|
||||
dataType: 'number',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
field: 'usfl',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: '1'
|
||||
},
|
||||
baseid.value != 'all'
|
||||
? {
|
||||
field: 'baseId',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: baseid.value
|
||||
}
|
||||
: null
|
||||
].filter(Boolean);
|
||||
|
||||
if (defaultValue.value === '') {
|
||||
filters.push({
|
||||
field: 'dtinType',
|
||||
operator: 'in',
|
||||
dataType: 'number',
|
||||
value: [0, 1]
|
||||
});
|
||||
} else {
|
||||
filters.push({
|
||||
field: 'dtinType',
|
||||
operator: 'eq',
|
||||
dataType: 'number',
|
||||
value: Number(defaultValue.value)
|
||||
});
|
||||
}
|
||||
|
||||
const params = {
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters
|
||||
},
|
||||
group: [
|
||||
{
|
||||
dir: 'desc',
|
||||
field: 'warnState',
|
||||
aggregates: [
|
||||
{
|
||||
field: 'stcd',
|
||||
aggregate: 'count'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
select: []
|
||||
};
|
||||
|
||||
const res = await getShuizhiGaojingList(params);
|
||||
const list = res?.data?.data || [];
|
||||
// debugger
|
||||
const result = {
|
||||
normal: 0,
|
||||
abnormal: 0,
|
||||
noData: 0
|
||||
};
|
||||
|
||||
list.forEach((item: any) => {
|
||||
const warnState = item.items?.[0]?.WARNSTATE;
|
||||
const count = item.items?.[0]?.COUNT_STCD ?? 0;
|
||||
if (warnState == 0) result.noData = count;
|
||||
else if (warnState == 1) result.normal = count;
|
||||
else if (warnState == 2) result.abnormal = count;
|
||||
});
|
||||
|
||||
data.value = [
|
||||
{
|
||||
name: '正常',
|
||||
num: '0',
|
||||
warnState: '1',
|
||||
icon: 'iconfont icon-map-rgszzDabiao zhuangji',
|
||||
background: '#78c300',
|
||||
},
|
||||
{
|
||||
background: '#78c300'
|
||||
},
|
||||
{
|
||||
name: '告警',
|
||||
num: '0',
|
||||
warnState: '2',
|
||||
icon: 'iconfont icon-map-rgszzDabiao shebeiZhengchang',
|
||||
background: '#e55555',
|
||||
},
|
||||
{
|
||||
background: '#e55555'
|
||||
},
|
||||
{
|
||||
name: '无数据',
|
||||
num: '0',
|
||||
warnState: '0',
|
||||
icon: 'iconfont icon-map-rgszzDabiao shebeiLixian',
|
||||
background: '#8d8d8d',
|
||||
},
|
||||
]);
|
||||
|
||||
const handlePanelChange = (payload: any) => {
|
||||
if (payload.select !== undefined) {
|
||||
defaultValue.value = payload.select;
|
||||
select.value.value = payload.select;
|
||||
}
|
||||
getCardData();
|
||||
};
|
||||
|
||||
const getCardData = async () => {
|
||||
if (!baseid.value) return;
|
||||
|
||||
dataLoading.value = true;
|
||||
|
||||
try {
|
||||
const filters: any[] = [
|
||||
{
|
||||
field: 'sttpCode',
|
||||
operator: 'in',
|
||||
dataType: 'string',
|
||||
value: ['WQ'],
|
||||
},
|
||||
{
|
||||
field: 'mway',
|
||||
operator: 'eq',
|
||||
dataType: 'number',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
field: 'usfl',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: '1',
|
||||
},
|
||||
baseid.value !='all'? {
|
||||
field: 'baseId',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: baseid.value,
|
||||
}: null,
|
||||
].filter(Boolean);
|
||||
|
||||
if (defaultValue.value === '') {
|
||||
filters.push({
|
||||
field: 'dtinType',
|
||||
operator: 'in',
|
||||
dataType: 'number',
|
||||
value: [0, 1],
|
||||
});
|
||||
} else {
|
||||
filters.push({
|
||||
field: 'dtinType',
|
||||
operator: 'eq',
|
||||
dataType: 'number',
|
||||
value: Number(defaultValue.value),
|
||||
});
|
||||
}
|
||||
|
||||
const params = {
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters,
|
||||
},
|
||||
group: [
|
||||
{
|
||||
dir: 'desc',
|
||||
field: 'warnState',
|
||||
aggregates: [
|
||||
{
|
||||
field: 'stcd',
|
||||
aggregate: 'count',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
select: [],
|
||||
};
|
||||
|
||||
const res = await getShuizhiGaojingList(params);
|
||||
const list = res?.data?.data || [];
|
||||
// debugger
|
||||
const result = {
|
||||
normal: 0,
|
||||
abnormal: 0,
|
||||
noData: 0,
|
||||
};
|
||||
|
||||
list.forEach((item: any) => {
|
||||
const warnState = item.items?.[0]?.WARNSTATE;
|
||||
const count = item.items?.[0]?.COUNT_STCD ?? 0;
|
||||
if (warnState == 0) result.noData = count;
|
||||
else if (warnState == 1) result.normal = count;
|
||||
else if (warnState == 2) result.abnormal = count;
|
||||
});
|
||||
|
||||
data.value = [
|
||||
{
|
||||
name: '正常',
|
||||
num: result.normal.toString(),
|
||||
warnState: '1',
|
||||
icon: 'iconfont icon-shuidianzhan1 zhuangji',
|
||||
background: '#78c300',
|
||||
},
|
||||
{
|
||||
name: '告警',
|
||||
num: result.abnormal.toString(),
|
||||
warnState: '2',
|
||||
icon: 'iconfont icon-shuidianzhanGaojing shebeiZhengchang',
|
||||
background: '#e55555',
|
||||
},
|
||||
{
|
||||
name: '无数据',
|
||||
num: result.noData.toString(),
|
||||
warnState: '0',
|
||||
icon: 'iconfont icon-shuidianzhanWushujv shebeiLixian',
|
||||
background: '#8d8d8d',
|
||||
},
|
||||
];
|
||||
} catch (error) {
|
||||
console.error('获取水质监测告警数据失败:', error);
|
||||
} finally {
|
||||
dataLoading.value = false;
|
||||
}
|
||||
background: '#8d8d8d'
|
||||
}
|
||||
];
|
||||
} catch (error) {
|
||||
console.error('获取水质监测告警数据失败:', error);
|
||||
} finally {
|
||||
dataLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
async newVal => {
|
||||
if (newVal && newVal.wbsCode) {
|
||||
baseid.value = newVal.wbsCode;
|
||||
getCardData();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
async newVal => {
|
||||
if (newVal && newVal.wbsCode) {
|
||||
baseid.value = newVal.wbsCode;
|
||||
getCardData();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.body_box {
|
||||
width: 100%;
|
||||
height: 132px;
|
||||
width: 100%;
|
||||
height: 132px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.box {
|
||||
width: 126px;
|
||||
height: 116px;
|
||||
border: 1px solid rgba(108, 164, 247, 0.35);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.box {
|
||||
width: 126px;
|
||||
height: 116px;
|
||||
border: 1px solid rgba(108, 164, 247, 0.35);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.iconfont {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #78c300;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
.title_text{
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.text_num{
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
.iconfont {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #78c300;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
.title_text {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.text_num {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick,watch } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
// import { ref, watch } from 'vue';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
import LsstjkTk from './LsstjkTk.vue';
|
||||
|
||||
@ -43,6 +43,7 @@ const HYDRO_DYNAMIC_LAYER_KEYS = [
|
||||
];
|
||||
const ENG_POINT_LAYER_KEY = 'eng_point';
|
||||
const ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5;
|
||||
const CESIUM_ZOOM_SYNC_DEBOUNCE_MS = 80;
|
||||
const LARGE_ENG_LEGEND_PREFIX = 'large_eng_';
|
||||
const MID_ENG_LEGEND_PREFIX = 'mid_eng_';
|
||||
|
||||
@ -55,12 +56,20 @@ export const useMapOrchestrator = () => {
|
||||
const mapViewStore = useMapViewStore();
|
||||
const jidiSelectEventStore = useJidiSelectEventStore();
|
||||
let activePageLoadRequestId = 0;
|
||||
let zoomListenerKey: any = null;
|
||||
let removeZoomListener: (() => void) | null = null;
|
||||
let stopBaseSelectionWatch: WatchStopHandle | null = null;
|
||||
const initializedBaseLayerKeys = new Set<string>();
|
||||
const baseSelectionDebounceTimer = ref<ReturnType<typeof setTimeout> | null>(
|
||||
null
|
||||
);
|
||||
const zoomSyncDebounceTimer = ref<ReturnType<typeof setTimeout> | null>(null);
|
||||
let latestHydroMenuGetter: (() => boolean) | null = null;
|
||||
let currentMapType: '2D' | '3D' = '2D';
|
||||
let hydroMenuDefaultCheckedKeys = new Set<string>();
|
||||
let hydroDynamicLayerSyncTask: {
|
||||
visible: boolean;
|
||||
promise: Promise<string[] | void>;
|
||||
} | null = null;
|
||||
|
||||
// 备注:统一获取当前地图视图对象,避免在多个组件里重复兼容 2D/3D view 访问方式。
|
||||
const getCurrentView = () => {
|
||||
@ -69,7 +78,14 @@ export const useMapOrchestrator = () => {
|
||||
|
||||
// 备注:统一获取当前地图缩放级别,供页面切换和缩放监听复用。
|
||||
const getCurrentZoom = (): number | undefined => {
|
||||
return getCurrentView()?.getZoom?.();
|
||||
return mapClass.getCurrentZoom();
|
||||
};
|
||||
|
||||
const clearZoomSyncDebounceTimer = () => {
|
||||
if (zoomSyncDebounceTimer.value) {
|
||||
clearTimeout(zoomSyncDebounceTimer.value);
|
||||
zoomSyncDebounceTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 备注:统一把当前页面中的 GIS 基础图层初始化到地图实例,避免在图层树组件里做配置解析和加层编排。
|
||||
@ -95,7 +111,20 @@ export const useMapOrchestrator = () => {
|
||||
}
|
||||
|
||||
if (item.key === 'customBaseLayer') {
|
||||
sessionStorage.setItem('customBaseLayer', JSON.stringify(item.config));
|
||||
const serializableBaseLayerConfig = {
|
||||
key: item.config?.key,
|
||||
id: item.config?.id,
|
||||
name: item.config?.name,
|
||||
type: item.config?.type,
|
||||
url: item.config?.url,
|
||||
url_3d: item.config?.url_3d,
|
||||
layers: item.config?.layers,
|
||||
rasteropacity: item.config?.rasteropacity
|
||||
};
|
||||
sessionStorage.setItem(
|
||||
'customBaseLayer',
|
||||
JSON.stringify(serializableBaseLayerConfig)
|
||||
);
|
||||
}
|
||||
|
||||
if (!initializedBaseLayerKeys.has(item.key)) {
|
||||
@ -154,6 +183,9 @@ export const useMapOrchestrator = () => {
|
||||
|
||||
// 先设置图层数据(更新 checkedLayerKeys),再设置图例数据
|
||||
if (layerConfig.length > 0) {
|
||||
hydroMenuDefaultCheckedKeys = new Set(
|
||||
mapConfigStore.extractCheckedLayerKeys(layerConfig)
|
||||
);
|
||||
mapStore.setLayerData(layerConfig);
|
||||
ensureBaseLayersInitialized(layerConfig);
|
||||
|
||||
@ -229,10 +261,56 @@ export const useMapOrchestrator = () => {
|
||||
await mapClass.init(container);
|
||||
|
||||
if (popupContainer) {
|
||||
const popupHost = container.parentElement;
|
||||
if (popupHost && popupContainer.parentElement !== popupHost) {
|
||||
popupHost.appendChild(popupContainer);
|
||||
}
|
||||
popupContainer.style.display = 'none';
|
||||
mapClass.initPopupOverlay(popupContainer);
|
||||
}
|
||||
};
|
||||
|
||||
const replayCurrentMapState = async (
|
||||
options: Pick<InitializeOptions, 'popupContainer'> & {
|
||||
getIsHydroMenu: () => boolean;
|
||||
}
|
||||
) => {
|
||||
initializedBaseLayerKeys.clear();
|
||||
|
||||
const currentZoom = getCurrentZoom();
|
||||
if (currentZoom !== undefined) {
|
||||
mapViewStore.setCurrentZoomLevel(currentZoom);
|
||||
}
|
||||
|
||||
if (options.popupContainer) {
|
||||
const container = document.getElementById('mapContainer');
|
||||
const popupHost = container?.parentElement || null;
|
||||
if (popupHost && options.popupContainer.parentElement !== popupHost) {
|
||||
popupHost.appendChild(options.popupContainer);
|
||||
}
|
||||
options.popupContainer.style.display = 'none';
|
||||
mapClass.initPopupOverlay(options.popupContainer);
|
||||
}
|
||||
|
||||
ensureBaseLayersInitialized(mapStore.layerData || []);
|
||||
|
||||
const checkedKeys = mapViewStore.getCheckedLayerKeys();
|
||||
await mapStore.updateLayerData(checkedKeys, true);
|
||||
mapStore.setSelectedLegendData();
|
||||
await syncZoomSensitiveState({
|
||||
isHydroMenu: options.getIsHydroMenu(),
|
||||
refreshEngPoint: true
|
||||
});
|
||||
|
||||
const selectedBaseId = mapViewStore.selectedBaseId;
|
||||
mapClass.jdPanelControlShowAndHidden(
|
||||
selectedBaseId || 'all',
|
||||
!!selectedBaseId
|
||||
);
|
||||
|
||||
bindZoomListener(options.getIsHydroMenu);
|
||||
};
|
||||
|
||||
// 备注:统一完成首屏页面配置加载,供挂载阶段和后续页面重载复用。
|
||||
const initialize = async ({
|
||||
container,
|
||||
@ -249,20 +327,18 @@ export const useMapOrchestrator = () => {
|
||||
|
||||
// 备注:统一绑定地图缩放监听,让页面组件只负责触发初始化,不再直接处理缩放联动细节。
|
||||
const bindZoomListener = (getIsHydroMenu: () => boolean) => {
|
||||
if (zoomListenerKey) {
|
||||
unByKey(zoomListenerKey);
|
||||
zoomListenerKey = null;
|
||||
if (removeZoomListener) {
|
||||
removeZoomListener();
|
||||
removeZoomListener = null;
|
||||
}
|
||||
|
||||
const view = getCurrentView();
|
||||
if (!view?.on) return;
|
||||
clearZoomSyncDebounceTimer();
|
||||
|
||||
const currentZoom = getCurrentZoom();
|
||||
if (currentZoom !== undefined) {
|
||||
mapViewStore.setCurrentZoomLevel(currentZoom);
|
||||
}
|
||||
|
||||
zoomListenerKey = view.on('change:resolution', async () => {
|
||||
const emitZoomChange = async () => {
|
||||
const zoom = getCurrentZoom();
|
||||
if (zoom === undefined) return;
|
||||
|
||||
@ -271,7 +347,36 @@ export const useMapOrchestrator = () => {
|
||||
zoom,
|
||||
getIsHydroMenu()
|
||||
);
|
||||
};
|
||||
|
||||
if (currentMapType === '3D') {
|
||||
const removeCesiumListener =
|
||||
mapClass.view?.camera?.changed?.addEventListener(() => {
|
||||
clearZoomSyncDebounceTimer();
|
||||
zoomSyncDebounceTimer.value = setTimeout(() => {
|
||||
zoomSyncDebounceTimer.value = null;
|
||||
void emitZoomChange();
|
||||
}, CESIUM_ZOOM_SYNC_DEBOUNCE_MS);
|
||||
});
|
||||
|
||||
if (typeof removeCesiumListener === 'function') {
|
||||
removeZoomListener = () => {
|
||||
clearZoomSyncDebounceTimer();
|
||||
removeCesiumListener();
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const view = getCurrentView();
|
||||
if (!view?.on) return;
|
||||
|
||||
const zoomListenerKey = view.on('change:resolution', () => {
|
||||
void emitZoomChange();
|
||||
});
|
||||
removeZoomListener = () => {
|
||||
unByKey(zoomListenerKey);
|
||||
};
|
||||
};
|
||||
|
||||
// 备注:统一绑定基地切换事件,让页面组件不再直接 watch 外部基地选择源。
|
||||
@ -308,10 +413,34 @@ export const useMapOrchestrator = () => {
|
||||
getIsHydroMenu: () => boolean;
|
||||
}
|
||||
) => {
|
||||
currentMapType = '2D';
|
||||
latestHydroMenuGetter = options.getIsHydroMenu;
|
||||
await initializeMapShell(options);
|
||||
bindBaseSelection();
|
||||
bindZoomListener(options.getIsHydroMenu);
|
||||
await loadPage({ pageKey: options.pageKey, isInitialLoad: true });
|
||||
await syncZoomSensitiveState({
|
||||
isHydroMenu: options.getIsHydroMenu(),
|
||||
refreshEngPoint: true
|
||||
});
|
||||
};
|
||||
|
||||
const switchMapType = async (
|
||||
type: '2D' | '3D',
|
||||
options: Pick<InitializeOptions, 'popupContainer'> & {
|
||||
getIsHydroMenu?: () => boolean;
|
||||
} = {}
|
||||
) => {
|
||||
const hydroMenuGetter =
|
||||
options.getIsHydroMenu || latestHydroMenuGetter || (() => false);
|
||||
latestHydroMenuGetter = hydroMenuGetter;
|
||||
currentMapType = type;
|
||||
|
||||
await mapClass.switchView(type);
|
||||
await replayCurrentMapState({
|
||||
popupContainer: options.popupContainer,
|
||||
getIsHydroMenu: hydroMenuGetter
|
||||
});
|
||||
};
|
||||
|
||||
// 备注:统一处理菜单切换后的页面配置重载,供页面 watch 直接复用。
|
||||
@ -361,47 +490,91 @@ export const useMapOrchestrator = () => {
|
||||
(layer: any) => layer?.url && !mapDataStore.hasPointLayerData(layer.key)
|
||||
);
|
||||
|
||||
for (const layer of layersToLoad) {
|
||||
await mapStore.loadLayerData(layer);
|
||||
if (layersToLoad.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(layersToLoad.map(layer => mapStore.loadLayerData(layer)));
|
||||
};
|
||||
|
||||
// 备注:统一控制水电开发菜单下的动态图层增删,避免页面组件直接操作加载和勾选细节。
|
||||
const syncHydroDynamicLayers = async (visible: boolean) => {
|
||||
const currentCheckedKeys = mapViewStore.getCheckedLayerKeys();
|
||||
const pageDefaultCheckedKeys = new Set(
|
||||
mapConfigStore.extractCheckedLayerKeys(mapStore.layerData || [])
|
||||
);
|
||||
if (
|
||||
hydroDynamicLayerSyncTask &&
|
||||
hydroDynamicLayerSyncTask.visible === visible
|
||||
) {
|
||||
return hydroDynamicLayerSyncTask.promise;
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
const layersToAdd = HYDRO_DYNAMIC_LAYER_KEYS.filter(
|
||||
key => !currentCheckedKeys.includes(key)
|
||||
);
|
||||
const task = (async () => {
|
||||
const currentCheckedKeys = mapViewStore.getCheckedLayerKeys();
|
||||
|
||||
if (layersToAdd.length === 0) {
|
||||
if (visible) {
|
||||
const layersToAdd = HYDRO_DYNAMIC_LAYER_KEYS.filter(
|
||||
key => !currentCheckedKeys.includes(key)
|
||||
);
|
||||
|
||||
if (layersToAdd.length === 0) {
|
||||
return currentCheckedKeys;
|
||||
}
|
||||
|
||||
await ensureDynamicLayersLoaded(layersToAdd);
|
||||
return applyLayerSelection({
|
||||
checkedKeys: [...currentCheckedKeys, ...layersToAdd]
|
||||
});
|
||||
}
|
||||
|
||||
const nextCheckedKeys = currentCheckedKeys.filter(key => {
|
||||
if (!HYDRO_DYNAMIC_LAYER_KEYS.includes(key)) {
|
||||
return true;
|
||||
}
|
||||
return hydroMenuDefaultCheckedKeys.has(key);
|
||||
});
|
||||
|
||||
if (nextCheckedKeys.length === currentCheckedKeys.length) {
|
||||
return currentCheckedKeys;
|
||||
}
|
||||
|
||||
await ensureDynamicLayersLoaded(layersToAdd);
|
||||
return applyLayerSelection({
|
||||
checkedKeys: [...currentCheckedKeys, ...layersToAdd]
|
||||
checkedKeys: nextCheckedKeys
|
||||
});
|
||||
}
|
||||
|
||||
const nextCheckedKeys = currentCheckedKeys.filter(key => {
|
||||
if (!HYDRO_DYNAMIC_LAYER_KEYS.includes(key)) {
|
||||
return true;
|
||||
})().finally(() => {
|
||||
if (hydroDynamicLayerSyncTask?.promise === task) {
|
||||
hydroDynamicLayerSyncTask = null;
|
||||
}
|
||||
return pageDefaultCheckedKeys.has(key);
|
||||
});
|
||||
|
||||
if (nextCheckedKeys.length === currentCheckedKeys.length) {
|
||||
return currentCheckedKeys;
|
||||
hydroDynamicLayerSyncTask = {
|
||||
visible,
|
||||
promise: task
|
||||
};
|
||||
return task;
|
||||
};
|
||||
|
||||
// 备注:统一按当前缩放对齐首页菜单的中型站过滤和 12 级动态图层状态。
|
||||
const syncZoomSensitiveState = async ({
|
||||
isHydroMenu,
|
||||
refreshEngPoint = false
|
||||
}: {
|
||||
isHydroMenu: boolean;
|
||||
refreshEngPoint?: boolean;
|
||||
}) => {
|
||||
const currentZoom = getCurrentZoom();
|
||||
if (currentZoom !== undefined) {
|
||||
mapViewStore.setCurrentZoomLevel(currentZoom);
|
||||
}
|
||||
|
||||
return applyLayerSelection({
|
||||
checkedKeys: nextCheckedKeys
|
||||
});
|
||||
if (
|
||||
isHydroMenu &&
|
||||
refreshEngPoint &&
|
||||
mapViewStore.getCheckedLayerKeys().includes(ENG_POINT_LAYER_KEY)
|
||||
) {
|
||||
mapStore.refreshPointLayerDisplayData(ENG_POINT_LAYER_KEY);
|
||||
}
|
||||
|
||||
await syncHydroDynamicLayers(
|
||||
!!isHydroMenu && currentZoom !== undefined && currentZoom >= 12
|
||||
);
|
||||
};
|
||||
|
||||
// 备注:统一处理地图缩放联动,收口水电开发菜单下的动态图层阈值切换逻辑。
|
||||
@ -419,6 +592,7 @@ export const useMapOrchestrator = () => {
|
||||
currentZoom <= ENG_POINT_MEDIUM_VISIBLE_ZOOM);
|
||||
|
||||
if (
|
||||
isHydroMenu &&
|
||||
crossedEngPointMediumThreshold &&
|
||||
mapViewStore.getCheckedLayerKeys().includes(ENG_POINT_LAYER_KEY)
|
||||
) {
|
||||
@ -454,13 +628,10 @@ export const useMapOrchestrator = () => {
|
||||
}
|
||||
|
||||
return reloadPage(pageKey).then(async () => {
|
||||
const currentZoom = getCurrentZoom();
|
||||
if (isHydroMenu && currentZoom !== undefined && currentZoom >= 12) {
|
||||
await syncHydroDynamicLayers(true);
|
||||
return;
|
||||
}
|
||||
|
||||
await syncHydroDynamicLayers(false);
|
||||
await syncZoomSensitiveState({
|
||||
isHydroMenu,
|
||||
refreshEngPoint: true
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -487,12 +658,15 @@ export const useMapOrchestrator = () => {
|
||||
// 备注:统一处理单个图例切换命令,供图例组件复用。
|
||||
const toggleLegend = (nameEn: string, checked?: number) => {
|
||||
if (!nameEn) return;
|
||||
console.log('toggleLegend', nameEn, checked);
|
||||
console.log(mapViewStore.getLegendChecked(nameEn));
|
||||
const nextChecked =
|
||||
checked === undefined
|
||||
? mapViewStore.getLegendChecked(nameEn) === 1
|
||||
? 0
|
||||
: 1
|
||||
: checked;
|
||||
console.log(nextChecked);
|
||||
mapStore.updateLegendChecked(nameEn, nextChecked);
|
||||
};
|
||||
|
||||
@ -570,30 +744,47 @@ export const useMapOrchestrator = () => {
|
||||
mapDataStore.pointData.find(pointMatcher) ||
|
||||
fallbackPoints.find(pointMatcher);
|
||||
if (!targetPoint?.lgtd || !targetPoint?.lttd) return;
|
||||
|
||||
const legendState = String(targetPoint?.anchoPointState || '');
|
||||
if (legendState && mapViewStore.getLegendChecked(legendState) !== 1) {
|
||||
mapStore.updateLegendChecked(legendState, 1);
|
||||
}
|
||||
|
||||
const layerKey = String(targetPoint?.layerKey || '');
|
||||
if (layerKey && mapClass.hasLayer(layerKey)) {
|
||||
mapClass.mdLayerTreeShowOrHidden(layerKey, true);
|
||||
}
|
||||
|
||||
mapClass.flyTopanto([targetPoint.lgtd, targetPoint.lttd], zoom);
|
||||
};
|
||||
|
||||
// 备注:统一释放地图页面生命周期里注册的监听,避免页面卸载后残留联动。
|
||||
const unmountView = () => {
|
||||
if (zoomListenerKey) {
|
||||
unByKey(zoomListenerKey);
|
||||
zoomListenerKey = null;
|
||||
if (removeZoomListener) {
|
||||
removeZoomListener();
|
||||
removeZoomListener = null;
|
||||
}
|
||||
clearZoomSyncDebounceTimer();
|
||||
|
||||
if (stopBaseSelectionWatch) {
|
||||
stopBaseSelectionWatch();
|
||||
stopBaseSelectionWatch = null;
|
||||
}
|
||||
|
||||
if (baseSelectionDebounceTimer.value) {
|
||||
clearTimeout(baseSelectionDebounceTimer.value);
|
||||
baseSelectionDebounceTimer.value = null;
|
||||
}
|
||||
|
||||
latestHydroMenuGetter = null;
|
||||
currentMapType = '2D';
|
||||
initializedBaseLayerKeys.clear();
|
||||
hydroMenuDefaultCheckedKeys.clear();
|
||||
};
|
||||
|
||||
return {
|
||||
initialize,
|
||||
mountView,
|
||||
switchMapType,
|
||||
unmountView,
|
||||
loadPage,
|
||||
reloadPage,
|
||||
|
||||
@ -74,19 +74,19 @@ export const DEFAULT_NEARBY_POINT_DENSITY_DISPLAY_RULES: NearbyPointDensityDispl
|
||||
},
|
||||
{
|
||||
minDensityValue: 800000,
|
||||
minZoom: 6.1
|
||||
minZoom: 0
|
||||
},
|
||||
{
|
||||
minDensityValue: 400000,
|
||||
minZoom: 8.1
|
||||
minZoom: 0
|
||||
},
|
||||
{
|
||||
minDensityValue: 50000,
|
||||
minZoom: 10.6
|
||||
minZoom: 2.0
|
||||
},
|
||||
{
|
||||
minDensityValue: 0,
|
||||
minZoom: 12.2
|
||||
minZoom: 4.0
|
||||
}
|
||||
];
|
||||
|
||||
@ -121,6 +121,37 @@ type GroupCandidate = {
|
||||
layerKey: string;
|
||||
};
|
||||
|
||||
const isAlarmRangeLegendState = (legendState: string): boolean => {
|
||||
return (
|
||||
legendState.startsWith('alarm_range_') ||
|
||||
legendState.startsWith('large_eng_built_alarm_range_') ||
|
||||
legendState.startsWith('mid_eng_built_alarm_range_')
|
||||
);
|
||||
};
|
||||
|
||||
const getPointRenderPriority = (point: Record<string, any>): number => {
|
||||
const layerKey = String(point.layerKey || point.type || '').toLowerCase();
|
||||
const legendState = String(point.anchoPointState || '').trim().toLowerCase();
|
||||
|
||||
if (
|
||||
layerKey.includes('eng_alarm_point') ||
|
||||
layerKey.includes('alarm_range') ||
|
||||
isAlarmRangeLegendState(legendState)
|
||||
) {
|
||||
return 300;
|
||||
}
|
||||
|
||||
if (
|
||||
layerKey.includes('eng_point') ||
|
||||
legendState.startsWith('large_eng_') ||
|
||||
legendState.startsWith('mid_eng_')
|
||||
) {
|
||||
return 200;
|
||||
}
|
||||
|
||||
return 100;
|
||||
};
|
||||
|
||||
const normalizeText = (value: unknown): string => {
|
||||
return String(value ?? '')
|
||||
.trim()
|
||||
@ -207,6 +238,12 @@ const compareGroupCandidates = (
|
||||
left: GroupCandidate,
|
||||
right: GroupCandidate
|
||||
): number => {
|
||||
const leftRenderPriority = getPointRenderPriority(left.point);
|
||||
const rightRenderPriority = getPointRenderPriority(right.point);
|
||||
if (leftRenderPriority !== rightRenderPriority) {
|
||||
return rightRenderPriority - leftRenderPriority;
|
||||
}
|
||||
|
||||
const leftDensityValue = toFiniteNumber(left.point.distance);
|
||||
const rightDensityValue = toFiniteNumber(right.point.distance);
|
||||
|
||||
@ -232,21 +269,18 @@ const buildRuntimeMeta = (
|
||||
displayName: string,
|
||||
rule: NearbyPointAutoRule
|
||||
): NearbyPointRuntimeMeta => {
|
||||
const displayMode: NearbyPointDisplayMode =
|
||||
priority === 1 ? 'default' : priority === 2 ? 'replace' : 'expand';
|
||||
|
||||
return {
|
||||
groupId,
|
||||
displayName,
|
||||
groupSize,
|
||||
priority,
|
||||
showZoomMin: priority === 1 ? null : rule.replaceAtZoom,
|
||||
showZoomMin: null,
|
||||
showZoomMax: null,
|
||||
replaceAtZoom: rule.replaceAtZoom,
|
||||
expandAtZoom: rule.expandAtZoom,
|
||||
expandOffsetPx: rule.expandOffsetPx,
|
||||
expandAngleDeg: priority === 1 ? 0 : (priority - 2) * rule.angleStepDeg,
|
||||
displayMode
|
||||
expandAngleDeg: 0,
|
||||
displayMode: 'default'
|
||||
};
|
||||
};
|
||||
|
||||
@ -279,61 +313,49 @@ const buildGroupCandidates = (
|
||||
return candidates;
|
||||
};
|
||||
|
||||
const buildLayerScopedNearbyGroups = (
|
||||
const buildNearbyGroups = (
|
||||
candidates: GroupCandidate[],
|
||||
rule: NearbyPointAutoRule
|
||||
): GroupCandidate[][] => {
|
||||
const layerScopedCandidates = new Map<string, GroupCandidate[]>();
|
||||
|
||||
candidates.forEach(candidate => {
|
||||
const layerKey = candidate.layerKey || '__empty_layer__';
|
||||
const layerItems = layerScopedCandidates.get(layerKey) || [];
|
||||
layerItems.push(candidate);
|
||||
layerScopedCandidates.set(layerKey, layerItems);
|
||||
});
|
||||
|
||||
const result: GroupCandidate[][] = [];
|
||||
const sortedCandidates = [...candidates].sort(compareGroupCandidates);
|
||||
const consumedIndexes = new Set<number>();
|
||||
|
||||
layerScopedCandidates.forEach(layerItems => {
|
||||
const sortedCandidates = [...layerItems].sort(compareGroupCandidates);
|
||||
const consumedIndexes = new Set<number>();
|
||||
sortedCandidates.forEach((seedCandidate, seedIndex) => {
|
||||
if (consumedIndexes.has(seedIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sortedCandidates.forEach((seedCandidate, seedIndex) => {
|
||||
if (consumedIndexes.has(seedIndex)) {
|
||||
return;
|
||||
const groupItems: GroupCandidate[] = [seedCandidate];
|
||||
const groupIndexes = [seedIndex];
|
||||
|
||||
for (
|
||||
let candidateIndex = seedIndex + 1;
|
||||
candidateIndex < sortedCandidates.length;
|
||||
candidateIndex += 1
|
||||
) {
|
||||
if (consumedIndexes.has(candidateIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const groupItems: GroupCandidate[] = [seedCandidate];
|
||||
const groupIndexes = [seedIndex];
|
||||
const currentCandidate = sortedCandidates[candidateIndex];
|
||||
const distanceMeters = haversineDistanceMeters(
|
||||
seedCandidate.lon,
|
||||
seedCandidate.lat,
|
||||
currentCandidate.lon,
|
||||
currentCandidate.lat
|
||||
);
|
||||
|
||||
for (
|
||||
let candidateIndex = seedIndex + 1;
|
||||
candidateIndex < sortedCandidates.length;
|
||||
candidateIndex += 1
|
||||
) {
|
||||
if (consumedIndexes.has(candidateIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentCandidate = sortedCandidates[candidateIndex];
|
||||
const distanceMeters = haversineDistanceMeters(
|
||||
seedCandidate.lon,
|
||||
seedCandidate.lat,
|
||||
currentCandidate.lon,
|
||||
currentCandidate.lat
|
||||
);
|
||||
|
||||
if (distanceMeters <= rule.distanceThresholdMeters) {
|
||||
groupItems.push(currentCandidate);
|
||||
groupIndexes.push(candidateIndex);
|
||||
}
|
||||
if (distanceMeters <= rule.distanceThresholdMeters) {
|
||||
groupItems.push(currentCandidate);
|
||||
groupIndexes.push(candidateIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupItems.length >= rule.minGroupSize) {
|
||||
groupIndexes.forEach(index => consumedIndexes.add(index));
|
||||
result.push(groupItems);
|
||||
}
|
||||
});
|
||||
if (groupItems.length >= rule.minGroupSize) {
|
||||
groupIndexes.forEach(index => consumedIndexes.add(index));
|
||||
result.push(groupItems);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
@ -354,7 +376,7 @@ export const attachNearbyPointRuntimeMeta = (
|
||||
if (candidates.length < rule.minGroupSize) {
|
||||
return points;
|
||||
}
|
||||
const groupedCandidates = buildLayerScopedNearbyGroups(candidates, rule);
|
||||
const groupedCandidates = buildNearbyGroups(candidates, rule);
|
||||
|
||||
let groupSeed = 0;
|
||||
groupedCandidates.forEach(groupItems => {
|
||||
|
||||
@ -28,10 +28,9 @@ export const useMapConfigStore = defineStore('map-config', () => {
|
||||
const legendLoading = ref(false);
|
||||
const lastLoadOptions = ref<MapConfigLoadOptions | null>(null);
|
||||
|
||||
// 备注:统一规范图例 nameEn,避免 `_测试` 后缀影响索引命中。
|
||||
const normalizeLegendNameEn = (nameEn?: string): string => {
|
||||
if (!nameEn) return '';
|
||||
return nameEn.includes('_测试') ? nameEn.split('_测试')[0] : nameEn;
|
||||
return nameEn;
|
||||
};
|
||||
|
||||
// 备注:重建图层配置索引,后续按 key 读取图层配置时不再全量递归。
|
||||
|
||||
@ -55,7 +55,9 @@ const select = ref({
|
||||
value: 'all',
|
||||
options: jiDiList.value,
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
format: undefined,
|
||||
width: '160px',
|
||||
minWidth:'160px'
|
||||
});
|
||||
const baseid = ref('');
|
||||
// 加载状态
|
||||
|
||||
@ -39,7 +39,7 @@ const baseid = ref('');
|
||||
const modelStore = useModelStore();
|
||||
|
||||
// Loading 状态和数据状态
|
||||
const loading = ref(false);
|
||||
const loading = ref(true);
|
||||
const hasData = ref(true); // 初始化为 true,因为 initChart 时会渲染
|
||||
const select = ref({
|
||||
show: true,
|
||||
@ -844,4 +844,7 @@ watch(
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
:deep(.ant-spin-nested-loading >div>.ant-spin){
|
||||
background: rgba(255, 255, 255) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<div class="chart-wrapper">
|
||||
<a-spin :spinning="loading">
|
||||
<!-- 始终渲染图表容器,确保有固定宽高 -->
|
||||
<div ref="chartRef" class="water-temp-chart"></div>
|
||||
<div ref="chartRef" class="water-temp-chart"></div>
|
||||
<!-- 无数据时显示Empty,但不影响容器尺寸 -->
|
||||
<div v-if="!loading && !hasData" class="empty-overlay">
|
||||
<a-empty description="暂无数据" />
|
||||
@ -41,7 +41,7 @@ let chartInstance: echarts.ECharts | null = null;
|
||||
const baseid = ref('');
|
||||
|
||||
// Loading 状态和数据状态
|
||||
const loading = ref(false);
|
||||
const loading = ref(true);
|
||||
const hasData = ref(true); // 初始化为 true,因为 initChart 时会渲染
|
||||
|
||||
// 选择器配置
|
||||
@ -732,4 +732,7 @@ onUnmounted(() => {
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
:deep(.ant-spin-nested-loading >div>.ant-spin){
|
||||
background: rgba(255, 255, 255) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -587,12 +587,12 @@ const fetchTableData = async () => {
|
||||
{
|
||||
field: 'stnm',
|
||||
operator: 'contains',
|
||||
value: '12'
|
||||
value: params.value.stnm
|
||||
},
|
||||
{
|
||||
field: 'ennm',
|
||||
operator: 'contains',
|
||||
value: '12'
|
||||
value: params.value.stnm
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -501,7 +501,7 @@ const updateChart = () => {
|
||||
center: ['35%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderRadius: 5,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
getMsstbprptKendoList
|
||||
} from '@/api/home';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
const props = defineProps<{
|
||||
dataDimensionVal?: string;
|
||||
name?: string;
|
||||
@ -337,17 +337,27 @@ const customTransform = (res: any) => ({
|
||||
total: res?.data?.total || 0
|
||||
});
|
||||
|
||||
const exportLoading = ref(false);
|
||||
function handleExport() {
|
||||
if (exportLoading.value) return;
|
||||
|
||||
exportLoading.value = true;
|
||||
|
||||
const currentTab = tabKey.value;
|
||||
if (
|
||||
tableRefs.value[currentTab] &&
|
||||
typeof tableRefs.value[currentTab].exportTable === 'function'
|
||||
) {
|
||||
tableRefs.value[currentTab].exportTable({
|
||||
fileName: '环境监测数据接入情况'
|
||||
});
|
||||
tableRefs.value[currentTab]
|
||||
.exportTable({
|
||||
fileName: `水电开发情况_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||
})
|
||||
.finally(() => {
|
||||
exportLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
message.info('导出功能待实现');
|
||||
message.info('当前Tab暂无导出数据');
|
||||
exportLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -640,6 +650,7 @@ const searchList = computed(() => [
|
||||
type: 'Input',
|
||||
name: 'stnm',
|
||||
label: '',
|
||||
placeholder:'请输入电站名称',
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入电站名称'
|
||||
@ -866,7 +877,7 @@ onUnmounted(() => {
|
||||
@reset="handleReset"
|
||||
>
|
||||
<template #actions>
|
||||
<a-button @click="handleExport">
|
||||
<a-button @click="handleExport" :loading ="exportLoading" >
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
getBaseWbsb
|
||||
} from '@/api/home';
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
const props = defineProps<{
|
||||
defaultJidiInfo?: Record<string, string | number | boolean>;
|
||||
seriesName?: string;
|
||||
@ -300,12 +300,27 @@ const customTransform = (res: any) => ({
|
||||
records: res?.data?.data || res?.data?.items || [],
|
||||
total: res?.data?.total || 0
|
||||
});
|
||||
|
||||
const exportLoading = ref(false);
|
||||
function handleExport() {
|
||||
if (tableRef.value && typeof tableRef.value.export === 'function') {
|
||||
tableRef.value.export();
|
||||
if (exportLoading.value) return;
|
||||
|
||||
exportLoading.value = true;
|
||||
|
||||
const currentTab = tabKey.value;
|
||||
if (
|
||||
tableRefs.value[currentTab] &&
|
||||
typeof tableRefs.value[currentTab].exportTable === 'function'
|
||||
) {
|
||||
tableRefs.value[currentTab]
|
||||
.exportTable({
|
||||
fileName: `水电开发情况_${dayjs().format('YYYY-MM-DD HH-mm-ss')}`
|
||||
})
|
||||
.finally(() => {
|
||||
exportLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
message.info('导出功能待实现');
|
||||
message.info('当前Tab暂无导出数据');
|
||||
exportLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -575,6 +590,7 @@ const searchList = computed(() => [
|
||||
type: 'Input',
|
||||
name: 'stnm',
|
||||
label: '',
|
||||
placeholder: '请输入电站名称',
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入电站名称'
|
||||
@ -946,7 +962,7 @@ onUnmounted(() => {
|
||||
@reset="handleReset"
|
||||
>
|
||||
<template #actions>
|
||||
<a-button @click="handleExport">
|
||||
<a-button @click="handleExport" :loading ="exportLoading" >
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
|
||||
@ -404,7 +404,7 @@ const getselectTwo = async (code: any) => {
|
||||
"filters": [
|
||||
code != 'all' ? {
|
||||
"field": "baseId",
|
||||
"operator": "contains",
|
||||
"operator": "eq",
|
||||
"dataType": "string",
|
||||
"value": code
|
||||
} : null,
|
||||
|
||||
@ -56,11 +56,28 @@ const paramsOne = {
|
||||
tm: []
|
||||
};
|
||||
const dataOne = ref('');
|
||||
// 使用 computed 使 prompts 响应 dataOne.value 的变化
|
||||
const prompts = computed(() => ({
|
||||
show: true,
|
||||
value: `注:最新数据时间为${dataOne.value || getCurrentHourTime()}`
|
||||
}));
|
||||
// 保存原始表格数据,用于提取 minTm
|
||||
const rawTableData = ref<any[]>([]);
|
||||
// 使用 computed 使 prompts 基于原始数据的 minTm 字段
|
||||
const prompts = computed(() => {
|
||||
// 如果没有数据或数据为空,不显示提示
|
||||
if (!rawTableData.value || rawTableData.value.length === 0) {
|
||||
return { show: true, value: '' };
|
||||
}
|
||||
|
||||
// 获取第一条数据的 minTm 字段
|
||||
const minTm = rawTableData.value[0]?.minTm;
|
||||
|
||||
// 如果 minTm 为空,也不显示提示
|
||||
if (!minTm) {
|
||||
return { show: true, value: '' };
|
||||
}
|
||||
|
||||
return {
|
||||
show: true,
|
||||
value: `注:最新数据时间为${minTm}`
|
||||
};
|
||||
});
|
||||
const showemit = ref(false);
|
||||
const loading = ref(false);
|
||||
// const selectOptions: any = ref([])
|
||||
@ -308,6 +325,9 @@ const init = async () => {
|
||||
let res = await getKendoListCust(params);
|
||||
let data = res.data.data || res.data;
|
||||
|
||||
// 保存原始数据,用于提取 minTm
|
||||
rawTableData.value = data;
|
||||
|
||||
if (data.length > 0) {
|
||||
showemit.value = true;
|
||||
} else {
|
||||
|
||||
@ -23,7 +23,7 @@ export const useJidiSelectEventStore = defineStore('jidiSelectEvent', () => {
|
||||
};
|
||||
const getJidiData = async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
const params = {
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
@ -50,7 +50,7 @@ export const useJidiSelectEventStore = defineStore('jidiSelectEvent', () => {
|
||||
select: ['wbsCode', 'wbsName']
|
||||
};
|
||||
const res = await wbsbGetKendoList(params);
|
||||
let data = res?.data?.data || [
|
||||
const data = res?.data?.data || [
|
||||
{
|
||||
wbsCode: 'all',
|
||||
wbsName: '当前全部',
|
||||
|
||||
@ -16,6 +16,7 @@ import { useMapViewStore } from '@/modules/map/stores/map-view.store';
|
||||
import request from '@/utils/request';
|
||||
const mapClass = MapClass.getInstance();
|
||||
const ENG_POINT_LAYER_KEY = 'eng_point';
|
||||
const ENG_ALARM_POINT_LAYER_KEY = 'eng_alarm_point';
|
||||
const YLFB_POINT_LAYER_KEY = 'ylfb_point';
|
||||
const ENG_POINT_MEDIUM_VISIBLE_ZOOM = 7.5;
|
||||
const ENG_POINT_LARGE_STATES = [
|
||||
@ -29,6 +30,22 @@ const ENG_POINT_MEDIUM_STATES = [
|
||||
'mid_eng_nbuilt'
|
||||
];
|
||||
|
||||
const isEngPointRuntimeItem = (item: any): boolean => {
|
||||
const runtimeLayerKey = String(
|
||||
item?.layerKey || item?.type || item?._layerKey || ''
|
||||
).toLowerCase();
|
||||
const sttpMap = String(
|
||||
item?._sttpMap || item?.sttpMap || item?.popupSttpMap || ''
|
||||
).toUpperCase();
|
||||
const sttpCode = String(item?.sttpCode || item?.sttp || '').toUpperCase();
|
||||
|
||||
if (runtimeLayerKey) {
|
||||
return runtimeLayerKey.includes(ENG_POINT_LAYER_KEY);
|
||||
}
|
||||
|
||||
return sttpMap === 'ENG' || sttpMap === 'ENG2' || sttpCode === 'ENG';
|
||||
};
|
||||
|
||||
const normalizeCachePayload = (value: any): any => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(item => normalizeCachePayload(item));
|
||||
@ -105,19 +122,48 @@ const normalizeRequestParams = (rawParams: any) => {
|
||||
};
|
||||
};
|
||||
|
||||
const parseAnchorParamFilters = (anchorParamJson?: string) => {
|
||||
if (!anchorParamJson) return [];
|
||||
const parseAnchorParamJson = (anchorParamJson?: string) => {
|
||||
const result: { filters: any[]; orders: any[] | null } = {
|
||||
filters: [],
|
||||
orders: null
|
||||
};
|
||||
|
||||
if (!anchorParamJson) return result;
|
||||
|
||||
try {
|
||||
const parsedParams = JSON.parse(anchorParamJson);
|
||||
if (!parsedParams || typeof parsedParams !== 'object') {
|
||||
return [];
|
||||
const parsed = JSON.parse(anchorParamJson);
|
||||
if (!parsed || typeof parsed !== 'object') return result;
|
||||
|
||||
// 解析 params -> filters(兼容旧格式:如果没有 params 字段,整个对象即为 params)
|
||||
const paramsObj = parsed.params || parsed;
|
||||
if (
|
||||
paramsObj &&
|
||||
typeof paramsObj === 'object' &&
|
||||
!Array.isArray(paramsObj)
|
||||
) {
|
||||
result.filters = buildFiltersFromParamsObject(paramsObj);
|
||||
}
|
||||
|
||||
return buildFiltersFromParamsObject(parsedParams);
|
||||
// 解析 orders -> sort 数组
|
||||
if (parsed.orders) {
|
||||
let ordersObj: Record<string, any> = {};
|
||||
if (typeof parsed.orders === 'string') {
|
||||
ordersObj = JSON.parse(parsed.orders);
|
||||
} else if (typeof parsed.orders === 'object') {
|
||||
ordersObj = parsed.orders;
|
||||
}
|
||||
if (ordersObj && typeof ordersObj === 'object') {
|
||||
result.orders = Object.entries(ordersObj).map(([field, dir]) => ({
|
||||
field,
|
||||
dir
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('解析 anchorParamJson 失败:', error);
|
||||
return [];
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
@ -183,6 +229,10 @@ export const useMapStore = defineStore('map', () => {
|
||||
|
||||
const allowMedium = currentZoom > ENG_POINT_MEDIUM_VISIBLE_ZOOM;
|
||||
return sourceData.filter((item: any) => {
|
||||
if (!isEngPointRuntimeItem(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const state = String(item?.anchoPointState || '');
|
||||
if (ENG_POINT_LARGE_STATES.includes(state)) {
|
||||
return true;
|
||||
@ -985,14 +1035,21 @@ export const useMapStore = defineStore('map', () => {
|
||||
layerKey: string,
|
||||
nameEn: string
|
||||
): string[] => {
|
||||
void layerKey;
|
||||
const normalizedNameEn = normalizeLegendNameEn(nameEn);
|
||||
if (!normalizedNameEn) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (normalizedNameEn.includes('alarm_range_')) {
|
||||
return [`large_eng_built_${normalizedNameEn}`];
|
||||
const candidates: string[] = [];
|
||||
if (layerKey === ENG_ALARM_POINT_LAYER_KEY) {
|
||||
candidates.push(`mid_eng_built_${normalizedNameEn}`);
|
||||
candidates.push(`large_eng_built_${normalizedNameEn}`);
|
||||
} else {
|
||||
candidates.push(`large_eng_built_${normalizedNameEn}`);
|
||||
candidates.push(`mid_eng_built_${normalizedNameEn}`);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
return [normalizedNameEn];
|
||||
@ -1130,9 +1187,12 @@ export const useMapStore = defineStore('map', () => {
|
||||
|
||||
requestParams = normalizeRequestParams(requestParams);
|
||||
|
||||
const anchorParamFilters = parseAnchorParamFilters(anchorParamJson);
|
||||
if (anchorParamFilters.length) {
|
||||
requestParams.filters.push(...anchorParamFilters);
|
||||
const anchorParamResult = parseAnchorParamJson(anchorParamJson);
|
||||
if (anchorParamResult.filters.length) {
|
||||
requestParams.filters.push(...anchorParamResult.filters);
|
||||
}
|
||||
if (anchorParamResult.orders) {
|
||||
requestOrders = anchorParamResult.orders;
|
||||
}
|
||||
|
||||
if (timeRangeLayerKeys.includes(layerKey)) {
|
||||
@ -1166,7 +1226,7 @@ export const useMapStore = defineStore('map', () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (layer?.orders) {
|
||||
if (!requestOrders && layer?.orders) {
|
||||
try {
|
||||
const ordersObj = JSON.parse(layer.orders);
|
||||
const ordersArray = Object.entries(ordersObj).map(([field, dir]) => ({
|
||||
|
||||
@ -66,7 +66,7 @@ export const useShuJuTianBaoStore = defineStore('shuJuTianBao', () => {
|
||||
if (type == 'baseId') {
|
||||
param = rvcd === 'all' ? {} : { baseId: rvcd };
|
||||
} else {
|
||||
param = rvcd === 'all' ? {} : { reachcd: rvcd };
|
||||
param = rvcd === 'all' ? {} : { rvcd: rvcd };
|
||||
}
|
||||
const res = await getEngInfoDropdown(param);
|
||||
if (res.data && Array.isArray(res.data)) {
|
||||
|
||||
@ -5,6 +5,7 @@ export const useUiStore = defineStore('ui', () => {
|
||||
// 右侧抽屉状态
|
||||
const drawerOpen = ref(true);
|
||||
const mapType = ref('2D');
|
||||
const mapSwitchCompletedTick = ref(0);
|
||||
|
||||
// 切换抽屉状态
|
||||
const toggleDrawer = () => {
|
||||
@ -16,10 +17,16 @@ export const useUiStore = defineStore('ui', () => {
|
||||
drawerOpen.value = open;
|
||||
};
|
||||
|
||||
const markMapSwitchCompleted = () => {
|
||||
mapSwitchCompletedTick.value += 1;
|
||||
};
|
||||
|
||||
return {
|
||||
drawerOpen,
|
||||
toggleDrawer,
|
||||
setDrawerOpen,
|
||||
mapType
|
||||
mapType,
|
||||
mapSwitchCompletedTick,
|
||||
markMapSwitchCompleted
|
||||
};
|
||||
});
|
||||
|
||||
@ -2451,10 +2451,10 @@ const themecc_list = [
|
||||
}
|
||||
];
|
||||
const getThemee = (fieldName?: string) => {
|
||||
let themeecList = themecc_list;
|
||||
const themeecList = themecc_list;
|
||||
let getitem: any = {};
|
||||
if (fieldName && themeecList.length > 0) {
|
||||
let getitemList = themeecList.filter(
|
||||
const getitemList = themeecList.filter(
|
||||
(i: any) => i.fieldName.toUpperCase() == fieldName.toUpperCase()
|
||||
);
|
||||
if (getitemList.length > 0) {
|
||||
@ -2642,16 +2642,19 @@ const timegj = [
|
||||
* @param props - 锚点数据对象
|
||||
* @returns HTML 字符串
|
||||
*/
|
||||
export function generatePopupHtml(props: any): string {
|
||||
export function generatePopupHtml(
|
||||
props: any,
|
||||
options?: {
|
||||
forceEng2?: boolean;
|
||||
}
|
||||
): string {
|
||||
const p = props;
|
||||
const dzFdl = getUnitConfigByCode('Other', 'FDLYKW')?.unit;
|
||||
const dzZjrl = getUnitConfigByCode('Other', 'ZJRL')?.unit;
|
||||
const title = p?.ennm || p?.stnm;
|
||||
const titleClass = getTitleClass(title);
|
||||
|
||||
let newsttpMap = p.sttpMap;
|
||||
console.log('newsttpMap', newsttpMap);
|
||||
if (p.qecInterval) {
|
||||
if (options?.forceEng2) {
|
||||
newsttpMap = 'eng2';
|
||||
}
|
||||
let html = '';
|
||||
@ -2714,7 +2717,7 @@ export function generatePopupHtml(props: any): string {
|
||||
} else {
|
||||
lastTmEngEqDataVo = p.lastTmEngEqDataVo;
|
||||
}
|
||||
if (!lastTmEngEqDataVo?.qo && !lastTmEngEqDataVo?.qi) return '';
|
||||
// if (!lastTmEngEqDataVo?.qo && !lastTmEngEqDataVo?.qi) return '';
|
||||
|
||||
const qecLimitVal = lastTmEngEqDataVo?.qecLimit;
|
||||
let qecLimitDisplay = '-';
|
||||
@ -3587,3 +3590,29 @@ export function generatePopupHtml(props: any): string {
|
||||
|
||||
return html ? `<div>${html}</div>` : '';
|
||||
}
|
||||
|
||||
export function getNormalizedPopupSttpMap(props: any): string {
|
||||
return String(props?._sttpMap || props?.sttpMap || props?.popupSttpMap || '')
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
export function isEngPopupFamily(props: any): boolean {
|
||||
return getNormalizedPopupSttpMap(props).startsWith('ENG');
|
||||
}
|
||||
|
||||
export function shouldPreferEng2Popup(
|
||||
props: any,
|
||||
options?: {
|
||||
forceEng2?: boolean;
|
||||
}
|
||||
): boolean {
|
||||
if (options?.forceEng2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
isEngPopupFamily(props) &&
|
||||
(!!props?.lastTmEngEqDataVo || !!props?.qecInterval)
|
||||
);
|
||||
}
|
||||
|
||||
@ -58,7 +58,6 @@ service.interceptors.response.use(
|
||||
message.error(response.data.msg || '请求失败');
|
||||
setTimeout(() => {
|
||||
localStorage.clear();
|
||||
router.push('/login');
|
||||
window.location.href = '/';
|
||||
}, 1000);
|
||||
return;
|
||||
|
||||
@ -246,6 +246,23 @@ const buildSearchParams = (values: any) => {
|
||||
return {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
values.rvcd && values.rvcd !== 'all'
|
||||
? {
|
||||
field: 'rvcd',
|
||||
operator: 'contains',
|
||||
dataType: 'string',
|
||||
value: values.rvcd
|
||||
}
|
||||
: null,
|
||||
|
||||
values.rstcd
|
||||
? {
|
||||
field: 'stcd',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: values.rstcd
|
||||
}
|
||||
: null,
|
||||
values.basin
|
||||
? {
|
||||
field: 'basin',
|
||||
|
||||
@ -510,6 +510,23 @@ const buildSearchParams = (values: any) => {
|
||||
return {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
values.rvcd && values.rvcd !== 'all'
|
||||
? {
|
||||
field: 'rvcd',
|
||||
operator: 'contains',
|
||||
dataType: 'string',
|
||||
value: values.rvcd
|
||||
}
|
||||
: null,
|
||||
|
||||
values.rstcd
|
||||
? {
|
||||
field: 'stcd',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: values.rstcd
|
||||
}
|
||||
: null,
|
||||
values.ptype && values.ptype !== 'all'
|
||||
? {
|
||||
field: 'ptype',
|
||||
|
||||
@ -22,7 +22,7 @@ let initialLeft = 0;
|
||||
let initialTop = 0;
|
||||
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
;
|
||||
|
||||
isDragging = true;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
|
||||
@ -238,6 +238,9 @@ const statusFilter = ref<"online" | "offline">("online");
|
||||
// 视频列表
|
||||
const videoList = ref<any[]>(Array(9).fill(null));
|
||||
|
||||
// 等待队列(存放超出布局限制的视频)
|
||||
const pendingVideoQueue = ref<any[]>([]);
|
||||
|
||||
// 移除视频
|
||||
const removeVideo = (index: number, nodeKey?: string) => {
|
||||
const videoData = videoList.value[index];
|
||||
@ -255,6 +258,19 @@ const removeVideo = (index: number, nodeKey?: string) => {
|
||||
}
|
||||
// 移除视频
|
||||
videoList.value[index] = null;
|
||||
|
||||
// 尝试从队列补位
|
||||
fillEmptySlot(index);
|
||||
};
|
||||
|
||||
// 填充空位(从队列中取第一个视频)
|
||||
const fillEmptySlot = (emptyIndex: number) => {
|
||||
if (pendingVideoQueue.value.length > 0) {
|
||||
const nextVideo = pendingVideoQueue.value.shift();
|
||||
if (nextVideo) {
|
||||
videoList.value[emptyIndex] = nextVideo;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 添加视频到列表
|
||||
@ -269,18 +285,49 @@ const addVideo = (videoData: any) => {
|
||||
} else if (videoList.value.filter((v, i) => i < maxCount).length < maxCount) {
|
||||
videoList.value.push(videoData);
|
||||
} else {
|
||||
// 如果满了,替换第一个
|
||||
videoList.value[0] = videoData;
|
||||
// 如果满了,加入等待队列
|
||||
if (!pendingVideoQueue.value.find((v) => v.key === videoData.key)) {
|
||||
pendingVideoQueue.value.push(videoData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 监听布局变化,调整视频列表长度
|
||||
watch(viewLayout, (newLayout) => {
|
||||
const maxCount = parseInt(newLayout);
|
||||
watch(viewLayout, (newLayout, oldLayout) => {
|
||||
const newMaxCount = parseInt(newLayout);
|
||||
const oldMaxCount = oldLayout ? parseInt(oldLayout) : newMaxCount;
|
||||
|
||||
// 布局减小时,超出部分移入队列
|
||||
if (newMaxCount < oldMaxCount) {
|
||||
for (let i = newMaxCount; i < oldMaxCount && i < videoList.value.length; i++) {
|
||||
const video = videoList.value[i];
|
||||
if (video && !pendingVideoQueue.value.find((v) => v.key === video.key)) {
|
||||
pendingVideoQueue.value.push(video);
|
||||
}
|
||||
videoList.value[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保数组长度足够
|
||||
while (videoList.value.length < maxCount) {
|
||||
while (videoList.value.length < newMaxCount) {
|
||||
videoList.value.push(null);
|
||||
}
|
||||
|
||||
// 布局增大时,从队列填充新空位
|
||||
if (newMaxCount > oldMaxCount) {
|
||||
const availableSlots = newMaxCount - oldMaxCount;
|
||||
for (let i = 0; i < availableSlots && pendingVideoQueue.value.length > 0; i++) {
|
||||
const emptyIndex = videoList.value.findIndex(
|
||||
(v, i) => i < newMaxCount && (v === null || v === undefined)
|
||||
);
|
||||
if (emptyIndex !== -1) {
|
||||
const nextVideo = pendingVideoQueue.value.shift();
|
||||
if (nextVideo) {
|
||||
videoList.value[emptyIndex] = nextVideo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 树形数据
|
||||
@ -408,8 +455,8 @@ const handlePlayRecord = (record: any) => {
|
||||
if (videoPath) {
|
||||
const maxCount = parseInt(viewLayout.value);
|
||||
|
||||
// 检查是否已经添加过该录像
|
||||
const existingIndex = videoList.value.findIndex((v) => v && v.flnm === record.flnm);
|
||||
// 检查是否已经添加过该录像(通过视频路径判断)
|
||||
const existingIndex = videoList.value.findIndex((v) => v && v.url === videoPath);
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
return;
|
||||
@ -560,10 +607,11 @@ const initTree = async () => {
|
||||
},
|
||||
};
|
||||
|
||||
// 清空视频列表和勾选状态
|
||||
// 清空视频列表、勾选状态和等待队列
|
||||
videoList.value = Array(9).fill(null);
|
||||
checkedKeys.value = [];
|
||||
expandedKeys.value = [];
|
||||
pendingVideoQueue.value = [];
|
||||
|
||||
treeLoading.value = true;
|
||||
try {
|
||||
@ -679,6 +727,7 @@ const handleMonitorTypeChange = () => {
|
||||
// 清空中间视频
|
||||
videoList.value = Array(9).fill(null);
|
||||
checkedKeys.value = [];
|
||||
pendingVideoQueue.value = [];
|
||||
}
|
||||
|
||||
// 切换到实时视频时,恢复在线默认状态
|
||||
@ -729,11 +778,15 @@ const handleTreeSelect = (checkedKeysValue: any[], info: any) => {
|
||||
// 自动添加所有父节点到 checkedKeys
|
||||
addParentKeys(nodeData.key);
|
||||
} else {
|
||||
// 超出限制,使用 setTimeout 延迟取消勾选,避免与 Tree 组件的自动勾选冲突
|
||||
setTimeout(() => {
|
||||
// 使用 filter 创建新数组以触发响应式更新
|
||||
checkedKeys.value = checkedKeys.value.filter((key) => key !== nodeData.key);
|
||||
}, 50);
|
||||
// 超出限制,加入等待队列(保持勾选状态)
|
||||
if (!pendingVideoQueue.value.find((v) => v.key === nodeData.key)) {
|
||||
pendingVideoQueue.value.push({
|
||||
flnm: nodeData.name,
|
||||
stnm: nodeData.stnm,
|
||||
url: videoUrl,
|
||||
...nodeData,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
@ -744,6 +797,17 @@ const handleTreeSelect = (checkedKeysValue: any[], info: any) => {
|
||||
} else {
|
||||
// 取消勾选:移除视频
|
||||
if (!nodeData.children || nodeData.children.length === 0) {
|
||||
// 先检查是否在队列中
|
||||
const queueIndex = pendingVideoQueue.value.findIndex(
|
||||
(v) => v.key === nodeData.key
|
||||
);
|
||||
if (queueIndex !== -1) {
|
||||
// 从队列中移除
|
||||
pendingVideoQueue.value.splice(queueIndex, 1);
|
||||
removeUnnecessaryParentKeys(nodeData.key);
|
||||
return;
|
||||
}
|
||||
|
||||
// 叶子节点:直接移除
|
||||
removeVideoByNodeKey(nodeData.key);
|
||||
|
||||
@ -912,16 +976,23 @@ const addVideosFromNodeWithLimit = (node: any) => {
|
||||
const remainingSlots = maxCount - currentCount;
|
||||
|
||||
if (remainingSlots <= 0) {
|
||||
// 没有剩余位置,延迟取消所有相关节点的勾选
|
||||
setTimeout(() => {
|
||||
cancelNodeCheck(node);
|
||||
}, 100);
|
||||
// 没有剩余位置,全部加入队列
|
||||
allLeafNodes.forEach((leafNode: any) => {
|
||||
if (!pendingVideoQueue.value.find((v) => v.key === leafNode.key)) {
|
||||
pendingVideoQueue.value.push({
|
||||
flnm: leafNode.name,
|
||||
stnm: leafNode.stnm,
|
||||
url: leafNode.url || leafNode.videoUrl || leafNode.playUrl,
|
||||
...leafNode,
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 只添加前 remainingSlots 个叶子节点
|
||||
const nodesToAdd = allLeafNodes.slice(0, remainingSlots);
|
||||
const nodesToCancel = allLeafNodes.slice(remainingSlots);
|
||||
const nodesToQueue = allLeafNodes.slice(remainingSlots);
|
||||
|
||||
// 添加允许的视频并勾选对应节点
|
||||
nodesToAdd.forEach((leafNode: any) => {
|
||||
@ -938,37 +1009,26 @@ const addVideosFromNodeWithLimit = (node: any) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 先取消超出限制的节点的勾选
|
||||
if (nodesToCancel.length > 0) {
|
||||
setTimeout(() => {
|
||||
cancelNodesCheck(nodesToCancel);
|
||||
|
||||
// 取消后再为所有添加的子节点添加父节点
|
||||
nodesToAdd.forEach((leafNode: any) => {
|
||||
addParentKeys(leafNode.key);
|
||||
});
|
||||
}, 50);
|
||||
} else {
|
||||
// 没有超出限制,直接添加父节点
|
||||
setTimeout(() => {
|
||||
nodesToAdd.forEach((leafNode: any) => {
|
||||
addParentKeys(leafNode.key);
|
||||
});
|
||||
}, 50);
|
||||
// 超出限制的节点加入队列(保持勾选状态)
|
||||
if (nodesToQueue.length > 0) {
|
||||
nodesToQueue.forEach((leafNode: any) => {
|
||||
if (!pendingVideoQueue.value.find((v) => v.key === leafNode.key)) {
|
||||
pendingVideoQueue.value.push({
|
||||
flnm: leafNode.name,
|
||||
stnm: leafNode.stnm,
|
||||
url: leafNode.url || leafNode.videoUrl || leafNode.playUrl,
|
||||
...leafNode,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 取消单个节点的勾选
|
||||
const cancelNodeCheck = (node: any) => {
|
||||
// 使用 filter 创建新数组以触发响应式更新
|
||||
checkedKeys.value = checkedKeys.value.filter((key) => key !== node.key);
|
||||
};
|
||||
|
||||
// 取消多个节点的勾选
|
||||
const cancelNodesCheck = (nodes: any[]) => {
|
||||
nodes.forEach((node: any) => {
|
||||
cancelNodeCheck(node);
|
||||
});
|
||||
// 添加父节点
|
||||
setTimeout(() => {
|
||||
nodesToAdd.forEach((leafNode: any) => {
|
||||
addParentKeys(leafNode.key);
|
||||
});
|
||||
}, 50);
|
||||
};
|
||||
|
||||
// 递归移除节点及其所有子节点的视频
|
||||
@ -986,9 +1046,20 @@ const removeVideosFromNode = (node: any) => {
|
||||
|
||||
// 根据节点key移除视频
|
||||
const removeVideoByNodeKey = (nodeKey: string) => {
|
||||
// 先检查是否在队列中
|
||||
const queueIndex = pendingVideoQueue.value.findIndex(
|
||||
(v) => v.key === nodeKey
|
||||
);
|
||||
if (queueIndex !== -1) {
|
||||
pendingVideoQueue.value.splice(queueIndex, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = videoList.value.findIndex((video) => video && video.key === nodeKey);
|
||||
if (index !== -1) {
|
||||
videoList.value[index] = null;
|
||||
// 尝试从队列补位
|
||||
fillEmptySlot(index);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -354,8 +354,8 @@ export const handleCellEdit = (params: any, fWorksheet: any, year: string) => {
|
||||
);
|
||||
|
||||
// 重建该组的所有单元格引用
|
||||
let groupCells = [];
|
||||
let currentDayCount = groupStartDayOfYear;
|
||||
const groupCells = [];
|
||||
const currentDayCount = groupStartDayOfYear;
|
||||
let currentMonth = 1;
|
||||
let cumulativeDays = 0;
|
||||
|
||||
@ -389,7 +389,7 @@ export const handleCellEdit = (params: any, fWorksheet: any, year: string) => {
|
||||
console.log('组中单元格:', groupCells);
|
||||
|
||||
// 构建公式
|
||||
let formula = `=ROUND(AVERAGE(${groupCells.join(',')}), 1)`;
|
||||
const formula = `=ROUND(AVERAGE(${groupCells.join(',')}), 1)`;
|
||||
|
||||
// 找到该组的结束位置用于显示公式
|
||||
let endMonth = 1;
|
||||
@ -446,7 +446,7 @@ export const getParams = (searchData: any, Psort?: any, Rstcd?: boolean) => {
|
||||
return result.slice(1, -1);
|
||||
};
|
||||
// if (searchData) {
|
||||
let params: any = [];
|
||||
const params: any = [];
|
||||
// if (sttpCode) {
|
||||
// params.push(
|
||||
// {
|
||||
@ -783,7 +783,7 @@ export const getParams = (searchData: any, Psort?: any, Rstcd?: boolean) => {
|
||||
].filter((el: any) => el != null);
|
||||
}
|
||||
|
||||
let filter = {
|
||||
const filter = {
|
||||
logic: 'and',
|
||||
filters: params
|
||||
};
|
||||
@ -917,7 +917,7 @@ export const calculateCrossMonthAverages = async (
|
||||
let groupValues: string[] = []; // 存储单元格引用
|
||||
let groupPositions: { month: number; day: number }[] = []; // 记录每个值的位置
|
||||
let validDataCount = 0; // 记录当前组中有效数据的数量
|
||||
let protectedRanges: any[] = [];
|
||||
const protectedRanges: any[] = [];
|
||||
for (let month = 1; month <= 12; month++) {
|
||||
const dataRow = month * 2 + 1; // 当前月份的数据行
|
||||
const daysInMonth = getDaysInMonth(year, month); // 根据年份获取该月天数
|
||||
@ -958,7 +958,7 @@ export const calculateCrossMonthAverages = async (
|
||||
if (validDataCount > 0) {
|
||||
// 构建平均值公式
|
||||
const range = groupValues.join(',');
|
||||
let formula = `=ROUND(AVERAGE(${range}), 1)`;
|
||||
const formula = `=ROUND(AVERAGE(${range}), 1)`;
|
||||
|
||||
// 在组的最后一天显示平均值
|
||||
const lastPosition = groupPositions[groupValues.length - 1];
|
||||
@ -989,7 +989,7 @@ export const calculateCrossMonthAverages = async (
|
||||
if (groupValues.length > 0 && groupValues.length < 5 && validDataCount > 0) {
|
||||
// 构建平均值公式
|
||||
const range = groupValues.join(',');
|
||||
let formula = `=ROUND(AVERAGE(${range}), 1)`;
|
||||
const formula = `=ROUND(AVERAGE(${range}), 1)`;
|
||||
|
||||
// 在组的最后一天显示平均值
|
||||
const lastPosition = groupPositions[groupValues.length - 1];
|
||||
|
||||
@ -0,0 +1,828 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:title="isEdit ? '编辑沿程配置' : '新增沿程配置'"
|
||||
v-model:open="modalVisible"
|
||||
:confirm-loading="localLoading"
|
||||
width="1200px"
|
||||
:destroy-on-close="true"
|
||||
ok-text="保存"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
name="edit_form"
|
||||
:labelCol="{ span: 5 }"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="沿程配置名称" name="name">
|
||||
<a-input
|
||||
v-model:value="formData.name"
|
||||
style="width: 100%"
|
||||
placeholder="请输入沿程配置名称"
|
||||
/> </a-form-item
|
||||
></a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="沿程配置编码" name="code">
|
||||
<a-input
|
||||
v-model:value="formData.code"
|
||||
style="width: 100%"
|
||||
placeholder="请输入沿程配置编码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="所在河段" name="rvcd">
|
||||
<a-select
|
||||
v-model:value="formData.rvcd"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
placeholder="请选择所在河段"
|
||||
:options="riverOptions"
|
||||
:filter-option="filterOption"
|
||||
@change="handleRiverSelect"
|
||||
:disabled="isEdit"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
v-model:value="formData.remark"
|
||||
style="width: 100%"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<div class="config-tree-container">
|
||||
<!-- 新增一级节点区域 -->
|
||||
<div v-if="addingNewNode" class="add-node-row">
|
||||
<a-select
|
||||
v-model:value="newNodeData.stcd"
|
||||
show-search
|
||||
placeholder="请选择站点"
|
||||
style="width: 300px"
|
||||
:filter-option="filterOption"
|
||||
@change="handleNodeSelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in stationList"
|
||||
:key="item.stcd"
|
||||
:value="item.stcd"
|
||||
>
|
||||
{{ item.stnm }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleSaveNewNode"
|
||||
style="margin-left: 8px"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
<a-button @click="handleCancelAdd" style="margin-left: 8px">
|
||||
<MinusOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button v-else class="mb-4" type="primary" @click="handleAdd">
|
||||
<PlusOutlined /> 新增一级节点
|
||||
</a-button>
|
||||
|
||||
<!-- 树形结构 -->
|
||||
<a-tree
|
||||
v-if="treeData.length > 0"
|
||||
:tree-data="treeData"
|
||||
:default-expand-all="false"
|
||||
:selectable="false"
|
||||
block-node
|
||||
style="width: 400px"
|
||||
>
|
||||
<template #title="{ dataRef }">
|
||||
<!-- 编辑状态:只显示编辑区域 -->
|
||||
<div
|
||||
v-if="editingNode && editingNodeKey === dataRef.key"
|
||||
class="add-child-node-row w-[300px]"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="editNodeData.stcd"
|
||||
show-search
|
||||
placeholder="请选择站点"
|
||||
style="width: 300px"
|
||||
:filter-option="filterOption"
|
||||
@change="() => handleEditNodeSelect(dataRef)"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in dataRef.parentId
|
||||
? childStationList
|
||||
: stationList"
|
||||
:key="item.stcd"
|
||||
:value="item.stcd"
|
||||
>
|
||||
{{ dataRef.parentId ? item.stnm : item.ennm }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.stop="handleSaveEditNode"
|
||||
style="margin-left: 8px"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
<a-button
|
||||
@click.stop="handleCancelEditNode"
|
||||
style="margin-left: 8px"
|
||||
>
|
||||
<MinusOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
<!-- 非编辑状态:显示正常节点内容 -->
|
||||
<template v-else>
|
||||
<div class="tree-node-wrapper">
|
||||
<span class="tree-node-title"> {{ getNodeTitle(dataRef) }} </span>
|
||||
<div class="tree-node-actions">
|
||||
<!-- 一级节点:支持新增子节点、编辑、排序、删除 -->
|
||||
<template v-if="!dataRef.parentId">
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click.stop="handleAddChild(dataRef)"
|
||||
:title="
|
||||
dataRef.sttpName
|
||||
? `子级添加-${dataRef.sttpName}`
|
||||
: '子级添加'
|
||||
"
|
||||
>
|
||||
<PlusOutlined />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click.stop="handleEditNode(dataRef)"
|
||||
title="编辑"
|
||||
>
|
||||
<EditOutlined />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click.stop="handleMoveUp(dataRef)"
|
||||
title="上移"
|
||||
:disabled="isFirstNode(dataRef)"
|
||||
>
|
||||
<ArrowUpOutlined />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click.stop="handleMoveDown(dataRef)"
|
||||
title="下移"
|
||||
:disabled="isLastNode(dataRef)"
|
||||
>
|
||||
<ArrowDownOutlined />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click.stop="handleDeleteNode(dataRef)"
|
||||
title="删除"
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</template>
|
||||
<!-- 二级节点:只支持编辑、排序、删除 -->
|
||||
<template v-else>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click.stop="handleEditNode(dataRef)"
|
||||
title="编辑"
|
||||
>
|
||||
<EditOutlined />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click.stop="handleMoveUp(dataRef)"
|
||||
title="上移"
|
||||
:disabled="isFirstNode(dataRef)"
|
||||
>
|
||||
<ArrowUpOutlined />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click.stop="handleMoveDown(dataRef)"
|
||||
title="下移"
|
||||
:disabled="isLastNode(dataRef)"
|
||||
>
|
||||
<ArrowDownOutlined />
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click.stop="handleDeleteNode(dataRef)"
|
||||
title="删除"
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 子节点添加区域 -->
|
||||
<div
|
||||
v-if="addingChildNode && currentParentKey === dataRef.key"
|
||||
class="add-child-node-row"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="childNodeData.stcd"
|
||||
show-search
|
||||
placeholder="请选择站点"
|
||||
style="width: 300px"
|
||||
:filter-option="filterOption"
|
||||
@change="handleChildNodeSelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in childStationList"
|
||||
:key="item.stcd"
|
||||
:value="item.stcd"
|
||||
>
|
||||
{{ item.stnm }}-{{ item.sttpName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.stop="handleSaveChildNode"
|
||||
style="margin-left: 8px"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
<a-button
|
||||
@click.stop="handleCancelAddChild"
|
||||
style="margin-left: 8px"
|
||||
>
|
||||
<MinusOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-tree>
|
||||
<a-empty v-else description="暂无数据" style="margin-top: 20px" />
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
PlusOutlined,
|
||||
MinusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { getEngInfoDropdown, getAddvcdDropdownByRvcd } from '@/api/select';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
initialValues?: any | null;
|
||||
loading?: boolean;
|
||||
riverOptions?: any[];
|
||||
}
|
||||
|
||||
const localLoading = ref(false);
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
initialValues: null,
|
||||
loading: false,
|
||||
riverOptions: () => []
|
||||
});
|
||||
|
||||
const modalVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: val => emit('update:visible', val)
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'cancel'): void;
|
||||
(e: 'ok', values: any): void;
|
||||
}>();
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const defaultFormData = reactive({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
rvcd: undefined,
|
||||
remark: undefined
|
||||
});
|
||||
const formData: any = reactive({ ...defaultFormData });
|
||||
|
||||
const rules: Record<string, Rule[]> = {
|
||||
name: [{ required: true, message: '请输入沿程配置名称', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '请输入配置编码', trigger: 'blur' }],
|
||||
rvcd: [{ required: true, message: '请选择所在河段', trigger: 'change' }]
|
||||
};
|
||||
|
||||
const handleRiverSelect = async (value: string) => {
|
||||
const res = await getEngInfoDropdown({ rvcd: value });
|
||||
stationList.value = (res.data || []).map((item: any) => ({
|
||||
...item,
|
||||
stnm: item.ennm
|
||||
}));
|
||||
};
|
||||
|
||||
const isEdit = computed(() => !!props.initialValues);
|
||||
|
||||
// 树形数据
|
||||
const treeData = ref<any[]>([]);
|
||||
|
||||
// 新增节点相关
|
||||
const addingNewNode = ref(false);
|
||||
const newNodeData = ref({
|
||||
stcd: undefined,
|
||||
ennm: '',
|
||||
sttpName: ''
|
||||
});
|
||||
|
||||
// 添加子节点相关
|
||||
const addingChildNode = ref(false);
|
||||
const currentParentKey = ref<string | null>(null);
|
||||
const childNodeData = ref({
|
||||
stcd: undefined,
|
||||
ennm: '',
|
||||
sttpName: ''
|
||||
});
|
||||
|
||||
// 编辑节点相关
|
||||
const editingNode = ref(false);
|
||||
const editingNodeKey = ref<string | null>(null);
|
||||
const editNodeData = ref({
|
||||
stcd: undefined,
|
||||
ennm: '',
|
||||
sttpName: '',
|
||||
originalData: null as any
|
||||
});
|
||||
|
||||
const stationList = ref([]);
|
||||
const childStationList = ref([]);
|
||||
|
||||
// 生成唯一ID
|
||||
let nodeIdCounter = 0;
|
||||
const generateNodeId = () => `node_${Date.now()}_${++nodeIdCounter}`;
|
||||
|
||||
// 获取节点标题
|
||||
const getNodeTitle = (dataRef: any) => {
|
||||
if (!dataRef.parentId) {
|
||||
return dataRef.ennm || '';
|
||||
} else {
|
||||
return `${dataRef.ennm || ''} ${dataRef.sttpName || ''}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索过滤
|
||||
const filterOption = (inputValue: string, option: any) => {
|
||||
const label = option.label || option.name || '';
|
||||
const reachcdName = option.reachcdName || '';
|
||||
const keyword = inputValue || '';
|
||||
|
||||
return label.indexOf(keyword) !== -1 || reachcdName.indexOf(keyword) !== -1;
|
||||
};
|
||||
|
||||
// 判断是否为第一个节点
|
||||
const isFirstNode = (dataRef: any) => {
|
||||
const siblings = getSiblings(dataRef);
|
||||
return siblings.length > 0 && siblings[0].key === dataRef.key;
|
||||
};
|
||||
|
||||
// 判断是否为最后一个节点
|
||||
const isLastNode = (dataRef: any) => {
|
||||
const siblings = getSiblings(dataRef);
|
||||
return (
|
||||
siblings.length > 0 && siblings[siblings.length - 1].key === dataRef.key
|
||||
);
|
||||
};
|
||||
|
||||
// 获取兄弟节点
|
||||
const getSiblings = (dataRef: any) => {
|
||||
if (!dataRef.parentId) {
|
||||
// 一级节点
|
||||
return treeData.value;
|
||||
} else {
|
||||
// 二级节点,找到父节点的 children
|
||||
const parent = findNodeByKey(treeData.value, dataRef.parentId);
|
||||
return parent?.children || [];
|
||||
}
|
||||
};
|
||||
|
||||
// 根据 key 查找节点
|
||||
const findNodeByKey = (nodes: any[], key: string): any => {
|
||||
for (const node of nodes) {
|
||||
if (node.key === key) {
|
||||
return node;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
const found = findNodeByKey(node.children, key);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 新增一级节点
|
||||
const handleAdd = () => {
|
||||
if (!formData.rvcd) {
|
||||
message.warning('请先选择所在河段');
|
||||
return;
|
||||
}
|
||||
addingNewNode.value = true;
|
||||
newNodeData.value = {
|
||||
stcd: undefined,
|
||||
ennm: '',
|
||||
sttpName: ''
|
||||
};
|
||||
};
|
||||
|
||||
// 选择节点
|
||||
const handleNodeSelect = (value: string) => {
|
||||
const station = stationList.value.find(item => item.stcd === value);
|
||||
if (station) {
|
||||
newNodeData.value.ennm = station.stnm;
|
||||
newNodeData.value.sttpName = station.sttpName;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存新增节点
|
||||
const handleSaveNewNode = () => {
|
||||
if (!newNodeData.value.stcd) {
|
||||
message.error('请选择站点');
|
||||
return;
|
||||
}
|
||||
|
||||
const newNode = {
|
||||
key: generateNodeId(),
|
||||
stcd: newNodeData.value.stcd,
|
||||
ennm: newNodeData.value.ennm,
|
||||
sttpName: newNodeData.value.sttpName,
|
||||
parentId: null,
|
||||
children: []
|
||||
};
|
||||
|
||||
treeData.value.push(newNode);
|
||||
addingNewNode.value = false;
|
||||
message.success('添加成功');
|
||||
};
|
||||
|
||||
// 取消新增
|
||||
const handleCancelAdd = () => {
|
||||
addingNewNode.value = false;
|
||||
newNodeData.value = {
|
||||
stcd: undefined,
|
||||
ennm: '',
|
||||
sttpName: ''
|
||||
};
|
||||
};
|
||||
|
||||
// 添加子节点(二级节点)
|
||||
const handleAddChild = async (parentData: any) => {
|
||||
addingChildNode.value = true;
|
||||
currentParentKey.value = parentData.key;
|
||||
childNodeData.value = {
|
||||
stcd: undefined,
|
||||
ennm: '',
|
||||
sttpName: ''
|
||||
};
|
||||
const res = await getAddvcdDropdownByRvcd({ stcd: parentData.stcd });
|
||||
childStationList.value = res.data || [];
|
||||
};
|
||||
|
||||
// 选择子节点
|
||||
const handleChildNodeSelect = (value: string) => {
|
||||
const station = childStationList.value.find(item => item.stcd === value);
|
||||
if (station) {
|
||||
childNodeData.value.ennm = station.stnm;
|
||||
childNodeData.value.sttpName = station.sttpName;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存新增子节点
|
||||
const handleSaveChildNode = () => {
|
||||
if (!childNodeData.value.stcd) {
|
||||
message.error('请选择站点');
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = findNodeByKey(treeData.value, currentParentKey.value!);
|
||||
if (!parent) {
|
||||
message.error('父节点不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const newChild = {
|
||||
key: generateNodeId(),
|
||||
stcd: childNodeData.value.stcd,
|
||||
ennm: childNodeData.value.ennm,
|
||||
sttpName: childNodeData.value.sttpName,
|
||||
parentId: parent.key,
|
||||
children: []
|
||||
};
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
// 在子节点列表顶部添加
|
||||
parent.children.unshift(newChild);
|
||||
|
||||
addingChildNode.value = false;
|
||||
currentParentKey.value = null;
|
||||
message.success('添加子节点成功');
|
||||
};
|
||||
|
||||
// 取消添加子节点
|
||||
const handleCancelAddChild = () => {
|
||||
addingChildNode.value = false;
|
||||
currentParentKey.value = null;
|
||||
childNodeData.value = {
|
||||
stcd: undefined,
|
||||
ennm: '',
|
||||
sttpName: ''
|
||||
};
|
||||
};
|
||||
|
||||
// 编辑节点
|
||||
const handleEditNode = async (dataRef: any) => {
|
||||
editingNode.value = true;
|
||||
editingNodeKey.value = dataRef.key;
|
||||
// 保存原始数据
|
||||
editNodeData.value = {
|
||||
stcd: dataRef.stcd,
|
||||
ennm: dataRef.ennm,
|
||||
sttpName: dataRef.sttpName,
|
||||
originalData: { ...dataRef }
|
||||
};
|
||||
if (dataRef.parentId) {
|
||||
const parent = findNodeByKey(treeData.value, dataRef.parentId);
|
||||
if (parent) {
|
||||
const res = await getAddvcdDropdownByRvcd({ stcd: parent.stcd });
|
||||
childStationList.value = res.data || [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 选择编辑节点
|
||||
const handleEditNodeSelect = (dataRef: any) => {
|
||||
const value = editNodeData.value.stcd;
|
||||
const list = dataRef.parentId ? childStationList.value : stationList.value;
|
||||
const station = list.find(item => item.stcd === value);
|
||||
if (station) {
|
||||
editNodeData.value.ennm = station.stnm;
|
||||
editNodeData.value.sttpName = station.sttpName;
|
||||
editNodeData.value.stcd = station.stcd;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存编辑节点
|
||||
const handleSaveEditNode = () => {
|
||||
if (!editNodeData.value.stcd) {
|
||||
message.error('请选择站点');
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到要编辑的节点
|
||||
const node = findNodeByKey(treeData.value, editingNodeKey.value!);
|
||||
if (!node) {
|
||||
message.error('节点不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新节点数据
|
||||
node.stcd = editNodeData.value.stcd;
|
||||
node.ennm = editNodeData.value.ennm;
|
||||
node.sttpName = editNodeData.value.sttpName;
|
||||
|
||||
editingNode.value = false;
|
||||
editingNodeKey.value = null;
|
||||
message.success('编辑成功');
|
||||
};
|
||||
|
||||
// 取消编辑节点
|
||||
const handleCancelEditNode = () => {
|
||||
editingNode.value = false;
|
||||
editingNodeKey.value = null;
|
||||
editNodeData.value = {
|
||||
stcd: undefined,
|
||||
ennm: '',
|
||||
sttpName: '',
|
||||
originalData: null
|
||||
};
|
||||
};
|
||||
|
||||
// 上移
|
||||
const handleMoveUp = (dataRef: any) => {
|
||||
const siblings = getSiblings(dataRef);
|
||||
const index = siblings.findIndex(item => item.key === dataRef.key);
|
||||
|
||||
if (index > 0) {
|
||||
// 交换位置
|
||||
[siblings[index - 1], siblings[index]] = [
|
||||
siblings[index],
|
||||
siblings[index - 1]
|
||||
];
|
||||
message.success('上移成功');
|
||||
}
|
||||
};
|
||||
|
||||
// 下移
|
||||
const handleMoveDown = (dataRef: any) => {
|
||||
const siblings = getSiblings(dataRef);
|
||||
const index = siblings.findIndex(item => item.key === dataRef.key);
|
||||
|
||||
if (index < siblings.length - 1) {
|
||||
// 交换位置
|
||||
[siblings[index], siblings[index + 1]] = [
|
||||
siblings[index + 1],
|
||||
siblings[index]
|
||||
];
|
||||
message.success('下移成功');
|
||||
}
|
||||
};
|
||||
|
||||
// 删除节点
|
||||
const handleDeleteNode = (dataRef: any) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除节点"${dataRef.ennm}"吗?`,
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
if (!dataRef.parentId) {
|
||||
// 删除一级节点
|
||||
const index = treeData.value.findIndex(
|
||||
item => item.key === dataRef.key
|
||||
);
|
||||
if (index > -1) {
|
||||
treeData.value.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
// 删除二级节点
|
||||
const parent = findNodeByKey(treeData.value, dataRef.parentId);
|
||||
if (parent && parent.children) {
|
||||
const index = parent.children.findIndex(
|
||||
item => item.key === dataRef.key
|
||||
);
|
||||
if (index > -1) {
|
||||
parent.children.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
message.success('删除成功');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
const submitValues = {
|
||||
...formData,
|
||||
captureDate: formData.captureDate
|
||||
? dayjs(formData.captureDate).format('YYYY-MM-DD')
|
||||
: undefined,
|
||||
configNodes: treeData.value // 提交树形数据
|
||||
};
|
||||
emit('ok', submitValues);
|
||||
} catch (error) {
|
||||
console.error('Validate Failed:', error);
|
||||
message.error('请检查表单填写是否正确');
|
||||
}
|
||||
};
|
||||
|
||||
const initForm = async () => {
|
||||
if (props.initialValues) {
|
||||
const values = props.initialValues;
|
||||
Object.keys(formData).forEach(key => {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
if (key === 'captureDate' && values[key]) {
|
||||
formData[key] = dayjs(values[key]);
|
||||
} else {
|
||||
formData[key] = values[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化树形数据
|
||||
if (values.configNodes && Array.isArray(values.configNodes)) {
|
||||
treeData.value = values.configNodes.map((node: any) => ({
|
||||
...node,
|
||||
children:
|
||||
node.children?.map((child: any) => ({
|
||||
...child
|
||||
})) || []
|
||||
}));
|
||||
} else {
|
||||
treeData.value = [];
|
||||
}
|
||||
|
||||
// 编辑时调用接口获取一级站点列表
|
||||
if (formData.rvcd) {
|
||||
const res = await getEngInfoDropdown({ rvcd: formData.rvcd });
|
||||
stationList.value = (res.data || []).map((item: any) => ({
|
||||
...item,
|
||||
stnm: item.ennm
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
resetForm();
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
Object.assign(formData, defaultFormData);
|
||||
treeData.value = [];
|
||||
addingNewNode.value = false;
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
resetForm();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
newVisible => {
|
||||
if (newVisible) {
|
||||
initForm();
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
localLoading
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.config-tree-container {
|
||||
margin-top: 16px;
|
||||
|
||||
.add-node-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-node-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
|
||||
.tree-node-title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tree-node-actions {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保鼠标悬停时显示操作按钮
|
||||
.tree-node-wrapper:hover {
|
||||
.tree-node-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 子节点添加区域样式
|
||||
.add-child-node-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0 8px 24px;
|
||||
margin-top: 4px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="content-search">
|
||||
<BasicSearch
|
||||
ref="basicSearchRef"
|
||||
:searchList="searchList"
|
||||
:initial-values="initSearchData"
|
||||
@reset="handleReset"
|
||||
@finish="onSearchFinish"
|
||||
@values-change="onValuesChange"
|
||||
>
|
||||
<template #actions>
|
||||
<a-tooltip title="新增配置">
|
||||
<a-button @click="props.handleAdd" type="primary">
|
||||
新增沿程配置
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</BasicSearch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import BasicSearch from '@/components/BasicSearch/index.vue';
|
||||
|
||||
interface Props {
|
||||
handleAdd: () => void;
|
||||
riverOptions: any[];
|
||||
riverOptionsLoad: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'reset', values: any): void;
|
||||
(e: 'searchFinish', values: any): void;
|
||||
}>();
|
||||
|
||||
const initSearchData = {
|
||||
name: null,
|
||||
rvcd: null
|
||||
};
|
||||
|
||||
const searchData = ref<any>({ ...initSearchData });
|
||||
const searchList: any = computed(() => [
|
||||
{
|
||||
type: 'Input',
|
||||
name: 'name',
|
||||
label: '沿程配置名称',
|
||||
fieldProps: {
|
||||
allowClear: true
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'Select',
|
||||
name: 'rvcd',
|
||||
label: '所在河段',
|
||||
width: 300,
|
||||
fieldProps: {
|
||||
allowClear: true
|
||||
},
|
||||
options: props.riverOptions,
|
||||
loading: props.riverOptionsLoad
|
||||
}
|
||||
]);
|
||||
|
||||
const onSearchFinish = (values: any) => {
|
||||
emit('searchFinish', values);
|
||||
};
|
||||
|
||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||
searchData.value = { ...searchData.value, ...allValues };
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
emit('reset', initSearchData);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
emit('searchFinish', initSearchData);
|
||||
});
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
379
frontend/src/views/system/ConfigManagement/index.vue
Normal file
379
frontend/src/views/system/ConfigManagement/index.vue
Normal file
@ -0,0 +1,379 @@
|
||||
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\ConfigManagement\index.vue -->
|
||||
<template>
|
||||
<div class="content">
|
||||
<ConfigManagementSearch
|
||||
ref="configManagementSearch"
|
||||
:handle-add="handleAdd"
|
||||
:river-options="riverOptions"
|
||||
:river-options-load="riverOptionsLoad"
|
||||
@reset="handleReset"
|
||||
@search-finish="onSearchFinish"
|
||||
/>
|
||||
<BasicTable
|
||||
ref="basicTable"
|
||||
:columns="columns"
|
||||
:list-url="getAllConfigTree"
|
||||
:search-params="searchParams"
|
||||
>
|
||||
<template #action="{ record }">
|
||||
<div class="flex gap-[6px]">
|
||||
<a-button
|
||||
class="!p-0"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEdit(record)"
|
||||
>编辑</a-button
|
||||
>
|
||||
<a-button
|
||||
class="!p-0"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="handleDelete(record)"
|
||||
>删除</a-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<ConfigManagementForm
|
||||
ref="configManagementForm"
|
||||
v-model:visible="editModalVisible"
|
||||
:initial-values="currentRecord"
|
||||
:river-options="riverOptions"
|
||||
:river-options-load="riverOptionsLoad"
|
||||
@cancel="editModalCancel"
|
||||
@ok="handleEditSubmit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onMounted } from 'vue';
|
||||
import BasicTable from '@/components/BasicTable/index.vue';
|
||||
import ConfigManagementSearch from './ConfigManagementSearch.vue';
|
||||
import ConfigManagementForm from './ConfigManagementForm.vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
getAllConfigTree,
|
||||
deleteBaseWbsb,
|
||||
saveBaseWbsbChild,
|
||||
saveBaseWbsbChildDetail,
|
||||
getChildConfigTree
|
||||
} from '@/api/system/map/ConfigManagement';
|
||||
import { getRvcdDropdown } from '@/api/select';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 表格实例
|
||||
const basicTable = ref<any>(null);
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
key: 'index',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
customRender: ({ index }) => index + 1
|
||||
},
|
||||
{
|
||||
title: '沿程配置名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '配置编码',
|
||||
key: 'code',
|
||||
dataIndex: 'code',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '所在河段',
|
||||
dataIndex: 'rvnm',
|
||||
key: 'rvnm',
|
||||
width: 300,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
key: 'recordUser',
|
||||
width: 120,
|
||||
dataIndex: 'recordUser'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'recordTime',
|
||||
dataIndex: 'recordTime',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
key: 'modifyTime',
|
||||
dataIndex: 'modifyTime',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
width: 360
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
];
|
||||
|
||||
// 搜索参数
|
||||
const searchParams = ref({
|
||||
"sort": [
|
||||
{
|
||||
"field": "recordTime",
|
||||
"dir": "desc"
|
||||
}
|
||||
],
|
||||
});
|
||||
// 编辑弹窗数据
|
||||
const currentRecord = ref<any | null>(null);
|
||||
const editModalVisible = ref(false);
|
||||
|
||||
// 新增处理
|
||||
const handleAdd = () => {
|
||||
currentRecord.value = null;
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 过滤掉值为 '-' 的字段
|
||||
const filterRecord = (record: any) => {
|
||||
const filtered: any = {};
|
||||
Object.keys(record).forEach(key => {
|
||||
if (record[key] !== '-') {
|
||||
filtered[key] = record[key];
|
||||
}
|
||||
});
|
||||
return filtered;
|
||||
};
|
||||
|
||||
// 编辑处理
|
||||
const handleEdit = async (record: any) => {
|
||||
currentRecord.value = filterRecord(record);
|
||||
const params = {
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
{
|
||||
field: 'alongId',
|
||||
operator: 'eq',
|
||||
value: record.id
|
||||
}
|
||||
]
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
field: 'sort',
|
||||
dir: 'asc'
|
||||
}
|
||||
]
|
||||
};
|
||||
const res = await getChildConfigTree(params);
|
||||
if (res.code == 0) {
|
||||
const data = res.data.data || [];
|
||||
const firstLevelNodes = data.filter((item: any) => !item.rstcd);
|
||||
const secondLevelNodes = data.filter((item: any) => item.rstcd);
|
||||
|
||||
const configNodes = firstLevelNodes.map((node: any, index: number) => {
|
||||
const children = secondLevelNodes.filter(
|
||||
(child: any) => child.rstcd === node.stcd
|
||||
);
|
||||
|
||||
return {
|
||||
key: `parent-${index}`,
|
||||
stcd: node.stcd,
|
||||
ennm: node.stnm,
|
||||
sttpName: node.sttpName,
|
||||
parentId: null,
|
||||
children: children.map((child: any, childIndex: number) => ({
|
||||
key: `child-${index}-${childIndex}`,
|
||||
stcd: child.stcd,
|
||||
ennm: child.stnm,
|
||||
sttpName: child.sttpName,
|
||||
parentId: `parent-${index}`,
|
||||
children: []
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
currentRecord.value.configNodes = configNodes;
|
||||
}
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 删除处理
|
||||
const handleDelete = (record: any) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
zIndex: 2002,
|
||||
onOk: async () => {
|
||||
try {
|
||||
let res = await deleteBaseWbsb([record.id]);
|
||||
if (res.code == 0) {
|
||||
message.success('删除成功');
|
||||
basicTable.value.refresh();
|
||||
} else {
|
||||
message.error('删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 搜索完成处理
|
||||
const onSearchFinish = (values: any) => {
|
||||
const params = {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
values.rvcd
|
||||
? {
|
||||
field: 'rvcd',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: values.rvcd
|
||||
}
|
||||
: null,
|
||||
values.name
|
||||
? {
|
||||
field: 'name',
|
||||
operator: 'contains',
|
||||
dataType: 'string',
|
||||
value: values.name
|
||||
}
|
||||
: null
|
||||
].filter(Boolean)
|
||||
};
|
||||
basicTable.value.getList(params);
|
||||
};
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
basicTable.value.getList({
|
||||
logic: 'and',
|
||||
filters: []
|
||||
});
|
||||
};
|
||||
|
||||
// 表单取消
|
||||
const editModalCancel = () => {
|
||||
currentRecord.value = null;
|
||||
editModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 表单提交
|
||||
const handleEditSubmit = async (values: any) => {
|
||||
try {
|
||||
const formData = {
|
||||
...currentRecord.value,
|
||||
...values,
|
||||
modifyTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||
};
|
||||
delete formData.configNodes;
|
||||
|
||||
Object.keys(formData).forEach(key => {
|
||||
if (key.includes('-')) {
|
||||
delete formData[key];
|
||||
}
|
||||
});
|
||||
|
||||
let res = await saveBaseWbsbChild(formData);
|
||||
if (res.code == 0) {
|
||||
const alongId = res.data;
|
||||
const treeData = values.configNodes || [];
|
||||
|
||||
if (treeData.length > 0) {
|
||||
const formatTreeData = (
|
||||
nodes: any[],
|
||||
parentId: string | null = null,
|
||||
parentStcd: string | null = null
|
||||
) => {
|
||||
return nodes.map(node => ({
|
||||
alongId,
|
||||
stcd: node.stcd,
|
||||
stnm: node.ennm || node.stnm,
|
||||
sttpName: node.sttpName,
|
||||
parentId: parentId ? parentId : null,
|
||||
rstcd: parentStcd || null,
|
||||
children:
|
||||
node.children && node.children.length > 0
|
||||
? formatTreeData(node.children, node.key, node.stcd)
|
||||
: []
|
||||
}));
|
||||
};
|
||||
|
||||
const detailData = formatTreeData(treeData);
|
||||
|
||||
const detailRes = await saveBaseWbsbChildDetail(detailData);
|
||||
if (detailRes.code == 0) {
|
||||
message.success(`保存成功`);
|
||||
editModalVisible.value = false;
|
||||
basicTable.value.refresh();
|
||||
} else {
|
||||
message.error(`保存子节点失败`);
|
||||
}
|
||||
} else {
|
||||
message.success(`保存成功`);
|
||||
editModalVisible.value = false;
|
||||
basicTable.value.refresh();
|
||||
}
|
||||
} else {
|
||||
message.error(`保存失败`);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(`保存失败`);
|
||||
editModalVisible.value = false;
|
||||
return;
|
||||
}
|
||||
};
|
||||
const riverOptions = ref<any>([]);
|
||||
const riverOptionsLoad = ref(false);
|
||||
const initOption = () => {
|
||||
const params = {};
|
||||
riverOptionsLoad.value = true;
|
||||
getRvcdDropdown(params).then((res: any) => {
|
||||
res.data.forEach((item: any) => {
|
||||
item.value = item.rvcd;
|
||||
item.label = item.rvnm;
|
||||
});
|
||||
riverOptions.value = res.data || [];
|
||||
riverOptionsLoad.value = false;
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
initOption();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 900;
|
||||
pointer-events: all;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.config-management {
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:title="isEdit ? '编辑电站' : '新增电站'"
|
||||
v-model:open="modalVisible"
|
||||
:confirm-loading="localLoading"
|
||||
width="600px"
|
||||
:destroy-on-close="true"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
name="edit_form"
|
||||
:labelCol="{ span: 5 }"
|
||||
>
|
||||
<a-form-item label="电站名称" name="stationName">
|
||||
<a-input
|
||||
v-model:value="formData.stationName"
|
||||
style="width: 100%"
|
||||
placeholder="请输入电站名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属流域" name="basin">
|
||||
<a-input
|
||||
v-model:value="formData.basin"
|
||||
style="width: 100%"
|
||||
placeholder="请输入所属流域"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="装机容量(MW)" name="capacity">
|
||||
<a-input-number
|
||||
v-model:value="formData.capacity"
|
||||
style="width: 100%"
|
||||
placeholder="请输入装机容量"
|
||||
:min="0"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select v-model:value="formData.status" style="width: 100%">
|
||||
<a-select-option value="running">运行中</a-select-option>
|
||||
<a-select-option value="stopped">停运</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="位置坐标" name="coordinates">
|
||||
<a-input
|
||||
v-model:value="formData.coordinates"
|
||||
style="width: 100%"
|
||||
placeholder="请输入位置坐标"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
initialValues?: any | null;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const localLoading = ref(false);
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
initialValues: null,
|
||||
loading: false
|
||||
});
|
||||
|
||||
const modalVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: val => emit('update:visible', val)
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'cancel'): void;
|
||||
(e: 'ok', values: any): void;
|
||||
}>();
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const defaultFormData = reactive({
|
||||
id: undefined,
|
||||
stationName: undefined,
|
||||
basin: undefined,
|
||||
capacity: undefined,
|
||||
status: undefined,
|
||||
coordinates: undefined
|
||||
});
|
||||
const formData: any = reactive({ ...defaultFormData });
|
||||
|
||||
const rules: Record<string, Rule[]> = {
|
||||
stationName: [{ required: true, message: '请输入电站名称', trigger: 'blur' }],
|
||||
basin: [{ required: true, message: '请输入所属流域', trigger: 'blur' }],
|
||||
capacity: [{ required: true, message: '请输入装机容量', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
const isEdit = computed(() => !!props.initialValues);
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
const submitValues = {
|
||||
...formData
|
||||
};
|
||||
emit('ok', submitValues);
|
||||
} catch (error) {
|
||||
console.error('Validate Failed:', error);
|
||||
message.error('请检查表单填写是否正确');
|
||||
}
|
||||
};
|
||||
|
||||
const initForm = () => {
|
||||
if (props.initialValues) {
|
||||
const values = props.initialValues;
|
||||
Object.keys(formData).forEach(key => {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
formData[key] = values[key];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resetForm();
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
Object.assign(formData, defaultFormData);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
resetForm();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
newVisible => {
|
||||
if (newVisible) {
|
||||
initForm();
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
localLoading
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="content-search">
|
||||
<BasicSearch
|
||||
ref="basicSearchRef"
|
||||
:searchList="searchList"
|
||||
:initial-values="initSearchData"
|
||||
@reset="handleReset"
|
||||
@finish="onSearchFinish"
|
||||
@values-change="onValuesChange"
|
||||
>
|
||||
<template #actions>
|
||||
<a-tooltip title="新增电站">
|
||||
<a-button @click="props.handleAdd" type="primary"> 新增电站 </a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</BasicSearch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import BasicSearch from "@/components/BasicSearch/index.vue";
|
||||
|
||||
interface Props {
|
||||
handleAdd: () => void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "reset", values: any): void;
|
||||
(e: "searchFinish", values: any): void;
|
||||
}>();
|
||||
|
||||
const initSearchData = {
|
||||
stationName: "",
|
||||
};
|
||||
|
||||
const searchData = ref<any>({ ...initSearchData });
|
||||
const searchList: any = computed(() => [
|
||||
{
|
||||
type: "Input",
|
||||
name: "stationName",
|
||||
label: "电站名称",
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const onSearchFinish = (values: any) => {
|
||||
emit("searchFinish", values);
|
||||
};
|
||||
|
||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||
searchData.value = { ...searchData.value, ...allValues };
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
emit("reset", initSearchData);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
emit("searchFinish", initSearchData);
|
||||
});
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
203
frontend/src/views/system/StationManagement/index.vue
Normal file
203
frontend/src/views/system/StationManagement/index.vue
Normal file
@ -0,0 +1,203 @@
|
||||
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\StationManagement\index.vue -->
|
||||
<template>
|
||||
<div class="content">
|
||||
<StationManagementSearch
|
||||
ref="stationManagementSearch"
|
||||
:handle-add="handleAdd"
|
||||
@reset="handleReset"
|
||||
@search-finish="onSearchFinish"
|
||||
/>
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:data="dataSource"
|
||||
:list-url="fetchStationData"
|
||||
:search-params="searchParams"
|
||||
:enable-row-selection="true"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #action="{ column, record }">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)"
|
||||
>编辑</a-button
|
||||
>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="handleDelete(record)"
|
||||
>删除</a-button
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #status="{ column, record }">
|
||||
<a-tag :color="record.status === 'running' ? 'green' : 'red'">
|
||||
{{ record.status === 'running' ? '运行中' : '停运' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<StationManagementForm
|
||||
ref="stationManagementForm"
|
||||
v-model:visible="editModalVisible"
|
||||
:initial-values="currentRecord"
|
||||
@cancel="editModalCancel"
|
||||
@ok="handleEditSubmit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import BasicTable from '@/components/BasicTable/index.vue';
|
||||
import StationManagementSearch from './StationManagementSearch.vue';
|
||||
import StationManagementForm from './StationManagementForm.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '电站名称',
|
||||
dataIndex: 'stationName',
|
||||
key: 'stationName',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '所属流域',
|
||||
dataIndex: 'basin',
|
||||
key: 'basin',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '装机容量(MW)',
|
||||
dataIndex: 'capacity',
|
||||
key: 'capacity',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
slots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
title: '位置坐标',
|
||||
dataIndex: 'coordinates',
|
||||
key: 'coordinates',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
];
|
||||
|
||||
// 搜索参数
|
||||
const searchParams = ref({});
|
||||
// 编辑弹窗数据
|
||||
const currentRecord = ref<any | null>(null);
|
||||
const editModalVisible = ref(false);
|
||||
|
||||
// 数据源
|
||||
const dataSource = ref([]);
|
||||
|
||||
// 模拟数据获取函数
|
||||
const fetchStationData = (params: any) => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
data: {
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
stationName: '三峡电站',
|
||||
basin: '长江流域',
|
||||
capacity: 22500,
|
||||
status: 'running',
|
||||
coordinates: '111.29,30.83'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
stationName: '葛洲坝电站',
|
||||
basin: '长江流域',
|
||||
capacity: 2715,
|
||||
status: 'running',
|
||||
coordinates: '111.28,30.75'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
stationName: '溪洛渡电站',
|
||||
basin: '金沙江流域',
|
||||
capacity: 13860,
|
||||
status: 'stopped',
|
||||
coordinates: '103.65,28.25'
|
||||
}
|
||||
],
|
||||
total: 3
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
currentRecord.value = null;
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 编辑处理
|
||||
const handleEdit = (record: any) => {
|
||||
currentRecord.value = { ...record };
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 删除处理
|
||||
const handleDelete = (record: any) => {
|
||||
message.warning(`删除电站: ${record.stationName}`);
|
||||
};
|
||||
|
||||
// 搜索完成处理
|
||||
const onSearchFinish = (values: any) => {
|
||||
console.log(values);
|
||||
};
|
||||
|
||||
// 重置
|
||||
const handleReset = (params: any) => {};
|
||||
|
||||
// 选择变化处理
|
||||
const handleSelectionChange = (selectedRows: any[]) => {
|
||||
console.log('选中的行:', selectedRows);
|
||||
};
|
||||
|
||||
// 表单取消
|
||||
const editModalCancel = () => {
|
||||
editModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 表单提交
|
||||
const handleEditSubmit = (values: any) => {
|
||||
console.log(values);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 900;
|
||||
pointer-events: all;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.station-management {
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:title="isEdit ? '编辑倾斜摄影' : '新增倾斜摄影'"
|
||||
v-model:open="modalVisible"
|
||||
:confirm-loading="localLoading"
|
||||
width="600px"
|
||||
:destroy-on-close="true"
|
||||
ok-text="保存"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
name="edit_form"
|
||||
:labelCol="{ span: 5 }"
|
||||
>
|
||||
<a-form-item label="倾斜影像名称" name="stnm">
|
||||
<a-input
|
||||
v-model:value="formData.stnm"
|
||||
style="width: 100%"
|
||||
placeholder="请输入倾斜影像名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所在河段" name="hbrvcd">
|
||||
<a-select
|
||||
v-model:value="formData.hbrvcd"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
placeholder="请选择所在河段"
|
||||
:options="riverOptions"
|
||||
:filter-option="filterOption"
|
||||
:disabled="isEdit"
|
||||
@change="handleRiverSelect"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属电站" name="rstcd">
|
||||
<a-select
|
||||
v-model:value="formData.rstcd"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
placeholder="请选择所属电站"
|
||||
:options="ennmOptions"
|
||||
:filter-option="filterOption"
|
||||
:disabled="isEdit"
|
||||
/>
|
||||
<!-- <a-input
|
||||
v-model:value="formData.ennm"
|
||||
style="width: 100%"
|
||||
placeholder="请输入所属电站"
|
||||
/> -->
|
||||
</a-form-item>
|
||||
<a-form-item label="倾斜影像URL" name="url">
|
||||
<a-input
|
||||
v-model:value="formData.url"
|
||||
style="width: 100%"
|
||||
placeholder="请输入倾斜影像URL"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="模型坐标" name="location">
|
||||
<a-textarea
|
||||
v-model:value="formData.location"
|
||||
:rows="4"
|
||||
style="width: 100%"
|
||||
placeholder="请输入模型坐标"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="模型精度" name="accuracy">
|
||||
<a-input-number
|
||||
v-model:value="formData.accuracy"
|
||||
style="width: 100%"
|
||||
placeholder="请输入模型精度"
|
||||
:min="0"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="边界范围" name="boundary">
|
||||
<a-textarea
|
||||
v-model:value="formData.boundary"
|
||||
:rows="4"
|
||||
style="width: 100%"
|
||||
placeholder="请输入边界范围"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="模型高度" name="height">
|
||||
<a-input-number
|
||||
v-model:value="formData.height"
|
||||
style="width: 100%"
|
||||
placeholder="请输入模型高度"
|
||||
:min="0"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="comments">
|
||||
<a-textarea
|
||||
v-model:value="formData.comments"
|
||||
:rows="4"
|
||||
style="width: 100%"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import dayjs from 'dayjs';
|
||||
import { getEngInfoDropdown } from '@/api/select';
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
initialValues?: any | null;
|
||||
loading?: boolean;
|
||||
riverOptions?: any[];
|
||||
}
|
||||
const localLoading = ref(false);
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
initialValues: null,
|
||||
loading: false,
|
||||
riverOptions: () => []
|
||||
});
|
||||
|
||||
const modalVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: val => emit('update:visible', val)
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'cancel'): void;
|
||||
(e: 'ok', values: any): void;
|
||||
}>();
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const defaultFormData = reactive({
|
||||
id: undefined,
|
||||
stnm: undefined,
|
||||
hbrvcd: undefined,
|
||||
rstcd: undefined,
|
||||
url: undefined,
|
||||
location: undefined,
|
||||
accuracy: undefined,
|
||||
boundary: undefined,
|
||||
height: undefined,
|
||||
comments: undefined
|
||||
});
|
||||
const formData: any = reactive({ ...defaultFormData });
|
||||
|
||||
const rules: Record<string, Rule[]> = {
|
||||
stnm: [{ required: true, message: '请输入倾斜影像名称', trigger: 'blur' }],
|
||||
hbrvcd: [{ required: true, message: '请输入所在河段', trigger: 'blur' }],
|
||||
rstcd: [{ required: true, message: '请输入所属电站', trigger: 'blur' }],
|
||||
url: [{ required: true, message: '请输入倾斜影像URL', trigger: 'blur' }],
|
||||
location: [{ required: true, message: '请输入模型坐标', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
const isEdit = computed(() => !!props.initialValues);
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
const ennmItem = ennmOptions.value.find(item => item.value == formData.rstcd);
|
||||
const riverItem = props.riverOptions.find(item => item.value == formData.hbrvcd);
|
||||
const submitValues = {
|
||||
...formData,
|
||||
ennm: ennmItem?.ennm || '',
|
||||
hbrvcdName: riverItem?.label || '',
|
||||
captureDate: formData.captureDate
|
||||
? dayjs(formData.captureDate).format('YYYY-MM-DD')
|
||||
: undefined,
|
||||
};
|
||||
|
||||
emit('ok', submitValues);
|
||||
} catch (error) {
|
||||
console.error('Validate Failed:', error);
|
||||
message.error('请检查表单填写是否正确');
|
||||
}
|
||||
};
|
||||
|
||||
const initForm = () => {
|
||||
if (props.initialValues) {
|
||||
const values = props.initialValues;
|
||||
Object.keys(formData).forEach(key => {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
if (key === 'captureDate' && values[key]) {
|
||||
formData[key] = dayjs(values[key]);
|
||||
} else {
|
||||
formData[key] = values[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resetForm();
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
Object.assign(formData, defaultFormData);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
resetForm();
|
||||
};
|
||||
// 搜索过滤
|
||||
const filterOption = (inputValue: string, option: any) => {
|
||||
const label = option.label || option.name || '';
|
||||
const reachcdName = option.reachcdName || '';
|
||||
const keyword = inputValue || '';
|
||||
|
||||
return label.indexOf(keyword) !== -1 || reachcdName.indexOf(keyword) !== -1;
|
||||
};
|
||||
const ennmOptions = ref([]);
|
||||
const handleRiverSelect = async (value: any) => {
|
||||
let res: any = await getEngInfoDropdown({ reachcd: value });
|
||||
if (res?.data?.length > 0) {
|
||||
res.data.forEach((item: any) => {
|
||||
item.value = item.stcd;
|
||||
item.label = item.ennm;
|
||||
});
|
||||
ennmOptions.value = res.data;
|
||||
} else {
|
||||
ennmOptions.value = [];
|
||||
}
|
||||
};
|
||||
watch(
|
||||
() => props.visible,
|
||||
newVisible => {
|
||||
if (newVisible) {
|
||||
initForm();
|
||||
handleRiverSelect('');
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
localLoading
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="content-search">
|
||||
<BasicSearch
|
||||
ref="basicSearchRef"
|
||||
:searchList="searchList"
|
||||
:initial-values="initSearchData"
|
||||
@reset="handleReset"
|
||||
@finish="onSearchFinish"
|
||||
@values-change="onValuesChange"
|
||||
>
|
||||
<template #actions>
|
||||
<a-tooltip title="新增倾斜摄影">
|
||||
<a-button @click="props.handleAdd" type="primary"> 新增倾斜摄影 </a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</BasicSearch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import BasicSearch from "@/components/BasicSearch/index.vue";
|
||||
|
||||
interface Props {
|
||||
handleAdd: () => void;
|
||||
riverOptions: any[];
|
||||
riverOptionsLoad: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "reset", values: any): void;
|
||||
(e: "searchFinish", values: any): void;
|
||||
}>();
|
||||
|
||||
const initSearchData = {
|
||||
stnm: "",
|
||||
rvcd:""
|
||||
};
|
||||
|
||||
const searchData = ref<any>({ ...initSearchData });
|
||||
const searchList: any = computed(() => [
|
||||
{
|
||||
type: "Input",
|
||||
name: "stnm",
|
||||
label: "倾斜影像名称",
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Select',
|
||||
name: 'rvcd',
|
||||
label: '所在河段',
|
||||
width: 300,
|
||||
fieldProps: {
|
||||
allowClear: true
|
||||
},
|
||||
options: props.riverOptions,
|
||||
loading: props.riverOptionsLoad
|
||||
}
|
||||
]);
|
||||
|
||||
const onSearchFinish = (values: any) => {
|
||||
emit("searchFinish", values);
|
||||
};
|
||||
|
||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||
searchData.value = { ...searchData.value, ...allValues };
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
emit("reset", initSearchData);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
emit("searchFinish", initSearchData);
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
269
frontend/src/views/system/TiltPhotoManagement/index.vue
Normal file
269
frontend/src/views/system/TiltPhotoManagement/index.vue
Normal file
@ -0,0 +1,269 @@
|
||||
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\TiltPhotoManagement\index.vue -->
|
||||
<template>
|
||||
<div class="content">
|
||||
<TiltPhotoManagementSearch
|
||||
ref="tiltPhotoManagementSearch"
|
||||
:river-options="riverOptions"
|
||||
:river-options-load="riverOptionsLoad"
|
||||
:handle-add="handleAdd"
|
||||
@reset="handleReset"
|
||||
@search-finish="onSearchFinish"
|
||||
/>
|
||||
<BasicTable
|
||||
ref="basicTable"
|
||||
:columns="columns"
|
||||
:list-url="getAllTiltPhotoTree"
|
||||
:search-params="searchParams"
|
||||
>
|
||||
<template #action="{ column, record }">
|
||||
<div class="flex gap-[6px]">
|
||||
<a-button
|
||||
class="!p-0"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEdit(record)"
|
||||
>编辑</a-button
|
||||
>
|
||||
<a-button
|
||||
class="!p-0"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="handleDelete(record)"
|
||||
>删除</a-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<TiltPhotoManagementForm
|
||||
ref="tiltPhotoManagementForm"
|
||||
v-model:visible="editModalVisible"
|
||||
:river-options="riverOptions"
|
||||
:river-options-load="riverOptionsLoad"
|
||||
:initial-values="currentRecord"
|
||||
@cancel="editModalCancel"
|
||||
@ok="handleEditSubmit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onMounted } from 'vue';
|
||||
import BasicTable from '@/components/BasicTable/index.vue';
|
||||
import TiltPhotoManagementSearch from './TiltPhotoManagementSearch.vue';
|
||||
import TiltPhotoManagementForm from './TiltPhotoManagementForm.vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
getAllTiltPhotoTree,
|
||||
saveTiltPhoto,
|
||||
deleteTiltPhoto
|
||||
} from '@/api/system/map/TiltPhotoManagement';
|
||||
import { getRvcdDropdown } from '@/api/select';
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
key: 'index',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
customRender: ({ text, record, index }) => index + 1
|
||||
},
|
||||
{
|
||||
title: '倾斜影像名称',
|
||||
dataIndex: 'stnm',
|
||||
key: 'stnm',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'url',
|
||||
key: 'url'
|
||||
// width: 360
|
||||
},
|
||||
{
|
||||
title: '所在河段',
|
||||
dataIndex: 'hbrvcdName',
|
||||
key: 'hbrvcdName',
|
||||
width: 280
|
||||
},
|
||||
{
|
||||
title: '所属电站',
|
||||
key: 'ennm',
|
||||
dataIndex: 'ennm',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
key: 'recordUser',
|
||||
dataIndex: 'recordUser',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'recordTime',
|
||||
dataIndex: 'recordTime',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
key: 'modifyTime',
|
||||
dataIndex: 'modifyTime',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
slots: { customRender: 'action' }
|
||||
}
|
||||
];
|
||||
|
||||
// 表格实例
|
||||
const basicTable = ref<any>(null);
|
||||
// 搜索参数
|
||||
const searchParams = ref({});
|
||||
// 编辑弹窗数据
|
||||
const currentRecord = ref<any | null>(null);
|
||||
const editModalVisible = ref(false);
|
||||
|
||||
// 添加处理
|
||||
const handleAdd = () => {
|
||||
currentRecord.value = null;
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 编辑处理
|
||||
const handleEdit = (record: any) => {
|
||||
currentRecord.value = { ...record };
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 删除处理
|
||||
const handleDelete = (record: any) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
zIndex: 2002,
|
||||
onOk: async () => {
|
||||
try {
|
||||
let res = await deleteTiltPhoto({ id: record.id });
|
||||
message.success('删除成功');
|
||||
basicTable.value.refresh();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 搜索完成处理
|
||||
const onSearchFinish = (values: any) => {
|
||||
const params = {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
values.parentId
|
||||
? {
|
||||
field: 'parentId',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: values.parentId
|
||||
}
|
||||
: null,
|
||||
values.stnm
|
||||
? {
|
||||
field: 'stnm',
|
||||
operator: 'contains',
|
||||
dataType: 'string',
|
||||
value: values.stnm
|
||||
}
|
||||
: null,
|
||||
values.rvcd
|
||||
? {
|
||||
field: 'hbrvcd',
|
||||
operator: 'contains',
|
||||
dataType: 'string',
|
||||
value: values.rvcd
|
||||
}
|
||||
: null
|
||||
].filter(Boolean)
|
||||
};
|
||||
basicTable.value.getList(params);
|
||||
};
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
nextTick(() => {
|
||||
basicTable.value.getList({
|
||||
logic: 'and',
|
||||
filters: []
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 表单取消
|
||||
const editModalCancel = () => {
|
||||
currentRecord.value = null;
|
||||
editModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 表单提交
|
||||
const handleEditSubmit = async (values: any) => {
|
||||
try {
|
||||
let res = await saveTiltPhoto({
|
||||
...currentRecord.value,
|
||||
...values,
|
||||
recordTime:currentRecord.value.recordTime != '-' ? currentRecord.value.recordTime : null
|
||||
});
|
||||
message.success(`保存成功`);
|
||||
|
||||
editModalVisible.value = false;
|
||||
basicTable.value.refresh();
|
||||
} catch (error) {
|
||||
message.error(`保存失败`);
|
||||
|
||||
editModalVisible.value = false;
|
||||
return;
|
||||
}
|
||||
};
|
||||
const riverOptions = ref<any>([]);
|
||||
const riverOptionsLoad = ref(false);
|
||||
const initOption = () => {
|
||||
const params = {};
|
||||
riverOptionsLoad.value = true;
|
||||
getRvcdDropdown(params).then((res: any) => {
|
||||
res.data.forEach((item: any) => {
|
||||
item.value = item.rvcd;
|
||||
item.label = item.rvnm;
|
||||
});
|
||||
riverOptions.value = res.data || [];
|
||||
riverOptionsLoad.value = false;
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
initOption();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tilt-photo-management {
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 900;
|
||||
pointer-events: all;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -20,6 +20,7 @@
|
||||
v-model:value="formData.title"
|
||||
style="width: 100%"
|
||||
placeholder="请输入图层名称"
|
||||
:maxlength="50"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="图层编码" name="code">
|
||||
|
||||
@ -265,7 +265,10 @@ const handleDelete = async (record: any) => {
|
||||
onOk: async () => {
|
||||
try {
|
||||
let res = await deleteMapLayer([record.id]);
|
||||
message.success('删除成功');
|
||||
if(res.code == 0){
|
||||
message.success('删除成功');
|
||||
}
|
||||
|
||||
basicTable.value.refresh();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\LegendData\index.vue -->
|
||||
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\componentsData\index.vue -->
|
||||
<template>
|
||||
<div class="content">
|
||||
<LegendDataSearch
|
||||
|
||||
@ -250,7 +250,9 @@ const handleDelete = (record: any) => {
|
||||
onOk: async () => {
|
||||
try {
|
||||
let res = await deleteMapLegend([record.id]);
|
||||
message.success('删除成功');
|
||||
if (res.code == 0) {
|
||||
message.success('删除成功');
|
||||
}
|
||||
basicTable.value.refresh();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
@ -375,10 +377,12 @@ const handleEditSubmit = async (values: any, type: string) => {
|
||||
...values,
|
||||
parentId: parentId.value
|
||||
});
|
||||
if (type === 'switch') {
|
||||
message.success('修改状态成功');
|
||||
} else {
|
||||
message.success(`保存成功`);
|
||||
if (res.code == 0) {
|
||||
if (type === 'switch') {
|
||||
message.success('修改状态成功');
|
||||
} else {
|
||||
message.success(`保存成功`);
|
||||
}
|
||||
}
|
||||
editModalVisible.value = false;
|
||||
basicTable.value.refresh();
|
||||
|
||||
@ -23,19 +23,33 @@
|
||||
placeholder="请输入倾斜影像名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所在河段" name="hbrvcdName">
|
||||
<a-input
|
||||
v-model:value="formData.hbrvcdName"
|
||||
<a-form-item label="所在河段" name="hbrvcd">
|
||||
<a-select
|
||||
v-model:value="formData.hbrvcd"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
placeholder="请输入所在河段"
|
||||
placeholder="请选择所在河段"
|
||||
:options="riverOptions"
|
||||
:filter-option="filterOption"
|
||||
:disabled="isEdit"
|
||||
@change="handleRiverSelect"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属电站" name="ennm">
|
||||
<a-input
|
||||
<a-form-item label="所属电站" name="rstcd">
|
||||
<a-select
|
||||
v-model:value="formData.rstcd"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
placeholder="请选择所属电站"
|
||||
:options="ennmOptions"
|
||||
:filter-option="filterOption"
|
||||
:disabled="isEdit"
|
||||
/>
|
||||
<!-- <a-input
|
||||
v-model:value="formData.ennm"
|
||||
style="width: 100%"
|
||||
placeholder="请输入所属电站"
|
||||
/>
|
||||
/> -->
|
||||
</a-form-item>
|
||||
<a-form-item label="倾斜影像URL" name="url">
|
||||
<a-input
|
||||
@ -76,9 +90,9 @@
|
||||
:min="0"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="description">
|
||||
<a-form-item label="备注" name="comments">
|
||||
<a-textarea
|
||||
v-model:value="formData.description"
|
||||
v-model:value="formData.comments"
|
||||
:rows="4"
|
||||
style="width: 100%"
|
||||
placeholder="请输入备注"
|
||||
@ -89,33 +103,34 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import type { Rule } from "ant-design-vue/es/form";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import dayjs from 'dayjs';
|
||||
import { getEngInfoDropdown } from '@/api/select';
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
initialValues?: any | null;
|
||||
loading?: boolean;
|
||||
riverOptions?: any[];
|
||||
}
|
||||
|
||||
const localLoading = ref(false);
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
initialValues: null,
|
||||
loading: false,
|
||||
riverOptions: () => []
|
||||
});
|
||||
|
||||
const modalVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit("update:visible", val),
|
||||
set: val => emit('update:visible', val)
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:visible", value: boolean): void;
|
||||
(e: "cancel"): void;
|
||||
(e: "ok", values: any): void;
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'cancel'): void;
|
||||
(e: 'ok', values: any): void;
|
||||
}>();
|
||||
|
||||
const formRef = ref();
|
||||
@ -123,23 +138,23 @@ const formRef = ref();
|
||||
const defaultFormData = reactive({
|
||||
id: undefined,
|
||||
stnm: undefined,
|
||||
hbrvcdName: undefined,
|
||||
ennm: undefined,
|
||||
hbrvcd: undefined,
|
||||
rstcd: undefined,
|
||||
url: undefined,
|
||||
location: undefined,
|
||||
accuracy: undefined,
|
||||
boundary: undefined,
|
||||
height: undefined,
|
||||
description: undefined,
|
||||
comments: undefined
|
||||
});
|
||||
const formData: any = reactive({ ...defaultFormData });
|
||||
|
||||
const rules: Record<string, Rule[]> = {
|
||||
stnm: [{ required: true, message: "请输入倾斜影像名称", trigger: "blur" }],
|
||||
hbrvcdName: [{ required: true, message: "请输入所在河段", trigger: "blur" }],
|
||||
ennm: [{ required: true, message: "请输入所属电站", trigger: "blur" }],
|
||||
url: [{ required: true, message: "请输入倾斜影像URL", trigger: "blur" }],
|
||||
location: [{ required: true, message: "请输入模型坐标", trigger: "blur" }],
|
||||
stnm: [{ required: true, message: '请输入倾斜影像名称', trigger: 'blur' }],
|
||||
hbrvcd: [{ required: true, message: '请输入所在河段', trigger: 'blur' }],
|
||||
rstcd: [{ required: true, message: '请输入所属电站', trigger: 'blur' }],
|
||||
url: [{ required: true, message: '请输入倾斜影像URL', trigger: 'blur' }],
|
||||
location: [{ required: true, message: '请输入模型坐标', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
const isEdit = computed(() => !!props.initialValues);
|
||||
@ -147,25 +162,33 @@ const isEdit = computed(() => !!props.initialValues);
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
const ennmItem = ennmOptions.value.find(item => item.value == formData.rstcd);
|
||||
const riverItem = props.riverOptions.find(item => item.value == formData.hbrvcd);
|
||||
const submitValues = {
|
||||
...formData,
|
||||
ennm: ennmItem?.ennm || '',
|
||||
lgtd: ennmItem?.lgtd || '',
|
||||
lttd: ennmItem?.lttd || '',
|
||||
hbrvcdName: riverItem?.label || '',
|
||||
baseId: riverItem?.baseId || '',
|
||||
baseName: riverItem?.baseName || '',
|
||||
captureDate: formData.captureDate
|
||||
? dayjs(formData.captureDate).format("YYYY-MM-DD")
|
||||
: undefined,
|
||||
? dayjs(formData.captureDate).format('YYYY-MM-DD')
|
||||
: undefined
|
||||
};
|
||||
emit("ok", submitValues);
|
||||
emit('ok', submitValues);
|
||||
} catch (error) {
|
||||
console.error("Validate Failed:", error);
|
||||
message.error("请检查表单填写是否正确");
|
||||
console.error('Validate Failed:', error);
|
||||
message.error('请检查表单填写是否正确');
|
||||
}
|
||||
};
|
||||
|
||||
const initForm = () => {
|
||||
if (props.initialValues) {
|
||||
const values = props.initialValues;
|
||||
Object.keys(formData).forEach((key) => {
|
||||
Object.keys(formData).forEach(key => {
|
||||
if (values.hasOwnProperty(key)) {
|
||||
if (key === "captureDate" && values[key]) {
|
||||
if (key === 'captureDate' && values[key]) {
|
||||
formData[key] = dayjs(values[key]);
|
||||
} else {
|
||||
formData[key] = values[key];
|
||||
@ -185,23 +208,44 @@ const resetForm = () => {
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
emit("update:visible", false);
|
||||
emit("cancel");
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
resetForm();
|
||||
};
|
||||
// 搜索过滤
|
||||
const filterOption = (inputValue: string, option: any) => {
|
||||
const label = option.label || option.name || '';
|
||||
const reachcdName = option.reachcdName || '';
|
||||
const keyword = inputValue || '';
|
||||
|
||||
return label.indexOf(keyword) !== -1 || reachcdName.indexOf(keyword) !== -1;
|
||||
};
|
||||
const ennmOptions = ref([]);
|
||||
const handleRiverSelect = async (value: any) => {
|
||||
let res: any = await getEngInfoDropdown({ reachcd: value });
|
||||
if (res?.data?.length > 0) {
|
||||
res.data.forEach((item: any) => {
|
||||
item.value = item.stcd;
|
||||
item.label = item.ennm;
|
||||
});
|
||||
ennmOptions.value = res.data;
|
||||
} else {
|
||||
ennmOptions.value = [];
|
||||
}
|
||||
};
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVisible) => {
|
||||
newVisible => {
|
||||
if (newVisible) {
|
||||
initForm();
|
||||
handleRiverSelect('');
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
localLoading,
|
||||
localLoading
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -23,6 +23,8 @@ import BasicSearch from "@/components/BasicSearch/index.vue";
|
||||
|
||||
interface Props {
|
||||
handleAdd: () => void;
|
||||
riverOptions: any[];
|
||||
riverOptionsLoad: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@ -34,6 +36,7 @@ const emit = defineEmits<{
|
||||
|
||||
const initSearchData = {
|
||||
stnm: "",
|
||||
rvcd:""
|
||||
};
|
||||
|
||||
const searchData = ref<any>({ ...initSearchData });
|
||||
@ -47,13 +50,16 @@ const searchList: any = computed(() => [
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "Select",
|
||||
name: "hbrvcdName",
|
||||
label: "所在河段",
|
||||
type: 'Select',
|
||||
name: 'rvcd',
|
||||
label: '所在河段',
|
||||
width: 300,
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
allowClear: true
|
||||
},
|
||||
},
|
||||
options: props.riverOptions,
|
||||
loading: props.riverOptionsLoad
|
||||
}
|
||||
]);
|
||||
|
||||
const onSearchFinish = (values: any) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user