feat(图表): 地图支持自定义区域
This commit is contained in:
parent
a6847abeb5
commit
bc4092ce9a
@ -0,0 +1,52 @@
|
||||
package io.dataease.map.dao.auto.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 自定义地理区域
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2024-11-22
|
||||
*/
|
||||
@TableName("core_custom_geo_area")
|
||||
public class CoreCustomGeoArea implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 区域名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CoreCustomGeoArea{" +
|
||||
"id = " + id +
|
||||
", name = " + name +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package io.dataease.map.dao.auto.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 自定义地理区域分区详情
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2024-11-22
|
||||
*/
|
||||
@TableName("core_custom_geo_sub_area")
|
||||
public class CoreCustomGeoSubArea implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 区域范围
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
/**
|
||||
* 自定义地理区域id
|
||||
*/
|
||||
private String geoAreaId;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getGeoAreaId() {
|
||||
return geoAreaId;
|
||||
}
|
||||
|
||||
public void setGeoAreaId(String geoAreaId) {
|
||||
this.geoAreaId = geoAreaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CoreCustomGeoSubArea{" +
|
||||
"id = " + id +
|
||||
", name = " + name +
|
||||
", scope = " + scope +
|
||||
", geoAreaId = " + geoAreaId +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package io.dataease.map.dao.auto.mapper;
|
||||
|
||||
import io.dataease.map.dao.auto.entity.CoreCustomGeoArea;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 自定义地理区域 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2024-11-22
|
||||
*/
|
||||
@Mapper
|
||||
public interface CoreCustomGeoAreaMapper extends BaseMapper<CoreCustomGeoArea> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package io.dataease.map.dao.auto.mapper;
|
||||
|
||||
import io.dataease.map.dao.auto.entity.CoreCustomGeoSubArea;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 自定义地理区域分区详情 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2024-11-22
|
||||
*/
|
||||
@Mapper
|
||||
public interface CoreCustomGeoSubAreaMapper extends BaseMapper<CoreCustomGeoSubArea> {
|
||||
|
||||
}
|
||||
@ -3,17 +3,20 @@ package io.dataease.map.manage;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import io.dataease.api.map.dto.GeometryNodeCreator;
|
||||
import io.dataease.api.map.vo.AreaNode;
|
||||
import io.dataease.api.map.vo.CustomGeoArea;
|
||||
import io.dataease.api.map.vo.CustomGeoSubArea;
|
||||
import io.dataease.constant.StaticResourceConstants;
|
||||
import io.dataease.exception.DEException;
|
||||
import io.dataease.map.bo.AreaBO;
|
||||
import io.dataease.map.dao.auto.entity.Area;
|
||||
import io.dataease.map.dao.auto.entity.CoreCustomGeoArea;
|
||||
import io.dataease.map.dao.auto.entity.CoreCustomGeoSubArea;
|
||||
import io.dataease.map.dao.auto.mapper.AreaMapper;
|
||||
import io.dataease.map.dao.auto.mapper.CoreCustomGeoAreaMapper;
|
||||
import io.dataease.map.dao.auto.mapper.CoreCustomGeoSubAreaMapper;
|
||||
import io.dataease.map.dao.ext.entity.CoreAreaCustom;
|
||||
import io.dataease.map.dao.ext.mapper.CoreAreaCustomMapper;
|
||||
import io.dataease.utils.BeanUtils;
|
||||
import io.dataease.utils.CommonBeanFactory;
|
||||
import io.dataease.utils.FileUtils;
|
||||
import io.dataease.utils.LogUtil;
|
||||
import io.dataease.utils.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
@ -32,6 +35,7 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.dataease.constant.CacheConstant.CommonCacheConstant.CUSTOM_GEO_CACHE;
|
||||
import static io.dataease.constant.CacheConstant.CommonCacheConstant.WORLD_MAP_CACHE;
|
||||
|
||||
@Component
|
||||
@ -51,6 +55,12 @@ public class MapManage {
|
||||
@Resource
|
||||
private AreaMapper areaMapper;
|
||||
|
||||
@Resource
|
||||
private CoreCustomGeoAreaMapper coreCustomGeoAreaMapper;
|
||||
|
||||
@Resource
|
||||
private CoreCustomGeoSubAreaMapper coreCustomGeoSubAreaMapper;
|
||||
|
||||
@Resource
|
||||
private CoreAreaCustomMapper coreAreaCustomMapper;
|
||||
|
||||
@ -175,6 +185,62 @@ public class MapManage {
|
||||
});
|
||||
}
|
||||
|
||||
@Cacheable(value = CUSTOM_GEO_CACHE, key = "'custom_geo_area'")
|
||||
public List<CustomGeoArea> listCustomGeoArea() {
|
||||
return coreCustomGeoAreaMapper.selectList(null).stream().map(o -> BeanUtils.copyBean(new CustomGeoArea(), o)).toList();
|
||||
}
|
||||
|
||||
public List<CustomGeoSubArea> getCustomGeoArea(String areaId) {
|
||||
var query = new QueryWrapper<CoreCustomGeoSubArea>();
|
||||
query.eq("geo_area_id", areaId);
|
||||
return coreCustomGeoSubAreaMapper.selectList(query).stream().map(o -> BeanUtils.copyBean(new CustomGeoSubArea(), o)).toList();
|
||||
}
|
||||
|
||||
@CacheEvict(cacheNames = CUSTOM_GEO_CACHE, key = "'custom_geo_area'")
|
||||
@Transactional
|
||||
public void deleteCustomGeoArea(String areaId) {
|
||||
coreCustomGeoAreaMapper.deleteById(areaId);
|
||||
var q = new QueryWrapper<CoreCustomGeoSubArea>();
|
||||
q.eq("geo_area_id", areaId);
|
||||
coreCustomGeoSubAreaMapper.delete(q);
|
||||
}
|
||||
|
||||
@CacheEvict(cacheNames = CUSTOM_GEO_CACHE, key = "'custom_geo_area'")
|
||||
@Transactional
|
||||
public void saveCustomGeoArea(CustomGeoArea geoArea) {
|
||||
var coreCustomGeoArea = new CoreCustomGeoArea();
|
||||
BeanUtils.copyBean(coreCustomGeoArea, geoArea);
|
||||
if (ObjectUtils.isEmpty(coreCustomGeoArea.getId())) {
|
||||
coreCustomGeoArea.setId("custom_" + IDUtils.snowID());
|
||||
coreCustomGeoAreaMapper.insert(coreCustomGeoArea);
|
||||
} else {
|
||||
coreCustomGeoAreaMapper.updateById(coreCustomGeoArea);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteCustomGeoSubArea(long areaId) {
|
||||
coreCustomGeoSubAreaMapper.deleteById(areaId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void saveCustomGeoSubArea(CustomGeoSubArea customGeoSubArea) {
|
||||
var geoSubArea = new CoreCustomGeoSubArea();
|
||||
BeanUtils.copyBean(geoSubArea, customGeoSubArea);
|
||||
if (ObjectUtils.isEmpty(geoSubArea.getId())) {
|
||||
geoSubArea.setId(IDUtils.snowID());
|
||||
coreCustomGeoSubAreaMapper.insert(geoSubArea);
|
||||
} else {
|
||||
coreCustomGeoSubAreaMapper.updateById(geoSubArea);
|
||||
}
|
||||
}
|
||||
|
||||
public List<AreaNode> getCustomGeoSubAreaOptions() {
|
||||
var q = new QueryWrapper<Area>();
|
||||
q.eq("pid", "156");
|
||||
return areaMapper.selectList(q).stream().map(a -> BeanUtils.copyBean(AreaNode.builder().build(), a)).toList();
|
||||
}
|
||||
|
||||
public void childTreeIdList(List<String> pidList, List<String> resultList) {
|
||||
QueryWrapper<CoreAreaCustom> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.in("pid", pidList);
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
package io.dataease.map.server;
|
||||
|
||||
import io.dataease.api.map.CustomGeoApi;
|
||||
import io.dataease.api.map.vo.AreaNode;
|
||||
import io.dataease.api.map.vo.CustomGeoArea;
|
||||
import io.dataease.api.map.vo.CustomGeoSubArea;
|
||||
import io.dataease.map.manage.MapManage;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/customGeo")
|
||||
public class CustomGeoServer implements CustomGeoApi {
|
||||
|
||||
@Resource
|
||||
private MapManage mapManage;
|
||||
|
||||
@Override
|
||||
public List<CustomGeoArea> listCustomGeoArea() {
|
||||
return mapManage.listCustomGeoArea();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CustomGeoSubArea> getCustomGeoArea(String id) {
|
||||
return mapManage.getCustomGeoArea(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCustomGeoArea(String id) {
|
||||
mapManage.deleteCustomGeoArea(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveCustomGeoArea(CustomGeoArea geoArea) {
|
||||
mapManage.saveCustomGeoArea(geoArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCustomGeoSubArea(long id) {
|
||||
mapManage.deleteCustomGeoSubArea(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveCustomGeoSubArea(CustomGeoSubArea geoSubArea) {
|
||||
mapManage.saveCustomGeoSubArea(geoSubArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AreaNode> getCustomGeoSubAreaOptions() {
|
||||
return mapManage.getCustomGeoSubAreaOptions();
|
||||
}
|
||||
}
|
||||
@ -28,4 +28,23 @@ UPDATE `visualization_background` SET `name` = 'Board5' WHERE `id` = 'board_5';
|
||||
UPDATE `visualization_background` SET `name` = 'Board6' WHERE `id` = 'board_6';
|
||||
UPDATE `visualization_background` SET `name` = 'Board7' WHERE `id` = 'board_7';
|
||||
UPDATE `visualization_background` SET `name` = 'Board8' WHERE `id` = 'board_8';
|
||||
UPDATE `visualization_background` SET `name` = 'Board9' WHERE `id` = 'board_9';
|
||||
UPDATE `visualization_background` SET `name` = 'Board9' WHERE `id` = 'board_9';
|
||||
|
||||
DROP TABLE IF EXISTS `core_custom_geo_area`;
|
||||
CREATE TABLE core_custom_geo_area
|
||||
(
|
||||
id varchar(50) not null comment 'id'
|
||||
primary key,
|
||||
name varchar(50) null comment '区域名称'
|
||||
)
|
||||
comment '自定义地理区域';
|
||||
DROP TABLE IF EXISTS `core_custom_geo_sub_area`;
|
||||
create table core_custom_geo_sub_area
|
||||
(
|
||||
id bigint not null comment 'id'
|
||||
primary key,
|
||||
name varchar(50) not null comment '名称',
|
||||
scope varchar(1024) null comment '区域范围',
|
||||
geo_area_id varchar(50) not null comment '自定义地理区域id'
|
||||
)
|
||||
comment '自定义地理区域分区详情';
|
||||
@ -31,4 +31,24 @@ UPDATE `visualization_background` SET `name` = 'Board5' WHERE `id` = 'board_5';
|
||||
UPDATE `visualization_background` SET `name` = 'Board6' WHERE `id` = 'board_6';
|
||||
UPDATE `visualization_background` SET `name` = 'Board7' WHERE `id` = 'board_7';
|
||||
UPDATE `visualization_background` SET `name` = 'Board8' WHERE `id` = 'board_8';
|
||||
UPDATE `visualization_background` SET `name` = 'Board9' WHERE `id` = 'board_9';
|
||||
UPDATE `visualization_background` SET `name` = 'Board9' WHERE `id` = 'board_9';
|
||||
|
||||
DROP TABLE IF EXISTS `core_custom_geo_area`;
|
||||
CREATE TABLE core_custom_geo_area
|
||||
(
|
||||
id varchar(50) not null comment 'id'
|
||||
primary key,
|
||||
name varchar(50) null comment '区域名称'
|
||||
)
|
||||
comment '自定义地理区域';
|
||||
DROP TABLE IF EXISTS `core_custom_geo_sub_area`;
|
||||
create table core_custom_geo_sub_area
|
||||
(
|
||||
id bigint not null comment 'id'
|
||||
primary key,
|
||||
name varchar(50) not null comment '名称',
|
||||
scope varchar(1024) null comment '区域范围',
|
||||
geo_area_id varchar(50) not null comment '自定义地理区域id'
|
||||
)
|
||||
comment '自定义地理区域分区详情';
|
||||
|
||||
|
||||
@ -90,6 +90,10 @@
|
||||
<key-type>java.lang.String</key-type>
|
||||
<value-type>java.lang.Object</value-type>
|
||||
</cache>
|
||||
<cache alias="de_v2_custom_geo" uses-template="common-cache">
|
||||
<key-type>java.lang.String</key-type>
|
||||
<value-type>java.util.List</value-type>
|
||||
</cache>
|
||||
|
||||
<cache alias="de_v2_user_token_cache">
|
||||
<key-type>java.lang.String</key-type>
|
||||
|
||||
@ -23,3 +23,31 @@ const isCustomGeo = (id: string) => {
|
||||
const getBusiGeoCode = (id: string) => {
|
||||
return id.substring(4)
|
||||
}
|
||||
|
||||
export const listCustomGeoArea = (): Promise<IResponse<CustomGeoArea[]>> => {
|
||||
return request.get({ url: '/customGeo/geoArea/list' })
|
||||
}
|
||||
|
||||
export const getCustomGeoArea = (id: string): Promise<IResponse<CustomGeoSubArea[]>> => {
|
||||
return request.get({ url: `/customGeo/geoArea/${id}` })
|
||||
}
|
||||
|
||||
export const deleteCustomGeoArea = (id: string) => {
|
||||
return request.delete({ url: `/customGeo/geoArea/${id}` })
|
||||
}
|
||||
|
||||
export const saveCustomGeoArea = (area: CustomGeoArea) => {
|
||||
return request.post({ url: '/customGeo/geoArea/save', data: area })
|
||||
}
|
||||
|
||||
export const deleteCustomGeoSubArea = (id: string) => {
|
||||
return request.delete({ url: `/customGeo/geoSubArea/${id}` })
|
||||
}
|
||||
|
||||
export const saveCustomGeoSubArea = (area: CustomGeoSubArea) => {
|
||||
return request.post({ url: '/customGeo/geoSubArea/save', data: area })
|
||||
}
|
||||
|
||||
export const listSubAreaOptions = (): Promise<IResponse<AreaNode[]>> => {
|
||||
return request.get({ url: '/customGeo/geoSubArea/options' })
|
||||
}
|
||||
|
||||
@ -333,7 +333,7 @@ declare interface ChartBasicStyle {
|
||||
/**
|
||||
* 最大行数
|
||||
*/
|
||||
maxLines?: boolean
|
||||
maxLines?: number
|
||||
}
|
||||
/**
|
||||
* 表头属性
|
||||
|
||||
10
core/core-frontend/src/models/chart/map.d.ts
vendored
10
core/core-frontend/src/models/chart/map.d.ts
vendored
@ -5,3 +5,13 @@ interface AreaNode {
|
||||
pid: string
|
||||
children: AreaNode[]
|
||||
}
|
||||
|
||||
interface CustomGeoArea {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
type CustomGeoSubArea = CustomGeoArea & {
|
||||
geoAreaId: string
|
||||
scope: string
|
||||
}
|
||||
|
||||
@ -69,6 +69,9 @@ const getAreaMapping = async areaId => {
|
||||
if (!areaId) {
|
||||
return {}
|
||||
}
|
||||
if (areaId.startsWith('custom_')) {
|
||||
areaId = '156'
|
||||
}
|
||||
const geoJson = await getGeoJsonFile(areaId)
|
||||
return geoJson.features.reduce((p, n) => {
|
||||
p[n.properties.name] = n.properties.name
|
||||
|
||||
@ -55,7 +55,7 @@ import CalcFieldEdit from '@/views/visualized/data/dataset/form/CalcFieldEdit.vu
|
||||
import { getFieldName, guid } from '@/views/visualized/data/dataset/form/util'
|
||||
import { cloneDeep, forEach, get } from 'lodash-es'
|
||||
import { deleteField, saveField } from '@/api/dataset'
|
||||
import { getWorldTree } from '@/api/map'
|
||||
import { getWorldTree, listCustomGeoArea } from '@/api/map'
|
||||
import chartViewManager from '@/views/chart/components/js/panel'
|
||||
import DatasetSelect from '@/views/chart/components/editor/dataset-select/DatasetSelect.vue'
|
||||
import { useDraggable } from '@vueuse/core'
|
||||
@ -293,8 +293,17 @@ watch(
|
||||
newVal => {
|
||||
if (showAxis('area')) {
|
||||
if (!state.worldTree?.length) {
|
||||
getWorldTree().then(res => {
|
||||
state.worldTree.splice(0, state.worldTree.length, res.data)
|
||||
getWorldTree().then(async res => {
|
||||
const customAreaList = (await listCustomGeoArea()).data
|
||||
const customRoot = {
|
||||
id: 'customRoot',
|
||||
name: '自定义区域',
|
||||
disabled: true
|
||||
}
|
||||
if (customAreaList.length) {
|
||||
customRoot.children = customAreaList
|
||||
}
|
||||
state.worldTree.splice(0, state.worldTree.length, res.data, customRoot)
|
||||
state.areaId = view.value?.customAttr?.map?.id
|
||||
})
|
||||
} else {
|
||||
@ -311,7 +320,8 @@ watch(
|
||||
)
|
||||
const treeProps = {
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
children: 'children',
|
||||
disabled: 'disabled'
|
||||
}
|
||||
|
||||
const recordSnapshotInfo = type => {
|
||||
@ -869,6 +879,9 @@ const renderChart = view => {
|
||||
}
|
||||
|
||||
const onAreaChange = val => {
|
||||
if (val.id === 'customRoot') {
|
||||
return
|
||||
}
|
||||
view.value.customAttr.map = { id: val.id, level: val.level }
|
||||
renderChart(view.value)
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
mapRendering
|
||||
} from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import type { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||
import { cloneDeep, defaultsDeep, isEmpty } from 'lodash-es'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { valueFormatter } from '../../../formatter'
|
||||
import {
|
||||
@ -37,6 +37,9 @@ import {
|
||||
} from '@antv/l7plot-component/dist/esm/legend/category/constants'
|
||||
import substitute from '@antv/util/esm/substitute'
|
||||
import { configCarouselTooltip } from '@/views/chart/components/js/panel/charts/map/tooltip-carousel'
|
||||
import { getCustomGeoArea } from '@/api/map'
|
||||
import { centroid } from '@turf/centroid'
|
||||
import { TextLayer } from '@antv/l7plot/dist/esm'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -77,15 +80,55 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
}
|
||||
|
||||
async drawChart(drawOption: L7PlotDrawOptions<Choropleth>): Promise<Choropleth> {
|
||||
const { chart, level, areaId, container, action } = drawOption
|
||||
const { chart, level, container, action, scope } = drawOption
|
||||
const { areaId } = drawOption
|
||||
if (!areaId) {
|
||||
return
|
||||
}
|
||||
chart.container = container
|
||||
const sourceData = JSON.parse(JSON.stringify(chart.data?.data || []))
|
||||
let data = []
|
||||
let sourceData = JSON.parse(JSON.stringify(chart.data?.data || []))
|
||||
const { misc } = parseJson(chart.customAttr)
|
||||
const { legend } = parseJson(chart.customStyle)
|
||||
let geoJson = {} as FeatureCollection
|
||||
// 自定义区域,去除非区域数据,优先级最高
|
||||
let customSubArea: CustomGeoSubArea[] = []
|
||||
if (areaId.startsWith('custom_')) {
|
||||
customSubArea = (await getCustomGeoArea(areaId)).data || []
|
||||
geoJson = cloneDeep(await getGeoJsonFile('156'))
|
||||
const areaNameMap = geoJson.features.reduce((p, n) => {
|
||||
p['156' + n.properties.adcode] = n.properties.name
|
||||
return p
|
||||
}, {})
|
||||
const areaMap = customSubArea.reduce((p, n) => {
|
||||
p[n.name] = n
|
||||
n.scopeArr = n.scope?.split(',') || []
|
||||
return p
|
||||
}, {})
|
||||
const fakeData = []
|
||||
sourceData.forEach(d => {
|
||||
const area = areaMap[d.name]
|
||||
if (area) {
|
||||
area.scopeArr.forEach(adcode => {
|
||||
fakeData.push({
|
||||
...d,
|
||||
name: areaNameMap[adcode],
|
||||
field: areaNameMap[adcode],
|
||||
scope: area.scopeArr,
|
||||
areaName: d.name
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
sourceData = fakeData
|
||||
} else {
|
||||
if (scope) {
|
||||
geoJson = cloneDeep(await getGeoJsonFile('156'))
|
||||
geoJson.features = geoJson.features.filter(f => scope.includes('156' + f.properties.adcode))
|
||||
} else {
|
||||
geoJson = cloneDeep(await getGeoJsonFile(areaId))
|
||||
}
|
||||
}
|
||||
let data = []
|
||||
// 自定义图例
|
||||
if (!misc.mapAutoLegend && legend.show) {
|
||||
let minValue = misc.mapLegendMin
|
||||
@ -112,7 +155,6 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
} else {
|
||||
data = sourceData
|
||||
}
|
||||
const geoJson = cloneDeep(await getGeoJsonFile(areaId))
|
||||
let options: ChoroplethOptions = {
|
||||
preserveDrawingBuffer: true,
|
||||
map: {
|
||||
@ -157,7 +199,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
// 禁用线上地图数据
|
||||
customFetchGeoData: () => null
|
||||
}
|
||||
const context = { drawOption, geoJson }
|
||||
const context: Record<string, any> = { drawOption, geoJson, customSubArea }
|
||||
options = this.setupOptions(chart, options, context)
|
||||
const { Choropleth } = await import('@antv/l7plot/dist/esm/plots/choropleth')
|
||||
const view = new Choropleth(container, options)
|
||||
@ -165,15 +207,25 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
mapRendering(container)
|
||||
view.once('loaded', () => {
|
||||
mapRendered(container)
|
||||
const { layers } = context
|
||||
if (layers) {
|
||||
layers.forEach(l => {
|
||||
view.addLayer(l)
|
||||
})
|
||||
}
|
||||
view.scene.map['keyboard'].disable()
|
||||
view.on('fillAreaLayer:click', (ev: MapMouseEvent) => {
|
||||
const data = ev.feature.properties
|
||||
if (areaId.startsWith('custom_')) {
|
||||
data.name = data.areaName
|
||||
data.adcode = '156'
|
||||
}
|
||||
action({
|
||||
x: ev.x,
|
||||
y: ev.y,
|
||||
data: {
|
||||
data,
|
||||
extra: { adcode: data.adcode }
|
||||
extra: { adcode: data.adcode, scope: data.scope }
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -206,16 +258,15 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
options.label && (options.label.field = 'name')
|
||||
return options
|
||||
}
|
||||
const sourceData = JSON.parse(JSON.stringify(chart.data.data))
|
||||
const sourceData = options.source.data
|
||||
const colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
|
||||
const { legend } = parseJson(chart.customStyle)
|
||||
let data = []
|
||||
data = sourceData
|
||||
let data = sourceData
|
||||
let colorScale = []
|
||||
let minValue = misc.mapLegendMin
|
||||
let maxValue = misc.mapLegendMax
|
||||
let mapLegendNumber = misc.mapLegendNumber
|
||||
if (legend.show) {
|
||||
let mapLegendNumber = misc.mapLegendNumber
|
||||
getMaxAndMinValueByData(sourceData, 'value', maxValue, minValue, (max, min) => {
|
||||
maxValue = max
|
||||
minValue = min
|
||||
@ -262,10 +313,38 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
return options
|
||||
}
|
||||
|
||||
// 内部函数 创建自定义图例的内容
|
||||
private createLegendCustomContent = showItems => {
|
||||
const containerDom = createDom(CONTAINER_TPL) as HTMLElement
|
||||
const listDom = containerDom.getElementsByClassName(LIST_CLASS)[0] as HTMLElement
|
||||
showItems.forEach(item => {
|
||||
let value = '-'
|
||||
if (item.value !== '') {
|
||||
if (Array.isArray(item.value)) {
|
||||
item.value.forEach((v, i) => {
|
||||
item.value[i] = Number.isNaN(v) || v === 'NaN' ? 'NaN' : parseFloat(v).toFixed(0)
|
||||
})
|
||||
value = item.value.join('-')
|
||||
} else {
|
||||
const tmp = item.value as string
|
||||
value = Number.isNaN(tmp) || tmp === 'NaN' ? 'NaN' : parseFloat(tmp).toFixed(0)
|
||||
}
|
||||
}
|
||||
const substituteObj = { ...item, value }
|
||||
|
||||
const domStr = substitute(ITEM_TPL, substituteObj)
|
||||
const itemDom = createDom(domStr)
|
||||
// 给 legend 形状用的
|
||||
itemDom.style.setProperty('--bgColor', item.color)
|
||||
listDom.appendChild(itemDom)
|
||||
})
|
||||
return listDom
|
||||
}
|
||||
|
||||
private customConfigLegend(
|
||||
chart: Chart,
|
||||
options: ChoroplethOptions,
|
||||
_context: Record<string, any>
|
||||
context: Record<string, any>
|
||||
): ChoroplethOptions {
|
||||
const { basicStyle, misc } = parseJson(chart.customAttr)
|
||||
const colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
|
||||
@ -276,33 +355,6 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
if (!legend.show) {
|
||||
return options
|
||||
}
|
||||
// 内部函数 创建自定义图例的内容
|
||||
const createLegendCustomContent = showItems => {
|
||||
const containerDom = createDom(CONTAINER_TPL) as HTMLElement
|
||||
const listDom = containerDom.getElementsByClassName(LIST_CLASS)[0] as HTMLElement
|
||||
showItems.forEach(item => {
|
||||
let value = '-'
|
||||
if (item.value !== '') {
|
||||
if (Array.isArray(item.value)) {
|
||||
item.value.forEach((v, i) => {
|
||||
item.value[i] = Number.isNaN(v) || v === 'NaN' ? 'NaN' : parseFloat(v).toFixed(0)
|
||||
})
|
||||
value = item.value.join('-')
|
||||
} else {
|
||||
const tmp = item.value as string
|
||||
value = Number.isNaN(tmp) || tmp === 'NaN' ? 'NaN' : parseFloat(tmp).toFixed(0)
|
||||
}
|
||||
}
|
||||
const substituteObj = { ...item, value }
|
||||
|
||||
const domStr = substitute(ITEM_TPL, substituteObj)
|
||||
const itemDom = createDom(domStr)
|
||||
// 给 legend 形状用的
|
||||
itemDom.style.setProperty('--bgColor', item.color)
|
||||
listDom.appendChild(itemDom)
|
||||
})
|
||||
return listDom
|
||||
}
|
||||
const LEGEND_SHAPE_STYLE_MAP = {
|
||||
circle: {
|
||||
borderRadius: '50%'
|
||||
@ -358,7 +410,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
})
|
||||
customLegend['customContent'] = (_: string, _items: CategoryLegendListItem[]) => {
|
||||
if (items?.length) {
|
||||
return createLegendCustomContent(items)
|
||||
return this.createLegendCustomContent(items)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@ -371,7 +423,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
customLegend['customContent'] = (_: string, items: CategoryLegendListItem[]) => {
|
||||
const showItems = items?.length > 30 ? items.slice(0, 30) : items
|
||||
if (showItems?.length) {
|
||||
return createLegendCustomContent(showItems)
|
||||
return this.createLegendCustomContent(showItems)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@ -386,6 +438,135 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
return options
|
||||
}
|
||||
|
||||
protected configCustomArea(
|
||||
chart: Chart,
|
||||
options: ChoroplethOptions,
|
||||
context: Record<string, any>
|
||||
): ChoroplethOptions {
|
||||
const { drawOption, customSubArea, geoJson } = context
|
||||
if (!drawOption.areaId.startsWith('custom_')) {
|
||||
return options
|
||||
}
|
||||
const customAttr = parseJson(chart.customAttr)
|
||||
const { label } = customAttr
|
||||
const data = chart.data.data
|
||||
const areaMap = data.reduce((obj, value) => {
|
||||
obj[value['field']] = value
|
||||
return obj
|
||||
}, {})
|
||||
//处理label
|
||||
options.label = false
|
||||
if (label.show) {
|
||||
const geoJsonMap = geoJson.features.reduce((p, n) => {
|
||||
if (n.properties['adcode']) {
|
||||
p['156' + n.properties['adcode']] = n
|
||||
}
|
||||
return p
|
||||
}, {})
|
||||
const labelLocation = []
|
||||
customSubArea.forEach(area => {
|
||||
const areaJsonArr = []
|
||||
area.scopeArr?.forEach(adcode => {
|
||||
const json = geoJsonMap[adcode]
|
||||
json && areaJsonArr.push(json)
|
||||
})
|
||||
if (areaJsonArr.length) {
|
||||
const areaJson: FeatureCollection = {
|
||||
type: 'FeatureCollection',
|
||||
features: areaJsonArr
|
||||
}
|
||||
const content = []
|
||||
if (label.showDimension) {
|
||||
content.push(area.name)
|
||||
}
|
||||
if (label.showQuota) {
|
||||
areaMap[area.name] &&
|
||||
content.push(valueFormatter(areaMap[area.name].value, label.quotaLabelFormatter))
|
||||
}
|
||||
const center = centroid(areaJson)
|
||||
labelLocation.push({
|
||||
name: content.join('\n\n'),
|
||||
x: center.geometry.coordinates[0],
|
||||
y: center.geometry.coordinates[1]
|
||||
})
|
||||
}
|
||||
})
|
||||
const areaLabelLayer = new TextLayer({
|
||||
name: 'areaLabelLayer',
|
||||
source: {
|
||||
data: labelLocation,
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'x',
|
||||
y: 'y'
|
||||
}
|
||||
},
|
||||
field: 'name',
|
||||
style: {
|
||||
fill: label.color,
|
||||
fontSize: label.fontSize,
|
||||
opacity: 1,
|
||||
fontWeight: 'bold',
|
||||
textAnchor: 'center',
|
||||
textAllowOverlap: label.fullDisplay,
|
||||
padding: !label.fullDisplay ? [2, 2] : undefined
|
||||
}
|
||||
})
|
||||
context.layers = [areaLabelLayer]
|
||||
}
|
||||
// 处理tooltip
|
||||
const subAreaMap = customSubArea.reduce((p, n) => {
|
||||
n.scopeArr.forEach(a => {
|
||||
p[a] = n.name
|
||||
})
|
||||
return p
|
||||
}, {})
|
||||
if (options.tooltip && options.tooltip.showComponent) {
|
||||
options.tooltip.items = ['name', 'adcode', 'value']
|
||||
options.tooltip.customTitle = ({ name, adcode }) => {
|
||||
adcode = '156' + adcode
|
||||
return subAreaMap[adcode] ?? name
|
||||
}
|
||||
const tooltip = customAttr.tooltip
|
||||
const formatterMap = tooltip.seriesTooltipFormatter
|
||||
?.filter(i => i.show)
|
||||
.reduce((pre, next) => {
|
||||
pre[next.id] = next
|
||||
return pre
|
||||
}, {}) as Record<string, SeriesFormatter>
|
||||
options.tooltip.customItems = originalItem => {
|
||||
const result = []
|
||||
if (isEmpty(formatterMap)) {
|
||||
return result
|
||||
}
|
||||
const head = originalItem.properties
|
||||
const { adcode } = head
|
||||
const areaName = subAreaMap['156' + adcode]
|
||||
const valItem = areaMap[areaName]
|
||||
if (!valItem) {
|
||||
return result
|
||||
}
|
||||
const formatter = formatterMap[valItem.quotaList?.[0]?.id]
|
||||
if (!isEmpty(formatter)) {
|
||||
const originValue = parseFloat(valItem.value as string)
|
||||
const value = valueFormatter(originValue, formatter.formatterCfg)
|
||||
const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
|
||||
result.push({ ...valItem, name, value: `${value ?? ''}` })
|
||||
}
|
||||
valItem.dynamicTooltipValue?.forEach(item => {
|
||||
const formatter = formatterMap[item.fieldId]
|
||||
if (formatter) {
|
||||
const value = valueFormatter(parseFloat(item.value), formatter.formatterCfg)
|
||||
const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
|
||||
result.push({ color: 'grey', name, value: `${value ?? ''}` })
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
setupDefaultOptions(chart: ChartObj): ChartObj {
|
||||
chart.customAttr.basicStyle.areaBaseColor = '#f4f4f4'
|
||||
return chart
|
||||
@ -402,7 +583,8 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
this.configStyle,
|
||||
this.configTooltip,
|
||||
this.configBasicStyle,
|
||||
this.customConfigLegend
|
||||
)(chart, options, context)
|
||||
this.customConfigLegend,
|
||||
this.configCustomArea
|
||||
)(chart, options, context, this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ export interface L7PlotDrawOptions<P> extends AntVDrawOptions<P> {
|
||||
areaId?: string
|
||||
level?: ViewLevel['level']
|
||||
geoJson?: FeatureCollection
|
||||
scope?: string[]
|
||||
}
|
||||
// S2 or others to be defined next
|
||||
export abstract class L7PlotChartView<
|
||||
|
||||
@ -184,11 +184,16 @@ const calcData = async (view, callback) => {
|
||||
if (!res?.drillFilters?.length) {
|
||||
dynamicAreaId.value = ''
|
||||
} else {
|
||||
dynamicAreaId.value =
|
||||
view.chartExtRequest?.drill?.[res?.drillFilters?.length - 1].extra?.adcode + ''
|
||||
const extra = view.chartExtRequest?.drill?.[res?.drillFilters?.length - 1].extra
|
||||
dynamicAreaId.value = extra?.adcode + ''
|
||||
scope = extra?.scope
|
||||
// 地图
|
||||
if (!dynamicAreaId.value?.startsWith(country.value)) {
|
||||
dynamicAreaId.value = country.value + dynamicAreaId.value
|
||||
if (country.value === 'cus') {
|
||||
dynamicAreaId.value = '156' + dynamicAreaId.value
|
||||
} else {
|
||||
dynamicAreaId.value = country.value + dynamicAreaId.value
|
||||
}
|
||||
}
|
||||
}
|
||||
dvMainStore.setViewDataDetails(view.id, res)
|
||||
@ -268,6 +273,7 @@ const dynamicAreaId = ref('')
|
||||
const country = ref('')
|
||||
const appStore = useAppStoreWithOut()
|
||||
const chartContainer = ref<HTMLElement>(null)
|
||||
let scope
|
||||
let mapTimer: number
|
||||
const renderL7Plot = async (chart: ChartObj, chartView: L7PlotChartView<any, any>, callback) => {
|
||||
const map = parseJson(chart.customAttr).map
|
||||
@ -293,7 +299,8 @@ const renderL7Plot = async (chart: ChartObj, chartView: L7PlotChartView<any, any
|
||||
container: containerId,
|
||||
chart,
|
||||
areaId,
|
||||
action
|
||||
action,
|
||||
scope
|
||||
})
|
||||
callback?.()
|
||||
emit('resetLoading')
|
||||
|
||||
@ -3,11 +3,6 @@
|
||||
<el-aside class="geonetry-aside">
|
||||
<div class="geo-title">
|
||||
<span>{{ t('online_map.geometry') }}</span>
|
||||
<span class="add-icon-span" @click="add()">
|
||||
<el-icon>
|
||||
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="geo-search">
|
||||
<el-input
|
||||
@ -46,85 +41,252 @@
|
||||
:title="data.name"
|
||||
v-html="data.colorName && keyword ? data.colorName : data.name"
|
||||
/>
|
||||
<span class="geo-operate-container">
|
||||
<span v-if="data.id === '000'" class="add-icon-span" @click.stop="add()">
|
||||
<el-icon>
|
||||
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<span class="geo-operate-container" v-if="data.custom">
|
||||
<el-tooltip
|
||||
v-if="data.custom"
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="t('common.delete')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon @click.stop="delHandler(data)" class="hover-icon">
|
||||
<Icon name="icon_delete-trash_outlined"
|
||||
><icon_deleteTrash_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
<Icon name="icon_delete-trash_outlined">
|
||||
<icon_deleteTrash_outlined class="svg-icon" />
|
||||
</Icon>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
<el-tree
|
||||
menu
|
||||
ref="customAreaTreeRef"
|
||||
node-key="id"
|
||||
:data="customTreeData"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false"
|
||||
:default-expand-all="false"
|
||||
:filter-node-method="filterResourceNode"
|
||||
@node-click="loadCustomSubArea"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="custom-area-root">
|
||||
<span class="label" :title="data.name">
|
||||
{{ data.name }}
|
||||
</span>
|
||||
<span class="opt-icon" v-if="data.id === '000'" @click.stop="editCustomArea()">
|
||||
<el-icon>
|
||||
<Icon name="icon_add_outlined"><icon_add_outlined /></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<el-dropdown placement="bottom-end" popper-class="area-opt-popper" v-else>
|
||||
<span class="opt-icon">
|
||||
<el-icon>
|
||||
<Icon name="icon_more_outlined"><icon_more_outlined /></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="editCustomArea(data)">重命名</el-dropdown-item>
|
||||
<el-dropdown-item @click.stop="deleteCustomArea(data)">删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
</template>
|
||||
<template #empty> 空的列表 </template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-aside>
|
||||
<el-main class="geometry-main">
|
||||
<div class="geo-content-container" v-if="!selectedData">
|
||||
<EmptyBackground img-type="noneWhite" :description="t('system.on_the_left')" />
|
||||
</div>
|
||||
<div v-else class="geo-content-container">
|
||||
<div class="geo-content-top">
|
||||
<span>{{ selectedData.name }}</span>
|
||||
<template v-if="showGeoJson">
|
||||
<div class="geo-content-container" v-if="!selectedData">
|
||||
<EmptyBackground img-type="noneWhite" :description="t('system.on_the_left')" />
|
||||
</div>
|
||||
<div class="geo-content-middle">
|
||||
<div class="geo-area">
|
||||
<div v-else class="geo-content-container">
|
||||
<div class="geo-content-top">
|
||||
<span>{{ selectedData.name }}</span>
|
||||
</div>
|
||||
<div class="geo-content-middle">
|
||||
<div class="geo-area">
|
||||
<div class="area-label">
|
||||
<span>{{ t('system.region_code') }}</span>
|
||||
</div>
|
||||
<div class="area-content">
|
||||
<span>{{ selectedData.id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="geo-area">
|
||||
<div class="area-label">
|
||||
<span>{{ t('system.superior_region') }}</span>
|
||||
</div>
|
||||
<div class="area-content">
|
||||
<span>{{ selectedData.parentName || '-' }}</span>
|
||||
<span v-if="selectedData.pid" class="area-secondary">{{
|
||||
'(' + selectedData.pid + ')'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="geo-content-bottom">
|
||||
<div class="area-label">
|
||||
<span>{{ t('system.region_code') }}</span>
|
||||
</div>
|
||||
<div class="area-content">
|
||||
<span>{{ selectedData.id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="geo-area">
|
||||
<div class="area-label">
|
||||
<span>{{ t('system.superior_region') }}</span>
|
||||
</div>
|
||||
<div class="area-content">
|
||||
<span>{{ selectedData.parentName || '-' }}</span>
|
||||
<span v-if="selectedData.pid" class="area-secondary">{{
|
||||
'(' + selectedData.pid + ')'
|
||||
}}</span>
|
||||
<span>{{ t('system.coordinate_file') }}</span>
|
||||
</div>
|
||||
<el-scrollbar class="area-content-geo">
|
||||
<span>{{ selectedData.geoJson }}</span>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="geo-content-bottom">
|
||||
<div class="area-label">
|
||||
<span>{{ t('system.coordinate_file') }}</span>
|
||||
</div>
|
||||
<el-scrollbar class="area-content-geo">
|
||||
<span>{{ selectedData.geoJson }}</span>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="showCustomEmpty">
|
||||
<EmptyBackground img-type="noneWhite" :description="t('system.on_the_left')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-area-view" v-else>
|
||||
<div id="map-container" class="map-container"></div>
|
||||
<el-divider />
|
||||
<div class="sub-area-editor">
|
||||
<span class="header">
|
||||
<span class="label">
|
||||
<span>自定义区域</span>
|
||||
<span>(仅对中国的省份、直辖市,支持自定义地理区域)</span>
|
||||
</span>
|
||||
<span class="add-btn" @click="editCustomSubArea">
|
||||
<el-icon>
|
||||
<Icon name="icon_add_outlined"><icon_add_outlined /></Icon>
|
||||
</el-icon>
|
||||
<span>添加区域</span>
|
||||
</span>
|
||||
</span>
|
||||
<el-table :data="subAreaList" stripe style="width: 100%">
|
||||
<el-table-column prop="name" label="区域名称">
|
||||
<template #default="{ row, $index }">
|
||||
<span
|
||||
class="area-color-symbol"
|
||||
:style="{ backgroundColor: AREA_COLOR[$index % AREA_COLOR.length] }"
|
||||
></span>
|
||||
<span>{{ row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="scopeName" label="区域范围" />
|
||||
<el-table-column fixed="right" label="操作" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<div class="area-edit-btn">
|
||||
<span @click="editCustomSubArea(row)">
|
||||
<el-icon>
|
||||
<Icon name="icon_edit_outlined"><icon_edit_outlined /></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<span @click="deleteCustomSubArea(row)">
|
||||
<el-icon>
|
||||
<Icon name="icon_delete-trash_outlined"><icon_deleteTrash_outlined /></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<geometry-edit ref="editor" @saved="loadTreeData(false)" />
|
||||
<el-dialog
|
||||
v-model="customAreaDialog"
|
||||
:title="`${editedCustomArea.id ? '编辑' : '新建'}自定义地理区域`"
|
||||
width="500"
|
||||
>
|
||||
<el-form
|
||||
ref="areaFormRef"
|
||||
:model="editedCustomArea"
|
||||
label-position="top"
|
||||
label-width="auto"
|
||||
:rules="areaRules"
|
||||
>
|
||||
<el-form-item label-position="top" required prop="name">
|
||||
<el-input v-model="editedCustomArea.name" :minlenegth="1" :maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="customAreaDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveGeoArea()"> 确定 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="customSubAreaDialog"
|
||||
:title="`${customSubArea.id ? '编辑' : '新建'}自定义区域`"
|
||||
width="500"
|
||||
>
|
||||
<el-form
|
||||
ref="subAreaFormRef"
|
||||
:model="customSubArea"
|
||||
label-position="top"
|
||||
label-width="auto"
|
||||
:rules="areaRules"
|
||||
>
|
||||
<el-form-item label="区域名称" label-position="top" required prop="name">
|
||||
<el-input v-model="customSubArea.name" :minlenegth="1" :maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请选择省份或直辖市" label-position="top" required prop="scopeArr">
|
||||
<el-select v-model="customSubArea.scopeArr" multiple style="width: 100%" filterable>
|
||||
<el-option
|
||||
v-for="item in subAreaOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="customSubAreaDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveGeoSubArea()"> 确定 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import icon_more_outlined from '@/assets/svg/icon_more_outlined.svg'
|
||||
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||
import { ref } from 'vue'
|
||||
import { onBeforeMount, reactive, ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { getWorldTree } from '@/api/map'
|
||||
import {
|
||||
getWorldTree,
|
||||
listCustomGeoArea,
|
||||
saveCustomGeoArea,
|
||||
saveCustomGeoSubArea,
|
||||
listSubAreaOptions,
|
||||
deleteCustomGeoArea,
|
||||
getCustomGeoArea,
|
||||
deleteCustomGeoSubArea
|
||||
} from '@/api/map'
|
||||
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
|
||||
import { getGeoJsonFile } from '@/views/chart/components/js/util'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { cloneDeep, debounce } from 'lodash-es'
|
||||
import { setColorName } from '@/utils/utils'
|
||||
import GeometryEdit from './GeometryEdit.vue'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import { ElMessage, ElMessageBox, FormRules } from 'element-plus-secondary'
|
||||
import request from '@/config/axios'
|
||||
import { Choropleth } from '@antv/l7plot/dist/esm/plots/choropleth'
|
||||
import { ChoroplethOptions, TextLayer } from '@antv/l7plot/dist/esm'
|
||||
import { nextTick } from 'vue'
|
||||
import { centroid } from '@turf/centroid'
|
||||
import { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
|
||||
const { wsCache } = useCache()
|
||||
const { t } = useI18n()
|
||||
const keyword = ref('')
|
||||
@ -137,9 +299,14 @@ interface Tree {
|
||||
const areaTreeRef = ref(null)
|
||||
const loading = ref(false)
|
||||
const selectedData = ref(null)
|
||||
const showGeoJson = ref(true)
|
||||
|
||||
const handleNodeClick = async (data: Tree) => {
|
||||
selectedData.value = data
|
||||
customAreaTreeRef.value?.setCurrentKey(null)
|
||||
mapInstance?.destroy()
|
||||
mapInstance = null
|
||||
curCustomGeoArea.id = ''
|
||||
const geoJson = cloneDeep(await getGeoJsonFile(data['id']))
|
||||
selectedData.value['geoJson'] = JSON.stringify(geoJson)
|
||||
const pid = data['pid']
|
||||
@ -149,6 +316,7 @@ const handleNodeClick = async (data: Tree) => {
|
||||
selectedData.value.parentName = parent.data.name
|
||||
}
|
||||
}
|
||||
showGeoJson.value = true
|
||||
}
|
||||
const delHandler = data => {
|
||||
ElMessageBox.confirm(t('system.delete_this_node'), {
|
||||
@ -205,6 +373,326 @@ const add = (pid?: string) => {
|
||||
}
|
||||
|
||||
loadTreeData(true)
|
||||
|
||||
// geoArea
|
||||
const customTreeData = ref([
|
||||
{
|
||||
id: '000',
|
||||
name: '自定义地理区域'
|
||||
}
|
||||
])
|
||||
const customAreaDialog = ref(false)
|
||||
const curCustomGeoArea: CustomGeoArea = reactive({
|
||||
id: '',
|
||||
name: ''
|
||||
})
|
||||
const customAreaTreeRef = ref()
|
||||
const areaFormRef = ref()
|
||||
const saveGeoArea = async () => {
|
||||
areaFormRef.value?.validate(async valid => {
|
||||
if (valid) {
|
||||
await saveCustomGeoArea(editedCustomArea)
|
||||
await loadCustomGeoArea()
|
||||
customAreaDialog.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
const loadCustomGeoArea = async () => {
|
||||
await listCustomGeoArea().then(res => {
|
||||
if (res.data?.length) {
|
||||
customTreeData.value[0].children = res.data
|
||||
if (curCustomGeoArea.id) {
|
||||
nextTick(() => {
|
||||
customAreaTreeRef.value?.setCurrentKey(curCustomGeoArea.id)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
customTreeData.value[0].children = null
|
||||
}
|
||||
})
|
||||
}
|
||||
const editedCustomArea = reactive({
|
||||
id: '',
|
||||
name: ''
|
||||
})
|
||||
const editCustomArea = (data?) => {
|
||||
if (data) {
|
||||
editedCustomArea.id = data.id
|
||||
editedCustomArea.name = data.name
|
||||
} else {
|
||||
editedCustomArea.id = ''
|
||||
editedCustomArea.name = ''
|
||||
}
|
||||
customAreaDialog.value = true
|
||||
}
|
||||
const deleteCustomArea = data => {
|
||||
ElMessageBox.confirm(
|
||||
'该操作会导致使用了自定义区域的地图无法正常展示,确定删除?',
|
||||
`删除[${data.name}]`,
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonType: 'danger',
|
||||
customClass: 'area-delete-dialog'
|
||||
}
|
||||
)
|
||||
.then(async () => {
|
||||
await deleteCustomGeoArea(data.id)
|
||||
await loadCustomGeoArea()
|
||||
if (!customTreeData.value[0].children?.length || data.id === curCustomGeoArea.id) {
|
||||
showCustomEmpty.value = true
|
||||
mapInstance?.destroy()
|
||||
mapInstance = null
|
||||
} else {
|
||||
showCustomEmpty.value = false
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
})
|
||||
}
|
||||
let areaNameMap: Record<string, string> = null
|
||||
const loadSubAreaOptions = () => {
|
||||
listSubAreaOptions().then(res => {
|
||||
subAreaOptions.value.splice(0, subAreaOptions.value.length, ...res.data)
|
||||
if (!areaNameMap) {
|
||||
areaNameMap = subAreaOptions.value.reduce((p, n) => {
|
||||
p[n.id] = n.name
|
||||
return p
|
||||
}, {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const showCustomEmpty = ref(false)
|
||||
const loadCustomSubArea = async (node, reload?) => {
|
||||
areaTreeRef.value?.setCurrentKey(null)
|
||||
if (node.id === '000' || (curCustomGeoArea.id === node.id && reload !== true)) {
|
||||
return
|
||||
}
|
||||
showCustomEmpty.value = false
|
||||
curCustomGeoArea.id = node.id
|
||||
getCustomGeoArea(node.id).then(res => {
|
||||
const tmpList = res.data.reduce((p, n) => {
|
||||
const ids = n.scope?.split(',') || []
|
||||
n.scopeArr = ids
|
||||
const nameArr = []
|
||||
ids.forEach(id => {
|
||||
nameArr.push(areaNameMap?.[id])
|
||||
})
|
||||
const area = {
|
||||
...n,
|
||||
scopeName: nameArr.join(',')
|
||||
}
|
||||
p.push(area)
|
||||
return p
|
||||
}, [])
|
||||
subAreaList.value.splice(0, subAreaList.value.length, ...tmpList)
|
||||
showGeoJson.value = false
|
||||
nextTick(() => {
|
||||
debounceRender()
|
||||
})
|
||||
})
|
||||
}
|
||||
// geoSubArea
|
||||
const customSubAreaDialog = ref(false)
|
||||
const customSubArea = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
scope: '',
|
||||
geoAreaId: '',
|
||||
scopeArr: []
|
||||
})
|
||||
const subAreaOptions = ref([])
|
||||
const subAreaList = ref([])
|
||||
const subAreaFormRef = ref()
|
||||
const areaRules = reactive<FormRules>({
|
||||
name: [
|
||||
{ required: true, message: '请输入名称', trigger: 'blur' },
|
||||
{ min: 1, max: 50, message: '名称长度为 1~50 格字符', trigger: 'blur' }
|
||||
],
|
||||
scopeArr: [{ type: 'array', required: true, message: '请选择区域', trigger: 'change' }]
|
||||
})
|
||||
const editCustomSubArea = (subArea?) => {
|
||||
customSubArea.geoAreaId = curCustomGeoArea.id
|
||||
if (!subArea) {
|
||||
customSubArea.name = ''
|
||||
customSubArea.scopeArr = []
|
||||
customSubArea.id = ''
|
||||
customSubArea.scope = ''
|
||||
} else {
|
||||
customSubArea.name = subArea.name
|
||||
customSubArea.scopeArr = subArea.scopeArr
|
||||
customSubArea.id = subArea.id
|
||||
customSubArea.scope = subArea.scope
|
||||
}
|
||||
customSubAreaDialog.value = true
|
||||
}
|
||||
const saveGeoSubArea = async () => {
|
||||
subAreaFormRef.value?.validate(async valid => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
customSubArea.scope = customSubArea.scopeArr.join(',')
|
||||
await saveCustomGeoSubArea(customSubArea)
|
||||
await loadCustomSubArea({ id: curCustomGeoArea.id }, true)
|
||||
customSubAreaDialog.value = false
|
||||
})
|
||||
}
|
||||
const deleteCustomSubArea = async data => {
|
||||
ElMessageBox.confirm('确定删除该自定义区域?', `删除[${data.name}]`, {
|
||||
type: 'warning',
|
||||
confirmButtonType: 'danger',
|
||||
customClass: 'area-delete-dialog'
|
||||
})
|
||||
.then(async () => {
|
||||
await deleteCustomGeoSubArea(data.id)
|
||||
await loadCustomSubArea({ id: curCustomGeoArea.id }, true)
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
})
|
||||
}
|
||||
loadCustomGeoArea()
|
||||
loadSubAreaOptions()
|
||||
|
||||
const AREA_COLOR = [
|
||||
'#1E90FF',
|
||||
'#90EE90',
|
||||
'#00CED1',
|
||||
'#E2BD84',
|
||||
'#7A90E0',
|
||||
'#3BA272',
|
||||
'#2BE7FF',
|
||||
'#0A8ADA',
|
||||
'#FFD700'
|
||||
]
|
||||
const mapOption: ChoroplethOptions = {
|
||||
map: {
|
||||
type: 'mapbox',
|
||||
style: 'blank'
|
||||
},
|
||||
geoArea: {
|
||||
type: 'geojson'
|
||||
},
|
||||
source: {
|
||||
data: [],
|
||||
joinBy: {
|
||||
sourceField: 'name',
|
||||
geoField: 'name'
|
||||
}
|
||||
},
|
||||
viewLevel: {
|
||||
adcode: 'all',
|
||||
level: 'world'
|
||||
},
|
||||
autoFit: true,
|
||||
chinaBorder: false,
|
||||
style: {
|
||||
stroke: 'grey',
|
||||
opacity: 1,
|
||||
lineWidth: 0.6,
|
||||
lineOpacity: 1
|
||||
},
|
||||
label: {
|
||||
field: 'name',
|
||||
style: {
|
||||
fill: 'black',
|
||||
textAnchor: 'center'
|
||||
}
|
||||
},
|
||||
state: {
|
||||
active: { stroke: 'green', lineWidth: 1 }
|
||||
},
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
// 禁用线上地图数据
|
||||
customFetchGeoData: () => null
|
||||
}
|
||||
let mapInstance: Choropleth = null
|
||||
const renderMap = async () => {
|
||||
if (!mapOption.source.joinBy.geoData) {
|
||||
const chinaGeoJson = cloneDeep(await getGeoJsonFile('156'))
|
||||
mapOption.source.joinBy.geoData = chinaGeoJson
|
||||
}
|
||||
const areaMap = mapOption.source.joinBy.geoData.features.reduce((p, n) => {
|
||||
if (n.properties['adcode']) {
|
||||
p['156' + n.properties['adcode']] = n
|
||||
}
|
||||
return p
|
||||
}, {})
|
||||
const areaTextLocation = []
|
||||
subAreaList.value?.forEach(area => {
|
||||
const areaJsonArr = []
|
||||
area.scopeArr?.forEach(adcode => {
|
||||
const json = areaMap[adcode]
|
||||
json && areaJsonArr.push(json)
|
||||
})
|
||||
if (areaJsonArr.length) {
|
||||
const areaJson: FeatureCollection = {
|
||||
type: 'FeatureCollection',
|
||||
features: areaJsonArr
|
||||
}
|
||||
const center = centroid(areaJson)
|
||||
areaTextLocation.push({
|
||||
name: area.name,
|
||||
x: center.geometry.coordinates[0],
|
||||
y: center.geometry.coordinates[1]
|
||||
})
|
||||
}
|
||||
})
|
||||
const areaTextLayer = new TextLayer({
|
||||
name: 'areaTextLayer',
|
||||
source: {
|
||||
data: areaTextLocation,
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'x',
|
||||
y: 'y'
|
||||
}
|
||||
},
|
||||
field: 'name',
|
||||
style: {
|
||||
fill: 'black',
|
||||
fontSize: 20,
|
||||
opacity: 1,
|
||||
fontWeight: 'bold',
|
||||
textAnchor: 'center'
|
||||
}
|
||||
})
|
||||
if (mapInstance) {
|
||||
const layer = mapInstance.getLayerByName('areaTextLayer')
|
||||
if (layer) {
|
||||
mapInstance.removeLayer(layer)
|
||||
}
|
||||
mapInstance.addLayer(areaTextLayer)
|
||||
mapInstance.update({})
|
||||
} else {
|
||||
mapOption.color = {
|
||||
field: ['name', 'adcode'],
|
||||
value: area => {
|
||||
let color = 'white'
|
||||
subAreaList.value?.forEach((subArea, i) => {
|
||||
if (subArea.scope?.includes(area.adcode)) {
|
||||
color = AREA_COLOR[i % AREA_COLOR.length]
|
||||
}
|
||||
})
|
||||
return color
|
||||
},
|
||||
scale: {
|
||||
type: 'quantize',
|
||||
unknown: 'white'
|
||||
}
|
||||
}
|
||||
mapInstance = new Choropleth('map-container', mapOption)
|
||||
mapInstance.on('loaded', () => {
|
||||
mapInstance.addLayer(areaTextLayer)
|
||||
})
|
||||
}
|
||||
}
|
||||
const debounceRender = debounce(renderMap, 500)
|
||||
onBeforeMount(() => {
|
||||
mapInstance?.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@ -225,19 +713,6 @@ loadTreeData(true)
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
.add-icon-span {
|
||||
color: var(--ed-color-primary);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
i {
|
||||
left: 2px;
|
||||
}
|
||||
&:hover {
|
||||
background: #1f232926;
|
||||
cursor: pointer;
|
||||
}
|
||||
border-radius: 2px;
|
||||
}
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.geo-search {
|
||||
@ -314,7 +789,6 @@ loadTreeData(true)
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: content-box;
|
||||
padding-right: 4px;
|
||||
overflow: hidden;
|
||||
justify-content: space-between;
|
||||
@ -333,5 +807,119 @@ loadTreeData(true)
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
.add-icon-span {
|
||||
color: var(--ed-color-primary);
|
||||
padding: 3px;
|
||||
border-radius: 3px;
|
||||
line-height: 1;
|
||||
&:hover {
|
||||
background: #1f232926;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.custom-area-root {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
padding-right: 4px;
|
||||
.label {
|
||||
max-width: 150px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.opt-icon {
|
||||
color: var(--ed-color-primary);
|
||||
padding: 3px;
|
||||
border-radius: 3px;
|
||||
line-height: 1;
|
||||
&:hover {
|
||||
background: #1f232926;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sub-area-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100;
|
||||
height: 100%;
|
||||
.map-container {
|
||||
flex: 7;
|
||||
}
|
||||
.ed-divider {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.sub-area-editor {
|
||||
flex: 3;
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.label {
|
||||
:first-child {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
:last-child {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.add-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: var(--ed-color-primary);
|
||||
padding: 3px;
|
||||
:first-child {
|
||||
margin-right: 2px;
|
||||
}
|
||||
&:hover {
|
||||
border-radius: 6px;
|
||||
background: #1f232926;
|
||||
}
|
||||
}
|
||||
}
|
||||
.area-color-symbol {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
display: inline-block;
|
||||
border-radius: 5px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
.area-edit-btn {
|
||||
color: var(--ed-color-primary);
|
||||
span {
|
||||
padding: 3px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
border-radius: 6px;
|
||||
background: #1f232926;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.area-opt-popper {
|
||||
margin-right: -20px !important;
|
||||
}
|
||||
|
||||
.area-delete-dialog {
|
||||
.ed-message-box__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
.ed-message-box__headerbtn {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
package io.dataease.api.map;
|
||||
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
|
||||
import io.dataease.api.map.vo.AreaNode;
|
||||
import io.dataease.api.map.vo.CustomGeoArea;
|
||||
import io.dataease.api.map.vo.CustomGeoSubArea;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "系统设置:自定义地理区域")
|
||||
@ApiSupport(order = 799)
|
||||
public interface CustomGeoApi {
|
||||
|
||||
@Operation(summary = "查询自定义地理区域")
|
||||
@GetMapping("/geoArea/list")
|
||||
List<CustomGeoArea> listCustomGeoArea();
|
||||
|
||||
@Operation(summary = "查询自定义地理区域详情")
|
||||
@GetMapping("/geoArea/{id}")
|
||||
List<CustomGeoSubArea> getCustomGeoArea(@PathVariable("id") String id);
|
||||
|
||||
@Operation(summary = "删除自定义地理区域")
|
||||
@DeleteMapping("/geoArea/{id}")
|
||||
void deleteCustomGeoArea(@PathVariable("id") String id);
|
||||
|
||||
@Operation(summary = "保存自定义地理区域")
|
||||
@PostMapping("/geoArea/save")
|
||||
void saveCustomGeoArea(@RequestBody CustomGeoArea geoArea);
|
||||
|
||||
@Operation(summary = "删除自定义地理子区域")
|
||||
@DeleteMapping("/geoSubArea/{id}")
|
||||
void deleteCustomGeoSubArea(@PathVariable("id") long id);
|
||||
|
||||
@Operation(summary = "保存自定义地理子区域")
|
||||
@PostMapping("/geoSubArea/save")
|
||||
void saveCustomGeoSubArea(@RequestBody CustomGeoSubArea geoSubArea);
|
||||
|
||||
@Operation(summary = "获取子区域下拉框可选列表")
|
||||
@GetMapping("/geoSubArea/options")
|
||||
List<AreaNode> getCustomGeoSubAreaOptions();
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package io.dataease.api.map.vo;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class CustomGeoArea implements Serializable {
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package io.dataease.api.map.vo;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class CustomGeoSubArea implements Serializable {
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
private String name;
|
||||
private String scope;
|
||||
private String geoAreaId;
|
||||
}
|
||||
@ -18,7 +18,7 @@ public class TokenFilter implements Filter {
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
String method = request.getMethod();
|
||||
if (!StringUtils.equalsAny(method, "GET", "POST", "OPTIONS")) {
|
||||
if (!StringUtils.equalsAny(method, "GET", "POST", "OPTIONS", "DELETE")) {
|
||||
HttpServletResponse res = (HttpServletResponse) servletResponse;
|
||||
res.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
|
||||
return;
|
||||
|
||||
@ -33,7 +33,7 @@ public class CorsConfig implements WebMvcConfigurer {
|
||||
.allowedOrigins(originList.toArray(new String[0]))
|
||||
.allowedHeaders("*")
|
||||
.maxAge(3600)
|
||||
.allowedMethods("GET", "POST");
|
||||
.allowedMethods("GET", "POST", "DELETE");
|
||||
}
|
||||
|
||||
public void addAllowedOrigins(List<String> origins) {
|
||||
|
||||
@ -25,6 +25,7 @@ public class CacheConstant {
|
||||
|
||||
public static class CommonCacheConstant {
|
||||
public static final String WORLD_MAP_CACHE = "de_v2_world_map";
|
||||
public static final String CUSTOM_GEO_CACHE = "de_v2_custom_geo";
|
||||
public static final String RSA_CACHE = "de_v2_rsa";
|
||||
public static final String PER_MENU_ID_CACHE = "de_v2_per_menu_id";
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@ public class WhitelistUtils {
|
||||
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/content")
|
||||
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/pluginStaticInfo")
|
||||
|| StringUtils.startsWithAny(requestURI, "/geo/")
|
||||
|| StringUtils.startsWithAny(requestURI, "/customGeo/")
|
||||
|| StringUtils.startsWithAny(requestURI, "/websocket")
|
||||
|| StringUtils.startsWithAny(requestURI, "/map/")
|
||||
|| StringUtils.startsWithAny(requestURI, "/oauth2/")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user