布局容器处理
看完前面两章的讲解,我们对仪表盘系统有了一个大概的理解。接着我们讲讲更深入的应用。
上文讲解的编辑器只是局限于平铺的组件集。而在编辑器中,还会有一种组件是布局容器。它允许其他组件拖拽进入在里面形成自己的一套布局。典型的有分页卡、布局容器等组件。
布局组件的放置
与前两章提到的普通组件一致。
放置后添加普通组件到布局组件内
添加普通组件到布局组件内有两种方式:
第一种:点击添加(选用先选中布局组件)
怎么实现选中布局组件可以看前面的文章。选中后仓库state的selectedField为当前高亮的布局组件。
点击添加普通组件,根据仓库里的selectedField判断是否当前选中了布局组件,如果选中,则往选中的布局组件里添加普通组件,如果没有选中的组件或者选中的组件不是布局组件,则按前面章节的点击添加组件生成默认布局属性进入到整体布局之中。
async handleClickAddField(type) {
// 布局组件类型
const limitType = [
// 布局容器
DashboardControlTypeEnum.Container,
// 分页卡
DashboardControlTypeEnum.Tabs,
];
// 创建普通组件
let field = createDashboardField(type);
// 当前选择了组件,且选择的组件是布局组件
if (this.selectedField && limitType.includes(this.selectedField.type)) {
// 新增的普通组件添加父组件属性
field.parentId = this.selectedField.pkId;
}
...
}
上面的代码。只是判断了添加方式为往现有布局组件里添加了普通组件。
我们先看看如果是嵌套布局(布局容器里有普通组件),此时的this.layout是什么样的?
现在这个设计器里只有一个布局容器,布局容器里有一个普通组件
此时this.layout只有一个元素。也就是说grid-layout不承认重叠关系。那么这个画中画是怎么实现的呢?
也就是布局组件自己内部是个grid-layout,拖拽进去的普通组件被存储到布局组件自身属性上。设计器的grid-layout不关心布局组件内部有多少普通组件,也不关心布局组件内部普通组件怎么排列。
// 最外层布局的组件排列
var layoutFieleds = [
// 布局组件
{
layout:{x:0,y:0,w:30,h:30},
// 自身属性
widget: {
// 布局组件内的组件排列
layoutFields: [
layout:{x:0,y:0,w:10,h:10},
....
]
}
}
]
那么布局组件的内部怎么实现,我们放到后面讲
第二种:拖拽添加
拖拽模型是这样:
// index.vue
<div>
<a-card>
<control-list :dragType.sync="dragType" @add="handleClickAddField" />
</a-card>
<a-card :class="$style.main">
<drag-container
:dragType.sync="dragType"
/>
</a-card>
</div>
dragType = null;
// control-list.vue
...
<div :class="$style.list">
<div
v-for="control in group.list"
:key="control.type"
:draggable="true"
@dragstart="handleDragStart(control.type)"
@dragend="handleDragEnd"
@click.stop="handleAdd(control)"
>
<x-icon :type="control.icon" />
<span>{{ control.label }}</span>
</div>
</div>
handleDragStart(type) {
this.$emit('update:dragType', type);
}
handleDragEnd() {
this.$emit('update:dragType', null);
}
左边的组件栏拖拽事件只要关注抛出的dragType就行了(要么是空要么是拖拽过去的组件类型)
index.vue负责把dragType传入到<drag-container>组件里。其他不理会。
// drag-container.vue
<grid-layout
@dragover.native="handleDrag"
@dragleave.native="handleDragLeave"
@drop.native="handleDrop"
>
<grid-item
v-for="layoutItem in layout"
:key="layoutItem.i"
v-bind="getLayoutProps(layoutItem)"
>
<component
:is="getComponent(layoutItem)"
v-bind="getComponentProps(layoutItem)"
@inChildComponent="inChildComponent"
/>
</grid-item>
</grid-layout>
可以看到,拖拽到布局容器里也是在grid-layout层里处理,而不是gird-item层中特殊处理。
要看gird-layout上三个drag事件之前,我们需要先搞懂一个叫isInChildCom属性的判断逻辑
// drag-container.vue
@Watch('dragType')
handleDragTypeChange(type) {
this.isInChildCom = false; // 重新拖动需要重置
...
}
每次左侧组件栏成功触发/结束拖拽事件,drag-container里的isInChildCom属性就会初始化为false.
普通组件:
虽然vue-gird-layout里的item是不支持重叠的。 但是我们依然需要当拖拽组件进入普通组件里面,去emit inChildComponent事件,使drag-cpntainer里的isInChildCom为true。从而阻断后面的放置操作。
// 普通组件
<div
@dragenter="dragenter"
@dragover="dragover"
@dragleave="dragleave"
@drop="drop"
>
...
</div>
/** @name 进入-有效目标 **/
dragenter() {
this.$emit('inChildComponent', true);
}
dragleave(e) {
this.$emit('inChildComponent', false);
}
布局组件:
布局组件也一样。通过改变isInChildCom 的值为ture,让index.vue拦截拖拽事务。把添加组件的逻辑遗留在布局组件内部进行处理。
// 布局组件
<div
@dragenter="dragenter"
@dragover="dragover"
@dragleave="dragleave"
@drop="drop"
>
...
</div>
/** @name 进入-有效目标 **/
dragenter() {
// 当拖拽的组件是布局组件或无效组件时
if (this.limited) this.$emit('inChildComponent', true);
this.$emit('inChildComponent', true);
}
/** @name 离开-有效目标 **/
dragleave(e) {
if (this.limit) return;
const rect = this.$el.getBoundingClientRect();
const inside = rectContainPoint(rect, {
x: e.clientX,
y: e.clientY,
});
// 确认拖拽组件已经离开布局
if (!inside) {
this.$emit('inChildComponent', false);
// 更新内部布局
this.updateInside(e);
}
}
drop(e) {
if (this.limit) return
...
// 添加组件到布局组件内部属性
}
具体的实现请看最后一篇推文
放置后禁止拖拽布局组件到布局组件内
上一节已经说到了,在布局组件内部的drop系列事件里通过limit去限制(limit的逻辑就是判断拖拽进来的是不是布局组件)。如果是的话,把isInChildCom改成true拦截index层处理,同时布局组件内部也通过limit拦截处理。
删除布局组件内的普通组件
由布局组件内部控制。详情请最后一篇推文