diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9136848 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.codeLens": true, + "java.test.editor.enableShortcuts": true, + "testing.gutterEnabled": true +} diff --git a/backend/src/main/java/com/yfd/platform/common/DynamicSQLMapper.java b/backend/src/main/java/com/yfd/platform/common/DynamicSQLMapper.java new file mode 100644 index 0000000..adefb25 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/common/DynamicSQLMapper.java @@ -0,0 +1,262 @@ +package com.yfd.platform.common; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +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; + +/** + * 通用动态 SQL Mapper。 + *

+ * 说明: + *

+ * 1. 该接口通过注解 SQL 直接执行动态查询,适用于“表名/联表 SQL/查询列”需要运行期决定的场景;
+ * 2. 带 {@code QueryWrapper} 的方法统一使用 {@code ew.customSqlSegment} 追加条件;
+ * 3. 传入的 {@code sql}/{@code select}/{@code tableName} 属于动态片段,请在上层确保来源可信,避免注入风险。 + */ +@Mapper +public interface DynamicSQLMapper { + + /** + * 分页执行完整 SQL(SQL 内可使用 map 参数)。 + */ + @Select({""}) + List> pageAllList(Page page, + @Param("sql") String sql, + @Param("map") Map map); + + /** + * 分页执行完整 SQL(泛型结果,依赖 MyBatis 映射规则)。 + */ + @Select({""}) + List pageAllListWithResultType(Page page, + @Param("sql") String sql, + @Param("map") Map map, + @Param("resultType") Class resultType); + + /** + * 不分页执行完整 SQL。 + */ + @Select({""}) + List> getAllList(@Param("sql") String sql, + @Param("map") Map map); + + /** + * 不分页执行完整 SQL(泛型结果,依赖 MyBatis 映射规则)。 + */ + @Select({""}) + List getAllListWithResultType(@Param("sql") String sql, + @Param("map") Map map, + @Param("resultType") Class resultType); + + /** + * 统计条数(含动态条件)。 + */ + @Select({ + "" + }) + Integer count(@Param("sql") String sql, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 统计条数(无动态条件)。 + */ + @Select({""}) + Integer countNoWrapper(@Param("sql") String sql); + + /** + * 分页查询(含动态条件)。 + */ + @Select({ + "" + }) + List> pageList(Page page, + @Param("select") String select, + @Param("sql") String sql, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 分页查询(含动态条件,泛型结果)。 + */ + @Select({ + "" + }) + List pageListWithResultType(Page page, + @Param("select") String select, + @Param("sql") String sql, + @Param("ew") QueryWrapper queryWrapper, + @Param("resultType") Class resultType); + + /** + * 分页查询(条件可选;方法名保持兼容)。 + */ + @Select({ + "" + }) + List> pageNoFilterList(Page page, + @Param("select") String select, + @Param("sql") String sql, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 分页查询(无动态条件)。 + */ + @Select({""}) + List> pageListNoWrapper(Page page, + @Param("select") String select, + @Param("sql") String sql); + + /** + * 非分页查询(含动态条件)。 + */ + @Select({ + "" + }) + List> getList(@Param("select") String select, + @Param("sql") String sql, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 非分页查询(含动态条件,历史命名保留;返回 Map 结构)。 + */ + @Select({ + "" + }) + List> getListWithResultType(@Param("select") String select, + @Param("sql") String sql, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 单表条件查询(直接传 where 条件片段)。 + */ + @Select({""}) + List> getSingleTableList(@Param("select") String select, + @Param("table") String table, + @Param("condition") String condition); + + /** + * 非分页查询(条件可选;方法名保持兼容)。 + */ + @Select({ + "" + }) + List> getNoFilterList(@Param("select") String select, + @Param("sql") String sql, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 非分页查询(无动态条件)。 + */ + @Select({""}) + List> getListNoWrapper(@Param("select") String select, + @Param("sql") String sql); + + /** + * 汇总查询(含动态条件),返回单行汇总结果。 + */ + @Select({ + "" + }) + Map totalSummary(@Param("select") String select, + @Param("sql") String sql, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 汇总查询(无动态条件)。 + */ + @Select({""}) + Map totalSummaryNoWrapper(@Param("select") String select, + @Param("sql") String sql); + + /** + * 单表汇总查询(含动态条件)。 + */ + @Select({ + "" + }) + Map totalSummarySingleTable(@Param("select") String select, + @Param("tableName") String tableName, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 单表汇总查询(无动态条件)。 + */ + @Select({""}) + Map totalSummaryNoWrapperSingleTable(@Param("select") String select, + @Param("tableName") String tableName); + + /** + * 获取表中最大排序值。 + */ + @Select({""}) + Integer getOrderIndexMax(@Param("tableName") String tableName); + + /** + * 按条件获取表中最大排序值。 + */ + @Select({ + "" + }) + Integer getOrderIndexMaxByParentId(@Param("tableName") String tableName, + @Param("ew") QueryWrapper queryWrapper); + + /** + * 获取分组字段列表。 + */ + @Select({""}) + List getGroup(@Param("info") String info, @Param("joinsql") String joinsql); +} diff --git a/backend/src/main/java/com/yfd/platform/utils/QueryWrapperUtil.java b/backend/src/main/java/com/yfd/platform/utils/QueryWrapperUtil.java new file mode 100644 index 0000000..7d17396 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/utils/QueryWrapperUtil.java @@ -0,0 +1,634 @@ +package com.yfd.platform.utils; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * 动态查询/动态 SQL 片段包装工具。 + *

+ * 主要用途: + *

+ * 1) 将前端 DevExtreme/Kendo 常见的 filter(数组嵌套 + and/or)解析为 MyBatis-Plus 的 {@link QueryWrapper} 条件;
+ * 2) 将 skip/take 转为 MyBatis-Plus 的 {@link Page};
+ * 3) 根据实体元数据把 Java 属性名映射为数据库列名({@link TableInfoHelper});
+ * 4) 生成 group by / order by 片段;
+ * 5) 反射提取对象字段值(用于调试/通用映射场景)。 + *

+ * 安全说明: + *

+ * - 本类只对“字段名/列名”做白名单校验({@link #SAFE_IDENTIFIER}),以避免把不安全字符串拼接进 SQL;
+ * - 条件值(value)通过 MyBatis-Plus 参数化方式参与查询,不做字符串拼接。 + */ +public class QueryWrapperUtil { + + /** + * Jackson JSON 解析器(用于把 DevExtreme/Kendo 风格的 filter 转为可遍历的 JsonNode)。 + */ + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /** + * SQL 标识符白名单:仅允许字母/数字/下划线,且支持 a.b 这种带前缀写法。 + * 用于防止把不安全字符串拼进 SQL(字段名/列名场景)。 + */ + private static final Pattern SAFE_IDENTIFIER = Pattern.compile("^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$"); + + /** + * 针对“前端传 null”但后端需要特定默认值的字段列表(历史兼容)。 + */ + public static final List FIELDNULL = new ArrayList<>(); + + /** + * 获取某些字段在 JSON 过滤条件为 null 时的“替代值”。 + * 例如某些 GUID 字段在前端传 null,需要用全 0 GUID 参与过滤。 + */ + public static String getJsonFieldNull(String columnName) { + return FIELDNULL.contains(columnName) ? "00000000-0000-0000-0000-000000000000" : null; + } + + /** + * 把 DevExtreme 常用的分页参数 skip/take 转为 MyBatis-Plus 的 Page。 + * current 从 1 开始:current = skip / take + 1 + */ + public static Page getPage(Integer skip, Integer take) { + if (take == null || take <= 0) { + return null; + } + int s = skip == null ? 0 : Math.max(0, skip); + Page page = new Page<>(); + page.setSize(take.longValue()); + page.setCurrent(s / take + 1L); + return page; + } + + /** + * 使用 {@link DataSourceRequest}(take/skip)构造分页对象。 + */ + public static Page getPage(DataSourceRequest dataSourceRequest) { + if (dataSourceRequest == null) { + return null; + } + return getPage(dataSourceRequest.getSkip(), dataSourceRequest.getTake()); + } + + /** + * 根据分组描述拼接 SQL 的 group by / order by 片段。 + *

+ * 返回值示例:{@code " group by dept_id, role_id order by dept_id asc, role_id desc"}。 + * + * @param dataSourceRequest 仅使用其 group 字段 + * @return 可直接拼接到 SQL 末尾的片段;无分组返回 null + */ + public static String getGroupBy(DataSourceRequest dataSourceRequest) { + if (dataSourceRequest == null) { + return null; + } + List groupDescriptorList = dataSourceRequest.getGroup(); + if (groupDescriptorList == null || groupDescriptorList.isEmpty()) { + return null; + } + + List groupByParts = new ArrayList<>(); + List orderByParts = new ArrayList<>(); + + for (GroupDescriptor groupingInfo : groupDescriptorList) { + if (groupingInfo == null) { + continue; + } + String selector = requireSafeIdentifier(groupingInfo.getField()); + groupByParts.add(selector); + if (groupingInfo.isNeedSortFlag()) { + String dir = groupingInfo.getDir(); + if ("desc".equalsIgnoreCase(dir)) { + orderByParts.add(selector + " desc"); + } else { + orderByParts.add(selector + " asc"); + } + } + } + + if (groupByParts.isEmpty()) { + return null; + } + + StringBuilder groupResult = new StringBuilder(); + groupResult.append(" group by ").append(String.join(", ", groupByParts)); + if (!orderByParts.isEmpty()) { + groupResult.append(" order by ").append(String.join(", ", orderByParts)); + } + return groupResult.toString(); + } + + /** + * 通过反射提取对象字段名与字段值,组装为 Map。 + *

+ * - 传入实例对象:返回所有字段(包含父类字段)的值;
+ * - 传入 Class:仅返回 static 字段的值。 + * + * @param obj 实例对象或 Class + * @return 字段名-字段值;无字段或入参为空时返回 null + */ + public static Map getFieldValues(Object obj) { + if (obj == null) { + return null; + } + + Class type = (obj instanceof Class clazz) ? clazz : obj.getClass(); + List fields = getAllFields(type); + if (fields.isEmpty()) { + return null; + } + + Map valueMap = new HashMap<>(); + boolean isClassObject = obj instanceof Class; + for (Field field : fields) { + field.setAccessible(true); + Object value; + try { + if (isClassObject) { + if (!Modifier.isStatic(field.getModifiers())) { + continue; + } + value = field.get(null); + } else { + value = field.get(obj); + } + } catch (IllegalAccessException e) { + continue; + } + valueMap.put(field.getName(), value); + } + + return valueMap.isEmpty() ? null : valueMap; + } + + /** + * 从 DevExtreme/Kendo 风格 filter 中提取指定字段的过滤值(多个值用逗号拼接)。 + *

+ * filter 形态通常是数组嵌套: + * ["name","contains","abc"] 或 [ ["a","=","1"], "and", ["b","=","2"] ] + */ + public static String getFilterFieldValue(Object filter, String fieldName) { + if (filter == null || fieldName == null || fieldName.isBlank()) { + return null; + } + JsonNode root = toJsonNode(filter); + if (root == null || root.isNull()) { + return null; + } + StringBuilder sb = new StringBuilder(); + collectFieldValues(root, fieldName, sb); + return sb.isEmpty() ? null : sb.toString(); + } + + /** + * 便捷方法:从 filter 构建一个新的 QueryWrapper。 + * + * @param filter 前端 filter(可传 JSON 字符串 / List / JsonNode / 任意对象) + * @param modelClass MyBatis-Plus 实体类(用于 property->column 映射) + * @param validateColumn 是否校验字段必须存在于实体(true 更安全) + */ + public static QueryWrapper buildWrapperFromDevExtremeFilter(Object filter, Class modelClass, boolean validateColumn) { + QueryWrapper wrapper = new QueryWrapper<>(); + return applyDevExtremeFilter(wrapper, filter, modelClass, validateColumn, null, null); + } + + /** + * 把 DevExtreme/Kendo 风格 filter 应用到已有的 QueryWrapper 上。 + * + * @param fieldsMap 允许自定义字段映射(前端字段->数据库列名),优先级高于实体映射 + * @param removeFields 字段黑名单(前端传了也忽略) + */ + public static QueryWrapper applyDevExtremeFilter( + QueryWrapper wrapper, + Object filter, + Class modelClass, + boolean validateColumn, + Map fieldsMap, + List removeFields + ) { + if (wrapper == null || filter == null) { + return wrapper; + } + JsonNode root = toJsonNode(filter); + if (root == null || root.isNull()) { + return wrapper; + } + + Set remove = removeFields == null ? Collections.emptySet() : new HashSet<>(removeFields); + Map mapped = fieldsMap == null ? Collections.emptyMap() : new HashMap<>(fieldsMap); + applyFilterNode(root, wrapper, mapped, remove, modelClass, validateColumn); + return wrapper; + } + + /** + * 根据实体属性名获取数据库列名(默认要求属性必须存在于实体元数据中)。 + */ + public static String getDBColumnName(Class modelClass, String property) { + return getDBColumnName(modelClass, property, true); + } + + /** + * 根据 MyBatis-Plus 元数据,把实体属性名映射为数据库列名。 + *

+ * validateColumn=false 时,找不到映射会直接返回传入值(同时做一次安全校验)。 + */ + public static String getDBColumnName(Class modelClass, String property, boolean validateColumn) { + if (modelClass == null) { + throw new IllegalArgumentException("modelClass is null"); + } + if (property == null || property.isBlank()) { + throw new IllegalArgumentException("property is blank"); + } + TableInfo tableInfo = TableInfoHelper.getTableInfo(modelClass); + if (tableInfo == null) { + if (validateColumn) { + throw new IllegalArgumentException("TableInfo not found for " + modelClass.getName()); + } + return requireSafeIdentifier(property); + } + if (property.equals(tableInfo.getKeyProperty())) { + return tableInfo.getKeyColumn(); + } + List fieldInfos = tableInfo.getFieldList().stream() + .filter(f -> property.equals(f.getProperty())) + .collect(Collectors.toList()); + if (!fieldInfos.isEmpty()) { + return fieldInfos.get(0).getColumn(); + } + if (validateColumn) { + throw new IllegalArgumentException(property + "列找不到"); + } + return requireSafeIdentifier(property); + } + + /** + * 获取实体对应的数据库表名(来自 MyBatis-Plus 元数据)。 + */ + public static String getDBTableName(Class modelClass) { + if (modelClass == null) { + throw new IllegalArgumentException("modelClass is null"); + } + TableInfo tableInfo = TableInfoHelper.getTableInfo(modelClass); + if (tableInfo == null) { + throw new IllegalArgumentException("TableInfo not found for " + modelClass.getName()); + } + return tableInfo.getTableName(); + } + + /** + * 驼峰转分隔符小写:myFieldName -> my_field_name(hyphenation 可传 "_" 或 "-")。 + */ + public static String toHyphenation(String src, String hyphenation) { + if (src == null || src.isEmpty()) { + return src; + } + String h = hyphenation == null ? "" : hyphenation; + StringBuilder sb = new StringBuilder(src); + int cnt = 0; + for (int i = 1; i < src.length(); i++) { + if (Character.isUpperCase(src.charAt(i))) { + sb.insert(i + cnt, h); + cnt += h.length(); + } + } + return sb.toString().toLowerCase(Locale.ROOT); + } + + /** + * 驼峰转下划线。 + */ + public static String toUnderline(String src) { + return toHyphenation(src, "_"); + } + + /** + * 把不同类型的 filter 入参转成 JsonNode,便于递归解析。 + */ + private static JsonNode toJsonNode(Object filter) { + try { + if (filter instanceof JsonNode n) { + return n; + } + if (filter instanceof String s) { + if (s.isBlank()) { + return null; + } + return MAPPER.readTree(s); + } + return MAPPER.valueToTree(filter); + } catch (Exception e) { + return null; + } + } + + /** + * 递归遍历 filter,提取目标字段的值(多个值逗号拼接)。 + */ + private static void collectFieldValues(JsonNode node, String fieldName, StringBuilder sb) { + if (node == null || node.isNull()) { + return; + } + if (node.isArray()) { + if (node.size() >= 3 && node.get(0).isTextual()) { + String field = node.get(0).asText(); + if (fieldName.equals(field)) { + JsonNode valueNode = node.get(2); + String value = valueNode == null || valueNode.isNull() ? getJsonFieldNull(field) : valueNode.asText(); + if (value != null && !value.isBlank()) { + if (!sb.isEmpty()) { + sb.append(','); + } + sb.append(value); + } + } + return; + } + for (int i = 0; i < node.size(); i++) { + collectFieldValues(node.get(i), fieldName, sb); + } + } + } + + /** + * 把 filter(JsonNode 数组结构)递归解析并应用到 QueryWrapper: + * - 单条件:["field","=",value] + * - 组合条件:[ cond1, "and"/"or", cond2, ... ] + */ + private static void applyFilterNode( + JsonNode node, + QueryWrapper wrapper, + Map fieldsMap, + Set removeFields, + Class modelClass, + boolean validateColumn + ) { + if (node == null || node.isNull()) { + return; + } + if (!node.isArray()) { + return; + } + + // 形如:["field","=",value] + if (node.size() >= 3 && node.get(0).isTextual()) { + applySingleCondition(node, wrapper, fieldsMap, removeFields, modelClass, validateColumn); + return; + } + + // 形如:[ [ ... ] ],拆一层继续解析 + if (node.size() == 1) { + applyFilterNode(node.get(0), wrapper, fieldsMap, removeFields, modelClass, validateColumn); + return; + } + + // 先解析第 0 个条件,然后按 and/or 递归追加后续条件 + applyFilterNode(node.get(0), wrapper, fieldsMap, removeFields, modelClass, validateColumn); + for (int i = 2; i < node.size(); i += 2) { + JsonNode opNode = node.get(i - 1); + JsonNode exprNode = node.get(i); + String op = opNode == null ? null : opNode.asText(); + if ("and".equalsIgnoreCase(op)) { + wrapper.and(w -> applyFilterNode(exprNode, w, fieldsMap, removeFields, modelClass, validateColumn)); + } else if ("or".equalsIgnoreCase(op)) { + wrapper.or(w -> applyFilterNode(exprNode, w, fieldsMap, removeFields, modelClass, validateColumn)); + } + } + } + + /** + * 处理单个条件:["field","operator",value] 并映射到 MyBatis-Plus 条件方法。 + */ + private static void applySingleCondition( + JsonNode conditionNode, + QueryWrapper wrapper, + Map fieldsMap, + Set removeFields, + Class modelClass, + boolean validateColumn + ) { + String field = conditionNode.get(0).asText(); + String operator = conditionNode.get(1).asText(); + JsonNode valueNode = conditionNode.get(2); + + if (removeFields.contains(field)) { + return; + } + + // 列名优先从 fieldsMap 取,其次从实体元数据映射;并做一次安全校验 + String databaseColumnName = fieldsMap.get(field); + if (databaseColumnName == null) { + databaseColumnName = getDBColumnName(modelClass, field, validateColumn); + } else { + databaseColumnName = requireSafeIdentifier(databaseColumnName); + } + + Object value = convertJsonValue(valueNode, field); + + // operator 统一为小写,兼容前端大小写差异 + String op = operator == null ? "" : operator.trim().toLowerCase(Locale.ROOT); + switch (op) { + case "contains" -> wrapper.like(databaseColumnName, value); + case "notcontains" -> wrapper.notLike(databaseColumnName, value); + case "startswith" -> wrapper.likeRight(databaseColumnName, value); + case "endswith" -> wrapper.likeLeft(databaseColumnName, value); + case "=" -> { + if (value == null) { + wrapper.isNull(databaseColumnName); + } else { + wrapper.eq(databaseColumnName, value); + } + } + case "!=", + "<>" -> { + if (value == null) { + wrapper.isNotNull(databaseColumnName); + } else { + wrapper.ne(databaseColumnName, value); + } + } + case "<" -> wrapper.lt(databaseColumnName, value); + case "<=" -> wrapper.le(databaseColumnName, value); + case ">" -> wrapper.gt(databaseColumnName, value); + case ">=" -> wrapper.ge(databaseColumnName, value); + } + } + + /** + * 将 JsonNode 转成 Java 值:Boolean/Number/String/数组对象字符串化。 + * null 时按 getJsonFieldNull 做兼容替换。 + */ + private static Object convertJsonValue(JsonNode valueNode, String columnName) { + if (valueNode == null || valueNode.isNull()) { + return getJsonFieldNull(columnName); + } + if (valueNode.isBoolean()) { + return valueNode.asBoolean(); + } + if (valueNode.isNumber()) { + return valueNode.numberValue(); + } + if (valueNode.isTextual()) { + return valueNode.asText(); + } + if (valueNode.isArray() || valueNode.isObject()) { + return valueNode.toString(); + } + return valueNode.asText(); + } + + /** + * 校验列名/字段名为安全标识符,避免拼接 SQL 注入。 + */ + private static String requireSafeIdentifier(String identifier) { + String id = identifier == null ? "" : identifier.trim(); + if (id.isEmpty() || !SAFE_IDENTIFIER.matcher(id).matches()) { + throw new IllegalArgumentException("Unsafe SQL identifier: " + identifier); + } + return id; + } + + /** + * 获取类及其父类(直到 Object)的所有声明字段。 + */ + private static List getAllFields(Class type) { + if (type == null) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + Class current = type; + while (current != null && current != Object.class) { + Field[] declared = current.getDeclaredFields(); + if (declared != null) { + Collections.addAll(result, declared); + } + current = current.getSuperclass(); + } + return result; + } + + /** + * 通用请求 DTO(用于承载分页与分组信息)。 + *

+ * 说明:该 DTO 仅用于内部/工具层做参数承载,不绑定特定前端框架。 + */ + public static class DataSourceRequest { + /** + * 每页条数(take)。 + */ + private int take; + /** + * 跳过条数(skip)。 + */ + private int skip; + /** + * 分组描述列表(用于生成 group by/order by)。 + */ + private List group; + + public DataSourceRequest() { + } + + public DataSourceRequest(int take, int skip, List group) { + this.take = take; + this.skip = skip; + this.group = group; + } + + public int getTake() { + return take; + } + + public void setTake(int take) { + this.take = take; + } + + public int getSkip() { + return skip; + } + + public void setSkip(int skip) { + this.skip = skip; + } + + public List getGroup() { + return group; + } + + public void setGroup(List group) { + this.group = group; + } + } + + /** + * 分组描述 DTO:字段名 + 排序方向 + 是否需要排序。 + */ + public static class GroupDescriptor { + /** + * 分组字段名(建议仅传安全标识符,如:dept_id / t.dept_id)。 + */ + private String field; + /** + * 排序方向:asc/desc(忽略大小写)。 + */ + private String dir; + /** + * 是否需要把该分组字段加入 order by。 + */ + private boolean needSortFlag; + + public GroupDescriptor() { + } + + public GroupDescriptor(String field, String dir, boolean needSortFlag) { + this.field = field; + this.dir = dir; + this.needSortFlag = needSortFlag; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public String getDir() { + return dir; + } + + public void setDir(String dir) { + this.dir = dir; + } + + public boolean isNeedSortFlag() { + return needSortFlag; + } + + public void setNeedSortFlag(boolean needSortFlag) { + this.needSortFlag = needSortFlag; + } + } + + static { + // 历史兼容:parentId 为 null 时用全 0 GUID 参与过滤/比较 + FIELDNULL.add("parentId"); + } +} diff --git a/backend/src/test/java/com/yfd/platform/utils/QueryWrapperUtilTest.java b/backend/src/test/java/com/yfd/platform/utils/QueryWrapperUtilTest.java new file mode 100644 index 0000000..1603821 --- /dev/null +++ b/backend/src/test/java/com/yfd/platform/utils/QueryWrapperUtilTest.java @@ -0,0 +1,120 @@ +package com.yfd.platform.utils; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class QueryWrapperUtilTest { + + @Test + void getPage_shouldBuildPageBySkipAndTake() { + Page page = QueryWrapperUtil.getPage(20, 10); + Assertions.assertNotNull(page); + Assertions.assertEquals(10L, page.getSize()); + Assertions.assertEquals(3L, page.getCurrent()); + } + + @Test + void getPage_shouldBuildPageFromDataSourceRequest() { + QueryWrapperUtil.DataSourceRequest req = new QueryWrapperUtil.DataSourceRequest(); + req.setSkip(0); + req.setTake(15); + + Page page = QueryWrapperUtil.getPage(req); + Assertions.assertNotNull(page); + Assertions.assertEquals(15L, page.getSize()); + Assertions.assertEquals(1L, page.getCurrent()); + } + + @Test + void getGroupBy_shouldBuildGroupAndOrderSegment() { + QueryWrapperUtil.GroupDescriptor g1 = new QueryWrapperUtil.GroupDescriptor("dept_id", "asc", true); + QueryWrapperUtil.GroupDescriptor g2 = new QueryWrapperUtil.GroupDescriptor("role_id", "desc", true); + QueryWrapperUtil.DataSourceRequest req = new QueryWrapperUtil.DataSourceRequest(10, 0, List.of(g1, g2)); + + String sql = QueryWrapperUtil.getGroupBy(req); + Assertions.assertEquals(" group by dept_id, role_id order by dept_id asc, role_id desc", sql); + } + + @Test + void getGroupBy_shouldRejectUnsafeIdentifier() { + QueryWrapperUtil.GroupDescriptor bad = new QueryWrapperUtil.GroupDescriptor("name;drop", "asc", true); + QueryWrapperUtil.DataSourceRequest req = new QueryWrapperUtil.DataSourceRequest(10, 0, List.of(bad)); + + Assertions.assertThrows(IllegalArgumentException.class, () -> QueryWrapperUtil.getGroupBy(req)); + } + + @Test + void getFieldValues_shouldReadInstanceAndSuperclassFields() { + Child child = new Child(); + Map values = QueryWrapperUtil.getFieldValues(child); + + Assertions.assertNotNull(values); + Assertions.assertEquals("parent", values.get("parentField")); + Assertions.assertEquals(7, values.get("childField")); + } + + @Test + void getFieldValues_shouldReadOnlyStaticWhenInputIsClass() { + Map values = QueryWrapperUtil.getFieldValues(StaticHolder.class); + + Assertions.assertNotNull(values); + Assertions.assertEquals("staticValue", values.get("S")); + Assertions.assertFalse(values.containsKey("normalField")); + } + + @Test + void getFilterFieldValue_shouldExtractNestedFieldValues() { + String filter = "[[\"name\",\"contains\",\"alice\"],\"and\",[[\"name\",\"=\",\"bob\"],\"or\",[\"age\",\">\",18]]]"; + String value = QueryWrapperUtil.getFilterFieldValue(filter, "name"); + Assertions.assertEquals("alice,bob", value); + } + + @Test + void applyDevExtremeFilter_shouldBuildExpectedSqlAndRespectRemoveFields() { + String filter = "[[\"username\",\"contains\",\"zhang\"],\"and\",[\"status\",\"=\",1],\"or\",[\"email\",\"=\",null]]"; + Map fieldsMap = Map.of( + "username", "USER_NAME", + "status", "STATUS", + "email", "EMAIL" + ); + QueryWrapper wrapper = new QueryWrapper<>(); + + QueryWrapperUtil.applyDevExtremeFilter( + wrapper, + filter, + Object.class, + false, + fieldsMap, + List.of("status") + ); + + String sqlSegment = wrapper.getSqlSegment(); + Assertions.assertNotNull(sqlSegment); + Assertions.assertTrue(sqlSegment.contains("USER_NAME")); + Assertions.assertFalse(sqlSegment.contains("STATUS")); + Assertions.assertTrue(sqlSegment.contains("EMAIL")); + } + + @Test + void toUnderline_shouldConvertCamelCase() { + Assertions.assertEquals("user_name", QueryWrapperUtil.toUnderline("userName")); + } + + private static class Parent { + private String parentField = "parent"; + } + + private static class Child extends Parent { + private int childField = 7; + } + + private static class StaticHolder { + private static String S = "staticValue"; + private String normalField = "normal"; + } +} + diff --git a/frontend/package.json b/frontend/package.json index 6cbbca7..4bbe9df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,9 +23,11 @@ "default-passive-events": "^2.0.0", "echarts": "^5.2.2", "element-plus": "^2.2.27", + "esri-leaflet": "^3.0.19", "js-base64": "^3.7.5", "js-cookie": "^3.0.1", "jsencrypt": "^3.3.2", + "leaflet": "^1.9.4", "nprogress": "^0.2.0", "path-browserify": "^1.0.1", "path-to-regexp": "^6.2.0", diff --git a/frontend/src/api/ecoFlow/index.ts b/frontend/src/api/ecoFlow/index.ts new file mode 100644 index 0000000..b8673bc --- /dev/null +++ b/frontend/src/api/ecoFlow/index.ts @@ -0,0 +1,22 @@ +import request from '@/utils/request'; +import type { EcoFlowStandard, EcoFlowQueryParams } from './types'; + +/** + * 获取生态流量达标情况数据 + * @param params 查询参数 + */ +export function getEcoFlowStandardData(params?: EcoFlowQueryParams): Promise<{ data: EcoFlowStandard[] }> { + return request({ + url: '/api/eco-flow/standard', + method: 'get', + params + }); +} + +export function getQgcStaticData(params?: EcoFlowQueryParams): Promise<{ data: EcoFlowStandard[] }> { + return request({ + url: '/eng/eq/interval/qgc/getQgcStaticData', + method: 'post', + data:params + }); +} diff --git a/frontend/src/api/ecoFlow/types.ts b/frontend/src/api/ecoFlow/types.ts new file mode 100644 index 0000000..4f8adf2 --- /dev/null +++ b/frontend/src/api/ecoFlow/types.ts @@ -0,0 +1,12 @@ +export interface EcoFlowStandard { + id: number; + baseName: string; + category: string; + currentRate: number; + lastYearRate: number; +} + +export interface EcoFlowQueryParams { + mode?: 'top' | 'left'; + baseId?: number; +} diff --git a/frontend/src/assets/legend/map-aishipinzhan.svg b/frontend/src/assets/legend/map-aishipinzhan.svg new file mode 100644 index 0000000..a6e8d91 --- /dev/null +++ b/frontend/src/assets/legend/map-aishipinzhan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dongwujiuzhuzhan.svg b/frontend/src/assets/legend/map-dongwujiuzhuzhan.svg new file mode 100644 index 0000000..a5e0851 --- /dev/null +++ b/frontend/src/assets/legend/map-dongwujiuzhuzhan.svg @@ -0,0 +1,17 @@ + + + map-dongwujiuzhuzhan + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dwsjhDieliangmenshi.svg b/frontend/src/assets/legend/map-dwsjhDieliangmenshi.svg new file mode 100644 index 0000000..15d19b0 --- /dev/null +++ b/frontend/src/assets/legend/map-dwsjhDieliangmenshi.svg @@ -0,0 +1,11 @@ + + + map-dwsjhDieliangmenshi + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dwsjhGeshuimuqiang.svg b/frontend/src/assets/legend/map-dwsjhGeshuimuqiang.svg new file mode 100644 index 0000000..4dc9b85 --- /dev/null +++ b/frontend/src/assets/legend/map-dwsjhGeshuimuqiang.svg @@ -0,0 +1,11 @@ + + + map-dwsjhGeshuimuqiang + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dwsjhQianzhidangqiang.svg b/frontend/src/assets/legend/map-dwsjhQianzhidangqiang.svg new file mode 100644 index 0000000..1a9eb87 --- /dev/null +++ b/frontend/src/assets/legend/map-dwsjhQianzhidangqiang.svg @@ -0,0 +1,11 @@ + + + map-dwsjhQianzhidangqiang + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dwsjhQita.svg b/frontend/src/assets/legend/map-dwsjhQita.svg new file mode 100644 index 0000000..120c81c --- /dev/null +++ b/frontend/src/assets/legend/map-dwsjhQita.svg @@ -0,0 +1,11 @@ + + + map-dwsjhQita + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dxsdzDabiaolv-1.svg b/frontend/src/assets/legend/map-dxsdzDabiaolv-1.svg new file mode 100644 index 0000000..049ae08 --- /dev/null +++ b/frontend/src/assets/legend/map-dxsdzDabiaolv-1.svg @@ -0,0 +1,16 @@ + + + map-dxsdzDabiaolv-1 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dxsdzDabiaolv-2.svg b/frontend/src/assets/legend/map-dxsdzDabiaolv-2.svg new file mode 100644 index 0000000..46af884 --- /dev/null +++ b/frontend/src/assets/legend/map-dxsdzDabiaolv-2.svg @@ -0,0 +1,16 @@ + + + map-dxsdzDabiaolv-2 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dxsdzDabiaolv-3.svg b/frontend/src/assets/legend/map-dxsdzDabiaolv-3.svg new file mode 100644 index 0000000..4c8adad --- /dev/null +++ b/frontend/src/assets/legend/map-dxsdzDabiaolv-3.svg @@ -0,0 +1,16 @@ + + + map-dxsdzDabiaolv-3 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dxsdzDabiaolv-4.svg b/frontend/src/assets/legend/map-dxsdzDabiaolv-4.svg new file mode 100644 index 0000000..0f6c92c --- /dev/null +++ b/frontend/src/assets/legend/map-dxsdzDabiaolv-4.svg @@ -0,0 +1,16 @@ + + + map-dxsdzDabiaolv-4 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dxsdzDabiaolv-5.svg b/frontend/src/assets/legend/map-dxsdzDabiaolv-5.svg new file mode 100644 index 0000000..eccff16 --- /dev/null +++ b/frontend/src/assets/legend/map-dxsdzDabiaolv-5.svg @@ -0,0 +1,16 @@ + + + map-dxsdzDabiaolv-5 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dxsdzGuihua.svg b/frontend/src/assets/legend/map-dxsdzGuihua.svg new file mode 100644 index 0000000..3c12f11 --- /dev/null +++ b/frontend/src/assets/legend/map-dxsdzGuihua.svg @@ -0,0 +1,23 @@ + + + map-dxsdzGuihua + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dxsdzYijian.svg b/frontend/src/assets/legend/map-dxsdzYijian.svg new file mode 100644 index 0000000..08efe9f --- /dev/null +++ b/frontend/src/assets/legend/map-dxsdzYijian.svg @@ -0,0 +1,23 @@ + + + map-dxsdzYijian + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dxsdzZaijian.svg b/frontend/src/assets/legend/map-dxsdzZaijian.svg new file mode 100644 index 0000000..3f78b1d --- /dev/null +++ b/frontend/src/assets/legend/map-dxsdzZaijian.svg @@ -0,0 +1,22 @@ + + + map-dxsdzZaijian + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-dzTushi.svg b/frontend/src/assets/legend/map-dzTushi.svg new file mode 100644 index 0000000..7607827 --- /dev/null +++ b/frontend/src/assets/legend/map-dzTushi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-gjszzBudabiao.svg b/frontend/src/assets/legend/map-gjszzBudabiao.svg new file mode 100644 index 0000000..04df4a9 --- /dev/null +++ b/frontend/src/assets/legend/map-gjszzBudabiao.svg @@ -0,0 +1,18 @@ + + + map-gjszzBudabiao + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-gjszzDabiao.svg b/frontend/src/assets/legend/map-gjszzDabiao.svg new file mode 100644 index 0000000..d1f5eea --- /dev/null +++ b/frontend/src/assets/legend/map-gjszzDabiao.svg @@ -0,0 +1,16 @@ + + + map-gjszzDabiao + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-gjszzWudabiaoshujv.svg b/frontend/src/assets/legend/map-gjszzWudabiaoshujv.svg new file mode 100644 index 0000000..ffde135 --- /dev/null +++ b/frontend/src/assets/legend/map-gjszzWudabiaoshujv.svg @@ -0,0 +1,16 @@ + + + map-gjszzWudabiaoshujv + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-gyssFangzirantongdao.svg b/frontend/src/assets/legend/map-gyssFangzirantongdao.svg new file mode 100644 index 0000000..c15fe80 --- /dev/null +++ b/frontend/src/assets/legend/map-gyssFangzirantongdao.svg @@ -0,0 +1,11 @@ + + + map-gyssFangzirantongdao + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-gyssJiyunyuxitong.svg b/frontend/src/assets/legend/map-gyssJiyunyuxitong.svg new file mode 100644 index 0000000..5fcd317 --- /dev/null +++ b/frontend/src/assets/legend/map-gyssJiyunyuxitong.svg @@ -0,0 +1,11 @@ + + + map-gyssJiyunyuxitong + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-gyssQita.svg b/frontend/src/assets/legend/map-gyssQita.svg new file mode 100644 index 0000000..a55f0e9 --- /dev/null +++ b/frontend/src/assets/legend/map-gyssQita.svg @@ -0,0 +1,12 @@ + + + map-gyssQita + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-gyssShengyuji.svg b/frontend/src/assets/legend/map-gyssShengyuji.svg new file mode 100644 index 0000000..f5befde --- /dev/null +++ b/frontend/src/assets/legend/map-gyssShengyuji.svg @@ -0,0 +1,11 @@ + + + map-gyssShengyuji + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-gyssYudao.svg b/frontend/src/assets/legend/map-gyssYudao.svg new file mode 100644 index 0000000..c4186a0 --- /dev/null +++ b/frontend/src/assets/legend/map-gyssYudao.svg @@ -0,0 +1,17 @@ + + + map-gyssYudao + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-liuliangzhan.svg b/frontend/src/assets/legend/map-liuliangzhan.svg new file mode 100644 index 0000000..4586a36 --- /dev/null +++ b/frontend/src/assets/legend/map-liuliangzhan.svg @@ -0,0 +1,16 @@ + + + map-liuliangzhan + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-qixidi.svg b/frontend/src/assets/legend/map-qixidi.svg new file mode 100644 index 0000000..7b5dc0e --- /dev/null +++ b/frontend/src/assets/legend/map-qixidi.svg @@ -0,0 +1,12 @@ + + + map-qixidi + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-rengongchanluanchang.svg b/frontend/src/assets/legend/map-rengongchanluanchang.svg new file mode 100644 index 0000000..01d9948 --- /dev/null +++ b/frontend/src/assets/legend/map-rengongchanluanchang.svg @@ -0,0 +1,16 @@ + + + map-rengongchanluanchang + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-rengongshuiwenjiance.svg b/frontend/src/assets/legend/map-rengongshuiwenjiance.svg new file mode 100644 index 0000000..312a85e --- /dev/null +++ b/frontend/src/assets/legend/map-rengongshuiwenjiance.svg @@ -0,0 +1,14 @@ + + + map-rengongshuiwenjiance + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-rgszzBudabiao.svg b/frontend/src/assets/legend/map-rgszzBudabiao.svg new file mode 100644 index 0000000..3b50627 --- /dev/null +++ b/frontend/src/assets/legend/map-rgszzBudabiao.svg @@ -0,0 +1,16 @@ + + + map-rgszzBudabiao + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-rgszzDabiao.svg b/frontend/src/assets/legend/map-rgszzDabiao.svg new file mode 100644 index 0000000..5732046 --- /dev/null +++ b/frontend/src/assets/legend/map-rgszzDabiao.svg @@ -0,0 +1,16 @@ + + + map-rgszzDabiao + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-rgszzWudabiaoshujv.svg b/frontend/src/assets/legend/map-rgszzWudabiaoshujv.svg new file mode 100644 index 0000000..2ea6057 --- /dev/null +++ b/frontend/src/assets/legend/map-rgszzWudabiaoshujv.svg @@ -0,0 +1,16 @@ + + + map-rgszzWudabiaoshujv + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-shipinzhan.svg b/frontend/src/assets/legend/map-shipinzhan.svg new file mode 100644 index 0000000..1aa526f --- /dev/null +++ b/frontend/src/assets/legend/map-shipinzhan.svg @@ -0,0 +1,16 @@ + + + map-shipinzhan + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-ssShuishengshengtai.svg b/frontend/src/assets/legend/map-ssShuishengshengtai.svg new file mode 100644 index 0000000..66c6bf8 --- /dev/null +++ b/frontend/src/assets/legend/map-ssShuishengshengtai.svg @@ -0,0 +1,14 @@ + + + map-ssShuishengshengtai + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-stllxfssFangliudong.svg b/frontend/src/assets/legend/map-stllxfssFangliudong.svg new file mode 100644 index 0000000..2a5af06 --- /dev/null +++ b/frontend/src/assets/legend/map-stllxfssFangliudong.svg @@ -0,0 +1,13 @@ + + + map-stllxfssFangliudong + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-stllxfssFangliuguan.svg b/frontend/src/assets/legend/map-stllxfssFangliuguan.svg new file mode 100644 index 0000000..dc474d0 --- /dev/null +++ b/frontend/src/assets/legend/map-stllxfssFangliuguan.svg @@ -0,0 +1,11 @@ + + + map-stllxfssFangliuguan + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-stllxfssFangliukong.svg b/frontend/src/assets/legend/map-stllxfssFangliukong.svg new file mode 100644 index 0000000..b6cf432 --- /dev/null +++ b/frontend/src/assets/legend/map-stllxfssFangliukong.svg @@ -0,0 +1,11 @@ + + + map-stllxfssFangliukong + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-stllxfssJihefadian.svg b/frontend/src/assets/legend/map-stllxfssJihefadian.svg new file mode 100644 index 0000000..e8662ba --- /dev/null +++ b/frontend/src/assets/legend/map-stllxfssJihefadian.svg @@ -0,0 +1,11 @@ + + + map-stllxfssJihefadian + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-stllxfssShengtaijizu.svg b/frontend/src/assets/legend/map-stllxfssShengtaijizu.svg new file mode 100644 index 0000000..cbc0922 --- /dev/null +++ b/frontend/src/assets/legend/map-stllxfssShengtaijizu.svg @@ -0,0 +1,11 @@ + + + map-stllxfssShengtaijizu + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-stllxfssXiehongzha.svg b/frontend/src/assets/legend/map-stllxfssXiehongzha.svg new file mode 100644 index 0000000..262f7ba --- /dev/null +++ b/frontend/src/assets/legend/map-stllxfssXiehongzha.svg @@ -0,0 +1,11 @@ + + + map-stllxfssXiehongzha + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-yeshengdongwu.svg b/frontend/src/assets/legend/map-yeshengdongwu.svg new file mode 100644 index 0000000..6efcded --- /dev/null +++ b/frontend/src/assets/legend/map-yeshengdongwu.svg @@ -0,0 +1,12 @@ + + + map-yeshengdongwu + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-yuleifenbu.svg b/frontend/src/assets/legend/map-yuleifenbu.svg new file mode 100644 index 0000000..b8eae26 --- /dev/null +++ b/frontend/src/assets/legend/map-yuleifenbu.svg @@ -0,0 +1,12 @@ + + + map-yuleifenbu + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-yuleizengzhizhan.svg b/frontend/src/assets/legend/map-yuleizengzhizhan.svg new file mode 100644 index 0000000..4476a07 --- /dev/null +++ b/frontend/src/assets/legend/map-yuleizengzhizhan.svg @@ -0,0 +1,15 @@ + + + map-yuleizengzhizhan + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zhenxizhiwuyuan.svg b/frontend/src/assets/legend/map-zhenxizhiwuyuan.svg new file mode 100644 index 0000000..da4e22e --- /dev/null +++ b/frontend/src/assets/legend/map-zhenxizhiwuyuan.svg @@ -0,0 +1,13 @@ + + + map-zhenxizhiwuyuan + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zidongshuiwenjiance.svg b/frontend/src/assets/legend/map-zidongshuiwenjiance.svg new file mode 100644 index 0000000..a0c16e7 --- /dev/null +++ b/frontend/src/assets/legend/map-zidongshuiwenjiance.svg @@ -0,0 +1,15 @@ + + + map-diwenshuijianhuan + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-ziranbaohuqu.svg b/frontend/src/assets/legend/map-ziranbaohuqu.svg new file mode 100644 index 0000000..0d089e0 --- /dev/null +++ b/frontend/src/assets/legend/map-ziranbaohuqu.svg @@ -0,0 +1,10 @@ + + + map-ziranbaohuqu + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zjszzBudabiao.svg b/frontend/src/assets/legend/map-zjszzBudabiao.svg new file mode 100644 index 0000000..5ad2ab5 --- /dev/null +++ b/frontend/src/assets/legend/map-zjszzBudabiao.svg @@ -0,0 +1,22 @@ + + + map-zjszzBudabiao + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zjszzDabiao.svg b/frontend/src/assets/legend/map-zjszzDabiao.svg new file mode 100644 index 0000000..8f50c4d --- /dev/null +++ b/frontend/src/assets/legend/map-zjszzDabiao.svg @@ -0,0 +1,20 @@ + + + map-zjszzDabiao + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zjszzWudabiaoshujv.svg b/frontend/src/assets/legend/map-zjszzWudabiaoshujv.svg new file mode 100644 index 0000000..f83185d --- /dev/null +++ b/frontend/src/assets/legend/map-zjszzWudabiaoshujv.svg @@ -0,0 +1,20 @@ + + + map-zjszzWudabiaoshujv + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zxsdzDabiaolv-1.svg b/frontend/src/assets/legend/map-zxsdzDabiaolv-1.svg new file mode 100644 index 0000000..d284446 --- /dev/null +++ b/frontend/src/assets/legend/map-zxsdzDabiaolv-1.svg @@ -0,0 +1,16 @@ + + + map-zxsdzDabiaolv-1 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zxsdzDabiaolv-2.svg b/frontend/src/assets/legend/map-zxsdzDabiaolv-2.svg new file mode 100644 index 0000000..39047b8 --- /dev/null +++ b/frontend/src/assets/legend/map-zxsdzDabiaolv-2.svg @@ -0,0 +1,16 @@ + + + map-zxsdzDabiaolv-2 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zxsdzDabiaolv-3.svg b/frontend/src/assets/legend/map-zxsdzDabiaolv-3.svg new file mode 100644 index 0000000..a8eeb61 --- /dev/null +++ b/frontend/src/assets/legend/map-zxsdzDabiaolv-3.svg @@ -0,0 +1,16 @@ + + + map-zxsdzDabiaolv-3 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zxsdzDabiaolv-4.svg b/frontend/src/assets/legend/map-zxsdzDabiaolv-4.svg new file mode 100644 index 0000000..83d95b5 --- /dev/null +++ b/frontend/src/assets/legend/map-zxsdzDabiaolv-4.svg @@ -0,0 +1,16 @@ + + + map-zxsdzDabiaolv-4 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zxsdzDabiaolv-5.svg b/frontend/src/assets/legend/map-zxsdzDabiaolv-5.svg new file mode 100644 index 0000000..4e7d951 --- /dev/null +++ b/frontend/src/assets/legend/map-zxsdzDabiaolv-5.svg @@ -0,0 +1,16 @@ + + + map-zxsdzDabiaolv-5 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zxsdzGuihua.svg b/frontend/src/assets/legend/map-zxsdzGuihua.svg new file mode 100644 index 0000000..353f0a1 --- /dev/null +++ b/frontend/src/assets/legend/map-zxsdzGuihua.svg @@ -0,0 +1,24 @@ + + + map-zxsdzGuihua + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zxsdzYijian.svg b/frontend/src/assets/legend/map-zxsdzYijian.svg new file mode 100644 index 0000000..264a762 --- /dev/null +++ b/frontend/src/assets/legend/map-zxsdzYijian.svg @@ -0,0 +1,22 @@ + + + map-zxsdzYijian + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/map-zxsdzZaijian.svg b/frontend/src/assets/legend/map-zxsdzZaijian.svg new file mode 100644 index 0000000..6109db9 --- /dev/null +++ b/frontend/src/assets/legend/map-zxsdzZaijian.svg @@ -0,0 +1,16 @@ + + + map-zxsdzZaijian + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/sdzGaojing0.svg b/frontend/src/assets/legend/sdzGaojing0.svg new file mode 100644 index 0000000..0e95569 --- /dev/null +++ b/frontend/src/assets/legend/sdzGaojing0.svg @@ -0,0 +1,16 @@ + + + sdzGaojing0 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/sdzGaojing1.svg b/frontend/src/assets/legend/sdzGaojing1.svg new file mode 100644 index 0000000..de5a2ef --- /dev/null +++ b/frontend/src/assets/legend/sdzGaojing1.svg @@ -0,0 +1,16 @@ + + + sdzGaojing1 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/sdzGaojing2.svg b/frontend/src/assets/legend/sdzGaojing2.svg new file mode 100644 index 0000000..735cda2 --- /dev/null +++ b/frontend/src/assets/legend/sdzGaojing2.svg @@ -0,0 +1,16 @@ + + + sdzGaojing2 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/sdzGaojing3.svg b/frontend/src/assets/legend/sdzGaojing3.svg new file mode 100644 index 0000000..8b69b64 --- /dev/null +++ b/frontend/src/assets/legend/sdzGaojing3.svg @@ -0,0 +1,16 @@ + + + sdzGaojing3 + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/yijiansheshi.svg b/frontend/src/assets/legend/yijiansheshi.svg new file mode 100644 index 0000000..3f0b8f1 --- /dev/null +++ b/frontend/src/assets/legend/yijiansheshi.svg @@ -0,0 +1,13 @@ + + + yijiansheshi + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/legend/zweijiansheshi.svg b/frontend/src/assets/legend/zweijiansheshi.svg new file mode 100644 index 0000000..67326ae --- /dev/null +++ b/frontend/src/assets/legend/zweijiansheshi.svg @@ -0,0 +1,13 @@ + + + zweijiansheshi + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/map/ddd1.svg b/frontend/src/assets/map/ddd1.svg new file mode 100644 index 0000000..2a93be2 --- /dev/null +++ b/frontend/src/assets/map/ddd1.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/map/ddd2.svg b/frontend/src/assets/map/ddd2.svg new file mode 100644 index 0000000..e1b3e46 --- /dev/null +++ b/frontend/src/assets/map/ddd2.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/map/ddd3.svg b/frontend/src/assets/map/ddd3.svg new file mode 100644 index 0000000..6200633 --- /dev/null +++ b/frontend/src/assets/map/ddd3.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/map/ddd4.svg b/frontend/src/assets/map/ddd4.svg new file mode 100644 index 0000000..46630e6 --- /dev/null +++ b/frontend/src/assets/map/ddd4.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/map/dzwj.svg b/frontend/src/assets/map/dzwj.svg new file mode 100644 index 0000000..4241d7b --- /dev/null +++ b/frontend/src/assets/map/dzwj.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/map/dzyj.svg b/frontend/src/assets/map/dzyj.svg new file mode 100644 index 0000000..4fc94d9 --- /dev/null +++ b/frontend/src/assets/map/dzyj.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/map/dzyjwjr.svg b/frontend/src/assets/map/dzyjwjr.svg new file mode 100644 index 0000000..0d8e31a --- /dev/null +++ b/frontend/src/assets/map/dzyjwjr.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/map/dzzj.svg b/frontend/src/assets/map/dzzj.svg new file mode 100644 index 0000000..4496420 --- /dev/null +++ b/frontend/src/assets/map/dzzj.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/components/RightDrawer/index.vue b/frontend/src/components/RightDrawer/index.vue index 523c545..e95d57e 100644 --- a/frontend/src/components/RightDrawer/index.vue +++ b/frontend/src/components/RightDrawer/index.vue @@ -20,7 +20,7 @@
-
+
@@ -47,6 +47,7 @@ const handleToggle = () => { diff --git a/frontend/src/components/gis/event-emitter.ts b/frontend/src/components/gis/event-emitter.ts new file mode 100644 index 0000000..c3e3bf2 --- /dev/null +++ b/frontend/src/components/gis/event-emitter.ts @@ -0,0 +1,115 @@ +type EventCallback = (...args: any[]) => void; + +export class EventEmitter { + private events: Map = new Map(); + + on(event: string, callback: EventCallback): this { + if (!this.events.has(event)) { + this.events.set(event, []); + } + this.events.get(event)!.push(callback); + return this; + } + + off(event: string, callback?: EventCallback): this { + if (!this.events.has(event)) return this; + + if (callback) { + const callbacks = this.events.get(event)!; + const index = callbacks.indexOf(callback); + if (index > -1) { + callbacks.splice(index, 1); + } + } else { + this.events.delete(event); + } + return this; + } + + emit(event: string, ...args: any[]): this { + if (this.events.has(event)) { + this.events.get(event)!.forEach(callback => { + try { + callback(...args); + } catch (error) { + console.error(`Event ${event} handler error:`, error); + } + }); + } + return this; + } + + once(event: string, callback: EventCallback): this { + const onceCallback: EventCallback = (...args: any[]) => { + callback(...args); + this.off(event, onceCallback); + }; + return this.on(event, onceCallback); + } + + removeAllListeners(): this { + this.events.clear(); + return this; + } + + listenerCount(event: string): number { + return this.events.get(event)?.length || 0; + } +}type EventCallback = (...args: any[]) => void; + +export class EventEmitter { + private events: Map = new Map(); + + on(event: string, callback: EventCallback): this { + if (!this.events.has(event)) { + this.events.set(event, []); + } + this.events.get(event)!.push(callback); + return this; + } + + off(event: string, callback?: EventCallback): this { + if (!this.events.has(event)) return this; + + if (callback) { + const callbacks = this.events.get(event)!; + const index = callbacks.indexOf(callback); + if (index > -1) { + callbacks.splice(index, 1); + } + } else { + this.events.delete(event); + } + return this; + } + + emit(event: string, ...args: any[]): this { + if (this.events.has(event)) { + this.events.get(event)!.forEach(callback => { + try { + callback(...args); + } catch (error) { + console.error(`Event ${event} handler error:`, error); + } + }); + } + return this; + } + + once(event: string, callback: EventCallback): this { + const onceCallback: EventCallback = (...args: any[]) => { + callback(...args); + this.off(event, onceCallback); + }; + return this.on(event, onceCallback); + } + + removeAllListeners(): this { + this.events.clear(); + return this; + } + + listenerCount(event: string): number { + return this.events.get(event)?.length || 0; + } +} \ No newline at end of file diff --git a/frontend/src/components/gis/map.class.ts b/frontend/src/components/gis/map.class.ts new file mode 100644 index 0000000..8cb657e --- /dev/null +++ b/frontend/src/components/gis/map.class.ts @@ -0,0 +1,29 @@ +import type { layer, MapInterface } from "./map.d"; +import { MapLeaflet } from "./map.leaflet"; + +interface MapClassInterface extends MapInterface { + layers: Map; + view: any; +} + +export const mapServerBaseUrl = localStorage.getItem("gisurl") || "http://210.72.227.199:18084/"; + +export class MapClass implements MapClassInterface { + layers: Map; + view: any; + private static instance: MapClass; + private service: MapInterface; + + static getInstance(): MapClass { + if (!this.instance) { + this.instance = new MapClass(); + } + return this.instance; + } + + constructor() { + this.layers = new Map(); + this.view = null; + this.service = new MapLeaflet(); + } +} diff --git a/frontend/src/components/gis/map.d.ts b/frontend/src/components/gis/map.d.ts new file mode 100644 index 0000000..8a54114 --- /dev/null +++ b/frontend/src/components/gis/map.d.ts @@ -0,0 +1,232 @@ +export type layerType = + | "markers" + | "tiledMap" + | "tiledMapQuery" + | "geoJson" + | "arcgisFeature" + | "dynamicMapLayer" + | "arcgisMap" + | "label"; +export type layerOption = { + opacity?: number; + data?: Array; + zIndex?: number; + clickEvent?: Function; + hoverEvent?: Function; + legendImages?: Array | null | undefined; + geoJsonLegend?: Array | null | undefined; + tiledMapType?: undefined | "superMap"; +}; + +export interface layer { + id: string; + key: string; + _layer?: any; + type?: layerType; + url?: string; + urlThd?: string; + label?: string; + thumbnail?: string; + visible?: boolean; + option?: layerOption; + tempobj?: any; + opacity?: any; +} + +export interface MapInterface { + getRainColor: any; + getRainColor1: any; + removeContentmarkers: any; + removeGanliumarkers: any; + addarcgisLayerWithNotLine: any + /** + * 地图初始化 + * @param container DOM容器 + * @return any 地图视图 + */ + init(container: React.ReactNode, rectangle?: any, center?: any): Promise; + + /** + *添加 图层 + */ + addLayer(layer: layer): any; + + /** + * 添加canvasMarker + */ + addCanvasMarker( + data: Array, + legend?: Array, + option?: { skipRemoveHtml?: boolean }, + flag?: any + ): any; + + invalidateSize(): void; + + getRain(data: any): void; + /** + * 添加地图缩放监听事件 + */ + addZoomEvent(func: Function, type?: string): void; + + /** + * 添加鼠标移动事件 + * @param func 事件 + */ + addMouseMoveEvent(func: Function): void; + + /** + * 添加鼠标点击事件 + * @param func 事件 + */ + addMouseClickEvent(func: Function): void; + + /** + * 定位 + * @param data + */ + fitBounds(data: any): void; + + /** + * 获取所有的图层 + */ + getLayers(): Map | null; + + /** + * 获取地图缩放级别 + */ + getMapZoom(): number; + + /** + * 放大 + */ + zoomEnlarge(): number; + + /** + * 缩小 + */ + zoomNarrow(): number; + + /** + * 获取当前地图视图的比例尺或者缩放级别 + */ + getMapZoomOrScale(): { scale?: number; zoom?: number }; + + /** + * 显示图层 + * @param layers + */ + showLayer(id: string): void; + + /** + * 隐藏图层 + * @param layers + */ + hideLayer(id: string): void; + + /** + * 设置地图位置 + * @param position + */ + setCenter(position: { lat: number; lng: number }, zoom?: any): void; + + /** + * 移除所有图层 + */ + removeAllLayer(): void; + + /** + * 移除所有站点标签 + */ + removeAllLabel(): void; + + /** + * 移除图层 + * @param layer + */ + removeLayer(layer: layer): void; + + /** + * 重新渲染图层数据 + */ + reRenderLayer( + key: string, + options?: { opacity?: number; data: Array; zIndex?: number } + ): Promise; + + /** + * 移除鼠标移动事件监听 + */ + removeMouseMoveEvent(): void; + + /** + * 移除监听事件 + */ + removeEvent(type: string, func: Function): void; + + /** + * 移除要素图层 + */ + removeFeatureLayer(): void; + + /** + * 移除水系图层 + */ + removedataSource(item: any): void; + + /** + * 移除鼠标点击事件监听 + */ + removeMouseClickEvent(): void; + + /** + * 缩放 + * out 放大 + * in 缩小 + */ + zoomToggle(type: "out" | "in"): void; + + /** + * 增加arcgis图层 + */ + addarcgisLayer(item: any, data?: any): void; + + /** + * 增加图层label + */ + addmapLabel(item: any, data: any, zoom: any): void; + addmapLine(item: any, data: any): void; + + /** + * 删除arcgis图层 + */ + removearcgisLayer(): void; + + /** + * 删除图层label + */ + removearcgisLabel(): void; + + removeLayermarkers(): void; + + removeRainLayer(): void; + + removeGroupLayer(): void; + + removeMapLayer(): void; + + /** + * 测距 + */ + handelstartDrawLine(): void; + + /** + * 测面积 + */ + handelstartDrawPolygon(): void; + + /** + * 清除 + */ + handelclearLayer(): void; +} diff --git a/frontend/src/components/gis/map.leaflet.ts b/frontend/src/components/gis/map.leaflet.ts new file mode 100644 index 0000000..c58b8f4 --- /dev/null +++ b/frontend/src/components/gis/map.leaflet.ts @@ -0,0 +1,373 @@ +import { layer, MapInterface } from "./map"; +import * as L from "leaflet"; +import * as esriLeaflet from "esri-leaflet"; +import { mapServerBaseUrl } from "./map.class"; +// @ts-ignore +import axios from "axios"; +// import "@/components/thematicMap/leaflet/leaflet.inflatable-markers-group.js" + +const tiledMapGroup = L.layerGroup(); +const chartMapGroup = L.layerGroup(); +const overlayGroup = L.layerGroup(); +const CENTER_positionCN: any = [37.072654, 86.171125]; // [26.072654, 107.171125]; //中心纬经度 中国 + +let boundCavansLayer: any = null; +let ganliulist:any = [] +export class MapLeaflet implements MapInterface { + map: any = null; + htmlMakerLayer: any = []; + minimumZoom = 7; + + temperatureMapObj: any = []; + + addLayer(layer: any): any { + // The WMTS URL + console.warn(layer) + switch (layer.type) { + case "tiledMap": + if (layer.url) { + let setzIndex = 1; + if (layer.zIndex && layer.zIndex != "" && layer.zIndex != "0") { + setzIndex = Number(layer.zIndex); + } + if (layer.title !== '干流河流') { + layer._layer = esriLeaflet + .dynamicMapLayer({ + url: layer.url, + opacity: layer.opacity, + zIndex: 999999999999 + }) + ?.addTo(this.map) + .bringToBack() + } else { + axios.get('http://210.72.227.199:18084/geoserver/cite/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=cite:glhl_epsg4326&maxFeatures=50&outputFormat=application%2Fjson') + .then((res) => { + res.data?.features.map((item: any, index: number) => { + const a = L.geoJSON(item, { + style: { + }, + }).addTo(this.map); + ganliulist.push(a) + }); + }) + } + } + return layer; + break + case "geoserver-wmts": + if (layer.url) { + if (layer.title == "地图瓦片图层") { + console.log('地图瓦片') + //增加遮罩 + axios.get('/zfile/qgcbuji/overlayLand3.json') + .then((res) => { + var landLayer = L.geoJson(res.data, { + style: { + opacity: 0, + //填充透明度 + fillOpacity: 1, + //填充颜色 + fillColor: '#ececec' + } + })?.addTo(overlayGroup).bringToBack(); + }) + axios.get('/zfile/qgcbuji/overlayOcean.json') + .then((res) => { + var oceanLayer = L.geoJson(res.data, { + style: { + opacity: 0, + //填充透明度 + fillOpacity: 1, + //填充颜色 + fillColor: '#8eccf1' + } + })?.addTo(overlayGroup).bringToFront(); + }) + overlayGroup?.addTo(this.map); + } + var optionobj = JSON.parse(layer.options); + if (optionobj) { + var ignLayer = L.tileLayer + .wmts(layer.url, { + tileMatrixSet: optionobj.tileMatrixSet, //"EPSG:3857_qgc_arcgistiles_l13_google", + tileSize: 256, //切片大小 + maxZoom: 13, + noWrap: true, + opacity: 1.01, + minZoom: 4, + layer: optionobj.layer, //"qgc_arcgistiles_l13_google", + }) + .addTo(this.map) + .bringToBack(); + layer._layer = ignLayer; + layer._layer.layerGroup = overlayGroup; + } + } + return layer; + case "arcgisMap": + if (layer.url) { + if (this.map) { + const mapLayer = esriLeaflet + .tiledMapLayer({ + url: layer.url, + format: "image/png", + maxNativeZoom: 17, + }) + .addTo(this.map) + .bringToBack(); + layer._layer = mapLayer; + } + } + return layer; + case "tiledMapQuery": + if (layer.url && layer.id) { + if (layer.title == "岛屿") { + layer.layers = "cite:nhqddt_epsg3857"; + } + layer._layer = L.tileLayer + .wms(layer.url, { + id: layer.id, + format: "image/png", + transparent: true, + zIndex: 9999, + layers: layer.layers, + // maxZoom:10, + }) + .addTo(this.map); + // .bringToBack(); + return layer; + } + return layer; + case "label": + if (layer.url) { + const tileLayer: any = L.tileLayer(layer.url, { + subdomains: ["0", "1", "2", "3", "4", "5", "6", "7"], + crossOrigin: "anonymous", + }); + const boundary = (providerName: any) => { + // return BoundaryCanvas(tileLayer, {}); + }; + let boundaryLayers; + if (layer.title == "行政标注") { + boundaryLayers = boundary(tileLayer._leaflet_id) + .addTo(this.map) + .bringToBack() + .setZIndex(10); + } else { + boundaryLayers = boundary(tileLayer._leaflet_id).addTo(this.map); + } + tileLayer.boundLayer = boundaryLayers; + layer._layer = boundaryLayers; + return layer; + } + break; + + case "markers": + this.getRain(layer); + break; + default: { + return; + } + } + } + + + /** + * 用于消除瓦片之间的缝隙(leaflet加载wmts在非整数缩放时瓦片间会有缝隙) + */ + inittiles() { + var originalInitTile = L.GridLayer.prototype._initTile; + L.GridLayer.include({ + _initTile: function (tile: any) { + originalInitTile.call(this, tile); + + var tileSize = this.getTileSize(); + + tile.style.width = tileSize.x + 2 + "px"; + tile.style.height = tileSize.y + 2 + "px"; + }, + }); + } + init(container: React.ReactNode, rectangle?: any): Promise { + try { + console.log("init初始化container", container); + this.inittiles(); + // var corner1 = L.latLng(0.712, -10.227), + // corner2 = L.latLng(70.774, 204.125), + + // var corner1 = L.latLng(-50.712, -10.227), + // corner2 = L.latLng(100.774, 204.125), + // var corner1 = L.latLng(20.712, 50.227), + // corner2 = L.latLng(51.774, 160.125), + var corner1 = L.latLng(20.712, 0), + corner2 = L.latLng(51.774, 179.125), + bounds = L.latLngBounds(corner1, corner2); + const map = L.map(container as any, { + preferCanvas: true, + minZoom: 4.4, + center: CENTER_positionCN, + zoom: 4.4, + maxZoom: 18, + zoomControl: false, + maxBounds: bounds, + zoomSnap: 0, + crs: L.CRS.EPSG3857, + }); + this.map = map; + + //遮罩 + var pNW = { lat: 85.0, lng: -90.0 }; + var pNE = { lat: 85.0, lng: 270.0 }; + var pSE = { lat: -45.0, lng: 270.0 }; + var pSW = { lat: -45.0, lng: -90.0 }; + var pArray = []; + pArray.push(pNW); + pArray.push(pSW); + pArray.push(pSE); + pArray.push(pNE); + pArray.push(pNW); + + let mapURL = + mapServerBaseUrl + "arcgis/rest/services/zh_boundary/MapServer/0/query"; + esriLeaflet + .query({ + url: mapURL, + }) + // .where(mapWhere) + .run((error: any, result: any, response: any) => { + if (response) { + // let test = arcgisToGeoJSON(response); + // let chinaList = test.features[0].geometry.coordinates; + // var boundsCounty = [ + // [0.0, -20.0], + // [276.0, -20.0], + // [276.0, 75.0], + // [0.0, 75.0], + // [0.0, -20.0], + // ]; + // console.log("chinaList", chinaList); + + // var geojsonRelt: any = { + // type: "Feature", + // properties: {}, + // geometry: { + // type: "MultiPolygon", + // coordinates: [[boundsCounty, ...chinaList]], + // }, + // }; + + // var chinaCavansLayer = L.geoJSON(geojsonRelt, { + // style: { + // color: "#8EA5BA", + // weight: 1, + // fillColor: "#ffffff", + // fillOpacity: 1, + // }, + // }); + //chinaCavansLayer.addTo(this.map).bringToBack().setZIndex(0); + } + }); + + let params = { + startTime: "2022-01-01 00:00:00", + }; + + + L.control + .scale({ maxWidth: 150, metric: true, imperial: false }) + .addTo(this.map); + this.map.on("zoomend", (e: any) => { + var scale = document.getElementsByClassName( + "leaflet-control-scale-line" + )[0].innerHTML; + var num = parseInt(scale); + + if (scale.includes("km")) { + console.log("比例尺:1:" + num * 1000 + "m"); + } else { + console.log("比例尺1:" + num + "m"); + } + console.log("scale", scale); + + console.log("当前缩放级别", e.target.getZoom()); + }); + console.log("rectangle", rectangle); + if (rectangle) { + if (rectangle[0] && rectangle[0][0] == 90) { + //this.map.setView([43.719771, 126.687641,], 9); + } else { + map.fitBounds(rectangle); + } + } + return Promise.resolve(map); + } catch (e) { + console.log("测试", e); + return Promise.reject({}); + } + } + + + removeLayer(layer: layer | any): void { + switch (layer.type) { + case "geoJson": + break; + default: + if (layer._layer) { + if (layer._layer.layerGroup) { + layer._layer.layerGroup.clearLayers(); + } + this.map?.off("zoom", layer._layer.func); + this.map && this.map.removeLayer(layer._layer); + } else if (layer) { + if (layer.layerGroup) { + layer.layerGroup.clearLayers(); + } + this.map?.off("zoom", layer.func); + this.map && this.map.removeLayer(layer); + let mapvalue = tiledMapGroup.getLayers(); + + for (let i = 0; i < mapvalue.length; i++) { + if (mapvalue[i].options.id == layer.id) { + tiledMapGroup.removeLayer(mapvalue[i]); + } + } + } + break; + } + } + + addboundCavansLayer = (list: any, bgStyle?: any) => { + if (boundCavansLayer && this.map.hasLayer(boundCavansLayer)) { + this.map.removeLayer(boundCavansLayer); + } + var boundsCounty = [ + [0.0, -20.0], + [176.0, -20.0], + [176.0, 75.0], + [0.0, 75.0], + [0.0, -20.0], + ]; + var geojsonRelt: any = { + type: "Feature", + properties: {}, + geometry: { + type: "MultiPolygon", + coordinates: [[boundsCounty, ...list]], + }, + }; + var boundCavansLayerRelt = L.geoJSON( + geojsonRelt, + bgStyle + ? { ...bgStyle } + : { + color: "transparent", + fillColor: "#0a2b4b", + fillOpacity: 0.9, + } + ); + boundCavansLayer = boundCavansLayerRelt; + console.warn(boundCavansLayerRelt.addTo) + boundCavansLayerRelt.addTo(this.map).bringToFront().setZIndex(99999999999); + }; + +} diff --git a/frontend/src/components/mapController/Calculate.vue b/frontend/src/components/mapController/Calculate.vue new file mode 100644 index 0000000..fd90ce6 --- /dev/null +++ b/frontend/src/components/mapController/Calculate.vue @@ -0,0 +1,61 @@ + + + diff --git a/frontend/src/components/mapController/LayerController.vue b/frontend/src/components/mapController/LayerController.vue new file mode 100644 index 0000000..de4748d --- /dev/null +++ b/frontend/src/components/mapController/LayerController.vue @@ -0,0 +1,133 @@ + + + diff --git a/frontend/src/components/mapController/index.vue b/frontend/src/components/mapController/index.vue index b89c4d8..d457bc8 100644 --- a/frontend/src/components/mapController/index.vue +++ b/frontend/src/components/mapController/index.vue @@ -1,84 +1,157 @@ \ No newline at end of file + diff --git a/frontend/src/components/mapLegend/LegendItem.vue b/frontend/src/components/mapLegend/LegendItem.vue new file mode 100644 index 0000000..453890c --- /dev/null +++ b/frontend/src/components/mapLegend/LegendItem.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/frontend/src/components/mapLegend/index.vue b/frontend/src/components/mapLegend/index.vue index add5b69..737c06c 100644 --- a/frontend/src/components/mapLegend/index.vue +++ b/frontend/src/components/mapLegend/index.vue @@ -8,20 +8,27 @@
- -
-
工程
+ +
+
{{ i.name }} {{ i.layerCode }}
-
-
-
- 大型水电站-已建 - +
@@ -29,13 +36,51 @@
@@ -69,10 +114,6 @@ const isOpen = ref(true); overflow-x: scroll; overflow-y: scroll; border-top: 1px solid #eeeeee; - overflow-y: hidden; - // &::-webkit-scrollbar { - // width: 0; - // } .legendGroup { display: inline-block; vertical-align: top; @@ -91,22 +132,8 @@ const isOpen = ref(true); filter: grayscale(100%); color: #00000073; } - .legendItem { - cursor: pointer; - margin-right: 10px; - padding: 4px 0; - min-height: 30px; - display: flex; - flex-direction: row; - .legendIcon { - width: 22px !important; - height: 22px !important; - background-color: red; - } - .legendIconTitle { - cursor: pointer; - flex: 1 1; - } + .disabled { + cursor: not-allowed !important; } } } diff --git a/frontend/src/modules/dianxingcuoshijieshao/index.vue b/frontend/src/modules/dianxingcuoshijieshao/index.vue index e722964..1f83a83 100644 --- a/frontend/src/modules/dianxingcuoshijieshao/index.vue +++ b/frontend/src/modules/dianxingcuoshijieshao/index.vue @@ -67,10 +67,7 @@ onMounted(() => { \ No newline at end of file diff --git a/frontend/src/modules/huanbaozdjcgzkzQK/index.vue b/frontend/src/modules/huanbaozdjcgzkzQK/index.vue index 5a15bc2..6ddbaf0 100644 --- a/frontend/src/modules/huanbaozdjcgzkzQK/index.vue +++ b/frontend/src/modules/huanbaozdjcgzkzQK/index.vue @@ -1,34 +1,28 @@ + \ No newline at end of file + diff --git a/frontend/src/modules/jidiSelectorMod.vue b/frontend/src/modules/jidiSelectorMod.vue index a7c7c0f..7208a2c 100644 --- a/frontend/src/modules/jidiSelectorMod.vue +++ b/frontend/src/modules/jidiSelectorMod.vue @@ -1,55 +1,14 @@ +