1.增加栅格容器响应式布局设置;2.增加常见表单模板库。

master
vdpAdmin 2021-11-20 19:21:58 +08:00
parent 85aaca2dd0
commit e33ce0c271
25 changed files with 597 additions and 82 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "variant-form", "name": "variant-form",
"version": "2.1.6", "version": "2.1.7",
"private": false, "private": false,
"scripts": { "scripts": {
"serve": "vue-cli-service serve --open src/main.js", "serve": "vue-cli-service serve --open src/main.js",
@ -15,7 +15,6 @@
"core-js": "^3.6.5", "core-js": "^3.6.5",
"element-ui": "^2.15.1", "element-ui": "^2.15.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"view-design": "^4.7.0-beta.10",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-i18n": "^8.24.5", "vue-i18n": "^8.24.5",
"vue2-editor": "^2.10.2", "vue2-editor": "^2.10.2",

View File

@ -1,6 +1,5 @@
<template> <template>
<el-col v-else-if="widget.type === 'grid-col'" class="grid-cell" :span="widget.options.span || 12" <el-col v-else-if="widget.type === 'grid-col'" class="grid-cell" v-bind="layoutProps"
:offset="widget.options.offset || 0" :push="widget.options.push || 0" :pull="widget.options.pull || 0"
:class="[selected ? 'selected' : '', customClass]" :class="[selected ? 'selected' : '', customClass]"
:key="widget.id" @click.native.stop="selectWidget(widget)"> :key="widget.id" @click.native.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}" <draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
@ -58,6 +57,19 @@
indexOfParentList: Number, indexOfParentList: Number,
designer: Object, designer: Object,
}, },
data() {
return {
layoutProps: {
span: this.widget.options.span || 12,
// md: this.widget.options.md || 12,
// sm: this.widget.options.sm || 12,
// xs: this.widget.options.xs || 12,
offset: this.widget.options.offset || 0,
push: this.widget.options.push || 0,
pull: this.widget.options.pull || 0,
}
}
},
computed: { computed: {
selected() { selected() {
return this.widget.id === this.designer.selectedId return this.widget.id === this.designer.selectedId
@ -69,9 +81,101 @@
}, },
watch: { watch: {
// 'designer.formConfig.layoutType': {
handler(val) {
if (!!this.widget.options.responsive) {
if (val === 'H5') {
this.layoutProps.span = this.widget.options.xs || 12
} else if (val === 'Pad') {
this.layoutProps.span = this.widget.options.sm || 12
} else {
this.layoutProps.span = this.widget.options.md || 12
}
} else {
this.layoutProps.span = this.widget.options.span || 12
}
}
},
'widget.options.responsive': {
handler(val) {
let lyType = this.designer.formConfig.layoutType
if (!!val) {
if (lyType === 'H5') {
this.layoutProps.span = this.widget.options.xs || 12
} else if (lyType === 'Pad') {
this.layoutProps.span = this.widget.options.sm || 12
} else {
this.layoutProps.span = this.widget.options.md || 12
}
} else {
this.layoutProps.span = this.widget.options.span || 12
}
}
},
'widget.options.span': {
handler(val) {
this.layoutProps.span = val
}
},
'widget.options.md': {
handler(val) {
this.layoutProps.span = val
}
},
'widget.options.sm': {
handler(val) {
this.layoutProps.span = val
}
},
'widget.options.xs': {
handler(val) {
this.layoutProps.span = val
}
},
'widget.options.offset': {
handler(val) {
this.layoutProps.offset = val
}
},
'widget.options.push': {
handler(val) {
this.layoutProps.push = val
}
},
'widget.options.pull': {
handler(val) {
this.layoutProps.pull = val
}
},
},
created() {
this.initLayoutProps()
}, },
methods: { methods: {
initLayoutProps() {
if (!!this.widget.options.responsive) {
let lyType = this.designer.formConfig.layoutType
if (lyType === 'H5') {
this.layoutProps.span = this.widget.options.xs || 12
} else if (lyType === 'Pad') {
this.layoutProps.span = this.widget.options.sm || 12
} else {
this.layoutProps.span = this.widget.options.md || 12
}
} else {
this.layoutProps.spn = this.widget.options.span
}
},
onGridDragEnd(evt, subList) { onGridDragEnd(evt, subList) {
// //
}, },

View File

@ -98,6 +98,13 @@ export default {
this.handleOnChange(values[0], values[1]) this.handleOnChange(values[0], values[1])
} }
}) })
/* 监听重新加载选项事件 */
this.$on('reloadOptions', function (widgetNames) {
if ((widgetNames.length === 0) || (widgetNames.indexOf(this.field.options.name) > -1)) {
this.initOptionItems(true)
}
})
}, },
handleOnCreated() { handleOnCreated() {
@ -141,7 +148,7 @@ export default {
} }
}, },
initOptionItems() { initOptionItems(keepSelected) {
if (this.designState) { if (this.designState) {
return return
} }
@ -149,7 +156,11 @@ export default {
if ((this.field.type === 'radio') || (this.field.type === 'checkbox') if ((this.field.type === 'radio') || (this.field.type === 'checkbox')
|| (this.field.type === 'select') || (this.field.type === 'cascader')) { || (this.field.type === 'select') || (this.field.type === 'cascader')) {
if (!!this.globalOptionData && this.globalOptionData.hasOwnProperty(this.field.options.name)) { if (!!this.globalOptionData && this.globalOptionData.hasOwnProperty(this.field.options.name)) {
this.loadOptions( this.globalOptionData[this.field.options.name] ) if (!!keepSelected) {
this.reloadOptions(this.globalOptionData[this.field.options.name])
} else {
this.loadOptions( this.globalOptionData[this.field.options.name] )
}
} }
} }
}, },
@ -165,6 +176,7 @@ export default {
if (!!this.field.options.required) { if (!!this.field.options.required) {
this.rules.push({ this.rules.push({
required: true, required: true,
trigger: ['blur', 'change'],
message: this.i18nt('render.hint.fieldRequired'), message: this.i18nt('render.hint.fieldRequired'),
}) })
} }
@ -199,6 +211,37 @@ export default {
} }
}, },
/**
* 禁用字段值变动触发表单校验
*/
disableChangeValidate() {
if (!this.rules) {
return
}
this.rules.forEach(rule => {
if (!!rule.trigger) {
rule.trigger.splice(0, rule.trigger.length)
}
})
},
/**
* 启用字段值变动触发表单校验
*/
enableChangeValidate() {
if (!this.rules) {
return
}
this.rules.forEach(rule => {
if (!!rule.trigger) {
rule.trigger.push('blur')
rule.trigger.push('change')
}
})
},
disableOptionOfList(optionList, optionValue) { disableOptionOfList(optionList, optionValue) {
if (!!optionList && (optionList.length > 0)) { if (!!optionList && (optionList.length > 0)) {
optionList.forEach(opt => { optionList.forEach(opt => {
@ -365,7 +408,11 @@ export default {
resetField() { resetField() {
let defaultValue = this.field.options.defaultValue let defaultValue = this.field.options.defaultValue
//this.disableChangeValidate() /* 禁用字段校验 */
this.setValue(defaultValue) this.setValue(defaultValue)
this.$nextTick(() => {
//this.enableChangeValidate() /* 开启字段校验 */
})
}, },
setWidgetOption(optionName, optionValue) { //通用组件选项修改API setWidgetOption(optionName, optionValue) { //通用组件选项修改API
@ -423,11 +470,23 @@ export default {
} }
}, },
/**
* 加载选项并清空字段值
* @param options
*/
loadOptions(options) { loadOptions(options) {
this.field.options.optionItems = deepClone(options) this.field.options.optionItems = deepClone(options)
this.clearSelectedOptions() //清空已选选项 this.clearSelectedOptions() //清空已选选项
}, },
/**
* 重新加载选项不清空字段值
* @param options
*/
reloadOptions(options) {
this.field.options.optionItems = deepClone(options)
},
disableOption(optionValue) { disableOption(optionValue) {
this.disableOptionOfList(this.field.options.optionItems, optionValue) this.disableOptionOfList(this.field.options.optionItems, optionValue)
}, },

View File

@ -160,6 +160,20 @@
return this.handleOnBeforeUpload(file) return this.handleOnBeforeUpload(file)
}, },
handleOnBeforeUpload(file) {
if (!!this.field.options.onBeforeUpload) {
let bfFunc = new Function('file', this.field.options.onBeforeUpload)
let result = bfFunc.call(this, file)
if (typeof result === 'boolean') {
return result
} else {
return true
}
}
return true
},
handleFileUpload(res, file, fileList) { handleFileUpload(res, file, fileList) {
if (!!this.field.options.onUploadSuccess) { if (!!this.field.options.onUploadSuccess) {
let mountFunc = new Function('result', 'file', 'fileList', this.field.options.onUploadSuccess) let mountFunc = new Function('result', 'file', 'fileList', this.field.options.onUploadSuccess)

View File

@ -150,6 +150,20 @@
return this.handleOnBeforeUpload(file) return this.handleOnBeforeUpload(file)
}, },
handleOnBeforeUpload(file) {
if (!!this.field.options.onBeforeUpload) {
let bfFunc = new Function('file', this.field.options.onBeforeUpload)
let result = bfFunc.call(this, file)
if (typeof result === 'boolean') {
return result
} else {
return true
}
}
return true
},
handlePictureUpload(res, file, fileList) { handlePictureUpload(res, file, fileList) {
if (!!this.field.options.onUploadSuccess) { if (!!this.field.options.onUploadSuccess) {
let customFn = new Function('result', 'file', 'fileList', this.field.options.onUploadSuccess) let customFn = new Function('result', 'file', 'fileList', this.field.options.onUploadSuccess)
@ -160,6 +174,7 @@
this.updateUploadFieldModelAndEmitDataChange() this.updateUploadFieldModelAndEmitDataChange()
this.uploadBtnHidden = this.fileList.length >= this.field.options.limit this.uploadBtnHidden = this.fileList.length >= this.field.options.limit
//console.log('test========', this.uploadBtnHidden)
} }
} }
}, },
@ -211,7 +226,7 @@
display: none; display: none;
} }
::v-deep div.el-upload__tip { /* 隐藏最后的文件上传按钮 */ ::v-deep div.el-upload__tip { /* 隐藏最后的文件上传按钮提示 */
display: none; display: none;
} }
} }

View File

@ -2,7 +2,7 @@
<div class="form-widget-container"> <div class="form-widget-container">
<el-form class="full-height-width widget-form" :label-position="labelPosition" <el-form class="full-height-width widget-form" :label-position="labelPosition"
:class="[customClass, layoutType === 'H5' ? 'h5-layout' : '']" :size="size" :validate-on-rule-change="false"> :class="[customClass, layoutType + '-layout']" :size="size" :validate-on-rule-change="false">
<div v-if="designer.widgetList.length === 0" class="no-widget-hint">{{i18nt('designer.noWidgetHint')}}</div> <div v-if="designer.widgetList.length === 0" class="no-widget-hint">{{i18nt('designer.noWidgetHint')}}</div>
@ -216,7 +216,18 @@
} }
} }
.el-form.h5-layout { .el-form.PC-layout {
//
}
.el-form.Pad-layout {
margin: 0 auto;
width: 960px;
border-radius: 15px;
box-shadow: 0 0 1px 10px #495060;
}
.el-form.H5-layout {
margin: 0 auto; margin: 0 auto;
width: 420px; width: 420px;
border-radius: 15px; border-radius: 15px;

View File

@ -158,7 +158,7 @@
return return
} }
axios.get(MOCK_CASE_URL + this.caseName).then(res => { axios.get(MOCK_CASE_URL + this.caseName + '.txt').then(res => {
if (!!res.data.code) { if (!!res.data.code) {
this.$message.error(this.i18nt('designer.hint.sampleLoadedFail')) this.$message.error(this.i18nt('designer.hint.sampleLoadedFail'))
return return
@ -167,7 +167,7 @@
this.setFormJson(res.data) this.setFormJson(res.data)
this.$message.success(this.i18nt('designer.hint.sampleLoadedSuccess')) this.$message.success(this.i18nt('designer.hint.sampleLoadedSuccess'))
}).catch(error => { }).catch(error => {
this.$message.error(this.i18nt('designer.hint.sampleLoadedFail') + ':' +error) this.$message.error(this.i18nt('designer.hint.sampleLoadedFail') + ':' + error)
}) })
}, },

