Commit 419077c4 by yuwei

Update: 智能表单代码

parent 6e32cec9
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
"vue-draggable-resizable-gorkys": "^2.4.4", "vue-draggable-resizable-gorkys": "^2.4.4",
"vue-grid-layout": "^2.3.11", "vue-grid-layout": "^2.3.11",
"vue-router": "3.0.6", "vue-router": "3.0.6",
"vue2-editor": "^2.10.2",
"vuedraggable": "^2.24.0", "vuedraggable": "^2.24.0",
"vuex": "3.1.0" "vuex": "3.1.0"
}, },
......
...@@ -97,6 +97,12 @@ export const constantRoutes = [ ...@@ -97,6 +97,12 @@ export const constantRoutes = [
}, },
{ {
path: '/form/making',
component: () => import('@/views/dynamicform/formmaking/FormDesign'),
hidden: true
},
{
path: '/', path: '/',
component: Layout, component: Layout,
redirect: '/dashboard', redirect: '/dashboard',
......
.form-design-container {
height: 100vh;
width: 100%;
margin: 0;
padding: 0;
::-webkit-scrollbar {
width: 3px;
height: 5px;
}
::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 10px;
}
.form-design-content {
height: 100%;
background: #fff;
// 左右栏
aside {
box-shadow: 0px 0px 1px 1px #ccc;
height: 100%;
// 左侧区域
&.left {
height: 100%;
overflow: auto;
user-select: none;
.widget-cate{
padding: 12px;
font-size: 14px;
}
ul {
position: relative;
overflow: hidden;
padding: 0 10px 10px;
margin: 0;
li {
font-size: 12px;
display: block;
width: 48%;
line-height: 26px;
position: relative;
float: left;
left: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 1%;
color: #333;
border: 1px solid #F4F6FC;
&:hover {
color : #409eff;
border : 1px solid #409eff;
position : relative;
z-index : 1;
box-shadow: 0 2px 6px #409eff;
}
&>a {
display: block;
cursor: move;
background: #F4F6FC;
border: 1px solid #F4F6FC;
.icon {
margin-right: 6px;
margin-left: 8px;
font-size: 14px;
display: inline-block;
vertical-align: middle;
}
span {
display: inline-block;
vertical-align: middle;
}
}
}
}
}
// 右侧区域
&.right {
height: 100%;
color: #fff;
overflow: hidden;
position: relative;
}
}
// 中间内容区域
.widget-center-container {
border-left: 1px solid #e0e0e0;
border-right: 1px solid #e0e0e0;
.btn-bar {
height: 45px;
line-height: 45px;
font-size: 18px;
border-bottom: 2px solid #e4e7ed;
text-align: right;
}
// 内容操作区域
.el-main {
padding: 0;
position: relative;
background: #fafafa;
.widget-form-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
::v-deep .el-form {
height: 100%;
.form-empty {
position: absolute;
left: 0;
top: 45%;
width: 100%;
text-align: center;
font-size: 20px;
color: #aaa;
}
.draggable-box {
height: 100%;
overflow: auto;
.draggable-list {
min-height: 100%;
padding: 5px;
position: relative;
background: #fafafa;
.moving {
// 拖放移动中
background: #409EFF;
border: 2px solid #409EFF;
outline-width: 0;
height: 3px;
box-sizing: border-box;
font-size: 0;
content: '';
overflow: hidden;
padding: 0;
}
.draggable-move {
border: 1px dashed hsla(0,0%,66.7%,.5);
background-color: rgba(236,245,255,.3);
.draggable-move-box {
position: relative;
box-sizing: border-box;
padding: 8px;
overflow: hidden;
transition: all .3s;
min-height: 36px;
&:hover {
background: #ecf5ff;
}
&::before {
content: '';
height: 5px;
width: 100%;
background: #13c2c2;
position: absolute;
top: 0;
right: -100%;
transition: all .3s;
}
&.active {
&::before {
right: 0;
}
background: #ecf5ff;
outline-offset: 0;
}
&.is_hidden {
background: #fef0f0;
}
.form-item-box {
position: relative;
box-sizing: border-box;
word-wrap: break-word;
&::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 888;
}
.el-form-item {
margin-bottom: 5px;
}
&.is_req {
.el-form-item__label::before {
content: '*';
color: #f56c6c;
margin-right: 4px;
}
}
}
.widget-view-action {
position: absolute;
right: 0;
bottom: 0;
height: 28px;
line-height: 28px;
background: #409eff;
z-index: 9;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: pointer;
}
}
.widget-view-drag {
position: absolute;
left: 0px;
top: 0px;
height: 28px;
line-height: 28px;
background: #409eff;
z-index: 9;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: move;
}
}
.widget-view-model {
position: absolute;
top: 10px;
right: 10px;
font-size: 12px;
color: #13c2c2;
z-index: 9;
font-weight: 500;
}
}
.subform-box,
.tabs-box,
.grid-box,
.table-box,
.card-box {
position: relative;
box-sizing: border-box;
padding: 5px;
background: rgba(253,246,236,.3);
width: 100%;
transition: all .3s;
overflow: hidden;
&:hover {
background: rgba(152, 103, 247, 0.24);
}
.subform-label {
font-size: 16px;
font-weight: 500;
padding: 10px 20px;
}
.form-item-box {
position: relative;
box-sizing: border-box;
}
.draggable-box {
min-height: 60px;
min-width: 50px;
border: 1px #ccc dashed;
background: #fff;
.draggable-list {
min-height: 60px;
position: relative;
border: 1px #ccc dashed;
}
}
&::before {
content: '';
height: 5px;
width: 100%;
background: transparent;
position: absolute;
top: 0;
right: -100%;
transition: all .3s;
}
&.active {
&::before {
background: #9867f7;
right: 0;
}
background: rgba(152, 103, 247, 0.24);
outline-offset: 0;
}
.table-layout {
background: rgba(152, 103, 247, 0.12);
width: 100%;
box-sizing: border-box;
transition: all 0.3s;
border-collapse: collapse;
tr {
transition: all 0.3s;
border-collapse: collapse;
td {
box-sizing: border-box;
transition: all 0.3s;
padding: 12px 12px;
border-collapse: collapse;
vertical-align:top;
}
}
&.bordered {
// 添加边框
tr {
td {
border: 1px solid #e8e8e8 !important;
}
}
}
&.bright {
// 点亮行
tr {
&:hover > td {
background: #e6f7ff;
}
}
}
&.small {
// 紧凑型
tr {
td {
padding: 8px 8px;
}
}
}
}
>.widget-view-action {
position: absolute;
right: 0;
bottom: 0;
height: 28px;
line-height: 28px;
background: #9867f7;
z-index: 9;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: pointer;
}
}
>.widget-view-drag {
position: absolute;
left: 0px;
top: 0px;
height: 28px;
line-height: 28px;
background: #9867f7;
z-index: 9;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: move;
}
}
}
}
}
}
}
}
}
}
}
}
<template>
<div class="form-design-container">
<el-container class="form-design-content">
<el-aside class="left" width="250px">
<template v-if="formComponents.length">
<div class="widget-cate">表单组件</div>
<draggable
tag="ul"
:list="formComponents"
v-bind="{ group: { name: 'form-draggable', pull: 'clone', put: false }, sort: false, animation: 180, ghostClass: 'moving' }"
>
<li v-for="(item, index) in formComponents" :key="index" @click="handleListPush(item)">
<a>
<i class="icon el-icon-star-off" />
<span>{{ item.label }}</span>
</a>
</li>
</draggable>
</template>
<template v-if="layoutComponents.length">
<div class="widget-cate">布局组件</div>
<draggable
tag="ul"
:list="layoutComponents"
v-bind="{ group: { name: 'form-draggable', pull: 'clone', put: false }, sort: false, animation: 180, ghostClass: 'moving' }"
>
<li v-for="(item, index) in layoutComponents" :key="index" @click="handleListPush(item)">
<a>
<i class="icon el-icon-star-off" />
<span>{{ item.label }}</span>
</a>
</li>
</draggable>
</template>
</el-aside>
<el-container class="widget-center-container" direction="vertical">
<el-header class="btn-bar" style="height: 45px;">
<el-button type="text" size="medium" icon="el-icon-view">预览</el-button>
<el-button type="text" size="medium" icon="el-icon-refresh">重置</el-button>
<el-button type="text" size="medium" icon="el-icon-plus">保存</el-button>
<el-button type="text" size="medium" icon="el-icon-delete">清空</el-button>
</el-header>
<el-main>
<widget-form :data="widgetData" :select.sync="selectWidget" />
</el-main>
</el-container>
<el-aside class="right">Aside</el-aside>
</el-container>
</div>
</template>
<script>
import { formComponents, layoutComponents } from './config/componentsConfig.js'
import draggable from 'vuedraggable'
import WidgetForm from './components/WidgetForm'
export default {
name: 'FormDesign',
components: {
draggable,
WidgetForm
},
data() {
return {
formComponents,
layoutComponents,
widgetData: {
list: [],
config: {
width: 100,
size: 'small',
labelWidth: 100,
labelPosition: 'right',
customStyle: ''
}
},
selectWidget: {}
}
},
methods: {
handleListPush(item) {
// 双击组件push到list
const key = new Date().getTime()
item = {
...item,
key,
model: item.type + '_' + key
}
// json深拷贝一次
const element = JSON.parse(JSON.stringify(item))
// 删除icon属性
delete element.icon
this.widgetData.list.push(element)
this.selectWidget = element
}
}
}
</script>
<style lang="scss" scoped>
@import '~@/styles/form-design.scss'
</style>
<template>
<div>
<!-- 子表单设计模块 start -->
<template v-if="element.type === 'subform'">
<div
class="subform-box"
:class="{ active: element.key === selectWidget.key }"
@click.stop="handleSelectWidget(index)"
>
<div class="subform-label">子表单</div>
<draggable
v-model="element.list"
tag="div"
class="draggable-box"
v-bind="{ group: moveAllowed ? 'form-draggable' : '', ghostClass: 'moving', animation: 180, handle: '.drag-widget' }"
@add="handleWidgetAdd"
>
<transition-group name="list" tag="div" class="draggable-list">
<widget-draggable-item
v-for="(item, i) in element.list"
:key="item.key"
class="draggable-move"
:index="i"
:element="item"
:select.sync="selectWidget"
:data="element"
/>
</transition-group>
</draggable>
<div v-if="selectWidget.key === element.key" class="widget-view-action widget-col-action">
<i class="el-icon-copy-document" @click.stop="handleWidgetCopy(index)" />
<i class="el-icon-delete" @click.stop="handleWidgetDelete(index)" />
</div>
<div v-if="selectWidget.key === element.key" class="widget-view-drag widget-col-drag">
<i class="el-icon-rank drag-widget" />
</div>
</div>
</template>
<!-- 子表单设计模块 end -->
<!-- 标签Tabs布局 start -->
<template v-else-if="element.type === 'tabs'">
<div
class="tabs-box"
:class="{ active: element.key === selectWidget.key }"
@click.stop="handleSelectWidget(index)"
>
<el-tabs
value="1"
:type="element.options.type"
:tab-position="element.options.tabPosition"
>
<el-tab-pane
v-for="(tabItem, idnex) in element.tabs"
:key="idnex"
:label="tabItem.label"
:name="tabItem.name"
>
<draggable
v-model="tabItem.list"
tag="div"
class="draggable-box"
v-bind="{ group: 'form-draggable', ghostClass: 'moving', animation: 180, handle: '.drag-widget' }"
@add="handleWidgetAdd"
>
<transition-group name="list" tag="div" class="draggable-list">
<widget-draggable-item
v-for="(item, i) in tabItem.list"
:key="item.key"
class="draggable-move"
:index="i"
:element="item"
:select.sync="selectWidget"
:data="tabItem"
/>
</transition-group>
</draggable>
</el-tab-pane>
</el-tabs>
<div v-if="selectWidget.key === element.key" class="widget-view-action widget-col-action">
<i class="el-icon-copy-document" @click.stop="handleWidgetCopy(index)" />
<i class="el-icon-delete" @click.stop="handleWidgetDelete(index)" />
</div>
<div v-if="selectWidget.key === element.key" class="widget-view-drag widget-col-drag">
<i class="el-icon-rank drag-widget" />
</div>
</div>
</template>
<!-- 标签Tabs布局 end -->
<!-- 栅格布局 start -->
<template v-else-if="element.type === 'grid'">
<div
class="grid-box"
:class="{ active: element.key === selectWidget.key }"
@click.stop="handleSelectWidget(index)"
>
<el-row :gutter="element.options.gutter">
<el-col
v-for="(colItem, idnex) in element.columns"
:key="idnex"
:span="colItem.span || 0"
>
<draggable
v-model="colItem.list"
tag="div"
class="draggable-box"
v-bind="{ group: 'form-draggable', ghostClass: 'moving', animation: 180, handle: '.drag-widget' }"
@add="handleWidgetAdd"
>
<transition-group name="list" tag="div" class="draggable-list">
<widget-draggable-item
v-for="(item, i) in colItem.list"
:key="item.key"
class="draggable-move"
:index="i"
:element="item"
:select.sync="selectWidget"
:data="colItem"
/>
</transition-group>
</draggable>
</el-col>
</el-row>
<div v-if="selectWidget.key === element.key" class="widget-view-action widget-col-action">
<i class="el-icon-copy-document" @click.stop="handleWidgetCopy(index)" />
<i class="el-icon-delete" @click.stop="handleWidgetDelete(index)" />
</div>
<div v-if="selectWidget.key === element.key" class="widget-view-drag widget-col-drag">
<i class="el-icon-rank drag-widget" />
</div>
</div>
</template>
<!-- 栅格布局 end -->
<!-- 卡片布局 start -->
<template v-else-if="element.type === 'card'">
<div
class="card-box"
:class="{ active: element.key === selectWidget.key }"
@click.stop="handleSelectWidget(index)"
>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>{{ element.label }}</span>
</div>
<draggable
v-model="element.list"
tag="div"
class="draggable-box"
v-bind="{ group: 'form-draggable', ghostClass: 'moving', animation: 180, handle: '.drag-widget' }"
@add="handleWidgetAdd"
>
<transition-group name="list" tag="div" class="draggable-list">
<widget-draggable-item
v-for="(item, i) in element.list"
:key="item.key"
class="draggable-move"
:index="i"
:element="item"
:select.sync="selectWidget"
:data="element"
/>
</transition-group>
</draggable>
</el-card>
<div v-if="selectWidget.key === element.key" class="widget-view-action widget-col-action">
<i class="el-icon-copy-document" @click.stop="handleWidgetCopy(index)" />
<i class="el-icon-delete" @click.stop="handleWidgetDelete(index)" />
</div>
<div v-if="selectWidget.key === element.key" class="widget-view-drag widget-col-drag">
<i class="el-icon-rank drag-widget" />
</div>
</div>
</template>
<!-- 卡片布局 end -->
<!-- 表格布局 start -->
<template v-else-if="element.type === 'table'">
<div
class="table-box"
:class="{ active: element.key === selectWidget.key }"
@click.stop="handleSelectWidget(index)"
>
<table
class="table-layout"
:class="{
bright: element.options.bright,
small: element.options.small,
bordered: element.options.bordered
}"
:style="element.options.customStyle"
>
<tr v-for="(trItem, trIndex) in element.trs" :key="trIndex">
<td
v-for="(tdItem, tdIndex) in trItem.tds"
:key="tdIndex"
:colspan="tdItem.colspan"
:rowspan="tdItem.rowspan"
>
<draggable
v-model="tdItem.list"
tag="div"
class="draggable-box"
v-bind="{ group: 'form-draggable', ghostClass: 'moving', animation: 180, handle: '.drag-widget' }"
@add="handleWidgetAdd"
>
<transition-group name="list" tag="div" class="draggable-list">
<widget-draggable-item
v-for="(item, i) in tdItem.list"
:key="item.key"
class="draggable-move"
:index="i"
:element="item"
:select.sync="selectWidget"
:data="tdItem"
/>
</transition-group>
</draggable>
</td>
</tr>
</table>
<div v-if="selectWidget.key === element.key" class="widget-view-action widget-col-action">
<i class="el-icon-copy-document" @click.stop="handleWidgetCopy(index)" />
<i class="el-icon-delete" @click.stop="handleWidgetDelete(index)" />
</div>
<div v-if="selectWidget.key === element.key" class="widget-view-drag widget-col-drag">
<i class="el-icon-rank drag-widget" />
</div>
</div>
</template>
<!-- 表格布局 end -->
<template v-else>
<widget-form-item
:key="element.key"
:index="index"
:element="element"
:select.sync="selectWidget"
:data="data"
/>
</template>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import WidgetFormItem from './WidgetFormItem'
export default {
name: 'WidgetDraggableItem',
components: {
draggable,
WidgetFormItem
},
props: {
element: {
type: Object,
required: true,
default: () => ({})
},
select: {
type: Object,
required: true,
default: () => ({})
},
index: {
type: Number,
required: true
},
data: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
moveAllowedType: [
'input',
'textarea',
'number',
'select',
'checkbox',
'radio',
'date',
'time',
'rate',
'slider',
'uploadFile',
'uploadImg',
'cascader',
'treeSelect',
'switch',
'text',
'html'
],
selectWidget: this.select
}
},
computed: {
moveAllowed() {
return this.moveAllowedType.includes(this.selectWidget.type)
}
},
watch: {
select(val) {
this.selectWidget = val
},
selectWidget: {
handler(val) {
this.$emit('update:select', val)
},
deep: true
}
},
methods: {
handleSelectWidget(index) {
this.selectWidget = this.data.list[index]
},
handleWidgetAdd(evt) {
// 为拖拽到容器的组件push到list
const newIndex = evt.newIndex
const key = new Date().getTime()
this.$set(this.data.list, newIndex, {
...this.data.list[newIndex],
key,
model: this.data.list[newIndex].type + '_' + key
})
this.selectWidget = this.data.list[newIndex]
},
handleWidgetDelete(index) {
if (this.data.list.length - 1 === index) {
if (index === 0) {
this.selectWidget = {}
} else {
this.selectWidget = this.data.list[index - 1]
}
} else {
this.selectWidget = this.data.list[index + 1]
}
this.$nextTick(() => {
this.data.list.splice(index, 1)
})
},
handleWidgetCopy(index) {
const key = new Date().getTime()
const cloneData = {
...this.data.list[index],
key,
model: this.data.list[index].type + '_' + key
}
// card布局处理
if (typeof cloneData.list !== 'undefined') {
cloneData.list = []
}
// grid布局处理
if (typeof cloneData.columns !== 'undefined') {
cloneData.columns = JSON.parse(JSON.stringify(cloneData.columns))
// 复制时,清空数据
cloneData.columns.forEach(item => {
item.list = []
})
}
// table布局处理
if (typeof cloneData.trs !== 'undefined') {
cloneData.trs = JSON.parse(JSON.stringify(cloneData.trs))
// 复制时,清空数据
cloneData.trs.forEach(item => {
item.tds.forEach(val => {
val.list = []
})
})
}
// tabs布局处理
if (typeof cloneData.tabs !== 'undefined') {
cloneData.tabs = JSON.parse(JSON.stringify(cloneData.tabs))
// 复制时,清空数据
cloneData.tabs.forEach(item => {
item.list = []
})
}
this.data.list.splice(index, 0, cloneData)
this.$nextTick(() => {
this.selectWidget = this.data.list[index + 1]
})
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="widget-form-wrapper" :style="{ width: data.config.width + '%' }">
<el-form :size="data.config.size" label-suffix=":" :label-position="data.config.labelPosition" :label-width="data.config.labelWidth + 'px'">
<div v-if="data.list.length === 0" class="form-empty">从左侧拖拽或点击来添加字段</div>
<draggable
tag="div"
class="draggable-box"
v-model="data.list"
v-bind="{ group: 'form-draggable', ghostClass: 'moving', animation: 180, handle: '.drag-widget' }"
@add="handleWidgetAdd"
>
<transition-group name="list" tag="div" class="draggable-list">
<widget-draggable-item
class="draggable-move"
v-for="(element, index) in data.list"
:key="element.key"
:index="index"
:element="element"
:select.sync="selectWidget"
:data="data"
/>
</transition-group>
</draggable>
</el-form>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import WidgetDraggableItem from './WidgetDraggableItem'
export default {
name: 'WidgetForm',
components: {
draggable,
WidgetDraggableItem
},
props: {
data: {
type: Object,
required: true,
default: () => ({})
},
select: {
type: Object,
default: () => ({})
}
},
data() {
return {
selectWidget: this.select
}
},
watch: {
select(val) {
this.selectWidget = val
},
selectWidget: {
handler(val) {
this.$emit('update:select', val)
},
deep: true
}
},
methods: {
handleSelectWidget(index) {
this.selectWidget = this.data.list[index]
},
handleWidgetAdd(evt) {
// 为拖拽到容器的组件push到list
const newIndex = evt.newIndex
const key = new Date().getTime()
this.$set(this.data.list, newIndex, {
...this.data.list[newIndex],
key,
model: this.data.list[newIndex].type + '_' + key
})
// 删除icon属性
delete this.data.list[newIndex].icon
this.selectWidget = this.data.list[newIndex]
}
}
}
</script>
<style lang="scss" scoped>
</style>
<template>
<div
class="draggable-move-box"
:class="{ active: selectWidget.key === element.key, 'is_hidden':element.options.hidden }"
@click.stop="handleSelectWidget(index)"
>
<div class="form-item-box">
<el-form-item
v-if="element && element.key"
:class="{ 'is_req': element.options.required }"
:label="element.label"
>
<template v-if="element.type === 'input'">
<el-input
v-model="element.options.defaultValue"
:style="{width: element.options.width}"
:disabled="element.options.disabled"
:placeholder="element.options.placeholder"
:type="element.options.type"
:clearable="element.options.clearable"
:max-length="element.options.maxLength"
/>
</template>
<template v-if="element.type === 'textarea'">
<el-input
v-model="element.options.defaultValue"
type="textarea"
:rows="4"
:style="{width: element.options.width}"
:disabled="element.options.disabled"
:placeholder="element.options.placeholder"
:auto-size="{
minRows: element.options.minRows,
maxRows: element.options.maxRows
}"
:max-length="element.options.maxLength"
/>
</template>
<template v-if="element.type === 'number'">
<el-input-number
v-model="element.options.defaultValue"
:style="{width: element.options.width}"
:disabled="element.options.disabled"
:controls-position="element.options.controlsPosition"
:min="element.options.min || element.options.min === 0 ? element.options.min : -Infinity"
:max="element.options.max || element.options.max === 0 ? element.options.max : Infinity"
:step="element.options.step"
:precision="element.options.precision > 50 || (!element.options.precision && element.options.precision !== 0) ? null : element.options.precision"
/>
</template>
<template v-if="element.type == 'select'">
<el-select
v-model="element.options.defaultValue"
:disabled="element.options.disabled"
:multiple="element.options.multiple"
:clearable="element.options.clearable"
:placeholder="element.options.placeholder"
:style="{width: element.options.width}"
>
<el-option v-for="(item, index) in element.options.options" :key="item.value + index" :value="item.value" :label="item.label" />
</el-select>
</template>
<template v-if="element.type == 'radio'">
<el-radio-group
v-model="element.options.defaultValue"
:disabled="element.options.disabled"
:style="{width: element.options.width}"
>
<el-radio
v-for="(item, index) in element.options.options"
:key="item.value + index"
:style="{display: element.options.inline ? 'inline-block' : 'block'}"
:label="item.value"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</template>
<template v-if="element.type == 'checkbox'">
<el-checkbox-group
v-model="element.options.defaultValue"
:disabled="element.options.disabled"
:style="{width: element.options.width}"
>
<el-checkbox
v-for="(item, index) in element.options.options"
:key="item.value + index"
:style="{display: element.options.inline ? 'inline-block' : 'block'}"
:label="item.value"
>
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</template>
<template v-if="element.type == 'date'">
<el-date-picker
v-model="element.options.defaultValue"
:type="element.options.type"
:placeholder="element.options.placeholder"
:start-placeholder="element.options.startPlaceholder"
:end-placeholder="element.options.endPlaceholder"
:readonly="element.options.readonly"
:disabled="element.options.disabled"
:editable="element.options.editable"
:clearable="element.options.clearable"
:style="{width: element.options.width}"
/>
</template>
<template v-if="element.type == 'time'">
<el-time-picker
v-model="element.options.defaultValue"
:is-range="element.options.isRange"
:placeholder="element.options.placeholder"
:start-placeholder="element.options.startPlaceholder"
:end-placeholder="element.options.endPlaceholder"
:readonly="element.options.readonly"
:disabled="element.options.disabled"
:editable="element.options.editable"
:clearable="element.options.clearable"
:arrow-control="element.options.arrowControl"
:style="{width: element.options.width}"
/>
</template>
<template v-if="element.type === 'rate'">
<el-rate
v-model="element.options.defaultValue"
:max="element.options.max"
:disabled="element.options.disabled"
:allow-half="element.options.allowHalf"
:show-score="element.options.showScore"
:style="{width: element.options.width}"
/>
</template>
<template v-if="element.type === 'slider'">
<el-slider
v-model="element.options.defaultValue"
:min="element.options.min"
:max="element.options.max"
:disabled="element.options.disabled"
:step="element.options.step"
:show-input="element.options.showInput"
:style="{width: element.options.width}"
/>
</template>
<template v-if="element.type == 'color'">
<el-color-picker
v-model="element.options.defaultValue"
:disabled="element.options.disabled"
:show-alpha="element.options.showAlpha"
/>
</template>
<template v-if="element.type === 'switch'">
<el-switch
v-model="element.options.defaultValue"
:disabled="element.options.disabled"
/>
</template>
<template v-if="element.type == 'cascader'">
<el-cascader
v-model="element.options.defaultValue"
:disabled="element.options.disabled"
:clearable="element.options.clearable"
:placeholder="element.options.placeholder"
:style="{width: element.options.width}"
:options="element.options.options"
:props="element.options.props"
/>
</template>
<template v-if="element.type == 'editor'">
<vue-editor
v-model="element.options.defaultValue"
:style="{width: element.options.width}"
>
</vue-editor>
</template>
<template v-if="element.type === 'alert'">
<el-alert
:title="element.options.title"
:description="element.options.description"
:type="element.options.type"
:effect="element.options.effect"
:show-icon="element.options.showIcon"
:closable="element.options.closable"
:center="element.options.center"
/>
</template>
<template v-if="element.type === 'text'">
<div :style="{ textAlign: element.options.textAlign }">
<label v-text="element.options.defaultValue" />
</div>
</template>
<template v-if="element.type === 'html'">
<div v-html="element.options.defaultValue" />
</template>
<template v-if="element.type === 'divider'">
<el-divider :content-position="element.options.orientation">{{ element.label }}</el-divider>
</template>
</el-form-item>
</div>
<div v-if="selectWidget.key === element.key" class="widget-view-action">
<i class="el-icon-copy-document" @click.stop="handleWidgetCopy(index)" />
<i class="el-icon-delete" @click.stop="handleWidgetDelete(index)" />
</div>
<div v-if="selectWidget.key === element.key" class="widget-view-drag">
<i class="el-icon-rank drag-widget" />
</div>
<div v-if="selectWidget.key === element.key" class="widget-view-model">
<span>{{ selectWidget.model }}</span>
</div>
</div>
</template>
<script>
import { VueEditor } from 'vue2-editor'
export default {
name: 'WidgetFormItem',
components: { VueEditor },
props: {
element: {
type: Object,
required: true,
default: () => ({})
},
select: {
type: Object,
required: true,
default: () => ({})
},
index: {
type: Number,
required: true
},
data: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
selectWidget: this.select
}
},
watch: {
select(val) {
this.selectWidget = val
},
selectWidget: {
handler(val) {
this.$emit('update:select', val)
},
deep: true
}
},
methods: {
handleSelectWidget(index) {
this.selectWidget = this.data.list[index]
},
handleWidgetDelete(index) {
if (this.data.list.length - 1 === index) {
if (index === 0) {
this.selectWidget = {}
} else {
this.selectWidget = this.data.list[index - 1]
}
} else {
this.selectWidget = this.data.list[index + 1]
}
this.$nextTick(() => {
this.data.list.splice(index, 1)
})
},
handleWidgetCopy(index) {
const key = new Date().getTime()
const cloneData = {
...this.data.list[index],
key,
model: this.data.list[index].type + '_' + key
}
this.data.list.splice(index, 0, cloneData)
this.$nextTick(() => {
this.selectWidget = this.data.list[index + 1]
})
}
}
}
</script>
<style lang="scss" scoped>
</style>
// 表单组件
export const formComponents = [
{
type: 'input', // 表单类型
label: '输入框', // 标题文字
icon: 'icon-input', // 图标
options: {
type: 'text', // 文本或密码,text或者password
width: '100%', // 宽度
defaultValue: '', // 默认值
placeholder: '', // 没有输入时,提示文字
maxLength: null, // 最大长度
clearable: false, // 是否可清除,false否,true是
hidden: false, // 是否隐藏,false显示,true隐藏
disabled: false // 是否禁用,false不禁用,true禁用
},
model: '', // 字段标识
key: '',
rules: [
// 验证规则
{
required: false, // 必须填写
message: '必填项'
}
]
},
{
type: 'textarea',
label: '文本框',
icon: 'icon-edit',
options: {
width: '100%',
defaultValue: '',
placeholder: '',
minRows: 4, // 最小行数
maxRows: 6, // 最大行数
maxLength: null,
hidden: false,
disabled: false
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'number',
label: '数字输入框',
icon: 'icon-number',
options: {
width: '100%',
defaultValue: 0,
placeholder: '',
min: null,
max: null,
precision: null,
step: 1,
hidden: false,
disabled: false
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'select',
label: '下拉选择器',
icon: 'icon-xiala',
options: {
width: '100%',
defaultValue: '',
multiple: false,
disabled: false,
clearable: false,
hidden: false,
placeholder: '',
dynamic: false,
dynamicKey: '',
dynamicFunc: '',
options: [
{
value: '1',
label: '下拉框1'
},
{
value: '2',
label: '下拉框2'
}
]
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'checkbox',
label: '多选框',
icon: 'icon-duoxuan1',
options: {
disabled: false,
hidden: false,
defaultValue: [],
inline: false,
dynamic: false,
dynamicKey: '',
dynamicFunc: '',
options: [
{
value: '1',
label: '选项1'
},
{
value: '2',
label: '选项2'
},
{
value: '3',
label: '选项3'
}
]
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'radio',
label: '单选框',
icon: 'icon-danxuan-cuxiantiao',
options: {
disabled: false,
hidden: false,
defaultValue: '',
inline: false,
dynamic: false,
dynamicKey: '',
dynamicFunc: '',
options: [
{
value: '1',
label: '选项1'
},
{
value: '2',
label: '选项2'
},
{
value: '3',
label: '选项3'
}
]
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'date',
label: '日期选择框',
icon: 'icon-calendar',
options: {
width: '100%',
defaultValue: '',
placeholder: '',
hidden: false,
readonly: false,
disabled: false,
editable: true,
clearable: true,
startPlaceholder: '',
endPlaceholder: '',
type: 'date',
format: 'yyyy-MM-dd',
timestamp: false
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'time',
label: '时间选择框',
icon: 'icon-time',
options: {
width: '100%',
defaultValue: '',
placeholder: '',
hidden: false,
readonly: false,
disabled: false,
editable: true,
clearable: true,
isRange: false,
startPlaceholder: '',
endPlaceholder: '',
arrowControl: true,
format: 'HH:mm:ss'
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'rate',
label: '评分',
icon: 'icon-pingfen_moren',
options: {
defaultValue: 0,
max: 5,
disabled: false,
hidden: false,
allowHalf: false,
showScore: false
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'slider',
label: '滑动输入条',
icon: 'icon-menu',
options: {
width: '100%',
defaultValue: 0,
disabled: false,
hidden: false,
min: 0,
max: 100,
step: 1,
showInput: false
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'color',
label: '颜色选择器',
icon: 'icon-pingfen_moren',
options: {
defaultValue: '',
disabled: false,
hidden: false,
showAlpha: false
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
// {
// type: 'uploadFile',
// label: '上传文件',
// icon: 'icon-upload',
// options: {
// defaultValue: [],
// disabled: false,
// hidden: false,
// multiple: false,
// width: '100%',
// limit: 3,
// data: '{}',
// fileName: 'file',
// headers: {},
// action: 'http://cdn.kcz66.com/uploadFile.txt',
// placeholder: '上传'
// },
// model: '',
// key: '',
// rules: [
// {
// required: false,
// message: '必填项'
// }
// ]
// },
// {
// type: 'uploadImg',
// label: '上传图片',
// icon: 'icon-image',
// options: {
// defaultValue: [],
// hidden: false,
// disabled: false,
// multiple: false,
// width: '100%',
// data: '{}',
// limit: 3,
// placeholder: '上传',
// fileName: 'image',
// headers: {},
// action: 'http://cdn.kcz66.com/upload-img.txt',
// listType: 'picture-card'
// },
// model: '',
// key: '',
// rules: [
// {
// required: false,
// message: '必填项'
// }
// ]
// },
{
type: 'cascader',
label: '级联选择器',
icon: 'icon-guanlian',
options: {
disabled: false,
hidden: false,
defaultValue: [],
placeholder: '',
width: '100%',
clearable: false,
dynamic: false,
dynamicKey: '',
dynamicFunc: '',
options: [
{
value: '1',
label: '选项1',
children: [
{
value: '11',
label: '选项11'
}
]
},
{
value: '2',
label: '选项2',
children: [
{
value: '22',
label: '选项22'
}
]
}
],
props: {
value: 'value',
label: 'label',
children: 'children'
}
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'subform',
label: '子表单',
icon: 'icon-biaoge',
list: [],
options: {
scrollY: 0,
disabled: false,
hidden: false,
showLabel: false,
hideSequence: false,
width: '100%'
},
model: '',
key: ''
},
{
type: 'editor',
label: '富文本',
icon: 'icon-LC_icon_edit_line_1',
options: {
height: 300,
placeholder: '',
defaultValue: '',
hidden: false,
disabled: false,
width: '100%'
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'switch',
label: '开关',
icon: 'icon-kaiguan3',
options: {
defaultValue: false,
hidden: false,
disabled: false
},
model: '',
key: '',
rules: [
{
required: false,
message: '必填项'
}
]
},
{
type: 'alert',
label: '警告提示',
icon: 'icon-zu',
options: {
type: 'success',
title: '提示的文案',
description: '',
effect: '',
hidden: false,
showIcon: false,
closable: false,
center: false
},
model: '',
key: ''
},
{
type: 'text',
label: '文字',
icon: 'icon-zihao',
options: {
hidden: false,
textAlign: 'left',
defaultValue: 'This is a text'
},
model: '',
key: ''
},
{
type: 'html',
label: 'HTML',
icon: 'icon-ai-code',
options: {
hidden: false,
defaultValue: '<strong>This is a HTML</strong>'
},
model: '',
key: ''
}
]
// 布局组件
export const layoutComponents = [
{
type: 'divider',
label: '分割线',
icon: 'icon-fengexian',
options: {
hidden: false,
orientation: 'left'
},
key: '',
model: ''
},
{
type: 'grid',
label: '栅格布局',
icon: 'icon-zhage',
columns: [
{
span: 12,
list: []
},
{
span: 12,
list: []
}
],
options: {
gutter: 0
},
key: '',
model: ''
},
{
type: 'table',
label: '表格布局',
icon: 'icon-biaoge',
trs: [
{
tds: [
{
colspan: 1,
rowspan: 1,
list: []
},
{
colspan: 1,
rowspan: 1,
list: []
}
]
},
{
tds: [
{
colspan: 1,
rowspan: 1,
list: []
},
{
colspan: 1,
rowspan: 1,
list: []
}
]
}
],
options: {
width: '100%',
bordered: true,
bright: false,
small: true,
customStyle: ''
},
key: '',
model: ''
},
{
type: 'tabs',
label: '标签页布局',
icon: 'icon-tabs',
options: {
type: 'border-card',
tabPosition: 'top'
},
tabs: [
{
name: '1',
label: '选项1',
list: []
},
{
name: '2',
label: '选项2',
list: []
}
],
key: '',
model: ''
},
{
type: 'card',
label: '卡片布局',
icon: 'icon-qiapian',
list: [],
key: '',
model: ''
}
]
<template>
<div class="app-container">
DynamicForm
</div>
</template>
<script>
export default {
name: 'DynamicForm'
}
</script>
<style lang="scss" scoped>
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment