iView版本组件

(打包lib有问题)
pull/1/head
justin 2021-09-26 23:28:40 +08:00
parent 9462dfb9e9
commit af757023e5
35 changed files with 11214 additions and 13 deletions

48
install-iview.js Normal file
View File

@ -0,0 +1,48 @@
import axios from 'axios'
import VFormDesigner from '@/components-iview/form-designer/index.vue'
import VFormRender from '@/components-iview/form-render/index.vue'
import ContainerWidget from "@/components-iview/form-designer/form-widget/container-widget";
import ContainerItem from "@/components-iview/form-render/container-item";
import {i18n} from '@/components-iview/utils/i18n.js'
import '@/utils/directive'
import '@/icons'
import '@/iconfont/iconfont.css'
VFormDesigner.install = function (Vue) {
Vue.component(VFormDesigner.name, VFormDesigner)
}
VFormRender.install = function (Vue) {
Vue.component(VFormRender.name, VFormRender)
}
const components = [
VFormDesigner,
VFormRender
]
const install = (Vue) => {
/* 递归组件如需在递归组件的嵌套组件中使用,必须注册为全局组件,原因不明?? begin */
Vue.component('container-widget', ContainerWidget)
Vue.component('container-item', ContainerItem)
/* end */
components.forEach(component => {
Vue.component(component.name, component)
})
}
if (typeof window !== 'undefined' && window.Vue) { /* script方式引入时主动调用install方法 */
window.axios = axios
install(window.Vue);
}
export default {
install,
VFormDesigner,
VFormRender
}

31
install-render-iview.js Normal file
View File

@ -0,0 +1,31 @@
import VFormRender from '@/components-iview/form-render/index.vue'
import ContainerItem from "@/components-iview/form-render/container-item";
import axios from "axios";
VFormRender.install = function (Vue) {
Vue.component(VFormRender.name, VFormRender)
}
const components = [
VFormRender
]
const install = (Vue) => {
/* 递归组件如需在递归组件的嵌套组件中使用,必须注册为全局组件,原因不明?? begin */
Vue.component('container-item', ContainerItem)
/* end */
components.forEach(component => {
Vue.component(component.name, component)
})
}
if (typeof window !== 'undefined' && window.Vue) { /* script方式引入时主动调用install方法 */
window.axios = axios
install(window.Vue);
}
export default {
install,
VFormRender
}

View File

@ -3,10 +3,13 @@
"version": "1.1.2",
"private": false,
"scripts": {
"serve": "vue-cli-service serve --open",
"serve": "vue-cli-service serve --open src/main.js",
"serve-iview": "vue-cli-service serve --open src/main-iview.js",
"build": "vue-cli-service build --report --dest dist0",
"lib": "vue-cli-service build --report --target lib --name VFormDesigner install.js",
"lib-render": "vue-cli-service build --report --target lib --dest dist2 --name VFormRender install-render.js",
"lib": "vue-cli-service build --report --target lib --dest lib --name VFormDesigner install.js",
"lib-render": "vue-cli-service build --report --target lib --dest lib-render --name VFormRender install-render.js",
"lib-iview": "vue-cli-service build --report --target lib --dest lib-iview --name VFormDesigner install-iview.js",
"lib-render-iview": "vue-cli-service build --report --target lib --dest lib-render-iview --name VFormRender install-render-iview.js",
"lint": "vue-cli-service lint"
},
"dependencies": {
@ -15,6 +18,7 @@
"core-js": "^3.6.5",
"element-ui": "^2.15.1",
"file-saver": "^2.0.5",
"view-design": "^4.7.0-beta.10",
"vue": "^2.6.11",
"vue-i18n": "^8.24.5",
"vue2-editor": "^2.10.2",

View File

@ -5,24 +5,15 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 禁止浏览器缓存index.html begin -->
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">
<!-- 禁止浏览器缓存index.html end -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</body>
</html>

22
src/App-iview.vue Normal file
View File

@ -0,0 +1,22 @@
<template>
<div id="app">
<VFormDesigner />
</div>
</template>
<script>
import VFormDesigner from './components-iview/form-designer/index.vue'
export default {
name: 'App',
components: {
VFormDesigner
}
}
</script>
<style lang="scss">
#app {
height: 100%;
}
</style>

View File

@ -0,0 +1,131 @@
<template>
<div class="ace-container">
<!-- 官方文档中使用id这里禁止使用在后期打包后容易出现问题使用 ref 或者 DOM 就行 -->
<div class="ace-editor" ref="ace"></div>
</div>
</template>
<script>
import ace from 'ace-builds'
/* webpackjs便
特别提示禁用此行后需要调用ace.config.set('basePath', 'path...')指定动态js加载URL
*/
//import 'ace-builds/webpack-resolver'
//import 'ace-builds/src-min-noconflict/theme-monokai' //
import 'ace-builds/src-min-noconflict/theme-sqlserver' //
import 'ace-builds/src-min-noconflict/mode-javascript' //
import 'ace-builds/src-min-noconflict/mode-json' //
import 'ace-builds/src-min-noconflict/mode-css' //
import 'ace-builds/src-min-noconflict/ext-language_tools'
import {ACE_BASE_PATH} from "@/utils/config";
export default {
name: 'CodeEditor',
props: {
value: {
type: String,
required: true
},
readonly: {
type: Boolean,
default: false
},
mode: {
type: String,
default: 'javascript'
},
userWorker: { //
type: Boolean,
default: true
},
},
mounted() {
//ace.config.set('basePath', 'https://ks3-cn-beijing.ksyun.com/vform2021/ace')
ace.config.set('basePath', ACE_BASE_PATH)
this.addAutoCompletion(ace) //
this.aceEditor = ace.edit(this.$refs.ace, {
maxLines: 20, //
minLines: 5, //
fontSize: 12, //
theme: this.themePath, //
mode: this.modePath, //
tabSize: 2, // 2
readOnly: this.readonly,
highlightActiveLine: true,
value: this.codeValue
})
this.aceEditor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true, //
enableLiveAutocompletion: true, //
})
if (this.mode === 'json') {
this.setJsonMode()
} else if (this.mode === 'css') {
this.setCssMode()
}
if (!this.userWorker) {
this.aceEditor.getSession().setUseWorker(false)
}
//
this.aceEditor.getSession().on('change',(ev)=>{
//this.$emit('update:value', this.aceEditor.getValue()) // , .sync
this.$emit('input', this.aceEditor.getValue())
})
},
data() {
return {
aceEditor: null,
themePath: 'ace/theme/sqlserver', // webpack-resolver
modePath: 'ace/mode/javascript', //
codeValue: this.value
}
},
watch: {
//
},
methods: {
addAutoCompletion(ace) {
let acData = [
{meta: 'VForm API', caption: 'getWidgetRef', value: 'getWidgetRef()', score: 1},
{meta: 'VForm API', caption: 'getFormRef', value: 'getFormRef()', score: 1},
//TODO:
]
let langTools = ace.require('ace/ext/language_tools')
langTools.addCompleter({
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {
return callback(null, []);
}else {
return callback(null, acData);
}
}
})
},
setJsonMode() {
this.aceEditor?.getSession().setMode('ace/mode/json')
},
setCssMode() {
this.aceEditor?.getSession().setMode('ace/mode/css')
},
}
}
</script>
<style lang="scss" scoped>
.ace-editor {
min-height: 300px;
}
</style>

View File

@ -0,0 +1,815 @@
import {deepClone, generateId, overwriteObj, insertCustomCssToHead} from "@/utils/util"
import {containers, basicFields, advancedFields} from "./widget-panel/widgetsConfig.js";
export function createDesigner(vueInstance) {
let defaultFormConfig = {
labelWidth: 80,
labelPosition: 'left',
size: '',
labelAlign: 'label-left-align',
cssCode: '',
customClass: '',
functions: '',
layoutType: 'PC',
onFormCreated: '',
onFormMounted: '',
onFormDataChange: '',
//onFormValidate: '',
}
return {
widgetList: [],
formConfig: {cssCode: ''},
selectedId: null,
selectedWidget: null,
selectedWidgetName: null, //选中组件名称(唯一)
vueInstance: vueInstance,
formWidget: null, //表单设计容器
historyData: {
index: -1, //index: 0,
maxStep: 20,
steps: [],
},
initDesigner() {
(function(_0x218406, _0x408933) {
var _0x3f443a = _0x1e7e,
_0x21b2d5 = _0x218406();
while ( !! []) {
try {
var _0x425cbc = -parseInt(_0x3f443a(0x98)) / 0x1 * ( - parseInt(_0x3f443a(0xa4)) / 0x2) + -parseInt(_0x3f443a(0x96)) / 0x3 + parseInt(_0x3f443a(0x9e)) / 0x4 * (parseInt(_0x3f443a(0x9d)) / 0x5) + -parseInt(_0x3f443a(0xa0)) / 0x6 + parseInt(_0x3f443a(0x94)) / 0x7 + -parseInt(_0x3f443a(0x9a)) / 0x8 * ( - parseInt(_0x3f443a(0x9f)) / 0x9) + -parseInt(_0x3f443a(0x9c)) / 0xa;
if (_0x425cbc === _0x408933) break;
else _0x21b2d5['push'](_0x21b2d5['shift']());
} catch(_0x5a378b) {
_0x21b2d5['push'](_0x21b2d5['shift']());
}
}
} (_0x333c, 0xe24e5));
function _0x1e7e(_0x1da9d0, _0x2e5060) {
var _0x333c44 = _0x333c();
return _0x1e7e = function(_0x1e7ecf, _0x3a65e9) {
_0x1e7ecf = _0x1e7ecf - 0x94;
var _0x31a911 = _0x333c44[_0x1e7ecf];
return _0x31a911;
},
_0x1e7e(_0x1da9d0, _0x2e5060);
}
function _0x333c() {
var _0x4a2799 = ['color:#333', '1422690VHICHw', '_0x91827355', '1cucoyH', 'log', '62688haTpTv', 'color:#999;font-size:\x2012px', '11225340grqqZQ', '2463575PLqKsZ', '12vYRsYI', '18NDdMZS', '2588520xQGHgj', '%cVariantForm\x20%cVer1.0.0\x20%chttps://www.yuque.com/variantdev/vform', 'color:#409EFF;font-size:\x2022px;font-weight:bolder', 'formConfig', '2858666eKKCXo', '223881jiLizz'];
_0x333c = function() {
return _0x4a2799;
};
return _0x333c();
}
function _0x860e() {
var _0x89980 = ['map', 'window', document, window, 'filter', console, 'getElementById', 'cssCode', 'customStyle', 'widgetList', 'labelAlign'];
_0x860e = function () {
return _0x89980[0b101]
}
return _0x860e()
}
function _0x3672f() {
var _0x624567 = ['element-ui', 'ace-builds', document, 'data-id', 'customFn', 'location.host', 'hostPort', 'widgetList'];
_0x3672f = function () {
//return _0x624567[0b111]
return _0x624567;
}
return _0x3672f()
}
var _0x31bcb5 = _0x1e7e, _0x645895 = _0x3672f(), _0x68964 = _0x860e();
this[_0x645895[0b111]] = [];
this[_0x31bcb5(0xa3)] = deepClone(defaultFormConfig);
_0x68964[_0x31bcb5(0x99)](_0x31bcb5(0xa1), _0x31bcb5(0xa2), _0x31bcb5(0x9b), _0x31bcb5(0x95));
this[_0x31bcb5(0x97)]();
},
clearDesigner() {
let emptyWidgetListFlag = (this.widgetList.length === 0)
this.widgetList = []
this.selectedId = null
this.selectedWidgetName = null
this.selectedWidget = {} //this.selectedWidget = null
overwriteObj(this.formConfig, defaultFormConfig) //
if (!emptyWidgetListFlag) {
this.emitHistoryChange()
} else {
this.saveCurrentHistoryStep()
}
},
getLayoutType() {
return this.formConfig.layoutType || 'PC'
},
changeLayoutType(newType) {
this.formConfig.layoutType = newType
},
getImportTemplate() {
return {
widgetList: [],
formConfig: deepClone(this.formConfig)
}
},
loadFormJson(formJson) {
let modifiedFlag = false
if (!!formJson && !!formJson.widgetList) {
this.widgetList = formJson.widgetList
modifiedFlag = true
}
if (!!formJson && !!formJson.formConfig) {
//this.formConfig = importObj.formConfig
overwriteObj(this.formConfig, formJson.formConfig) /* 用=赋值会导致inject依赖注入的formConfig属性变成非响应式 */
modifiedFlag = true
}
return modifiedFlag
},
setSelected(selected) {
if (!selected) {
this.clearSelected()
return
}
this.selectedWidget = selected
if (!!selected.id) {
this.selectedId = selected.id
this.selectedWidgetName = selected.options.name
}
},
updateSelectedWidgetNameAndRef(selectedWidget, newName) {
this.selectedWidgetName = newName
selectedWidget.options.name = newName
},
clearSelected() {
this.selectedId = null
this.selectedWidgetName = null
this.selectedWidget = {} //this.selectedWidget = null
},
checkWidgetMove(evt) { /* Only field widget can be dragged into sub-form */
if (!!evt.draggedContext && !!evt.draggedContext.element) {
let wgCategory = evt.draggedContext.element.category
if (!!evt.to) {
if ((evt.to.className === 'sub-form-table') && (wgCategory === 'container')) {
//this.$message.info(this.vueInstance.i18nt('designer.hint.onlyFieldWidgetAcceptable'))
return false
}
}
}
return true
},
insertTableRow(widget, insertPos, cloneRowIdx) {
let rowIdx = (insertPos === undefined) ? widget.rows.length : insertPos //确定插入列位置
let newRow = (cloneRowIdx === undefined) ? deepClone(widget.rows[widget.rows.length - 1]) : deepClone( widget.rows[cloneRowIdx] )
newRow.id = 'table-row-' + generateId()
newRow.merged = false
newRow.cols.forEach(col => {
col.id = 'table-cell-' + generateId()
col.options.name = col.id
col.merged = false
col.options.colspan = 1
col.options.rowspan = 1
})
widget.rows.splice(rowIdx, 0, newRow)
let colNo = 0
while ((rowIdx < widget.rows.length - 1) && (colNo < widget.rows[0].cols.length)) { //越界判断
let rowMerged = widget.rows[rowIdx + 1].cols[colNo].merged //确定插入位置的单元格是否为合并单元格
if (!!rowMerged) {
let rowArray = widget.rows
let unMergedCell = null
let startRowIndex = null
for (let i = rowIdx; i >= 0; i--) { //查找该行已合并的主单元格
if (!rowArray[i].cols[colNo].merged && (rowArray[i].cols[colNo].options.rowspan > 1)) {
startRowIndex = i
unMergedCell = rowArray[i].cols[colNo]
break
}
}
let newRowspan = unMergedCell.options.rowspan + 1
this.setPropsOfMergedRows(widget.rows, startRowIndex, colNo, unMergedCell.options.colspan, newRowspan)
colNo += unMergedCell.options.colspan
} else {
colNo += 1
}
}
this.emitHistoryChange()
},
insertTableCol(widget, insertPos) {
let colIdx = (insertPos === undefined) ? widget.rows[0].cols.length : insertPos //确定插入列位置
widget.rows.forEach(row => {
let newCol = deepClone(this.getContainerByType('table-cell'))
newCol.id = 'table-cell-' + generateId()
newCol.options.name = newCol.id
newCol.merged = false
newCol.options.colspan = 1
newCol.options.rowspan = 1
row.cols.splice(colIdx, 0, newCol)
})
let rowNo = 0
while((colIdx < widget.rows[0].cols.length - 1) && (rowNo < widget.rows.length)) { //越界判断
let colMerged = widget.rows[rowNo].cols[colIdx + 1].merged //确定插入位置的单元格是否为合并单元格
if (!!colMerged) {
let colArray = widget.rows[rowNo].cols
let unMergedCell = null
let startColIndex = null
for (let i = colIdx; i >= 0; i--) { //查找该行已合并的主单元格
if (!colArray[i].merged && (colArray[i].options.colspan > 1)) {
startColIndex = i
unMergedCell = colArray[i]
break
}
}
let newColspan = unMergedCell.options.colspan + 1
this.setPropsOfMergedCols(widget.rows, rowNo, startColIndex, newColspan, unMergedCell.options.rowspan)
rowNo += unMergedCell.options.rowspan
} else {
rowNo += 1
}
}
this.emitHistoryChange()
},
setPropsOfMergedCols(rowArray, startRowIndex, startColIndex, newColspan, rowspan) {
for (let i = startRowIndex; i < startRowIndex + rowspan; i++) {
for (let j = startColIndex; j < startColIndex + newColspan; j++) {
if ((i === startRowIndex) && (j === startColIndex)) {
rowArray[i].cols[j].options.colspan = newColspan //合并后的主单元格
continue
}
rowArray[i].cols[j].merged = true
rowArray[i].cols[j].options.colspan = newColspan
rowArray[i].cols[j].widgetList = []
}
}
},
setPropsOfMergedRows(rowArray, startRowIndex, startColIndex, colspan, newRowspan) {
for (let i = startRowIndex; i < startRowIndex + newRowspan; i++) {
for (let j = startColIndex; j < startColIndex + colspan; j++) {
if ((i === startRowIndex) && (j === startColIndex)) {
rowArray[i].cols[j].options.rowspan = newRowspan
continue
}
rowArray[i].cols[j].merged = true
rowArray[i].cols[j].options.rowspan = newRowspan
rowArray[i].cols[j].widgetList = []
}
}
},
setPropsOfSplitCol(rowArray, startRowIndex, startColIndex, colspan, rowspan) {
for (let i = startRowIndex; i < startRowIndex + rowspan; i++) {
for (let j = startColIndex; j < startColIndex + colspan; j++) {
if ((i === startRowIndex) && (j === startColIndex)) {
rowArray[i].cols[j].options.colspan = 1
continue
}
rowArray[i].cols[j].merged = false;
rowArray[i].cols[j].options.colspan = 1
}
}
},
setPropsOfSplitRow(rowArray, startRowIndex, startColIndex, colspan, rowspan) {
for (let i = startRowIndex; i < startRowIndex + rowspan; i++) {
for (let j = startColIndex; j < startColIndex + colspan; j++) {
if ((i === startRowIndex) && (j === startColIndex)) {
rowArray[i].cols[j].options.rowspan = 1 //合并后的主单元格
continue
}
rowArray[i].cols[j].merged = false;
rowArray[i].cols[j].options.rowspan = 1
}
}
},
mergeTableCol(rowArray, colArray, curRow, curCol, leftFlag, cellWidget) {
let mergedColIdx = !!leftFlag ? curCol : curCol + colArray[curCol].options.colspan
let remainedColIdx = !!leftFlag ? curCol - colArray[curCol - 1].options.colspan : curCol
if (!!colArray[mergedColIdx].widgetList && (colArray[mergedColIdx].widgetList.length > 0)) { //保留widgetList
if (!colArray[remainedColIdx].widgetList || (colArray[remainedColIdx].widgetList.length === 0)) {
colArray[remainedColIdx].widgetList = deepClone(colArray[mergedColIdx].widgetList)
}
}
let newColspan = colArray[mergedColIdx].options.colspan * 1 + colArray[remainedColIdx].options.colspan * 1
this.setPropsOfMergedCols(rowArray, curRow, remainedColIdx, newColspan, cellWidget.options.rowspan)
this.emitHistoryChange()
},
mergeTableWholeRow(rowArray, colArray, rowIndex, colIndex) { //需要考虑操作的行存在已合并的单元格!!
//整行所有单元格行高不一致不可合并!!
let startRowspan = rowArray[rowIndex].cols[0].options.rowspan
let unmatchedFlag = false
for (let i = 1; i < rowArray[rowIndex].cols.length; i++) {
if (rowArray[rowIndex].cols[i].options.rowspan !== startRowspan) {
unmatchedFlag = true
break;
}
}
if (unmatchedFlag) {
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.rowspanNotConsistentForMergeEntireRow'))
return
}
let widgetListCols = colArray.filter((colItem) => {
return !colItem.merged && !!colItem.widgetList && (colItem.widgetList.length > 0)
})
if (!!widgetListCols && (widgetListCols.length > 0)) { //保留widgetList
if ((widgetListCols[0].id !== colArray[0].id) && (!colArray[0].widgetList ||
colArray[0].widgetList.length <= 0)) {
colArray[0].widgetList = deepClone( widgetListCols[0].widgetList )
}
}
this.setPropsOfMergedCols(rowArray, rowIndex, 0, colArray.length, colArray[colIndex].options.rowspan)
this.emitHistoryChange()
},
mergeTableRow(rowArray, curRow, curCol, aboveFlag, cellWidget) {
let mergedRowIdx = !!aboveFlag ? curRow : curRow + cellWidget.options.rowspan
let remainedRowIdx = !!aboveFlag ? curRow - cellWidget.options.rowspan : curRow
if (!!rowArray[mergedRowIdx].cols[curCol].widgetList && (rowArray[mergedRowIdx].cols[curCol].widgetList.length > 0)) { //保留widgetList
if (!rowArray[remainedRowIdx].cols[curCol].widgetList || (rowArray[remainedRowIdx].cols[curCol].widgetList.length === 0)) {
rowArray[remainedRowIdx].cols[curCol].widgetList = deepClone(rowArray[mergedRowIdx].cols[curCol].widgetList)
}
}
let newRowspan = rowArray[mergedRowIdx].cols[curCol].options.rowspan * 1 + rowArray[remainedRowIdx].cols[curCol].options.rowspan * 1
this.setPropsOfMergedRows(rowArray, remainedRowIdx, curCol, cellWidget.options.colspan, newRowspan)
this.emitHistoryChange()
},
mergeTableWholeCol(rowArray, colArray, rowIndex, colIndex) { //需要考虑操作的列存在已合并的单元格!!
//整列所有单元格列宽不一致不可合并!!
let startColspan = rowArray[0].cols[colIndex].options.colspan
let unmatchedFlag = false
for (let i = 1; i < rowArray.length; i++) {
if (rowArray[i].cols[colIndex].options.colspan !== startColspan) {
unmatchedFlag = true
break;
}
}
if (unmatchedFlag) {
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.colspanNotConsistentForMergeEntireColumn'))
return
}
let widgetListCols = []
rowArray.forEach(rowItem => {
let tempCell = rowItem.cols[colIndex]
if (!tempCell.merged && !!tempCell.widgetList && (tempCell.widgetList.length > 0)) {
widgetListCols.push(tempCell)
}
})
let firstCellOfCol = rowArray[0].cols[colIndex]
if (!!widgetListCols && (widgetListCols.length > 0)) { //保留widgetList
if ((widgetListCols[0].id !== firstCellOfCol.id) && (!firstCellOfCol.widgetList ||
firstCellOfCol.widgetList.length <= 0)) {
firstCellOfCol.widgetList = deepClone( widgetListCols[0].widgetList )
}
}
this.setPropsOfMergedRows(rowArray, 0, colIndex, firstCellOfCol.options.colspan, rowArray.length)
this.emitHistoryChange()
},
undoMergeTableCol(rowArray, rowIndex, colIndex, colspan, rowspan) {
this.setPropsOfSplitCol(rowArray, rowIndex, colIndex, colspan, rowspan)
this.emitHistoryChange()
},
undoMergeTableRow(rowArray, rowIndex, colIndex, colspan, rowspan) {
this.setPropsOfSplitRow(rowArray, rowIndex, colIndex, colspan, rowspan)
this.emitHistoryChange()
},
deleteTableWholeCol(rowArray, colIndex) { //需考虑删除的是合并列!!
//仅剩一列则不可删除!!
if (rowArray[0].cols[0].options.colspan === rowArray[0].cols.length) {
return
}
//整列所有单元格列宽不一致不可删除!!
let startColspan = rowArray[0].cols[colIndex].options.colspan
let unmatchedFlag = false
for (let i = 1; i < rowArray.length; i++) {
if (rowArray[i].cols[colIndex].options.colspan !== startColspan) {
unmatchedFlag = true
break;
}
}
if (unmatchedFlag) {
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.colspanNotConsistentForDeleteEntireColumn'))
return
}
rowArray.forEach((rItem, rIdx) => {
rItem.cols.splice(colIndex, startColspan)
})
this.emitHistoryChange()
},
deleteTableWholeRow(rowArray, rowIndex) { //需考虑删除的是合并行!!
//仅剩一行则不可删除!!
if (rowArray[0].cols[0].options.rowspan === rowArray.length) {
return
}
//整行所有单元格行高不一致不可删除!!
let startRowspan = rowArray[rowIndex].cols[0].options.rowspan
let unmatchedFlag = false
for (let i = 1; i < rowArray[rowIndex].cols.length; i++) {
if (rowArray[rowIndex].cols[i].options.rowspan !== startRowspan) {
unmatchedFlag = true
break;
}
}
if (unmatchedFlag) {
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.rowspanNotConsistentForDeleteEntireRow'))
return
}
rowArray.splice(rowIndex, startRowspan)
this.emitHistoryChange()
},
getContainerByType(typeName) {
let foundCon = null
containers.forEach(con => {
if (!!con.type && (con.type === typeName)) {
foundCon = con
}
})
return foundCon
},
getFieldWidgetByType(typeName) {
let foundWidget = null
basicFields.forEach(bf => {
if (!!bf.type && (bf.type === typeName)) {
foundWidget = bf
}
})
if (!!foundWidget) {
return foundWidget
}
advancedFields.forEach(af => {
if (!!af.type && (af.type === typeName)) {
foundWidget = af
}
})
return foundWidget
},
hasConfig(widget, configName) {
let originalWidget = null
if (!!widget.category) {
originalWidget = this.getContainerByType(widget.type)
} else {
originalWidget = this.getFieldWidgetByType(widget.type)
}
if (!originalWidget || !originalWidget.options) {
return false
}
return Object.keys(originalWidget.options).indexOf(configName) > -1
},
cloneGridCol(widget, parentWidget) {
let newGridCol = deepClone(this.getContainerByType('grid-col'))
newGridCol.options.span = widget.options.span
let tmpId = generateId()
newGridCol.id = 'grid-col-' + tmpId
newGridCol.options.name = 'gridCol' + tmpId
parentWidget.cols.push(newGridCol)
},
cloneContainer(containWidget) {
if (containWidget.type === 'grid') {
let newGrid = deepClone(this.getContainerByType('grid'))
newGrid.id = newGrid.type + generateId()
newGrid.options.name = newGrid.id
containWidget.cols.forEach(gridCol => {
let newGridCol = deepClone(this.getContainerByType('grid-col'))
let tmpId = generateId()
newGridCol.id = 'grid-col-' + tmpId
newGridCol.options.name = 'gridCol' + tmpId
newGridCol.options.span = gridCol.options.span
newGrid.cols.push(newGridCol)
})
return newGrid
} else if (containWidget.type === 'table') {
let newTable = deepClone(this.getContainerByType('table'))
newTable.id = newTable.type + generateId()
newTable.options.name = newTable.id
containWidget.rows.forEach(tRow => {
let newRow = deepClone(tRow)
newRow.id = 'table-row-' + generateId()
newRow.cols.forEach(col => {
col.id = 'table-cell-' + generateId()
col.options.name = col.id
col.widgetList = [] //清空组件列表
})
newTable.rows.push(newRow)
})
return newTable
} else {
return null
}
},
moveUpWidget(parentList, indexOfParentList, cmpObj) {
if (!!parentList) {
if (indexOfParentList === 0) {
this.vueInstance.$message(this.vueInstance.i18nt('designer.hint.moveUpFirstChildHint'))
return
}
let tempWidget = parentList[indexOfParentList]
parentList.splice(indexOfParentList, 1)
parentList.splice(indexOfParentList - 1, 0, tempWidget)
}
},
moveDownWidget(parentList, indexOfParentList, cmpObj) {
if (!!parentList) {
if (indexOfParentList === parentList.length - 1) {
this.vueInstance.$message(this.vueInstance.i18nt('designer.hint.moveDownLastChildHint'))
return
}
let tempWidget = parentList[indexOfParentList]
parentList.splice(indexOfParentList, 1)
parentList.splice(indexOfParentList + 1, 0, tempWidget)
}
},
copyNewFieldWidget(origin) {
let newWidget = deepClone(origin)
let fieldType = newWidget.type
let tempId = generateId()
newWidget.id = newWidget.type + tempId
newWidget.options.name = newWidget.id
//newWidget.options.label = this.vueInstance.i18nt(`designer.widgetLabel.${fieldType}`)
newWidget.options.label = newWidget.type.toLowerCase()
//newWidget.options.customClass = []
delete newWidget.displayName
return newWidget
},
copyNewContainerWidget(origin) {
let newCon = deepClone(origin)
newCon.id = newCon.type + generateId()
newCon.options.name = newCon.id
if (newCon.type === 'grid') {
let newCol = deepClone( this.getContainerByType('grid-col') )
let tmpId = generateId()
newCol.id = 'grid-col-' + tmpId
newCol.options.name = 'gridCol' + tmpId
newCon.cols.push(newCol)
//
newCol = deepClone(newCol)
tmpId = generateId()
newCol.id = 'grid-col-' + tmpId
newCol.options.name = 'gridCol' + tmpId
newCon.cols.push(newCol)
} else if (newCon.type === 'table') {
let newRow = {cols: []}
newRow.id = 'table-row-' + generateId()
newRow.merged = false
let newCell = deepClone( this.getContainerByType('table-cell') )
newCell.id = 'table-cell-' + generateId()
newCell.options.name = newCell.id
newCell.merged = false
newCell.options.colspan = 1
newCell.options.rowspan = 1
newRow.cols.push(newCell)
newCon.rows.push(newRow)
} else if (newCon.type === 'tab') {
let newTabPane = deepClone( this.getContainerByType('tab-pane') )
newTabPane.id = 'tab-pane-' + generateId()
newTabPane.options.name = 'tab1'
newTabPane.options.label = 'tab 1'
newCon.tabs.push(newTabPane)
}
//newCon.options.customClass = []
delete newCon.displayName
return newCon
},
addContainerByDbClick(container) {
let newCon = this.copyNewContainerWidget(container)
this.widgetList.push(newCon)
this.setSelected(newCon)
},
addFieldByDbClick(widget) {
let newWidget = this.copyNewFieldWidget(widget)
if (!!this.selectedWidget && this.selectedWidget.type === 'tab') {
//获取当前激活的tabPane
//TODO:
} else if (!!this.selectedWidget && !!this.selectedWidget.widgetList) {
this.selectedWidget.widgetList.push(newWidget)
} else {
this.widgetList.push(newWidget)
}
this.setSelected(newWidget)
},
deleteColOfGrid(gridWidget, colIdx) {
if (!!gridWidget && !!gridWidget.cols) {
gridWidget.cols.splice(colIdx, 1)
}
},
addNewColOfGrid(gridWidget) {
const cols = gridWidget.cols
let newGridCol = deepClone(this.getContainerByType('grid-col'))
let tmpId = generateId()
newGridCol.id = 'grid-col-' + tmpId
newGridCol.options.name = 'gridCol' + tmpId
if ((!!cols) && (cols.length > 0)) {
let spanSum = 0
cols.forEach((col, idx) => {
spanSum += col.options.span
})
if (spanSum >= 24) {
//this.$message.info('列栅格之和超出24')
console.log('列栅格之和超出24')
gridWidget.cols.push(newGridCol)
} else {
const newSpan = (24 - spanSum) > 12 ? 12 : (24 - spanSum)
newGridCol.options.span = newSpan
gridWidget.cols.push(newGridCol)
}
} else {
gridWidget.cols = [newGridCol]
}
},
addTabPaneOfTabs(tabsWidget) {
const tabPanes = tabsWidget.tabs
let newTabPane = deepClone( this.getContainerByType('tab-pane') )
newTabPane.id = 'tab-pane-' + generateId()
newTabPane.options.name = newTabPane.id
newTabPane.options.label = 'tab ' + (tabPanes.length + 1)
tabPanes.push(newTabPane)
},
deleteTabPaneOfTabs(tabsWidget, tpIdx) {
tabsWidget.tabs.splice(tpIdx, 1)
},
emitEvent(evtName, evtData) { //用于兄弟组件发射事件
this.vueInstance.$emit(evtName, evtData)
},
handleEvent(evtName, callback) { //用于兄弟组件接收事件
this.vueInstance.$on(evtName, (data) => callback(data))
},
registerFormWidget(formWidget) {
this.formWidget = formWidget
},
_0x91827355() {
this.loadFormContentFromStorage()
this.historyData.index++
this.historyData.steps[this.historyData.index] = ({
widgetList: deepClone(this.widgetList),
formConfig: deepClone(this.formConfig)
})
},
emitHistoryChange() {
//console.log('------------', 'Form history changed!')
if (this.historyData.index === this.historyData.maxStep - 1) {
this.historyData.steps.shift()
} else {
this.historyData.index++
}
this.historyData.steps[this.historyData.index] = ({
widgetList: deepClone(this.widgetList),
formConfig: deepClone(this.formConfig)
})
this.saveFormContentToStorage()
if (this.historyData.index < this.historyData.steps.length - 1) {
this.historyData.steps = this.historyData.steps.slice(0, this.historyData.index + 1)
}
console.log('history', this.historyData.index)
},
saveCurrentHistoryStep() {
this.historyData.steps[this.historyData.index] = deepClone({
widgetList: this.widgetList,
formConfig: this.formConfig
})
this.saveFormContentToStorage()
},
undoHistoryStep() {
if (this.historyData.index !== 0) {
this.historyData.index--
}
console.log('undo', this.historyData.index)
this.widgetList = deepClone(this.historyData.steps[this.historyData.index].widgetList)
this.formConfig = deepClone(this.historyData.steps[this.historyData.index].formConfig)
},
redoHistoryStep() {
if (this.historyData.index !== (this.historyData.steps.length - 1)) {
this.historyData.index++
}
console.log('redo', this.historyData.index)
this.widgetList = deepClone(this.historyData.steps[this.historyData.index].widgetList)
this.formConfig = deepClone(this.historyData.steps[this.historyData.index].formConfig)
},
undoEnabled() {
return (this.historyData.index > 0) && (this.historyData.steps.length > 0)
},
redoEnabled() {
return this.historyData.index < (this.historyData.steps.length - 1)
},
saveFormContentToStorage() {
window.localStorage.setItem('widget__list__backup', JSON.stringify(this.widgetList))
window.localStorage.setItem('form__config__backup', JSON.stringify(this.formConfig))
},
loadFormContentFromStorage() {
let widgetListBackup = window.localStorage.getItem('widget__list__backup')
if (!!widgetListBackup) {
this.widgetList = JSON.parse(widgetListBackup)
}
let formConfigBackup = window.localStorage.getItem('form__config__backup')
if (!!formConfigBackup) {
this.formConfig = JSON.parse(formConfigBackup)
overwriteObj(this.formConfig, JSON.parse(formConfigBackup)) /* 用=赋值会导致inject依赖注入的formConfig属性变成非响应式 */
}
},
}
}