View File

@ -200,11 +200,13 @@
return false return false
} }
return this.designer.hasConfig(this.selectedWidget, propName) let originalPropName = propName.replace(this.selectedWidget.type + '-', '') //-
return this.designer.hasConfig(this.selectedWidget, originalPropName)
}, },
getPropEditor(propName, editorName) { getPropEditor(propName, editorName) {
let ownPropEditorName = `${this.selectedWidget.type}-${propName}-editor` let originalPropName = propName.replace(this.selectedWidget.type + '-', '') //-
let ownPropEditorName = `${this.selectedWidget.type}-${originalPropName}-editor`
//console.log(ownPropEditorName, this.$options.components[ownPropEditorName]) //console.log(ownPropEditorName, this.$options.components[ownPropEditorName])
if (!!this.$options.components[ownPropEditorName]) { // if (!!this.$options.components[ownPropEditorName]) { //
return ownPropEditorName return ownPropEditorName

View File

@ -0,0 +1,23 @@
<template>
<el-form-item :label="i18nt('designer.setting.responsive')">
<el-checkbox v-model="optionModel.responsive"></el-checkbox>
</el-form-item>
</template>
<script>
import i18n from "@/utils/i18n"
export default {
name: "grid-col-responsive-editor",
mixins: [i18n],
props: {
designer: Object,
selectedWidget: Object,
optionModel: Object,
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,8 +1,25 @@
<template> <template>
<el-form-item :label="i18nt('designer.setting.colSpanTitle')"> <div>
<el-input-number v-model.number="optionModel.span" :min="1" :max="24" <el-form-item :label="i18nt('designer.setting.colSpanTitle')" v-if="!optionModel.responsive">
style="width: 100%"></el-input-number> <el-input-number v-model.number="optionModel.span" :min="1" :max="24"
</el-form-item> style="width: 100%"></el-input-number>
</el-form-item>
<el-form-item :label="i18nt('designer.setting.colSpanTitle') + '(PC)'"
v-if="!!optionModel.responsive && (formConfig.layoutType === 'PC')">
<el-input-number v-model.number="optionModel.md" :min="1" :max="24"
style="width: 100%"></el-input-number>
</el-form-item>
<el-form-item :label="i18nt('designer.setting.colSpanTitle') + '(Pad)'"
v-if="!!optionModel.responsive && (formConfig.layoutType === 'Pad')">
<el-input-number v-model.number="optionModel.sm" :min="1" :max="24"
style="width: 100%"></el-input-number>
</el-form-item>
<el-form-item :label="i18nt('designer.setting.colSpanTitle') + '(H5)'"
v-if="!!optionModel.responsive && (formConfig.layoutType === 'H5')">
<el-input-number v-model.number="optionModel.xs" :min="1" :max="24"
style="width: 100%"></el-input-number>
</el-form-item>
</div>
</template> </template>
<script> <script>
@ -16,6 +33,13 @@
selectedWidget: Object, selectedWidget: Object,
optionModel: Object, optionModel: Object,
}, },
computed: {
formConfig() {
return this.designer.formConfig
},
}
} }
</script> </script>

View File

@ -58,6 +58,7 @@ const COMMON_PROPERTIES = {
'cellWidth' : 'cellWidth-editor', 'cellWidth' : 'cellWidth-editor',
'cellHeight' : 'cellHeight-editor', 'cellHeight' : 'cellHeight-editor',
'gutter' : 'gutter-editor', 'gutter' : 'gutter-editor',
'responsive' : 'responsive-editor',
'span' : 'span-editor', 'span' : 'span-editor',
'offset' : 'offset-editor', 'offset' : 'offset-editor',
'push' : 'push-editor', 'push' : 'push-editor',
@ -125,35 +126,66 @@ const EVENT_PROPERTIES = {
} }
/** /**
* 如属性编辑器的组件名称设置为null则不显示该属性编辑器 * 注册组件常见属性
* @param propName 属性名称 * 如属性编辑器的组件名称propEditorName设置为null则不显示该属性编辑器
* @param uniquePropName 属性名称保证名称唯一不跟其他组件属性冲突
* @param propEditorName 对应属性编辑器的组件名称 * @param propEditorName 对应属性编辑器的组件名称
*/ */
export function registerCommonProperty(propName, propEditorName) { export function registerCommonProperty(uniquePropName, propEditorName) {
COMMON_PROPERTIES[propName] = propEditorName COMMON_PROPERTIES[uniquePropName] = propEditorName
} }
export function registerAdvancedProperty(propName, propEditorName) { /**
ADVANCED_PROPERTIES[propName] = propEditorName * 注册组件高级属性
* 如属性编辑器的组件名称propEditorName设置为null则不显示该属性编辑器
* @param uniquePropName 属性名称保证名称唯一不跟其他组件属性冲突
* @param propEditorName 对应属性编辑器的组件名称
*/
export function registerAdvancedProperty(uniquePropName, propEditorName) {
ADVANCED_PROPERTIES[uniquePropName] = propEditorName
} }
export function registerEventProperty(propName, propEditorName) { /**
EVENT_PROPERTIES[propName] = propEditorName * 注册组件事件属性
* 如属性编辑器的组件名称propEditorName设置为null则不显示该属性编辑器
* @param uniquePropName 属性名称保证名称唯一不跟其他组件属性冲突
* @param propEditorName 对应属性编辑器的组件名称
*/
export function registerEventProperty(uniquePropName, propEditorName) {
EVENT_PROPERTIES[uniquePropName] = propEditorName
} }
export function registerCPEditor(propName, propEditorName, editorComponent) { /**
* 注册常见属性对应的属性编辑器
* @param uniquePropName
* @param propEditorName
* @param editorComponent
*/
export function registerCPEditor(uniquePropName, propEditorName, editorComponent) {
Vue.component(propEditorName, editorComponent) Vue.component(propEditorName, editorComponent)
registerCommonProperty(propName, propEditorName) registerCommonProperty(uniquePropName, propEditorName)
} }
export function registerAPEditor(propName, propEditorName, editorComponent) { /**
* 注册高级属性对应的属性编辑器
* @param uniquePropName
* @param propEditorName
* @param editorComponent
*/
export function registerAPEditor(uniquePropName, propEditorName, editorComponent) {
Vue.component(propEditorName, editorComponent) Vue.component(propEditorName, editorComponent)
registerAdvancedProperty(propName, propEditorName) registerAdvancedProperty(uniquePropName, propEditorName)
} }
export function registerEPEditor(propName, propEditorName, editorComponent) { /**
* 注册事件属性对应的属性编辑器
* @param uniquePropName
* @param propEditorName
* @param editorComponent
*/
export function registerEPEditor(uniquePropName, propEditorName, editorComponent) {
Vue.component(propEditorName, editorComponent) Vue.component(propEditorName, editorComponent)
registerEventProperty(propName, propEditorName) registerEventProperty(uniquePropName, propEditorName)
} }
export default { export default {

View File

@ -8,6 +8,8 @@
<el-button-group style="margin-left: 20px"> <el-button-group style="margin-left: 20px">
<el-button :type="layoutType === 'PC' ? 'info': ''" @click="changeLayoutType('PC')"> <el-button :type="layoutType === 'PC' ? 'info': ''" @click="changeLayoutType('PC')">
{{i18nt('designer.toolbar.pcLayout')}}</el-button> {{i18nt('designer.toolbar.pcLayout')}}</el-button>
<el-button :type="layoutType === 'Pad' ? 'info': ''" @click="changeLayoutType('Pad')">
{{i18nt('designer.toolbar.padLayout')}}</el-button>
<el-button :type="layoutType === 'H5' ? 'info': ''" @click="changeLayoutType('H5')"> <el-button :type="layoutType === 'H5' ? 'info': ''" @click="changeLayoutType('H5')">
{{i18nt('designer.toolbar.mobileLayout')}}</el-button> {{i18nt('designer.toolbar.mobileLayout')}}</el-button>
</el-button-group> </el-button-group>

View File

@ -55,9 +55,22 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="formLib" v-if="false"> <el-tab-pane name="formLib" style="padding: 8px">
<span slot="label"><i class="el-icon-c-scale-to-original"></i> {{i18nt('designer.formLib')}}</span> <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">
</el-popover>
<div class="bottom clear-fix">
<span class="ft-title">#{{idx+1}} {{ft.title}}</span>
<el-button type="text" class="right-button" @click="loadFormTemplate(ft.jsonUrl)">
{{i18nt('designer.hint.loadFormTemplate')}}</el-button>
</div>
</el-card>
</template>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -68,9 +81,11 @@
<script> <script>
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
import {containers, basicFields, advancedFields, customFields} from "./widgetsConfig"; import {containers, basicFields, advancedFields, customFields} from "./widgetsConfig"
import {addWindowResizeHandler} from "@/utils/util"; import {formTemplates} from './templatesConfig'
import i18n from "@/utils/i18n"; import {addWindowResizeHandler} from "@/utils/util"
import i18n from "@/utils/i18n"
import axios from "axios"
export default { export default {
name: "FieldPanel", name: "FieldPanel",
@ -94,7 +109,7 @@
advancedFields, advancedFields,
customFields, customFields,
allContainers: [], formTemplates: formTemplates,
} }
}, },
computed: { computed: {
@ -113,8 +128,6 @@
}, },
methods: { methods: {
loadWidgets() { loadWidgets() {
//this.allContainers = deepClone(this.containers)
this.containers = this.containers.map(con => { this.containers = this.containers.map(con => {
return { return {
...con, ...con,
@ -168,10 +181,34 @@
}, },
addFieldByDbClick(widget) { addFieldByDbClick(widget) {
//console.log('addWidgetByDbClick')
this.designer.addFieldByDbClick(widget) this.designer.addFieldByDbClick(widget)
}, },
loadFormTemplate(jsonUrl) {
this.$confirm(this.i18nt('designer.hint.loadFormTemplateHint'), this.i18nt('render.hint.prompt'), {
confirmButtonText: this.i18nt('render.hint.confirm'),
cancelButtonText: this.i18nt('render.hint.cancel')
}).then(() => {
axios.get(jsonUrl).then(res => {
let modifiedFlag = false
if (typeof res.data === 'string') {
modifiedFlag = this.designer.loadFormJson( JSON.parse(res.data) )
} else if (res.data.constructor === Object) {
modifiedFlag = this.designer.loadFormJson(res.data)
}
if (modifiedFlag) {
this.designer.emitHistoryChange()
}
this.$message.success(this.i18nt('designer.hint.loadFormTemplateSuccess'))
}).catch(error => {
this.$message.error(this.i18nt('designer.hint.loadFormTemplateFailed') + ':' + error)
})
}).catch(error => {
console.error(error)
})
}
} }
} }
@ -179,18 +216,12 @@
<style lang="scss" scoped> <style lang="scss" scoped>
.side-scroll-bar { .side-scroll-bar {
//height: calc(100% - 56px);
//height: 100%;
::v-deep .el-scrollbar__wrap { ::v-deep .el-scrollbar__wrap {
overflow-x: hidden; overflow-x: hidden;
} }
} }
div.panel-container { div.panel-container {
//height: calc(100% - 48px);
//height: 100%;
//overflow-y: hidden;
padding-bottom: 10px; padding-bottom: 10px;
} }
@ -266,4 +297,44 @@
} }
} }
.el-card.ft-card {
border: 1px solid #8896B3;
}
.ft-card {
margin-bottom: 10px;
.bottom {
margin-top: 10px;
line-height: 12px;
}
/*
.image-zoom {
height: 500px;
width: 620px
}
*/
.ft-title {
font-size: 13px;
font-weight: bold;
}
.right-button {
padding: 0;
float: right;
}
.clear-fix:before, .clear-fix:after {
display: table;
content: "";
}
.clear-fix:after {
clear: both;
}
}
</style> </style>

View File

@ -0,0 +1,58 @@
export const formTemplates = [
{
title: '单列表单',
imgUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/t1.png',
jsonUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/json1.txt',
description: '表单模板详细说明...'
},
{
title: '多列表单',
imgUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/t2.png',
jsonUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/json2.txt',
description: '表单模板详细说明...'
},
{
title: '分组表单',
imgUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/t3.png',
jsonUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/json3.txt',
description: '表单模板详细说明...'
},
{
title: '标签页表单',
imgUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/t4.png',
jsonUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/json4.txt',
description: '表单模板详细说明...'
},
{
title: '主从表单',
imgUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/t5.png',
jsonUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/json5.txt',
description: '表单模板详细说明...'
},
{
title: '响应式表单',
imgUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/t6.png',
jsonUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/json6.txt',
description: '表单模板详细说明...'
},
{
title: '问卷调查表',
imgUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/t7.png',
jsonUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/json7.txt',
description: '表单模板详细说明...'
},
{
title: '固定表格表单',
imgUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/t8.png',
jsonUrl: 'https://ks3-cn-beijing.ksyuncs.com/vform-static/form-samples/json8.txt',
description: '表单模板详细说明...'
},
]

View File

@ -65,6 +65,10 @@ export const containers = [
offset: 0, offset: 0,
push: 0, push: 0,
pull: 0, pull: 0,
responsive: false, //是否开启响应式布局
md: 12,
sm: 12,
xs: 12,
customClass: '', //自定义css类名 customClass: '', //自定义css类名
} }
}, },

