| <template> | 
|   <div class="container"> | 
|     <div class="left-board"> | 
|       <div class="logo-wrapper"> | 
|         <div class="logo"> | 
|           <img :src="logo" alt="logo"> Form Generator | 
|         </div> | 
|       </div> | 
|       <el-scrollbar class="left-scrollbar"> | 
|         <div class="components-list"> | 
|           <div class="components-title"> | 
|             <svg-icon icon-class="component" />输入型组件 | 
|           </div> | 
|           <draggable | 
|             class="components-draggable" | 
|             :list="inputComponents" | 
|             :group="{ name: 'componentsGroup', pull: 'clone', put: false }" | 
|             :clone="cloneComponent" | 
|             draggable=".components-item" | 
|             :sort="false" | 
|             @end="onEnd" | 
|           > | 
|             <div | 
|               v-for="(element, index) in inputComponents" :key="index" class="components-item" | 
|               @click="addComponent(element)" | 
|             > | 
|               <div class="components-body"> | 
|                 <svg-icon :icon-class="element.tagIcon" /> | 
|                 {{ element.label }} | 
|               </div> | 
|             </div> | 
|           </draggable> | 
|           <div class="components-title"> | 
|             <svg-icon icon-class="component" />选择型组件 | 
|           </div> | 
|           <draggable | 
|             class="components-draggable" | 
|             :list="selectComponents" | 
|             :group="{ name: 'componentsGroup', pull: 'clone', put: false }" | 
|             :clone="cloneComponent" | 
|             draggable=".components-item" | 
|             :sort="false" | 
|             @end="onEnd" | 
|           > | 
|             <div | 
|               v-for="(element, index) in selectComponents" | 
|               :key="index" | 
|               class="components-item" | 
|               @click="addComponent(element)" | 
|             > | 
|               <div class="components-body"> | 
|                 <svg-icon :icon-class="element.tagIcon" /> | 
|                 {{ element.label }} | 
|               </div> | 
|             </div> | 
|           </draggable> | 
|           <div class="components-title"> | 
|             <svg-icon icon-class="component" /> 布局型组件 | 
|           </div> | 
|           <draggable | 
|             class="components-draggable" :list="layoutComponents" | 
|             :group="{ name: 'componentsGroup', pull: 'clone', put: false }" :clone="cloneComponent" | 
|             draggable=".components-item" :sort="false" @end="onEnd" | 
|           > | 
|             <div | 
|               v-for="(element, index) in layoutComponents" :key="index" class="components-item" | 
|               @click="addComponent(element)" | 
|             > | 
|               <div class="components-body"> | 
|                 <svg-icon :icon-class="element.tagIcon" /> | 
|                 {{ element.label }} | 
|               </div> | 
|             </div> | 
|           </draggable> | 
|         </div> | 
|       </el-scrollbar> | 
|     </div> | 
|   | 
|     <div class="center-board"> | 
|       <div class="action-bar"> | 
|         <el-button icon="el-icon-download" type="text" @click="download"> | 
|           导出vue文件 | 
|         </el-button> | 
|         <el-button class="copy-btn-main" icon="el-icon-document-copy" type="text" @click="copy"> | 
|           复制代码 | 
|         </el-button> | 
|         <el-button class="delete-btn" icon="el-icon-delete" type="text" @click="empty"> | 
|           清空 | 
|         </el-button> | 
|       </div> | 
|       <el-scrollbar class="center-scrollbar"> | 
|         <el-row class="center-board-row" :gutter="formConf.gutter"> | 
|           <el-form | 
|             :size="formConf.size" | 
|             :label-position="formConf.labelPosition" | 
|             :disabled="formConf.disabled" | 
|             :label-width="formConf.labelWidth + 'px'" | 
|           > | 
|             <draggable class="drawing-board" :list="drawingList" :animation="340" group="componentsGroup"> | 
|               <draggable-item | 
|                 v-for="(element, index) in drawingList" | 
|                 :key="element.renderKey" | 
|                 :drawing-list="drawingList" | 
|                 :element="element" | 
|                 :index="index" | 
|                 :active-id="activeId" | 
|                 :form-conf="formConf" | 
|                 @activeItem="activeFormItem" | 
|                 @copyItem="drawingItemCopy" | 
|                 @deleteItem="drawingItemDelete" | 
|               /> | 
|             </draggable> | 
|             <div v-show="!drawingList.length" class="empty-info"> | 
|               从左侧拖入或点选组件进行表单设计 | 
|             </div> | 
|           </el-form> | 
|         </el-row> | 
|       </el-scrollbar> | 
|     </div> | 
|   | 
|     <right-panel | 
|       :active-data="activeData" | 
|       :form-conf="formConf" | 
|       :show-field="!!drawingList.length" | 
|       @tag-change="tagChange" | 
|     /> | 
|   | 
|     <code-type-dialog | 
|       :visible.sync="dialogVisible" | 
|       title="选择生成类型" | 
|       :show-file-name="showFileName" | 
|       @confirm="generate" | 
|     /> | 
|     <input id="copyNode" type="hidden"> | 
|   </div> | 
| </template> | 
|   | 
| <script> | 
| import draggable from 'vuedraggable' | 
| import beautifier from 'js-beautify' | 
| import ClipboardJS from 'clipboard' | 
| import render from '@/utils/generator/render' | 
| import RightPanel from './RightPanel' | 
| import { inputComponents, selectComponents, layoutComponents, formConf } from '@/utils/generator/config' | 
| import { beautifierConf, titleCase } from '@/utils/index' | 
| import { makeUpHtml, vueTemplate, vueScript, cssStyle } from '@/utils/generator/html' | 
| import { makeUpJs } from '@/utils/generator/js' | 
| import { makeUpCss } from '@/utils/generator/css' | 
| import drawingDefault from '@/utils/generator/drawingDefault' | 
| import logo from '@/assets/logo/logo.png' | 
| import CodeTypeDialog from './CodeTypeDialog' | 
| import DraggableItem from './DraggableItem' | 
|   | 
| let oldActiveId | 
| let tempActiveData | 
|   | 
| export default { | 
|   components: { | 
|     draggable, | 
|     render, | 
|     RightPanel, | 
|     CodeTypeDialog, | 
|     DraggableItem | 
|   }, | 
|   data() { | 
|     return { | 
|       logo, | 
|       idGlobal: 100, | 
|       formConf, | 
|       inputComponents, | 
|       selectComponents, | 
|       layoutComponents, | 
|       labelWidth: 100, | 
|       drawingList: drawingDefault, | 
|       drawingData: {}, | 
|       activeId: drawingDefault[0].formId, | 
|       drawerVisible: false, | 
|       formData: {}, | 
|       dialogVisible: false, | 
|       generateConf: null, | 
|       showFileName: false, | 
|       activeData: drawingDefault[0] | 
|     } | 
|   }, | 
|   created() { | 
|     // 防止 firefox 下 拖拽 会新打卡一个选项卡 | 
|     document.body.ondrop = event => { | 
|       event.preventDefault() | 
|       event.stopPropagation() | 
|     } | 
|   }, | 
|   watch: { | 
|     // eslint-disable-next-line func-names | 
|     'activeData.label': function (val, oldVal) { | 
|       if ( | 
|         this.activeData.placeholder === undefined | 
|         || !this.activeData.tag | 
|         || oldActiveId !== this.activeId | 
|       ) { | 
|         return | 
|       } | 
|       this.activeData.placeholder = this.activeData.placeholder.replace(oldVal, '') + val | 
|     }, | 
|     activeId: { | 
|       handler(val) { | 
|         oldActiveId = val | 
|       }, | 
|       immediate: true | 
|     } | 
|   }, | 
|   mounted() { | 
|     const clipboard = new ClipboardJS('#copyNode', { | 
|       text: trigger => { | 
|         const codeStr = this.generateCode() | 
|         this.$notify({ | 
|           title: '成功', | 
|           message: '代码已复制到剪切板,可粘贴。', | 
|           type: 'success' | 
|         }) | 
|         return codeStr | 
|       } | 
|     }) | 
|     clipboard.on('error', e => { | 
|       this.$message.error('代码复制失败') | 
|     }) | 
|   }, | 
|   methods: { | 
|     activeFormItem(element) { | 
|       this.activeData = element | 
|       this.activeId = element.formId | 
|     }, | 
|     onEnd(obj, a) { | 
|       if (obj.from !== obj.to) { | 
|         this.activeData = tempActiveData | 
|         this.activeId = this.idGlobal | 
|       } | 
|     }, | 
|     addComponent(item) { | 
|       const clone = this.cloneComponent(item) | 
|       this.drawingList.push(clone) | 
|       this.activeFormItem(clone) | 
|     }, | 
|     cloneComponent(origin) { | 
|       const clone = JSON.parse(JSON.stringify(origin)) | 
|       clone.formId = ++this.idGlobal | 
|       clone.span = formConf.span | 
|       clone.renderKey = +new Date() // 改变renderKey后可以实现强制更新组件 | 
|       if (!clone.layout) clone.layout = 'colFormItem' | 
|       if (clone.layout === 'colFormItem') { | 
|         clone.vModel = `field${this.idGlobal}` | 
|         clone.placeholder !== undefined && (clone.placeholder += clone.label) | 
|         tempActiveData = clone | 
|       } else if (clone.layout === 'rowFormItem') { | 
|         delete clone.label | 
|         clone.componentName = `row${this.idGlobal}` | 
|         clone.gutter = this.formConf.gutter | 
|         tempActiveData = clone | 
|       } | 
|       return tempActiveData | 
|     }, | 
|     AssembleFormData() { | 
|       this.formData = { | 
|         fields: JSON.parse(JSON.stringify(this.drawingList)), | 
|         ...this.formConf | 
|       } | 
|     }, | 
|     generate(data) { | 
|       const func = this[`exec${titleCase(this.operationType)}`] | 
|       this.generateConf = data | 
|       func && func(data) | 
|     }, | 
|     execRun(data) { | 
|       this.AssembleFormData() | 
|       this.drawerVisible = true | 
|     }, | 
|     execDownload(data) { | 
|       const codeStr = this.generateCode() | 
|       const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' }) | 
|       this.$download.saveAs(blob, data.fileName) | 
|     }, | 
|     execCopy(data) { | 
|       document.getElementById('copyNode').click() | 
|     }, | 
|     empty() { | 
|       this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then( | 
|         () => { | 
|           this.drawingList = [] | 
|         } | 
|       ) | 
|     }, | 
|     drawingItemCopy(item, parent) { | 
|       let clone = JSON.parse(JSON.stringify(item)) | 
|       clone = this.createIdAndKey(clone) | 
|       parent.push(clone) | 
|       this.activeFormItem(clone) | 
|     }, | 
|     createIdAndKey(item) { | 
|       item.formId = ++this.idGlobal | 
|       item.renderKey = +new Date() | 
|       if (item.layout === 'colFormItem') { | 
|         item.vModel = `field${this.idGlobal}` | 
|       } else if (item.layout === 'rowFormItem') { | 
|         item.componentName = `row${this.idGlobal}` | 
|       } | 
|       if (Array.isArray(item.children)) { | 
|         item.children = item.children.map(childItem => this.createIdAndKey(childItem)) | 
|       } | 
|       return item | 
|     }, | 
|     drawingItemDelete(index, parent) { | 
|       parent.splice(index, 1) | 
|       this.$nextTick(() => { | 
|         const len = this.drawingList.length | 
|         if (len) { | 
|           this.activeFormItem(this.drawingList[len - 1]) | 
|         } | 
|       }) | 
|     }, | 
|     generateCode() { | 
|       const { type } = this.generateConf | 
|       this.AssembleFormData() | 
|       const script = vueScript(makeUpJs(this.formData, type)) | 
|       const html = vueTemplate(makeUpHtml(this.formData, type)) | 
|       const css = cssStyle(makeUpCss(this.formData)) | 
|       return beautifier.html(html + script + css, beautifierConf.html) | 
|     }, | 
|     download() { | 
|       this.dialogVisible = true | 
|       this.showFileName = true | 
|       this.operationType = 'download' | 
|     }, | 
|     run() { | 
|       this.dialogVisible = true | 
|       this.showFileName = false | 
|       this.operationType = 'run' | 
|     }, | 
|     copy() { | 
|       this.dialogVisible = true | 
|       this.showFileName = false | 
|       this.operationType = 'copy' | 
|     }, | 
|     tagChange(newTag) { | 
|       newTag = this.cloneComponent(newTag) | 
|       newTag.vModel = this.activeData.vModel | 
|       newTag.formId = this.activeId | 
|       newTag.span = this.activeData.span | 
|       delete this.activeData.tag | 
|       delete this.activeData.tagIcon | 
|       delete this.activeData.document | 
|       Object.keys(newTag).forEach(key => { | 
|         if (this.activeData[key] !== undefined | 
|           && typeof this.activeData[key] === typeof newTag[key]) { | 
|           newTag[key] = this.activeData[key] | 
|         } | 
|       }) | 
|       this.activeData = newTag | 
|       this.updateDrawingList(newTag, this.drawingList) | 
|     }, | 
|     updateDrawingList(newTag, list) { | 
|       const index = list.findIndex(item => item.formId === this.activeId) | 
|       if (index > -1) { | 
|         list.splice(index, 1, newTag) | 
|       } else { | 
|         list.forEach(item => { | 
|           if (Array.isArray(item.children)) this.updateDrawingList(newTag, item.children) | 
|         }) | 
|       } | 
|     } | 
|   } | 
| } | 
| </script> | 
|   | 
| <style lang='scss'> | 
| .editor-tabs{ | 
|   background: #121315; | 
|   .el-tabs__header{ | 
|     margin: 0; | 
|     border-bottom-color: #121315; | 
|     .el-tabs__nav{ | 
|       border-color: #121315; | 
|     } | 
|   } | 
|   .el-tabs__item{ | 
|     height: 32px; | 
|     line-height: 32px; | 
|     color: #888a8e; | 
|     border-left: 1px solid #121315 !important; | 
|     background: #363636; | 
|     margin-right: 5px; | 
|     user-select: none; | 
|   } | 
|   .el-tabs__item.is-active{ | 
|     background: #1e1e1e; | 
|     border-bottom-color: #1e1e1e!important; | 
|     color: #fff; | 
|   } | 
|   .el-icon-edit{ | 
|     color: #f1fa8c; | 
|   } | 
|   .el-icon-document{ | 
|     color: #a95812; | 
|   } | 
| } | 
|   | 
| // home | 
| .right-scrollbar { | 
|   .el-scrollbar__view { | 
|     padding: 12px 18px 15px 15px; | 
|   } | 
| } | 
| .left-scrollbar .el-scrollbar__wrap { | 
|   box-sizing: border-box; | 
|   overflow-x: hidden !important; | 
|   margin-bottom: 0 !important; | 
| } | 
| .center-tabs{ | 
|   .el-tabs__header{ | 
|     margin-bottom: 0!important; | 
|   } | 
|   .el-tabs__item{ | 
|     width: 50%; | 
|     text-align: center; | 
|   } | 
|   .el-tabs__nav{ | 
|     width: 100%; | 
|   } | 
| } | 
| .reg-item{ | 
|   padding: 12px 6px; | 
|   background: #f8f8f8; | 
|   position: relative; | 
|   border-radius: 4px; | 
|   .close-btn{ | 
|     position: absolute; | 
|     right: -6px; | 
|     top: -6px; | 
|     display: block; | 
|     width: 16px; | 
|     height: 16px; | 
|     line-height: 16px; | 
|     background: rgba(0, 0, 0, 0.2); | 
|     border-radius: 50%; | 
|     color: #fff; | 
|     text-align: center; | 
|     z-index: 1; | 
|     cursor: pointer; | 
|     font-size: 12px; | 
|     &:hover{ | 
|       background: rgba(210, 23, 23, 0.5) | 
|     } | 
|   } | 
|   & + .reg-item{ | 
|     margin-top: 18px; | 
|   } | 
| } | 
| .action-bar{ | 
|   & .el-button+.el-button { | 
|     margin-left: 15px; | 
|   } | 
|   & i { | 
|     font-size: 20px; | 
|     vertical-align: middle; | 
|     position: relative; | 
|     top: -1px; | 
|   } | 
| } | 
|   | 
| .custom-tree-node{ | 
|   width: 100%; | 
|   font-size: 14px; | 
|   .node-operation{ | 
|     float: right; | 
|   } | 
|   i[class*="el-icon"] + i[class*="el-icon"]{ | 
|     margin-left: 6px; | 
|   } | 
|   .el-icon-plus{ | 
|     color: #409EFF; | 
|   } | 
|   .el-icon-delete{ | 
|     color: #157a0c; | 
|   } | 
| } | 
|   | 
| .left-scrollbar .el-scrollbar__view{ | 
|   overflow-x: hidden; | 
| } | 
|   | 
| .el-rate{ | 
|   display: inline-block; | 
|   vertical-align: text-top; | 
| } | 
| .el-upload__tip{ | 
|   line-height: 1.2; | 
| } | 
|   | 
| $selectedColor: #f6f7ff; | 
| $lighterBlue: #409EFF; | 
|   | 
| .container { | 
|   position: relative; | 
|   width: 100%; | 
|   height: 100%; | 
| } | 
|   | 
| .components-list { | 
|   padding: 8px; | 
|   box-sizing: border-box; | 
|   height: 100%; | 
|   .components-item { | 
|     display: inline-block; | 
|     width: 48%; | 
|     margin: 1%; | 
|     transition: transform 0ms !important; | 
|   } | 
| } | 
| .components-draggable{ | 
|   padding-bottom: 20px; | 
| } | 
| .components-title{ | 
|   font-size: 14px; | 
|   color: #222; | 
|   margin: 6px 2px; | 
|   .svg-icon{ | 
|     color: #666; | 
|     font-size: 18px; | 
|   } | 
| } | 
|   | 
| .components-body { | 
|   padding: 8px 10px; | 
|   background: $selectedColor; | 
|   font-size: 12px; | 
|   cursor: move; | 
|   border: 1px dashed $selectedColor; | 
|   border-radius: 3px; | 
|   .svg-icon{ | 
|     color: #777; | 
|     font-size: 15px; | 
|   } | 
|   &:hover { | 
|     border: 1px dashed #787be8; | 
|     color: #787be8; | 
|     .svg-icon { | 
|       color: #787be8; | 
|     } | 
|   } | 
| } | 
|   | 
| .left-board { | 
|   width: 260px; | 
|   position: absolute; | 
|   left: 0; | 
|   top: 0; | 
|   height: 100vh; | 
| } | 
| .left-scrollbar{ | 
|   height: calc(100vh - 42px); | 
|   overflow: hidden; | 
| } | 
| .center-scrollbar { | 
|   height: calc(100vh - 42px); | 
|   overflow: hidden; | 
|   border-left: 1px solid #f1e8e8; | 
|   border-right: 1px solid #f1e8e8; | 
|   box-sizing: border-box; | 
| } | 
| .center-board { | 
|   height: 100vh; | 
|   width: auto; | 
|   margin: 0 350px 0 260px; | 
|   box-sizing: border-box; | 
| } | 
| .empty-info{ | 
|   position: absolute; | 
|   top: 46%; | 
|   left: 0; | 
|   right: 0; | 
|   text-align: center; | 
|   font-size: 18px; | 
|   color: #ccb1ea; | 
|   letter-spacing: 4px; | 
| } | 
| .action-bar{ | 
|   position: relative; | 
|   height: 42px; | 
|   text-align: right; | 
|   padding: 0 15px; | 
|   box-sizing: border-box;; | 
|   border: 1px solid #f1e8e8; | 
|   border-top: none; | 
|   border-left: none; | 
|   .delete-btn{ | 
|     color: #F56C6C; | 
|   } | 
| } | 
| .logo-wrapper{ | 
|   position: relative; | 
|   height: 42px; | 
|   background: #fff; | 
|   border-bottom: 1px solid #f1e8e8; | 
|   box-sizing: border-box; | 
| } | 
| .logo{ | 
|   position: absolute; | 
|   left: 12px; | 
|   top: 6px; | 
|   line-height: 30px; | 
|   color: #00afff; | 
|   font-weight: 600; | 
|   font-size: 17px; | 
|   white-space: nowrap; | 
|   > img{ | 
|     width: 30px; | 
|     height: 30px; | 
|     vertical-align: top; | 
|   } | 
|   .github{ | 
|     display: inline-block; | 
|     vertical-align: sub; | 
|     margin-left: 15px; | 
|     > img{ | 
|       height: 22px; | 
|     } | 
|   } | 
| } | 
|   | 
| .center-board-row { | 
|   padding: 12px 12px 15px 12px; | 
|   box-sizing: border-box; | 
|   & > .el-form { | 
|     // 69 = 12+15+42 | 
|     height: calc(100vh - 69px); | 
|   } | 
| } | 
| .drawing-board { | 
|   height: 100%; | 
|   position: relative; | 
|   .components-body { | 
|     padding: 0; | 
|     margin: 0; | 
|     font-size: 0; | 
|   } | 
|   .sortable-ghost { | 
|     position: relative; | 
|     display: block; | 
|     overflow: hidden; | 
|     &::before { | 
|       content: " "; | 
|       position: absolute; | 
|       left: 0; | 
|       right: 0; | 
|       top: 0; | 
|       height: 3px; | 
|       background: rgb(89, 89, 223); | 
|       z-index: 2; | 
|     } | 
|   } | 
|   .components-item.sortable-ghost { | 
|     width: 100%; | 
|     height: 60px; | 
|     background-color: $selectedColor; | 
|   } | 
|   .active-from-item { | 
|     & > .el-form-item{ | 
|       background: $selectedColor; | 
|       border-radius: 6px; | 
|     } | 
|     & > .drawing-item-copy, & > .drawing-item-delete{ | 
|       display: initial; | 
|     } | 
|     & > .component-name{ | 
|       color: $lighterBlue; | 
|     } | 
|   } | 
|   .el-form-item{ | 
|     margin-bottom: 15px; | 
|   } | 
| } | 
| .drawing-item{ | 
|   position: relative; | 
|   cursor: move; | 
|   &.unfocus-bordered:not(.activeFromItem) > div:first-child  { | 
|     border: 1px dashed #ccc; | 
|   } | 
|   .el-form-item{ | 
|     padding: 12px 10px; | 
|   } | 
| } | 
| .drawing-row-item{ | 
|   position: relative; | 
|   cursor: move; | 
|   box-sizing: border-box; | 
|   border: 1px dashed #ccc; | 
|   border-radius: 3px; | 
|   padding: 0 2px; | 
|   margin-bottom: 15px; | 
|   .drawing-row-item { | 
|     margin-bottom: 2px; | 
|   } | 
|   .el-col{ | 
|     margin-top: 22px; | 
|   } | 
|   .el-form-item{ | 
|     margin-bottom: 0; | 
|   } | 
|   .drag-wrapper{ | 
|     min-height: 80px; | 
|   } | 
|   &.active-from-item{ | 
|     border: 1px dashed $lighterBlue; | 
|   } | 
|   .component-name{ | 
|     position: absolute; | 
|     top: 0; | 
|     left: 0; | 
|     font-size: 12px; | 
|     color: #bbb; | 
|     display: inline-block; | 
|     padding: 0 6px; | 
|   } | 
| } | 
| .drawing-item, .drawing-row-item{ | 
|   &:hover { | 
|     & > .el-form-item{ | 
|       background: $selectedColor; | 
|       border-radius: 6px; | 
|     } | 
|     & > .drawing-item-copy, & > .drawing-item-delete{ | 
|       display: initial; | 
|     } | 
|   } | 
|   & > .drawing-item-copy, & > .drawing-item-delete{ | 
|     display: none; | 
|     position: absolute; | 
|     top: -10px; | 
|     width: 22px; | 
|     height: 22px; | 
|     line-height: 22px; | 
|     text-align: center; | 
|     border-radius: 50%; | 
|     font-size: 12px; | 
|     border: 1px solid; | 
|     cursor: pointer; | 
|     z-index: 1; | 
|   } | 
|   & > .drawing-item-copy{ | 
|     right: 56px; | 
|     border-color: $lighterBlue; | 
|     color: $lighterBlue; | 
|     background: #fff; | 
|     &:hover{ | 
|       background: $lighterBlue; | 
|       color: #fff; | 
|     } | 
|   } | 
|   & > .drawing-item-delete{ | 
|     right: 24px; | 
|     border-color: #F56C6C; | 
|     color: #F56C6C; | 
|     background: #fff; | 
|     &:hover{ | 
|       background: #F56C6C; | 
|       color: #fff; | 
|     } | 
|   } | 
| } | 
| </style> |