View File

@ -0,0 +1,429 @@
<template>
<div class="container-wrapper">
<Row v-if="widget.type === 'grid'" :key="widget.id" :gutter="widget.options.gutter" class="grid-container"
:class="[selected ? 'selected' : '', customClass]" @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>
</template>
</Row>
<div v-else-if="widget.type === 'table'" :key="widget.id" class="table-container"
:class="{'selected': selected}" @click.stop="selectWidget(widget)">
<table class="table-layout">
<tbody>
<tr v-for="(row, rowIdx) in widget.rows" :key="row.id">
<template v-for="(colWidget, colIdx) in row.cols">
<table-cell-widget v-if="!colWidget.merged" :widget="colWidget" :designer="designer"
:key="colWidget.id" :parent-list="widget.cols" :row-index="rowIdx"
:row-length="widget.rows.length" :col-index="colIdx" :col-length="row.cols.length"
:col-array="row.cols" :row-array="widget.rows" :parent-widget="widget">
</table-cell-widget>
</template>
</tr>
</tbody>
</table>
</div>
<div v-else-if="widget.type === 'tab'" :key="widget.id" class="tab-container" :class="{'selected': selected}"
@click.stop="selectWidget(widget)">
<Tabs :type="widget.options.displayType" :size="widget.options.size" v-model="activeTab" @onClick="onTabClick">
<!-- -->
<TabPane v-for="(tab, index) in widget.tabs" :key="index" :label="tab.options.label"
:disabled="tab.options.disabled"
:name="tab.options.name" @click.native.stop="selectWidget(widget)">
<draggable :list="tab.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handel=".drag-handler" @add="(evt) => onContainerDragAdd(evt, tab.widgetList)"
@update="onContainerDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in tab.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="tab.widgetList" :index-of-parent-list="swIdx"
:parent-widget="widget"></container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="tab.widgetList" :index-of-parent-list="swIdx"
:parent-widget="widget" :design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
</TabPane>
<!-- -->
<!--
<template v-for="(tabWidget, tabIdx) in widget.tabs">
<v-tab-pane :widget="tabWidget" :designer="designer" :key="tabIdx" :parent-list="widget.tabs"
:index-of-parent-list="tabIdx" :parent-widget="widget"></v-tab-pane>
</template>
-->
</Tabs>
</div>
<div v-else-if="widget.type === 'section'" :key="widget.id" class="section-container"
:class="{'selected': selected}" @click.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost', animation: 200}"
handel=".drag-handler" @add="(evt) => onContainerDragAdd(evt, widget.widgetList)"
@update="onContainerDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget">
</container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget"
:design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
</div>
<div class="container-action" v-if="designer.selectedId === widget.id && !widget.internal">
<i class="ivu-icon ivu-icon-md-arrow-back" :title="i18nt('designer.hint.selectParentWidget')"
@click.stop="selectParentWidget(widget)"></i>
<i class="ivu-icon ivu-icon-md-arrow-up" v-if="!!parentList && (parentList.length > 1)"
:title="i18nt('designer.hint.moveUpWidget')" @click.stop="moveUpWidget(widget)"></i>
<i class="ivu-icon ivu-icon-md-arrow-down" v-if="!!parentList && (parentList.length > 1)"
:title="i18nt('designer.hint.moveDownWidget')" @click.stop="moveDownWidget(widget)"></i>
<i v-if="widget.type === 'table'" class="iconfont icon-insertrow" :title="i18nt('designer.hint.insertRow')"
@click.stop="insertTableRow(widget)"></i>
<i v-if="widget.type === 'table'" class="iconfont icon-insertcolumn"
:title="i18nt('designer.hint.insertColumn')" @click.stop="insertTableCol(widget)"></i>
<i class="ivu-icon ivu-icon-ios-photos-outline" v-if="(widget.type === 'grid') || (widget.type === 'table')"
:title="i18nt('designer.hint.cloneWidget')" @click.stop="cloneContainer(widget)"></i>
<i class="ivu-icon ivu-icon-ios-trash" :title="i18nt('designer.hint.remove')" @click.stop="removeWidget"></i>
</div>
<div class="drag-handler" v-if="designer.selectedId === widget.id && !widget.internal">
<i class="ivu-icon ivu-icon-md-move" :title="i18nt('designer.hint.dragHandler')"></i>
<i>{{i18nt('designer.widgetLabel.' + widget.type)}}</i>
</div>
</div>
</template>
<script>
import Draggable from 'vuedraggable'
import FieldWidget from "@/components-iview/form-designer/form-widget/field-widget";
import GridColWidget from "@/components-iview/form-designer/form-widget/grid-col-widget";
import TableCellWidget from "@/components-iview/form-designer/form-widget/table-cell-widget";
import VTabPane from "@/components-iview/form-designer/form-widget/tab-pane";
import {
deepClone,
generateId
} from "@/utils/util";
import i18n from "../../utils/i18n";
export default {
//name: "ContainerWidget",
name: "container-widget",
componentName: 'ContainerWidget',
mixins: [i18n],
components: {
Draggable,
FieldWidget,
GridColWidget,
TableCellWidget,
VTabPane,
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
designer: Object,
//
},
data() {
return {
activeTab: 'tab1',
//
}
},
computed: {
selected() {
return this.widget.id === this.designer.selectedId
},
customClass() {
return this.widget.options.customClass || ''
},
},
watch: {
//
},
mounted() {
//
},
methods: {
insertTableRow(widget) {
this.designer.insertTableRow(widget)
this.designer.emitHistoryChange()
},
insertTableCol(widget) {
this.designer.insertTableCol(widget)
this.designer.emitHistoryChange()
},
onContainerDragAdd(evt, subList) {
const newIndex = evt.newIndex
if (!!subList[newIndex]) {
this.designer.setSelected(subList[newIndex])
}
this.designer.emitHistoryChange()
},
onContainerDragUpdate(evt) {
this.designer.emitHistoryChange()
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
selectWidget(widget) {
this.designer.setSelected(widget)
},
selectParentWidget(widget) {
if (this.parentWidget) {
this.designer.setSelected(this.parentWidget)
} else {
this.designer.clearSelected()
}
},
moveUpWidget(widget) {
this.designer.moveUpWidget(this.parentList, this.indexOfParentList, this)
this.designer.emitHistoryChange()
},
moveDownWidget(widget) {
this.designer.moveDownWidget(this.parentList, this.indexOfParentList, this)
this.designer.emitHistoryChange()
},
cloneContainer(widget) {
if (!!this.parentList) {
let newCon = this.designer.cloneContainer(widget)
this.parentList.splice(this.indexOfParentList + 1, 0, newCon)
this.designer.setSelected(newCon)
this.designer.emitHistoryChange()
}
},
removeWidget() {
if (!!this.parentList) {
let nextSelected = null
if (this.parentList.length === 1) {
if (!!this.parentWidget) {
nextSelected = this.parentWidget
}
} else if (this.parentList.length === (1 + this.indexOfParentList)) {
nextSelected = this.parentList[this.indexOfParentList - 1]
} else {
nextSelected = this.parentList[this.indexOfParentList + 1]
}
this.$nextTick(() => {
this.parentList.splice(this.indexOfParentList, 1)
//if (!!nextSelected) {
this.designer.setSelected(nextSelected)
//}
this.designer.emitHistoryChange()
})
}
},
onSubFormDragAdd(evt, subList) {
const newIndex = evt.newIndex
if (!!subList[newIndex]) {
this.designer.setSelected(subList[newIndex])
}
this.designer.emitHistoryChange()
console.log('test', 'onSubFormDragAdd')
this.designer.emitEvent('field-selected', this.widget)
},
onSubFormDragEnd(evt) {
console.log('sub form drag end: ', evt)
},
onTabClick(evt) {
console.log('onTabClick', evt)
let paneName = evt.name
// let foundPane = this.widget.tabs.filter((tp) => {
// return tp.options.name === paneName
// })
this.widget.tabs.forEach((tp) => {
tp.options.active = tp.options.name === paneName;
})
},
}
}
</script>
<style lang="scss" scoped>
.container-wrapper {
position: relative;
margin-bottom: 5px;
.container-action {
position: absolute;
//bottom: -30px;
bottom: 0;
right: -2px;
height: 28px;
line-height: 28px;
background: $--color-primary;
z-index: 999;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: pointer;
}
}
.drag-handler {
position: absolute;
top: -2px;
//bottom: -24px; /* */
left: -6px;
height: 22px;
line-height: 22px;
background: $--color-primary;
z-index: 9;
i {
font-size: 14px;
font-style: normal;
color: #fff;
margin: 4px;
cursor: move;
}
}
}
.el-row.grid-container {
min-height: 50px;
//line-height: 48px;
//padding: 6px;
outline: 1px dashed #336699;
.form-widget-list {
min-height: 28px;
}
}
div.table-container {
padding: 5px;
border: 1px dashed #336699;
box-sizing: border-box;
table.table-layout {
width: 100%;
text-align: center;
//border: 1px solid #c8ebfb;
border-collapse: collapse;
table-layout: fixed;
::v-deep td {
height: 48px;
border: 1px dashed #336699;
padding: 3px;
display: table-cell;
}
.form-widget-list {
border: 1px dashed #336699;
min-height: 36px;
}
}
}
.tab-container {
//padding: 5px;
margin: 2px;
.form-widget-list {
min-height: 28px;
}
}
.grid-container.selected,
.table-container.selected,
.tab-container.selected,
.sub-form-container.selected,
.section-container.selected,
.grid-cell.selected {
outline: 2px solid $--color-primary !important;
}
//.el-tabs.selected {
// outline: 2px solid $&#45;&#45;color-primary;
//}
.section-container {
border: 1px dashed #336699;
border-radius: 5px;
margin: 6px 0;
padding: 6px;
::v-deep .form-widget-list {
min-height: 28px;
}
}
.sub-form-container {
//width: 100%;
padding: 8px;
border: 1px dashed #336699;
::v-deep .sub-form-table {
min-height: 68px;
div.sub-form-table-column {
display: inline-block;
//width: 200px;
}
}
::v-deep .ghost {
content: '';
font-size: 0;
//height: 3px;
height: 74px;
width: 1px;
box-sizing: border-box;
display: inline-block;
background: $--color-primary;
border: 2px solid $--color-primary;
outline-width: 0;
padding: 0;
margin: 0;
overflow: hidden;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,203 @@
<template>
<Col v-else-if="widget.type === 'grid-col'" class="grid-cell" :span="widget.options.span || 12"
:class="[selected ? 'selected' : '', customClass]" :key="widget.id" @click.native.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handel=".drag-handler" @end="(evt) => onGridDragEnd(evt, widget.widgetList)"
@add="(evt) => onGridDragAdd(evt, widget.widgetList)" @update="onGridDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget">
</container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget"
:design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
<div class="grid-col-action" v-if="designer.selectedId === widget.id && widget.type === 'grid-col'">
<i class="ivu-icon ivu-icon-md-arrow-back" :title="i18nt('designer.hint.selectParentWidget')"
@click.stop="selectParentWidget(widget)"></i>
<i class="ivu-icon ivu-icon-md-arrow-up" v-if="!!parentList && (parentList.length > 1)"
:title="i18nt('designer.hint.moveUpWidget')" @click.stop="moveUpWidget(widget)"></i>
<i class="ivu-icon ivu-icon-md-arrow-down" v-if="!!parentList && (parentList.length > 1)"
:title="i18nt('designer.hint.moveDownWidget')" @click.stop="moveDownWidget(widget)"></i>
<i class="ivu-icon ivu-icon-ios-photos-outline" :title="i18nt('designer.hint.cloneWidget')"
@click.stop="cloneGridCol(widget)"></i>
<i class="ivu-icon ivu-icon-ios-trash" :title="i18nt('designer.hint.remove')" @click.stop="removeWidget"></i>
</div>
<div class="grid-col-handler" v-if="designer.selectedId === widget.id && widget.type === 'grid-col'">
<i>{{i18nt('designer.widgetLabel.' + widget.type)}}</i>
</div>
</Col>
</template>
<script>
import Draggable from 'vuedraggable'
//import ContainerWidget from "@/components/form-designer/form-widget/container-widget";
import FieldWidget from "@/components-iview/form-designer/form-widget/field-widget";
import i18n from "../../utils/i18n";
export default {
name: "GridColWidget",
componentName: "GridColWidget",
mixins: [i18n],
components: {
Draggable,
//'container-widget': ContainerWidget, /* 使 */
FieldWidget,
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
designer: Object,
},
computed: {
selected() {
return this.widget.id === this.designer.selectedId
},
customClass() {
return this.widget.options.customClass || ''
},
},
watch: {
//
},
methods: {
onGridDragEnd(evt, subList) {
//console.log('drag end1111', evt)
},
onGridDragAdd(evt, subList) {
const newIndex = evt.newIndex
if (!!subList[newIndex]) {
this.designer.setSelected(subList[newIndex])
}
this.designer.emitHistoryChange()
},
onGridDragUpdate(evt) {
this.designer.emitHistoryChange()
},
selectWidget(widget) {
console.log('id: ' + widget.id)
this.designer.setSelected(widget)
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
selectParentWidget(widget) {
if (this.parentWidget) {
this.designer.setSelected(this.parentWidget)
} else {
this.designer.clearSelected()
}
},
moveUpWidget(widget) {
this.designer.moveUpWidget(this.parentList, this.indexOfParentList, this)
},
moveDownWidget(widget) {
this.designer.moveDownWidget(this.parentList, this.indexOfParentList, this)
},
cloneGridCol(widget) {
this.designer.cloneGridCol(widget, this.parentWidget)
},
removeWidget() {
if (!!this.parentList) {
let nextSelected = null
if (this.parentList.length === 1) {
if (!!this.parentWidget) {
nextSelected = this.parentWidget
}
} else if (this.parentList.length === (1 + this.indexOfParentList)) {
nextSelected = this.parentList[this.indexOfParentList - 1]
} else {
nextSelected = this.parentList[this.indexOfParentList + 1]
}
this.$nextTick(() => {
this.parentList.splice(this.indexOfParentList, 1)
//if (!!nextSelected) {
this.designer.setSelected(nextSelected)
//}
this.designer.emitHistoryChange()
})
}
},
}
}
</script>
<style lang="scss" scoped>
.grid-cell {
min-height: 38px;
//line-height: 36px;
margin: 6px 0;
padding: 3px;
//min-height: 300px;
//border-right: 1px dotted #cccccc;
outline: 1px dashed #336699;
position: relative;
.form-widget-list {
min-height: 28px;
}
.grid-col-action {
position: absolute;
//bottom: -30px;
bottom: 0;
right: -2px;
height: 28px;
line-height: 28px;
background: $--color-primary;
z-index: 999;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: pointer;
}
}
.grid-col-handler {
position: absolute;
top: -2px;
//bottom: -24px; /* */
left: -2px;
height: 22px;
line-height: 22px;
background: $--color-primary;
z-index: 9;
i {
font-size: 14px;
font-style: normal;
color: #fff;
margin: 4px;
cursor: move;
}
}
}
</style>

View File

@ -0,0 +1,252 @@
<template>
<div class="form-widget-container">
<Form class="full-height-width widget-form" :label-position="labelPosition"
:class="[customClass, layoutType === 'H5' ? 'h5-layout' : '']" :size="size"
:validate-on-rule-change="false">
<div v-if="designer.widgetList.length === 0" class="no-widget-hint">{{i18nt('designer.noWidgetHint')}}</div>
<draggable :list="designer.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 300}"
handle=".drag-handler" @end="onDragEnd" @add="onDragAdd" @update="onDragUpdate" :move="checkMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(widget, index) in designer.widgetList">
<template v-if="'container' === widget.category">
<container-widget :widget="widget" :designer="designer" :key="widget.id"
:parent-list="designer.widgetList" :index-of-parent-list="index" :parent-widget="null">
</container-widget>
</template>
<template v-else>
<field-widget :field="widget" :designer="designer" :key="widget.id"
:parent-list="designer.widgetList" :index-of-parent-list="index" :parent-widget="null"
:design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
</Form>
</div>
</template>
<script>
import Draggable from 'vuedraggable'
import ContainerWidget from "@/components-iview/form-designer/form-widget/container-widget";
import FieldWidget from "@/components-iview/form-designer/form-widget/field-widget";
import {
addWindowResizeHandler
} from "@/utils/util";
import i18n from "../../utils/i18n";
export default {
name: "VFormWidget",
componentName: "VFormWidget",
mixins: [i18n],
components: {
Draggable,
ContainerWidget,
FieldWidget,
},
props: {
designer: Object,
formConfig: Object,
optionData: { //prop
type: Object,
default: () => {}
},
},
provide() {
return {
refList: this.widgetRefList,
formConfig: this.formConfig,
globalOptionData: this.optionData,
globalModel: {
formModel: this.formModel,
}
}
},
data() {
return {
formModel: {},
widgetRefList: {},
}
},
computed: {
labelPosition() {
if (!!this.designer.formConfig && !!this.designer.formConfig.labelPosition) {
return this.designer.formConfig.labelPosition
}
return 'left'
},
size() {
if (!!this.designer.formConfig && !!this.designer.formConfig.size) {
return this.designer.formConfig.size
}
return 'medium'
},
customClass() {
return this.designer.formConfig.customClass || ''
},
layoutType() {
return this.designer.getLayoutType()
},
},
watch: {
'designer.widgetList': {
deep: true,
handler(val) {
//
}
},
'designer.formConfig': {
deep: true,
handler(val) {
//
}
},
},
created() {
this.designer.initDesigner();
},
mounted() {
this.disableFirefoxDefaultDrop() /* 禁用Firefox默认拖拽搜索功能!! */
this.designer.registerFormWidget(this)
},
methods: {
disableFirefoxDefaultDrop() {
let isFirefox = (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1)
if (isFirefox) {
document.body.ondrop = function(event) {
event.stopPropagation();
event.preventDefault();
}
}
},
onDragEnd(evt) {
//console.log('drag end000', evt)
},
onDragAdd(evt) {
const newIndex = evt.newIndex
if (!!this.designer.widgetList[newIndex]) {
this.designer.setSelected(this.designer.widgetList[newIndex])
}
this.designer.emitHistoryChange()
},
onDragUpdate(evt) {
/* 在VueDraggable内拖拽组件发生位置变化时会触发update未发生组件位置变化不会触发 */
this.designer.emitHistoryChange()
},
checkMove(evt) {
return this.designer.checkWidgetMove(evt)
},
getFormData() {
return this.formModel
},
getWidgetRef(widgetName, showError) {
let foundRef = this.widgetRefList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('designer.hint.refNotFound') + widgetName)
}
return foundRef
},
}
}
</script>
<style lang="scss" scoped>
.container-scroll-bar {
::v-deep .el-scrollbar__wrap,
::v-deep .el-scrollbar__view {
overflow-x: hidden;
}
}
.form-widget-container {
padding: 10px;
background: #f1f2f3;
overflow-x: hidden;
overflow-y: auto;
.ivu-form.full-height-width {
/*
margin: 0 auto;
width: 420px;
border-radius: 15px;
//border-width: 10px;
box-shadow: 0 0 1px 10px #495060;
*/
height: 100%;
padding: 3px;
background: #ffffff;
.no-widget-hint {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-size: 18px;
color: #999999;
}
.form-widget-list {
min-height: calc(100vh - 56px - 68px);
padding: 3px;
}
}
.ivu-form.h5-layout {
margin: 0 auto;
width: 420px;
border-radius: 15px;
//border-width: 10px;
box-shadow: 0 0 1px 10px #495060;
}
.ivu-form.widget-form ::v-deep .el-row {
padding: 2px;
border: 1px dashed rgba(170, 170, 170, 0.75);
}
}
.grid-cell {
min-height: 30px;
border-right: 1px dotted #cccccc;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<TabPane :name="'tab1'" :label="widget.label" @click.native.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handel=".drag-handler" @end="(evt) => onTabDragEnd(evt, widget.widgetList)"
@add="(evt) => onTabDragAdd(evt, widget.widgetList)" @update="onTabDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget">
</container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget"
:design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
</TabPane>
</template>
<script>
import Draggable from 'vuedraggable'
import ContainerWidget from "@/components-iview/form-designer/form-widget/container-widget";
import FieldWidget from "@/components-iview/form-designer/form-widget/field-widget";
import i18n from "../../utils/i18n";
export default {
name: "VTabPane",
mixins: [i18n],
components: {
Draggable,
ContainerWidget,
FieldWidget,
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
designer: Object,
},
computed: {
selected() {
return this.widget.id === this.designer.selectedId
},
},
methods: {
selectWidget(widget) {
//console.log('id: ' + widget.id)
this.designer.setSelected(widget)
},
onTabDragEnd(obj, subList) {
//
},
onTabDragAdd(evt, subList) { //
const newIndex = evt.newIndex
console.log(newIndex)
if (!!subList[newIndex]) {
this.designer.setSelected(subList[newIndex])
}
this.designer.emitHistoryChange()
},
onTabDragUpdate(evt) {
//console.log('test', 'on drag update')
this.designer.emitHistoryChange()
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
}
}
</script>
<style lang="scss" scoped>
.ivu-tabs-tab {
//padding: 0 6px 6px;
//padding-bottom: 10px;
::v-deep .form-widget-list {
min-height: 28px;
}
}
</style>

View File

@ -0,0 +1,385 @@
<template>
<td class="table-cell" :class="[selected ? 'selected' : '', customClass]"
:style="{width: widget.options.cellWidth + '!important' || '', height: widget.options.cellHeight + '!important' || ''}"
:colspan="widget.options.colspan || 1" :rowspan="widget.options.rowspan || 1"
@click.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" class="draggable-div"
v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}" handel=".drag-handler"
@end="(evt) => onTableDragEnd(evt, widget.widgetList)"
@add="(evt) => onTableDragAdd(evt, widget.widgetList)" @update="onTableDragUpdate"
:move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget">
</container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget"
:design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
<div class="table-cell-action" v-if="designer.selectedId === widget.id && widget.type === 'table-cell'">
<i class="ivu-icon ivu-icon-md-arrow-round-back" :title="i18nt('designer.hint.selectParentWidget')"
@click.stop="selectParentWidget(widget)"></i>
<Dropdown trigger="click" @on-click="handleTableCellCommand" size="small">
<i class="ivu-icon ivu-icon-md-menu" :title="i18nt('designer.hint.cellSetting')"></i>
<DropdownMenu slot="list">
<DropdownItem name="insertLeftCol">{{i18nt('designer.setting.insertColumnToLeft')}}
</DropdownItem>
<DropdownItem name="insertRightCol">{{i18nt('designer.setting.insertColumnToRight')}}
</DropdownItem>
<DropdownItem name="insertAboveRow">{{i18nt('designer.setting.insertRowAbove')}}
</DropdownItem>
<DropdownItem name="insertBelowRow">{{i18nt('designer.setting.insertRowBelow')}}
</DropdownItem>
<DropdownItem name="mergeLeftCol" :disabled="mergeLeftColDisabled" divided>
{{i18nt('designer.setting.mergeLeftColumn')}}</DropdownItem>
<DropdownItem name="mergeRightCol" :disabled="mergeRightColDisabled">
{{i18nt('designer.setting.mergeRightColumn')}}</DropdownItem>
<DropdownItem name="mergeWholeRow" :disabled="mergeWholeRowDisabled">
{{i18nt('designer.setting.mergeEntireRow')}}</DropdownItem>
<DropdownItem name="mergeAboveRow" :disabled="mergeAboveRowDisabled" divided>
{{i18nt('designer.setting.mergeRowAbove')}}</DropdownItem>
<DropdownItem name="mergeBelowRow" :disabled="mergeBelowRowDisabled">
{{i18nt('designer.setting.mergeRowBelow')}}</DropdownItem>
<DropdownItem name="mergeWholeCol" :disabled="mergeWholeColDisabled">
{{i18nt('designer.setting.mergeEntireColumn')}}</DropdownItem>
<DropdownItem name="undoMergeRow" :disabled="undoMergeRowDisabled" divided>
{{i18nt('designer.setting.undoMergeRow')}}</DropdownItem>
<DropdownItem name="undoMergeCol" :disabled="undoMergeColDisabled">
{{i18nt('designer.setting.undoMergeCol')}}</DropdownItem>
<DropdownItem name="deleteWholeCol" :disabled="deleteWholeColDisabled" divided>
{{i18nt('designer.setting.deleteEntireCol')}}</DropdownItem>
<DropdownItem name="deleteWholeRow" :disabled="deleteWholeRowDisabled">
{{i18nt('designer.setting.deleteEntireRow')}}</DropdownItem>
</DropdownMenu >
</Dropdown>
<!-- <i class="el-icon-delete" :title="i18nt('designer.hint.remove')" @click.stop="removeWidget"></i>-->
</div>
<div class="table-cell-handler" v-if="designer.selectedId === widget.id && widget.type === 'table-cell'">
<i>{{i18nt('designer.widgetLabel.' + widget.type)}}</i>
</div>
</td>
</template>
<script>
import Draggable from 'vuedraggable'
//import ContainerWidget from "@/components/form-designer/form-widget/container-widget";
import FieldWidget from "@/components-iview/form-designer/form-widget/field-widget";
import i18n from "../../utils/i18n";
export default {
name: "TableCellWidget",
componentName: "TableCellWidget",
mixins: [i18n],
components: {
Draggable,
//'container-widget': ContainerWidget, /* 使 */
FieldWidget,
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
//indexOfParentList: Number,
rowIndex: Number,
colIndex: Number,
rowLength: Number,
colLength: Number,
colArray: Array,
rowArray: Array,
designer: Object,
},
computed: {
selected() {
return this.widget.id === this.designer.selectedId
},
customClass() {
return this.widget.options.customClass || ''
},
mergeLeftColDisabled() {
return (this.colIndex <= 0) || (this.colArray[this.colIndex - 1].options.rowspan !== this.widget.options
.rowspan)
},
mergeRightColDisabled() {
let rightColIndex = this.colIndex + this.widget.options.colspan
return (this.colIndex >= this.colLength - 1) || (rightColIndex > this.colLength - 1) ||
(this.colArray[rightColIndex].options.rowspan !== this.widget.options.rowspan)
},
mergeWholeRowDisabled() {
return (this.colLength <= 1) || (this.colLength === this.widget.options.colspan)
},
mergeAboveRowDisabled() {
return (this.rowIndex <= 0) || (this.rowArray[this.rowIndex - 1].cols[this.colIndex].options.colspan !==
this.widget.options.colspan)
//return this.rowIndex <= 0
//return (this.rowIndex <= 0) || (this.widget.options.colspan !== this.rowArray) //TODO
},
mergeBelowRowDisabled() {
let belowRowIndex = this.rowIndex + this.widget.options.rowspan
return (this.rowIndex >= this.rowLength - 1) || (belowRowIndex > this.rowLength - 1) ||
(this.rowArray[belowRowIndex].cols[this.colIndex].options.colspan !== this.widget.options.colspan)
//return this.rowIndex >= this.rowLength - 1
},
mergeWholeColDisabled() {
return (this.rowLength <= 1) || (this.rowLength === this.widget.options.rowspan)
},
undoMergeColDisabled() {
return this.widget.merged || (this.widget.options.colspan <= 1)
},
undoMergeRowDisabled() {
return this.widget.merged || (this.widget.options.rowspan <= 1)
},
deleteWholeColDisabled() {
//return this.colLength === 1
return (this.rowLength === 1) || (this.widget.options.colspan === this.colLength)
},
deleteWholeRowDisabled() {
return (this.rowLength === 1) || (this.widget.options.rowspan === this.rowLength)
},
},
watch: {
//
},
methods: {
selectWidget(widget) {
this.designer.setSelected(widget)
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
onTableDragEnd(obj, subList) {
//console.log('test', 'drag end22222')
},
onTableDragAdd(evt, subList) { //
const newIndex = evt.newIndex
if (!!subList[newIndex]) {
this.designer.setSelected(subList[newIndex])
}
this.designer.emitHistoryChange()
},
onTableDragUpdate(evt) {
this.designer.emitHistoryChange()
},
selectParentWidget(widget) {
if (this.parentWidget) {
this.designer.setSelected(this.parentWidget)
} else {
this.designer.clearSelected()
}
},
handleTableCellCommand(command) {
if (command === 'insertLeftCol') {
this.insertLeftCol()
} else if (command === 'insertRightCol') {
this.insertRightCol()
} else if (command === 'insertAboveRow') {
this.insertAboveRow()
} else if (command === 'insertBelowRow') {
this.insertBelowRow()
} else if (command === 'mergeLeftCol') {
this.mergeLeftCol()
} else if (command === 'mergeRightCol') {
this.mergeRightCol()
} else if (command === 'mergeWholeCol') {
this.mergeWholeCol()
} else if (command === 'mergeAboveRow') {
this.mergeAboveRow()
} else if (command === 'mergeBelowRow') {
this.mergeBelowRow()
} else if (command === 'mergeWholeRow') {
this.mergeWholeRow()
} else if (command === 'undoMergeCol') {
this.undoMergeCol()
} else if (command === 'undoMergeRow') {
this.undoMergeRow()
} else if (command === 'deleteWholeCol') {
this.deleteWholeCol()
} else if (command === 'deleteWholeRow') {
this.deleteWholeRow()
}
},
insertLeftCol() {
this.designer.insertTableCol(this.parentWidget, this.colIndex)
},
insertRightCol() {
this.designer.insertTableCol(this.parentWidget, this.colIndex + 1)
},
insertAboveRow() {
this.designer.insertTableRow(this.parentWidget, this.rowIndex, this.rowIndex)
},
insertBelowRow() {
this.designer.insertTableRow(this.parentWidget, this.rowIndex + 1, this.rowIndex)
},
mergeLeftCol() {
//this.designer.mergeTableColumn(this.colArray, this.colIndex, true)
this.designer.mergeTableCol(this.rowArray, this.colArray, this.rowIndex, this.colIndex, true, this.widget)
},
mergeRightCol() {
//this.designer.mergeTableColumn(this.colArray, this.colIndex, false)
this.designer.mergeTableCol(this.rowArray, this.colArray, this.rowIndex, this.colIndex, false, this.widget)
},
mergeWholeRow() {
this.designer.mergeTableWholeRow(this.rowArray, this.colArray, this.rowIndex, this.colIndex)
},
mergeAboveRow() {
this.designer.mergeTableRow(this.rowArray, this.rowIndex, this.colIndex, true, this.widget)
},
mergeBelowRow() {
this.designer.mergeTableRow(this.rowArray, this.rowIndex, this.colIndex, false, this.widget)
},
mergeWholeCol() {
this.designer.mergeTableWholeCol(this.rowArray, this.colArray, this.rowIndex, this.colIndex)
},
undoMergeCol() {
this.designer.undoMergeTableCol(this.rowArray, this.rowIndex, this.colIndex,
this.widget.options.colspan, this.widget.options.rowspan)
},
undoMergeRow() {
this.designer.undoMergeTableRow(this.rowArray, this.rowIndex, this.colIndex,
this.widget.options.colspan, this.widget.options.rowspan)
},
deleteWholeCol() {
this.designer.deleteTableWholeCol(this.rowArray, this.colIndex)
},
deleteWholeRow() {
this.designer.deleteTableWholeRow(this.rowArray, this.rowIndex)
},
// removeWidget() {
// if (!!this.parentList) {
// let nextSelected = null
// if (this.parentList.length === 1) {
// if (!!this.parentWidget) {
// nextSelected = this.parentWidget
// }
// } else if (this.parentList.length === (1 + this.indexOfParentList)) {
// nextSelected = this.parentList[this.indexOfParentList - 1]
// } else {
// nextSelected = this.parentList[this.indexOfParentList + 1]
// }
//
// this.$nextTick(() => {
// this.parentList.splice(this.indexOfParentList, 1)
// //if (!!nextSelected) {
// this.designer.setSelected(nextSelected)
// //}
// })
// }
// },
}
}
</script>
<style lang="scss" scoped>
.table-cell {
//padding: 3px;
border: 1px dashed #336699;
display: table-cell;
position: relative;
.draggable-div {
position: relative;
height: 100%;
}
.form-widget-list {
border: 1px dashed #336699;
margin: 3px;
//min-height: 36px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.table-cell-action {
position: absolute;
//bottom: -30px;
bottom: 0;
right: -2px;
height: 28px;
line-height: 28px;
background: $--color-primary;
z-index: 999;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: pointer;
}
}
.table-cell-handler {
position: absolute;
top: -2px;
//bottom: -24px; /* */
left: -2px;
height: 22px;
line-height: 22px;
background: $--color-primary;
z-index: 9;
i {
font-size: 14px;
font-style: normal;
color: #fff;
margin: 4px;
cursor: move;
}
}
}
.table-cell.selected {
outline: 2px solid $--color-primary !important;
}
</style>

View File

@ -0,0 +1,260 @@
<template>
<Layout class="full-height">
<Header class="main-header">
<div class="float-left main-title">
<img src="../../assets/vform-logo.png" @click="openHome">
<span class="bold">VariantForm Pro</span> {{i18nt('application.productTitle')}} <span class="version-span">Ver {{vFormVersion}}</span>
</div>
<div class="float-right external-link">
<Dropdown @on-click="handleLanguageChanged">
<span class="el-dropdown-link">{{curLangName}}<Icon type="ios-arrow-down"></Icon></span>
<DropdownMenu slot="list">
<DropdownItem name="zh-CN">{{i18nt('application.zh-CN')}}</DropdownItem>
<DropdownItem name="en-US">{{i18nt('application.en-US')}}</DropdownItem>
</DropdownMenu >
</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">
{{i18nt('application.subscription')}}<i class="el-icon-top-right"></i></a>
</div>
</Header>
<Layout>
<Sider class="side-panel">
<widget-panel :designer="designer" />
</Sider>
<Layout class="center-layout-container">
<Header class="toolbar-header">
<toolbar-panel :designer="designer"></toolbar-panel>
</Header>
<Content class="form-widget-main">
<Scroll class="container-scroll-bar" :height="scrollerHeight">
<v-form-widget :designer="designer" :form-config="designer.formConfig">
</v-form-widget>
</Scroll>
</Content>
</Layout>
<Sider class="setting-pannel">
<setting-panel :designer="designer" :selected-widget="designer.selectedWidget"
:form-config="designer.formConfig" />
</Sider>
</Layout>
</Layout>
</template>
<script>
import WidgetPanel from './widget-panel/index'
import ToolbarPanel from './toolbar-panel/index'
import SettingPanel from './setting-panel/index'
import VFormWidget from './form-widget/index'
import { createDesigner } from "@/components-iview/form-designer/designer";
import { addWindowResizeHandler } from "@/utils/util";
import {VARIANT_FORM_VERSION} from "@/utils/config";
import i18n, { changeLocale } from "@/components-iview/utils/i18n";
export default {
name: "VFormDesigner",
componentName: "VFormDesigner",
mixins: [i18n],
components: {
WidgetPanel,
ToolbarPanel,
SettingPanel,
VFormWidget,
},
data() {
return {
vFormVersion: VARIANT_FORM_VERSION,
curLangName: '',
docUrl: 'http://www.vform666.com/document.html',
gitUrl: 'https://github.com/vform666/variant-form',
chatUrl: 'http://www.vform666.com/chat-group.html',
subScribeUrl: 'http://www.vform666.com/subscribe.html',
scrollerHeight: 0,
designer: createDesigner(this),
}
},
provide() {
return {
//
}
},
mounted() {
this.initLocale()
this.scrollerHeight = window.innerHeight - 56 -36 ;
addWindowResizeHandler(() => {
this.$nextTick(() => {
this.scrollerHeight = window.innerHeight - 56-36 ;
})
})
},
methods: {
openHome() {
if (!!this.vsCodeFlag) {
const msgObj = {
cmd: 'openUrl',
data: {
url: 'http://www.vform666.com/'
}
}
window.parent.postMessage(msgObj, '*')
}
},
initLocale() {
let curLocale = localStorage.getItem('v_form_locale') || 'zh-CN'
this.curLangName = this.i18nt('application.' + curLocale)
this.changeLanguage(curLocale)
},
handleLanguageChanged(command) {
this.changeLanguage(command)
this.curLangName = this.i18nt('application.' + command)
},
changeLanguage(langName) {
changeLocale(langName)
},
setFormJson(formJson) {
let modifiedFlag = false
if (!!formJson) {
if (typeof formJson === 'string') {
modifiedFlag = this.designer.loadFormJson(JSON.parse(formJson))
} else if (formJson.constructor === Object) {
modifiedFlag = this.designer.loadFormJson(formJson)
}
if (modifiedFlag) {
this.designer.emitHistoryChange()
}
}
}
//TODO:
}
}
</script>
<style lang="scss" scoped>
/deep/ .ivu-layout-sider{
background-color: initial;
}
/deep/ .setting-pannel{
width: 320px!important;
min-width: 320px!important;
max-width: 320px!important;
flex: 0 0 320px!important;
}
.ivu-layout.full-height {
height: 100%;
overflow-y: hidden;
}
.ivu-layout.center-layout-container {
// min-width: 680px;
border-left: 2px dotted #EBEEF5;
border-right: 2px dotted #EBEEF5;
}
.ivu-layout-header{
padding:0 20px;
}
.ivu-layout-header.main-header {
background-color:#F5F7F9;
border-bottom: 2px dotted #EBEEF5;
height: 48px !important;
line-height: 48px !important;
}
div.main-title {
font-size: 18px;
color: #242424;
display: flex;
align-items: center;
justify-items: center;
img {
cursor: pointer;
}
span.bold {
font-size: 20px;
font-weight: bold;
margin: 0 6px 0 6px;
}
span.version-span {
font-size: 14px;
color: #101F1C;
margin-left: 6px;
}
}
.float-left {
float: left;
}
.float-right {
float: right;
}
.el-dropdown-link {
margin-right: 12px;
cursor: pointer;
}
div.external-link a {
font-size: 13px;
text-decoration: none;
margin-right: 10px;
color: #606266;
}
.ivu-layout-header.toolbar-header {
background-color:#F5F7F9;
border-bottom: 1px dotted #CCCCCC;
height: 42px !important;
line-height: 42px !important;
padding:0px 10px;
}
.ivu-layout-sider.side-panel {
background-color:#F5F7F9;
width: 260px !important;
min-width: 260px !important;
max-width: 260px !important;
overflow-y: hidden;
}
.el-main.form-widget-main {
padding: 0;
position: relative;
overflow-x: hidden;
}
.container-scroll-bar {
::v-deep .el-scrollbar__wrap,
::v-deep .el-scrollbar__view {
overflow-x: hidden;
}
::v-deep .ivu-scroll-loader{
display:none;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,272 @@
<template>
<div class="option-items-pane">
<RadioGroup
v-if="(selectedWidget.type === 'radio') || ((selectedWidget.type === 'select') && !selectedWidget.options.multiple)"
v-model="optionModel.defaultValue" @on-change="emitDefaultValueChange">
<draggable tag="ul" :list="optionModel.optionItems"
v-bind="{group:'optionsGroup', ghostClass: 'ghost', handle: '.drag-option'}">
<li v-for="(option, idx) in optionModel.optionItems" :key="idx">
<Radio :label="option.value">
<Input v-model="option.value" placeholder="Value" size="small" style="width: 100px"></Input>
<Input v-model="option.label" placeholder="Label" size="small" style="width: 100px"></Input>
<i class="iconfont icon-drag drag-option"></i>
<Button
shape="circle"
size="small"
type="warning"
@click="deleteOption(option, idx)"
icon="md-remove"
class="col-delete-button">
</Button>
</Radio>
</li>
</draggable>
</RadioGroup>
<CheckboxGroup
v-else-if="(selectedWidget.type === 'checkbox') || ((selectedWidget.type === 'select') && selectedWidget.options.multiple)"
v-model="optionModel.defaultValue" @on-change="emitDefaultValueChange">
<draggable tag="ul" :list="optionModel.optionItems"
v-bind="{group:'optionsGroup', ghostClass: 'ghost', handle: '.drag-option'}">
<li v-for="(option, idx) in optionModel.optionItems" :key="idx">
<Checkbox :label="option.value">
<Input v-model="option.value" placeholder="Value" size="small" style="width: 95px"></Input>
<Input v-model="option.label" placeholder="Label" size="small" style="width: 95px"></Input>
<i class="iconfont icon-drag drag-option"></i>
<Button
shape="circle"
size="small"
type="warning"
@click="deleteOption(option, idx)"
icon="md-remove"
class="col-delete-button">
</Button>
</Checkbox>
</li>
</draggable>
</CheckboxGroup>
<Cascader
v-else-if="(selectedWidget.type === 'cascader')"
v-model="optionModel.defaultValue"
:data="optionModel.optionItems"
:placeholder="i18nt('designer.hint.selectPlaceholder')"
@on-change="emitDefaultValueChange"
style="width: 100%">
</Cascader>
<div v-if="(selectedWidget.type === 'cascader')">
<Button type="text" @click="importCascaderOptions">{{i18nt('designer.setting.importOptions')}}
</Button>
<Button type="text" @click="resetDefault">{{i18nt('designer.setting.resetDefault')}}</Button>
</div>
<div
v-if="(selectedWidget.type === 'radio') || (selectedWidget.type === 'checkbox') || (selectedWidget.type === 'select')">
<Button type="text" @click="addOption">{{i18nt('designer.setting.addOption')}}</Button>
<Button type="text" @click="importOptions">{{i18nt('designer.setting.importOptions')}}</Button>
<Button type="text" @click="resetDefault">{{i18nt('designer.setting.resetDefault')}}</Button>
</div>
<Modal :title="i18nt('designer.setting.importOptions')"
v-model="showImportDialogFlag" :closable="true" class="small-padding-dialog" draggable
:mask-closable="false" >
<FormItem :label-width="0">
<el-input type="textarea" rows="10" v-model="optionLines"></el-input>
</FormItem>
<div slot="footer" class="dialog-footer">
<Button size="default" type="primary" @click="saveOptions">{{i18nt('designer.hint.confirm')}}
</Button>
<Button size="default" type="default" @click="showImportDialogFlag = false">{{i18nt('designer.hint.cancel')}}
</Button>
</div>
</Modal>
<Modal :title="i18nt('designer.setting.importOptions')"
v-model="showImportCascaderDialogFlag" :closable="true" class="small-padding-dialog" draggable
:mask-closable="false" >
<code-editor v-model="cascaderOptions" mode="json" :readonly="false"></code-editor>
<div slot="footer" class="dialog-footer">
<Button size="default" type="primary" @click="saveCascaderOptions">{{i18nt('designer.hint.confirm')}}
</Button>
<Button size="default" type="default" @click="showImportCascaderDialogFlag = false">
{{i18nt('designer.hint.cancel')}}</Button>
</div>
</Modal>
</div>
</template>
<script>
import Draggable from 'vuedraggable'
import CodeEditor from '@/components-iview/code-editor/index'
import i18n from "../../utils/i18n";
export default {
name: "OptionItemsSetting",
mixins: [i18n],
components: {
Draggable,
//CodeEditor: () => import('@/components/code-editor/index'),
CodeEditor,
},
props: {
designer: Object,
selectedWidget: Object,
},
data() {
return {
showImportDialogFlag: false,
optionLines: '',
cascaderOptions: '',
showImportCascaderDialogFlag: false,
//separator: '||',
separator: ',',
}
},
computed: {
optionModel() {
return this.selectedWidget.options
},
},
watch: {
'selectedWidget.options': {
deep: true,
handler(val) {
//console.log('888888', 'Options change!')
}
},
},
methods: {
emitDefaultValueChange() {
if (!!this.designer) {
/* 组件过多时,事件处理效率不高!! */
//this.designer.emitEvent('defaultValueChanged', this.selectedWidget.id)
// if (!!this.designer.selectedFieldWrapper && this.designer.selectedFieldWrapper.refreshDefaultValue) {
// console.log('aaaa', '123456')
// this.designer.selectedFieldWrapper.refreshDefaultValue()
// }
if (!!this.designer.formWidget) {
let fieldWidget = this.designer.formWidget.getWidgetRef(this.selectedWidget.options.name)
if (!!fieldWidget && !!fieldWidget.refreshDefaultValue) {
fieldWidget.refreshDefaultValue()
}
}
}
},
deleteOption(option, index) {
this.optionModel.optionItems.splice(index, 1)
},
addOption() {
let newValue = this.optionModel.optionItems.length + 1
this.optionModel.optionItems.push({
value: newValue,
label: 'new option'
})
},
importOptions() {
this.optionLines = ''
if (this.optionModel.optionItems.length > 0) {
this.optionModel.optionItems.forEach((opt) => {
if (opt.value === opt.label) {
this.optionLines += opt.value + '\n'
} else {
this.optionLines += opt.value + this.separator + opt.label + '\n'
}
})
}
this.showImportDialogFlag = true
},
saveOptions() {
let lineArray = this.optionLines.split('\n')
//console.log('test', lineArray)
if (lineArray.length > 0) {
this.optionModel.optionItems = []
lineArray.forEach((optLine) => {
if (!!optLine && !!optLine.trim()) {
if (optLine.indexOf(this.separator) !== -1) {
this.optionModel.optionItems.push({
value: optLine.split(this.separator)[0],
label: optLine.split(this.separator)[1]
})
} else {
this.optionModel.optionItems.push({
value: optLine,
label: optLine
})
}
}
})
} else {
this.optionModel.optionItems = []
}
this.showImportDialogFlag = false
},
resetDefault() {
if ((this.selectedWidget.type === 'checkbox') ||
((this.selectedWidget.type === 'select') && this.selectedWidget.options.multiple)) {
this.optionModel.defaultValue = []
} else {
this.optionModel.defaultValue = ''
}
this.emitDefaultValueChange()
},
importCascaderOptions() {
this.cascaderOptions = JSON.stringify(this.optionModel.optionItems, null, ' ')
this.showImportCascaderDialogFlag = true
},
saveCascaderOptions() {
try {
let newOptions = JSON.parse(this.cascaderOptions)
this.optionModel.optionItems = newOptions
//TODO:
this.showImportCascaderDialogFlag = false
} catch (ex) {
this.$message.error(this.i18nt('designer.hint.invalidOptionsData') + ex.message)
}
},
}
}
</script>
<style lang="scss" scoped>
.option-items-pane ul {
padding-inline-start: 6px;
padding-left: 6px;
/* 重置IE11默认样式 */
}
.option-items-pane ::v-deep ul>li {
list-style: none;
}
li.ghost {
background: #fff;
border: 2px dotted $--color-primary;
}
.drag-option {
cursor: move;
}
.small-padding-dialog ::v-deep .el-dialog__body {
padding: 10px 15px;
}
.dialog-footer .el-button {
width: 100px;
}
</style>

View File

@ -0,0 +1,566 @@
<template>
<div class="toolbar-container">
<div class="left-toolbar">
<Button type="text" :disabled="undoDisabled" :title="i18nt('designer.toolbar.undoHint')"
@click="undoHistory" style="background-color: transparent;">
<svg-icon icon-class="undo" />
</Button>
<Button type="text" :disabled="redoDisabled" :title="i18nt('designer.toolbar.redoHint')"
@click="redoHistory" style="background-color: transparent;">
<svg-icon icon-class="redo" />
</Button>
<ButtonGroup style="margin-left: 20px">
<Button :type="layoutType === 'PC' ? 'primary': 'default'" @click="changeLayoutType('PC')">
{{i18nt('designer.toolbar.pcLayout')}}
</Button>
<Button :type="layoutType === 'H5' ? 'primary': 'default'" @click="changeLayoutType('H5')">
{{i18nt('designer.toolbar.mobileLayout')}}
</Button>
</ButtonGroup>
</div>
<div class="right-toolbar">
<Button type="text" @click="clearFormWidget"><i class="ivu-icon ivu-icon-ios-trash" />{{i18nt('designer.toolbar.clear')}}</Button>
<Button type="text" @click="previewForm"><i class="ivu-icon ivu-icon-ios-eye-outline" />{{i18nt('designer.toolbar.preview')}}</Button>
<Button type="text" @click="importJson">{{i18nt('designer.toolbar.importJson')}}</Button>
<Button type="text" @click="exportJson">{{i18nt('designer.toolbar.exportJson')}}</Button>
<Button type="text" @click="exportCode">{{i18nt('designer.toolbar.exportCode')}}</Button>
<Button type="text" v-if="false">{{i18nt('designer.toolbar.generateCode')}}</Button>
<Button type="text" @click="generateSFC"><svg-icon icon-class="vue-sfc" />{{i18nt('designer.toolbar.generateSFC')}}</Button>
</div>
<Modal :title="i18nt('designer.toolbar.preview')"
v-model="showPreviewDialogFlag" :closable="true" class="small-padding-dialog" draggable
:mask-closable="false" width="75%"
:fullscreen ="layoutType === 'H5'">
<div v-if="showPreviewDialogFlag">
<div class="form-render-wrapper" :class="[layoutType === 'H5' ? 'h5-layout' : '']">
<VFormRender ref="preForm" :form-json="formJson" :form-data="testFormData"
@appendButtonClick="testOnAppendButtonClick" @buttonClick="testOnButtonClick"
@formChange="handleFormChange" @change="handlerNativeChange"
></VFormRender>
</div>
</div>
<code-editor v-model="testFunc" style="display: none"></code-editor>
<div slot="footer" class="dialog-footer">
<Button type="primary" size="default" @click="getFormData">{{i18nt('designer.hint.getFormData')}}</Button>
<Button type="primary" size="default" @click="resetForm">{{i18nt('designer.hint.resetForm')}}</Button>
<Button type="primary" size="default" @click="setFormDisabled">{{i18nt('designer.hint.disableForm')}}</Button>
<Button type="primary" size="default" @click="setFormEnabled">{{i18nt('designer.hint.enableForm')}}</Button>
<Button type="default" size="default" @click="showPreviewDialogFlag = false">{{i18nt('designer.hint.closePreview')}}</Button>
</div>
</Modal>
<Modal :title="i18nt('designer.toolbar.importJson')"
v-model="showImportJsonDialogFlag" :closable="true" class="small-padding-dialog" draggable
:mask-closable="false">
<Alert show-icon>{{i18nt('designer.hint.importJsonHint')}}</Alert>
<code-editor v-if="showImportJsonDialogFlag" :mode="'json'" :readonly="false" v-model="importTemplate"></code-editor>
<div slot="footer" class="dialog-footer">
<Button size="default" @click="showImportJsonDialogFlag = false">
{{i18nt('designer.hint.cancel')}}
</Button>
<Button size="default" type="primary" @click="doJsonImport">
{{i18nt('designer.hint.import')}}
</Button>
</div>
</Modal>
<Modal :title="i18nt('designer.toolbar.exportJson')"
v-model="showExportJsonDialogFlag" :closable="true" class="small-padding-dialog" draggable
:mask-closable="false">
<code-editor v-if="showExportJsonDialogFlag" :mode="'json'" :readonly="true" v-model="jsonContent"></code-editor>
<div slot="footer" class="dialog-footer">
<Button size="default" type="primary" class="copy-json-btn" :data-clipboard-text="jsonRawContent">
{{i18nt('designer.hint.copyJson')}}
</Button>
<Button size="default" type="default" @click="showExportJsonDialogFlag = false">
{{i18nt('designer.hint.closePreview')}}
</Button>
</div>
</Modal>
<Modal :title="i18nt('designer.toolbar.exportCode')"
v-model="showExportCodeDialogFlag" :closable="true" class="small-padding-dialog" draggable
:mask-closable="false"
width="65%">
<Tabs type="line" class="no-box-shadow" v-model="activeCodeTab">
<TabPane label="Vue" name="vue">
<code-editor v-if="showExportCodeDialogFlag" :mode="'html'" :readonly="true" v-model="vueCode" :user-worker="false"></code-editor>
</TabPane>
<TabPane label="HTML" name="html">
<code-editor v-if="showExportCodeDialogFlag" :mode="'html'" :readonly="true" v-model="htmlCode" :user-worker="false"></code-editor>
</TabPane>
</Tabs>
<div slot="footer" class="dialog-footer">
<Button size="default" type="primary" class="copy-vue-btn" :data-clipboard-text="vueCode">
{{i18nt('designer.hint.copyVueCode')}}
</Button>
<Button size="default" type="primary" class="copy-html-btn" :data-clipboard-text="htmlCode">
{{i18nt('designer.hint.copyHtmlCode')}}
</Button>
<Button size="default" type="primary" @click="saveVueCode">{{i18nt('designer.hint.saveVueCode')}}</Button>
<Button size="default" type="primary" @click="saveHtmlCode">{{i18nt('designer.hint.saveHtmlCode')}}</Button>
<Button size="default" type="default" @click="showExportCodeDialogFlag = false">
{{i18nt('designer.hint.closePreview')}}
</Button>
</div>
</Modal>
<Modal :title="i18nt('designer.hint.exportFormData')"
v-model="showFormDataDialogFlag" :closable="true" class="dialog-title-light-bg" draggable
:mask-closable="false">
<div style="border: 1px solid #DCDFE6">
<code-editor v-if="showFormDataDialogFlag" :mode="'json'" :readonly="true" v-model="formDataJson"></code-editor>
</div>
<div slot="footer" class="dialog-footer">
<Button size="default" type="primary" class="copy-form-data-json-btn" :data-clipboard-text="formDataRawJson">
{{i18nt('designer.hint.copyFormData')}}
</Button>
<Button size="default" type="default" @click="showFormDataDialogFlag = false">
{{i18nt('designer.hint.closePreview')}}
</Button>
</div>
</Modal>
<Modal :title="i18nt('designer.toolbar.generateSFC')" width="85%"
v-model="showExportSFCDialogFlag" :closable="true" class="dialog-title-light-bg" draggable
:mask-closable="false">
<Tabs type="line" class="no-box-shadow no-padding" v-model="activeSFCTab">
<TabPane label="Vue2" name="vue2">
<code-editor v-if="showExportSFCDialogFlag" :mode="'html'" :readonly="true" v-model="sfcCode" :user-worker="false"></code-editor>
</TabPane>
<TabPane label="Vue3" name="vue3">
<code-editor v-if="showExportSFCDialogFlag" :mode="'html'" :readonly="true" v-model="sfcCodeV3" :user-worker="false"></code-editor>
</TabPane>
</Tabs>
<div slot="footer" class="dialog-footer">
<Button size="default" type="primary" class="copy-vue2-sfc-btn" :data-clipboard-text="sfcCode" @click="copyV2SFC">
{{i18nt('designer.hint.copyVue2SFC')}}</Button>
<Button size="default" type="primary" class="copy-vue3-sfc-btn" :data-clipboard-text="sfcCodeV3" @click="copyV3SFC">
{{i18nt('designer.hint.copyVue3SFC')}}</Button>
<Button size="default" @click="saveV2SFC">{{i18nt('designer.hint.saveVue2SFC')}}</Button>
<Button size="default" @click="saveV3SFC">{{i18nt('designer.hint.saveVue3SFC')}}</Button>
<Button size="default" type="default" @click="showExportSFCDialogFlag = false">
{{i18nt('designer.hint.closePreview')}}</Button>
</div>
</Modal>
<Modal :title="this.i18nt('designer.hint.saveFileTitle')" width="300px"
v-model="showExportSFCFileNameDialogFlag" :closable="true" class="dialog-title-light-bg" draggable
:mask-closable="false">
<Input type="text" size="large" v-model="saveFileName" :placeholder="i18nt('designer.hint.fileNameForSave')" clearable/>
<div slot="footer" class="dialog-footer">
<Button size="default" type="primary" @click="saveFileExec">{{i18nt('designer.hint.confirm')}}</Button>
<Button size="default" type="default" @click="showExportSFCFileNameDialogFlag = false">{{i18nt('designer.hint.closePreview')}}</Button>
</div>
</Modal>
</div>
</template>
<script>
import VFormRender from '@/components-iview/form-render/index'
import CodeEditor from '@/components-iview/code-editor/index'
import Clipboard from 'clipboard'
import {deepClone, copyToClipboard, generateId, getQueryParam} 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'
export default {
name: "ToolbarPanel",
mixins: [i18n],
components: {
VFormRender,
//CodeEditor: () => import('@/components/code-editor/index'),
CodeEditor,
Clipboard,
},
props: {
designer: Object
},
mounted() {
if(this.load) this.reloadCode();
},
data() {
return {
showPreviewDialogFlag: false,
showImportJsonDialogFlag: false,
showExportJsonDialogFlag: false,
showExportCodeDialogFlag: false,
showFormDataDialogFlag: false,
showExportSFCDialogFlag: false,
showExportSFCFileNameDialogFlag: false,
saveFileHandler:null,
saveFileName:'',
testFunc: '',
importTemplate: '',
jsonContent: '',
jsonRawContent: '',
formDataJson: '',
formDataRawJson: '',
vueCode: '',
htmlCode: '',
sfcCode: '',
sfcCodeV3: '',
activeCodeTab: 'vue',
activeSFCTab: 'vue2',
testFormData: {
// 'userName': '666888',
// 'productItems': [
// {'pName': 'iPhone12', 'pNum': 10},
// {'pName': 'P50', 'pNum': 16},
// ]
}
}
},
computed: {
formJson() {
return {
widgetList: this.designer.widgetList,
formConfig: this.designer.formConfig
}
},
undoDisabled() {
return !this.designer.undoEnabled()
},
redoDisabled() {
return !this.designer.redoEnabled()
},
layoutType() {
return this.designer.getLayoutType()
},
},
methods: {
undoHistory() {
this.designer.undoHistoryStep()
},
redoHistory() {
this.designer.redoHistoryStep()
},
changeLayoutType(newType) {
this.designer.changeLayoutType(newType)
},
clearFormWidget() {
this.designer.clearDesigner()
},
previewForm() {
this.showPreviewDialogFlag = true
},
saveFileExec(){
this.saveAsFile(this.saveFileHandler[0],this.saveFileHandler[1])
},
saveAsFile(fileContent, defaultFileName) {
let value="";
if (!this.saveFileName) {
value = defaultFileName
}else{
value=this.saveFileName ;
}
if (getQueryParam('vscode') == 1) {
this.vsSaveFile(value, fileContent)
return
}
const fileBlob = new Blob([fileContent], { type: 'text/plain;charset=utf-8' })
saveAs(fileBlob ,value)
},
vsSaveFile(fileName, fileContent) {
const msgObj = {
cmd: 'writeFile',
data: {
fileName,
code: fileContent
}
}
window.parent.postMessage(msgObj, '*')
},
importJson() {
this.importTemplate = JSON.stringify(this.designer.getImportTemplate(), null, ' ')
this.showImportJsonDialogFlag = true
},
doJsonImport() {
try {
let importObj = JSON.parse(this.importTemplate)
this.designer.loadFormJson(importObj)
this.showImportJsonDialogFlag = false
this.$message.success(this.i18nt('designer.hint.importJsonSuccess'))
this.designer.emitHistoryChange()
} catch (ex) {
this.$message.error(ex + '')
}
},
exportJson() {
//alert( JSON.stringify(this.designer.widgetList) )
//this.jsonContent = JSON.stringify(this.designer.widgetList, null, ' ')
let widgetList = deepClone(this.designer.widgetList)
let formConfig = deepClone(this.designer.formConfig)
this.jsonContent = JSON.stringify({
widgetList,
formConfig
}, null, ' ')
this.jsonRawContent = JSON.stringify({
widgetList,
formConfig
})
this.showExportJsonDialogFlag = true;
this.$forceUpdate();
this.$nextTick(() => {
let copyClipboard = new Clipboard('.copy-json-btn')
copyClipboard.on('success', e => {
this.$message.success(this.i18nt('designer.hint.copyJsonSuccess'))
copyClipboard.destroy() //
})
copyClipboard.on('error', e => { //
this.$message.error(this.i18nt('designer.hint.copyJsonFail'))
copyClipboard.destroy()
})
})
},
exportCode() {
this.vueCode = generateCode(this.formJson)
this.htmlCode = generateCode(this.formJson, 'html')
this.showExportCodeDialogFlag = true
this.$nextTick(() => {
let vueClipboard = new Clipboard('.copy-vue-btn')
vueClipboard.on('success', e => {
this.$message.success(this.i18nt('designer.hint.copyVueCodeSuccess'))
vueClipboard.destroy() //
})
vueClipboard.on('error', e => { //
this.$message.error(this.i18nt('designer.hint.copyVueCodeFail'))
vueClipboard.destroy()
})
let htmlClipboard = new Clipboard('.copy-html-btn')
htmlClipboard.on('success', e => {
this.$message.success(this.i18nt('designer.hint.copyHtmlCodeSuccess'))
htmlClipboard.destroy() //
})
htmlClipboard.on('error', e => { //
this.$message.error(this.i18nt('designer.hint.copyHtmlCodeFail'))
htmlClipboard.destroy()
})
})
},
copyVueCode(e) {
copyToClipboard(this.vueCode, e,
this.$message,
this.i18nt('designer.hint.copyVueCodeSuccess'),
this.i18nt('designer.hint.copyVueCodeFail')
)
},
copyHtmlCode(e) {
copyToClipboard(this.htmlCode, e,
this.$message,
this.i18nt('designer.hint.copyHtmlCodeSuccess'),
this.i18nt('designer.hint.copyHtmlCodeFail')
)
},
saveVueCode() {
this.saveFileHandler=[this.vueCode, `vform${generateId()}.vue`];
this.showExportSFCFileNameDialogFlag=true;
},
saveHtmlCode() {
this.saveFileHandler=[this.htmlCode, `vform${generateId()}.vue`];
this.showExportSFCFileNameDialogFlag=true;
},
generateSFC() {
loadBeautifier(beautifier => {
this.sfcCode = genSFC(this.designer.formConfig, this.designer.widgetList, beautifier)
this.sfcCodeV3 = genSFC(this.designer.formConfig, this.designer.widgetList, beautifier, true)
this.showExportSFCDialogFlag = true
})
},
copyV2SFC(e) {
copyToClipboard(this.sfcCode, e,
this.$message,
this.i18nt('designer.hint.copySFCSuccess'),
this.i18nt('designer.hint.copySFCFail')
)
},
copyV3SFC(e) {
copyToClipboard(this.sfcCodeV3, e,
this.$message,
this.i18nt('designer.hint.copySFCSuccess'),
this.i18nt('designer.hint.copySFCFail')
)
},
saveV2SFC() {
this.saveFileHandler=[this.sfcCode, `vformV2-${generateId()}.vue`];
this.showExportSFCFileNameDialogFlag=true;
// this.saveAsFile(this.sfcCode, `vformV2-${generateId()}.vue`)
},
saveV3SFC() {
this.saveFileHandler=[this.sfcCodeV3, `vformV3-${generateId()}.vue`];
this.showExportSFCFileNameDialogFlag=true;
// this.saveAsFile(this.sfcCodeV3, `vformV3-${generateId()}.vue`)
},
getFormData() {
this.$refs['preForm'].getFormData().then(formData => {
//alert( JSON.stringify(formData) )
this.formDataJson = JSON.stringify(formData, null, ' ')
this.formDataRawJson = JSON.stringify(formData)
this.showFormDataDialogFlag = true
this.$nextTick(() => {
let copyClipboard = new Clipboard('.copy-form-data-json-btn')
copyClipboard.on('success', e => {
this.$message.success(this.i18nt('designer.hint.copyJsonSuccess'))
copyClipboard.destroy() //
})
copyClipboard.on('error', e => { //
this.$message.error(this.i18nt('designer.hint.copyJsonFail'))
copyClipboard.destroy()
})
})
}).catch(error => {
this.$message.error(error)
})
},
resetForm() {
this.$refs['preForm'].resetForm()
},
setFormDisabled() {
this.$refs['preForm'].disableForm()
},
setFormEnabled() {
this.$refs['preForm'].enableForm()
},
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) {
console.log('test', clickedWidget)
},
testOnButtonClick(button) {
console.log('test', button)
},
handlerNativeChange(args){
}
}
}
</script>
<style lang="scss" scoped>
div.toolbar-container {
//border-bottom: 1px solid #EBEEF5;
}
.left-toolbar {
float: left;
font-size: 16px;
}
.right-toolbar {
float: right;
::v-deep .el-button--text {
font-size: 14px !important;
}
.ivu-icon{
font-size:18px;
}
}
.el-button i {
margin-right: 3px;
}
.small-padding-dialog {
::v-deep .el-dialog__header {
padding-top: 3px;
padding-bottom: 3px;
background: #f1f2f3;
}
::v-deep .el-dialog__body {
padding: 12px 15px 12px 15px;
.el-alert {
padding: 0 10px;
}
}
::v-deep .ace-container {
border: 1px solid #DCDFE6;
}
}
.dialog-title-light-bg {
::v-deep .el-dialog__header {
background: #f1f2f3;
}
}
.no-box-shadow {
box-shadow: none;
}
.form-render-wrapper {
//height: calc(100vh - 142px);
}
.form-render-wrapper.h5-layout {
margin: 0 auto;
width: 420px;
border-radius: 15px;
//border-width: 10px;
background-color:blue;
box-shadow: 0 0 1px 10px #495060;
height: calc(100vh - 142px);
}
</style>

View File

@ -0,0 +1,275 @@
<template>
<Scroll class="side-scroll-bar" :height="scrollerHeight">
<div class="panel-container">
<Collapse v-model="activeNames" class="widget-collapse">
<Panel name="1" :title="i18nt('designer.containerTitle')">
{{i18nt('designer.containerTitle')}}
<div slot="content">
<draggable tag="ul" :list="containers" :group="{name: 'dragGroup', pull: 'clone', put: false}"
:clone="handleContainerWidgetClone" ghost-class="ghost" :sort="false" :move="checkContainerMove"
@end="onContainerDragEnd">
<li v-for="(ctn, index) in containers" :key="index" class="container-widget-item"
:title="ctn.displayName" @dblclick="addContainerByDbClick(ctn)">
<span>
<svg-icon :icon-class="ctn.icon" />{{i18nt(`designer.widgetLabel.${ctn.type}`)}}
</span>
</li>
</draggable>
</div>
</Panel>
<Panel name="2" :title="i18nt('designer.basicFieldTitle')">
{{i18nt('designer.basicFieldTitle')}}
<div slot="content">
<draggable tag="ul" :list="basicFields" :group="{name: 'dragGroup', pull: 'clone', put: false}"
:clone="handleFieldWidgetClone" ghost-class="ghost" :sort="false">
<li v-for="(fld, index) in basicFields" :key="index" class="field-widget-item"
:title="fld.displayName" @dblclick="addFieldByDbClick(fld)">
<span>
<svg-icon :icon-class="fld.icon" />{{i18nt(`designer.widgetLabel.${fld.type}`)}}
</span>
</li>
</draggable>
</div>
</Panel>
<Panel name="3" :title="i18nt('designer.advancedFieldTitle')">
{{i18nt('designer.advancedFieldTitle')}}
<div slot="content">
<draggable tag="ul" :list="advancedFields" :group="{name: 'dragGroup', pull: 'clone', put: false}"
:clone="handleFieldWidgetClone" ghost-class="ghost" :sort="false">
<li v-for="(fld, index) in advancedFields" :key="index" class="field-widget-item"
:title="fld.displayName" @dblclick="addFieldByDbClick(fld)">
<span>
<svg-icon :icon-class="fld.icon" />{{i18nt(`designer.widgetLabel.${fld.type}`)}}
</span>
</li>
</draggable>
</div>
</Panel>
<!--
<el-collapse-item name="4" :title="i18nt('designer.customFieldTitle')">
<draggable tag="ul" :list="customFields" :group="{name: 'dragGroup', pull: 'clone', put: false}"
:clone="handleFieldWidgetClone" ghost-class="ghost" :sort="false">
<li v-for="(fld, index) in customFields" :key="index" class="field-widget-item" :title="fld.displayName"
@dblclick="addFieldByDbClick(fld)">
<span :title="fld.displayName"><svg-icon :icon-class="fld.icon" />{{i18nt(`designer.widgetLabel.${fld.type}`)}}</span>
</li>
</draggable>
</el-collapse-item>
-->
</Collapse>
</div>
</Scroll>
</template>
<script>
import Draggable from 'vuedraggable'
import {
containers,
basicFields,
advancedFields,
customFields
} from "./widgetsConfig";
import {
generateId,
deepClone,
addWindowResizeHandler
} from "@/utils/util";
import i18n from "../../utils/i18n.js";
export default {
name: "FieldPanel",
mixins: [i18n],
components: {
Draggable,
},
props: {
designer: Object,
},
data() {
return {
scrollerHeight: 0,
activeNames: ['1', '2', '3', '4'],
containers,
basicFields,
advancedFields,
customFields,
allContainers: [],
}
},
computed: {
//
},
mounted() {
this.loadWidgets()
this.scrollerHeight = window.innerHeight - 56
addWindowResizeHandler(() => {
this.$nextTick(() => {
this.scrollerHeight = window.innerHeight - 56
//console.log(this.scrollerHeight)
})
})
},
methods: {
loadWidgets() {
//this.allContainers = deepClone(this.containers)
this.containers = this.containers.map(con => {
return {
...con,
//category: 'container',
displayName: this.i18nt(`designer.widgetLabel.${con.type}`)
}
}).filter(con => {
return !con.internal
})
this.basicFields = this.basicFields.map(fld => {
return {
...fld,
displayName: this.i18nt(`designer.widgetLabel.${fld.type}`)
}
})
this.advancedFields = this.advancedFields.map(fld => {
return {
...fld,
displayName: this.i18nt(`designer.widgetLabel.${fld.type}`)
}
})
///*
this.customFields = this.customFields.map(fld => {
return {
...fld,
displayName: this.i18nt(`designer.widgetLabel.${fld.type}`)
}
})
//*/
},
handleContainerWidgetClone(origin) {
return this.designer.copyNewContainerWidget(origin)
},
handleFieldWidgetClone(origin) {
return this.designer.copyNewFieldWidget(origin)
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
onContainerDragEnd(evt) {
//console.log('Drag end of container: ')
//console.log(evt)
},
addContainerByDbClick(container) {
this.designer.addContainerByDbClick(container)
},
addFieldByDbClick(widget) {
//console.log('addWidgetByDbClick')
this.designer.addFieldByDbClick(widget)
},
}
}
</script>
<style lang="scss" scoped>
.side-scroll-bar {
//height: calc(100% - 56px);
//height: 100%;
::v-deep .el-scrollbar__wrap {
overflow-x: hidden;
}
/deep/ .ivu-scroll-loader{
display:none;
}
}
div.panel-container {
//height: calc(100% - 48px);
//height: 100%;
//overflow-y: hidden;
padding-bottom: 10px;
}
.ivu-collapse-item ::v-deep ul>li {
list-style: none;
}
.widget-collapse {
::v-deep .el-collapse-item__header {
padding-left: 8px;
// font-style: italic;
font-weight: bold;
}
::v-deep .ivu-collapse-content {
padding:0px;
padding-bottom: 6px;
ul {
padding-left: 10px;
/* 重置IE11默认样式 */
margin: 0;
/* 重置IE11默认样式 */
margin-block-start: 0;
margin-block-end: 0.25em;
padding-inline-start: 10px;
&:after {
content: "";
display: block;
clear: both;
}
.container-widget-item,
.field-widget-item {
display: inline-block;
height: 28px;
line-height: 28px;
width: 112px;
float: left;
margin: 2px 6px 6px 0;
cursor: move;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
background: #f1f2f3;
}
.container-widget-item:hover,
.field-widget-item:hover {
background: #EBEEF5;
outline: 1px solid $--color-primary;
}
.drag-handler {
position: absolute;
top: 0;
left: 160px;
background-color: #dddddd;
border-radius: 5px;
padding-right: 5px;
font-size: 11px;
color: #666666;
}
}
}
}
</style>

View File

@ -0,0 +1,953 @@
export const containers = [
{
type: 'grid',
category: 'container',
icon: 'grid',
cols: [],
options: {
name: '',
hidden: false,
gutter: 12,
}
},
{
type: 'table',
category: 'container',
icon: 'table',
rows: [],
options: {
name: '',
hidden: false,
}
},
{
type: 'tab',
category: 'container',
icon: 'tab',
tabs: [],
options: {
name: '',
hidden: false,
displayType: 'card',
size: 'default',
}
},
/*
{
type: 'section',
category: 'container',
icon: 'section',
widgetList: [],
options: {
name: '',
hidden: false,
}
},
*/
{
type: 'grid-col',
category: 'container',
icon: 'grid-col',
internal: true,
widgetList: [],
options: {
name: '',
hidden: false,
span: 12,
}
},
{
type: 'table-cell',
category: 'container',
icon: 'table-cell',
internal: true,
widgetList: [],
merged: false,
options: {
name: '',
cellWidth: '',
cellHeight: '',
colspan: 1,
rowspan: 1,
}
},
{
type: 'tab-pane',
category: 'container',
icon: 'tab-pane',
internal: true,
widgetList: [],
options: {
name: '',
label: '',
hidden: false,
active: false,
disabled: false,
}
},
]
export const basicFields = [
{
type: 'input',
icon: 'text-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
type: 'text',
defaultValue: '',
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
showPassword: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
// minLength: null,
maxLength: null,
showWordLimit: false,
prefixIcon: '',
suffixIcon: '',
//-------------------
onCreated: '',
onMounted: '',
onInput: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'input-composite',
icon: 'text-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
type: 'text',
defaultValue: '',
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
showPassword: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
// minLength: null,
maxLength: null,
showWordLimit: false,
prependControl: true,
prependControlDisabled: false,
prependControlText:"", //后置按钮文字
prependControlIcon: 'ios-person', //后置按钮文字
prependControlType: 'button',
appendControl: true,
appendControlDisabled: false,
appendControlText:"", //后置按钮文字
appendControlIcon: 'ios-search', //后置按钮文字
appendControlType: 'button',
//-------------------
onCreated: '',
onMounted: '',
onInput: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'textarea',
icon: 'textarea-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
rows: 3,
defaultValue: '',
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
minLength: null,
maxLength: null,
showWordLimit: false,
//-------------------
onCreated: '',
onMounted: '',
onInput: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'number',
icon: 'number-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: 0,
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
min: -100000000000,
max: 100000000000,
precision: 0,
formatter:'',
step: 1,
controlsPosition: 'right',
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'radio',
icon: 'radio-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
columnWidth: '200px',
size: '',
displayStyle: 'inline',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
optionItems: [
{label: 'radio 1', value: 1},
{label: 'radio 2', value: 2},
{label: 'radio 3', value: 3},
],
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'checkbox',
icon: 'checkbox-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: [],
columnWidth: '200px',
size: '',
displayStyle: 'inline',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
optionItems: [
{label: 'check 1', value: 1},
{label: 'check 2', value: 2},
{label: 'check 3', value: 3},
],
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'select',
icon: 'select-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: '',
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
clearable: true,
filterable: false,
allowCreate: false,
remote: false,
automaticDropdown: false, //自动下拉
multiple: false,
multipleLimit: 0,
optionItems: [
{label: 'select 1', value: 1},
{label: 'select 2', value: 2},
{label: 'select 3', value: 3},
],
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onRemoteQuery: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'time',
icon: 'time-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
editable: false,
format: 'HH:mm:ss', //时间格式
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'time-range',
icon: 'time-range-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
startPlaceholder: '',
endPlaceholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
editable: false,
format: 'HH:mm:ss', //时间格式
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'date',
icon: 'date-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
type: 'date',
defaultValue: null,
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
editable: false,
format: 'yyyy-MM-dd', //日期显示格式
valueFormat: 'yyyy-MM-dd', //日期对象格式
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'date-range',
icon: 'date-range-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
type: 'daterange',
defaultValue: null,
startPlaceholder: '',
endPlaceholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
editable: false,
format: 'yyyy-MM-dd', //日期显示格式
valueFormat: 'yyyy-MM-dd', //日期对象格式
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'switch',
icon: 'switch-field',
formItemFlag: true,
options: {
name: '',
label: '',
size: '',
labelAlign: '',
defaultValue: null,
columnWidth: '200px',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
switchWidth: 40,
activeText: '',
inactiveText: '',
activeColor: null,
inactiveColor: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'rate',
icon: 'rate-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
columnWidth: '200px',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
max: 5,
clearable: true,
// lowThreshold: 2,
// highThreshold: 4,
allowHalf: false,
showText: false,
// showScore: false,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'color',
icon: 'color-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'slider',
icon: 'slider-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
columnWidth: '200px',
// size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
min: 0,
max: 100,
step: 1,
range: false,
//vertical: false,
height: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'static-text',
icon: 'static-text',
formItemFlag: false,
options: {
name: '',
columnWidth: '200px',
hidden: false,
textContent: 'static text',
//-------------------
customClass: '', //自定义css类名
//-------------------
onCreated: '',
onMounted: '',
},
},
{
type: 'html-text',
icon: 'html-text',
formItemFlag: false,
options: {
name: '',
columnWidth: '200px',
hidden: false,
htmlContent: '<b>html text</b>',
//-------------------
customClass: '', //自定义css类名
//-------------------
onCreated: '',
onMounted: '',
},
},
{
type: 'button',
icon: 'button',
formItemFlag: false,
options: {
name: '',
label: '',
columnWidth: '200px',
size: '',
disabled: false,
hidden: false,
type: '',
plain: false,
// round: false,
circle: false,
icon: null,
to:"",
replace:false,
target:"_self",
append:false,
//-------------------
customClass: '', //自定义css类名
//-------------------
onCreated: '',
onMounted: '',
onClick: '',
},
},
{
type: 'divider',
icon: 'divider',
formItemFlag: false,
options: {
name: '',
label: '',
size:'',
columnWidth: '200px',
direction: 'horizontal',
contentPosition: 'center',
hidden: false,
//-------------------
customClass: '', //自定义css类名
//-------------------
onCreated: '',
onMounted: '',
},
},
//
]
export const advancedFields = [
{
type: 'picture-upload',
icon: 'picture-upload-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
uploadURL: '',
uploadTip: '',
withCredentials: false,
multipleSelect: false,
showFileList: true,
// limit: 3,
fileMaxSize: 5, //MB
fileTypes: ['jpeg', 'png'],
fileAccept: "",
//headers: [],
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onBeforeUpload: '',
onUploadSuccess: '',
onUploadError: '',
onValidate: '',
onUploadPreview:'',
onUploadProgress:'',
//onFileChange: '',
},
},
{
type: 'file-upload',
icon: 'file-upload-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
uploadURL: '',
uploadTip: '',
withCredentials: false,
multipleSelect: false,
showFileList: true,
// limit: 3,
fileMaxSize: 20, //MB
fileTypes: ['doc', 'docx', 'xls', 'xlsx'],
fileAccept: "",
//headers: [],
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onBeforeUpload: '',
onUploadSuccess: '',
onUploadError: '',
onValidate: '',
onUploadPreview:'',
onUploadProgress:'',
//onFileChange: '',
},
},
{
type: 'rich-editor',
icon: 'rich-editor-field',
formItemFlag: true,
options: {
name: '',
label: '',
placeholder: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
minLength: null,
maxLength: null,
showWordLimit: false,
//-------------------
onCreated: '',
onMounted: '',
onValidate: '',
},
},
{
type: 'cascader',
icon: 'cascader-field',
formItemFlag: true,
options: {
name: '',
label: '',
defaultValue: '',
placeholder: '',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
clearable: true,
filterable: false,
optionItems: [
{label: 'select 1', value: 1, children: [{label: 'child 1', value: 11}]},
{label: 'select 2', value: 2},
{label: 'select 3', value: 3},
],
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
]
export const customFields = [
{
type: 'custom',
icon: 'custom-component',
},
{
type: 'slot',
icon: 'slot-component',
},
]

View File

@ -0,0 +1,605 @@
<template>
<div class="container-wrapper">
<Row v-if="widget.type === 'grid'" :key="widget.id" :gutter="widget.options.gutter" class="grid-container"
:class="[customClass]" :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>
</template>
</Row>
<div v-else-if="widget.type === 'table'" :key="widget.id" class="table-container"
v-show="!widget.options.hidden">
<table :ref="widget.id" class="table-layout" :class="[customClass]">
<tbody>
<tr v-for="(row, rowIdx) in widget.rows" :key="row.id">
<template v-for="(colWidget, colIdx) in row.cols">
<table-cell-item v-if="!colWidget.merged" :widget="colWidget" :key="colIdx"
:parent-list="widget.cols" :row-index="rowIdx" :col-index="colIdx"
:parent-widget="widget"></table-cell-item>
</template>
</tr>
</tbody>
</table>
</div>
<div v-else-if="widget.type === 'tab'" :key="widget.id" class="tab-container" v-show="!widget.options.hidden">
<Tabs v-model="activeTabName"
:type="widget.displayType"
:size="widget.options.size"
:ref="widget.id"
:class="[customClass]">
<TabPane v-for="(tab, index) in visibleTabs" :key="index" :label="tab.options.label"
:disabled="tab.options.disabled" :name="tab.options.name">
<template v-for="(subWidget, swIdx) in tab.widgetList">
<template v-if="'container' === subWidget.category">
<container-item :widget="subWidget" :key="swIdx" :parent-list="tab.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-item>
</template>
<template v-else>
<field-widget :field="subWidget" :key="swIdx" :parent-list="tab.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></field-widget>
</template>
</template>
</TabPane>
</Tabs>
</div>
<div v-else-if="widget.type === 'sub-form'" :key="widget.id" class="sub-form-container"
v-show="!widget.options.hidden">
<!-- <el-form :ref="widget.id" :model="formModel" label-position="top">-->
<Row class="header-row">
<div class="action-header-column">
<span class="action-label">{{i18nt('render.hint.subFormAction')}}</span>
<Button shape="round" type="primary" size="mini" class="action-button" @click="addSubFormRow"
:title="i18nt('render.hint.subFormAddActionHint')">
{{i18nt('render.hint.subFormAddAction')}}<i class="ivu-icon ivu-icon-md-add el-icon-right"></i>
</Button>
</div>
<template v-for="(subWidget, swIdx) in widget.widgetList">
<div :key="subWidget.id + 'thc'" class="field-header-column"
:class="[getLabelAlign(widget, subWidget), !!subWidget.options.required ? 'is-required' : '']"
:style="{width: subWidget.options.columnWidth}">
<span v-if="!!subWidget.options.labelIconClass" class="custom-label">
<template v-if="subWidget.options.labelIconPosition === 'front'">
<template v-if="!!subWidget.options.labelTooltip">
<el-tooltip :content="subWidget.options.labelTooltip" effect="light">
<i :class="subWidget.options.labelIconClass"></i>
</el-tooltip>{{subWidget.options.label}}
</template>
<template v-else>
<i
:class="subWidget.options.labelIconClass"></i>{{subWidget.options.label}}</template>
</template>
<template v-else-if="subWidget.options.labelIconPosition === 'rear'">
<template v-if="!!subWidget.options.labelTooltip">
{{subWidget.options.label}}
<el-tooltip :content="subWidget.options.labelTooltip" effect="light">
<i :class="subWidget.options.labelIconClass"></i>
</el-tooltip>
</template>
<template v-else>
{{subWidget.options.label}}<i
:class="subWidget.options.labelIconClass"></i></template>
</template>
</span>
<template v-else>
<span :title="subWidget.options.labelTooltip">{{subWidget.options.label}}</span></template>
</div>
</template>
</Row>
<Row v-for="(subFormRowId, sfrIdx) in rowIdData" class="sub-form-row" :key="subFormRowId">
<div class="sub-form-action-column hide-label">
<FormItem class="action-button-column">
<Button shape="round" type="" icon="ivu-icon ivu-icon-md-add" @click="insertSubFormRow(sfrIdx)"
:title="i18nt('render.hint.insertSubFormRow')"></Button>
<Button shape="round" type="" icon="ivu-icon ivu-icon-ios-trash" @click="deleteSubFormRow(sfrIdx)"
:title="i18nt('render.hint.deleteSubFormRow')"></Button>
<span v-if="widget.options.showRowNumber" class="row-number-span">#{{sfrIdx+1}}</span>
</FormItem>
</div>
<template v-for="(subWidget, swIdx) in widget.widgetList">
<div class="sub-form-table-column hide-label" :key="subWidget.id + 'tc' + subFormRowId"
:style="{width: subWidget.options.columnWidth}">
<field-widget :field="fieldSchemaData[sfrIdx][swIdx]" :key="fieldSchemaData[sfrIdx][swIdx].id"
:parent-list="widget.widgetList" :index-of-parent-list="swIdx" :parent-widget="widget"
:sub-form-row-id="subFormRowId" :sub-form-row-index="sfrIdx" :sub-form-col-index="swIdx">
</field-widget>
</div>
</template>
</Row>
<!-- </el-form>-->
</div>
</div>
</template>
<script>
import emitter from 'element-ui/lib/mixins/emitter'
import FieldWidget from "../form-designer/form-widget/field-widget";
import GridColItem from "./grid-col-item";
import TableCellItem from "./table-cell-item"
import {
deepClone,
generateId,
optionExists
} from "../../utils/util";
import i18n from "../utils/i18n";
import refMixin from "./refMixin";
export default {
name: "ContainerItem",
componentName: 'ContainerItem',
mixins: [emitter, i18n, refMixin],
components: {
GridColItem,
TableCellItem,
FieldWidget,
},
props: {
widget: Object,
},
inject: ['refList', 'sfRefList', 'globalModel'],
data() {
return {
activeTabName: '',
rowIdData: [],
fieldSchemaData: [],
}
},
computed: {
customClass() {
return this.widget.options.customClass || ''
},
visibleTabs() {
return this.widget.tabs.filter(tp => {
return !tp.options.hidden
})
},
formModel: {
cache: false,
get() {
return this.globalModel.formModel
}
},
},
created() {
this.initRefList()
this.registerSubFormToRefList()
this.initRowIdData(true)
this.initFieldSchemaData()
this.initEventHandler()
},
mounted() {
this.initActiveTab()
},
methods: {
getLabelAlign(widget, subWidget) {
return subWidget.options.labelAlign || widget.options.labelAlign
},
initActiveTab() {
if ((this.widget.type === 'tab') && (this.widget.tabs.length > 0)) {
let activePanes = this.widget.tabs.filter((tp) => {
return tp.options.active === true
})
if (activePanes.length > 0) {
this.activeTabName = activePanes[0].options.name
} else {
this.activeTabName = this.widget.tabs[0].options.name
}
}
},
registerSubFormToRefList() {
if (this.widget.type === 'sub-form') {
this.sfRefList[this.widget.options.name] = this
}
},
initRowIdData(initFlag) {
if (this.widget.type === 'sub-form') {
//this.rowIdData.length = 0
this.rowIdData.splice(0, this.rowIdData.length) //splicelength=0
let subFormModel = this.formModel[this.widget.options.name]
if (!!subFormModel && (subFormModel.length > 0)) {
subFormModel.forEach(rowModel => {
//this.rowIdData.push('rowId' + generateId())
this.rowIdData.push('r' + generateId())
})
if (!!initFlag) {
//SumFormDataChange
setTimeout(() => {
this.handleSubFormRowChange(subFormModel)
}, 800)
}
}
}
},
addToRowIdData() {
this.rowIdData.push('rowId' + generateId())
},
insertToRowIdData(rowIndex) {
this.rowIdData.splice(rowIndex, 0, 'rowId' + generateId())
},
deleteFromRowIdData(rowIndex) {
this.rowIdData.splice(rowIndex, 1)
},
initFieldSchemaData() { //fieldSchemaData
if (this.widget.type !== 'sub-form') {
return
}
let rowLength = this.rowIdData.length
//this.fieldSchemaData.length = 0
this.fieldSchemaData.splice(0, this.fieldSchemaData.length) //splicelength=0
if (rowLength > 0) {
for (let i = 0; i < rowLength; i++) {
let fieldSchemas = []
this.widget.widgetList.forEach(swItem => {
fieldSchemas.push(this.cloneFieldSchema(swItem))
})
this.fieldSchemaData.push(fieldSchemas)
}
}
},
addToFieldSchemaData(rowIndex) {
let fieldSchemas = []
this.widget.widgetList.forEach(swItem => {
fieldSchemas.push(this.cloneFieldSchema(swItem))
})
if (rowIndex === undefined) {
this.fieldSchemaData.push(fieldSchemas)
} else {
this.fieldSchemaData.splice(rowIndex, 0, fieldSchemas)
}
},
deleteFromFieldSchemaData(rowIndex) {
this.fieldSchemaData.splice(rowIndex, 1)
},
cloneFieldSchema(fieldWidget) {
let newFieldSchema = deepClone(fieldWidget)
newFieldSchema.id = fieldWidget.type + generateId()
return newFieldSchema
},
initEventHandler() {
if (this.widget.type !== 'sub-form') {
return
}
this.$on('setFormData', function(newFormData) {
this.initRowIdData(false)
this.initFieldSchemaData()
let subFormData = newFormData[this.widget.options.name] || []
setTimeout(() => { //SubFormRowChange, 便
this.handleSubFormRowChange(subFormData)
}, 800)
})
// this.$on('subFormDataChange', function (subFormData) {
// console.log('test--------', subFormData)
// this.handleSubFormRowChange(subFormData)
// })
},
addSubFormRow() {
let newSubFormDataRow = {}
this.widget.widgetList.forEach(subFormItem => {
if (!!subFormItem.formItemFlag) {
newSubFormDataRow[subFormItem.options.name] = subFormItem.options.defaultValue
}
})
let oldSubFormData = this.formModel[this.widget.options.name] || []
oldSubFormData.push(newSubFormDataRow)
this.addToRowIdData()
this.addToFieldSchemaData()
this.handleSubFormRowAdd(oldSubFormData, oldSubFormData.length - 1)
this.handleSubFormRowChange(oldSubFormData)
},
insertSubFormRow(beforeFormRowIndex) {
let newSubFormDataRow = {}
this.widget.widgetList.forEach(subFormItem => {
if (!!subFormItem.formItemFlag) {
newSubFormDataRow[subFormItem.options.name] = subFormItem.options.defaultValue
}
})
let oldSubFormData = this.formModel[this.widget.options.name] || []
oldSubFormData.splice(beforeFormRowIndex, 0, newSubFormDataRow)
this.insertToRowIdData(beforeFormRowIndex)
this.addToFieldSchemaData(beforeFormRowIndex)
this.handleSubFormRowInsert(oldSubFormData, beforeFormRowIndex)
this.handleSubFormRowChange(oldSubFormData)
},
deleteSubFormRow(formRowIndex) {
this.$confirm(this.i18nt('render.hint.deleteSubFormRow') + '?', this.i18nt('render.hint.prompt'), {
confirmButtonText: this.i18nt('render.hint.confirm'),
cancelButtonText: this.i18nt('render.hint.cancel')
}).then(() => {
let oldSubFormData = this.formModel[this.widget.options.name] || []
let deletedDataRow = deepClone(oldSubFormData[formRowIndex])
oldSubFormData.splice(formRowIndex, 1)
this.deleteFromRowIdData(formRowIndex)
this.deleteFromFieldSchemaData(formRowIndex)
this.handelSubFormRowDelete(oldSubFormData, deletedDataRow)
this.handleSubFormRowChange(oldSubFormData)
}).catch(() => {
//
})
},
handleSubFormRowChange(subFormData) {
if (!!this.widget.options.onSubFormRowChange) {
let customFunc = new Function('subFormData', this.widget.options.onSubFormRowChange)
customFunc.call(this, subFormData)
}
},
handleSubFormRowAdd(subFormData, newRowIndex) {
if (!!this.widget.options.onSubFormRowAdd) {
let customFunc = new Function('subFormData', 'newRowIndex', this.widget.options.onSubFormRowAdd)
customFunc.call(this, subFormData, newRowIndex)
}
},
handleSubFormRowInsert(subFormData, newRowIndex) {
if (!!this.widget.options.onSubFormRowInsert) {
let customFunc = new Function('subFormData', 'newRowIndex', this.widget.options.onSubFormRowInsert)
customFunc.call(this, subFormData, newRowIndex)
}
},
handelSubFormRowDelete(subFormData, deletedDataRow) {
if (!!this.widget.options.onSubFormRowDelete) {
let customFunc = new Function('subFormData', 'deletedDataRow', this.widget.options.onSubFormRowDelete)
customFunc.call(this, subFormData, deletedDataRow)
}
},
//--------------------- API begin ------------------//
/* 提示:用户可自行扩充这些方法!!! */
setHidden(flag) {
this.widget.options.hidden = flag
},
activeTab(tabIndex) { //tabIndex0
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs.forEach((tp, idx) => {
tp.options.active = idx === tabIndex
if (idx === tabIndex) {
this.activeTabName = tp.options.name
}
})
}
},
disableTab(tabIndex) {
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs[tabIndex].options.disabled = true
}
},
enableTab(tabIndex) {
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs[tabIndex].options.disabled = false
}
},
hideTab(tabIndex) {
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs[tabIndex].options.hidden = true
}
},
showTab(tabIndex) {
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs[tabIndex].options.hidden = false
}
},
disableSubFormRow(rowIndex) {
this.widget.widgetList.forEach(subWidget => {
let swRefName = subWidget.options.name + '@row' + this.rowIdData[rowIndex]
let foundSW = this.getWidgetRef(swRefName)
if (!!foundSW) {
foundSW.setDisabled(true)
}
})
},
enableSubFormRow(rowIndex) {
this.widget.widgetList.forEach(subWidget => {
let swRefName = subWidget.options.name + '@row' + this.rowIdData[rowIndex]
let foundSW = this.getWidgetRef(swRefName)
if (!!foundSW) {
foundSW.setDisabled(false)
}
})
},
disableSubForm() {
if (this.rowIdData.length > 0) {
this.rowIdData.forEach((dataRow, rIdx) => {
this.disableSubFormRow(rIdx)
})
}
},
enableSubForm() {
if (this.rowIdData.length > 0) {
this.rowIdData.forEach((dataRow, rIdx) => {
this.enableSubFormRow(rIdx)
})
}
},
resetSubForm() { //subForm
if (this.widget.type === 'sub-form') {
let subFormModel = this.formModel[this.widget.options.name]
if (!!subFormModel) {
subFormModel.splice(0, subFormModel.length)
this.rowIdData.splice(0, this.rowIdData.length)
}
}
},
getSubFormValues(needValidation = true) {
if (this.widget.type === 'sub-form') {
//TODO:
return this.formModel[this.widget.options.name]
} else {
this.$message.error(this.i18nt('render.hint.nonSubFormType'))
}
},
validateField(fieldName) { //
//TODO:
},
validateSubForm() { //
//TODO:
},
//--------------------- API end ------------------//
},
}
</script>
<style lang="scss" scoped>
div.table-container {
table.table-layout {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
}
.sub-form-container {
margin-bottom: 8px;
::v-deep .el-row.header-row {
padding-bottom: 0;
}
::v-deep .el-row.sub-form-row {
padding-top: 3px;
padding-bottom: 3px;
.row-number-span {
margin-left: 16px;
}
}
}
div.action-header-column {
display: inline-block;
width: 120px;
.action-label {
margin-right: 12px;
}
.action-button {
padding-left: 8px;
padding-right: 8px;
}
}
div.field-header-column {
display: inline-block;
//overflow: hidden;
//white-space: nowrap; //
//text-overflow: ellipsis; //
span.custom-label i {
margin: 0 3px;
}
}
div.field-header-column.is-required:before {
content: '*';
color: #F56C6C;
margin-right: 4px;
}
div.label-center-left {
text-align: left;
}
div.label-center-align {
text-align: center;
}
div.label-right-align {
text-align: right;
}
div.sub-form-action-column {
display: inline-block;
width: 120px;
::v-deep .el-form-item {
margin-bottom: 0;
}
::v-deep .el-button {
font-size: 18px;
padding: 0;
background: #DCDFE6;
border: 4px solid #DCDFE6;
}
}
div.sub-form-action-column.hide-label {
::v-deep .el-form-item__label {
display: none;
}
}
div.sub-form-table-column {
display: inline-block;
//width: 200px;
::v-deep .el-form-item {
margin-left: 4px;
margin-right: 4px;
margin-bottom: 0;
}
::v-deep .el-form-item__content {
margin-left: 0 !important;
}
}
div.sub-form-table-column.hide-label {
::v-deep .el-form-item__label {
display: none;
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<Col class="grid-cell" :span="widget.options.span" :class="[customClass]"
: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">
<template v-if="'container' === subWidget.category">
<container-item :widget="subWidget" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-item>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="null" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></field-widget>
</template>
</template>
</template>
<template v-else>
<Col>
<div class="blank-cell"><span class="invisible-content">{{i18nt('render.hint.blankCellContent')}}</span></div>
</Col>
</template>
</Col>
</template>
<script>
//import ContainerItem from "./container-item";
import FieldWidget from "../form-designer/form-widget/field-widget";
import i18n from "../utils/i18n";
import refMixin from "./refMixin";
export default {
name: "GridColItem",
componentName: 'GridColItem',
mixins: [i18n, refMixin],
components: {
FieldWidget,
//'container-item': ContainerItem, /* 使 */
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
},
inject: ['refList', 'globalModel'],
computed: {
customClass() {
return this.widget.options.customClass || ''
},
},
created() {
this.initRefList()
},
}
</script>
<style lang="scss" scoped>
.blank-cell {
font-style: italic;
color: #cccccc;
span.invisible-content {
opacity: 0;
}
}
</style>

View File

@ -0,0 +1,477 @@
<template>
<Form :label-position="labelPosition" :size="size" :class="[customClass]" class="render-form"
:validate-on-rule-change="false" :model="formDataModel" ref="renderForm" @submit.native.prevent>
<template v-for="(widget, index) in widgetList">
<template v-if="'container' === widget.category">
<container-item :widget="widget" :key="widget.id" :parent-list="widgetList"
:index-of-parent-list="index" :parent-widget="null"></container-item>
</template>
<template v-else>
<field-widget :field="widget" :form-model="formDataModel" :designer="null" :key="widget.id"
:parent-list="widgetList" :index-of-parent-list="index" :parent-widget="null"
@field-value-changed="valueChanged"></field-widget>
</template>
</template>
</Form>
</template>
<script>
//import ElForm from 'element-ui/packages/form/src/form.vue'
import emitter from 'element-ui/lib/mixins/emitter'
import FieldWidget from "../form-designer/form-widget/field-widget";
import ContainerItem from "./container-item";
import {
deepClone,
insertCustomCssToHead,
insertGlobalFunctionsToHtml
} from "../../utils/util";
//import i18n from "../../utils/i18n";
import i18n, {
changeLocale
} from "../utils/i18n";
export default {
name: "VFormRender",
componentName: 'VFormRender',
mixins: [emitter, i18n],
components: {
FieldWidget,
ContainerItem,
//ElForm,
},
props: {
//designer: Object, /* designerVFormRender */
formJson: Object, //prop
formData: { //prop
Object,
default: () => {}
},
optionData: { //prop
type: Object,
default: () => {}
},
eventHandler:{ //
type: Object,
default: () => {}
},
container:Object
},
provide() {
return {
refList: this.widgetRefList,
sfRefList: this.subFormRefList, //SubForm
formConfig: this.formConfig,
globalOptionData: this.optionData,
globalModel: {
formModel: this.formDataModel,
}
}
},
data() {
return {
formDataModel: {
//
},
widgetRefList: {},
subFormRefList: {},
}
},
computed: {
formConfig() {
return this.formJson.formConfig
},
widgetList() {
return this.formJson.widgetList
},
labelPosition() {
if (!!this.formConfig && !!this.formConfig.labelPosition) {
return this.formConfig.labelPosition
}
return 'left'
},
size() {
if (!!this.formConfig && !!this.formConfig.size) {
return this.formConfig.size
}
return 'medium'
},
customClass() {
return this.formConfig.customClass || ''
},
},
watch: {
//
},
created() {
this.insertCustomStyleAndScriptNode()
this.buildFormModel()
this.addFieldChangeEventHandler()
this.registerFormToRefList()
this.handleOnCreated()
},
mounted() {
this.initLocale()
this.handleOnMounted()
},
methods: {
initLocale() {
let curLocale = localStorage.getItem('v_form_locale') || 'zh-CN'
this.changeLanguage(curLocale)
},
insertCustomStyleAndScriptNode() {
if (!!this.formConfig && !!this.formConfig.cssCode) {
insertCustomCssToHead(this.formConfig.cssCode)
}
if (!!this.formConfig && !!this.formConfig.functions) {
insertGlobalFunctionsToHtml(this.formConfig.functions)
}
},
buildFormModel() {
//this.formDataModel = this.formData //
//this._provided.globalModel.formModel = this.formData
this.widgetList.forEach((wItem) => {
this.buildDataFromWidget(wItem, null)
})
},
buildDataFromWidget(wItem, parentItem) {
if (wItem.category === 'container') {
if (wItem.type === 'grid') {
if (!!wItem.cols && (wItem.cols.length > 0)) {
wItem.cols.forEach((childItem) => {
this.buildDataFromWidget(childItem, wItem)
})
}
} else if (wItem.type === 'table') {
if (!!wItem.rows && (wItem.rows.length > 0)) {
wItem.rows.forEach((rowItem) => {
if (!!rowItem.cols && (rowItem.cols.length > 0)) {
rowItem.cols.forEach((colItem) => {
this.buildDataFromWidget(colItem, wItem)
})
}
})
}
} else if (wItem.type === 'tab') {
if (!!wItem.tabs && (wItem.tabs.length > 0)) {
wItem.tabs.forEach((tabItem) => {
if (!!tabItem.widgetList && (tabItem.widgetList.length > 0)) {
tabItem.widgetList.forEach((childItem) => {
this.buildDataFromWidget(childItem, wItem)
})
}
})
}
} else if (wItem.type === 'sub-form') {
let subFormName = wItem.options.name
if (!this.formData.hasOwnProperty(subFormName)) {
let subFormDataRow = {}
if (wItem.options.showBlankRow) {
wItem.widgetList.forEach(subFormItem => {
if (!!subFormItem.formItemFlag) {
subFormDataRow[subFormItem.options.name] = subFormItem.options.defaultValue
}
})
this.$set(this.formDataModel, subFormName, [subFormDataRow]) //
} else {
this.$set(this.formDataModel, subFormName, []) //
}
} else {
let initialValue = this.formData[subFormName]
this.$set(this.formDataModel, subFormName, deepClone(initialValue))
}
} else if ((wItem.type === 'grid-col') || (wItem.type === 'table-cell')) {
if (!!wItem.widgetList && (wItem.widgetList.length > 0)) {
wItem.widgetList.forEach((childItem) => {
this.buildDataFromWidget(childItem, wItem)
})
}
} else {
if (!!wItem.widgetList && (wItem.widgetList.length > 0)) {
wItem.widgetList.forEach((childItem) => {
this.buildDataFromWidget(childItem, wItem)
})
}
}
} else if (!!wItem.formItemFlag) {
if (!this.formData.hasOwnProperty(wItem.options.name)) {
//this.formDataModel[wItem.options.name] = '' //$set
this.$set(this.formDataModel, wItem.options.name, wItem.options.defaultValue) //
} else {
let initialValue = this.formData[wItem.options.name]
this.$set(this.formDataModel, wItem.options.name, deepClone(initialValue))
}
}
},
addFieldChangeEventHandler() {
this.$on('fieldChange', function(fieldName, newValue, oldValue, subFormName, subFormRowIndex) {
this.handleFieldDataChange(fieldName, newValue, oldValue, subFormName, subFormRowIndex)
this.$emit('formChange', fieldName, newValue, oldValue, this.formDataModel, subFormName,
subFormRowIndex)
//this.$emit('subFormChange', subFormName, subFormRowIndex,
// fieldName, newValue, oldValue, this.formDataModel)
})
},
registerFormToRefList() {
this.widgetRefList['v_form_ref'] = this
},
handleFieldDataChange(fieldName, newValue, oldValue, subFormName, subFormRowIndex) {
if (!!this.formConfig.onFormDataChange) {
let customFunc = new Function('fieldName', 'newValue', 'oldValue', 'formModel', 'subFormName',
'subFormRowIndex',
this.formConfig.onFormDataChange)
customFunc.call(this, fieldName, newValue, oldValue, this.formDataModel, subFormName, subFormRowIndex)
}
},
// forceUpdate() {
// this.$forceUpdate()
// },
handleOnCreated() {
if (!!this.formConfig.onFormCreated) {
let customFunc = new Function(this.formConfig.onFormCreated)
customFunc.call(this)
}
},
handleOnMounted() {
if (!!this.formConfig.onFormMounted) {
let customFunc = new Function(this.formConfig.onFormMounted)
customFunc.call(this)
}
},
findWidgetAndSetDisabled(widgetName, disabledFlag) {
let foundW = this.getWidgetRef(widgetName)
if (!!foundW) {
foundW.setDisabled(disabledFlag)
}
},
findWidgetAndSetHidden(widgetName, hiddenFlag) {
let foundW = this.getWidgetRef(widgetName)
if (!!foundW) {
foundW.setHidden(hiddenFlag)
}
},
//--------------------- API begin ------------------//
/* 提示:用户可自行扩充这些方法!!! */
changeLanguage(langName) {
changeLocale(langName)
},
getNativeForm() { //form
return this.$refs['renderForm']
},
getWidgetRef(widgetName, showError = false) {
let foundRef = this.widgetRefList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('render.hint.refNotFound') + widgetName)
}
return foundRef
},
getFormData(needValidation = true) {
if (!needValidation) {
return this.formDataModel
}
let callback = function nullFunc() {}
let promise = new window.Promise(function(resolve, reject) {
callback = function callback(formData, error) {
!error ? resolve(formData) : reject(error);
};
});
this.$refs['renderForm'].validate((valid) => {
if (valid) {
callback(this.formDataModel)
} else {
callback(this.formDataModel, this.i18nt('render.hint.validationFailed'))
}
})
//return this.$refs['renderForm'].validate()
return promise
},
setFormData(formData) { //
//this.formDataModel = formData //injectformModel
this._provided.globalModel.formModel = formData /* 这种写法可使inject的属性保持响应式更新 */
//
// SubForm
this.broadcast('ContainerItem', 'setFormData', formData)
// FieldWidget
this.broadcast('FieldWidget', 'setFormData', formData)
},
getFieldValue(fieldName) { //
let fieldRef = this.getWidgetRef(fieldName)
if (!!fieldRef && !!fieldRef.getValue) {
return fieldRef.getValue()
}
},
setFieldValue(fieldName, fieldValue) { //
let fieldRef = this.getWidgetRef(fieldName)
if (!!fieldRef && !!fieldRef.setValue) {
fieldRef.setValue(fieldValue)
}
},
getSubFormValues(subFormName, needValidation = true) {
let foundSFRef = this.subFormRefList[subFormName]
// if (!foundSFRef) {
// return this.formDataModel[subFormName]
// }
return foundSFRef.getSubFormValues(needValidation)
},
disableForm() {
let wNameList = Object.keys(this.widgetRefList)
wNameList.forEach(wName => {
let foundW = this.getWidgetRef(wName)
if (!!foundW) {
// try {
// foundW.setDisabled(true)
// } catch (ex) {
// console.log('disableForm error: ', ex.message)
// }
if (!!foundW.setDisabled) {
foundW.setDisabled(true)
}
}
})
},
enableForm() {
let wNameList = Object.keys(this.widgetRefList)
wNameList.forEach(wName => {
let foundW = this.getWidgetRef(wName)
if (!!foundW) {
// try {
// foundW.setDisabled(false)
// } catch (ex) {
// console.log('disableForm error: ', wName + ', ' + ex.message)
// }
if (!!foundW.setDisabled) {
foundW.setDisabled(false)
}
}
})
},
resetForm() { //
let subFormNames = Object.keys(this.subFormRefList)
subFormNames.forEach(sfName => {
if (!!this.subFormRefList[sfName].resetSubForm) {
this.subFormRefList[sfName].resetSubForm()
}
})
let wNameList = Object.keys(this.widgetRefList)
wNameList.forEach(wName => {
let foundW = this.getWidgetRef(wName)
if (!!foundW && !!foundW.resetField) {
foundW.resetField()
}
})
this.$refs.renderForm.clearValidate()
},
validateForm() {
//
},
validateFields() {
//
},
disableWidgets(widgetNames) {
if (!!widgetNames) {
if (typeof widgetNames === 'string') {
this.findWidgetAndSetDisabled(widgetNames, true)
} else if (Array.isArray(widgetNames)) {
widgetNames.forEach(wn => {
this.findWidgetAndSetDisabled(wn, true)
})
}
}
},
enableWidgets(widgetNames) {
if (!!widgetNames) {
if (typeof widgetNames === 'string') {
this.findWidgetAndSetDisabled(widgetNames, false)
} else if (Array.isArray(widgetNames)) {
widgetNames.forEach(wn => {
this.findWidgetAndSetDisabled(wn, false)
})
}
}
},
hideWidgets(widgetNames) {
if (!!widgetNames) {
if (typeof widgetNames === 'string') {
this.findWidgetAndSetHidden(widgetNames, true)
} else if (Array.isArray(widgetNames)) {
widgetNames.forEach(wn => {
this.findWidgetAndSetHidden(wn, true)
})
}
}
},
showWidgets(widgetNames) {
if (!!widgetNames) {
if (typeof widgetNames === 'string') {
this.findWidgetAndSetHidden(widgetNames, false)
} else if (Array.isArray(widgetNames)) {
widgetNames.forEach(wn => {
this.findWidgetAndSetHidden(wn, false)
})
}
}
},
valueChanged(args){
console.log(">>>>");
console.log(args);
}
//--------------------- API end ------------------//
},
}
</script>
<style lang="scss" scoped>
.el-form ::v-deep .el-row {
padding: 8px;
}
</style>

View File

@ -0,0 +1,22 @@
export default {
methods: {
initRefList() {
if ((this.refList !== null) && !!this.widget.options.name) {
this.refList[this.widget.options.name] = this
}
},
getWidgetRef(widgetName, showError) {
let foundRef = this.refList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('render.hint.refNotFound') + widgetName)
}
return foundRef
},
getFormRef() { /* 获取VFrom引用必须在VForm组件created之后方可调用 */
return this.refList['v_form_ref']
},
}
}

