parent
9462dfb9e9
commit
af757023e5
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
10
package.json
10
package.json
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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'
|
||||
/* 启用此行后webpack打包回生成很多动态加载的js文件,不便于部署,故禁用!!
|
||||
特别提示:禁用此行后,需要调用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>
|
|
@ -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属性变成非响应式 */
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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 $--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
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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',
|
||||
},
|
||||
|
||||
]
|
|
@ -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) //清除数组必须用splice,length=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) //清除数组必须用splice,length=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) { //tabIndex从0计数
|
||||
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>
|
|
@ -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>
|
|
@ -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, /* 不能引入designer对象,VFormRender组件脱离表单设计器运行!! */
|
||||
|
||||
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 //inject注入的formModel不是响应式的,直接赋值在其他组件拿不到最新值!!
|
||||
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>
|
|
@ -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']
|
||||
},
|
||||
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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',
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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: '表单事件处理',
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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: '组件类型不是子表单',
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>`
|
||||
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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),
|
||||
})
|
Loading…
Reference in New Issue