Vue.Draggable 心得
Posted sp42a
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue.Draggable 心得相关的知识,希望对你有一定的参考价值。
Vue.Draggable 为基于 Sortable.js 的 Vue 组件,用以实现拖拽功能。
一般用法
- Vue.Draggable 当界面拖动的时候 data 也同时在变化
- Vue.Draggable 新版本废弃了 options 属性,建议使用 v-bind 属性作为配置项
html vs. Render VNode
一开始,我用标签的方法,基本可以实现拖放,每处要拖放的地方,在子元素里面加多一层 <draggable />
即可。因为组件嵌套组件,肯定涉及递归的概念。组件还好可以封装为 Vue 组件,从而实现递归,例如这个:
<template>
<!--被渲染的组件-->
<span>
<Input v-if="item.type === 'input_text'" @click.native.stop="activeSection($event, mainData.item)" @mouseenter.native.stop="showHoverBorder($event, item)" @mouseleave.native.stop="hideHoverBorder" :data-uid="item.uid" type="text" class="input" />
<Input @click.native="activeSection($event, item)" @mouseenter.native.stop="showHoverBorder($event, item)" :data-uid="item.uid" type="password" class="input" password v-if="item.type === 'input_password'" />
<Input @click.native="activeSection($event, item)" @mouseenter.native.stop="showHoverBorder($event, item)" :data-uid="item.uid" type="textarea" class="input" :rows="5" v-if="item.type === 'input_textarea'" />
</span>
</template>
<script lang="ts">
import SelectionMixins from "./selection-mixins";
export default
mixins: [SelectionMixins],
props:
metaData: required: false ,
mainCmp:
required: false,
default()
// 不知为何传不了进来
// @ts-ignore
return window.DESIGNER_MAIN_CMP;
,
,
mainData: Object,
item: type: Object, required: true ,
isWysiwyg: type: Boolean, required: false, default: true ,
,
methods: ,
;
</script>
<style lang="less" scoped>
.input
width: 400px;
margin-right: 10px;
</style>
然后里面调用。但是,对于布局类的嵌套,标签无法实现递归……
<div v-for="(item, index) in metaData" :key="item.type +'_'+ index">
<!-- 表单布局 -->
<Form v-if="item.type == 'Form'" :label-width="form_cfg.labelWidth" class="widget-form" :label-position="form_cfg.labelPosition" :data-uid="item.uid" @mouseenter.native.stop="showHoverBorder($event, item)" @mouseleave.native.stop="hideHoverBorder" @click.native.stop="activeSection($event, item)">
<draggable v-model="item.children" group="FormItem" chosenClass="chosen" forceFallback="true" animation="1000" @end="activeCmp=null;">
<FormItem v-for="(formItem, formItem_index) in item.children" :key="formItem.type +'_'+ formItem_index" :label="formItem.label" @mouseenter.native.stop="showHoverBorder($event, formItem)" @mouseleave.native.stop="hideHoverBorder" @click.native.stop="activeSection($event, formItem)" :data-uid="formItem.uid">
<draggable tag="span" v-model="formItem.children" group="FormItemWidget" chosenClass="chosen" forceFallback="true" animation="1000" @add="onDrapNewAdd" @end="activeCmp=null;">
<span v-for="(formEl, formEl_index) in formItem.children" :key="formEl.type +'_'+ formEl_index">
<!-- 插入栅格 -->
<Row v-if="formEl.type === 'Row'" class="column-container" :data-uid="formEl.uid" @click.native.stop="activeSection($event, formEl)" @mouseenter.native.stop="showHoverBorder($event, formEl)" @mouseleave.native.stop="hideHoverBorder">
<draggable tag="Col" v-model="formEl.children" group="FormItem" chosenClass="chosen" forceFallback="true" animation="500" v-for="(ColItem, ColItem_index) in formEl.children" :key="ColItem.type +'_'+ ColItem_index" :component-data="getColSpan(formEl.children)" :data-uid="ColItem.uid" @start="activeCmp=null;" @click.native.stop="activeSection($event, ColItem)" @mouseenter.native.stop="showHoverBorder($event, ColItem)" @mouseleave.native.stop="hideHoverBorder">
<RenderCmp v-for="(ColItemWidget, ColItemWidget_index) in ColItem.children" :key="ColItemWidget.type +'_'+ ColItemWidget_index" :item="ColItemWidget" :main-cmp="mainCmp" :meta-data="metaData" :is-wysiwyg="true" />
</draggable>
<draggable tag="Col" v-if="!formEl.children || !formEl.children.length" v-model="formEl.children" group="FormItem" chosenClass="chosen" forceFallback="true" animation="500" :component-data="getColSpan(formEl.children)">
<div class="emptyContainerText">emptyContainerText</div>
</draggable>
</Row>
<RenderCmp v-else :item="formEl" :main-cmp="mainCmp" :meta-data="metaData" :is-wysiwyg="true" />
</span>
<!-- EMPTY DUMMY FOR INCOMING Drop -->
<div v-if="!formItem.children || !formItem.children.length" class="emptyContainerText">emptyContainerText</div>
</draggable>
</FormItem>
</draggable>
</Form>
</div>
此路不通,于是考虑能不能读取 JSON 元数据,然后让 Vue 渲染即可(如果要额外修改一些组件的如 DD,在解析过程中配置即可)。这个很容易联想到 Render 函数去解决。解析递归遍历 JSON 都不是问题,比较头大的是 createElement()
函数(或叫 h()
),每个节点都要对其 h() 返回 VNode,返回的结构给父节点作为参数传入。这个我尝试了很多方法都不行,唉 还是数据结构的知识不行。
没办法,只能求救网络,搜索一番找到两个方法:
第一个方法依然还是使用递归,就几行代码给完美地解决了……
/**
* Create component is a recursive function that takes a DOM structure
* and a rendering function (of vue.js) and returns a Vuejs component.
*/
const createComponent = (dNode, h: CreateElement) =>
// Handle empty elements and return empty array in case the dNode passed in is empty
if (_.isEmpty(dNode))
return [];
// if the el is array call createComponent for all nodes
if (_.isArray(dNode))
return dNode.map((child) => createComponent(child, h));
let children = [];
if (dNode.children && dNode.children.length > 0)
dNode.children.forEach((c) =>
if (_.isString(c))
children.push(c)
else
children.push(createComponent(c, h))
);
// Need to clone
const properties = _.cloneDeep(dNode.properties)
return h(dNode.tagName, properties, children.length > 0 ? children : dNode.textNode)
非标签方法使用 Vue.Draggable
既然使用了 Render 生成,那么 Vue.Draggable 也不能用标签。但全网几乎没有人那么做,还只能找到一个老外的:How to make Vue-Draggable work with different structure of elements/components,但他用的有点奇怪。
其实是可行的,问题在于没有 v-model
……
绑定数据
开明宗义,Vue 中 Render 函数里没有与 v-model 相对应的 API,因此若想实现双向的数据绑定需要自己来实现逻辑。
一开始标签的时候,Vue.Draggable 帮我做了很多事,我不禁 Vue.Draggable 的强大,秒实现拖放并同步修改数据。但改成 Render 后,我的心态是这样的。
没办法,自己写。但后来觉得也不是那么难……
/**
* 配置可以拖放
*
* @param children 数组
* @param group 分组
* @returns 组件的配置,特别这是给 <draggable> 元素用的属性
*/
makeDragDropProp(children: RenderedMeta[], group: string): any
return
props: list: children ,
attrs: group: group, chosenClass: "chosen", forceFallback: true, animation: 500 ,
on:
/**
* 使用 Render/Vnode 的代价是无法使用 V-Model 双向绑定。
* 此时 Vue.Draggable 令 DOM 变化了但没同步修改 Data,要自己给出。
* 百度不了任何资料。不过自己写,其实也不难。
* 主要是 list 输入数据(Array),然后 change 事件里面同步数据
*
* @param $event
*/
change: ($event: any): void =>
let e: any;
if ($event.moved)
e = $event.moved;
let newIndex: number = e.newIndex, oldIndex: number = e.oldIndex;
let old: any = children[oldIndex];
let temp: any = children[newIndex];
children[newIndex] = old;
children[oldIndex] = temp;
else if ($event.added)
e = $event.added;
children.splice(e.newIndex, 0, e.element);
else if ($event.removed)
e = $event.removed;
children.splice(e.oldIndex, 1);
else
alert('todo');
debugger
this.$forceUpdate();
,
end: () =>
this.ActiveWdiget = null;
,
tag 属性的使用
在 iView 组件中,Row DOM 下面不能有因为 DD 生成的 div
,那样会破坏 Row/Col 的结构,故用 tag,减少一层 div
。于是这时候 tag 属性可以派上用场了。
参考
- Vue.Draggable 主页 https://github.com/SortableJS/Vue.Draggable
- 很好的中文文档:https://www.itxst.com/vue-draggable/tutorial.html
- vuedraggable实际开发中的细节问题 https://segmentfault.com/a/1190000010078042
- 替代:awe-dnd https://github.com/hilongjw/vue-dragging
以上是关于Vue.Draggable 心得的主要内容,如果未能解决你的问题,请参考以下文章
解决 vue.draggable 拖拽 点击事件失效无效与拖拽事件冲突的问题