From cb0a4b78ee30e49c695c565ad47b9470bfc8d321 Mon Sep 17 00:00:00 2001 From: RuoYi <yzz_ivy@163.com> Date: 星期一, 24 二月 2020 09:28:16 +0800 Subject: [PATCH] 若依 2.1 --- ruoyi-ui/src/views/tool/build/index.vue | 790 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 787 insertions(+), 3 deletions(-) diff --git a/ruoyi-ui/src/views/tool/build/index.vue b/ruoyi-ui/src/views/tool/build/index.vue index 9d4496e..0d8adef 100644 --- a/ruoyi-ui/src/views/tool/build/index.vue +++ b/ruoyi-ui/src/views/tool/build/index.vue @@ -1,5 +1,789 @@ <template> - <div class="app-container"> - 构建工具 + <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> \ No newline at end of file +</template> + +<script> +import draggable from 'vuedraggable' +import { saveAs } from 'file-saver' +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 { + exportDefault, beautifierConf, isNumberStr, 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 drawingDefalut from '@/utils/generator/drawingDefalut' +import logo from '@/assets/logo/logo.png' +import CodeTypeDialog from './CodeTypeDialog' +import DraggableItem from './DraggableItem' + +const emptyActiveData = { style: {}, autosize: {} } +let oldActiveId +let tempActiveData + +export default { + components: { + draggable, + render, + RightPanel, + CodeTypeDialog, + DraggableItem + }, + data() { + return { + logo, + idGlobal: 100, + formConf, + inputComponents, + selectComponents, + layoutComponents, + labelWidth: 100, + drawingList: drawingDefalut, + drawingData: {}, + activeId: drawingDefalut[0].formId, + drawerVisible: false, + formData: {}, + dialogVisible: false, + generateConf: null, + showFileName: false, + activeData: drawingDefalut[0] + } + }, + computed: { + }, + 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' }) + 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'> +body, html{ + margin: 0; + padding: 0; + background: #fff; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; +} + +input, textarea{ + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; +} + +.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; + } +} +.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; + } +} + +.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> -- Gitblit v1.9.2