因为标签组件的表现和交互逻辑等都是一致的,这里做了一个公共可复用的标签组件,对外接收两个参数:title(标签文本)和type(标签类型)。在标签容器组件创建一个包含所有标签组件数据对象的数组,在模板视图中遍历这个数组,就创建了所有的标签组件。
公共标签组件的统一的属性和方法等存入了一个对象字面量里面,导入以后通过mixin方式混合,组件就会拥有这些属性和方法。目前这样做的意义不大,因为已经有一个公共的标签组件了,mixin里面的东西完全可以直接写到这个公共组件内。但如果每个类型的标签组件都是一个单独的.vue组件文件,mixin的好处就体现出来了:可复用、易维护。
具体实现的代码,省略掉样式
//labelWrapper.vue 标签组件容器(组件标签区域) <template> <div> <div>组件标签区域</div> <div> <common-label v-for="label in labelArr" :title="label.title" :type="label.type"></common-label> </div> </div> </template> <script> import commonLabel from './widget/commonLabel.vue' //导入公共标签组件 export default { name : "label_wrapper", components : { commonLabel //注册为子组件(es6同名对象字面量缩写) }, data (){ return { labelArr : [ {title : "节标题", type : "sectionTitle"}, {title : "投票", type : "vote"}, {title : "正文", type : "content"}, {title : "用户", type : "user"}, {title : "图片", type : "image"}, {title : "视频", type : "video"}, {title : "音频", type : "audio"}, {title : "跳转链接", type : "link"} ] } } } </script> <style lang="stylus"> /*...*/ </style>
//commonLabel.vue 公共标签组件 <template> <div title="拖入模板设计区域" draggable="true" @dragstart="dragStart"> <img alt="{{title}}" :src="iconUrl"> <span>{{title}}</span> </div> </template> <script> //导入mixin import labelMixin from './mixin/labelMixin' export default { name : "label", props : { title : String, type : String }, mixins : [labelMixin], computed : { iconUrl (){ return this.type + '.png' } } } </script> <style lang="stylus"> /*...*/ </style>
//labelMixin.js import typeDataMap from './typeDataMap' export default { methods : { dragStart (e){ var id = parseInt(Date.now() + "" + parseInt(Math.random() * 90)) var widgetData = typeDataMap[this.type] var dt = e.dataTransfer widgetData['id'] = id dt.setData("id", id) dt.setData("type", this.type) dt.setData("widgetData", JSON.stringify(widgetData)) } } }
预览组件
预览组件相对较简单,除了数据的绑定,就是拖动排序。拖动排序的实现是通过html5原生的drag事件,基于vue数据驱动的原理,拖动的时候并不需要去手动改变预览区域内各组件的DOM顺序,只需要改变组件数据数组里面各数据对象的index即可,数据的变化会反应到DOM上。简单的节标题预览组件:
<template> <div draggable="true" :class="{'active': isActive}" @click="showEdit" @dragover="allowDrop" @dragstart="dragStart" @drop="dropIn" > <span :class="{'active': isActive}" title="删除该组件"> <div v-on:click="delMe">x</div> </span> <label>- 节标题 -</label> <div> <div>{{text}}</div> </div> </div> </template> <script> //导入action import {addPreviewAndData, deleteWidgetPreview, changeWidgetEdit, changPreviewAndDataIndex} from '../../../store/actions' //导入mixin import previewMixin from './mixin/previewMixin' export default { name : "sectionTitle_preview", mixins : [previewMixin], props : { id : Number, index : Number }, computed : { //mixin外的私有属性 text (){ for (let value of this.widgetDataArr) if (value.id == this.id) return value.text } }, vuex : { //绑定mixin需要的属性和方法 getters : { widgetDataArr : (state) => state.widgetDataArr, currentEditWidgetId : (state) => state.currentEditWidgetId }, actions : { addPreviewAndData, deleteWidgetPreview, changeWidgetEdit, changPreviewAndDataIndex } } } </script> <style lang="stylus"> /*...*/ </style>
/** * previewMixin.js * 预览组件的mixin * @提取同类组件之间可复用的计算属性与方法 */ export default { computed : { //该预览组件是否为当前点击的 isActive (){ return this.id == this.currentEditWidgetId } }, methods : { //删除该预览组件 delMe (){ this.deleteWidgetPreview(this.id) }, //显示该预览组件对应的编辑组件 showEdit (){this.changeWidgetEdit(this.id) }, //允许向该预览组件拖放其他组件 allowDrop (e){ e.preventDefault(); }, //开始拖放该预览组件 dragStart (e){ var dt = e.dataTransfer dt.setData("index", this.index) }, //向该预览组件拖放其他组件(预览组件或者标签组件) dropIn (e){ e.preventDefault() e.stopPropagation() var dt = e.dataTransfer var id = parseInt(dt.getData("id")) if (id){ //有id表明拖入的是标签组件 var type = dt.getData("type") var widgetData = JSON.parse(dt.getData("widgetData"))this.changeWidgetEdit(id) this.addValidation(id) //添加组件验证项 } else { var index = parseInt(dt.getData("index")) this.changPreviewAndDataIndex(index, this.index) } //清空dataTransfer dt.clearData() } } }
编辑组件
还是以节标题组件为例: