form-create-designer (opens new window)是一个表单设计器,最近研究了下如何在它上面生成table slot,就是在一个table组件中预设了slot,然后尝试通过拖拽子组件完成这个table组件。
先说下普通插槽的拖拽是如何实现的,也就是一个组件只有一个默认slot的情况。方法如下:
- rule增加一个drag:true,children: []
- 组件增加一个slot,就可以向内拖拽了。
再说下table slot,我们的table组件是基于Element的,长这样:
<el-table
:data="tableData"
>
<template v-for="(item, cIndex) in columnData">
<el-table-column
v-if="item.type == 'operation'"
:key="item.field"
:prop="item.field"
:label="item.fieldName"
>
</el-table-column>
<el-table-column
v-else
:key="item.field"
:prop="item.field"
:label="item.fieldName"
>
<template slot-scope="scope">
<slot
v-if="item.slotName"
:scope="scope"
:name="item.slotName"
/>
<slot
v-else-if="item.isSlot"
:scope="scope"
:name="`${scope.$index}-${cIndex}`"
/>
{{ scope.row[item.field] }}
</template>
</el-table-column>
</el-table>
可以看到有两个基本的props,tableData、columnData,如果columnData的slotName存在就展示slot。
其实这里并没有必要非要给一个slotName,因为每个单元格的位置是确定的,所以这里可以简化为,指定了某一列的isSlot为true就可以,slot name自然就确定了。
<slot
v-if="item.isSlot"
:scope="scope"
:name="`${scope.$index}-${cIndex}`"
/>
再说说拖拽的实现,一开始尝试了下在table组件内增加draggable,包裹slot,然后为其增加dragAdd、dragEnd等事件。这种方法缺点是:
- 会增加大量table组件与父组件的值、事件的传递
- dragTool面板上还有一些事件要处理,会增加额外工作,且不好维护
- 不利于扩展,如果有类似需求的组件,难以复用
所以目前采用的方法是,利用rule.children生成slot类型的DragBox,前置知识在这里 (opens new window)。
要用好rule,需要知道它的数据结构,以及它在拖拽过程中是如何维护的,基本是这样:DragBox => DragTool => 子组件
,比如:
rule: [{
type: 'DragBox',
children: [{
type: 'DragTool',
children: [{
type: 'test-comp',
children: [{
type: 'dragBox',
children: [{
type: 'DragTool',
children: [{
type: 'span',
}],
}],
}],
}],
}],
}],
table slot有两种情况,一种是只有占位Box,还没有拖拽子组件,另一种是已经拖拽了子组件。
- 如果是第一种只有占位的情况,根据tableData和columnData生成空的DragBox就可以,注意声明DragBox的slot值。
- 如果是第二种情况,根据子组件的类型生成rule,然后为其包裹DragBox、DragTool。同样的,在最外层声明slot值。
其实也很简单,主要思路就是构造一个可渲染的slot块,过程中要尽可能地复用之前的逻辑(拖动、增加、删除、激活等)。核心逻辑如下:
function makeRule(rue) {
if (config.drag) {
const children = [];
if (rule.children.length) {
const list = [];
rule.children.map((item) => {
if (item.type) {
const tempRule = this.makeRule(ruleList[item.type]);
const tempRule2 = this.makeDragRule([tempRule], item.slot);
tempRule2[0].slot = item.slot;
list.push(...tempRule2);
} else {
const tempRule = this.makeDrag(config.drag, 'draggable', [], {
end: (inject, evt) => this.dragEnd(inject.self.children, evt),
add: (inject, evt) => this.dragAdd(inject.self.children, evt),
start: (inject, evt) => this.dragStart(inject.self.children, evt),
unchoose: (inject, evt) => this.dragUnchoose(inject.self.children, evt),
}, {
name: 'fade',
tag: 'div',
isSlot: item.slot,
});
tempRule.slot = item.slot;
list.push(tempRule);
}
});
rule.children = list;
} else {
rule.children.push(drag = this.makeDrag(config.drag, rule.type, children, {
end: (inject, evt) => this.dragEnd(inject.self.children, evt),
add: (inject, evt) => this.dragAdd(inject.self.children, evt),
start: (inject, evt) => this.dragStart(inject.self.children, evt),
unchoose: (inject, evt) => this.dragUnchoose(inject.self.children, evt),
}));
}
}
}