View File

@ -1,6 +1,5 @@
<template> <template>
<el-col class="grid-cell" :span="widget.options.span" :class="[customClass]" <el-col class="grid-cell" :class="[customClass]" v-bind="layoutProps"
:offset="widget.options.offset || 0" :push="widget.options.push || 0" :pull="widget.options.pull || 0"
:key="widget.id" v-show="!widget.options.hidden"> :key="widget.id" v-show="!widget.options.hidden">
<template v-if="!!widget.widgetList && (widget.widgetList.length > 0)"> <template v-if="!!widget.widgetList && (widget.widgetList.length > 0)">
<template v-for="(subWidget, swIdx) in widget.widgetList"> <template v-for="(subWidget, swIdx) in widget.widgetList">
@ -40,7 +39,20 @@
parentList: Array, parentList: Array,
indexOfParentList: Number, indexOfParentList: Number,
}, },
inject: ['refList', 'globalModel'], inject: ['refList', 'globalModel', 'formConfig', 'previewState'],
data() {
return {
layoutProps: {
span: this.widget.options.span,
md: this.widget.options.md || 12,
sm: this.widget.options.sm || 12,
xs: this.widget.options.xs || 12,
offset: this.widget.options.offset || 0,
push: this.widget.options.push || 0,
pull: this.widget.options.pull || 0,
}
}
},
computed: { computed: {
customClass() { customClass() {
return this.widget.options.customClass || '' return this.widget.options.customClass || ''
@ -48,8 +60,36 @@
}, },
created() { created() {
this.initLayoutProps()
this.initRefList() this.initRefList()
}, },
methods: {
initLayoutProps() {
if (!!this.widget.options.responsive) {
if (!!this.previewState) {
this.layoutProps.md = undefined
this.layoutProps.sm = undefined
this.layoutProps.xs = undefined
let lyType = this.formConfig.layoutType
if (lyType === 'H5') {
this.layoutProps.span = this.widget.options.xs || 12
} else if (lyType === 'Pad') {
this.layoutProps.span = this.widget.options.sm || 12
} else {
this.layoutProps.span = this.widget.options.md || 12
}
} else {
this.layoutProps.span = undefined
}
} else {
this.layoutProps.md = undefined
this.layoutProps.sm = undefined
this.layoutProps.xs = undefined
}
},
}
} }
</script> </script>