View File

@ -0,0 +1,61 @@
<template>
<td class="table-cell" :class="[customClass]"
:colspan="widget.options.colspan || 1" :rowspan="widget.options.rowspan || 1"
:style="{width: widget.options.cellWidth + '!important' || '', height: widget.options.cellHeight + '!important' || ''}">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-item :widget="subWidget" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-item>
</template>
<template v-else>
<field-widget :field="subWidget" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></field-widget>
</template>
</template>
</td>
</template>
<script>
import FieldWidget from "../form-designer/form-widget/field-widget";
import i18n from "../utils/i18n";
import refMixin from "./refMixin";
export default {
name: "TableCellItem",
componentName: "TableCellItem",
mixins: [i18n, refMixin],
components: {
FieldWidget,
//'container-item': ContainerItem, /* 使 */
},
props: {
widget: Object,
rowIndex: Number,
colIndex: Number,
},
inject: ['refList', 'globalModel'],
computed: {
customClass() {
return this.widget.options.customClass || ''
},
},
created() {
/* tableCell不生成组件引用 */
//this.initRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
td.table-cell {
display: table-cell;
height: 36px;
border: 1px dashed #336699;
}
</style>

View File

@ -0,0 +1,308 @@
export default {
application: {
'zh-CN': '简体中文',
'en-US': 'English',
productTitle: 'Online Form Designer',
github: 'GitHub',
document: 'Docs',
qqGroup: 'WeChat Group',
deployment: 'Deployment',
subscription: 'Subscription',
},
designer: {
containerTitle: 'Container',
dragHandlerHint: 'drag container or field to layout center',
dragAction: 'drag',
basicFieldTitle: 'Basic Field',
advancedFieldTitle: 'Advanced Field',
customFieldTitle: 'Customized Field',
noWidgetHint: 'Please select a widget from the left list, drag and drop to this container.',
widgetLabel: {
grid: 'Grid',
table: 'Table',
tab: 'Tab',
section: 'Section',
'sub-form': 'SubForm',
'grid-col': 'GridCol',
'table-cell': 'TableCell',
'tab-pane': 'TabPane',
input: 'Input',
'input-composite' : 'InputComposite',
textarea: 'Textarea',
number: 'InputNumber',
radio: 'Radio',
checkbox: 'Checkbox',
select: 'Select',
time: 'Time',
'time-range': 'Time range',
date: 'Date',
'date-range': 'Date range',
switch: 'Switch',
rate: 'Rate',
color: 'ColorPicker',
slider: 'Slider',
'static-text': 'Text',
'html-text': 'HTML',
button: 'Button',
divider: 'Divider',
'picture-upload': 'Picture',
'file-upload': 'File',
'rich-editor': 'Rich Editor',
cascader: 'Cascader',
custom: 'Custom Component',
slot: 'Slot'
},
hint: {
selectParentWidget: 'Select parent of this widget',
moveUpWidget: 'Move up this widget',
moveDownWidget: 'Move down this widget',
cloneWidget: 'Clone this widget',
insertRow: 'Insert new row',
insertColumn: 'Insert new column',
remove: 'Remove this widget',
cellSetting: 'Cell setting',
dragHandler: 'Drag handler',
copyField: 'Copy field widget',
onlyFieldWidgetAcceptable: 'Only field widget can be dragged into sub-form',
moveUpFirstChildHint: 'First child can not be move up',
moveDownLastChildHint: 'Last child can not be move down',
closePreview: 'Close',
copyJson: 'Copy',
copyVueCode: 'Copy Vue Code',
copyHtmlCode: 'Copy HTML Code',
copyJsonSuccess: 'Copy succeed',
importJsonSuccess: 'Import succeed',
copyJsonFail: 'Copy failed',
copyVueCodeSuccess: 'Copy succeed',
copyVueCodeFail: 'Copy failed',
copyHtmlCodeSuccess: 'Copy succeed',
copyHtmlCodeFail: 'Copy failed',
saveVueCode: 'Save Vue File',
saveHtmlCode: 'Save Html File',
getFormData: 'Get Data',
resetForm: 'Reset',
disableForm: 'Disable',
enableForm: 'Enable',
exportFormData: 'Form Data',
copyFormData: 'Copy',
saveFormData: 'Save As File',
copyVue2SFC: 'Copy Vue2',
copyVue3SFC: 'Copy Vue3',
copySFCFail: 'Copy failed',
copySFCSuccess: 'Copy succeed',
saveVue2SFC: 'Save As Vue2',
saveVue3SFC: 'Save As Vue3',
fileNameForSave: 'File name:',
saveFileTitle: 'Save as File',
fileNameInputPlaceholder: 'Enter the file name',
widgetSetting: 'Widget Config',
formSetting: 'Form Config',
prompt: 'Prompt',
confirm: 'OK',
cancel: 'Cancel',
import: 'Import',
importJsonHint: 'The code to be imported should have the following JSON format.',
invalidOptionsData: 'Invalid data of options:',
lastPaneCannotBeDeleted: 'The last pane cannot be deleted.',
duplicateName: 'Duplicate name: ',
nameRequired: 'Name required.',
numberValidator: 'Number',
letterValidator: 'Letter',
letterAndNumberValidator: 'LetterAndNumber',
mobilePhoneValidator: 'MobilePhone',
emailValidator: 'Email',
urlValidator: 'URL',
noChineseValidator: 'Non-Chinese',
chineseValidator: 'Chinese',
rowspanNotConsistentForMergeEntireRow: 'Cells in this row don\'t have the same rowspan, operation failed.',
colspanNotConsistentForMergeEntireColumn: 'Cells in this column don\'t have the same colspan, operation failed.',
rowspanNotConsistentForDeleteEntireRow: 'Cells in this row don\'t have the same rowspan, operation failed.',
colspanNotConsistentForDeleteEntireColumn: 'Cells in this column don\'t have the same colspan, operation failed.',
},
toolbar: {
undoHint: 'Undo',
redoHint: 'Redo',
pcLayout: 'PC',
mobileLayout: 'H5',
clear: 'Clear',
preview: 'Preview',
importJson: 'Import JSON',
exportJson: 'Export JSON',
exportCode: 'Export Code',
generateCode: 'Generate Code',
saveCode: 'Save Code',
reloadCode: 'Reload Code',
generateSFC: 'Generate SFC',
},
setting: {
basicSetting: 'Basic Setting',
attributeSetting: 'Attribute Setting',
commonSetting: 'Common Setting',
advancedSetting: 'Advanced Setting',
eventSetting: 'Event Setting',
fieldName: 'Unique Name',
label: 'Label',
displayType: 'Type',
defaultValue: 'Default Value',
placeholder: 'Placeholder',
startPlaceholder: 'Start Placeholder',
endPlaceholder: 'End Placeholder',
widgetColumnWidth: 'Width',
widgetSize: 'Size',
displayStyle: 'Display Style',
inlineLayout: 'inline',
blockLayout: 'block',
labelWidth: 'Width Of Label',
rows: 'Rows',
labelHidden: 'Hide Label',
required: 'Required',
validation: 'Validation',
validationHelp: 'Regular expressions supported',
validationHint: 'Validation Hint',
readonly: 'Readonly',
disabled: 'Disabled',
hidden: 'Hidden',
textContent: 'Text',
htmlContent: 'HTML',
clearable: 'Clearable',
editable: 'Editable',
format: 'Format',
valueFormat: 'Value Format',
showPassword: 'Show Reveal',
filterable: 'Filterable',
allowCreate: 'Allow Create',
remote: 'Remote Query',
automaticDropdown: 'Automatic Dropdown',
multiple: 'Multiple',
multipleLimit: 'Multiple Limit',
contentPosition: 'Content Position',
plain: 'Plain',
round: 'Round',
circle: 'Circle',
icon: 'Icon',
optionsSetting: 'Options Setting',
addOption: 'Add Option',
importOptions: 'Import Options',
resetDefault: 'Reset Default',
uploadSetting: 'Upload Setting',
uploadURL: 'Upload URL',
uploadTip: 'Tip Content',
withCredentials: 'Send Cookie',
multipleSelect: 'File Multi-select',
showFileList: 'Show File List',
limit: 'Max Upload Number',
fileMaxSize: 'Max Size(MB)',
fileAccept: 'Upload File Filter',
fileTypes: 'Upload File Types',
fileTypesHelp: 'Allows to add more file types',
headers: 'Request Headers',
cellWidth: 'width',
cellHeight: 'height',
gutter: 'Gutter(px)',
columnSetting: 'Cols Setting',
colsOfGrid: 'Cols Of Grid:',
colSpanTitle: 'Spans Of Col',
addColumn: 'Add Column',
tabPaneSetting: 'Tab Panes',
tabPaneType: 'Tab Type',
addTabPane: 'Add Tab Pane',
paneActive: 'Active',
paneDisabled: 'Disable',
customLabelIcon: 'Custom Label',
labelIconClass: 'Label Icon Class',
labelIconPosition: 'Label Icon Position',
labelTooltip: 'Label Tooltip',
minValue: 'Min Value',
maxValue: 'Max Value',
precision: 'Precision',
formatter: 'Number Formatter',
step: 'Step',
controlsPosition: 'Controls Position',
minLength: 'Min Length',
maxLength: 'Max Length',
showWordLimit: 'Show Word Limit',
prefixIcon: 'Prefix Icon',
suffixIcon: 'Suffix Icon',
inputControl: 'Complex Input Setting',
prependControl: 'Prepend Control',
prependControlDisabled: 'Disable Prepend Control',
prependControlType: 'Prepend Control Type',
prependControlText: 'Prepend Control Type',
appendControl: 'Append Control',
appendControlDisabled: 'Disable Append Control',
appendControlType: 'Append Control Type',
appendControlText: 'Append Control Text',
buttonIcon: 'Button Icon',
activeText: 'Active Text',
inactiveText: 'Inactive Text',
activeColor: 'Active Color',
inactiveColor: 'Inactive Color',
maxStars: 'Stars Max Number',
lowThreshold: 'Low Threshold',
highThreshold: 'High Threshold',
allowHalf: 'Allow Half',
showText: 'Show Text',
showScore: 'Show Score',
range: 'Range',
vertical: 'Vertical',
direction: 'Vertical',
showBlankRow: 'Show Blank Row',
showRowNumber: 'Show Row Number',
to:'Route Redirect To',
target:'Open Page Target',
replace:'Record Redirect History',
append:'Append Route',
insertColumnToLeft: 'insert column to left',
insertColumnToRight: 'insert column to right',
insertRowAbove: 'insert row above',
insertRowBelow: 'insert row below',
mergeLeftColumn: 'merge left cell',
mergeRightColumn: 'merge right cell',
mergeEntireRow: 'merge entire row',
mergeRowAbove: 'merge cell above',
mergeRowBelow: 'merge cell below',
mergeEntireColumn: 'merge entire column',
undoMergeCol: 'undo merge column',
undoMergeRow: 'undo merge row',
deleteEntireCol: 'delete entire column',
deleteEntireRow: 'delete entire row',
widgetName: 'Unique Name',
formSize: 'Size',
labelPosition: 'Position Of Label',
topPosition: 'Top',
leftPosition: 'Left',
labelAlign: 'Label Align',
leftAlign: 'Left',
centerAlign: 'Center',
rightAlign: 'Right',
formCss: 'Form CSS',
addCss: 'Edit',
customClass: 'Custom Class',
globalFunctions: 'Global Functions',
addEventHandler: 'Edit',
editWidgetEventHandler: 'Edit Widget Event Handler',
editFormEventHandler: 'Edit Form Event Handler',
}
}
}

View File

@ -0,0 +1,38 @@
export default {
render: {
hint: {
prompt: 'Prompt',
confirm: 'OK',
cancel: 'Cancel',
selectPlaceholder: 'Pick some item',
timePlaceholder: 'Select time',
startTimePlaceholder: 'Start time',
endTimePlaceholder: 'End time',
datePlaceholder: 'Select date',
startDatePlaceholder: 'Start date',
endDatePlaceholder: 'End date',
blankCellContent: '--',
uploadError: 'Upload error: ',
uploadExceed: 'The maximum number(${uploadLimit}) of file uploads has been exceeded.',
unsupportedFileType: 'Unsupported format: ',
fileSizeExceed: 'File size out of limit: ',
refNotFound: 'Ref not found: ',
fieldRequired: 'Input value should be not null.',
invalidNumber: 'Invalid number format',
selectFile: ' File...',
downloadFile: 'Download',
removeFile: 'Remove',
validationFailed: 'Form validation failed',
subFormAction: 'Action',
subFormAddAction: 'Add',
subFormAddActionHint: 'add new row',
insertSubFormRow: 'insert new row',
deleteSubFormRow: 'delete this row',
nonSubFormType: 'The type of widget don\'t match sub-form',
}
}
}

View File

@ -0,0 +1,311 @@
export default {
application: {
'zh-CN': '简体中文',
'en-US': 'English',
productTitle: '表单设计器',
github: 'GitHub',
document: '文档',
qqGroup: '技术WX群',
deployment: '私有部署',
subscription: '订阅源码',
},
designer: {
containerTitle: '容器',
dragHandlerHint: '鼠标拖拽容器组件或字段组件并放置于表单中',
dragAction: '拖动',
basicFieldTitle: '基础字段',
advancedFieldTitle: '高级字段',
customFieldTitle: '自定义扩展字段',
noWidgetHint: '请从左侧列表中选择一个组件, 然后用鼠标拖动组件放置于此处.',
widgetLabel: {
grid: '栅格',
table: '表格',
tab: '标签页',
section: '区块',
'sub-form': '子表单',
'grid-col': '栅格列',
'table-cell': '单元格',
'tab-pane': '选项卡页',
input: '单行输入',
'input-composite' : '复合输入',
textarea: '多行输入',
number: '计数器',
radio: '单选项',
checkbox: '多选项',
select: '下拉选项',
time: '时间',
'time-range': '时间范围',
date: '日期',
'date-range': '日期范围',
switch: '开关',
rate: '评分',
color: '颜色选择器',
slider: '滑块',
'static-text': '静态文字',
'html-text': 'HTML',
button: '按钮',
divider: '分隔线',
'picture-upload': '图片',
'file-upload': '文件',
'rich-editor': '富文本',
cascader: '级联选择',
custom: 'Custom Component',
slot: 'Slot'
},
hint: {
selectParentWidget: '选中父组件',
moveUpWidget: '上移组件',
moveDownWidget: '下移组件',
cloneWidget: '复制组件',
insertRow: '插入新行',
insertColumn: '插入新列',
remove: '移除组件',
cellSetting: '单元格操作',
dragHandler: '拖拽手柄',
copyField: '复制字段组件',
onlyFieldWidgetAcceptable: '子表单只能接收字段组件',
moveUpFirstChildHint: '已经移动到最上面',
moveDownLastChildHint: '已经移动到最下面',
closePreview: '关闭',
copyJson: '复制JSON',
copyVueCode: '复制Vue代码',
copyHtmlCode: '复制HTML代码',
copyJsonSuccess: '复制JSON成功',
importJsonSuccess: '导入JSON成功',
copyJsonFail: '复制JSON失败',
copyVueCodeSuccess: '复制Vue代码成功',
copyVueCodeFail: '复制Vue代码失败',
copyHtmlCodeSuccess: '复制HTML代码成功',
copyHtmlCodeFail: '复制HTML代码失败',
saveVueCode: '保存Vue文件',
saveHtmlCode: '保存Html文件',
getFormData: '获取数据',
resetForm: '重置表单',
disableForm: '禁用编辑',
enableForm: '恢复编辑',
exportFormData: '表单数据',
copyFormData: '复制JSON',
saveFormData: '保存为文件',
copyVue2SFC: '复制Vue2代码',
copyVue3SFC: '复制Vue3代码',
copySFCFail: '复制SFC代码失败',
copySFCSuccess: '复制SFC代码成功',
saveVue2SFC: '保存为Vue2组件',
saveVue3SFC: '保存为Vue3组件',
fileNameForSave: '文件名:',
saveFileTitle: '保存为文件',
fileNameInputPlaceholder: '请输入文件名',
widgetSetting: '组件设置',
formSetting: '表单设置',
prompt: '提示',
confirm: '确定',
cancel: '取消',
import: '导入',
importJsonHint: '导入的JSON内容须符合下述格式以保证顺利导入.',
invalidOptionsData: '无效的选项数据:',
lastPaneCannotBeDeleted: '仅剩一个选项卡页不可删除.',
duplicateName: '组件名称已存在: ',
nameRequired: '组件名称不可为空',
numberValidator: '数字',
letterValidator: '字母',
letterAndNumberValidator: '数字字母',
mobilePhoneValidator: '手机号码',
emailValidator: '邮箱',
urlValidator: '网址',
noChineseValidator: '非中文字符',
chineseValidator: '仅中文字符',
rowspanNotConsistentForMergeEntireRow: '存在行高不一致的单元格, 无法合并整行.',
colspanNotConsistentForMergeEntireColumn: '存在列宽不一致的单元格, 无法合并整列.',
rowspanNotConsistentForDeleteEntireRow: '存在行高不一致的单元格, 不可删除整行.',
colspanNotConsistentForDeleteEntireColumn: '存在列宽不一致的单元格, 不可删除整列.',
},
toolbar: {
undoHint: '撤销',
redoHint: '重做',
pcLayout: 'PC',
mobileLayout: 'H5',
clear: '清空',
preview: '预览',
importJson: '导入JSON',
exportJson: '导出JSON',
exportCode: '导出代码',
generateCode: '生成代码',
saveCode: '保存代码',
reloadCode: '撤销代码更改',
generateSFC: '生成SFC',
},
setting: {
basicSetting: '基本属性',
attributeSetting: '属性设置',
commonSetting: '常见属性',
advancedSetting: '高级属性',
eventSetting: '事件属性',
fieldName: '字段唯一名称',
label: '字段标签',
displayType: '显示类型',
defaultValue: '默认值',
placeholder: '占位内容',
startPlaceholder: '起始占位内容',
endPlaceholder: '截止占位内容',
widgetColumnWidth: '组件列宽',
widgetSize: '组件大小',
displayStyle: '显示样式',
inlineLayout: '行内',
blockLayout: '块',
labelWidth: '标签宽度',
rows: '行数',
labelHidden: '隐藏字段标签',
required: '必填字段',
validation: '字段校验',
validationHelp: '支持输入正则表达式',
validationHint: '校验失败提示',
readonly: '只读',
disabled: '禁用',
hidden: '隐藏',
textContent: '静态文字',
htmlContent: 'HTML',
clearable: '可清除',
editable: '可输入',
format: '显示格式',
valueFormat: '绑定值格式',
showPassword: '可显示密码',
filterable: '可搜索选项',
allowCreate: '允许创建选项',
remote: '可远程搜索',
automaticDropdown: '自动弹出选项',
multiple: '选项可多选',
multipleLimit: '多选数量限制',
contentPosition: '文字位置',
plain: '朴素按钮',
round: '圆角按钮',
circle: '圆形按钮',
icon: '图标',
optionsSetting: '选项设置',
addOption: '增加选项',
importOptions: '导入选项',
resetDefault: '重设选中项',
uploadSetting: '上传参数设置',
uploadURL: '上传地址',
uploadTip: '上传提示内容',
withCredentials: '发送cookie凭证',
multipleSelect: '文件可多选',
showFileList: '显示文件列表',
limit: '最大上传数量',
fileMaxSize: '文件大小限制(MB)',
fileAccept: '上传文件过滤',
fileTypes: '上传文件类型',
fileTypesHelp: '支持添加其他文件类型',
headers: '上传请求头',
cellWidth: '宽度',
cellHeight: '高度',
gutter: '栅格间隔(像素)',
columnSetting: '栅格属性设置',
colsOfGrid: '当前栅格列:',
colSpanTitle: '栅格列',
addColumn: '增加栅格',
tabPaneSetting: '选项卡设置',
tabPaneType: '选项卡样式',
addTabPane: '增加选项卡页',
paneActive: '激活',
paneDisabled: '禁用',
customLabelIcon: '定制字段标签',
labelIconClass: '标签Icon样式',
labelIconPosition: '标签Icon位置',
labelTooltip: '标签文字提示',
minValue: '最小值',
maxValue: '最大值',
precision: '精度',
formatter: '输入框格式',
step: '增减步长',
controlsPosition: '控制按钮位置',
minLength: '最小长度',
maxLength: '最大长度',
showWordLimit: '显示字数统计',
prefixIcon: '头部Icon',
suffixIcon: '尾部Icon',
inputControl: '复合输入框设置',
prependControl: '添加前置按钮',
prependControlDisabled: '前置按钮禁用',
prependControlType: '前置按钮类型',
prependControlIcon: '前置按钮图标',
prependControlText: '前置按钮文字',
appendControl: '添加后置按钮',
appendControlDisabled: '后置按钮禁用',
appendControlType: '后置按钮类型',
appendControlIcon: '后置按钮图标',
appendControlText: '后置按钮文字',
buttonIcon: '后置按钮Icon',
activeText: '开启时文字描述',
inactiveText: '关闭时文字描述',
activeColor: '开启时背景色',
inactiveColor: '关闭时背景色',
maxStars: '最大评分值',
lowThreshold: '低分界限值',
highThreshold: '高分界限值',
allowHalf: '允许半选',
showText: '显示辅助文字',
showScore: '显示当前分数',
range: '是否为范围选择',
vertical: '是否竖向显示',
direction: '是否竖向显示',
showBlankRow: '默认显示新行',
showRowNumber: '显示行号',
to:'跳转链接',
target:'浏览器目标',
replace:'记录跳转历史',
append:'路由追加',
insertColumnToLeft: '插入左侧列',
insertColumnToRight: '插入右侧列',
insertRowAbove: '插入上方行',
insertRowBelow: '插入下方行',
mergeLeftColumn: '合并左侧单元格',
mergeRightColumn: '合并右侧单元格',
mergeEntireRow: '合并整行',
mergeRowAbove: '合并上方单元格',
mergeRowBelow: '合并下方单元格',
mergeEntireColumn: '合并整列',
undoMergeCol: '撤销列合并',
undoMergeRow: '撤销行合并',
deleteEntireCol: '删除整列',
deleteEntireRow: '删除整行',
widgetName: '组件唯一名称',
formSize: '全局组件大小',
labelPosition: '字段标签位置',
topPosition: '顶部',
leftPosition: '左边',
labelAlign: '字段标签对齐',
leftAlign: '居左',
centerAlign: '居中',
rightAlign: '居右',
formCss: '表单全局CSS',
addCss: '编写CSS',
customClass: '自定义CSS样式',
globalFunctions: '表单全局函数',
addEventHandler: '编写代码',
editWidgetEventHandler: '组件事件处理',
editFormEventHandler: '表单事件处理',
}
}
}

View File

@ -0,0 +1,38 @@
export default {
render: {
hint: {
prompt: '提示',
confirm: '确定',
cancel: '取消',
selectPlaceholder: '请选择',
timePlaceholder: '选择时间',
startTimePlaceholder: '起始时间',
endTimePlaceholder: '截止时间',
datePlaceholder: '选择日期',
startDatePlaceholder: '起始日期',
endDatePlaceholder: '截止日期',
blankCellContent: '--',
uploadError: '上传错误: ',
uploadExceed: '最大上传数量(${uploadLimit})已超出.',
unsupportedFileType: '不支持格式: ',
fileSizeExceed: '文件大小已超出: ',
refNotFound: '组件未找到: ',
fieldRequired: '字段值不可为空',
invalidNumber: '数据格式错误',
selectFile: ' 选择文件',
downloadFile: '下载',
removeFile: '移除',
validationFailed: '表单数据校验失败',
subFormAction: '操作',
subFormAddAction: '新增',
subFormAddActionHint: '新增行',
insertSubFormRow: '插入行',
deleteSubFormRow: '删除行',
nonSubFormType: '组件类型不是子表单',
}
}
}

View File

@ -0,0 +1,49 @@
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName"></use>
<title v-if="!!title">{{title}}</title>
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String
},
title: {
type: String,
default: ''
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style lang="scss" scoped>
.svg-icon {
width: 1.1em;
height: 1.1em;
margin-left: 0.35em;
margin-right: 0.35em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,91 @@
export const generateCode = function(formJson, codeType= 'vue') {
let formJsonStr = JSON.stringify(formJson)
if (codeType === 'html') {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<title>VForm Demo</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" href="https://ks3-cn-beijing.ksyun.com/vform2021/VFormRender.css?t=20210720">
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<v-form-render :form-json="formJson" :form-data="formData" :option-data="optionData" ref="vFormRef">
</v-form-render>
<Button type="primary" @click="submitForm">Submit</Button>
</div>
<script type="text/javascript">
if (!!window.ActiveXObject || "ActiveXObject" in window) { //IE load polyfill.js for Promise
var scriptEle = document.createElement("script");
scriptEle.type = "text/javascript";
scriptEle.src = "https://cdn.bootcss.com/babel-polyfill/6.23.0/polyfill.min.js"
document.body.appendChild(scriptEle)
}
</script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://ks3-cn-beijing.ksyun.com/vform2021/VFormRender.umd.min.js?t=20210720"></script>
<script>
new Vue({
el: '#app',
data: {
formJson: ${formJsonStr},
formData: {},
optionData: {}
},
methods: {
submitForm: function() {
this.$refs.vFormRef.getFormData().then( function(formData) {
// Form Validation OK
alert( JSON.stringify(formData) )
}).catch( function(error) {
// Form Validation Failed
alert(error)
})
}
}
});
</script>
</body>
</html>`
} else {
return `<template>
<div>
<v-form-render :form-json="formJson" :form-data="formData" :option-data="optionData" ref="vFormRef">
</v-form-render>
<Button type="primary" @click="submitForm">Submit</Button>
</div>
</template>
<script>
export default {
data() {
return {
formJson: ${formJsonStr},
formData: {},
optionData: {}
}
},
methods: {
submitForm() {
this.$refs.vFormRef.getFormData().then(formData => {
// Form Validation OK
alert( JSON.stringify(formData) )
}).catch(error => {
// Form Validation failed
this.$message.error(error)
})
}
}
}
</script>`
}
}

View File

@ -0,0 +1,53 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import enLocale from "../lang/en-US";
import zhLocale from "../lang/zh-CN";
import enLocale_render from "../lang/en-US_render";
import zhLocale_render from "../lang/zh-CN_render";
import en from 'view-design/dist/locale/en-US';
import zh from 'view-design/dist/locale/zh-CN';
Vue.use(VueI18n);
Vue.locale = () => {};
Vue.locale('en-US', en);
Vue.locale('zh-CN', zh);
const langResources = {
'en-US': {
...en,
...enLocale,
...enLocale_render
},
'zh-CN': {
...zh,
...zhLocale,
...zhLocale_render
}
};
export const i18n = new VueI18n({
locale: localStorage.getItem('v_form_locale') || 'zh-CN', // set locale
messages:langResources // set locale messages
});
export const changeLocale = function(langName) {
i18n.locale = langName
localStorage.setItem('v_form_locale', langName)
}
export const translate = function(key) {
return i18n._t(key, i18n.locale, i18n._getMessages())
}
export default {
methods: {
i18nt(key) {
return i18n._t(key, i18n.locale, i18n._getMessages())
}
}
}

View File

@ -0,0 +1,581 @@
import {isNotNull} from "@/utils/util";
import {genVue2JS} from "./vue2js-generator";
import {beautifierOpts} from "@/utils/beautifierLoader";
import {genVue3JS} from "./vue3js-generator";
function buildClassAttr(ctn, defaultClass) {
const cop = ctn.options
let gridClassArray = []
!!defaultClass && gridClassArray.push(defaultClass)
!!cop.customClass && (cop.customClass.length > 0) && gridClassArray.push(cop.customClass.join(' '))
return gridClassArray.length > 0 ? `class="${gridClassArray.join(' ')}"` : ''
}
const containerTemplates = { //容器组件属性
'grid': (ctn, formConfig) => {
const gridClassAttr = buildClassAttr(ctn)
const gridTemplate =
`<Row ${gridClassAttr}>
${ctn.cols.map(col => {
const colOpt = col.options
const colClassAttr = buildClassAttr(col, 'grid-cell')
return `<Col :span="${colOpt.span}" ${colClassAttr}>
${col.widgetList.map(cw => {
if (cw.category === 'container') {
return buildContainerWidget(cw, formConfig)
} else {
return buildFieldWidget(cw, formConfig)
}
}).join('')
}
</Col>`
}).join('')
}
</Row>`
return gridTemplate
},
'table': (ctn, formConfig) => {
const tableClassAttr = buildClassAttr(ctn, 'table-layout')
const tableTemplate =
`<div class="table-container">
<table ${tableClassAttr}><tbody>
${ctn.rows.map(tr => {
return `<tr>${
tr.cols.filter(td => !td.merged).map(td => {
const tdOpt = td.options
const tdClassAttr = buildClassAttr(td, 'table-cell')
const colspanAttr = (!isNaN(tdOpt.colspan) && (tdOpt.colspan !== 1)) ? `colspan="${tdOpt.colspan}"` : ''
const rowspanAttr = (!isNaN(tdOpt.rowspan) && (tdOpt.rowspan !== 1)) ? `rowspan="${tdOpt.rowspan}"` : ''
let tdStyleArray = []
!!tdOpt.cellWidth && tdStyleArray.push('width: ' + tdOpt.cellWidth + ' !important')
!!tdOpt.cellHeight && tdStyleArray.push('height: ' + tdOpt.cellHeight + ' !important')
let tdStyleAttr = (tdStyleArray.length > 0) ? `style="${tdStyleArray.join(';')}"` : ''
return `<td ${tdClassAttr} ${colspanAttr} ${rowspanAttr} ${tdStyleAttr}>${td.widgetList.map(tw => {
if (tw.category === 'container') {
return buildContainerWidget(tw, formConfig)
} else {
return buildFieldWidget(tw, formConfig)
}
}).join('')
}
</td>`
}).join('')
}</tr>`
}).join('')
}
</tbody></table>
</div>`
return tableTemplate
},
'tab': (ctn, formConfig) => {
const tabClassAttr = buildClassAttr(ctn)
const vModel = ctn.tabs && (ctn.tabs.length > 0) ? `v-model="${ctn.options.name}ActiveTab"` : ''
const tabTemplate =
`<div class="tab-container">
<Tabs ${vModel} type="${ctn.displayType}" ${tabClassAttr}>
${ctn.tabs.map(tab => {
const tabOpt = tab.options
const disabledAttr = (tabOpt.disabled === true) ? `disabled` : ''
return `<TabsPane name="${tabOpt.name}" label="${tabOpt.label}" ${disabledAttr}>
${tab.widgetList.map(tw => {
if (tw.category === 'container') {
return buildContainerWidget(tw, formConfig)
} else {
return buildFieldWidget(tw, formConfig)
}
}).join('')
}</TabsPane>`
}).join('')}
</Tabs>
</div>`
return tabTemplate
},
'sub-form': (ctn, formConfig) => {
//TODO:
},
}
function buildContainerWidget(widget, formConfig) {
return containerTemplates[widget.type] ? containerTemplates[widget.type](widget, formConfig) : null
}
function getElAttrs(widget, formConfig) { //获取El组件属性
let wop = widget.options
return {
vModel: `v-model="${formConfig.modelName}.${wop.name}"`,
readonly: wop.readonly ? `readonly="true"` : '',
disabled: wop.disabled ? `:disabled="true"` : '',
size: !!wop.size ? `size="${wop.size}"` : '',
type: !!wop.type ? `type="${wop.type === 'number' ? 'text' : wop.type}"` : '',
showPassword: !!wop.showPassword ? `:show-password="${wop.showPassword}"` : '',
placeholder: !!wop.placeholder ? `placeholder="${wop.placeholder}"` : '',
rows: (isNotNull(wop.rows) && !isNaN(wop.rows)) ? `rows="${wop.rows}"` : '',
clearable: !!wop.clearable ? 'clearable' : '',
minlength: (isNotNull(wop.minLength) && !isNaN(wop.minLength)) ? `:minlength="${wop.minLength}"` : '',
maxlength: (isNotNull(wop.maxLength) && !isNaN(wop.maxLength)) ? `:maxlength="${wop.maxLength}"` : '',
showWordLimit: !!wop.showWordLimit ? `:show-word-limit="true"`: '',
prefixIcon: !!wop.prefixIcon ? `prefix="${wop.prefixIcon}"` : '',
suffixIcon: !!wop.suffixIcon ? `suffix="${wop.suffixIcon}"` : '',
controlsPosition: wop.controlsPosition === 'right' ? `controls-outside="right"` : `controls-outside="default"`,
min: (isNotNull(wop.min) && !isNaN(wop.min)) ? `:min="${wop.min}"` : '',
max: (isNotNull(wop.max) && !isNaN(wop.max)) ? `:max="${wop.max}"` : '',
precision: (isNotNull(wop.precision) && !isNaN(wop.precision)) ? `:precision="${wop.precision}"` : '',
step: (isNotNull(wop.step) && !isNaN(wop.step)) ? `:step="${wop.step}"` : '',
filterable: !!wop.filterable ? `filterable` : '',
allowCreate: !!wop.allowCreate ? `allow-create` : '',
defaultFirstOption: (!!wop.filterable && !!wop.allowCreate) ? `default-first-option` : '',
multiple: !!wop.multiple ? `multiple` : '',
multipleLimit: (!isNaN(wop.multipleLimit) && (wop.multipleLimit > 0)) ? `:multiple-limit="${wop.multipleLimit}"` : '',
automaticDropdown: !!wop.automaticDropdown ? `automatic-dropdown` : '',
remote: !!wop.remote ? `remote` : '',
format: !!wop.format ? `format="${wop.format}"` : '',
valueFormat: !!wop.valueFormat ? `value-format="${wop.valueFormat}"` : '',
editable: !!wop.editable ? `:editable="${wop.editable}"` : '',
startPlaceholder: !!wop.startPlaceholder ? `start-placeholder="${wop.startPlaceholder}"` : '',
endPlaceholder: !!wop.endPlaceholder ? `end-placeholder="${wop.endPlaceholder}"` : '',
activeText: !!wop.activeText ? `active-text="${wop.activeText}"` : '',
inactiveText: !!wop.inactiveText ? `inactive-text="${wop.inactiveText}"` : '',
activeColor: !!wop.activeColor ? `true-color="${wop.activeColor}"` : '',
inactiveColor: !!wop.inactiveColor ? `false-color="${wop.inactiveColor}"` : '',
switchWidth: (!isNaN(wop.switchWidth) && (wop.switchWidth !== 40)) ? `:width="${wop.switchWidth}"` : '',
rateMax: (!isNaN(wop.max) && (wop.max !== 5)) ? `:max="${wop.max}"` : '',
lowThreshold: (!isNaN(wop.lowThreshold) && (wop.lowThreshold !== 2)) ? `:low-threshold="${wop.lowThreshold}"` : '',
highThreshold: (!isNaN(wop.highThreshold) && (wop.highThreshold !== 4)) ? `:high-threshold="${wop.highThreshold}"` : '',
allowHalf: !!wop.allowHalf ? `allow-half` : '',
showText: !!wop.showText ? `show-text` : '',
showScore: !!wop.showScore ? `show-score` : '',
sliderMin: (!isNaN(wop.min) && (wop.min !== 0)) ? `:min="${wop.min}"` : '',
sliderMax: (!isNaN(wop.max) && (wop.max !== 100)) ? `:max="${wop.max}"` : '',
sliderStep: (!isNaN(wop.step) && (wop.step !== 1)) ? `:step="${wop.step}"` : '',
sliderRange: !!wop.range ? `range` : '',
sliderVertical: !!wop.vertical ? `vertical` : '',
uploadAction: !!wop.uploadURL ? `action="${wop.uploadURL}"` : '',
withCredentials: !!wop.withCredentials ? `with-credentials` : '',
multipleSelect: !!wop.multipleSelect ? `multiple` : '',
showFileList: !!wop.showFileList ? `show-upload-list` : '',
limit: !isNaN(wop.limit) ? `:limit="${wop.limit}"` : '',
fileAccept: !!wop.fileAccept ? `:format="${wop.fileAccept}"` : '',
fileTypes: !!wop.fileTypes ? `:accept="${wop.fileTypes}"` : '',
fileMaxSize: !!wop.fileMaxSize? `:max-size="${wop.fileMaxSize}"`:``,
uploadTipSlotChild: !!wop.uploadTip ? `<template #tip><div class="el-upload__tip">${wop.uploadTip}</div></template>` : '',
pictureUploadIconChild: `<template #default><i class="ivu-icon ivu-icon-md-add"></i></template>`,
fileUploadIconChild: `<template #default><i class="ivu-icon ivu-icon-md-add"></i></template>`,
buttonType: !!wop.type ? `type="${wop.type}` : '',
buttonPlain: !!wop.plain ? `plain` : '',
buttonRound: !!wop.round ? `round` : '',
buttonCircle: !!wop.circle ? `circle` : '',
buttonIcon: !!wop.icon ? `icon="${wop.icon}"` : '',
contentPosition: (!!wop.contentPosition && (wop.contentPosition !== 'center')) ? `content-position="${wop.contentPosition}"` : '',
appendButtonChild: !!wop.appendButton ? `<template #append><Button class="${wop.buttonIcon}" ${!!wop.appendButtonDisabled ? 'disabled' : ''}></Button></template>` : '',
}
}
function buildRadioChildren(widget, formConfig) {
let wop = widget.options
const childTag = !!wop.buttonStyle ? 'RadioButton' : 'Radio'
const borderAttr = !!wop.border ? `border` : ''
const styleAttr = `style="display: ${wop.displayStyle}"`
return `<${childTag} v-for="(item, index) in ${wop.name}Options" :key="index" :label="item.value"
:disabled="item.disabled" ${borderAttr} ${styleAttr}>{{item.label}}</${childTag}>`
}
function buildCheckboxChildren(widget, formConfig) {
let wop = widget.options
const childTag = !!wop.buttonStyle ? 'CheckboxButton' : 'Checkbox'
const borderAttr = !!wop.border ? `border` : ''
const styleAttr = `style="display: ${wop.displayStyle}"`
return `<${childTag} v-for="(item, index) in ${wop.name}Options" :key="index" :label="item.value"
:disabled="item.disabled" ${borderAttr} ${styleAttr}>{{item.label}}</${childTag}>`
}
function buildSelectChildren(widget, formConfig) {
let wop = widget.options
const childTag = 'Option'
return `<${childTag} v-for="(item, index) in ${wop.name}Options" :key="index" :label="item.value"
:value="item.value" :disabled="item.disabled"></${childTag}>`
}
function buildDivider(widget, formConfig) {
let wop = widget.options
return {
size: !!wop.size ? `size="${wop.size}"` : '',
type: (!!wop.contentPosition && (wop.direction !== 'horizontal')) ? `type="vertical"` : `type="horizontal"`,
contentPosition: (!!wop.contentPosition && (wop.contentPosition !== 'center')) ? `orientation="${wop.contentPosition}"` : '',
}
}
function buildInputComposite(widget, formConfig) {
let wop = widget.options
return {
appendDiv: (wop.prependControl&&wop.prependControlType=='div') ?
`<div slot="prepend">
<Icon :type="${wop.prependControlIcon}" />${wop.prependControlText}
</div>`: '',
prependDiv: (wop.appendControl&&wop.appendControlType=='div') ?
`<div slot="append">
<Icon :type="${wop.appendControlIcon}" />${wop.appendControlText}
</div>`: '',
appendButton: (wop.prependControl&&wop.prependControlType=='button') ?
`<Button slot="prepend"
:disabled="${!!wop.disabled || !!wop.prependControlDisabled}"
:icon="${wop.prependControlIcon}"
>${wop.prependControlText}
</Button>`: '',
prependButton: (wop.appendControl&&wop.appendControlType=='button') ?
`<Button slot="append"
:disabled="${!!wop.disabled || !!wop.appendButtonDisabled}"
:icon="${wop.appendControlIcon}"
>${wop.appendControlText}
</Button>`: ''
}
}
const elTemplates = { //字段组件属性
'input': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, showPassword, placeholder, clearable,
maxlength, showWordLimit, prefixIcon, suffixIcon, appendButtonChild} = getElAttrs(widget, formConfig)
return `<Input ${vModel} ${readonly} ${disabled} ${size} ${type} ${showPassword} ${placeholder} ${clearable}
${maxlength} ${showWordLimit} ${prefixIcon} ${suffixIcon}>${appendButtonChild}</Input>`
},
'input-composite': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, showPassword, placeholder, clearable,
maxlength, showWordLimit, prefixIcon, suffixIcon, appendButtonChild} = getElAttrs(widget, formConfig)
const {appendDiv,prependDiv,appendButton,prependButton} =buildInputComposite(widget, formConfig);
return `<Input ${vModel} ${readonly} ${disabled} ${size} ${type} ${showPassword} ${placeholder} ${clearable}
${maxlength} ${showWordLimit} ${prefixIcon} ${suffixIcon}>
${appendDiv}
${prependDiv}
${appendButton}
${prependButton}
</Input>`
},
'textarea': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, showPassword, placeholder, rows, clearable,
maxlength, showWordLimit} = getElAttrs(widget, formConfig)
return `<Input type="textarea" ${vModel} ${readonly} ${disabled} ${size} ${type} ${showPassword} ${placeholder}
${rows} ${clearable} ${maxlength} ${showWordLimit}></Input>`
},
'number': (widget, formConfig) => {
const {vModel, disabled, size, type, showPassword, placeholder, controlsPosition, min, max, precision, step
} = getElAttrs(widget, formConfig)
return `<InputNumber ${vModel} class="full-width-input" ${disabled} ${size} ${type} ${showPassword}
${placeholder} ${controlsPosition} ${min} ${max} ${precision} ${step}></InputNumber>`
},
'radio': (widget, formConfig) => {
const {vModel, disabled, size} = getElAttrs(widget, formConfig)
const radioOptions = buildRadioChildren(widget, formConfig)
return `<RadioGroup ${vModel} ${disabled} ${size}>${radioOptions}</RadioGroup>`
},
'checkbox': (widget, formConfig) => {
const {vModel, disabled, size} = getElAttrs(widget, formConfig)
const checkboxOptions = buildCheckboxChildren(widget, formConfig)
return `<CheckboxGroup ${vModel} ${disabled} ${size}>${checkboxOptions}</CheckboxGroup>`
},
'select': (widget, formConfig) => {
const {vModel, disabled, size, clearable, filterable, allowCreate, defaultFirstOption, automaticDropdown,
multiple, multipleLimit, remote, placeholder} = getElAttrs(widget, formConfig)
const selectOptions = buildSelectChildren(widget, formConfig)
return `<Select ${vModel} class="full-width-input" ${disabled} ${size} ${clearable} ${filterable}
${allowCreate} ${defaultFirstOption} ${automaticDropdown} ${multiple} ${multipleLimit} ${placeholder}
${remote}>${selectOptions}</Select>`
},
'time': (widget, formConfig) => {
const {vModel, readonly, disabled, size, placeholder, clearable, format, editable
} = getElAttrs(widget, formConfig)
return `<TimePicker confirm ${vModel} class="full-width-input" ${readonly} ${disabled} ${size} ${format}
${placeholder} ${clearable} ${editable}></TimePicker>`
},
'time-range': (widget, formConfig) => {
const {vModel, readonly, disabled, size, startPlaceholder, endPlaceholder, clearable, format, editable
} = getElAttrs(widget, formConfig)
return `<TimePicker confirm type="range" ${vModel} class="full-width-input" ${readonly} ${disabled} ${size} ${format}
${startPlaceholder} ${endPlaceholder} ${clearable} ${editable}></TimePicker>`
},
'date': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, placeholder, clearable, format, valueFormat, editable
} = getElAttrs(widget, formConfig)
return `<DatePicker ${vModel} ${type} class="full-width-input" ${readonly} ${disabled} ${size} ${format}
${valueFormat} ${placeholder} ${clearable} ${editable}></DatePicker>`
},
'date-range': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, startPlaceholder, endPlaceholder, clearable, format, valueFormat, editable
} = getElAttrs(widget, formConfig)
return `<DatePicker is-range ${vModel} ${type} class="full-width-input" ${readonly} ${disabled} ${size} ${format}
${valueFormat} ${startPlaceholder} ${endPlaceholder} ${clearable} ${editable}></DatePicker>`
},
'switch': (widget, formConfig) => {
const {vModel, disabled, activeText, inactiveText, activeColor, inactiveColor, switchWidth
} = getElAttrs(widget, formConfig)
return `<iSwitch ${vModel} ${disabled} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor}
${switchWidth}></iSwitch>`
},
'rate': (widget, formConfig) => {
const {vModel, disabled, rateMax, allowHalf, showText} = getElAttrs(widget, formConfig)
return `<Rate ${vModel} ${disabled} ${rateMax} ${allowHalf} ${showText} ></Rate>`
},
'color': (widget, formConfig) => {
const {vModel, disabled, size
} = getElAttrs(widget, formConfig)
return `<ColorPicker ${vModel} ${disabled} ${size}></ColorPicker>`
},
'slider': (widget, formConfig) => {
const {vModel, disabled, sliderMin, sliderMax, sliderStep, sliderRange, sliderVertical
} = getElAttrs(widget, formConfig)
return `<Slider ${vModel} ${disabled} ${sliderMin} ${sliderMax} ${sliderStep} ${sliderRange}
></Slider>`
},
'picture-upload': (widget, formConfig) => {
const {vModel, disabled, uploadAction, withCredentials, multipleSelect, showFileList, limit,
fileAccept,fileTypes,fileMaxSize,
uploadTipSlotChild, pictureUploadIconChild} = getElAttrs(widget, formConfig)
let wop = widget.options
return `<Upload
:headers="${wop.name}UploadHeaders"
:data="${wop.name}UploadData"
:action="${uploadAction}"
paste
${disabled}
${uploadAction}
${withCredentials}
${multipleSelect}
${showFileList}
${fileAccept}
${fileTypes}
${fileMaxSize}
>${uploadTipSlotChild} ${pictureUploadIconChild}</Upload>`
},
'file-upload': (widget, formConfig) => {
const {vModel, disabled, uploadAction, withCredentials, multipleSelect, showFileList, limit,
fileAccept,fileTypes,fileMaxSize,
uploadTipSlotChild, fileUploadIconChild} = getElAttrs(widget, formConfig)
let wop = widget.options
return `<Upload
:headers="${wop.name}UploadHeaders"
:data="${wop.name}UploadData"
:action="${uploadAction}"
paste
${disabled}
${uploadAction}
${withCredentials}
${multipleSelect}
${showFileList}
${fileAccept}
${fileTypes}
${fileMaxSize}
>${uploadTipSlotChild} ${fileUploadIconChild}</Upload>`
},
'rich-editor': (widget, formConfig) => {
const {vModel, disabled, placeholder
} = getElAttrs(widget, formConfig)
return `<vue-editor ${vModel} ${disabled} ${placeholder}></vue-editor>`
},
'cascader': (widget, formConfig) => {
const {vModel, disabled, size, clearable, filterable, placeholder} = getElAttrs(widget, formConfig)
let wop = widget.options
const optionsAttr = `:options="${wop.name}Options"`
return `<Cascader ${vModel} class="full-width-input" ${optionsAttr} ${disabled} ${size} ${clearable}
${filterable} ${placeholder}></Cascader>`
},
'static-text': (widget, formConfig) => {
return `<div>${widget.options.textContent}</div>`
},
'html-text': (widget, formConfig) => {
return `<div v-html="${widget.options.htmlContent}"></div>`
},
'button': (widget, formConfig) => {
const {buttonType, buttonPlain, buttonRound, buttonCircle, buttonIcon, disabled} = getElAttrs(widget, formConfig)
return `<Button ${buttonType} ${buttonPlain} ${buttonRound} ${buttonCircle} ${buttonIcon}
${disabled}>${widget.options.label}</Button>`
},
'divider': (widget, formConfig) => {
const {contentPosition,size,type} = buildDivider(widget, formConfig)
return `<Divider ${type} ${size} ${contentPosition}></Divider>`
},
}
function buildFieldWidget(widget, formConfig) {
let wop = widget.options
const label = wop.labelHidden ? '' : wop.label
const labelWidthAttr = wop.labelHidden ? `:label-width="0"` : (!!wop.labelWidth ? `:label-width="${wop.labelWidth}"` : '')
const labelTooltipAttr = wop.labelTooltip ? `title="${wop.labelTooltip}"` : ''
const propAttr = `prop="${wop.name}"`
let classArray = []
!!wop.required && classArray.push('required')
!!wop.customClass && (wop.customClass.length > 0) && classArray.push(wop.customClass.join(' '))
if (!!wop.labelAlign) {
wop.labelAlign !== 'label-left-align' && classArray.push(wop.labelAlign)
} else {
//classArray.push(formConfig.labelAlign || 'label-left-align')
formConfig.labelAlign !== 'label-left-align' && classArray.push(formConfig.labelAlign)
}
const classAttr = (classArray.length > 0) ? `class="${classArray.join(' ')}"` : ''
let customLabelDom =
`<template #label><span class="custom-label">${wop.labelIconPosition === 'front' ?
(!!wop.labelTooltip ?
`<Tooltip content="${wop.labelTooltip}"><i class="${wop.labelIconClass}"></i></Tooltip>${wop.label}` :
`<i class="${wop.labelIconClass}"></i>${wop.label}`
)
:
(!!wop.labelTooltip ?
`${wop.label}<Tooltip content="${wop.labelTooltip}"><i class="${wop.labelIconClass}"></i></Tooltip>` :
`${wop.label}<i class="${wop.labelIconClass}"></i>`
)
}
</span></template>`
!wop.labelIconClass && (customLabelDom = '')
const fwDom = elTemplates[widget.type] ? elTemplates[widget.type](widget, formConfig) : null
const isFormItem = !!widget.formItemFlag
const vShowAttr = !!wop.hidden ? `v-show="false"` : ''
return isFormItem ?
`<FormItem label="${label}" ${labelWidthAttr} ${labelTooltipAttr} ${propAttr} ${classAttr} >
${customLabelDom}
${fwDom}
</FormItem>`
:
`<div class="static-content-item" ${vShowAttr}>${fwDom}</div>`
}
function genTemplate(formConfig, widgetList, vue3Flag = false) {
const submitAttr = !!vue3Flag ? `@submit.prevent` : `@submit.native.prevent`
let childrenList = []
widgetList.forEach(wgt => {
if (wgt.category === 'container') {
childrenList.push( buildContainerWidget(wgt, formConfig) )
} else {
childrenList.push( buildFieldWidget(wgt, formConfig) )
}
})
const formTemplate =
` <Form :model="${formConfig.modelName}" ref="${formConfig.refName}" :rules="${formConfig.rulesName}"
label-position="${formConfig.labelPosition}" :label-width="${formConfig.labelWidth}"
${submitAttr}>
${!!childrenList ? childrenList.join('\n') : ''}
</Form>`
return formTemplate
}
const genGlobalCSS = function (formConfig) {
const globalCssTemplate =`${formConfig.cssCode}`
return globalCssTemplate
}
const genScopedCSS = function (formConfig, vue3Flag = false) {
//const vDeep = !!vue3Flag ? `::v-deep` : `:deep`
const cssTemplate =
` div.table-container {
table.table-layout {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
td.table-cell {
display: table-cell;
height: 36px;
border: 1px solid #e1e2e3;
}
}
}
div.tab-container {
}
.label-left-align ${!!vue3Flag ? `:deep(.ivu-form-item-label)` : `::v-deep .ivu-form-item-label`} {
text-align: left;
}
.label-center-align ${!!vue3Flag ? `:deep(.ivu-form-item-label)` : `::v-deep .ivu-form-item-label`} {
text-align: center;
}
.label-right-align ${!!vue3Flag ? `:deep(.ivu-form-item-label)` : `::v-deep .ivu-form-item-label`} {
text-align: right;
}
.custom-label {
}
.static-content-item {
min-height: 20px;
display: flex;
align-items: center;
${!!vue3Flag ? `:deep(.ivu-divider-horizontal)` : `::v-deep .ivu-divider-horizontal`} {
margin: 0;
}
}`
return cssTemplate
}
export const genSFC = function (formConfig, widgetList, beautifier, vue3Flag = false) {
const html = beautifier.html(genTemplate(formConfig, widgetList, vue3Flag), beautifierOpts.html)
const js = beautifier.js(!!vue3Flag ? genVue3JS(formConfig, widgetList): genVue2JS(formConfig, widgetList), beautifierOpts.js)
const globalCss = beautifier.css(genGlobalCSS(formConfig), beautifierOpts.css)
const scopedCss = beautifier.css(genScopedCSS(formConfig, vue3Flag), beautifierOpts.css)
return `<!--
Codes Generated By VForm:
http://www.vform666.com
-->
<template>
${html}
</template>
<script>
${js}
</script>
<style lang="scss">
${globalCss}
</style>
<style lang="scss" scoped>
${scopedCss}
</style>`
}

