Skip to content

Commit

Permalink
【feature】图层列表支持自定义拖拽调整图层顺序
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongjiaojiao committed Sep 29, 2024
2 parents 9baf6a8 + 5ed76e3 commit 91a90dc
Show file tree
Hide file tree
Showing 17 changed files with 1,116 additions and 148 deletions.
12 changes: 11 additions & 1 deletion docs/zh/api/control/layer-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| iconClass | 收缩按钮的 Font class 类名 | string | - | 'sm-components-icon-layer-list' |
| headerName | 标题名 | string | - | '图层' |
| attributes | 属性表配置 <a href="#attributes">配置项</a> | object | - | - |
| layerOperations | 图层操作配置 <a href="#layeroperations">配置项</a> | object | - | - |

> 支持[主题混入参数](/zh/api/mixin/mixin.md#theme)[卡片混入参数](/zh/api/mixin/mixin.md#collapsedcard)[Control 混入参数](/zh/api/mixin/mixin.md#control)
Expand All @@ -27,4 +28,13 @@
| style | 属性表样式 | object | - | - |
| position | 属性表的位置。(父级容器需设置样式: positions: absolute ) | string | 'bottom-left' \| 'top-left' \| 'bottom-right' \| 'top-right' \| 'top' \| 'bottom' \| 'left' \| 'right' \| | 'bottom' |

> 更多配置项,请查看 [Attributes](/zh/api/common/attributes.md)(其中不支持layerName、dataset和mapTarget参数)。
> 更多配置项,请查看 [Attributes](/zh/api/common/attributes.md)(其中不支持layerName、dataset和mapTarget参数)。

### layerOperations

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| :--------------- | :------------------------------------------------------------------------ | :---------------- | :--------------------------------------------------------------------------------- | :--------------------------------------------------------------- |
| zoomToLayer | 是否开启缩放至图层功能 | boolean | - | true |
| layerOrder | 是否开启拖拽调整图层顺序功能 | boolean | - | false |
| opacity | 是否开启调整图层不透明度功能 | boolean | - | false |
9 changes: 6 additions & 3 deletions src/common/_lang/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ export default {
unavailableVideo: 'This video is temporarily unavailable, please try again later',
mapNotLoaded: 'The associated map has not been loaded yet, please wait for a second',
unassociatedMap: 'You need to configure the associated map!',
videojs:
'Please import video.js plugin: https://github.com/videojs/video.js',
videojs: 'Please import video.js plugin: https://github.com/videojs/video.js',
flvPlayer:
'Please import flv related plugin: https://github.com/bilibili/flv.js, https://github.com/mister-ben/videojs-flvjs'
},
Expand Down Expand Up @@ -81,7 +80,11 @@ export default {
unSupportedData: 'The current data does not support linkage with the map'
},
layerList: {
title: 'Layer'
title: 'Layer',
layerStyle: 'layerStyle',
attributes: 'Attributes',
zoomToLayer: 'zoomToLayer',
opacity: 'Opacity'
},
slideshow: {
title: 'Slideshow'
Expand Down
6 changes: 5 additions & 1 deletion src/common/_lang/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ export default {
unSupportedData: '当前数据不支持与地图联动'
},
layerList: {
title: '图层'
title: '图层',
layerStyle: '图层样式',
attributes: '属性表',
zoomToLayer: '缩放至图层',
opacity: '不透明度'
},
slideshow: {
title: '幻灯片'
Expand Down
92 changes: 92 additions & 0 deletions src/mapboxgl/_types/__tests__/map-event.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,97 @@ describe('map-event-mapboxgl', () => {
expect(legendList[0]).toEqual(mapLegends[1]);
done();
});

it('webmap get ordered LayerList', done => {
const layerList1 = [
{ id: 'layer1', renderSource: { id: 'source1' }, renderLayers: ['layer1'], visible: true },
{
id: 'group2',
type: 'group',
visible: true,
children: [{ id: 'sourceLayer', renderSource: { id: 'source2', sourceLayer: 'sourceLayer2' }, visible: true }]
}
];
const layerList2 = [
{ id: 'layer3', renderSource: { id: 'source3' }, renderLayers: ['layer3'], visible: true },
{
id: 'group4',
renderSource: {},
type: 'group',
visible: true,
children: [{ id: 'sourceLayer4', renderSource: { id: 'source4', sourceLayer: 'sourceLayer4' }, visible: true }]
}
]
const mainWebMap = {
getLayerList: () => layerList1
};
const webmap2 = {
getLayerList: () => layerList2,
cacheLayerCatalogIds: ['layer3', 'group4', 'sourceLayer4']
};
mapEvent.$options.setWebMap(mapTarget, mainWebMap);
mapEvent.$options.setWebMap(mapTarget, webmap2, 'webmap2');
const webmap = mapEvent.$options.getWebMap(mapTarget);
const layerList = webmap.getLayerList();
expect(layerList.length).toBe(4);
expect(layerList).toEqual(layerList2.concat(layerList1));
const OrderedLyerList = layerList1.concat(layerList2);
mapEvent.$options.setLayerCatalog(mapTarget, OrderedLyerList);
const newLayerList = webmap.getLayerList();
expect(newLayerList).toEqual(OrderedLyerList);
expect(mapEvent.$options.customLayerCatalogCache[mapTarget].length).toEqual(4);
mapEvent.$options.deleteWebMap(mapTarget, 'webmap2');
expect(mapEvent.$options.customLayerCatalogCache[mapTarget].length).toEqual(2);
done();
});

it('webmap get ordered AppreciableLayers', done => {
const mainWebMap = {
getAppreciableLayers: () => [
{ id: 'layer1', renderSource: { id: 'source1' } },
{ id: 'layer2', renderSource: { id: 'source2' } }
]
};
mapEvent.$options.setWebMap(mapTarget, mainWebMap);
const webmap = mapEvent.$options.getWebMap(mapTarget);
const appreciableLayers1 = webmap.getAppreciableLayers();
expect(appreciableLayers1[0].id).toBe('layer1');
const newLayerList = [
{ id: 'layer1', renderSource: { id: 'source1' } },
{ id: 'layer2', renderSource: { id: 'source2' } }
];
mapEvent.$options.setLayerCatalog(mapTarget, newLayerList);
const appreciableLayers2 = webmap.getAppreciableLayers();
expect(appreciableLayers2[0].id).toBe('layer2');
done();
});

it('group visible is determined by children', done => {
const layerList1 = [
{
id: 'group1',
type: 'group',
visible: false,
children: [{ id: 'sourceLayer1', renderSource: { id: 'source1', sourceLayer: 'sourceLayer1' }, visible: true }]
},
{
id: 'group2',
type: 'group',
visible: true,
children: [{ id: 'sourceLayer', renderSource: { id: 'source2', sourceLayer: 'sourceLayer2' }, visible: false }]
}
];
const mainWebMap = {
getLayerList: () => layerList1
};
mapEvent.$options.setWebMap(mapTarget, mainWebMap);
const webmap = mapEvent.$options.getWebMap(mapTarget);
const layerList = webmap.getLayerList();
expect(layerList[0].id).toBe('group1');
expect(layerList[0].visible).toBe(true);
expect(layerList[1].id).toBe('group2');
expect(layerList[1].visible).toBe(false);
done();
});
});

81 changes: 67 additions & 14 deletions src/mapboxgl/_types/map-event.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Vue from 'vue';
import globalEvent from 'vue-iclient/src/common/_utils/global-event';
import { flattenLayerCatalog, getGroupChildrenLayers, removeLayersByIds, sortLayerCatalog } from '../web-map/GroupUtil';

export default new Vue({
customLayerCatalogCache: {},
mapCache: {},
webMapCache: new Map(),
getMap: function (mapTarget) {
Expand Down Expand Up @@ -54,6 +56,20 @@ export default new Vue({
if (['getLegendInfo'].includes(propKey)) {
datas.push(...mainWebMapDatas);
}
if (propKey === 'getLayerList') {
if (_this.customLayerCatalogCache[webmapTarget]) {
datas = sortLayerCatalog(datas, _this.customLayerCatalogCache[webmapTarget]);
}
_this.updateLayerCatalogsVisible(datas);
datas = _this.updateImmutableOrderLayers(datas);
}
if (propKey === 'getAppreciableLayers') {
if (_this.customLayerCatalogCache[webmapTarget]) {
const flatterLayers = flattenLayerCatalog(_this.customLayerCatalogCache[webmapTarget]);
datas = sortLayerCatalog(datas, flatterLayers.reverse());
}
datas = _this.updateImmutableOrderLayers(datas, true);
}
return () => datas;
}
if (['changeItemVisible', 'setLayersVisible'].includes(propKey)) {
Expand Down Expand Up @@ -82,6 +98,9 @@ export default new Vue({
this.webMapCache.set(webmapTarget, []);
}
this.webMapCache.get(webmapTarget).push([identifyId, webmap]);
if (identifyId !== webmapTarget && webmap.map) {
webmap.map.fire('data', { dataType: 'style' });
}
},
deleteWebMap: function (webmapTarget, identifyId) {
if (!this.webMapCache.has(webmapTarget)) {
Expand All @@ -90,27 +109,61 @@ export default new Vue({
if (identifyId) {
const combinations = this.webMapCache.get(webmapTarget);
const matchIndex = combinations.findIndex(item => item[0] === identifyId);
matchIndex > -1 && combinations.splice(matchIndex, 1);
if (matchIndex > -1) {
const deleteDatas = combinations.splice(matchIndex, 1)[0];
const customLayerCatalog = this.customLayerCatalogCache[webmapTarget];
if (customLayerCatalog) {
this.customLayerCatalogCache[webmapTarget] = null;
removeLayersByIds(customLayerCatalog, deleteDatas[1].cacheLayerCatalogIds);
this.customLayerCatalogCache[webmapTarget] = customLayerCatalog;
}
}
return;
}
this.webMapCache.delete(webmapTarget);
delete this.customLayerCatalogCache[webmapTarget];
},
collectCatalogsKeys(layerCatalog) {
const ids = [];
for (const layer of layerCatalog) {
if (layer.renderLayers?.length) {
ids.push(...layer.renderLayers);
} else if (layer.renderSource?.id) {
ids.push(layer.renderSource.id);
} else {
ids.push(layer.id);
}
if (layer.children && layer.children.length > 0) {
ids.push(...this.collectCatalogsKeys(layer.children));
}
}
return ids;
},
collectCatalogsKeys(catalogs, list = []) {
setLayerCatalog(webmapTarget, data) {
this.customLayerCatalogCache[webmapTarget] = data;
},
updateLayerCatalogsVisible(catalogs) {
for (const data of catalogs) {
if (data.children && data.children.length > 0) {
this.collectCatalogsKeys(data.children, list);
continue;
}
let ids = [];
if (data.renderLayers?.length) {
ids.push(...data.renderLayers);
} else if (data.renderSource?.id) {
ids.push(data.renderSource.id);
} else {
ids.push(data.id);
const allChildren = getGroupChildrenLayers(data.children);
if (allChildren.length > 0) {
// parent 的显隐最终取决于 children,children 全隐藏,parent 隐藏,有个 children 显示,parent 显示
data.visible = allChildren.some(item => item.visible);
}
this.updateLayerCatalogsVisible(data.children);
}
list.push(...ids);
}
return list;
},
updateImmutableOrderLayers(layers, revert) {
let topLayers = layers.filter(item => item.layerOrder && item.layerOrder.toLowerCase() === 'top');
const migrationLayers = topLayers.filter(item => item.type === 'MIGRATION');
const leftTopLayers = topLayers.filter(item => item.type !== 'MIGRATION');
topLayers = migrationLayers.concat(leftTopLayers);
const bottomLayers = layers.filter(item => item.layerOrder && item.layerOrder.toLowerCase() === 'bottom');
const autoLayers = layers.filter(item => !item.layerOrder || item.layerOrder.toLowerCase() === 'auto');
if (revert) {
return bottomLayers.concat(autoLayers, topLayers);
}
return topLayers.concat(autoLayers, bottomLayers);
}
});
88 changes: 86 additions & 2 deletions src/mapboxgl/web-map/GroupUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,92 @@ export function getGroupChildrenLayers(layerGroup) {
continue;
}
// 图层组
const group = item;
targetItems.push(...getGroupChildrenLayers(group.children));
targetItems.push(...getGroupChildrenLayers(item.children));
}
return targetItems;
}

export function findLayerCatalog(layerCatalog, targetId) {
for (const layer of layerCatalog) {
if (layer.id === targetId) {
return layer;
}
if (layer.children && layer.children.length > 0) {
const found = findLayerCatalog(layer.children, targetId);
if (found) {
return found;
}
}
}
return null;
}

export function flattenLayerCatalog(layerCatalog) {
function collectionLayers(referenceNodes) {
const list = [];
for (const refNode of referenceNodes) {
const result = !refNode.children ? [refNode] : collectionLayers(refNode.children);
list.push(...result);
}
return list;
}
return collectionLayers(layerCatalog);
}

export function removeLayersByIds(layerCatalog, targetIds) {
function remove(layerCatalog, targetId) {
for (const layer of layerCatalog) {
if (targetId === layer.id) {
layerCatalog.splice(layerCatalog.indexOf(layer), 1);
return layer;
}
if (layer.children && layer.children.length > 0) {
const found = remove(layer.children, targetId);
if (found) {
return found;
}
}
}
}
return targetIds.map(targetId => remove(layerCatalog, targetId));
}

export function sortLayerCatalog(source, reference) {
function buildSortedTree(referenceNodes, remainingSourceNodes) {
const sorted = [];

// 遍历 referenceNodes,按 reference 的顺序构建 sorted 树
for (const refNode of referenceNodes) {
const sourceNode = findLayerCatalog(source, refNode.id);
if (sourceNode) {
// 如果在 source 中找到了对应的节点,则添加到 sorted 中,并从 remainingSourceNodes 中移除
sorted.push({ ...sourceNode });
removeLayersByIds(remainingSourceNodes, [sourceNode.id]);

if (refNode.children) {
sorted[sorted.length - 1].children = buildSortedTree(refNode.children, remainingSourceNodes);
}
}
}
return sorted;
}

// remainingSourceNodes 复制源节点数组以跟踪剩余节点
const remainingSourceNodes = [...source];
const sortedResult = buildSortedTree(reference, remainingSourceNodes);
// 将 remainingSourceNodes(即 source 中有但 reference 中没有的节点)添加到 sortedResult 的开头
sortedResult.unshift(...remainingSourceNodes);

return sortedResult;
}

export function getLayerCatalogIds(layerCatalog) {
const ids = [];
for (const layer of layerCatalog) {
ids.push(layer.id);
if (layer.children && layer.children.length > 0) {
ids.push(...getLayerCatalogIds(layer.children));
}
}
return ids;
}
Loading

0 comments on commit 91a90dc

Please sign in to comment.