第二节:创建图层菜单,定义图层管理器
上一节完成后,我们已经创建了地图,根组件,并且在根组件中渲染了测试的图层菜单以及工具按钮,这一节我们将创建一个完整的图层菜单,并且对如何创建、删除图层做一些思考,最终设计一个用于管理这个项目图层的图层管理器。
创建图层菜单
接下来,我们先把图层菜单构建出来,我们只需要对我们需要实现的图层进行分析,即可通过配置文件配置出菜单。 从上一节的菜单数据源的定义可以看出,我们只要一个IMenuItem数组就可以,目前可以明确的是有气象实况和GFS预报两部分,因此我们分别进行创建。
在哪里创建配置信息
气象实况菜单
const realTimeConfig = {
name: "气象实况",
childs: [
{
name: "Metar报",
id: "realtime_metar",
},
{
name: "卫星云图",
id: "realtime_sate",
}
],
id: "realtime"
}
<MenuList
dataSource={[realTimeConfig]}
selectedItemIds={["test"]}
mode="inline"
theme="dark"
/>
上面我们将原来的测试数据源改成了我们刚创建的实时信息数据源,于是我们的拥有了实况菜单:
GFS预报菜单
const gfsConfig = {
name: "GFS预报",
childs: [
{
name: "2米温度-024",
id: "gfs_temp2m_024"
},
{
name: "累计降水-024",
id: "gfs_accprecp_024"
},
{
name: "850hPa风场-024",
id: "gfs_850uv_024"
},
{
name: "2米相对湿度-024",
id: "gfs_rhu2m-024"
},
{
name: "零度层相对湿度-024",
id: "gfs_0rhu_024"
},
{
name: "地面气压-024",
id: "gfs_pslp_024"
}
],
id: "gfs"
}
<MenuList
dataSource={[realTimeConfig,gfsConfig]}
selectedItemIds={["test"]}
mode="inline"
theme="dark"
// defaultOpenKeys={props.layerStore.menuData.map(g => g.id)}
/>
设置默认展开
这里默认菜单是关闭的,点击后可以打开,这与我们通常的业务需要不太吻合,我们希望有些菜单是默认打开的,本示例中我们把所有菜单都默认打开,这就需要设置defaultOpenKeys属性,这个属性是antd的菜单自带属性,我们只需要做一个遍历就可以把所有菜单都打开,在这之前,我们先把这两个菜单合并成一个配置对象menuConfig:
const menuConfig=[
//分别把realtime和gfs的配置写到这里
]
接下来设置默认打开
<MenuList
dataSource={menuConfig}
selectedItemIds={["test"]}
mode="inline"
theme="dark"
defaultOpenKeys={menuConfig.map(g => g.id)}
/>
响应菜单事件
要让图层能在地图显示和删除,就需要响应图层菜单的选中和取消选中事件,MenuList中已经实现了相关的回调属性 onItemSelected
和onItemDeSelect
,因此创建function来进行响应:
...
async function selectItem(){
}
function deSelectItem() {
}
return (
...
<MenuList
dataSource={menuConfig}
selectedItemIds={["test"]}
mode="inline"
theme="dark"
defaultOpenKeys={menuConfig.map(g => g.id)}
onItemSelected={selectItem}
onItemDeSelect={deSelectItem}
...
)
可以看到,选中事件的响应函数是异步的,因为后续要加载图层,获取数据是异步操作。
设定当前选中状态
默认我们还是选中的test菜单,但是当前并没有test菜单,我们就需要一个状态来表示当前选中的所有菜单项,因此使用useState来进行状态处理:
const defaults: IMenuItem[] = [];
const [selected, setSelected] = useState(defaults);
...
<MenuList
...
selectedItemIds={selected.map(i => i.id)}
...
/>
...
可以看到,选中菜单是一个数组,数组里面的对象是图层配置的菜单项,这样可以在后面获取当前菜单的各种信息时更加方便。
接下来,就需要在选中和取消选中的事件中对选中的项进行状态设定,如果是单选模式,那就非常简单,只要在选中的时候 setSelected([item]),在取消选中的时候setSelected([])即可。
菜单支持多选
但是咱们目前计划支持单选和多选,首先我们需要指定菜单支持多选,这只要在MenuList创建的时候设置即可:
<MenuList
...
multiple={true}
/>
图层叠加与不叠加
为了实现图层叠加和不叠加,就需要在图层的配置项中具有单选和多选的信息,可以通过IMenuItem的userData对象来设定这个信息,userData是用户自定义信息,是一个对象,在这里,我们在userData对象中增加一个overlay
属性,属性值是boolean类型,这样未设置或者设置为false表示不支持多层叠加,如果希望支持,只要把这个值设置为true就行,目前,气象实况的站点实况希望支持的,其配置修改如下:
{
name: "Metar报",
id: "realtime_metar",
userData: {
overlay: true
}
},
有了这样的配置后,就可以在选中和取消选中的中考虑单选和多选的逻辑,这部分代码如下所示:
async function selectItem(item: IMenuItem) {
let items = [];
if (selected.length === 0) {
items = [item];
} else if (item.userData?.overlay) {
items = [...selected, item]
} else {
//还没有不可叠加的图层加入
if (selected[0].userData?.overlay) {
items = [item, ...selected];
} else {
//第一个图层是不可叠加图层,要去掉
items = [...selected];
//todo:remove layer
items[0] = item;
}
}
setSelected(items);
//todo add layer
}
function deSelectItem(item: IMenuItem) {
const itemIdx = selected.findIndex(i => i.id === item.id);
if (itemIdx < 0) {
message.error("当前选中的节点有误!");
return;
}
const items = [...selected];
items.splice(itemIdx, 1);
setSelected(items);
//todo remove layer
}
到目前为止,我们已经可以在菜单上实现点击后选中和取消的效果了,接下来就可以开始跟数据及图层进行关联了。我们在两个响应方法中留下来的todo就是做这个事情。
图层管理器
最简单的图层关联方式,就是在上面的selectItem和deSelectItem中预留的todo地方进行图层的创建和删除,但是咱们需要显示的图层类型较多,全部都放到这个UI组件中会显得很臃肿,而且扩展性也不好,因此我们把图层相关的内容抽取到组件外部,然后作为组件的属性传入,外部的这些图层相关的属性和方法我们使用一个类来进行封装,在这里,我们称为LayerStore
,说明这是一个对图层进行操作的类。
设计LayerStore功能
LayerStore需要至少具备以下功能:
因此,该类的结构如下:
class LayerStore {
public menuData: IMenuItem[] = [
//原来UI组件中的menuConfig内容
];
public async createLayer(item: IMenuItem): Promise<boolean> {
return true;
}
public removeLayer(item: IMenuItem) {
}
}
同时,对原来的组件进行微调,设计一个props的类型,然后将以上图层管理类的实例通过props传入,并且把menuData绑定到MenuList的dataSource后删除组件中的menuConfig:
interface IAppProps {
layerStore: LayerStore
}
...
const AviationApp: React.FC<IAppProps> = (props) => {
...
return (
<MenuList
dataSource={props.layerStore.menuData}
...
defaultOpenKeys={props.layerstore.menuData.map(g => g.id)}
...
/>
)
...
}
...
const load = async () => {
...
const layerStore = new LayerStore();
ReactDOM.render(<AviationApp
layerStore={layerStore}
/>, document.getElementById("plugins"));
}
...
绑定图层创建和删除方法
图层管理器中已经有了图层的添加和删除方法,我们将其绑定到UI组件中:
async function selectItem(item: IMenuItem) {
let items = [];
if (selected.length === 0) {
items = [item];
} else if (item.userData?.overlay) {
items = [...selected, item]
} else {
//还没有不可叠加的图层加入
if (selected[0].userData?.overlay) {
items = [item, ...selected];
} else {
//第一个图层是不可叠加图层,要去掉
items = [...selected];
props.layerStore.removeLayer(selected[0]);
items[0] = item;
}
}
setSelected(items);
props.layerStore.createLayer(item);
}
function deSelectItem(item: IMenuItem) {
...
setSelected(items);
props.layerStore.removeLayer(item);
}
这样,我们就完成了图层管理器的基础结构了,接下来要做的就是真正的创建和删除图层了。
默认选中菜单
如果我们希望有些图层一开始就有,那就需要在组件初始化完成后,就自动选中这些菜单,因此,我们需要在组件加载完成的状态中进行setSelected操作,而默认选中的项,我们同样可以放到LayerStore中:
public defaultSelectedItemIds = ["realtime_metar"];
实现这个需要使用useEffect,只要在组件加载的时候,对当前选中的菜单进行选择即可。
useEffect(() => {
//初始化逻辑
if (props.layerStore.defaultSelectedItemIds) {
const selectedItems: IMenuItem[] = [];
const getSelectedItems = (items: IMenuItem[]) => {
items.forEach(i => {
if (i.childs?.length) {
getSelectedItems(i.childs);
} else if (props.layerStore.defaultSelectedItemIds.indexOf(i.id) >= 0) {
selectedItems.push(i);
}
});
};
getSelectedItems(props.layerStore.menuData);
selectItems(selectedItems);
}
}, []);
async function selectItems(items: IMenuItem[]) {
setSelected(items);
for (const item of items) {
await selectItem(item, false);
}
}
async function selectItem(item: IMenuItem, update = true) {
if (update) {
...
}
props.layerStore.createLayer(item);
}
现在,可以前往下一节开始真正接入数据了!
No Comments