View File

@ -54,7 +54,8 @@
td.table-cell { td.table-cell {
display: table-cell; display: table-cell;
height: 36px; height: 36px;
border: 1px dashed #336699; //border: 1px dashed #336699;
border: 1px solid #e5e5e5;
} }
</style> </style>

View File

@ -53,6 +53,10 @@
type: Object, type: Object,
default: () => {} default: () => {}
}, },
previewState: { //
type: Boolean,
default: false
}
}, },
provide() { provide() {
return { return {
@ -62,7 +66,8 @@
globalOptionData: this.optionData, globalOptionData: this.optionData,
globalModel: { globalModel: {
formModel: this.formDataModel, formModel: this.formDataModel,
} },
previewState: this.previewState,
} }
}, },
data() { data() {
@ -163,14 +168,6 @@
this.buildDataFromWidget(wItem) this.buildDataFromWidget(wItem)
}) })
} }
// if (!this.formJsonObj || !this.widgetList) {
// return
// }
//
// this.widgetList.forEach((wItem) => {
// this.buildDataFromWidget(wItem)
// })
}, },
buildDataFromWidget(wItem) { buildDataFromWidget(wItem) {
@ -354,6 +351,20 @@
} }
}, },
/**
* 重新加载选项数据
* @param widgetNames 指定重新加载的组件名称或组件名数组不传则重新加载所有选项字段
*/
reloadOptionData(widgetNames) {
let eventParams = []
if (!!widgetNames && (typeof widgetNames === 'string')) {
eventParams = [widgetNames]
} else if (!!widgetNames && Array.isArray(widgetNames)) {
eventParams = [...widgetNames]
}
this.broadcast('FieldWidget', 'reloadOptions', [eventParams])
},
getFormData(needValidation = true) { getFormData(needValidation = true) {
if (!needValidation) { if (!needValidation) {
return this.formDataModel return this.formDataModel
@ -361,7 +372,7 @@
let callback = function nullFunc() {} let callback = function nullFunc() {}
let promise = new window.Promise(function (resolve, reject) { let promise = new window.Promise(function (resolve, reject) {
callback = function callback(formData, error) { callback = function(formData, error) {
!error ? resolve(formData) : reject(error); !error ? resolve(formData) : reject(error);
}; };
}); });
@ -465,7 +476,13 @@
} }
}) })
this.$refs.renderForm.clearValidate() this.$nextTick(() => {
this.clearValidate() /* 清除resetField方法触发的校验错误提示 */
})
},
clearValidate(props) {
this.$refs.renderForm.clearValidate(props)
}, },
validateForm() { validateForm() {

View File

@ -33,13 +33,13 @@ export const loadExtension = function () {
Vue.component(CardWidget.name, CardWidget) //注册设计期的容器组件 Vue.component(CardWidget.name, CardWidget) //注册设计期的容器组件
Vue.component(CardItem.name, CardItem) //注册运行期的容器组件 Vue.component(CardItem.name, CardItem) //注册运行期的容器组件
/* -------------------------------------------------- */ /* -------------------------------------------------- */
PERegister.registerCPEditor('folded', 'card-folded-editor', PERegister.registerCPEditor('card-folded', 'card-folded-editor',
PEFactory.createBooleanEditor('folded', 'extension.setting.cardFolded')) PEFactory.createBooleanEditor('folded', 'extension.setting.cardFolded'))
PERegister.registerCPEditor('showFold', 'card-showFold-editor', PERegister.registerCPEditor('card-showFold', 'card-showFold-editor',
PEFactory.createBooleanEditor('showFold', 'extension.setting.cardShowFold')) PEFactory.createBooleanEditor('showFold', 'extension.setting.cardShowFold'))
PERegister.registerCPEditor('cardWidth', 'card-cardWidth-editor', PERegister.registerCPEditor('card-cardWidth', 'card-cardWidth-editor',
PEFactory.createInputTextEditor('cardWidth', 'extension.setting.cardWidth')) PEFactory.createInputTextEditor('cardWidth', 'extension.setting.cardWidth'))
let shadowOptions = [ let shadowOptions = [
@ -47,7 +47,7 @@ export const loadExtension = function () {
{label: 'hover', value: 'hover'}, {label: 'hover', value: 'hover'},
{label: 'always', value: 'always'}, {label: 'always', value: 'always'},
] ]
PERegister.registerCPEditor('shadow', 'card-shadow-editor', PERegister.registerCPEditor('card-shadow', 'card-shadow-editor',
PEFactory.createSelectEditor('shadow', 'extension.setting.cardShadow', PEFactory.createSelectEditor('shadow', 'extension.setting.cardShadow',
{optionItems: shadowOptions})) {optionItems: shadowOptions}))
/* -------------------------------------------------- */ /* -------------------------------------------------- */
@ -67,7 +67,7 @@ export const loadExtension = function () {
/* -------------------------------------------------- */ /* -------------------------------------------------- */
Vue.component(AlertWidget.name, AlertWidget) //注册组件 Vue.component(AlertWidget.name, AlertWidget) //注册组件
/* -------------------------------------------------- */ /* -------------------------------------------------- */
PERegister.registerCPEditor('title', 'alert-title-editor', PERegister.registerCPEditor('alert-title', 'alert-title-editor',
PEFactory.createInputTextEditor('title', 'extension.setting.alertTitle')) PEFactory.createInputTextEditor('title', 'extension.setting.alertTitle'))
let typeOptions = [ let typeOptions = [
@ -76,34 +76,34 @@ export const loadExtension = function () {
{label: 'info', value: 'info'}, {label: 'info', value: 'info'},
{label: 'error', value: 'error'}, {label: 'error', value: 'error'},
] ]
PERegister.registerCPEditor('type', 'alert-type-editor', PERegister.registerCPEditor('alert-type', 'alert-type-editor',
PEFactory.createSelectEditor('type', 'extension.setting.alertType', PEFactory.createSelectEditor('type', 'extension.setting.alertType',
{optionItems: typeOptions})) {optionItems: typeOptions}))
PERegister.registerCPEditor('description', 'alert-description-editor', PERegister.registerCPEditor('alert-description', 'alert-description-editor',
PEFactory.createInputTextEditor('description', 'extension.setting.description')) PEFactory.createInputTextEditor('description', 'extension.setting.description'))
PERegister.registerCPEditor('closable', 'alert-closable-editor', PERegister.registerCPEditor('alert-closable', 'alert-closable-editor',
PEFactory.createBooleanEditor('closable', 'extension.setting.closable')) PEFactory.createBooleanEditor('closable', 'extension.setting.closable'))
PERegister.registerCPEditor('closeText', 'alert-closeText-editor', PERegister.registerCPEditor('alert-closeText', 'alert-closeText-editor',
PEFactory.createInputTextEditor('closeText', 'extension.setting.closeText')) PEFactory.createInputTextEditor('closeText', 'extension.setting.closeText'))
PERegister.registerCPEditor('center', 'alert-center-editor', PERegister.registerCPEditor('alert-center', 'alert-center-editor',
PEFactory.createBooleanEditor('center', 'extension.setting.center')) PEFactory.createBooleanEditor('center', 'extension.setting.center'))
PERegister.registerCPEditor('showIcon', 'alert-showIcon-editor', PERegister.registerCPEditor('alert-showIcon', 'alert-showIcon-editor',
PEFactory.createBooleanEditor('showIcon', 'extension.setting.showIcon')) PEFactory.createBooleanEditor('showIcon', 'extension.setting.showIcon'))
let effectOptions = [ let effectOptions = [
{label: 'light', value: 'light'}, {label: 'light', value: 'light'},
{label: 'dark', value: 'dark'}, {label: 'dark', value: 'dark'},
] ]
PERegister.registerCPEditor('effect', 'alert-effect-editor', PERegister.registerCPEditor('alert-effect', 'alert-effect-editor',
PEFactory.createRadioButtonGroupEditor('effect', 'extension.setting.effect', PEFactory.createRadioButtonGroupEditor('effect', 'extension.setting.effect',
{optionItems: effectOptions})) {optionItems: effectOptions}))
PERegister.registerEPEditor('onClose', 'alert-onClose-editor', PERegister.registerEPEditor('alert-onClose', 'alert-onClose-editor',
PEFactory.createEventHandlerEditor('onClose', [])) PEFactory.createEventHandlerEditor('onClose', []))
/* -------------------------------------------------- */ /* -------------------------------------------------- */
registerFWGenerator('alert', alertTemplateGenerator) //注册字段组件的代码生成器 registerFWGenerator('alert', alertTemplateGenerator) //注册字段组件的代码生成器

View File

@ -5,7 +5,8 @@
:ref="widget.id" v-show="!widget.options.hidden"> :ref="widget.id" v-show="!widget.options.hidden">
<div slot="header" class="clear-fix"> <div slot="header" class="clear-fix">
<span>{{widget.options.label}}</span> <span>{{widget.options.label}}</span>
<i class="float-right" :class="[!widget.options.folded ? 'el-icon-arrow-down' : 'el-icon-arrow-up']" @click="toggleCard"></i> <i v-if="widget.options.showFold" class="float-right"
:class="[!widget.options.folded ? 'el-icon-arrow-down' : 'el-icon-arrow-up']" @click="toggleCard"></i>
</div> </div>
<template v-if="!!widget.widgetList && (widget.widgetList.length > 0)"> <template v-if="!!widget.widgetList && (widget.widgetList.length > 0)">
<template v-for="(subWidget, swIdx) in widget.widgetList"> <template v-for="(subWidget, swIdx) in widget.widgetList">

View File

@ -2,7 +2,7 @@ import {buildClassAttr, buildContainerWidget, buildFieldWidget} from '@/utils/sf
export const cardTemplateGenerator = function (cw, formConfig) { export const cardTemplateGenerator = function (cw, formConfig) {
const wop = cw.options const wop = cw.options
const headerAttr = `header="${wop.label}"` //const headerAttr = `header="${wop.label}"`
const classAttr = buildClassAttr(cw) const classAttr = buildClassAttr(cw)
const styleAttr = !!wop.cardWidth ? `style="{width: ${wop.cardWidth} !important}"` : '' const styleAttr = !!wop.cardWidth ? `style="{width: ${wop.cardWidth} !important}"` : ''
const shadowAttr = `shadow="${wop.shadow}"` const shadowAttr = `shadow="${wop.shadow}"`
@ -10,7 +10,11 @@ export const cardTemplateGenerator = function (cw, formConfig) {
const cardTemplate = const cardTemplate =
`<div class="card-container"> `<div class="card-container">
<el-card ${headerAttr} ${classAttr} ${styleAttr} ${shadowAttr} ${vShowAttr}> <el-card ${classAttr} ${styleAttr} ${shadowAttr} ${vShowAttr}>
<div slot="header" class="clear-fix">
<span>${wop.label}</span>
${!!wop.showFold ? `<i class="float-right el-icon-arrow-down"></i>` : ''}
</div>
${ ${
cw.widgetList.map(wItem => { cw.widgetList.map(wItem => {
if (wItem.category === 'container') { if (wItem.category === 'container') {

View File

@ -108,6 +108,10 @@ export default {
fileNameInputPlaceholder: 'Enter the file name', fileNameInputPlaceholder: 'Enter the file name',
sampleLoadedSuccess: 'Example loaded successfully', sampleLoadedSuccess: 'Example loaded successfully',
sampleLoadedFail: 'Sample load failed', sampleLoadedFail: 'Sample load failed',
loadFormTemplate: 'Load This',
loadFormTemplateHint: 'Are you sure to load this template?',
loadFormTemplateSuccess: 'Load form template success!',
loadFormTemplateFailed: 'Load form template failed.',
widgetSetting: 'Widget Config', widgetSetting: 'Widget Config',
formSetting: 'Form Config', formSetting: 'Form Config',
@ -141,6 +145,7 @@ export default {
undoHint: 'Undo', undoHint: 'Undo',
redoHint: 'Redo', redoHint: 'Redo',
pcLayout: 'PC', pcLayout: 'PC',
padLayout: 'Pad',
mobileLayout: 'H5', mobileLayout: 'H5',
clear: 'Clear', clear: 'Clear',
preview: 'Preview', preview: 'Preview',
@ -226,6 +231,7 @@ export default {
colPushTitle: 'Push Of Col', colPushTitle: 'Push Of Col',
colPullTitle: 'Pull Of Col', colPullTitle: 'Pull Of Col',
addColumn: 'Add Column', addColumn: 'Add Column',
responsive: 'Responsive',
tabPaneSetting: 'Tab Panes', tabPaneSetting: 'Tab Panes',
addTabPane: 'Add Tab Pane', addTabPane: 'Add Tab Pane',

View File

@ -108,6 +108,10 @@ export default {
fileNameInputPlaceholder: '请输入文件名', fileNameInputPlaceholder: '请输入文件名',
sampleLoadedSuccess: '表单示例加载成功', sampleLoadedSuccess: '表单示例加载成功',
sampleLoadedFail: '表单示例加载失败', sampleLoadedFail: '表单示例加载失败',
loadFormTemplate: '加载此模板',
loadFormTemplateHint: '是否加载这个模板?加载后会覆盖设计器当前表单,你可以使用“撤销”功能恢复。',
loadFormTemplateSuccess: '表单模板加载成功',
loadFormTemplateFailed: '表单模板加载失败',
widgetSetting: '组件设置', widgetSetting: '组件设置',
formSetting: '表单设置', formSetting: '表单设置',
@ -141,6 +145,7 @@ export default {
undoHint: '撤销', undoHint: '撤销',
redoHint: '重做', redoHint: '重做',
pcLayout: 'PC', pcLayout: 'PC',
padLayout: 'Pad',
mobileLayout: 'H5', mobileLayout: 'H5',
clear: '清空', clear: '清空',
preview: '预览', preview: '预览',
@ -226,6 +231,7 @@ export default {
colPushTitle: '右移栅格数', colPushTitle: '右移栅格数',
colPullTitle: '左移栅格数', colPullTitle: '左移栅格数',
addColumn: '增加栅格', addColumn: '增加栅格',
responsive: '响应式布局',
tabPaneSetting: '选项卡设置', tabPaneSetting: '选项卡设置',
addTabPane: '增加选项卡页', addTabPane: '增加选项卡页',

View File

@ -8,9 +8,10 @@ export const DESIGNER_OPTIONS = {
} }
export const VARIANT_FORM_VERSION = '2.1.6' export const VARIANT_FORM_VERSION = '2.1.7'
export const MOCK_CASE_URL = 'https://www.fastmock.site/mock/2de212e0dc4b8e0885fea44ab9f2e1d0/vform/' //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/'
//export const ACE_BASE_PATH = 'public/lib/ace/src-min-noconflict' //export const ACE_BASE_PATH = 'public/lib/ace/src-min-noconflict'
export const ACE_BASE_PATH = 'https://ks3-cn-beijing.ksyun.com/vform2021/ace-mini' export const ACE_BASE_PATH = 'https://ks3-cn-beijing.ksyun.com/vform2021/ace-mini'

View File

@ -18,8 +18,15 @@ const containerTemplates = { //容器组件属性
`<el-row ${gridClassAttr}> `<el-row ${gridClassAttr}>
${ctn.cols.map(col => { ${ctn.cols.map(col => {
const colOpt = col.options const colOpt = col.options
const spanAttr = !!colOpt.responsive ? '' : `:span="${colOpt.span}"`
const mdAttr = !colOpt.responsive ? '' : `:md="${colOpt.md}"`
const smAttr = !colOpt.responsive ? '' : `:sm="${colOpt.sm}"`
const xsAttr = !colOpt.responsive ? '' : `:xs="${colOpt.xs}"`
const offsetAttr = !!colOpt.offset ? `:offset="${colOpt.offset}"` : ''
const pushAttr = !!colOpt.push ? `:push="${colOpt.push}"` : ''
const pullAttr = !!colOpt.pull ? `:pull="${colOpt.pull}"` : ''
const colClassAttr = buildClassAttr(col, 'grid-cell') const colClassAttr = buildClassAttr(col, 'grid-cell')
return `<el-col :span="${colOpt.span}" ${colClassAttr}> return `<el-col ${spanAttr} ${mdAttr} ${smAttr} ${xsAttr} ${offsetAttr} ${pushAttr} ${pullAttr} ${colClassAttr}>
${col.widgetList.map(cw => { ${col.widgetList.map(cw => {
if (cw.category === 'container') { if (cw.category === 'container') {
return buildContainerWidget(cw, formConfig) return buildContainerWidget(cw, formConfig)
@ -430,8 +437,9 @@ function genTemplate(formConfig, widgetList, vue3Flag = false) {
const genGlobalCSS = function (formConfig) { const genGlobalCSS = function (formConfig) {
const globalCssTemplate = const globalCssTemplate =
` .el-input-number.full-width-input, .el-cascader.full-width-input { ` .el-input-number.full-width-input, .el-cascader.full-width-input {
width: 100% !important; width: 100% !important;
} }
.el-form-item--medium { .el-form-item--medium {
.el-radio { .el-radio {
line-height: 36px !important; line-height: 36px !important;
@ -461,6 +469,19 @@ const genGlobalCSS = function (formConfig) {
margin-top: 4px; margin-top: 4px;
} }
} }
.clear-fix:before, .clear-fix:after {
display: table;
content: "";
}
.clear-fix:after {
clear: both;
}
.float-right {
float: right;
}
${formConfig.cssCode}` ${formConfig.cssCode}`