1.表单设计器增加初始化属性;2.增加组件层次结构树视图;3.修复少量bug。

master
vdpAdmin 2021-11-28 23:04:14 +08:00
parent 4cb5376139
commit e603c524a2
32 changed files with 603 additions and 45 deletions

View File

@ -9,7 +9,7 @@
[在线Demo](http://demo.vform666.com)
### 友情链接
[fantastic-admin](https://hooray.gitee.io/fantastic-admin/) —— 一款开箱即用的 Vue 中后台管理系统框架支持Vue2/Vue3
[Fantastic-admin](https://hooray.gitee.io/fantastic-admin/) —— 一款开箱即用的 Vue 中后台管理系统框架支持Vue2/Vue3
<br/>

View File

@ -1,6 +1,6 @@
{
"name": "variant-form",
"version": "2.1.7",
"version": "2.1.8",
"private": false,
"scripts": {
"serve": "vue-cli-service serve --open src/main.js",

BIN
src/assets/ft-images/t1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
src/assets/ft-images/t2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
src/assets/ft-images/t3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
src/assets/ft-images/t4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
src/assets/ft-images/t5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
src/assets/ft-images/t6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
src/assets/ft-images/t7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
src/assets/ft-images/t8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -62,7 +62,7 @@ export function createDesigner(vueInstance) {
this.initHistoryData()
},
clearDesigner() {
clearDesigner(skipHistoryChange) {
let emptyWidgetListFlag = (this.widgetList.length === 0)
this.widgetList = []
this.selectedId = null
@ -70,7 +70,9 @@ export function createDesigner(vueInstance) {
this.selectedWidget = {} //this.selectedWidget = null
overwriteObj(this.formConfig, defaultFormConfig) //
if (!emptyWidgetListFlag) {
if (!!skipHistoryChange) {
//什么也不做!!
} else if (!emptyWidgetListFlag) {
this.emitHistoryChange()
} else {
this.saveCurrentHistoryStep()
@ -618,7 +620,14 @@ export function createDesigner(vueInstance) {
let newWidget = this.copyNewFieldWidget(widget)
if (!!this.selectedWidget && this.selectedWidget.type === 'tab') {
//获取当前激活的tabPane
//TODO:
let activeTab = this.selectedWidget.tabs[0]
this.selectedWidget.tabs.forEach(tabPane => {
if (!!tabPane.options.active) {
activeTab = tabPane
}
})
!!activeTab && activeTab.widgetList.push(newWidget)
} else if (!!this.selectedWidget && !!this.selectedWidget.widgetList) {
this.selectedWidget.widgetList.push(newWidget)
} else {
@ -626,6 +635,7 @@ export function createDesigner(vueInstance) {
}
this.setSelected(newWidget)
this.designer.emitHistoryChange()
},
deleteColOfGrid(gridWidget, colIdx) {

View File

@ -1,6 +1,6 @@
<template>
<el-col v-else-if="widget.type === 'grid-col'" class="grid-cell" v-bind="layoutProps"
:class="[selected ? 'selected' : '', customClass]"
:class="[selected ? 'selected' : '', customClass]" :style="colHeightStyle"
:key="widget.id" @click.native.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler" @end="(evt) => onGridDragEnd(evt, widget.widgetList)"
@ -56,6 +56,12 @@
parentList: Array,
indexOfParentList: Number,
designer: Object,
colHeight: {
type: String,
default: null
},
},
data() {
return {
@ -79,6 +85,10 @@
return this.widget.options.customClass || ''
},
colHeightStyle() {
return !!this.colHeight ? {height: this.colHeight + 'px'} : {}
},
},
watch: {
'designer.formConfig.layoutType': {

View File

@ -17,7 +17,8 @@
@click.native.stop="selectWidget(widget)">
<template v-for="(colWidget, colIdx) in widget.cols">
<grid-col-widget :widget="colWidget" :designer="designer" :key="colWidget.id" :parent-list="widget.cols"
:index-of-parent-list="colIdx" :parent-widget="widget"></grid-col-widget>
:index-of-parent-list="colIdx" :parent-widget="widget"
:col-height="widget.options.colHeight"></grid-col-widget>
</template>
</el-row>

View File

@ -2,7 +2,7 @@ import {deepClone} from "@/utils/util"
import FormValidators from '@/utils/validators'
export default {
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel', 'getOptionData'],
computed: {
subFormName() {
@ -100,7 +100,7 @@ export default {
})
/* 监听重新加载选项事件 */
this.$on('reloadOptions', function (widgetNames) {
this.$on('reloadOptionItems', function (widgetNames) {
if ((widgetNames.length === 0) || (widgetNames.indexOf(this.field.options.name) > -1)) {
this.initOptionItems(true)
}
@ -157,7 +157,10 @@ export default {
|| (this.field.type === 'select') || (this.field.type === 'cascader')) {
if (!!this.globalOptionData && this.globalOptionData.hasOwnProperty(this.field.options.name)) {
if (!!keepSelected) {
this.reloadOptions(this.globalOptionData[this.field.options.name])
//this.reloadOptions(this.globalOptionData[this.field.options.name]) /* 异步更新option-data之后不能获取到最新值
// 以下改用provide的getOptionData()方法 */
const newOptionItems = this.getOptionData()
this.reloadOptions(newOptionItems[this.field.options.name])
} else {
this.loadOptions( this.globalOptionData[this.field.options.name] )
}

View File

@ -55,6 +55,7 @@
refList: this.widgetRefList,
formConfig: this.formConfig,
globalOptionData: this.optionData,
getOptionData: () => this.optionData,
globalModel: {
formModel: this.formModel,
}
@ -222,7 +223,7 @@
.el-form.Pad-layout {
margin: 0 auto;
width: 960px;
max-width: 960px;
border-radius: 15px;
box-shadow: 0 0 1px 10px #495060;
}

View File

@ -15,17 +15,17 @@
<img src="../../assets/vform-logo.png" @click="openHome">
<span class="bold">VForm</span> {{i18nt('application.productTitle')}} <span class="version-span">Ver {{vFormVersion}}</span></div>
<div class="float-right external-link">
<el-dropdown @command="handleLanguageChanged">
<el-dropdown v-if="showLink('languageMenu')" @command="handleLanguageChanged">
<span class="el-dropdown-link">{{curLangName}}<i class="el-icon-arrow-down el-icon--right"></i></span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh-CN">{{i18nt('application.zh-CN')}}</el-dropdown-item>
<el-dropdown-item command="en-US">{{i18nt('application.en-US')}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<a href="javascript:void(0)" @click="(ev) => openUrl(ev, gitUrl)" target="_blank"><svg-icon icon-class="github" />{{i18nt('application.github')}}</a>
<a href="javascript:void(0)" @click="(ev) => openUrl(ev, docUrl)" target="_blank"><svg-icon icon-class="document" />{{i18nt('application.document')}}</a>
<a href="javascript:void(0)" @click="(ev) => openUrl(ev, chatUrl)" target="_blank">{{i18nt('application.qqGroup')}}</a>
<a href="javascript:void(0)" @click="(ev) => openUrl(ev, subScribeUrl)" target="_blank">
<a v-if="showLink('externalLink')" href="javascript:void(0)" @click="(ev) => openUrl(ev, gitUrl)" target="_blank"><svg-icon icon-class="github" />{{i18nt('application.github')}}</a>
<a v-if="showLink('externalLink')" href="javascript:void(0)" @click="(ev) => openUrl(ev, docUrl)" target="_blank"><svg-icon icon-class="document" />{{i18nt('application.document')}}</a>
<a v-if="showLink('externalLink')" href="javascript:void(0)" @click="(ev) => openUrl(ev, chatUrl)" target="_blank">{{i18nt('application.qqGroup')}}</a>
<a v-if="showLink('externalLink')" href="javascript:void(0)" @click="(ev) => openUrl(ev, subScribeUrl)" target="_blank">
{{i18nt('application.subscription')}}<i class="el-icon-top-right"></i></a>
</div>
</el-header>
@ -37,7 +37,9 @@
<el-container class="center-layout-container">
<el-header class="toolbar-header">
<toolbar-panel :designer="designer"></toolbar-panel>
<toolbar-panel :designer="designer" ref="toolbarRef">
<template #toolButton><slot name="customToolButtons"></slot></template>
</toolbar-panel>
</el-header>
<el-main class="form-widget-main">
<el-scrollbar class="container-scroll-bar" :style="{height: scrollerHeight}">
@ -76,10 +78,36 @@
VFormWidget,
},
props: {
/* 后端字段列表API */
fieldListApi: {
type: Object,
default: null,
}
},
/* 禁止显示的组件名称数组 */
bannedWidgets: {
type: Array,
default: () => []
},
designerConfig: {
type: Object,
default: () => {
return {
languageMenu: true, //
externalLink: true, //GitHub
formTemplates: true, //
eventCollapse: true, //
clearDesignerButton: true, //
previewFormButton: true, //
importJsonButton: true, //JSON
exportJsonButton: true, //JSON
exportCodeButton: true, //
generateSFCButton: true, //SFC
}
}
},
},
data() {
return {
@ -104,6 +132,8 @@
provide() {
return {
serverFieldList: this.fieldList,
getDesignerConfig: () => this.designerConfig,
getBannedWidgets: () => this.bannedWidgets,
}
},
created() {
@ -125,6 +155,14 @@
this.loadFieldListFromServer()
},
methods: {
showLink(configName) {
if (this.designerConfig[configName] === undefined) {
return true
}
return !!this.designerConfig[configName]
},
openHome() {
if (!!this.vsCodeFlag) {
const msgObj = {
@ -233,6 +271,57 @@
}
},
clearDesigner() {
this.$refs.toolbarRef.clearFormWidget()
},
/**
* 刷新表单设计器
*/
refreshDesigner() {
//this.designer.loadFormJson( this.getFormJson() ) //
let fJson = this.getFormJson()
this.designer.clearDesigner(true) //
this.designer.loadFormJson(fJson)
},
/**
* 预览表单
*/
previewForm() {
this.$refs.toolbarRef.previewForm()
},
/**
* 导入表单JSON
*/
importJson() {
this.$refs.toolbarRef.importJson()
},
/**
* 导出表单JSON
*/
exportJson() {
this.$refs.toolbarRef.exportJson()
},
/**
* 导出Vue/HTML代码
*/
exportCode() {
this.$refs.toolbarRef.exportCode()
},
/**
* 生成SFC代码
*/
generateSFC() {
this.$refs.toolbarRef.generateSFC()
},
//TODO:
}

View File

@ -55,7 +55,7 @@
</el-form-item>
</el-collapse-item>
<el-collapse-item name="2" :title="i18nt('designer.setting.eventSetting')">
<el-collapse-item v-if="showEventCollapse()" name="2" :title="i18nt('designer.setting.eventSetting')">
<el-form-item label="onFormCreated" label-width="150px">
<el-button type="info" icon="el-icon-edit" plain round @click="editFormEventHandler('onFormCreated')">
{{i18nt('designer.setting.addEventHandler')}}</el-button>
@ -136,8 +136,11 @@
designer: Object,
formConfig: Object,
},
inject: ['getDesignerConfig'],
data() {
return {
designerConfig: this.getDesignerConfig(),
formActiveCollapseNames: ['1', '2'],
formSizes: [
@ -188,6 +191,14 @@
}, 1200)
},
methods: {
showEventCollapse() {
if (this.designerConfig['eventCollapse'] === undefined) {
return true
}
return !!this.designerConfig['eventCollapse']
},
editFormCss() {
this.formCssCode = this.designer.formConfig.cssCode
this.showEditFormCssDialogFlag = true

View File

@ -8,21 +8,21 @@
<el-form :model="optionModel" size="mini" label-position="left" label-width="120px" class="setting-form"
@submit.native.prevent>
<el-collapse v-model="widgetActiveCollapseNames" class="setting-collapse">
<el-collapse-item name="1" :title="i18nt('designer.setting.commonSetting')">
<el-collapse-item name="1" v-if="showCollapse(commonProps)" :title="i18nt('designer.setting.commonSetting')">
<template v-for="(editorName, propName) in commonProps">
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
</template>
</el-collapse-item>
<el-collapse-item name="2" :title="i18nt('designer.setting.advancedSetting')">
<el-collapse-item name="2" v-if="showCollapse(advProps)" :title="i18nt('designer.setting.advancedSetting')">
<template v-for="(editorName, propName) in advProps">
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
</template>
</el-collapse-item>
<el-collapse-item name="3" :title="i18nt('designer.setting.eventSetting')">
<el-collapse-item name="3" v-if="showEventCollapse() && showCollapse(eventProps)" :title="i18nt('designer.setting.eventSetting')">
<template v-for="(editorName, propName) in eventProps">
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
@ -37,21 +37,21 @@
<el-form :model="optionModel" size="mini" label-position="left" label-width="120px" class="setting-form"
@submit.native.prevent>
<el-collapse v-model="widgetActiveCollapseNames" class="setting-collapse">
<el-collapse-item name="1" :title="i18nt('designer.setting.commonSetting')">
<el-collapse-item name="1" v-if="showCollapse(commonProps)" :title="i18nt('designer.setting.commonSetting')">
<template v-for="(editorName, propName) in commonProps">
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
</template>
</el-collapse-item>
<el-collapse-item name="2" :title="i18nt('designer.setting.advancedSetting')">
<el-collapse-item name="2" v-if="showCollapse(advProps)" :title="i18nt('designer.setting.advancedSetting')">
<template v-for="(editorName, propName) in advProps">
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
</template>
</el-collapse-item>
<el-collapse-item name="3" :title="i18nt('designer.setting.eventSetting')">
<el-collapse-item name="3" v-if="showEventCollapse() && showCollapse(eventProps)" :title="i18nt('designer.setting.eventSetting')">
<template v-for="(editorName, propName) in eventProps">
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
@ -114,8 +114,11 @@
selectedWidget: Object,
formConfig: Object,
},
inject: ['getDesignerConfig'],
data() {
return {
designerConfig: this.getDesignerConfig(),
scrollerHeight: 0,
activeTab: "2",
@ -195,6 +198,14 @@
})
},
methods: {
showEventCollapse() {
if (this.designerConfig['eventCollapse'] === undefined) {
return true
}
return !!this.designerConfig['eventCollapse']
},
hasPropEditor(propName, editorName) {
if (!editorName) {
return false
@ -215,6 +226,23 @@
return !!this.$root.$options.components[ownPropEditorName] ? ownPropEditorName : editorName //
},
showCollapse(propsObj) {
let result = false
for (let propName in propsObj) {
if (!propsObj.hasOwnProperty(propName)) {
continue
}
if (this.hasPropEditor(propName, propsObj[propName])) {
result = true
break
}
}
return result
},
editEventHandler(eventName, eventParams) {
this.curEventName = eventName
this.eventHeader = `${this.optionModel.name}.${eventName}(${eventParams.join(', ')}) {`

View File

@ -0,0 +1,28 @@
<template>
<div>
<el-form-item :label="i18nt('designer.setting.gridColHeight')">
<el-input type="number" v-model="optionModel.colHeight" @input.native="inputNumberHandler"
min="0" class="hide-spin-button"></el-input>
</el-form-item>
</div>
</template>
<script>
import i18n from "@/utils/i18n"
import propertyMixin from "@/components/form-designer/setting-panel/property-editor/propertyMixin"
export default {
name: "colHeight-editor",
mixins: [i18n, propertyMixin],
props: {
designer: Object,
selectedWidget: Object,
optionModel: Object,
},
}
</script>
<style scoped>
</style>

View File

@ -1,7 +1,9 @@
<template>
<el-input-number v-model="optionModel.defaultValue" :min="0" :max="optionModel.max" style="width: 100%"
@change="emitDefaultValueChange">
</el-input-number>
<el-form-item :label="i18nt('designer.setting.defaultValue')">
<el-input-number v-model="optionModel.defaultValue" :min="0" :max="optionModel.max" style="width: 100%"
@change="emitDefaultValueChange">
</el-input-number>
</el-form-item>
</template>
<script>

View File

@ -57,6 +57,7 @@ const COMMON_PROPERTIES = {
'showRowNumber' : 'showRowNumber-editor',
'cellWidth' : 'cellWidth-editor',
'cellHeight' : 'cellHeight-editor',
'colHeight' : 'colHeight-editor',
'gutter' : 'gutter-editor',
'responsive' : 'responsive-editor',
'span' : 'span-editor',

View File

@ -13,15 +13,30 @@
<el-button :type="layoutType === 'H5' ? 'info': ''" @click="changeLayoutType('H5')">
{{i18nt('designer.toolbar.mobileLayout')}}</el-button>
</el-button-group>
<el-button type="" style="margin-left: 20px" :title="i18nt('designer.toolbar.nodeTreeHint')" @click="showNodeTreeDrawer">
<svg-icon icon-class="node-tree" /></el-button>
</div>
<el-drawer :title="i18nt('designer.toolbar.nodeTreeTitle')" direction="ltr" :visible.sync="showNodeTreeDrawerFlag" :modal="false" :size="280"
:destroy-on-close="true" class="node-tree-drawer">
<el-tree ref="nodeTree" :data="nodeTreeData" node-key="id" default-expand-all highlight-current class="node-tree"
icon-class="el-icon-arrow-right" @node-click="onNodeTreeClick"></el-tree>
</el-drawer>
<div class="right-toolbar">
<el-button type="text" @click="clearFormWidget"><i class="el-icon-delete" />{{i18nt('designer.toolbar.clear')}}</el-button>
<el-button type="text" @click="previewForm"><i class="el-icon-view" />{{i18nt('designer.toolbar.preview')}}</el-button>
<el-button type="text" @click="importJson">{{i18nt('designer.toolbar.importJson')}}</el-button>
<el-button type="text" @click="exportJson">{{i18nt('designer.toolbar.exportJson')}}</el-button>
<el-button type="text" @click="exportCode">{{i18nt('designer.toolbar.exportCode')}}</el-button>
<el-button type="text" @click="generateSFC"><svg-icon icon-class="vue-sfc" />{{i18nt('designer.toolbar.generateSFC')}}</el-button>
<el-button v-if="showToolButton('clearDesignerButton')" type="text" @click="clearFormWidget">
<i class="el-icon-delete" />{{i18nt('designer.toolbar.clear')}}</el-button>
<el-button v-if="showToolButton('previewFormButton')" type="text" @click="previewForm">
<i class="el-icon-view" />{{i18nt('designer.toolbar.preview')}}</el-button>
<el-button v-if="showToolButton('importJsonButton')" type="text" @click="importJson">
{{i18nt('designer.toolbar.importJson')}}</el-button>
<el-button v-if="showToolButton('exportJsonButton')" type="text" @click="exportJson">
{{i18nt('designer.toolbar.exportJson')}}</el-button>
<el-button v-if="showToolButton('exportCodeButton')" type="text" @click="exportCode">
{{i18nt('designer.toolbar.exportCode')}}</el-button>
<el-button v-if="showToolButton('generateSFCButton')" type="text" @click="generateSFC">
<svg-icon icon-class="vue-sfc" />{{i18nt('designer.toolbar.generateSFC')}}</el-button>
<slot name="toolButton"></slot>
</div>
<el-dialog :title="i18nt('designer.toolbar.preview')" :visible.sync="showPreviewDialogFlag" v-if="showPreviewDialogFlag"
@ -29,7 +44,8 @@
:destroy-on-close="true" class="small-padding-dialog" width="75%" :fullscreen="layoutType === 'H5'">
<div>
<div class="form-render-wrapper" :class="[layoutType === 'H5' ? 'h5-layout' : '']">
<VFormRender ref="preForm" :form-json="formJson" :form-data="testFormData"
<VFormRender ref="preForm" :form-json="formJson" :form-data="testFormData" :preview-state="true"
:option-data="testOptionData"
@appendButtonClick="testOnAppendButtonClick" @buttonClick="testOnButtonClick"
@formChange="handleFormChange"></VFormRender>
</div>
@ -138,12 +154,19 @@
import VFormRender from '@/components/form-render/index'
import CodeEditor from '@/components/code-editor/index'
import Clipboard from 'clipboard'
import {deepClone, copyToClipboard, generateId, getQueryParam} from "@/utils/util";
import {
deepClone,
copyToClipboard,
generateId,
getQueryParam,
traverseAllWidgets
} from "@/utils/util";
import i18n from '@/utils/i18n'
import {generateCode} from "@/utils/code-generator";
import {genSFC} from "@/utils/sfc-generator";
import loadBeautifier from "@/utils/beautifierLoader";
import { saveAs } from 'file-saver'
import axios from "axios"
export default {
name: "ToolbarPanel",
@ -156,14 +179,20 @@
props: {
designer: Object
},
inject: ['getDesignerConfig'],
data() {
return {
designerConfig: this.getDesignerConfig(),
showPreviewDialogFlag: false,
showImportJsonDialogFlag: false,
showExportJsonDialogFlag: false,
showExportCodeDialogFlag: false,
showFormDataDialogFlag: false,
showExportSFCDialogFlag: false,
showNodeTreeDrawerFlag: false,
nodeTreeData: [],
testFunc: '',
importTemplate: '',
@ -188,7 +217,17 @@
// {'pName': 'iPhone12', 'pNum': 10},
// {'pName': 'P50', 'pNum': 16},
// ]
'select62173': 2,
},
testOptionData: {
'select62173': [
{label: '01', value: 1},
{label: '22', value: 2},
{label: '333', value: 3},
]
},
}
},
computed: {
@ -211,8 +250,122 @@
return this.designer.getLayoutType()
},
},
watch: {
'designer.widgetList': {
deep: true,
handler(val) {
//console.log('test-----', val)
//this.refreshNodeTree()
}
},
},
methods: {
showToolButton(configName) {
if (this.designerConfig[configName] === undefined) {
return true
}
return !!this.designerConfig[configName]
},
buildTreeNodeOfWidget(widget, treeNode) {
let curNode = {
id: widget.id,
label: widget.options.label || widget.type,
//selectable: true,
}
treeNode.push(curNode)
if (widget.category === undefined) {
return
}
curNode.children = []
if (widget.type === 'grid') {
widget.cols.map(col => {
let colNode = {
id: col.id,
label: col.options.name || widget.type,
children: []
}
curNode.children.push(colNode)
col.widgetList.map(wChild => {
this.buildTreeNodeOfWidget(wChild, colNode.children)
})
})
} else if (widget.type === 'table') {
//TODO:
widget.rows.map(row => {
let rowNode = {
id: row.id,
label: 'table-row',
selectable: false,
children: [],
}
curNode.children.push(rowNode)
row.cols.map(cell => {
if (!!cell.merged) { //
return
}
let rowChildren = rowNode.children
let cellNode = {
id: cell.id,
label: 'table-cell',
children: []
}
rowChildren.push(cellNode)
cell.widgetList.map(wChild => {
this.buildTreeNodeOfWidget(wChild, cellNode.children)
})
})
})
} else if (widget.type === 'tab') {
widget.tabs.map(tab => {
let tabNode = {
id: tab.id,
label: tab.options.name || widget.type,
selectable: false,
children: []
}
curNode.children.push(tabNode)
tab.widgetList.map(wChild => {
this.buildTreeNodeOfWidget(wChild, tabNode.children)
})
})
} else if (widget.type === 'sub-form') {
widget.widgetList.map(wChild => {
this.buildTreeNodeOfWidget(wChild, curNode.children)
})
} else if (widget.category === 'container') { //
widget.widgetList.map(wChild => {
this.buildTreeNodeOfWidget(wChild, curNode.children)
})
}
},
refreshNodeTree() {
this.nodeTreeData.length = 0
this.designer.widgetList.forEach(wItem => {
this.buildTreeNodeOfWidget(wItem, this.nodeTreeData)
})
},
showNodeTreeDrawer() {
this.refreshNodeTree()
this.showNodeTreeDrawerFlag = true
this.$nextTick(() => {
if (!!this.designer.selectedId) { //
this.$refs.nodeTree.setCurrentKey(this.designer.selectedId)
}
})
},
undoHistory() {
this.designer.undoHistoryStep()
},
@ -405,12 +558,14 @@
},
handleFormChange(fieldName, newValue, oldValue, formModel) {
/*
console.log('---formChange start---')
console.log('fieldName', fieldName)
console.log('newValue', newValue)
console.log('oldValue', oldValue)
console.log('formModel', formModel)
console.log('---formChange end---')
*/
},
testOnAppendButtonClick(clickedWidget) {
@ -421,6 +576,31 @@
console.log('test', button)
},
findWidgetById(wId) {
let foundW = null
traverseAllWidgets(this.designer.widgetList, (w) => {
if (w.id === wId) {
foundW = w
}
})
return foundW
},
onNodeTreeClick(nodeData, node, nodeEl) {
//console.log('test', JSON.stringify(nodeData))
if ((nodeData.selectable !== undefined) && !nodeData.selectable) {
this.$message.info(this.i18nt('designer.hint.currentNodeCannotBeSelected'))
} else {
const selectedId = nodeData.id
const foundW = this.findWidgetById(selectedId)
if (!!foundW) {
this.designer.setSelected(foundW)
}
}
},
}
}
</script>
@ -496,4 +676,106 @@
box-shadow: 0 0 1px 10px #495060;
height: calc(100vh - 142px);
}
.node-tree-drawer ::v-deep {
.el-drawer {
padding: 15px;
overflow: auto;
}
.el-drawer__header {
margin-bottom: 12px;
padding: 5px 5px 0;
}
}
/*.node-tree-scroll-bar {*/
/* height: 100%;*/
/* overflow: auto;*/
/*}*/
.node-tree ::v-deep {
.el-tree > .el-tree-node:after {
border-top: none;
}
.el-tree-node {
position: relative;
padding-left: 12px;
}
.el-tree-node__content {
padding-left: 0 !important;
}
.el-tree-node__expand-icon.is-leaf{
display: none;
}
.el-tree-node__children {
padding-left: 12px;
}
.el-tree-node :last-child:before {
height: 38px;
}
.el-tree > .el-tree-node:before {
border-left: none;
}
.el-tree > .el-tree-node:after {
border-top: none;
}
.el-tree-node:before {
content: "";
left: -4px;
position: absolute;
right: auto;
border-width: 1px;
}
.el-tree-node:after {
content: "";
left: -4px;
position: absolute;
right: auto;
border-width: 1px;
}
.el-tree-node:before {
border-left: 1px dashed #4386c6;
bottom: 0px;
height: 100%;
top: -26px;
width: 1px;
}
.el-tree-node:after {
border-top: 1px dashed #4386c6;
height: 20px;
top: 12px;
width: 16px;
}
.el-tree-node.is-current > .el-tree-node__content {
background: #c2d6ea !important;
}
.el-tree-node__expand-icon {
margin-left: -3px;
padding: 6px 6px 6px 0px;
font-size: 16px;
}
.el-tree-node__expand-icon.el-icon-caret-right:before {
//font-size: 16px;
//content: "\e723";
}
.el-tree-node__expand-icon.expanded.el-icon-caret-right:before {
//font-size: 16px;
//content: "\e722";
}
}
</style>

View File

@ -55,14 +55,14 @@
</el-tab-pane>
<el-tab-pane name="formLib" style="padding: 8px">
<el-tab-pane v-if="showFormTemplates()" name="formLib" style="padding: 8px">
<span slot="label"><i class="el-icon-c-scale-to-original"></i> {{i18nt('designer.formLib')}}</span>
<template v-for="(ft, idx) in formTemplates">
<el-card :bord-style="{ padding: '0' }" shadow="hover" class="ft-card">
<el-popover placement="right" trigger="hover">
<img slot="reference" :src="ft.imgUrl" style="width: 200px">
<img :src="ft.imgUrl" style="height: 600px;width: 720px">
<img slot="reference" :src="ftImages[idx].imgUrl" style="width: 200px">
<img :src="ftImages[idx].imgUrl" style="height: 600px;width: 720px">
</el-popover>
<div class="bottom clear-fix">
<span class="ft-title">#{{idx+1}} {{ft.title}}</span>
@ -87,6 +87,15 @@
import i18n from "@/utils/i18n"
import axios from "axios"
import ftImg1 from '@/assets/ft-images/t1.png'
import ftImg2 from '@/assets/ft-images/t2.png'
import ftImg3 from '@/assets/ft-images/t3.png'
import ftImg4 from '@/assets/ft-images/t4.png'
import ftImg5 from '@/assets/ft-images/t5.png'
import ftImg6 from '@/assets/ft-images/t6.png'
import ftImg7 from '@/assets/ft-images/t7.png'
import ftImg8 from '@/assets/ft-images/t8.png'
export default {
name: "FieldPanel",
mixins: [i18n],
@ -96,8 +105,11 @@
props: {
designer: Object,
},
inject: ['getBannedWidgets', 'getDesignerConfig'],
data() {
return {
designerConfig: this.getDesignerConfig(),
firstTab: 'componentLib',
scrollerHeight: 0,
@ -110,6 +122,16 @@
customFields,
formTemplates: formTemplates,
ftImages: [
{imgUrl: ftImg1},
{imgUrl: ftImg2},
{imgUrl: ftImg3},
{imgUrl: ftImg4},
{imgUrl: ftImg5},
{imgUrl: ftImg6},
{imgUrl: ftImg7},
{imgUrl: ftImg8},
]
}
},
computed: {
@ -127,6 +149,18 @@
})
},
methods: {
isBanned(wName) {
return this.getBannedWidgets().indexOf(wName) > -1
},
showFormTemplates() {
if (this.designerConfig['formTemplates'] === undefined) {
return true
}
return !!this.designerConfig['formTemplates']
},
loadWidgets() {
this.containers = this.containers.map(con => {
return {
@ -134,7 +168,7 @@
displayName: this.i18n2t(`designer.widgetLabel.${con.type}`, `extension.widgetLabel.${con.type}`)
}
}).filter(con => {
return !con.internal
return !con.internal && !this.isBanned(con.type)
})
this.basicFields = this.basicFields.map(fld => {
@ -142,6 +176,8 @@
...fld,
displayName: this.i18n2t(`designer.widgetLabel.${fld.type}`, `extension.widgetLabel.${fld.type}`)
}
}).filter(fld => {
return !this.isBanned(fld.type)
})
this.advancedFields = this.advancedFields.map(fld => {
@ -149,6 +185,8 @@
...fld,
displayName: this.i18n2t(`designer.widgetLabel.${fld.type}`, `extension.widgetLabel.${fld.type}`)
}
}).filter(fld => {
return !this.isBanned(fld.type)
})
this.customFields = this.customFields.map(fld => {
@ -156,6 +194,8 @@
...fld,
displayName: this.i18n2t(`designer.widgetLabel.${fld.type}`, `extension.widgetLabel.${fld.type}`)
}
}).filter(fld => {
return !this.isBanned(fld.type)
})
},

View File

@ -9,6 +9,7 @@ export const containers = [
name: '',
hidden: false,
gutter: 12,
colHeight: null, //栅格列统一高度属性,用于解决栅格列设置响应式布局浮动后被挂住的问题!!
customClass: '', //自定义css类名
}
},

View File

@ -1,5 +1,5 @@
<template>
<el-col class="grid-cell" :class="[customClass]" v-bind="layoutProps"
<el-col class="grid-cell" :class="[customClass]" v-bind="layoutProps" :style="colHeightStyle"
:key="widget.id" v-show="!widget.options.hidden">
<template v-if="!!widget.widgetList && (widget.widgetList.length > 0)">
<template v-for="(subWidget, swIdx) in widget.widgetList">
@ -38,6 +38,12 @@
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
colHeight: {
type: String,
default: null
},
},
inject: ['refList', 'globalModel', 'formConfig', 'previewState'],
data() {
@ -58,6 +64,10 @@
return this.widget.options.customClass || ''
},
colHeightStyle() {
return !!this.colHeight ? {height: this.colHeight + 'px'} : {}
},
},
created() {
this.initLayoutProps()

View File

@ -6,7 +6,8 @@
:ref="widget.id" v-show="!widget.options.hidden">
<template v-for="(colWidget, colIdx) in widget.cols">
<grid-col-item :widget="colWidget" :key="colIdx" :parent-list="widget.cols"
:index-of-parent-list="colIdx" :parent-widget="widget"></grid-col-item>
:index-of-parent-list="colIdx" :parent-widget="widget"
:col-height="widget.options.colHeight"></grid-col-item>
</template>
</el-row>

View File

@ -64,6 +64,7 @@
sfRefList: this.subFormRefList, //SubForm
formConfig: this.formConfig,
globalOptionData: this.optionData,
getOptionData: () => this.optionData, /* 该方法用于在异步更新option-data之后重新获取到最新值 */
globalModel: {
formModel: this.formDataModel,
},
@ -356,13 +357,15 @@
* @param widgetNames 指定重新加载的组件名称或组件名数组不传则重新加载所有选项字段
*/
reloadOptionData(widgetNames) {
//this._provided.globalOptionData = this.optionData
let eventParams = []
if (!!widgetNames && (typeof widgetNames === 'string')) {
eventParams = [widgetNames]
} else if (!!widgetNames && Array.isArray(widgetNames)) {
eventParams = [...widgetNames]
}
this.broadcast('FieldWidget', 'reloadOptions', [eventParams])
this.broadcast('FieldWidget', 'reloadOptionItems', [eventParams])
},
getFormData(needValidation = true) {

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1637920503900" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4875" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M332.48 500.864a25.6 25.6 0 1 0 0-51.2H192.384v-184.96a115.2 115.2 0 0 0 89.6-112.128c0-63.488-51.712-115.2-115.2-115.2s-115.2 51.712-115.2 115.2a115.2 115.2 0 0 0 89.6 112.128v696.192a25.6 25.6 0 1 0 51.2 0v-141.12c2.304 0.192 4.48 0.512 6.912 0.512h133.184a25.6 25.6 0 1 0 0-51.2H199.296c-3.456 0-5.504-0.448-6.08-0.256a29.184 29.184 0 0 1-0.896-8.576V500.8h140.16zM102.784 152.64c0-35.264 28.736-64 64-64s64 28.736 64 64-28.736 64-64 64-64-28.736-64-64zM921.216 360.064h-486.4c-28.224 0-51.2 22.976-51.2 51.2v128c0 28.224 22.976 51.2 51.2 51.2h486.4c28.224 0 51.2-22.976 51.2-51.2v-128c0-28.224-22.976-51.2-51.2-51.2z m-486.336 179.2v-128h486.4v128h-486.4zM921.216 679.616h-486.4c-28.224 0-51.2 22.976-51.2 51.2v128c0 28.224 22.976 51.2 51.2 51.2h486.4c28.224 0 51.2-22.976 51.2-51.2v-128c0-28.224-22.976-51.2-51.2-51.2z m-486.336 179.2v-128h486.4v128h-486.4z" p-id="4876"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -112,6 +112,7 @@ export default {
loadFormTemplateHint: 'Are you sure to load this template?',
loadFormTemplateSuccess: 'Load form template success!',
loadFormTemplateFailed: 'Load form template failed.',
currentNodeCannotBeSelected: 'The current node cannot be selected.',
widgetSetting: 'Widget Config',
formSetting: 'Form Config',
@ -147,6 +148,8 @@ export default {
pcLayout: 'PC',
padLayout: 'Pad',
mobileLayout: 'H5',
nodeTreeHint: 'Tree View Of Component Hierarchy',
nodeTreeTitle: 'Tree View Of Component Hierarchy',
clear: 'Clear',
preview: 'Preview',
importJson: 'Import JSON',
@ -223,6 +226,7 @@ export default {
cellWidth: 'Width',
cellHeight: 'Height',
gridColHeight: 'Height Of Col(px)',
gutter: 'Gutter(px)',
columnSetting: 'Cols Setting',
colsOfGrid: 'Cols Of Grid:',

View File

@ -112,6 +112,7 @@ export default {
loadFormTemplateHint: '是否加载这个模板?加载后会覆盖设计器当前表单,你可以使用“撤销”功能恢复。',
loadFormTemplateSuccess: '表单模板加载成功',
loadFormTemplateFailed: '表单模板加载失败',
currentNodeCannotBeSelected: '当前组件节点不可选择',
widgetSetting: '组件设置',
formSetting: '表单设置',
@ -147,6 +148,8 @@ export default {
pcLayout: 'PC',
padLayout: 'Pad',
mobileLayout: 'H5',
nodeTreeHint: '组件层次结构树',
nodeTreeTitle: '组件层次结构树',
clear: '清空',
preview: '预览',
importJson: '导入JSON',
@ -223,7 +226,8 @@ export default {
cellWidth: '宽度',
cellHeight: '高度',
gutter: '栅格间隔(像素)',
gridColHeight: '栅格列统一高度(px)',
gutter: '栅格间隔(px)',
columnSetting: '栅格属性设置',
colsOfGrid: '当前栅格列:',
colSpanTitle: '栅格宽度',

View File

@ -8,7 +8,7 @@ export const DESIGNER_OPTIONS = {
}
export const VARIANT_FORM_VERSION = '2.1.7'
export const VARIANT_FORM_VERSION = '2.1.8'
//export const MOCK_CASE_URL = 'https://www.fastmock.site/mock/2de212e0dc4b8e0885fea44ab9f2e1d0/vform/'
export const MOCK_CASE_URL = 'https://ks3-cn-beijing.ksyuncs.com/vform-static/vcase/'

View File

@ -172,6 +172,34 @@ export function traverseContainWidgets(widgetList, handler) {
})
}
export function traverseAllWidgets(widgetList, handler) {
widgetList.map(w => {
handler(w)
if (w.type === 'grid') {
w.cols.map(col => {
handler(col)
traverseAllWidgets(col.widgetList, handler)
})
} else if (w.type === 'table') {
w.rows.map(row => {
row.cols.map(cell => {
handler(cell)
traverseAllWidgets(cell.widgetList, handler)
})
})
} else if (w.type === 'tab') {
w.tabs.map(tab => {
traverseAllWidgets(tab.widgetList, handler)
})
} else if (w.type === 'sub-form') {
traverseAllWidgets(w.widgetList, handler)
} else if (w.category === 'container') { //自定义容器
traverseAllWidgets(w.widgetList, handler)
}
})
}
export function copyToClipboard(content, clickEvent, $message, successMsg, errorMsg) {
const clipboard = new Clipboard(clickEvent.target, {
text: () => content