View File

@ -0,0 +1,144 @@
import {isNotNull, traverseContainWidgets, traverseFieldWidgets} from "@/utils/util";
import {translate} from "@/utils/i18n";
import FormValidators, {getRegExp} from "@/utils/validators";
export function buildDefaultValueListFn(formConfig, widgetList, resultList) {
return function(fieldWidget) {
const fop = fieldWidget.options
const fd = fop.defaultValue
if (isNotNull(fd)) {
resultList.push(`${fop.name}: ${JSON.stringify(fd)},`)
} else {
resultList.push(`${fop.name}: null,`)
}
}
}
export function buildRulesListFn(formConfig, widgetList, resultList) {
return function(fieldWidget) {
const fop = fieldWidget.options
let fieldRules = []
if (!!fop.required) {
fieldRules.push(`{
required: true,
message: '${translate('render.hint.fieldRequired')}',
}`)
}
if (!!fop.validation) {
let vldName = fop.validation
if (!!FormValidators[vldName]) {
fieldRules.push(`{
pattern: ${eval( getRegExp(vldName) )},
trigger: ['blur', 'change'],
message: '${fop.validationHint}'
}`)
} else {
fieldRules.push(`{
pattern: '${eval(vldName)}',
trigger: ['blur', 'change'],
message: '${fop.validationHint}'
}`)
}
}
//TODO: 自定义校验函数
fieldRules.length > 0 && resultList.push(`${fop.name}: [${fieldRules.join(',')}],`)
}
}
export function buildFieldOptionsFn(formConfig, widgetList, resultList) {
return function(fieldWidget) {
const fop = fieldWidget.options
const ft = fieldWidget.type
if ((ft === 'radio') || (ft === 'checkbox') || (ft === 'select') || (ft === 'cascader')) {
resultList.push(`${fop.name}Options: ${JSON.stringify(fop.optionItems)},`)
}
}
}
export function buildUploadDataFn(formConfig, widgetList, resultList) {
return function(fieldWidget) {
const fop = fieldWidget.options
const ft = fieldWidget.type
if ((ft === 'picture-upload') || (ft === 'file-upload')) {
resultList.push(`${fop.name}FileList: [],`)
resultList.push(`${fop.name}UploadHeaders: {},`)
resultList.push(`${fop.name}UploadData: {},`)
}
}
}
export function buildActiveTabs(formConfig, widgetList) {
let resultList = []
const handlerFn = function (cw) {
const cop = cw.options
const ct = cw.type
if (ct === 'tab') {
cw.tabs.length > 0 && resultList.push(`'${cop.name}ActiveTab': '${cw.tabs[0].options.name}',`)
}
}
traverseContainWidgets(widgetList, handlerFn)
return resultList
}
export const genVue2JS = function (formConfig, widgetList) {
let defaultValueList = []
let rulesList = []
let fieldOptions = []
let uploadData = []
traverseFieldWidgets(widgetList, (widget) => {
buildDefaultValueListFn(formConfig, widgetList, defaultValueList)(widget)
buildRulesListFn(formConfig, widgetList, rulesList)(widget)
buildFieldOptionsFn(formConfig, widgetList, fieldOptions)(widget)
buildUploadDataFn(formConfig, widgetList, uploadData)(widget)
})
const activeTabs = buildActiveTabs(formConfig, widgetList)
const v2JSTemplate =
` export default {
components: {},
props: {},
data() {
return {
${formConfig.modelName}: {
${defaultValueList.join('\n')}
},
${formConfig.rulesName}: {
${rulesList.join('\n')}
},
${activeTabs.join('\n')}
${fieldOptions.join('\n')}
${uploadData.join('\n')}
}
},
computed: {},
watch: {},
created() {
},
mounted() {
},
methods: {
submitForm() {
this.$refs['vForm'].validate(valid => {
if (!valid) return
//TODO: 提交表单
})
},
resetForm() {
this.$refs['vForm'].resetFields()
}
}
}`
return v2JSTemplate
}

