You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
813 lines
19 KiB
813 lines
19 KiB
<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: #ffffff; |
|
margin: 6px 2px; |
|
.svg-icon { |
|
color: #666; |
|
font-size: 18px; |
|
} |
|
} |
|
|
|
.components-body { |
|
padding: 8px 10px; |
|
background: #4b6e91; |
|
font-size: 12px; |
|
cursor: move; |
|
border: 1px dashed #436efc; |
|
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 #0e5191; |
|
border-right: 1px solid #0e5191 ; |
|
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 #0e5191 ; |
|
border-top: none; |
|
border-left: none; |
|
.delete-btn { |
|
color: #f56c6c; |
|
} |
|
} |
|
.logo-wrapper { |
|
position: relative; |
|
height: 42px; |
|
background: #4b6e91; |
|
border-bottom: 1px solid #0e5191; |
|
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: #4b6e91; |
|
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; |
|
} |
|
} |
|
} |
|
.delete-btn::before, |
|
.delete-btn::after { |
|
display: none !important; |
|
} |
|
</style>
|
|
|