feat(仪表板): 增加世界地图及相关配置

This commit is contained in:
fit2cloud-chenyw 2022-07-21 13:41:03 +08:00
parent c731df25e1
commit 7f7cdeb0dd
22 changed files with 1315 additions and 30 deletions

View File

@ -54,6 +54,7 @@ public class ShiroServiceImpl implements ShiroService {
// 验证链接
filterChainDefinitionMap.put("/api/link/validate**", ANON);
filterChainDefinitionMap.put("/api/map/areaEntitys/**", ANON);
filterChainDefinitionMap.put("/api/map/globalEntitys/**", ANON);
filterChainDefinitionMap.put("/linkJump/queryPanelJumpInfo/**", ANON);
filterChainDefinitionMap.put("/linkJump/queryTargetPanelJumpInfo", ANON);

View File

@ -9,7 +9,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${geo.rootpath:file:/opt/dataease/data/feature/full/}")
@Value("${geo.rootpath:file:/opt/dataease/data/feature/}")
private String geoPath;
@Override

View File

@ -0,0 +1,20 @@
package io.dataease.listener;
import io.dataease.map.service.MapTransferService;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class GlobalMapTransferListener implements ApplicationListener<ApplicationReadyEvent> {
@Resource
private MapTransferService mapTransferService;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
mapTransferService.execute();
}
}

View File

@ -1,9 +1,9 @@
package io.dataease.map.api;
import io.dataease.map.dto.entity.AreaEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import io.dataease.map.dto.request.MapNodeRequest;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@ -19,4 +19,9 @@ public interface MapApi {
@GetMapping("/globalEntitys/{pcode}")
List<AreaEntity> globalEntitys(@PathVariable String pcode);
@PostMapping(value = "/saveMapNode", consumes = {"multipart/form-data"})
void saveMapNode(MapNodeRequest request, MultipartFile file) throws Exception;
@PostMapping("/delMapNode")
void delMapNode(MapNodeRequest request);
}

View File

@ -0,0 +1,20 @@
package io.dataease.map.dto.request;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
public class MapNodeRequest implements Serializable {
@ApiModelProperty("区域代码")
private String code;
@ApiModelProperty("区域名称")
private String name;
@ApiModelProperty("上级代码")
private String pcode;
@ApiModelProperty("上级级别")
private Integer plevel;
}

View File

@ -0,0 +1,21 @@
package io.dataease.map.dto.response;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
public class MapNodeReadReponse implements Serializable {
@ApiModelProperty("区域代码")
private String code;
@ApiModelProperty("区域名称")
private String name;
@ApiModelProperty("区域级别")
private Integer level;
@ApiModelProperty("上级区域")
private MapNodeReadReponse parent;
@ApiModelProperty("geoGson")
private String json;
}

View File

@ -1,15 +1,17 @@
package io.dataease.map.server;
import io.dataease.commons.utils.LogUtil;
import io.dataease.map.api.MapApi;
import io.dataease.map.dto.entity.AreaEntity;
import io.dataease.map.dto.request.MapNodeRequest;
import io.dataease.map.service.MapService;
import io.dataease.map.utils.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@RestController
@ -35,4 +37,15 @@ public class MapServer implements MapApi {
}
return mapService.entitysByPid(areaEntities, pcode);
}
@Override
public void saveMapNode(@RequestPart("request") MapNodeRequest request, @RequestPart(value = "file") MultipartFile file) throws Exception{
mapService.saveMapNode(request, file);
}
@Override
public void delMapNode(@RequestBody MapNodeRequest request) {
mapService.delMapNode(request);
}
}

View File