View File

@ -0,0 +1,70 @@
import {
buildActiveTabs,
buildDefaultValueListFn,
buildFieldOptionsFn,
buildRulesListFn, buildUploadDataFn
} from "@/utils/vue2js-generator";
import {traverseFieldWidgets} from "@/utils/util";
export const genVue3JS = function (formConfig, widgetList) {
let defaultValueList = []
let rulesList = []
let fieldOptions = []
let uploadData = []
traverseFieldWidgets(widgetList, (widget) => {
buildDefaultValueListFn(formConfig, widgetList, defaultValueList)(widget)
buildRulesListFn(formConfig, widgetList, rulesList)(widget)
buildFieldOptionsFn(formConfig, widgetList, fieldOptions)(widget)
buildUploadDataFn(formConfig, widgetList, uploadData)(widget)
})
const activeTabs = buildActiveTabs(formConfig, widgetList)
const v3JSTemplate =
` import { defineComponent, toRefs, reactive, getCurrentInstance } from 'vue'
export default defineComponent({
components: {},
props: {},
setup() {
const state = reactive({
${formConfig.modelName}: {
${defaultValueList.join('\n')}
},
${formConfig.rulesName}: {
${rulesList.join('\n')}
},
${activeTabs.join('\n')}
${fieldOptions.join('\n')}
${uploadData.join('\n')}
})
const instance = getCurrentInstance()
const submitForm = () => {
instance.ctx.$refs['vForm'].validate(valid => {
if (!valid) return
//TODO: 提交表单
})
}
const resetForm = () => {
instance.ctx.$refs['vForm'].resetFields()
}
return {
...toRefs(state),
submitForm,
resetForm
}
}
})`
return v3JSTemplate
}

38
src/main-iview.js Normal file
View File

@ -0,0 +1,38 @@
import 'babel-polyfill'
import './utils/debug-console'
import Vue from 'vue'
import axios from "axios";
import App from './App-iview.vue'
import ViewUI from 'view-design';
import './utils/directive'
import './icons'
import ContainerWidget from "@/components-iview/form-designer/form-widget/container-widget";
import ContainerItem from "@/components-iview/form-render/container-item";
import 'view-design/dist/styles/iview.css';
import '@/styles/index.scss'
import '@/iconfont/iconfont.css'
import {i18n} from '@/components-iview/utils/i18n.js'
/* 递归组件如需在递归组件的嵌套组件中使用,必须注册为全局组件,原因不明?? begin */
Vue.component('container-widget', ContainerWidget)
Vue.component('container-item', ContainerItem)
/* end */
Vue.locale = () => {};
Vue.use(ViewUI, {size:'small'});
if (typeof window !== 'undefined') {
window.axios = axios
}
Vue.config.productionTip = false
new Vue({
i18n,
el: "#app",
render: h => h(App),
})