@ -1,14 +1,27 @@
package io.dataease.map.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.FileUtil;
import io.dataease.commons.exception.DEException;
import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.listener.util.CacheUtils;
import io.dataease.map.dto.entity.AreaEntity;
import io.dataease.map.dto.request.MapNodeRequest;
import io.dataease.map.utils.MapUtils;
import io.dataease.plugins.common.base.domain.AreaMappingGlobal;
import io.dataease.plugins.common.base.domain.AreaMappingGlobalExample;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
@Service
public class MapService {
@ -21,7 +34,7 @@ public class MapService {
return areaEntities;
}
@Cacheable("sys_map_areas")
@Cacheable("sys_map_areas_global")
public List<AreaEntity> globalEntities() {
List<AreaEntity> areaEntities = MapUtils.readGlobalAreaEntity();
return areaEntities;
@ -45,4 +58,293 @@ public class MapService {
}
public String generateAreaCode(String pCode) {
Long pValue = Long.parseLong(pCode);
MapService mapService = CommonBeanFactory.getBean(MapService.class);
List<AreaEntity> areaEntities = mapService.globalEntities();
List<AreaEntity> brothers = entitysByPid(areaEntities, pCode);
brothers.sort(Comparator.comparing(item -> Long.parseLong(item.getCode())));
AreaEntity lastBrother = brothers.get(brothers.size() - 1);
Long lastCode = Long.parseLong(lastBrother.getCode());
long areaCodeSuffix = lastCode - pValue;
int step = 0;
while (areaCodeSuffix % 10 == 0) {
step++;
areaCodeSuffix /= 10;
}
areaCodeSuffix++;
while (step > 0) {
areaCodeSuffix *= 10;
step--;
}
long targetCode = areaCodeSuffix + pValue;
return String.valueOf(targetCode);
}
private void delFileByNodes(List<AreaMappingGlobal> nodes, Integer pLevel) {
Set<String> sets = nodes.stream().flatMap(node -> codesByNode(node, pLevel).stream()).collect(Collectors.toSet());
sets.forEach(code -> {
String countryCode = code.substring(0, 3);
String path = dirPath + "/full/" + countryCode + "/" + code +"_full.json";
if (FileUtil.exist(path)) {
FileUtil.del(path);
}
});
}
private Set<String> codesByNode(AreaMappingGlobal node, Integer pLevel) {
Set<String> sets = new TreeSet<>();
if (pLevel == 2) {
if(StringUtils.isNotBlank(node.getProvinceCode())) sets.add(node.getProvinceCode());
if(StringUtils.isNotBlank(node.getCityCode())) sets.add(node.getCityCode());
if(StringUtils.isNotBlank(node.getCountyCode())) sets.add(node.getCountyCode());
} else if (pLevel == 3) {
if(StringUtils.isNotBlank(node.getCityCode())) sets.add(node.getCityCode());
if(StringUtils.isNotBlank(node.getCountyCode())) sets.add(node.getCountyCode());
} else if (pLevel == 4) {
if(StringUtils.isNotBlank(node.getCountyCode())) sets.add(node.getCountyCode());
} else {
if(StringUtils.isNotBlank(node.getCountryCode())) sets.add(node.getCountryCode());
if(StringUtils.isNotBlank(node.getProvinceCode())) sets.add(node.getProvinceCode());
if(StringUtils.isNotBlank(node.getCityCode())) sets.add(node.getCityCode());
if(StringUtils.isNotBlank(node.getCountyCode())) sets.add(node.getCountyCode());
}
return sets;
}
@Transactional
public void delMapNode(MapNodeRequest request) {
String pCode = request.getPcode();
Integer pLevel = request.getPlevel();
String code = request.getCode();
AreaMappingGlobalExample example = new AreaMappingGlobalExample();
AreaMappingGlobal curRoot = new AreaMappingGlobal();
List<AreaMappingGlobal> nodes = null;
if(pLevel == 0) {
nodes = MapUtils.selectByExample(example);
MapUtils.deleteByExample(example);
delFileByNodes(nodes, pLevel);
} else if (pLevel == 1) {
example.createCriteria().andCountryCodeEqualTo(code);
nodes = MapUtils.selectByExample(example);
MapUtils.deleteByExample(example);
delFileByNodes(nodes, pLevel);
} else if (pLevel == 2) {
example.createCriteria().andCountryCodeEqualTo(pCode).andProvinceCodeEqualTo(code);
nodes = MapUtils.selectByExample(example);
MapUtils.deleteByExample(example);
example.clear();
example.createCriteria().andCountryCodeEqualTo(pCode);
if (!MapUtils.exampleExist(example) && CollectionUtil.isNotEmpty(nodes)) {
AreaMappingGlobal template = nodes.get(0);
curRoot.setCountryCode(template.getCountryCode());
curRoot.setCountryName(template.getCountryName());
MapUtils.addNode(curRoot);
}
delFileByNodes(nodes, pLevel);
} else if (pLevel == 3) {
example.createCriteria().andProvinceCodeEqualTo(pCode).andCityCodeEqualTo(code);
nodes = MapUtils.selectByExample(example);
MapUtils.deleteByExample(example);
example.clear();
example.createCriteria().andProvinceCodeEqualTo(pCode);
if (!MapUtils.exampleExist(example) && CollectionUtil.isNotEmpty(nodes)) {
AreaMappingGlobal template = nodes.get(0);
curRoot.setCountryCode(template.getCountryCode());
curRoot.setCountryName(template.getCountryName());
curRoot.setProvinceCode(template.getProvinceCode());
curRoot.setProvinceName(template.getProvinceName());
MapUtils.addNode(curRoot);
}
delFileByNodes(nodes, pLevel);
} else if (pLevel == 4) {
example.createCriteria().andCityCodeEqualTo(pCode).andCountyCodeEqualTo(code);
nodes = MapUtils.selectByExample(example);
MapUtils.deleteByExample(example);
example.clear();
example.createCriteria().andProvinceCodeEqualTo(pCode);
if (!MapUtils.exampleExist(example) && CollectionUtil.isNotEmpty(nodes)) {
AreaMappingGlobal template = nodes.get(0);
curRoot.setCountryCode(template.getCountryCode());
curRoot.setCountryName(template.getCountryName());
curRoot.setProvinceCode(template.getProvinceCode());
curRoot.setProvinceName(template.getProvinceName());
curRoot.setCityCode(template.getCityCode());
curRoot.setCityName(template.getCityName());
MapUtils.addNode(curRoot);
}
delFileByNodes(nodes, pLevel);
}
CacheUtils.removeAll("sys_map_areas_global");
}
private void validateFile(MultipartFile file) {
long size = file.getSize();
String name = file.getName();
if (size / 1024 / 1024 > 30) {
DEException.throwException("large file that exceed 30M is not supported");
}
if (!StringUtils.endsWith(name, ".json")) {
DEException.throwException("only json file supported");
}
}
@Transactional
public void saveMapNode(MapNodeRequest request, MultipartFile file) throws Exception{
validateFile(file);
String pCode = request.getPcode();
Integer plevel = request.getPlevel();
String code = request.getCode();
if(StringUtils.isBlank(code)) {
String newAreaCode = generateAreaCode(pCode);
request.setCode(newAreaCode);
}
AreaMappingGlobalExample example = new AreaMappingGlobalExample();
if (plevel == 1) {
example.createCriteria().andCountryCodeEqualTo(code);
}
else if (plevel == 2) {
example.createCriteria().andCountryCodeEqualTo(pCode).andProvinceCodeEqualTo(code);
}
else if (plevel == 3) {
example.createCriteria().andProvinceCodeEqualTo(pCode).andCityCodeEqualTo(code);
}else if (plevel == 4) {
example.createCriteria().andCityCodeEqualTo(pCode).andCountyCodeEqualTo(code);
} else {
DEException.throwException("只支持3级行政区");
}
List<AreaMappingGlobal> lists = MapUtils.selectByExample(example);
if (CollectionUtil.isNotEmpty(lists)) {
DEException.throwException("区域代码已存在");
}
example.clear();
AreaMappingGlobalExample pExample = new AreaMappingGlobalExample();
if (plevel == 1) {
pExample.createCriteria().andCountryCodeIsNull().andProvinceCodeIsNull().andCityCodeIsNull().andCountyCodeIsNull();
List<AreaMappingGlobal> existLists = MapUtils.selectByExample(pExample);
if (CollectionUtil.isNotEmpty(existLists)) {
AreaMappingGlobal node = existLists.get(0);
node.setCountryCode(code);
node.setCountryName(request.getName());
MapUtils.update(node);
}else {
AreaMappingGlobal node = new AreaMappingGlobal();
node.setCountryCode(code);
node.setCountryName(request.getName());
MapUtils.addNode(node);
}
}
else if (plevel == 2) {
pExample.createCriteria().andCountryCodeEqualTo(pCode).andProvinceCodeIsNull().andCityCodeIsNull().andCountyCodeIsNull();
List<AreaMappingGlobal> existLists = MapUtils.selectByExample(pExample);
if (CollectionUtil.isNotEmpty(existLists)) {
AreaMappingGlobal node = existLists.get(0);
node.setProvinceCode(code);
node.setProvinceName(request.getName());
MapUtils.update(node);
}else {
AreaMappingGlobal country = country(pCode);
AreaMappingGlobal node = new AreaMappingGlobal();
node.setCountryCode(pCode);
node.setCountryName(country.getCountryName());
node.setProvinceCode(code);
node.setProvinceName(request.getName());
MapUtils.addNode(node);
}
}
else if (plevel == 3) {
pExample.createCriteria().andProvinceCodeEqualTo(pCode).andCityCodeIsNull();
List<AreaMappingGlobal> existLists = MapUtils.selectByExample(pExample);
if (CollectionUtil.isNotEmpty(existLists)) {
AreaMappingGlobal node = existLists.get(0);
node.setCityCode(code);
node.setCityName(request.getName());
MapUtils.update(node);
}else {
AreaMappingGlobal province = province(pCode);
AreaMappingGlobal node = new AreaMappingGlobal();
node.setCountryCode(province.getCountryCode());
node.setCountryName(province.getCountryName());
node.setProvinceCode(pCode);
node.setProvinceName(province.getProvinceName());
node.setCityCode(code);
node.setCityName(request.getName());
MapUtils.addNode(node);
}
} else if (plevel == 4) {
pExample.createCriteria().andCountryCodeEqualTo(pCode).andCountyCodeIsNull();
List<AreaMappingGlobal> existLists = MapUtils.selectByExample(pExample);
if (CollectionUtil.isNotEmpty(existLists)) {
AreaMappingGlobal node = existLists.get(0);
node.setCountyCode(code);
node.setCountyName(request.getName());
MapUtils.update(node);
}else {
AreaMappingGlobal city = city(pCode);
AreaMappingGlobal node = new AreaMappingGlobal();
node.setCountryCode(city.getCountryCode());
node.setCountryName(city.getCountryName());
node.setProvinceCode(city.getProvinceCode());
node.setProvinceName(city.getProvinceName());
node.setCityCode(pCode);
node.setCityName(request.getName());
node.setCountyCode(code);
node.setCountyName(request.getName());
MapUtils.addNode(node);
}
} else {
DEException.throwException("只支持3级行政区");
}
uploadMapFile(file, code);
CacheUtils.removeAll("sys_map_areas_global");
}
public void uploadMapFile(MultipartFile file, String areaCode) throws Exception{
String dir = dirPath + "full/";
File fileDir = new File(dir);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
String countryCode = areaCode.substring(0, 3);
String targetPath = dir + countryCode + "/" + areaCode+"_full.json";
File target = new File(targetPath);
file.transferTo(target);
}
private AreaMappingGlobal country(String pCode) {
AreaMappingGlobalExample pExample = new AreaMappingGlobalExample();
pExample.createCriteria().andCountryCodeEqualTo(pCode);
return MapUtils.selectByExample(pExample).stream().findFirst().get();
}
private AreaMappingGlobal province(String pCode) {
AreaMappingGlobalExample pExample = new AreaMappingGlobalExample();
pExample.createCriteria().andProvinceCodeEqualTo(pCode);
return MapUtils.selectByExample(pExample).stream().findFirst().get();
}
private AreaMappingGlobal city(String pCode) {
AreaMappingGlobalExample pExample = new AreaMappingGlobalExample();
pExample.createCriteria().andCityCodeEqualTo(pCode);
return MapUtils.selectByExample(pExample).stream().findFirst().get();
}
}

View File

@ -0,0 +1,107 @@
package io.dataease.map.service;
import cn.hutool.core.io.FileUtil;
import com.google.gson.Gson;
import io.dataease.plugins.common.base.domain.ChartView;
import io.dataease.plugins.common.base.domain.ChartViewExample;
import io.dataease.plugins.common.base.domain.ChartViewWithBLOBs;
import io.dataease.plugins.common.base.mapper.ChartViewMapper;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class MapTransferService {
@Value("${geo.rootpath:/opt/dataease/data/feature/}")
private String geoPath;
private static final List<String> MATCH_TYPES = new ArrayList<>();
private static final Gson gson = new Gson();
private static final String AREA_CODE_KEY = "areaCode";
private static final String GLOBAL_CHINA_PREFIX = "156";
private static final String FILE_SEPARATOR = "/";
private static final String FULL_KEY = "full";
private static final String BORDER_KEY = "border";
private static final String FULL_FILE_SUFFIX = "_full.json";
@PostConstruct
public void init() {
MATCH_TYPES.add("map");
MATCH_TYPES.add("buddle-map");
}
@Resource
private ChartViewMapper chartViewMapper;
public void execute() {
ChartViewExample example = new ChartViewExample();
List<ChartViewWithBLOBs> chartViews = chartViewMapper.selectByExampleWithBLOBs(example);
chartViews.forEach(view -> {
if (typeMatch(view) && StringUtils.isNotBlank(view.getCustomAttr())) {
Map<String, Object> customAttrMap = convert(view.getCustomAttr());
if (customMatch(customAttrMap)) {
view.setCustomAttr(gson.toJson(customAttrMap));
chartViewMapper.updateByPrimaryKeyWithBLOBs(view);
}
}
});
moveMapFiles();
}
public void moveMapFiles() {
String chinaRootPath = geoPath + FULL_KEY + FILE_SEPARATOR;
File chinaRootDir = new File(chinaRootPath);
File[] files = chinaRootDir.listFiles();
Map<String, List<File>> listMap = Arrays.stream(files).filter(FileUtil::isFile).collect(Collectors.groupingBy(this::fileType));
if (ObjectUtils.isEmpty(listMap)) return;
moveFiles(listMap, BORDER_KEY);
moveFiles(listMap, FULL_KEY);
}
private void moveFiles(Map<String, List<File>> listMap, String fileType) {
String dirPath = geoPath + fileType + FILE_SEPARATOR;
Optional.ofNullable(listMap.get(fileType)).ifPresent(files -> {
files.forEach(file -> {
String fileName = file.getName();
String newFilePath = dirPath + GLOBAL_CHINA_PREFIX + FILE_SEPARATOR + GLOBAL_CHINA_PREFIX + fileName;
FileUtil.move(file, new File(newFilePath), true);
});
});
}
private String fileType(File file) {
return file.getName().endsWith(FULL_FILE_SUFFIX) ? FULL_KEY : BORDER_KEY;
}
private Boolean typeMatch(ChartView chartView) {
return MATCH_TYPES.stream().anyMatch(type -> StringUtils.equals(type, chartView.getType()));
}
private Map<String, Object> convert(String customJson) {
return gson.fromJson(customJson, Map.class);
}
private Boolean customMatch(Map<String, Object> customAttrMap) {
Object codeObj = null;
if((codeObj = customAttrMap.get(AREA_CODE_KEY)) != null) {
String code = codeObj.toString();
boolean matych = code.length() == 6;
customAttrMap.put(AREA_CODE_KEY, GLOBAL_CHINA_PREFIX + code);
return matych;
}
return false;
}
}

View File

@ -46,6 +46,15 @@ public class MapUtils {
return mappingGlobals;
}
public static List<AreaMappingGlobal> selectByExample(AreaMappingGlobalExample example) {
List<AreaMappingGlobal> mappingGlobals = areaMappingGlobalMapper.selectByExample(Optional.ofNullable(example).orElse(new AreaMappingGlobalExample()));
return mappingGlobals;
}
public static Boolean exampleExist(AreaMappingGlobalExample example) {
return areaMappingGlobalMapper.countByExample(example) > 0;
}
public static List<Map<String, Object>> readCodeList() {
AreaMappingExample example = new AreaMappingExample();
List<AreaMapping> areaMappings = areaMappingMapper.selectByExample(example);
@ -75,30 +84,25 @@ public class MapUtils {
String city_code = map.getCityCode();
String county_code = map.getCountyCode();
// 是否是跨级直辖
Boolean isCrossLevel = StrUtil.equals(province_code, city_code)
&& !StrUtil.equals(province_code, "156710000");
Boolean isCrossLevel = StrUtil.equals(province_code, city_code) && !StrUtil.equals(province_code, "156710000");
if (!countryMap.containsKey(country_code)) {
String country_name = map.getCountryName();
AreaEntity child = AreaEntity.builder().code(country_code).name(country_name)
.pcode(globalRoot.getCode()).build();
AreaEntity child = AreaEntity.builder().code(country_code).name(country_name).pcode(globalRoot.getCode()).build();
countryMap.put(country_code, child);
globalRoot.addChild(child);
}
AreaEntity currentCountry = countryMap.get(country_code);
String province_name = map.getProvinceName();
if (!provinceMap.containsKey(province_code)) {
AreaEntity child = AreaEntity.builder().code(province_code).name(province_name)
.pcode(currentCountry.getCode()).build();
if (StringUtils.isNotBlank(province_code) && !provinceMap.containsKey(province_code)) {
AreaEntity currentCountry = countryMap.get(country_code);
String province_name = map.getProvinceName();
AreaEntity child = AreaEntity.builder().code(province_code).name(province_name).pcode(currentCountry.getCode()).build();
provinceMap.put(province_code, child);
currentCountry.addChild(child);
}
// 当前省
AreaEntity currentProvince = provinceMap.get(province_code);
String city_name = map.getCityName();
if (isCrossLevel) {
@ -106,8 +110,9 @@ public class MapUtils {
city_name = map.getCountyName();
}
if (StringUtils.isNotBlank(city_code) && !cityMap.containsKey(city_code)) {
AreaEntity child = AreaEntity.builder().code(city_code).name(city_name).pcode(currentProvince.getCode())
.build();
// 当前省
AreaEntity currentProvince = provinceMap.get(province_code);
AreaEntity child = AreaEntity.builder().code(city_code).name(city_name).pcode(currentProvince.getCode()).build();
cityMap.put(city_code, child);
currentProvince.addChild(child);
}
@ -199,4 +204,16 @@ public class MapUtils {
return AreaEntity.builder().code("000000000").name("地球村").build();
}
public static void addNode(AreaMappingGlobal node) {
areaMappingGlobalMapper.insert(node);
}
public static void update(AreaMappingGlobal node) {
areaMappingGlobalMapper.updateByPrimaryKey(node);
}
public static void deleteByExample(AreaMappingGlobalExample example) {
areaMappingGlobalMapper.deleteByExample(Optional.ofNullable(example).orElse(new AreaMappingGlobalExample()));
}
}

View File

@ -36,4 +36,31 @@ ADD COLUMN `phone_prefix` varchar(255) NULL COMMENT '手机号前缀' AFTER `sub
INSERT INTO `my_plugin` (`name`, `store`, `free`, `cost`, `category`, `descript`, `version`, `creator`, `load_mybatis`,
`install_time`, `module_name`, `ds_type`)
VALUES ('Mongo 数据源插件', 'default', '0', '0', 'datasource', 'Mongo 数据源插件', '1.0-SNAPSHOT', 'DATAEASE', '0',
'1650765903630', 'mongo-backend', 'mongobi');
'1650765903630', 'mongo-backend', 'mongobi');
DROP TABLE IF EXISTS `area_mapping_global`;
CREATE TABLE `area_mapping_global` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`country_code` varchar(255) DEFAULT NULL COMMENT '国家代码',
`country_name` varchar(255) DEFAULT NULL COMMENT '国家名称',
`province_name` varchar(255) DEFAULT NULL COMMENT '省名称',
`province_code` varchar(255) DEFAULT NULL COMMENT '省代码',
`city_name` varchar(255) DEFAULT NULL COMMENT '市名称',
`city_code` varchar(255) DEFAULT NULL COMMENT '市代码',
`county_name` varchar(255) DEFAULT NULL COMMENT '县名称',
`county_code` varchar(255) DEFAULT NULL COMMENT '县代码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
BEGIN;
insert into area_mapping_global (province_code, province_name, city_code, city_name, county_code, county_name)
select province_code, province_name, city_code, city_name, county_code, county_name from area_mapping;
update area_mapping_global set
country_code = '156100000',
country_name = '中华人民共和国',
province_code = concat('156', province_code),
city_code = concat('156', city_code),
county_code = concat('156', county_code);
COMMIT;

View File

@ -138,6 +138,15 @@
diskPersistent="false"
/>
<cache
name="sys_map_areas_global"
eternal="true"
maxElementsInMemory="100"
maxElementsOnDisk="3000"
overflowToDisk="true"
diskPersistent="false"
/>
<!--用户授权数据源缓存-->
<cache

View File

@ -62,6 +62,7 @@
"vue-friendly-iframe": "^0.20.0",
"vue-fullscreen": "^2.5.2",
"vue-i18n": "7.3.2",
"vue-json-views": "^1.3.0",
"vue-proportion-directive": "^1.1.0",
"vue-router": "3.0.6",
"vue-to-pdf": "^1.0.0",

View File

@ -2,7 +2,7 @@ import request from '@/utils/request'
export const areaMapping = () => {
return request({
url: '/api/map/areaEntitys/0',
url: '/api/map/globalEntitys/0',
method: 'get',
loading: true
})
@ -17,9 +17,28 @@ export const globalMapping = () => {
}
export function geoJson(areaCode) {
const countryCode = areaCode.substring(0, 3)
return request({
url: '/geo/' + areaCode + '_full.json',
url: '/geo/full/' + countryCode + '/' + areaCode + '_full.json',
method: 'get',
loading: true
})
}
export function saveMap(data) {
return request({
url: '/api/map/saveMapNode',
method: 'post',
loading: true,
data
})
}
export function removeMap(data) {
return request({
url: '/api/map/delMapNode',
method: 'post',
loading: true,
data
})
}

View File

@ -475,7 +475,8 @@ export default {
ldap: 'LDAP Setting',
oidc: 'OIDC Setting',
theme: 'Theme Setting',
cas: 'CAS Setting'
cas: 'CAS Setting',
map: 'MAP Setting'
},
license: {
i18n_no_license_record: 'No License Record',
@ -2130,5 +2131,19 @@ export default {
},
plugin_style: {
border: 'Border'
},
map_setting: {
area_level: 'Area Level',
area_code: 'Area Code',
please_input: 'please key',
parent_area: 'Parent Area',
area_code_tip: 'The format of area code is 9 digits',
area_name: 'Area Name',
parent_name: 'Parent Area',
geo_json: 'Geo Json',
fileplaceholder: 'Please upload the JSON format coordinate file',
delete_confirm: 'And child nodes will be deleted. Confirm to execute ?',
cur_node: 'Current node'
}
}

View File

@ -475,7 +475,8 @@ export default {
ldap: 'LDAP設置',
oidc: 'OIDC設置',
theme: '主題設置',
cas: 'CAS設置'
cas: 'CAS設置',
map: '地圖設置'
},
license: {
i18n_no_license_record: '沒有 License 記錄',
@ -2141,5 +2142,18 @@ export default {
},
plugin_style: {
border: '邊框'
},
map_setting: {
area_level: '區域等級',
area_code: '區域代碼',
please_input: '請填寫',
parent_area: '上級區域',
area_code_tip: '區域代碼格式為9位數字',
area_name: '區域名稱',
parent_name: '上級區域',
geo_json: '坐標文件',
fileplaceholder: '請上傳json格式坐標文件',
delete_confirm: '及子節點都會被刪除,確認執行?',
cur_node: '當前節點'
}
}

View File

@ -476,7 +476,8 @@ export default {
ldap: 'LDAP设置',
oidc: 'OIDC设置',
theme: '主题设置',
cas: 'CAS设置'
cas: 'CAS设置',
map: '地图设置'
},
license: {
i18n_no_license_record: '没有 License 记录',
@ -2153,5 +2154,18 @@ export default {
},
sql_variable: {
variable_mgm: '参数设置'
},
map_setting: {
area_level: '区域等级',
area_code: '区域代码',
please_input: '请填写',
parent_area: '上级区域',
area_code_tip: '区域代码格式为9位数字',
area_name: '区域名称',
parent_name: '上级区域',
geo_json: '坐标文件',
fileplaceholder: '请上传json格式坐标文件',
delete_confirm: '及子节点都会被删除,确认执行?',
cur_node: '当前节点'
}
}

View File

@ -0,0 +1,259 @@
<template>
<el-col class="tree-style">
<el-col>
<el-row style="margin-bottom: 10px">
<el-input
v-model="filterText"
size="mini"
:placeholder="$t('commons.search')"
prefix-icon="el-icon-search"
clearable
class="main-area-input"
/>
</el-row>
<el-col class="custom-tree-container">
<div class="block">
<el-tree
ref="tree"
class="filter-tree"
:data="treeDatas"
:props="defaultProps"
:filter-node-method="filterNode"
:expand-on-click-node="false"
node-key="code"
:accordion="true"
highlight-current
:default-expanded-keys="expandedKeys"
@current-change="nodeClick"
>
<span slot-scope="{ node, data }" class="custom-tree-node father">
<span style="display: flex;flex: 1;width: 0;">
<span style="margin-left: 6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" :title="data.name">{{ node.data.name }}</span>
</span>
<span v-if="!data.code.startWith('156')" class="child">
<span @click.stop>
<span class="el-dropdown-link">
<el-button
icon="el-icon-plus"
type="text"
size="small"
@click="addHandler(data, node)"
/>
</span>
</span>
<span v-if="!data.code.startWith('000')" style="margin-left: 12px;" @click.stop>
<span class="el-dropdown-link">
<el-button
icon="el-icon-delete"
type="text"
size="small"
@click="removeHandler(data, node)"
/>
</span>
</span>
</span>
</span>
</el-tree>
</div>
</el-col>
</el-col>
</el-col>
</template>
<script>
import { removeMap } from '@/api/map/map'
export default {
name: 'MapSettingLeft',
props: {
treeDatas: {
type: Array,
default: () => []
}
},
data() {
return {
filterText: '',
defaultProps: {
children: 'children',
label: 'name',
value: 'id'
},
data: [],
expandedKeys: []
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val)
}
},
created() {
},
methods: {
filterNode(value, data) {
if (!value) return true
return data.label.indexOf(value) !== -1
},
nodeClick(data, node) {
let parent = null
if (node.parent.data instanceof Array) {
parent = node.parent.data[0]
} else {
parent = node.parent.data
}
const nodeInfo = {
code: data.code,
name: data.name,
pcode: data.pcode,
pname: parent.name
}
this.$emit('show-node-info', this.setStatus(nodeInfo, 'read-only'))
},
addHandler(data, node) {
let form = {
pLevel: node.level,
pCode: data.code
}
if (node.level > 4) {
this.$error('不支持4级行政级别')
form = {}
}
this.$emit('emit-add', this.setStatus(form, 'add'))
},
removeHandler(data, node) {
let parent = null
if (node.parent.data instanceof Array) {
parent = node.parent.data[0]
} else {
parent = node.parent.data
}
const param = {
code: data.code,
pcode: parent.code,
plevel: node.parent.level,
name: data.name
}
const msg = this.$t('map_setting.cur_node') + '[' + data.name + ']' + this.$t('map_setting.delete_confirm')
this.$confirm(msg, '', {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
removeMap(param).then(res => {
this.$emit('refresh-tree')
this.$success(this.$t('commons.delete_success'))
})
}).catch(() => {
this.$info(this.$t('commons.delete_cancel'))
})
},
setStatus(data, status) {
const form = JSON.parse(JSON.stringify(data))
return Object.assign(form, {
status: status || 'read-only'
})
},
showNewNode(code) {
this.$refs.tree.setCurrentKey(code)
}
}
}
</script>
<style lang="scss" scoped>
.el-divider--horizontal {
margin: 12px 0
}
.search-input {
padding: 12px 0;
}
.custom-tree-container{
margin-top: 10px;
}
.tree-list>>>.el-tree-node__expand-icon.is-leaf{
display: none;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right:8px;
}
.custom-tree-node-list {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding:0 8px;
}
.custom-position {
flex: 1;
display: flex;
align-items: center;
font-size: 14px;
flex-flow: row nowrap;
}
.form-item {
margin-bottom: 0;
}
.title-css {
height: 26px;
}
.title-text {
line-height: 26px;
}
.scene-title{
width: 100%;
display: flex;
}
.scene-title-name{
width: 100%;
overflow: hidden;
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
}
.father .child {
visibility: hidden;
}
.father:hover .child {
visibility: visible;
}
.dialog-css >>> .el-dialog__body {
padding: 10px 20px 20px;
}
.inner-dropdown-menu{
display: flex;
justify-content: space-between;
align-items: center;
width: 100%
}
.tree-style {
height: 100%;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,349 @@
<template>
<div>
<el-empty v-if="status === 'empty'" description="请在左侧选择区域" />
<el-descriptions v-else-if="status === 'read-only'" title="区域信息" :column="1">
<el-descriptions-item :label="$t('map_setting.area_code')">{{ nodeInfo.code }}</el-descriptions-item>
<el-descriptions-item :label="$t('map_setting.area_name')">{{ nodeInfo.name }}</el-descriptions-item>
<el-descriptions-item :label="$t('map_setting.parent_name')">{{ nodeInfo.pname }}</el-descriptions-item>
<el-descriptions-item :label="$t('map_setting.geo_json')">
<json-view :data="json" />
</el-descriptions-item>
</el-descriptions>
<!--基础配置表单-->
<el-form
v-else
ref="formInline"
v-loading="loading"
:model="formInline"
:rules="rules"
class="demo-form-inline"
size="small"
>
<el-input v-show="false" v-model="formInline.pLevel" />
<el-row>
<el-row>
<el-col>
<el-form-item :label="$t('map_setting.parent_area')" prop="pCode">
<el-tree-select
v-if="treeShow"
ref="deSelectTree"
v-model="formInline.pCode"
popover-class="test-class-wrap"
:data="treeDatas"
:select-params="selectParams"
:tree-params="treeParams"
:filter-node-method="_filterFun"
:tree-render-fun="_renderFun"
@searchFun="_searchFun"
@node-click="changeNode"
@select-clear="selectClear"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item :label="$t('map_setting.area_code')" prop="code">
<el-input v-model="formInline.code" :placeholder="$t('map_setting.please_input')" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item :label="$t('map_setting.area_name')" prop="name">
<el-input v-model="formInline.name" maxlength="30" show-word-limit :placeholder="$t('map_setting.please_input')" />
</el-form-item>
</el-col>
</el-row>
</el-row>
<el-row>
<el-col>
<el-form-item :label="$t('map_setting.geo_json')">
<el-upload
style="float: right;margin-left: 10px"
class="upload-demo"
action=""
accept=".json"
:on-exceed="handleExceed"
:before-upload="uploadValidate"
:on-error="handleError"
:show-file-list="false"
:file-list="filesTmp"
:http-request="uploadMapFile"
>
<el-button style="display: inline-block" size="mini" type="success" plain>
{{ $t('commons.upload') }}
</el-button>
</el-upload>
<el-button
style="float:right;margin-top: 3px"
size="mini"
type="danger"
plain
@click="removeFile"
>
{{ $t('commons.clear') }}
</el-button>
<el-input
v-model="formInline.fileName"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<div>
<el-button type="success" size="small" @click="save('formInline')">
{{ $t('commons.save') }}
</el-button>
</div>
</el-form>
</div>
</template>
<script>
import jsonView from 'vue-json-views'
import { geoJson, saveMap } from '@/api/map/map'
import ElTreeSelect from '@/components/ElTreeSelect'
export default {
name: 'MapSettingRight',
components: { jsonView, ElTreeSelect },
props: {
status: {
type: String,
default: 'empty'
},
treeDatas: {
type: Array,
default: () => []
}
},
data() {
return {
formInline: { pCode: '' },
loading: false,
rules: {
pCode: [
{ required: true, message: this.$t('map_setting.parent_name') + this.$t('commons.cannot_be_null'), trigger: 'change' }
],
code: [
{ required: true, message: this.$t('map_setting.area_code') + this.$t('commons.cannot_be_null'), trigger: ['change', 'blur'] },
{ pattern: /^\d{9}$/, message: this.$t('map_setting.area_code_tip'), trigger: ['change', 'blur'] }
],
name: [
{ required: true, message: this.$t('map_setting.area_name') + this.$t('commons.cannot_be_null'), trigger: ['change', 'blur'] }
],
fileName: [
{ required: true, message: this.$t('map_setting.geo_json') + this.$t('commons.cannot_be_null'), trigger: 'change' }
]
},
levelOptions: [
{ value: 1, label: '国家' },
{ value: 2, label: '一级行政区划(省)' },
{ value: 3, label: '二级行政区划(市)' },
{ value: 4, label: '三级行政区划(区县)' },
{ value: 5, label: '四级行政区划(乡镇)' }
],
suffixes: new Set(['json']),
errList: [],
filesTmp: [],
nodeInfo: {
code: -1,
name: '',
level: -1,
pcode: -1,
pname: ''
},
noGsoJson: { success: false, message: 'no json file' },
json: {},
selectParams: {
clearable: true,
placeholder: this.$t('commons.please_select') + this.$t('map_setting.parent_name')
},
treeParams: {
showParent: true,
clickParent: true,
filterable: true,
//
leafOnly: false,
includeHalfChecked: false,
'check-strictly': false,
'default-expand-all': false,
'expand-on-click-node': false,
'render-content': this._renderFun,
data: [],
props: {
children: 'children',
label: 'name',
rootId: '000000000',
disabled: 'disabled',
parentId: 'pcode',
value: 'code'
}
},
treeShow: true
}
},
watch: {
treeDatas: function(val) {
this.treeParams.data = val
}
},
created() {
// this.query()
},
methods: {
handleExceed(files, fileList) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'))
},
handleError() {
this.$warning(this.$t('test_track.case.import.upload_limit_count'))
},
uploadValidate(file) {
const suffix = file.name.substring(file.name.lastIndexOf('.') + 1)
if (!this.suffixes.has(suffix)) {
this.$warning(this.$t('test_track.case.import.upload_limit_format'))
return false
}
if (file.size / 1024 / 1024 > 30) {
this.$warning(this.$t('test_track.case.import.upload_limit_size'))
return false
}
this.errList = []
return true
},
uploadMapFile(file) {
this.formInline.fileName = file.file.name
this.formInline.file = file.file
},
removeFile() {
this.formInline.fileName = null
this.formInline.file = null
},
buildFormData(file, files, param) {
const formData = new FormData()
if (file) {
formData.append('file', file)
}
if (files) {
files.forEach(f => {
formData.append('files', f)
})
}
formData.append('request', new Blob([JSON.stringify(param)], { type: 'application/json' }))
return formData
},
save(formInline) {
const param = {
code: this.formInline.code,
name: this.formInline.name,
pcode: this.formInline.pCode,
plevel: this.formInline.pLevel
}
this.$refs[formInline].validate(valid => {
if (valid) {
this.saveHandler(param)
} else {
// this.result = false
}
})
},
saveHandler(param) {
const formData = this.buildFormData(this.formInline.file, null, param)
saveMap(formData).then(response => {
const flag = response.success
if (flag) {
this.$emit('refresh-tree', param)
this.$success(this.$t('commons.save_success'))
} else {
this.$message.error(this.$t('commons.save_failed'))
}
})
},
emitAdd(form) {
this.formInline = JSON.parse(JSON.stringify(form))
this.treeShow = false
this.$nextTick(() => {
this.treeShow = true
})
},
loadForm(form) {
if (form && form.code) {
this.nodeInfo = JSON.parse(JSON.stringify(form))
this.setGeoJson()
}
},
setGeoJson() {
if (!this.nodeInfo || !this.nodeInfo.code) {
this.json = JSON.parse(JSON.stringify(this.noGsoJson))
return
}
const cCode = this.nodeInfo.code
if (this.$store.getters.geoMap[cCode]) {
const json = this.$store.getters.geoMap[cCode]
this.json = JSON.parse(JSON.stringify(json))
return
}
geoJson(cCode).then(res => {
this.$store.dispatch('map/setGeo', {
key: cCode,
value: res
}).then(() => {
this.json = JSON.parse(JSON.stringify(res))
})
})
},
_filterFun(value, data, node) {
if (!value) return true
return data.id.toString().indexOf(value.toString()) !== -1
},
//
_nodeClickFun(data, node, vm) {
},
//
_searchFun(value) {
this.$refs.deSelectTree.filterFun(value)
},
// render
_renderFun(h, { node, data, store }) {
const { props, clickParent } = this.treeParams
return (
<span class={['custom-tree-node', !clickParent && data[props.children] && data[props.children].length ? 'disabled' : null]}>
<span>{node.label}</span>
</span>
)
},
changeNode(data, node) {
if (node.level > 4) {
this.$error('不支持4级行政级别')
this.formInline.pLevel = null
this.formInline.pCode = null
return
}
this.formInline.pLevel = node.level
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,66 @@
<template>
<de-container v-loading="$store.getters.loadingMap[$store.getters.currentPath]">
<de-aside-container type="mapset">
<map-setting-left ref="map_setting_tree" :tree-datas="treeDatas" @emit-add="emitAdd" @refresh-tree="refreshTree" @show-node-info="loadForm" />
</de-aside-container>
<de-main-container>
<map-setting-right ref="map_setting_form" :tree-datas="treeDatas" :status="formStatus" @refresh-tree="refreshTree" />
</de-main-container>
</de-container>
</template>
<script>
import DeMainContainer from '@/components/dataease/DeMainContainer'
import DeContainer from '@/components/dataease/DeContainer'
import DeAsideContainer from '@/components/dataease/DeAsideContainer'
import { areaMapping } from '@/api/map/map'
import MapSettingLeft from './MapSettingLeft'
import MapSettingRight from './MapSettingRight'
export default {
name: 'MapSetting',
components: { DeMainContainer, DeContainer, DeAsideContainer, MapSettingLeft, MapSettingRight },
data() {
return {
formStatus: 'empty',
treeDatas: []
}
},
created() {
this.loadTreeData()
},
methods: {
emitAdd(form) {
this.setStatus(form.status)
this.$refs && this.$refs['map_setting_form'] && this.$refs['map_setting_form'].emitAdd(form)
},
loadForm(nodeInfo) {
this.setStatus(nodeInfo.status)
this.$refs && this.$refs['map_setting_form'] && this.$refs['map_setting_form'].loadForm(nodeInfo)
},
setStatus(status) {
this.formStatus = status
},
loadTreeData() {
Object.keys(this.treeDatas).length === 0 && areaMapping().then(res => {
this.treeDatas = res.data
})
},
refreshTree(node) {
areaMapping().then(res => {
this.treeDatas = res.data
if (node && node.code) {
this.$refs && this.$refs['map_setting_tree'] && this.$refs['map_setting_tree'].showNewNode(node.code)
}
})
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -42,11 +42,16 @@
<plugin-com v-if="isPluginLoaded" ref="CasSetting" component-name="CasSetting" />
</el-tab-pane>
<el-tab-pane :lazy="true" :label="$t('sysParams.map')" name="ten">
<map-setting v-if="activeName === 'ten'" ref="mapSetting" />
</el-tab-pane>
</el-tabs>
</layout-content>
</template>
<script>
import BasicSetting from './BasicSetting'
import MapSetting from './MapSetting'
import EmailSetting from './EmailSetting'
import SimpleMode from './SimpleModeSetting'
import ClusterMode from './ClusterModeSetting'
@ -57,7 +62,7 @@ import { pluginLoaded } from '@/api/user'
import { engineMode } from '@/api/system/engine'
export default {
components: { BasicSetting, EmailSetting, LayoutContent, PluginCom, SimpleMode, ClusterMode, KettleSetting },
components: { BasicSetting, EmailSetting, LayoutContent, PluginCom, SimpleMode, ClusterMode, KettleSetting, MapSetting },
data() {
return {
activeName: 'zero',

File diff suppressed because one or more lines are too long