Vue 3版本初次提交,继续测试中。
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
6
.idea/misc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/variant-form3-vite.iml" filepath="$PROJECT_DIR$/variant-form3-vite.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
7
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/tmp" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["johnsoncodehk.volar"]
|
||||
}
|
7
README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
|
13
index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
10
jsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"######": "本文件用于解决IDEA无法识别Vue项目@符号的问题",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
3154
package-lock.json
generated
Normal file
38
package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "variant-form3",
|
||||
"version": "3.0.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"serve": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.24.0",
|
||||
"clipboard": "^2.0.8",
|
||||
"core-js": "^3.6.5",
|
||||
"element-plus": "^1.2.0-beta.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"mitt": "^3.0.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-i18n": "^9.2.0-beta.23",
|
||||
"vue3-quill": "^0.2.6",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.0",
|
||||
"@vitejs/plugin-vue": "^2.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.3",
|
||||
"ace-builds": "^1.4.12",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"mvdir": "^1.0.21",
|
||||
"sass": "^1.45.0",
|
||||
"vite": "^2.7.2",
|
||||
"vite-plugin-svg-icons": "^1.0.5"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
40
src/App.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<VFormDesigner />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VFormDesigner from './components/form-designer/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
VFormDesigner,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formJson: {"widgetList":[],"formConfig":{"modelName":"formData","refName":"vForm","rulesName":"rules","labelWidth":80,"labelPosition":"left","size":"","labelAlign":"label-left-align","cssCode":"","customClass":"","functions":"","layoutType":"PC","onFormCreated":"","onFormMounted":"","onFormDataChange":"","onFormValidate":""}},
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
BIN
src/assets/ft-images/t1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/ft-images/t2.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/ft-images/t3.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
src/assets/ft-images/t4.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
src/assets/ft-images/t5.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
src/assets/ft-images/t6.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/ft-images/t7.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
src/assets/ft-images/t8.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
src/assets/vform-logo.png
Normal file
After Width: | Height: | Size: 453 B |
135
src/components/code-editor/index.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<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: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
//required: true
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'javascript'
|
||||
},
|
||||
userWorker: { //是否开启语法检查,默认开启
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
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())
|
||||
*/
|
||||
|
||||
this.$emit('update:modelValue', this.aceEditor.getValue())
|
||||
})
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
aceEditor: null,
|
||||
themePath: 'ace/theme/sqlserver', // 不导入 webpack-resolver,该模块路径会报错
|
||||
modePath: 'ace/mode/javascript', // 同上
|
||||
codeValue: this.modelValue
|
||||
}
|
||||
},
|
||||
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>
|
929
src/components/form-designer/designer.js
Normal file
@ -0,0 +1,929 @@
|
||||
/**
|
||||
* author: vformAdmin
|
||||
* email: vdpadmin@163.com
|
||||
* website: https://www.vform666.com
|
||||
* date: 2021.08.18
|
||||
* remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
|
||||
*/
|
||||
|
||||
import {deepClone, generateId, overwriteObj} from "@/utils/util"
|
||||
import {containers, advancedFields, basicFields, customFields} from "@/components/form-designer/widget-panel/widgetsConfig.js"
|
||||
import {VARIANT_FORM_VERSION} from "@/utils/config"
|
||||
import eventBus from "@/utils/event-bus"
|
||||
|
||||
export function createDesigner(vueInstance) {
|
||||
let defaultFormConfig = {
|
||||
modelName: 'formData',
|
||||
refName: 'vForm',
|
||||
rulesName: 'rules',
|
||||
labelWidth: 80,
|
||||
labelPosition: 'left',
|
||||
size: '',
|
||||
labelAlign: 'label-left-align',
|
||||
cssCode: '',
|
||||
customClass: '',
|
||||
functions: '',
|
||||
layoutType: 'PC',
|
||||
|
||||
onFormCreated: '',
|
||||
onFormMounted: '',
|
||||
onFormDataChange: '',
|
||||
}
|
||||
|
||||
return {
|
||||
widgetList: [],
|
||||
formConfig: {cssCode: ''},
|
||||
|
||||
selectedId: null,
|
||||
selectedWidget: null,
|
||||
selectedWidgetName: null, //选中组件名称(唯一)
|
||||
vueInstance: vueInstance,
|
||||
|
||||
formWidget: null, //表单设计容器
|
||||
|
||||
cssClassList: [], //自定义样式列表
|
||||
|
||||
historyData: {
|
||||
index: -1, //index: 0,
|
||||
maxStep: 20,
|
||||
steps: [],
|
||||
},
|
||||
|
||||
initDesigner() {
|
||||
this.widgetList = []
|
||||
this.formConfig = deepClone(defaultFormConfig)
|
||||
|
||||
//输出版本信息和语雀链接
|
||||
console.info(`%cVariantForm %cVer${VARIANT_FORM_VERSION} %chttps://www.yuque.com/variantdev/vform`,
|
||||
"color:#409EFF;font-size: 22px;font-weight:bolder",
|
||||
"color:#999;font-size: 12px",
|
||||
"color:#333"
|
||||
)
|
||||
|
||||
this.initHistoryData()
|
||||
},
|
||||
|
||||
clearDesigner(skipHistoryChange) {
|
||||
let emptyWidgetListFlag = (this.widgetList.length === 0)
|
||||
this.widgetList = []
|
||||
this.selectedId = null
|
||||
this.selectedWidgetName = null
|
||||
this.selectedWidget = {} //this.selectedWidget = null
|
||||
overwriteObj(this.formConfig, defaultFormConfig) //
|
||||
|
||||
if (!!skipHistoryChange) {
|
||||
//什么也不做!!
|
||||
} else if (!emptyWidgetListFlag) {
|
||||
this.emitHistoryChange()
|
||||
} else {
|
||||
this.saveCurrentHistoryStep()
|
||||
}
|
||||
},
|
||||
|
||||
loadPresetCssCode(preCssCode) {
|
||||
if ((this.formConfig.cssCode === '') && !!preCssCode) {
|
||||
this.formConfig.cssCode = preCssCode
|
||||
}
|
||||
},
|
||||
|
||||
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, newLabel) {
|
||||
this.selectedWidgetName = newName
|
||||
//selectedWidget.options.name = newName //此行多余
|
||||
if (!!newLabel && (Object.keys(selectedWidget.options).indexOf('label') > -1)) {
|
||||
selectedWidget.options.label = newLabel
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
/**
|
||||
* 追加表格新行
|
||||
* @param widget
|
||||
*/
|
||||
appendTableRow(widget) {
|
||||
let rowIdx = widget.rows.length//确定插入行位置
|
||||
let newRow = deepClone(widget.rows[widget.rows.length - 1])
|
||||
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
|
||||
col.widgetList.length = 0
|
||||
})
|
||||
widget.rows.splice(rowIdx, 0, newRow)
|
||||
|
||||
this.emitHistoryChange()
|
||||
},
|
||||
|
||||
/**
|
||||
* 追加表格新列
|
||||
* @param widget
|
||||
*/
|
||||
appendTableCol(widget) {
|
||||
let colIdx = widget.rows[0].cols.length //确定插入列位置
|
||||
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
|
||||
newCol.widgetList.length = 0
|
||||
row.cols.splice(colIdx, 0, newCol)
|
||||
})
|
||||
|
||||
this.emitHistoryChange()
|
||||
},
|
||||
|
||||
insertTableRow(widget, insertPos, cloneRowIdx, curCol, aboveFlag) {
|
||||
let newRowIdx = !!aboveFlag ? insertPos : (insertPos + 1) //初步确定插入行位置
|
||||
if (!aboveFlag) { //继续向下寻找同列第一个未被合并的单元格
|
||||
let tmpRowIdx = newRowIdx
|
||||
let rowFoundFlag = false
|
||||
while (tmpRowIdx < widget.rows.length) {
|
||||
if (!widget.rows[tmpRowIdx].cols[curCol].merged) {
|
||||
newRowIdx = tmpRowIdx
|
||||
rowFoundFlag = true
|
||||
break
|
||||
} else {
|
||||
tmpRowIdx++
|
||||
}
|
||||
}
|
||||
|
||||
if (!rowFoundFlag) {
|
||||
newRowIdx = widget.rows.length
|
||||
}
|
||||
}
|
||||
|
||||
let newRow = 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
|
||||
col.widgetList.length = 0
|
||||
})
|
||||
widget.rows.splice(newRowIdx, 0, newRow)
|
||||
|
||||
let colNo = 0
|
||||
while ((newRowIdx < widget.rows.length - 1) && (colNo < widget.rows[0].cols.length)) { //越界判断
|
||||
const cellOfNextRow = widget.rows[newRowIdx + 1].cols[colNo]
|
||||
const rowMerged = cellOfNextRow.merged //确定插入位置下一行的单元格是否为合并单元格
|
||||
if (!!rowMerged) {
|
||||
let rowArray = widget.rows
|
||||
let unMergedCell = {}
|
||||
let startRowIndex = null
|
||||
for (let i = newRowIdx; i >= 0; i--) { //查找该行已合并的主单元格
|
||||
if (!rowArray[i].cols[colNo].merged && (rowArray[i].cols[colNo].options.rowspan > 1)) {
|
||||
startRowIndex = i
|
||||
unMergedCell = rowArray[i].cols[colNo]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!!unMergedCell.options) { //如果有符合条件的unMergedCell
|
||||
let newRowspan = unMergedCell.options.rowspan + 1
|
||||
this.setPropsOfMergedRows(widget.rows, startRowIndex, colNo, unMergedCell.options.colspan, newRowspan)
|
||||
colNo += unMergedCell.options.colspan
|
||||
} else {
|
||||
colNo += 1
|
||||
}
|
||||
} else {
|
||||
//colNo += 1
|
||||
colNo += cellOfNextRow.options.colspan || 1
|
||||
}
|
||||
}
|
||||
|
||||
this.emitHistoryChange()
|
||||
},
|
||||
|
||||
insertTableCol(widget, insertPos, curRow, leftFlag) {
|
||||
let newColIdx = !!leftFlag ? insertPos : (insertPos + 1) //初步确定插入列位置
|
||||
if (!leftFlag) { //继续向右寻找同行第一个未被合并的单元格
|
||||
let tmpColIdx = newColIdx
|
||||
let colFoundFlag = false
|
||||
while (tmpColIdx < widget.rows[curRow].cols.length) {
|
||||
if (!widget.rows[curRow].cols[tmpColIdx].merged) {
|
||||
newColIdx = tmpColIdx
|
||||
colFoundFlag = true
|
||||
break
|
||||
} else {
|
||||
tmpColIdx++
|
||||
}
|
||||
|
||||
if (!colFoundFlag) {
|
||||
newColIdx = widget.rows[curRow].cols.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
newCol.widgetList.length = 0
|
||||
row.cols.splice(newColIdx, 0, newCol)
|
||||
})
|
||||
|
||||
let rowNo = 0
|
||||
while((newColIdx < widget.rows[0].cols.length - 1) && (rowNo < widget.rows.length)) { //越界判断
|
||||
const cellOfNextCol = widget.rows[rowNo].cols[newColIdx + 1]
|
||||
const colMerged = cellOfNextCol.merged //确定插入位置右侧列的单元格是否为合并单元格
|
||||
if (!!colMerged) {
|
||||
let colArray = widget.rows[rowNo].cols
|
||||
let unMergedCell = {}
|
||||
let startColIndex = null
|
||||
for (let i = newColIdx; i >= 0; i--) { //查找该行已合并的主单元格
|
||||
if (!colArray[i].merged && (colArray[i].options.colspan > 1)) {
|
||||
startColIndex = i
|
||||
unMergedCell = colArray[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!!unMergedCell.options) { //如果有符合条件的unMergedCell
|
||||
let newColspan = unMergedCell.options.colspan + 1
|
||||
this.setPropsOfMergedCols(widget.rows, rowNo, startColIndex, newColspan, unMergedCell.options.rowspan)
|
||||
rowNo += unMergedCell.options.rowspan
|
||||
} else {
|
||||
rowNo += 1
|
||||
}
|
||||
} else {
|
||||
//rowNo += 1
|
||||
rowNo += cellOfNextCol.options.rowspan || 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++) {
|
||||
rowArray[i].cols[j].merged = false;
|
||||
rowArray[i].cols[j].options.rowspan = 1
|
||||
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++) {
|
||||
rowArray[i].cols[j].merged = false;
|
||||
rowArray[i].cols[j].options.rowspan = 1
|
||||
rowArray[i].cols[j].options.colspan = 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
|
||||
let remainedColIdx = !!leftFlag ? curCol - 1 : curCol
|
||||
if (!!leftFlag) { //继续向左寻找同行未被合并的第一个单元格
|
||||
let tmpColIdx = remainedColIdx
|
||||
while (tmpColIdx >= 0) {
|
||||
if (!rowArray[curRow].cols[tmpColIdx].merged) {
|
||||
remainedColIdx = tmpColIdx
|
||||
break;
|
||||
} else {
|
||||
tmpColIdx--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
let remainedRowIdx = !!aboveFlag ? curRow - 1 : curRow
|
||||
if (!!aboveFlag) { //继续向上寻找同列未被合并的第一个单元格
|
||||
let tmpRowIdx = remainedRowIdx
|
||||
while (tmpRowIdx >= 0) {
|
||||
if (!rowArray[tmpRowIdx].cols[curCol].merged) {
|
||||
remainedRowIdx = tmpRowIdx
|
||||
break;
|
||||
} else {
|
||||
tmpRowIdx--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) { //需考虑删除的是合并列!!
|
||||
let onlyOneColFlag = true
|
||||
rowArray.forEach(ri => {
|
||||
if (ri.cols[0].options.colspan !== rowArray[0].cols.length) {
|
||||
onlyOneColFlag = false
|
||||
}
|
||||
})
|
||||
//仅剩一列则不可删除!!
|
||||
if (onlyOneColFlag) {
|
||||
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.lastColCannotBeDeleted'))
|
||||
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) => {
|
||||
rItem.cols.splice(colIndex, startColspan)
|
||||
})
|
||||
|
||||
this.emitHistoryChange()
|
||||
},
|
||||
|
||||
deleteTableWholeRow(rowArray, rowIndex) { //需考虑删除的是合并行!!
|
||||
let onlyOneRowFlag = true
|
||||
rowArray[0].cols.forEach(ci => {
|
||||
if (ci.options.rowspan !== rowArray.length) {
|
||||
onlyOneRowFlag = false
|
||||
}
|
||||
})
|
||||
//仅剩一行则不可删除!!
|
||||
if (onlyOneRowFlag) {
|
||||
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.lastRowCannotBeDeleted'))
|
||||
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 allWidgets = [...containers, ...basicFields, ...advancedFields, ...customFields]
|
||||
let foundCon = null
|
||||
allWidgets.forEach(con => {
|
||||
if (!!con.category && !!con.type && (con.type === typeName)) {
|
||||
foundCon = con
|
||||
}
|
||||
})
|
||||
|
||||
return foundCon
|
||||
},
|
||||
|
||||
getFieldWidgetByType(typeName) {
|
||||
let allWidgets = [...containers, ...basicFields, ...advancedFields, ...customFields]
|
||||
let foundWidget = null
|
||||
allWidgets.forEach(widget => {
|
||||
if (!!!widget.category && !!widget.type && (widget.type === typeName)) {
|
||||
foundWidget = widget
|
||||
}
|
||||
})
|
||||
|
||||
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 { //其他容器组件不支持clone操作
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
moveUpWidget(parentList, indexOfParentList) {
|
||||
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) {
|
||||
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 tempId = generateId()
|
||||
newWidget.id = newWidget.type.replace(/-/g, '') + tempId
|
||||
newWidget.options.name = newWidget.id
|
||||
newWidget.options.label = newWidget.type.toLowerCase()
|
||||
|
||||
delete newWidget.displayName
|
||||
return newWidget
|
||||
},
|
||||
|
||||
copyNewContainerWidget(origin) {
|
||||
let newCon = deepClone(origin)
|
||||
newCon.id = newCon.type.replace(/-/g, '') + 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
|
||||
let activeTab = this.selectedWidget.tabs[0]
|
||||
this.selectedWidget.tabs.forEach(tabPane => {
|
||||
if (!!tabPane.options.active) {
|
||||
activeTab = tabPane
|
||||
}
|
||||
})
|
||||
|
||||
!!activeTab && activeTab.widgetList.push(newWidget)
|
||||
} else if (!!this.selectedWidget && !!this.selectedWidget.widgetList) {
|
||||
this.selectedWidget.widgetList.push(newWidget)
|
||||
} else {
|
||||
this.widgetList.push(newWidget)
|
||||
}
|
||||
|
||||
this.setSelected(newWidget)
|
||||
this.emitHistoryChange()
|
||||
},
|
||||
|
||||
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) => {
|
||||
spanSum += col.options.span
|
||||
})
|
||||
|
||||
if (spanSum >= 24) {
|
||||
//this.$message.info('列栅格之和超出24')
|
||||
console.log('列栅格之和超出24')
|
||||
gridWidget.cols.push(newGridCol)
|
||||
} else {
|
||||
newGridCol.options.span = (24 - spanSum) > 12 ? 12 : (24 - spanSum)
|
||||
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)
|
||||
eventBus.$emit(evtName, evtData)
|
||||
},
|
||||
|
||||
handleEvent(evtName, callback) { //用于兄弟组件接收事件
|
||||
//this.vueInstance.$on(evtName, (data) => callback(data))
|
||||
eventBus.$on(evtName, (data) => callback(data))
|
||||
},
|
||||
|
||||
setCssClassList(cssClassList) {
|
||||
this.cssClassList = cssClassList
|
||||
},
|
||||
|
||||
getCssClassList() {
|
||||
return this.cssClassList
|
||||
},
|
||||
|
||||
registerFormWidget(formWidget) {
|
||||
this.formWidget = formWidget
|
||||
},
|
||||
|
||||
initHistoryData() {
|
||||
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,108 @@
|
||||
<!--
|
||||
/**
|
||||
* author: vformAdmin
|
||||
* email: vdpadmin@163.com
|
||||
* website: https://www.vform666.com
|
||||
* date: 2021.08.18
|
||||
* remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="container-wrapper" :class="[customClass]">
|
||||
<slot></slot>
|
||||
|
||||
<div class="container-action" v-if="designer.selectedId === widget.id && !widget.internal">
|
||||
<i :title="i18nt('designer.hint.selectParentWidget')" @click.stop="selectParentWidget(widget)">
|
||||
<svg-icon icon-class="el-back" />
|
||||
</i>
|
||||
<i class="el-icon-top" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveUpWidget')"
|
||||
@click.stop="moveUpWidget()"></i>
|
||||
<i class="el-icon-bottom" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveDownWidget')"
|
||||
@click.stop="moveDownWidget()"></i>
|
||||
<i v-if="widget.type === 'table'" class="iconfont icon-insertrow" :title="i18nt('designer.hint.insertRow')"
|
||||
@click.stop="appendTableRow(widget)"></i>
|
||||
<i v-if="widget.type === 'table'" class="iconfont icon-insertcolumn" :title="i18nt('designer.hint.insertColumn')"
|
||||
@click.stop="appendTableCol(widget)"></i>
|
||||
<i class="el-icon-copy-document" v-if="(widget.type === 'grid') || (widget.type === 'table')"
|
||||
:title="i18nt('designer.hint.cloneWidget')" @click.stop="cloneContainer(widget)"></i>
|
||||
<i class="el-icon-delete" :title="i18nt('designer.hint.remove')" @click.stop="removeWidget"></i>
|
||||
</div>
|
||||
|
||||
<div class="drag-handler" v-if="designer.selectedId === widget.id && !widget.internal">
|
||||
<i class="el-icon-rank" :title="i18nt('designer.hint.dragHandler')"></i>
|
||||
<i>{{i18n2t(`designer.widgetLabel.${widget.type}`, `extension.widgetLabel.${widget.type}`)}}</i>
|
||||
<i v-if="widget.options.hidden === true" class="iconfont icon-hide"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
import containerMixin from "@/components/form-designer/form-widget/container-widget/containerMixin";
|
||||
|
||||
export default {
|
||||
name: "container-wrapper",
|
||||
mixins: [i18n, containerMixin],
|
||||
props: {
|
||||
widget: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
},
|
||||
computed: {
|
||||
customClass() {
|
||||
return !!this.widget.options.customClass ? this.widget.options.customClass.join(' ') : ''
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</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: -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,85 @@
|
||||
export default {
|
||||
methods: {
|
||||
appendTableRow(widget) {
|
||||
this.designer.appendTableRow(widget)
|
||||
},
|
||||
|
||||
appendTableCol(widget) {
|
||||
this.designer.appendTableCol(widget)
|
||||
},
|
||||
|
||||
onContainerDragAdd(evt, subList) {
|
||||
const newIndex = evt.newIndex
|
||||
if (!!subList[newIndex]) {
|
||||
this.designer.setSelected( subList[newIndex] )
|
||||
}
|
||||
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
onContainerDragUpdate() {
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
checkContainerMove(evt) {
|
||||
return this.designer.checkWidgetMove(evt)
|
||||
},
|
||||
|
||||
selectWidget(widget) {
|
||||
this.designer.setSelected(widget)
|
||||
},
|
||||
|
||||
selectParentWidget() {
|
||||
if (this.parentWidget) {
|
||||
this.designer.setSelected(this.parentWidget)
|
||||
} else {
|
||||
this.designer.clearSelected()
|
||||
}
|
||||
},
|
||||
|
||||
moveUpWidget() {
|
||||
this.designer.moveUpWidget(this.parentList, this.indexOfParentList)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
moveDownWidget() {
|
||||
this.designer.moveDownWidget(this.parentList, this.indexOfParentList)
|
||||
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()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<el-col v-if="widget.type === 'grid-col'" class="grid-cell" v-bind="layoutProps"
|
||||
:class="[selected ? 'selected' : '', customClass]" :style="colHeightStyle"
|
||||
:key="widget.id" @click.stop="selectWidget(widget)">
|
||||
<draggable :list="widget.widgetList" item-key="id" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
|
||||
tag="transition-group" :component-data="{name: 'fade'}"
|
||||
handle=".drag-handler" @end="(evt) => onGridDragEnd(evt, widget.widgetList)"
|
||||
@add="(evt) => onGridDragAdd(evt, widget.widgetList)"
|
||||
@update="onGridDragUpdate" :move="checkContainerMove">
|
||||
<template #item="{ element: subWidget, index: swIdx }">
|
||||
<div class="form-widget-list">
|
||||
<template v-if="'container' === subWidget.category">
|
||||
<component :is="subWidget.type + '-widget'" :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
|
||||
:index-of-parent-list="swIdx" :parent-widget="widget"></component>
|
||||
</template>
|
||||
<template v-else>
|
||||
<component :is="subWidget.type + '-widget'" :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
|
||||
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></component>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<div class="grid-col-action" v-if="designer.selectedId === widget.id && widget.type === 'grid-col'">
|
||||
<i class="el-icon-back" :title="i18nt('designer.hint.selectParentWidget')"
|
||||
@click.stop="selectParentWidget(widget)"></i>
|
||||
<i class="el-icon-top" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveUpWidget')"
|
||||
@click.stop="moveUpWidget()"></i>
|
||||
<i class="el-icon-bottom" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveDownWidget')"
|
||||
@click.stop="moveDownWidget()"></i>
|
||||
<i class="el-icon-copy-document" :title="i18nt('designer.hint.cloneWidget')" @click.stop="cloneGridCol(widget)"></i>
|
||||
<i class="el-icon-delete" :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>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Draggable from 'vuedraggable'
|
||||
import i18n from "@/utils/i18n";
|
||||
import FieldComponents from '@/components/form-designer/form-widget/field-widget/index'
|
||||
|
||||
export default {
|
||||
name: "GridColWidget",
|
||||
componentName: "GridColWidget",
|
||||
mixins: [i18n],
|
||||
components: {
|
||||
Draggable,
|
||||
...FieldComponents,
|
||||
},
|
||||
props: {
|
||||
widget: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
colHeight: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
layoutProps: {
|
||||
span: this.widget.options.span || 12,
|
||||
// md: this.widget.options.md || 12,
|
||||
// sm: this.widget.options.sm || 12,
|
||||
// xs: this.widget.options.xs || 12,
|
||||
offset: this.widget.options.offset || 0,
|
||||
push: this.widget.options.push || 0,
|
||||
pull: this.widget.options.pull || 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selected() {
|
||||
return this.widget.id === this.designer.selectedId
|
||||
},
|
||||
|
||||
customClass() {
|
||||
return this.widget.options.customClass || ''
|
||||
},
|
||||
|
||||
colHeightStyle() {
|
||||
return !!this.colHeight ? {height: this.colHeight + 'px'} : {}
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
'designer.formConfig.layoutType': {
|
||||
handler(val) {
|
||||
if (!!this.widget.options.responsive) {
|
||||
if (val === 'H5') {
|
||||
this.layoutProps.span = this.widget.options.xs || 12
|
||||
} else if (val === 'Pad') {
|
||||
this.layoutProps.span = this.widget.options.sm || 12
|
||||
} else {
|
||||
this.layoutProps.span = this.widget.options.md || 12
|
||||
}
|
||||
} else {
|
||||
this.layoutProps.span = this.widget.options.span || 12
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'widget.options.responsive': {
|
||||
handler(val) {
|
||||
let lyType = this.designer.formConfig.layoutType
|
||||
if (!!val) {
|
||||
if (lyType === 'H5') {
|
||||
this.layoutProps.span = this.widget.options.xs || 12
|
||||
} else if (lyType === 'Pad') {
|
||||
this.layoutProps.span = this.widget.options.sm || 12
|
||||
} else {
|
||||
this.layoutProps.span = this.widget.options.md || 12
|
||||
}
|
||||
} else {
|
||||
this.layoutProps.span = this.widget.options.span || 12
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'widget.options.span': {
|
||||
handler(val) {
|
||||
this.layoutProps.span = val
|
||||
}
|
||||
},
|
||||
|
||||
'widget.options.md': {
|
||||
handler(val) {
|
||||
this.layoutProps.span = val
|
||||
}
|
||||
},
|
||||
|
||||
'widget.options.sm': {
|
||||
handler(val) {
|
||||
this.layoutProps.span = val
|
||||
}
|
||||
},
|
||||
|
||||
'widget.options.xs': {
|
||||
handler(val) {
|
||||
this.layoutProps.span = val
|
||||
}
|
||||
},
|
||||
|
||||
'widget.options.offset': {
|
||||
handler(val) {
|
||||
this.layoutProps.offset = val
|
||||
}
|
||||
},
|
||||
|
||||
'widget.options.push': {
|
||||
handler(val) {
|
||||
this.layoutProps.push = val
|
||||
}
|
||||
},
|
||||
|
||||
'widget.options.pull': {
|
||||
handler(val) {
|
||||
this.layoutProps.pull = val
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
created() {
|
||||
this.initLayoutProps()
|
||||
},
|
||||
methods: {
|
||||
initLayoutProps() {
|
||||
if (!!this.widget.options.responsive) {
|
||||
let lyType = this.designer.formConfig.layoutType
|
||||
if (lyType === 'H5') {
|
||||
this.layoutProps.span = this.widget.options.xs || 12
|
||||
} else if (lyType === 'Pad') {
|
||||
this.layoutProps.span = this.widget.options.sm || 12
|
||||
} else {
|
||||
this.layoutProps.span = this.widget.options.md || 12
|
||||
}
|
||||
} else {
|
||||
this.layoutProps.spn = this.widget.options.span
|
||||
}
|
||||
},
|
||||
|
||||
onGridDragEnd(evt, subList) {
|
||||
//
|
||||
},
|
||||
|
||||
onGridDragAdd(evt, subList) {
|
||||
const newIndex = evt.newIndex
|
||||
if (!!subList[newIndex]) {
|
||||
this.designer.setSelected( subList[newIndex] )
|
||||
}
|
||||
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
onGridDragUpdate() {
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
selectWidget(widget) {
|
||||
console.log('id: ' + widget.id)
|
||||
this.designer.setSelected(widget)
|
||||
},
|
||||
|
||||
checkContainerMove(evt) {
|
||||
return this.designer.checkWidgetMove(evt)
|
||||
},
|
||||
|
||||
selectParentWidget() {
|
||||
if (this.parentWidget) {
|
||||
this.designer.setSelected(this.parentWidget)
|
||||
} else {
|
||||
this.designer.clearSelected()
|
||||
}
|
||||
},
|
||||
|
||||
moveUpWidget() {
|
||||
this.designer.moveUpWidget(this.parentList, this.indexOfParentList)
|
||||
},
|
||||
|
||||
moveDownWidget() {
|
||||
this.designer.moveDownWidget(this.parentList, this.indexOfParentList)
|
||||
},
|
||||
|
||||
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;
|
||||
//margin: 6px 0; /* 设置了margin,栅格列的offset、push、pull会失效!! */
|
||||
padding: 3px;
|
||||
outline: 1px dashed #336699;
|
||||
position: relative;
|
||||
|
||||
.form-widget-list {
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.grid-col-action{
|
||||
position: absolute;
|
||||
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;
|
||||
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: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,89 @@
|
||||
<!--
|
||||
/**
|
||||
* author: vformAdmin
|
||||
* email: vdpadmin@163.com
|
||||
* website: https://www.vform666.com
|
||||
* date: 2021.08.18
|
||||
* remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<container-wrapper :designer="designer" :widget="widget" :parent-widget="parentWidget" :parent-list="parentList"
|
||||
:index-of-parent-list="indexOfParentList">
|
||||
|
||||
<el-row :key="widget.id" :gutter="widget.options.gutter" class="grid-container"
|
||||
:class="[selected ? 'selected' : '', customClass]"
|
||||
@click.stop="selectWidget(widget)">
|
||||
<template v-for="(colWidget, colIdx) in widget.cols" :key="colWidget.id">
|
||||
<grid-col-widget :widget="colWidget" :designer="designer" :parent-list="widget.cols"
|
||||
:index-of-parent-list="colIdx" :parent-widget="widget"
|
||||
:col-height="widget.options.colHeight"></grid-col-widget>
|
||||
</template>
|
||||
</el-row>
|
||||
|
||||
</container-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import GridColWidget from "@/components/form-designer/form-widget/container-widget/grid-col-widget"
|
||||
import containerMixin from "@/components/form-designer/form-widget/container-widget/containerMixin";
|
||||
import ContainerWrapper from "@/components/form-designer/form-widget/container-widget/container-wrapper";
|
||||
|
||||
export default {
|
||||
name: "grid-widget",
|
||||
componentName: 'ContainerWidget',
|
||||
mixins: [i18n, containerMixin],
|
||||
components: {
|
||||
ContainerWrapper,
|
||||
GridColWidget
|
||||
},
|
||||
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: {
|
||||
//
|
||||
},
|
||||
mounted() {
|
||||
//
|
||||
},
|
||||
methods: {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-row.grid-container {
|
||||
min-height: 50px;
|
||||
//line-height: 48px;
|
||||
//padding: 6px;
|
||||
outline: 1px dashed #336699;
|
||||
|
||||
.form-widget-list {
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.grid-container.selected, .grid-cell.selected {
|
||||
outline: 2px solid $--color-primary !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,10 @@
|
||||
const modules = import.meta.globEager('./*.vue')
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
for (const path in modules) {
|
||||
let cname = modules[path].default.name
|
||||
app.component(cname, modules[path].default)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
<!--
|
||||
/**
|
||||
* author: vformAdmin
|
||||
* email: vdpadmin@163.com
|
||||
* website: https://www.vform666.com
|
||||
* date: 2021.08.18
|
||||
* remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<container-wrapper :designer="designer" :widget="widget" :parent-widget="parentWidget" :parent-list="parentList"
|
||||
:index-of-parent-list="indexOfParentList">
|
||||
|
||||
<div :key="widget.id" class="tab-container"
|
||||
:class="{'selected': selected}" @click.stop="selectWidget(widget)">
|
||||
<el-tabs :type="widget.displayType" v-model="activeTab" @tab-click="onTabClick">
|
||||
|
||||
<el-tab-pane v-for="(tab, index) in widget.tabs" :key="index" :label="tab.options.label" :name="tab.options.name"
|
||||
@click.stop="selectWidget(widget)">
|
||||
<draggable :list="tab.widgetList" item-key="id" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
|
||||
handle=".drag-handler" tag="transition-group" :component-data="{name: 'fade'}"
|
||||
@add="(evt) => onContainerDragAdd(evt, tab.widgetList)"
|
||||
@update="onContainerDragUpdate" :move="checkContainerMove">
|
||||
<template #item="{ element: subWidget, index: swIdx }">
|
||||
<div class="form-widget-list">
|
||||
<template v-if="'container' === subWidget.category">
|
||||
<component :is="subWidget.type + '-widget'" :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="tab.widgetList"
|
||||
:index-of-parent-list="swIdx" :parent-widget="widget"></component>
|
||||
</template>
|
||||
<template v-else>
|
||||
<component :is="subWidget.type + '-widget'" :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="tab.widgetList"
|
||||
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></component>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
</container-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Draggable from 'vuedraggable'
|
||||
import i18n from "@/utils/i18n"
|
||||
import containerMixin from "@/components/form-designer/form-widget/container-widget/containerMixin"
|
||||
import ContainerWrapper from "@/components/form-designer/form-widget/container-widget/container-wrapper"
|
||||
import FieldComponents from '@/components/form-designer/form-widget/field-widget/index'
|
||||
|
||||
export default {
|
||||
name: "tab-widget",
|
||||
componentName: 'ContainerWidget',
|
||||
mixins: [i18n, containerMixin],
|
||||
components: {
|
||||
ContainerWrapper,
|
||||
Draggable,
|
||||
|
||||
...FieldComponents,
|
||||
},
|
||||
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: {
|
||||
onTabClick(evt) {
|
||||
console.log('onTabClick', evt)
|
||||
let paneName = evt.name
|
||||
this.widget.tabs.forEach((tp) => {
|
||||
tp.options.active = tp.options.name === paneName;
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tab-container {
|
||||
//padding: 5px;
|
||||
margin: 2px;
|
||||
|
||||
.form-widget-list {
|
||||
min-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-container.selected {
|
||||
outline: 2px solid $--color-primary !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,344 @@
|
||||
<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" item-key="id" class="draggable-div" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
|
||||
tag="transition-group" :component-data="{name: 'fade'}"
|
||||
handle=".drag-handler" @end="(evt) => onTableDragEnd(evt, widget.widgetList)"
|
||||
@add="(evt) => onTableDragAdd(evt, widget.widgetList)"
|
||||
@update="onTableDragUpdate" :move="checkContainerMove">
|
||||
<template #item="{ element: subWidget, index: swIdx }">
|
||||
<div class="form-widget-list">
|
||||
<template v-if="'container' === subWidget.category">
|
||||
<component :is="subWidget.type + '-widget'" :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
|
||||
:index-of-parent-list="swIdx" :parent-widget="widget"></component>
|
||||
</template>
|
||||
<template v-else>
|
||||
<component :is="subWidget.type + '-widget'" :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
|
||||
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></component>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<div class="table-cell-action" v-if="designer.selectedId === widget.id && widget.type === 'table-cell'">
|
||||
<i class="el-icon-back" :title="i18nt('designer.hint.selectParentWidget')"
|
||||
@click.stop="selectParentWidget()"></i>
|
||||
<el-dropdown trigger="click" @command="handleTableCellCommand" size="small">
|
||||
<i class="el-icon-menu" :title="i18nt('designer.hint.cellSetting')"></i>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="insertLeftCol">{{i18nt('designer.setting.insertColumnToLeft')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="insertRightCol">{{i18nt('designer.setting.insertColumnToRight')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="insertAboveRow">{{i18nt('designer.setting.insertRowAbove')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="insertBelowRow">{{i18nt('designer.setting.insertRowBelow')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="mergeLeftCol" :disabled="mergeLeftColDisabled" divided>{{i18nt('designer.setting.mergeLeftColumn')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="mergeRightCol" :disabled="mergeRightColDisabled">{{i18nt('designer.setting.mergeRightColumn')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="mergeWholeRow" :disabled="mergeWholeRowDisabled">{{i18nt('designer.setting.mergeEntireRow')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="mergeAboveRow" :disabled="mergeAboveRowDisabled" divided>{{i18nt('designer.setting.mergeRowAbove')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="mergeBelowRow" :disabled="mergeBelowRowDisabled">{{i18nt('designer.setting.mergeRowBelow')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="mergeWholeCol" :disabled="mergeWholeColDisabled">{{i18nt('designer.setting.mergeEntireColumn')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="undoMergeRow" :disabled="undoMergeRowDisabled" divided>{{i18nt('designer.setting.undoMergeRow')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="undoMergeCol" :disabled="undoMergeColDisabled">{{i18nt('designer.setting.undoMergeCol')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="deleteWholeCol" :disabled="deleteWholeColDisabled" divided>{{i18nt('designer.setting.deleteEntireCol')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="deleteWholeRow" :disabled="deleteWholeRowDisabled">{{i18nt('designer.setting.deleteEntireRow')}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</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 i18n from "@/utils/i18n"
|
||||
import FieldComponents from '@/components/form-designer/form-widget/field-widget/index'
|
||||
|
||||
export default {
|
||||
name: "TableCellWidget",
|
||||
componentName: "TableCellWidget",
|
||||
mixins: [i18n],
|
||||
components: {
|
||||
Draggable,
|
||||
...FieldComponents,
|
||||
},
|
||||
props: {
|
||||
widget: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
|
||||
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)
|
||||
},
|
||||
|
||||
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.colLength === 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) {
|
||||
//
|
||||
},
|
||||
|
||||
onTableDragAdd(evt, subList) { //重复代码,可合并
|
||||
const newIndex = evt.newIndex
|
||||
if (!!subList[newIndex]) {
|
||||
this.designer.setSelected( subList[newIndex] )
|
||||
}
|
||||
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
onTableDragUpdate() {
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
selectParentWidget() {
|
||||
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, this.rowIndex, true)
|
||||
},
|
||||
|
||||
insertRightCol() {
|
||||
this.designer.insertTableCol(this.parentWidget, this.colIndex, this.rowIndex, false)
|
||||
},
|
||||
|
||||
insertAboveRow() {
|
||||
this.designer.insertTableRow(this.parentWidget, this.rowIndex, this.rowIndex, this.colIndex, true)
|
||||
},
|
||||
|
||||
insertBelowRow() {
|
||||
this.designer.insertTableRow(this.parentWidget, this.rowIndex, this.rowIndex, this.colIndex, false)
|
||||
},
|
||||
|
||||
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)
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</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;
|
||||
height: 100%;
|
||||
|
||||
/*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: default; //cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.table-cell.selected {
|
||||
outline: 2px solid $--color-primary !important;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,111 @@
|
||||
<!--
|
||||
/**
|
||||
* author: vformAdmin
|
||||
* email: vdpadmin@163.com
|
||||
* website: https://www.vform666.com
|
||||
* date: 2021.08.18
|
||||
* remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<container-wrapper :designer="designer" :widget="widget" :parent-widget="parentWidget" :parent-list="parentList"
|
||||
:index-of-parent-list="indexOfParentList">
|
||||
|
||||
<div :key="widget.id" class="table-container"
|
||||
:class="[selected ? 'selected' : '', customClass]" @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>
|
||||
|
||||
</container-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import containerMixin from "@/components/form-designer/form-widget/container-widget/containerMixin"
|
||||
import ContainerWrapper from "@/components/form-designer/form-widget/container-widget/container-wrapper"
|
||||
import TableCellWidget from "@/components/form-designer/form-widget/container-widget/table-cell-widget"
|
||||
|
||||
export default {
|
||||
name: "table-widget",
|
||||
componentName: 'ContainerWidget',
|
||||
mixins: [i18n, containerMixin],
|
||||
components: {
|
||||
ContainerWrapper,
|
||||
TableCellWidget,
|
||||
},
|
||||
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: {
|
||||
//
|
||||
},
|
||||
mounted() {
|
||||
//
|
||||
},
|
||||
methods: {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
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;
|
||||
|
||||
:deep(td) {
|
||||
height: 48px;
|
||||
border: 1px dashed #336699;
|
||||
padding: 3px;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.form-widget-list {
|
||||
border: 1px dashed #336699;
|
||||
min-height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-container.selected {
|
||||
outline: 2px solid $--color-primary !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<static-content-wrapper :designer="designer" :field="field" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-button ref="fieldEditor" :type="field.options.type" :size="field.options.size"
|
||||
:plain="field.options.plain" :round="field.options.round"
|
||||
:circle="field.options.circle" :icon="field.options.icon"
|
||||
:disabled="field.options.disabled"
|
||||
@click="handleButtonWidgetClick">
|
||||
{{field.options.label}}</el-button>
|
||||
</static-content-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StaticContentWrapper from './static-content-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "button-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
StaticContentWrapper,
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; //* static-content-wrapper已引入,还需要重复引入吗? *//
|
||||
|
||||
</style>
|
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-cascader ref="fieldEditor" :options="field.options.optionItems" v-model="fieldModel" class="full-width-input"
|
||||
:disabled="field.options.disabled"
|
||||
:size="field.options.size"
|
||||
:clearable="field.options.clearable"
|
||||
:filterable="field.options.filterable"
|
||||
:placeholder="field.options.placeholder || i18nt('render.hint.selectPlaceholder')"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
</el-cascader>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "cascader-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initOptionItems()
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-checkbox-group ref="fieldEditor" v-model="fieldModel"
|
||||
:disabled="field.options.disabled" :size="field.options.size"
|
||||
@change="handleChangeEvent">
|
||||
<template v-if="!!field.options.buttonStyle">
|
||||
<el-checkbox-button v-for="(item, index) in field.options.optionItems" :key="index" :label="item.value"
|
||||
:disabled="item.disabled" :border="field.options.border"
|
||||
:style="{display: field.options.displayStyle}">{{item.label}}</el-checkbox-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-checkbox v-for="(item, index) in field.options.optionItems" :key="index" :label="item.value"
|
||||
:disabled="item.disabled" :border="field.options.border"
|
||||
:style="{display: field.options.displayStyle}">{{item.label}}</el-checkbox>
|
||||
</template>
|
||||
</el-checkbox-group>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "checkbox-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initOptionItems()
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
</style>
|
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-color-picker ref="fieldEditor" v-model="fieldModel"
|
||||
:size="field.options.size"
|
||||
:disabled="field.options.disabled"
|
||||
@change="handleChangeEvent">
|
||||
</el-color-picker>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "color-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-date-picker ref="fieldEditor" :type="field.options.type" v-model="fieldModel" class="full-width-input"
|
||||
:disabled="field.options.disabled" :readonly="field.options.readonly"
|
||||
:size="field.options.size"
|
||||
:clearable="field.options.clearable" :editable="field.options.editable"
|
||||
:format="field.options.format" :value-format="field.options.valueFormat"
|
||||
:start-placeholder="field.options.startPlaceholder || i18nt('render.hint.startDatePlaceholder')"
|
||||
:end-placeholder="field.options.endPlaceholder || i18nt('render.hint.endDatePlaceholder')"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
</el-date-picker>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "date-range-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-date-picker ref="fieldEditor" :type="field.options.type" v-model="fieldModel" class="full-width-input"
|
||||
:readonly="field.options.readonly" :disabled="field.options.disabled"
|
||||
:size="field.options.size"
|
||||
:clearable="field.options.clearable" :editable="field.options.editable"
|
||||
:format="field.options.format" :value-format="field.options.valueFormat"
|
||||
:placeholder="field.options.placeholder || i18nt('render.hint.datePlaceholder')"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
</el-date-picker>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "date-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<static-content-wrapper :designer="designer" :field="field" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-divider ref="fieldEditor" direction="horizontal" :content-position="field.options.contentPosition">
|
||||
{{field.options.label}}</el-divider>
|
||||
</static-content-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StaticContentWrapper from './static-content-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "divider-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
StaticContentWrapper,
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; //* static-content-wrapper已引入,还需要重复引入吗? *//
|
||||
|
||||
</style>
|
@ -0,0 +1,546 @@
|
||||
import {deepClone} from "@/utils/util"
|
||||
import FormValidators from '@/utils/validators'
|
||||
import eventBus from "@/utils/event-bus"
|
||||
|
||||
export default {
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel', 'getOptionData'],
|
||||
|
||||
computed: {
|
||||
subFormName() {
|
||||
return !!this.parentWidget ? this.parentWidget.options.name : ''
|
||||
},
|
||||
|
||||
subFormItemFlag() {
|
||||
return !!this.parentWidget ? this.parentWidget.type === 'sub-form' : false
|
||||
},
|
||||
|
||||
formModel: {
|
||||
cache: false,
|
||||
get() {
|
||||
return this.globalModel.formModel
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
//--------------------- 组件内部方法 begin ------------------//
|
||||
|
||||
initFieldModel() {
|
||||
if (!this.field.formItemFlag) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!!this.subFormItemFlag && !this.designState) { //SubForm子表单组件需要特殊处理!!
|
||||
let subFormData = this.formModel[this.subFormName]
|
||||
if (((subFormData === undefined) || (subFormData[this.subFormRowIndex] === undefined) ||
|
||||
(subFormData[this.subFormRowIndex][this.field.options.name] === undefined)) &&
|
||||
(this.field.options.defaultValue !== undefined)) {
|
||||
this.fieldModel = this.field.options.defaultValue
|
||||
subFormData[this.subFormRowIndex][this.field.options.name] = this.field.options.defaultValue
|
||||
} else if (subFormData[this.subFormRowIndex][this.field.options.name] === undefined) {
|
||||
this.fieldModel = null
|
||||
subFormData[this.subFormRowIndex][this.field.options.name] = null
|
||||
} else {
|
||||
this.fieldModel = subFormData[this.subFormRowIndex][this.field.options.name]
|
||||
}
|
||||
|
||||
/* 主动触发子表单内field-widget的onChange事件!! */
|
||||
setTimeout(() => { //延时触发onChange事件, 便于更新计算字段!!
|
||||
this.handleOnChangeForSubForm(this.fieldModel, this.oldFieldValue, subFormData, this.subFormRowId)
|
||||
}, 800)
|
||||
this.oldFieldValue = deepClone(this.fieldModel)
|
||||
|
||||
this.initFileList() //处理图片上传、文件上传字段
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if ((this.formModel[this.field.options.name] === undefined) &&
|
||||
(this.field.options.defaultValue !== undefined)) {
|
||||
this.fieldModel = this.field.options.defaultValue
|
||||
} else if (this.formModel[this.field.options.name] === undefined) { //如果formModel为空对象,则初始化字段值为null!!
|
||||
this.formModel[this.field.options.name] = null
|
||||
} else {
|
||||
this.fieldModel = this.formModel[this.field.options.name]
|
||||
}
|
||||
this.oldFieldValue = deepClone(this.fieldModel)
|
||||
this.initFileList() //处理图片上传、文件上传字段
|
||||
},
|
||||
|
||||
initFileList() { //初始化上传组件的已上传文件列表
|
||||
if ( ((this.field.type !== 'picture-upload') && (this.field.type !== 'file-upload')) || (this.designState === true) ) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!!this.fieldModel) {
|
||||
if (Array.isArray(this.fieldModel)) {
|
||||
this.fileList = deepClone(this.fieldModel)
|
||||
} else {
|
||||
this.fileList.splice(0, 0, deepClone(this.fieldModel))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
initEventHandler() {
|
||||
eventBus.$on('setFormData', function (newFormData) {
|
||||
console.log('formModel of globalModel----------', this.globalModel.formModel)
|
||||
if (!this.subFormItemFlag) {
|
||||
this.setValue(newFormData[this.field.options.name])
|
||||
}
|
||||
})
|
||||
|
||||
eventBus.$on('field-value-changed', function (values) {
|
||||
if (!!this.subFormItemFlag) {
|
||||
let subFormData = this.formModel[this.subFormName]
|
||||
this.handleOnChangeForSubForm(values[0], values[1], subFormData, this.subFormRowId)
|
||||
} else {
|
||||
this.handleOnChange(values[0], values[1])
|
||||
}
|
||||
})
|
||||
|
||||
/* 监听重新加载选项事件 */
|
||||
eventBus.$on('reloadOptionItems', function (widgetNames) {
|
||||
if ((widgetNames.length === 0) || (widgetNames.indexOf(this.field.options.name) > -1)) {
|
||||
this.initOptionItems(true)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleOnCreated() {
|
||||
if (!!this.field.options.onCreated) {
|
||||
let customFunc = new Function(this.field.options.onCreated)
|
||||
customFunc.call(this)
|
||||
}
|
||||
},
|
||||
|
||||
handleOnMounted() {
|
||||
if (!!this.field.options.onMounted) {
|
||||
let mountFunc = new Function(this.field.options.onMounted)
|
||||
mountFunc.call(this)
|
||||
}
|
||||
},
|
||||
|
||||
registerToRefList(oldRefName) {
|
||||
if ((this.refList !== null) && !!this.field.options.name) {
|
||||
if (this.subFormItemFlag && !this.designState) { //处理子表单元素(且非设计状态)
|
||||
if (!!oldRefName) {
|
||||
delete this.refList[oldRefName + '@row' + this.subFormRowId]
|
||||
}
|
||||
this.refList[this.field.options.name + '@row' + this.subFormRowId] = this
|
||||
} else {
|
||||
if (!!oldRefName) {
|
||||
delete this.refList[oldRefName]
|
||||
}
|
||||
this.refList[this.field.options.name] = this
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unregisterFromRefList() { //销毁组件时注销组件ref
|
||||
if ((this.refList !== null) && !!this.field.options.name) {
|
||||
let oldRefName = this.field.options.name
|
||||
if (this.subFormItemFlag && !this.designState) { //处理子表单元素(且非设计状态)
|
||||
delete this.refList[oldRefName + '@row' + this.subFormRowId]
|
||||
} else {
|
||||
delete this.refList[oldRefName]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
initOptionItems(keepSelected) {
|
||||
if (this.designState) {
|
||||
return
|
||||
}
|
||||
|
||||
if ((this.field.type === 'radio') || (this.field.type === 'checkbox')
|
||||
|| (this.field.type === 'select') || (this.field.type === 'cascader')) {
|
||||
if (!!this.globalOptionData && this.globalOptionData.hasOwnProperty(this.field.options.name)) {
|
||||
if (!!keepSelected) {
|
||||
//this.reloadOptions(this.globalOptionData[this.field.options.name]) /* 异步更新option-data之后不能获取到最新值,
|
||||
// 以下改用provide的getOptionData()方法 */
|
||||
const newOptionItems = this.getOptionData()
|
||||
this.reloadOptions(newOptionItems[this.field.options.name])
|
||||
} else {
|
||||
this.loadOptions( this.globalOptionData[this.field.options.name] )
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
refreshDefaultValue() {
|
||||
if ((this.designState === true) && (this.field.options.defaultValue !== undefined)) {
|
||||
this.fieldModel = this.field.options.defaultValue
|
||||
}
|
||||
},
|
||||
|
||||
clearFieldRules() {
|
||||
if (!this.field.formItemFlag) {
|
||||
return
|
||||
}
|
||||
|
||||
this.rules.splice(0, this.rules.length) //清空已有
|
||||
},
|
||||
|
||||
buildFieldRules() {
|
||||
if (!this.field.formItemFlag) {
|
||||
return
|
||||
}
|
||||
|
||||
this.rules.splice(0, this.rules.length) //清空已有
|
||||
if (!!this.field.options.required) {
|
||||
this.rules.push({
|
||||
required: true,
|
||||
//trigger: ['blur', 'change'],
|
||||
trigger: ['blur'], /* 去掉change事件触发校验,change事件触发时formModel数据尚未更新,导致radio/checkbox必填校验出错!! */
|
||||
message: this.i18nt('render.hint.fieldRequired'),
|
||||
})
|
||||
}
|
||||
|
||||
if (!!this.field.options.validation) {
|
||||
let vldName = this.field.options.validation
|
||||
if (!!FormValidators[vldName]) {
|
||||
this.rules.push({
|
||||
validator: FormValidators[vldName],
|
||||
trigger: ['blur', 'change'],
|
||||
label: this.field.options.label,
|
||||
errorMsg: this.field.options.validationHint
|
||||
})
|
||||
} else {
|
||||
this.rules.push({
|
||||
validator: FormValidators['regExp'],
|
||||
trigger: ['blur', 'change'],
|
||||
regExp: vldName,
|
||||
label: this.field.options.label,
|
||||
errorMsg: this.field.options.validationHint
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!!this.field.options.onValidate) {
|
||||
let customFn = new Function('rule', 'value', 'callback', this.field.options.onValidate)
|
||||
this.rules.push({
|
||||
validator: customFn,
|
||||
trigger: ['blur', 'change'],
|
||||
label: this.field.options.label
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 禁用字段值变动触发表单校验
|
||||
*/
|
||||
disableChangeValidate() {
|
||||
if (!this.rules) {
|
||||
return
|
||||
}
|
||||
|
||||
this.rules.forEach(rule => {
|
||||
if (!!rule.trigger) {
|
||||
rule.trigger.splice(0, rule.trigger.length)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 启用字段值变动触发表单校验
|
||||
*/
|
||||
enableChangeValidate() {
|
||||
if (!this.rules) {
|
||||
return
|
||||
}
|
||||
|
||||
this.rules.forEach(rule => {
|
||||
if (!!rule.trigger) {
|
||||
rule.trigger.push('blur')
|
||||
rule.trigger.push('change')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
disableOptionOfList(optionList, optionValue) {
|
||||
if (!!optionList && (optionList.length > 0)) {
|
||||
optionList.forEach(opt => {
|
||||
if (opt.value === optionValue) {
|
||||
opt.disabled = true
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
enableOptionOfList(optionList, optionValue) {
|
||||
if (!!optionList && (optionList.length > 0)) {
|
||||
optionList.forEach(opt => {
|
||||
if (opt.value === optionValue) {
|
||||
opt.disabled = false
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------- 组件内部方法 end ------------------//
|
||||
|
||||
//--------------------- 事件处理 begin ------------------//
|
||||
|
||||
emitFieldDataChange(newValue, oldValue) {
|
||||
this.$emit('field-value-changed', [newValue, oldValue])
|
||||
|
||||
/* 必须用dispatch向指定父组件派发消息!! */
|
||||
this.dispatch('VFormRender', 'fieldChange',
|
||||
[this.field.options.name, newValue, oldValue, this.subFormName, this.subFormRowIndex])
|
||||
},
|
||||
|
||||
syncUpdateFormModel(value) {
|
||||
if (!!this.designState) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!!this.subFormItemFlag) {
|
||||
let subFormData = this.formModel[this.subFormName] || [{}]
|
||||
let subFormDataRow = subFormData[this.subFormRowIndex]
|
||||
subFormDataRow[this.field.options.name] = value
|
||||
} else {
|
||||
this.formModel[this.field.options.name] = value
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeEvent(value) {
|
||||
this.syncUpdateFormModel(value)
|
||||
this.emitFieldDataChange(value, this.oldFieldValue)
|
||||
|
||||
//number组件一般不会触发focus事件,故此处需要手工赋值oldFieldValue!!
|
||||
this.oldFieldValue = deepClone(value) /* oldFieldValue需要在initFieldModel()方法中赋初值!! */
|
||||
|
||||
/* 主动触发表单的单个字段校验,用于清除字段可能存在的校验错误提示 */
|
||||
this.dispatch('VFormRender', 'fieldValidation', [this.field.options.name])
|
||||
},
|
||||
|
||||
handleFocusCustomEvent(event) {
|
||||
this.oldFieldValue = deepClone(this.fieldModel) //保存修改change之前的值
|
||||
|
||||
if (!!this.field.options.onFocus) {
|
||||
let customFn = new Function('event', this.field.options.onFocus)
|
||||
customFn.call(this, event)
|
||||
}
|
||||
},
|
||||
|
||||
handleBlurCustomEvent(event) {
|
||||
if (!!this.field.options.onBlur) {
|
||||
let customFn = new Function('event', this.field.options.onBlur)
|
||||
customFn.call(this, event)
|
||||
}
|
||||
},
|
||||
|
||||
handleInputCustomEvent(value) {
|
||||
this.syncUpdateFormModel(value)
|
||||
|
||||
if (!!this.field.options.onInput) {
|
||||
let customFn = new Function('value', this.field.options.onInput)
|
||||
customFn.call(this, value)
|
||||
}
|
||||
},
|
||||
|
||||
emitAppendButtonClick() {
|
||||
/* 必须调用mixins中的dispatch方法逐级向父组件发送消息!! */
|
||||
this.dispatch('VFormRender', 'appendButtonClick', [this]);
|
||||
},
|
||||
|
||||
handleOnChange(val, oldVal) { //自定义onChange事件
|
||||
if (!!this.field.options.onChange) {
|
||||
let changeFn = new Function('value', 'oldValue', this.field.options.onChange)
|
||||
changeFn.call(this, val, oldVal)
|
||||
}
|
||||
},
|
||||
|
||||
handleOnChangeForSubForm(val, oldVal, subFormData, rowId) { //子表单自定义onChange事件
|
||||
if (!!this.field.options.onChange) {
|
||||
let changeFn = new Function('value', 'oldValue', 'subFormData', 'rowId', this.field.options.onChange)
|
||||
changeFn.call(this, val, oldVal, subFormData, rowId)
|
||||
}
|
||||
},
|
||||
|
||||
handleButtonWidgetClick() {
|
||||
if (!!this.designState) { //设计状态不触发点击事件
|
||||
return
|
||||
}
|
||||
|
||||
if (!!this.field.options.onClick) {
|
||||
let changeFn = new Function(this.field.options.onClick)
|
||||
changeFn.call(this)
|
||||
} else {
|
||||
this.dispatch('VFormRender', 'buttonClick', [this]);
|
||||
}
|
||||
},
|
||||
|
||||
remoteQuery(keyword) {
|
||||
if (!!this.field.options.onRemoteQuery) {
|
||||
let remoteFn = new Function('keyword', this.field.options.onRemoteQuery)
|
||||
remoteFn.call(this, keyword)
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------- 事件处理 end ------------------//
|
||||
|
||||
//--------------------- 以下为组件支持外部调用的API方法 begin ------------------//
|
||||
/* 提示:用户可自行扩充这些方法!!! */
|
||||
|
||||
getFormRef() { /* 获取VFrom引用,必须在VForm组件created之后方可调用 */
|
||||
return this.refList['v_form_ref']
|
||||
},
|
||||
|
||||
getWidgetRef(widgetName, showError) {
|
||||
let foundRef = this.refList[widgetName]
|
||||
if (!foundRef && !!showError) {
|
||||
this.$message.error(this.i18nt('render.hint.refNotFound') + widgetName)
|
||||
}
|
||||
return foundRef
|
||||
},
|
||||
|
||||
getFieldEditor() { //获取内置的el表单组件
|
||||
return this.$refs['fieldEditor']
|
||||
},
|
||||
|
||||
/*
|
||||
注意:VFormRender的setFormData方法不会触发子表单内field-widget的setValue方法,
|
||||
因为setFormData方法调用后,子表单内所有field-widget组件已被清空,接收不到setFormData事件!!
|
||||
* */
|
||||
setValue(newValue) {
|
||||
/* if ((this.field.type === 'picture-upload') || (this.field.type === 'file-upload')) {
|
||||
this.fileList = newValue
|
||||
} else */ if (!!this.field.formItemFlag) {
|
||||
let oldValue = deepClone(this.fieldModel)
|
||||
this.fieldModel = newValue
|
||||
this.initFileList()
|
||||
|
||||
this.syncUpdateFormModel(newValue)
|
||||
this.emitFieldDataChange(newValue, oldValue)
|
||||
}
|
||||
},
|
||||
|
||||
getValue() {
|
||||
/* if ((this.field.type === 'picture-upload') || (this.field.type === 'file-upload')) {
|
||||
return this.fileList
|
||||
} else */ {
|
||||
return this.fieldModel
|
||||
}
|
||||
},
|
||||
|
||||
resetField() {
|
||||
let defaultValue = this.field.options.defaultValue
|
||||
this.setValue(defaultValue)
|
||||
this.$nextTick(() => {
|
||||
//
|
||||
})
|
||||
|
||||
//清空上传组件文件列表
|
||||
if ((this.field.type === 'picture-upload') || (this.field.type === 'file-upload')) {
|
||||
this.$refs['fieldEditor'].clearFiles()
|
||||
this.fileList.splice(0, this.fileList.length)
|
||||
}
|
||||
},
|
||||
|
||||
setWidgetOption(optionName, optionValue) { //通用组件选项修改API
|
||||
if (this.field.options.hasOwnProperty(optionName)) {
|
||||
this.field.options[optionName] = optionValue
|
||||
//TODO: 是否重新构建组件??有些属性修改后必须重新构建组件才能生效,比如字段校验规则。
|
||||
}
|
||||
},
|
||||
|
||||
setReadonly(flag) {
|
||||
this.field.options.readonly = flag
|
||||
},
|
||||
|
||||
setDisabled(flag) {
|
||||
this.field.options.disabled = flag
|
||||
},
|
||||
|
||||
setAppendButtonVisible(flag) {
|
||||
this.field.options.appendButton = flag
|
||||
},
|
||||
|
||||
setAppendButtonDisabled(flag) {
|
||||
this.field.options.appendButtonDisabled = flag
|
||||
},
|
||||
|
||||
setHidden(flag) {
|
||||
this.field.options.hidden = flag
|
||||
|
||||
if (!!flag) { //清除组件校验规则
|
||||
this.clearFieldRules()
|
||||
} else { //重建组件校验规则
|
||||
this.buildFieldRules()
|
||||
}
|
||||
},
|
||||
|
||||
setRequired(flag) {
|
||||
this.field.options.required = flag
|
||||
this.buildFieldRules()
|
||||
},
|
||||
|
||||
setLabel(newLabel) {
|
||||
this.field.options.label = newLabel
|
||||
},
|
||||
|
||||
focus() {
|
||||
if (!!this.getFieldEditor() && !!this.getFieldEditor().focus) {
|
||||
this.getFieldEditor().focus()
|
||||
}
|
||||
},
|
||||
|
||||
clearSelectedOptions() { //清空已选选项
|
||||
if ((this.field.type !== 'checkbox') && (this.field.type !== 'radio') && (this.field.type !== 'select')) {
|
||||
return
|
||||
}
|
||||
|
||||
if ((this.field.type === 'checkbox') ||
|
||||
((this.field.type === 'select') && this.field.options.multiple)) {
|
||||
this.fieldModel = []
|
||||
} else {
|
||||
this.fieldModel = ''
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载选项,并清空字段值
|
||||
* @param options
|
||||
*/
|
||||
loadOptions(options) {
|
||||
this.field.options.optionItems = deepClone(options)
|
||||
//this.clearSelectedOptions() //清空已选选项
|
||||
},
|
||||
|
||||
/**
|
||||
* 重新加载选项,不清空字段值
|
||||
* @param options
|
||||
*/
|
||||
reloadOptions(options) {
|
||||
this.field.options.optionItems = deepClone(options)
|
||||
},
|
||||
|
||||
disableOption(optionValue) {
|
||||
this.disableOptionOfList(this.field.options.optionItems, optionValue)
|
||||
},
|
||||
|
||||
enableOption(optionValue) {
|
||||
this.enableOptionOfList(this.field.options.optionItems, optionValue)
|
||||
},
|
||||
|
||||
setUploadHeader(name, value) {
|
||||
//this.$set(this.uploadHeaders, name, value)
|
||||
this.uploadHeaders[name] = value
|
||||
},
|
||||
|
||||
setUploadData(name, value) {
|
||||
//this.$set(this.uploadData, name, value)
|
||||
this.uploadData[name] = value
|
||||
},
|
||||
|
||||
setToolbar(customToolbar) {
|
||||
this.customToolbar = customToolbar
|
||||
},
|
||||
|
||||
//--------------------- 以上为组件支持外部调用的API方法 end ------------------//
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<!-- el-upload增加:name="field.options.name"后,会导致又拍云上传失败!故删除之!! -->
|
||||
<el-upload ref="fieldEditor" :disabled="field.options.disabled"
|
||||
:style="styleVariables" class="dynamicPseudoAfter"
|
||||
:action="field.options.uploadURL" :headers="uploadHeaders" :data="uploadData"
|
||||
:with-credentials="field.options.withCredentials"
|
||||
:multiple="field.options.multipleSelect" :file-list="fileList"
|
||||
:show-file-list="field.options.showFileList" :class="{'hideUploadDiv': uploadBtnHidden}"
|
||||
:limit="field.options.limit" :on-exceed="handleFileExceed" :before-upload="beforeFileUpload"
|
||||
:on-success="handleFileUpload" :on-error="handelUploadError" :on-remove="handleFileRemove">
|
||||
<template #tip>
|
||||
<div class="el-upload__tip"
|
||||
v-if="!!field.options.uploadTip">{{field.options.uploadTip}}</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<i class="el-icon-plus avatar-uploader-icon"></i>
|
||||
</template>
|
||||
<template #file="{ file }">
|
||||
<div class="upload-file-list">
|
||||
<span class="upload-file-name" :title="file.name">{{file.name}}</span>
|
||||
<a :href="file.url" download="">
|
||||
<i class="el-icon-download file-action" title="i18nt('render.hint.downloadFile')"></i></a>
|
||||
<i class="el-icon-delete file-action" title="i18nt('render.hint.removeFile')" v-if="!field.options.disabled"
|
||||
@click="removeUploadFile(file.name)"></i>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import {deepClone} from "@/utils/util";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
let selectFileText = "'" + translate('render.hint.selectFile') + "'"
|
||||
|
||||
export default {
|
||||
name: "file-upload-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
|
||||
uploadHeaders: {},
|
||||
uploadData: {
|
||||
key: '', //七牛云上传文件名
|
||||
//token: '', //七牛云上传token
|
||||
|
||||
//policy: '', //又拍云上传policy
|
||||
//authorization: '', //又拍云上传签名
|
||||
},
|
||||
fileList: [], //上传文件列表
|
||||
uploadBtnHidden: false,
|
||||
|
||||
styleVariables: {
|
||||
'--select-file-action': selectFileText,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleFileExceed() {
|
||||
let uploadLimit = this.field.options.limit /* 此行不能注释,下一行ES6模板字符串需要用到!! */
|
||||
this.$message.warning(eval('`' + this.i18nt('render.hint.uploadExceed') + '`'));
|
||||
},
|
||||
|
||||
updateUploadFieldModelAndEmitDataChange(fileList) {
|
||||
let oldValue = deepClone(this.fieldModel)
|
||||
this.fieldModel = deepClone(fileList)
|
||||
this.syncUpdateFormModel(this.fieldModel)
|
||||
this.emitFieldDataChange(this.fieldModel, oldValue)
|
||||
},
|
||||
|
||||
beforeFileUpload(file) {
|
||||
let fileTypeCheckResult = false
|
||||
let extFileName = file.name.substring(file.name.lastIndexOf('.') + 1)
|
||||
if (!!this.field.options && !!this.field.options.fileTypes) {
|
||||
let uploadFileTypes = this.field.options.fileTypes
|
||||
if (uploadFileTypes.length > 0) {
|
||||
fileTypeCheckResult = uploadFileTypes.some( (ft) => {
|
||||
return extFileName.toLowerCase() === ft.toLowerCase()
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!fileTypeCheckResult) {
|
||||
this.$message.error(this.i18nt('render.hint.unsupportedFileType') + extFileName)
|
||||
return false;
|
||||
}
|
||||
|
||||
let fileSizeCheckResult = false
|
||||
let uploadFileMaxSize = 5 //5MB
|
||||
if (!!this.field.options && !!this.field.options.fileMaxSize) {
|
||||
uploadFileMaxSize = this.field.options.fileMaxSize
|
||||
}
|
||||
fileSizeCheckResult = file.size / 1024 / 1024 <= uploadFileMaxSize
|
||||
if (!fileSizeCheckResult) {
|
||||
this.$message.error(this.i18nt('render.hint.fileSizeExceed') + uploadFileMaxSize + 'MB')
|
||||
return false;
|
||||
}
|
||||
|
||||
this.uploadData.key = file.name
|
||||
return this.handleOnBeforeUpload(file)
|
||||
},
|
||||
|
||||
handleOnBeforeUpload(file) {
|
||||
if (!!this.field.options.onBeforeUpload) {
|
||||
let bfFunc = new Function('file', this.field.options.onBeforeUpload)
|
||||
let result = bfFunc.call(this, file)
|
||||
if (typeof result === 'boolean') {
|
||||
return result
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
handleFileUpload(res, file, fileList) {
|
||||
if (!!this.field.options.onUploadSuccess) {
|
||||
let mountFunc = new Function('result', 'file', 'fileList', this.field.options.onUploadSuccess)
|
||||
mountFunc.call(this, res, file, fileList)
|
||||
} else {
|
||||
if (file.status === 'success') {
|
||||
//this.fileList.push(file) /* 上传过程中,this.fileList是只读的,不能修改赋值!! */
|
||||
this.updateUploadFieldModelAndEmitDataChange(fileList)
|
||||
this.fileList = deepClone(fileList)
|
||||
|
||||
this.uploadBtnHidden = fileList.length >= this.field.options.limit
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleFileRemove(file, fileList) {
|
||||
this.fileList = deepClone(fileList) //this.fileList = fileList
|
||||
this.updateUploadFieldModelAndEmitDataChange(fileList)
|
||||
|
||||
this.uploadBtnHidden = fileList.length >= this.field.options.limit
|
||||
},
|
||||
|
||||
removeUploadFile(fileName) {
|
||||
let foundIdx = -1
|
||||
this.fileList.forEach((file,idx) => {
|
||||
if (file.name === fileName) {
|
||||
foundIdx = idx
|
||||
}
|
||||
})
|
||||
|
||||
if (foundIdx >= 0) {
|
||||
this.fileList.splice(foundIdx, 1)
|
||||
this.updateUploadFieldModelAndEmitDataChange(this.fileList)
|
||||
|
||||
this.uploadBtnHidden = this.fileList.length >= this.field.options.limit
|
||||
}
|
||||
},
|
||||
|
||||
handelUploadError(err, file, fileList) {
|
||||
if (!!this.field.options.onUploadError) {
|
||||
let customFn = new Function('error', 'file', 'fileList', this.field.options.onUploadError)
|
||||
customFn.call(this, err, file, fileList)
|
||||
} else {
|
||||
this.$message({
|
||||
message: this.i18nt('render.hint.uploadError') + err,
|
||||
duration: 3000,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.dynamicPseudoAfter :deep(.el-upload.el-upload--text) {
|
||||
color: $--color-primary;
|
||||
font-size: 12px;
|
||||
.el-icon-plus:after {
|
||||
content: var(--select-file-action);
|
||||
}
|
||||
}
|
||||
|
||||
.hideUploadDiv {
|
||||
:deep(div.el-upload--picture-card) { /* 隐藏最后的图片上传按钮 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(div.el-upload--text) { /* 隐藏最后的文件上传按钮 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(div.el-upload__tip) { /* 隐藏最后的文件上传按钮 */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-file-list {
|
||||
font-size: 12px;
|
||||
|
||||
.file-action {
|
||||
color: $--color-primary;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,333 @@
|
||||
<!--
|
||||
/**
|
||||
* author: vformAdmin
|
||||
* email: vdpadmin@163.com
|
||||
* website: https://www.vform666.com
|
||||
* date: 2021.08.18
|
||||
* remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="field-wrapper" :class="{'design-time-bottom-margin': !!this.designer}">
|
||||
<el-form-item v-if="!!field.formItemFlag" :label="label" :label-width="labelWidth + 'px'"
|
||||
:title="field.options.labelTooltip"
|
||||
v-show="!field.options.hidden || (designState === true)"
|
||||
:rules="rules" :prop="getPropName()"
|
||||
:class="[selected ? 'selected' : '', labelAlign, customClass, field.options.required ? 'required' : '']"
|
||||
@click.stop="selectField(field)">
|
||||
|
||||
<template #label>
|
||||
<span v-if="!!field.options.labelIconClass" class="custom-label">
|
||||
<template v-if="field.options.labelIconPosition === 'front'">
|
||||
<template v-if="!!field.options.labelTooltip">
|
||||
<el-tooltip :content="field.options.labelTooltip" effect="light">
|
||||
<i :class="field.options.labelIconClass"></i></el-tooltip>{{label}}</template>
|
||||
<template v-else>
|
||||
<i :class="field.options.labelIconClass"></i>{{label}}</template>
|
||||
</template>
|
||||
<template v-else-if="field.options.labelIconPosition === 'rear'">
|
||||
<template v-if="!!field.options.labelTooltip">
|
||||
{{label}}<el-tooltip :content="field.options.labelTooltip" effect="light">
|
||||
<i :class="field.options.labelIconClass"></i></el-tooltip></template>
|
||||
<template v-else>
|
||||
{{label}}<i :class="field.options.labelIconClass"></i></template>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="!!this.designer">
|
||||
<div class="field-action" v-if="designer.selectedId === field.id">
|
||||
<i class="el-icon-back" :title="i18nt('designer.hint.selectParentWidget')" @click.stop="selectParentWidget(field)"></i>
|
||||
<i class="el-icon-top" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveUpWidget')"
|
||||
@click.stop="moveUpWidget(field)"></i>
|
||||
<i class="el-icon-bottom" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveDownWidget')"
|
||||
@click.stop="moveDownWidget(field)"></i>
|
||||
<i class="el-icon-delete" :title="i18nt('designer.hint.remove')" @click.stop="removeFieldWidget"></i>
|
||||
</div>
|
||||
|
||||
<div class="drag-handler background-opacity" v-if="designer.selectedId === field.id">
|
||||
<i class="el-icon-rank" :title="i18nt('designer.hint.dragHandler')"></i>
|
||||
<i>{{i18n2t(`designer.widgetLabel.${field.type}`, `extension.widgetLabel.${field.type}`)}}</i>
|
||||
<i v-if="field.options.hidden === true" class="iconfont icon-hide"></i>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
|
||||
export default {
|
||||
name: "form-item-wrapper",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
designer: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
rules: Array,
|
||||
},
|
||||
inject: ['formConfig'],
|
||||
computed: {
|
||||
selected() {
|
||||
return !!this.designer && this.field.id === this.designer.selectedId
|
||||
},
|
||||
|
||||
label() {
|
||||
if (!!this.field.options.labelHidden) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return this.field.options.label
|
||||
},
|
||||
|
||||
labelWidth() {
|
||||
if (!!this.field.options.labelHidden) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (!!this.field.options.labelWidth) {
|
||||
return this.field.options.labelWidth
|
||||
}
|
||||
|
||||
if (!!this.designer) {
|
||||
return this.designer.formConfig.labelWidth
|
||||
} else {
|
||||
return this.formConfig.labelWidth
|
||||
}
|
||||
},
|
||||
|
||||
labelAlign() {
|
||||
if (!!this.field.options.labelAlign) {
|
||||
return this.field.options.labelAlign
|
||||
}
|
||||
|
||||
if (!!this.designer) {
|
||||
return this.designer.formConfig.labelAlign || 'label-left-align'
|
||||
} else {
|
||||
return this.formConfig.labelAlign || 'label-left-align'
|
||||
}
|
||||
},
|
||||
|
||||
customClass() {
|
||||
return !!this.field.options.customClass ? this.field.options.customClass.join(' ') : ''
|
||||
},
|
||||
|
||||
subFormName() {
|
||||
return !!this.parentWidget ? this.parentWidget.options.name : ''
|
||||
},
|
||||
|
||||
subFormItemFlag() {
|
||||
return !!this.parentWidget ? this.parentWidget.type === 'sub-form' : false
|
||||
},
|
||||
|
||||
},
|
||||
created() {
|
||||
//
|
||||
},
|
||||
methods: {
|
||||
|
||||
selectField(field) {
|
||||
if (!!this.designer) {
|
||||
this.designer.setSelected(field)
|
||||
this.designer.emitEvent('field-selected', this.parentWidget) //发送选中组件的父组件对象
|
||||
}
|
||||
},
|
||||
|
||||
selectParentWidget() {
|
||||
if (this.parentWidget) {
|
||||
this.designer.setSelected(this.parentWidget)
|
||||
} else {
|
||||
this.designer.clearSelected()
|
||||
}
|
||||
},
|
||||
|
||||
moveUpWidget() {
|
||||
this.designer.moveUpWidget(this.parentList, this.indexOfParentList)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
moveDownWidget() {
|
||||
this.designer.moveDownWidget(this.parentList, this.indexOfParentList)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
removeFieldWidget() {
|
||||
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()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getPropName() {
|
||||
if (this.subFormItemFlag && !this.designState) {
|
||||
return this.subFormName + "." + this.subFormRowIndex + "." + this.field.options.name + ""
|
||||
} else {
|
||||
return this.field.options.name
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss";
|
||||
|
||||
.design-time-bottom-margin {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
position: relative;
|
||||
|
||||
.field-action{
|
||||
position: absolute;
|
||||
//bottom: -24px;
|
||||
bottom: 0;
|
||||
right: -2px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
background: $--color-primary;
|
||||
z-index: 9;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin: 0 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-handler {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
//bottom: -22px; /* 拖拽手柄位于组件下方,有时无法正常拖动,原因未明?? */
|
||||
left: -1px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
//background: $--color-primary;
|
||||
z-index: 9;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
color: #fff;
|
||||
margin: 4px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
//opacity: 1;
|
||||
background: $--color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
//margin-bottom: 0 !important;
|
||||
//margin-bottom: 6px;
|
||||
|
||||
//margin-top: 2px;
|
||||
position: relative;
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
//position: unset; /* TODO: 忘了这个样式设置是为了解决什么问题?? */
|
||||
}
|
||||
|
||||
span.custom-label i {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
/* 隐藏Chrome浏览器中el-input数字输入框右侧的上下调整小箭头 */
|
||||
:deep(.hide-spin-button) input::-webkit-outer-spin-button,
|
||||
:deep(.hide-spin-button) input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none !important;
|
||||
}
|
||||
|
||||
/* 隐藏Firefox浏览器中el-input数字输入框右侧的上下调整小箭头 */
|
||||
:deep(.hide-spin-button) input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
}
|
||||
|
||||
.required :deep(.el-form-item__label)::before {
|
||||
content: '*';
|
||||
color: #F56C6C;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.static-content-item {
|
||||
min-height: 20px;
|
||||
display: flex; /* 垂直居中 */
|
||||
align-items: center; /* 垂直居中 */
|
||||
|
||||
:deep(.el-divider--horizontal) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item.selected, .static-content-item.selected {
|
||||
outline: 2px solid $--color-primary;
|
||||
}
|
||||
|
||||
:deep(.label-left-align) .el-form-item__label {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:deep(.label-center-align) .el-form-item__label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.label-right-align) .el-form-item__label {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<static-content-wrapper :designer="designer" :field="field" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<div ref="fieldEditor" v-html="field.options.htmlContent"></div>
|
||||
</static-content-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StaticContentWrapper from './static-content-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "html-text-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
StaticContentWrapper,
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; //* static-content-wrapper已引入,还需要重复引入吗? *//
|
||||
|
||||
</style>
|
@ -0,0 +1,9 @@
|
||||
let comps = {}
|
||||
|
||||
const modules = import.meta.globEager('./*.vue')
|
||||
for (const path in modules) {
|
||||
let cname = modules[path].default.name
|
||||
comps[cname] = modules[path].default
|
||||
}
|
||||
|
||||
export default comps
|
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-input ref="fieldEditor" v-model="fieldModel"
|
||||
:disabled="field.options.disabled" :readonly="field.options.readonly"
|
||||
:size="field.options.size" class="hide-spin-button"
|
||||
:type="inputType"
|
||||
:show-password="field.options.showPassword"
|
||||
:placeholder="field.options.placeholder"
|
||||
:clearable="field.options.clearable"
|
||||
:minlength="field.options.minLength" :maxlength="field.options.maxLength"
|
||||
:show-word-limit="field.options.showWordLimit"
|
||||
:prefix-icon="field.options.prefixIcon" :suffix-icon="field.options.suffixIcon"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent" @input="handleInputCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
<template #append>
|
||||
<el-button v-if="field.options.appendButton" :disabled="field.options.disabled || field.options.appendButtonDisabled"
|
||||
:class="field.options.buttonIcon" @click="emitAppendButtonClick"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "input-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
inputType() {
|
||||
if (this.field.options.type === 'number') {
|
||||
return 'text' //当input的type设置为number时,如果输入非数字字符,则v-model拿到的值为空字符串,无法实现输入校验!故屏蔽之!!
|
||||
}
|
||||
|
||||
return this.field.options.type
|
||||
},
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
</style>
|
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-input-number ref="fieldEditor" v-model="fieldModel" class="full-width-input"
|
||||
:disabled="field.options.disabled"
|
||||
:size="field.options.size" :controls-position="field.options.controlsPosition"
|
||||
:placeholder="field.options.placeholder"
|
||||
:min="field.options.min" :max="field.options.max"
|
||||
:precision="field.options.precision" :step="field.options.step"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
</el-input-number>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "number-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<!-- el-upload增加:name="field.options.name"后,会导致又拍云上传失败!故删除之!! -->
|
||||
<el-upload ref="fieldEditor" :disabled="field.options.disabled"
|
||||
:action="field.options.uploadURL" :headers="uploadHeaders" :data="uploadData"
|
||||
:with-credentials="field.options.withCredentials"
|
||||
:multiple="field.options.multipleSelect" :file-list="fileList" :show-file-list="field.options.showFileList"
|
||||
list-type="picture-card" :class="{'hideUploadDiv': uploadBtnHidden}"
|
||||
:limit="field.options.limit" :on-exceed="handlePictureExceed"
|
||||
:before-upload="beforePictureUpload"
|
||||
:on-success="handlePictureUpload" :on-error="handelUploadError" :on-remove="handlePictureRemove">
|
||||
<template #tip>
|
||||
<div class="el-upload__tip"
|
||||
v-if="!!field.options.uploadTip">{{field.options.uploadTip}}</div>
|
||||
</template>
|
||||
<i class="el-icon-plus avatar-uploader-icon"></i>
|
||||
</el-upload>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import {deepClone} from "@/utils/util";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "picture-upload-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
|
||||
uploadHeaders: {},
|
||||
uploadData: {
|
||||
key: '', //七牛云上传文件名
|
||||
//token: '', //七牛云上传token
|
||||
|
||||
//policy: '', //又拍云上传policy
|
||||
//authorization: '', //又拍云上传签名
|
||||
},
|
||||
fileList: [], //上传文件列表
|
||||
uploadBtnHidden: false,
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
handlePictureExceed() {
|
||||
let uploadLimit = this.field.options.limit /* 此行不能注释,下一行ES6模板字符串需要用到!! */
|
||||
this.$message.warning(eval('`' + this.i18nt('render.hint.uploadExceed') + '`'));
|
||||
},
|
||||
|
||||
updateUploadFieldModelAndEmitDataChange(fileList) {
|
||||
let oldValue = deepClone(this.fieldModel)
|
||||
this.fieldModel = deepClone(fileList)
|
||||
this.syncUpdateFormModel(this.fieldModel)
|
||||
this.emitFieldDataChange(this.fieldModel, oldValue)
|
||||
},
|
||||
|
||||
beforePictureUpload(file) {
|
||||
let fileTypeCheckResult = false
|
||||
if (!!this.field.options && !!this.field.options.fileTypes) {
|
||||
let uploadFileTypes = this.field.options.fileTypes
|
||||
if (uploadFileTypes.length > 0) {
|
||||
fileTypeCheckResult = uploadFileTypes.some( (ft) => {
|
||||
return file.type === 'image/' + ft
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!fileTypeCheckResult) {
|
||||
this.$message.error(this.i18nt('render.hint.unsupportedFileType') + file.type)
|
||||
return false;
|
||||
}
|
||||
|
||||
let fileSizeCheckResult = false
|
||||
let uploadFileMaxSize = 5 //5MB
|
||||
if (!!this.field.options && !!this.field.options.fileMaxSize) {
|
||||
uploadFileMaxSize = this.field.options.fileMaxSize
|
||||
}
|
||||
fileSizeCheckResult = file.size / 1024 / 1024 <= uploadFileMaxSize
|
||||
if (!fileSizeCheckResult) {
|
||||
this.$message.error(this.$('render.hint.fileSizeExceed') + uploadFileMaxSize + 'MB')
|
||||
return false;
|
||||
}
|
||||
|
||||
this.uploadData.key = file.name
|
||||
return this.handleOnBeforeUpload(file)
|
||||
},
|
||||
|
||||
handleOnBeforeUpload(file) {
|
||||
if (!!this.field.options.onBeforeUpload) {
|
||||
let bfFunc = new Function('file', this.field.options.onBeforeUpload)
|
||||
let result = bfFunc.call(this, file)
|
||||
if (typeof result === 'boolean') {
|
||||
return result
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
handlePictureUpload(res, file, fileList) {
|
||||
if (!!this.field.options.onUploadSuccess) {
|
||||
let customFn = new Function('result', 'file', 'fileList', this.field.options.onUploadSuccess)
|
||||
customFn.call(this, res, file, fileList)
|
||||
} else {
|
||||
if (file.status === 'success') {
|
||||
//this.fileList.push(file) /* 上传过程中,this.fileList是只读的,不能修改赋值!! */
|
||||
this.updateUploadFieldModelAndEmitDataChange(fileList)
|
||||
|
||||
this.uploadBtnHidden = fileList.length >= this.field.options.limit
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handlePictureRemove(file, fileList) {
|
||||
this.fileList = deepClone(fileList) //this.fileList = fileList
|
||||
this.updateUploadFieldModelAndEmitDataChange(fileList)
|
||||
|
||||
this.uploadBtnHidden = fileList.length >= this.field.options.limit
|
||||
},
|
||||
|
||||
handelUploadError(err, file, fileList) {
|
||||
if (!!this.field.options.onUploadError) {
|
||||
let customFn = new Function('error', 'file', 'fileList', this.field.options.onUploadError)
|
||||
customFn.call(this, err, file, fileList)
|
||||
} else {
|
||||
this.$message({
|
||||
message: this.i18nt('render.hint.uploadError') + err,
|
||||
duration: 3000,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.hideUploadDiv {
|
||||
:deep(div.el-upload--picture-card) { /* 隐藏最后的图片上传按钮 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(div.el-upload--text) { /* 隐藏最后的文件上传按钮 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(div.el-upload__tip) { /* 隐藏最后的文件上传按钮提示 */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-radio-group ref="fieldEditor" v-model="fieldModel"
|
||||
:disabled="field.options.disabled" :size="field.options.size"
|
||||
@change="handleChangeEvent">
|
||||
<template v-if="!!field.options.buttonStyle">
|
||||
<el-radio-button v-for="(item, index) in field.options.optionItems" :key="index" :label="item.value"
|
||||
:disabled="item.disabled" :border="field.options.border"
|
||||
:style="{display: field.options.displayStyle}">{{item.label}}</el-radio-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-radio v-for="(item, index) in field.options.optionItems" :key="index" :label="item.value"
|
||||
:disabled="item.disabled" :border="field.options.border"
|
||||
:style="{display: field.options.displayStyle}">{{item.label}}</el-radio>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "radio-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initOptionItems()
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
</style>
|
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-rate ref="fieldEditor" v-model="fieldModel"
|
||||
:disabled="field.options.disabled"
|
||||
:max="field.options.max"
|
||||
:low-threshold="field.options.lowThreshold" :high-threshold="field.options.highThreshold"
|
||||
:allow-half="field.options.allowHalf"
|
||||
:show-text="field.options.showText" :show-score="field.options.showScore"
|
||||
@change="handleChangeEvent">
|
||||
</el-rate>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "rate-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<!--
|
||||
<vue-editor ref="fieldEditor" v-model="fieldModel" :editor-toolbar="customToolbar"
|
||||
:disabled="field.options.disabled" :placeholder="field.options.placeholder"
|
||||
@text-change="handleRichEditorChangeEvent"
|
||||
@focus="handleRichEditorFocusEvent" @blur="handleRichEditorBlurEvent">
|
||||
</vue-editor>
|
||||
-->
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import {deepClone} from "@/utils/util";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "rich-editor-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
|
||||
// VueEditor: resolve => { //懒加载!!
|
||||
// require(['vue2-editor'], ({VueEditor}) => resolve(VueEditor))
|
||||
// }
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
|
||||
customToolbar: [], //富文本编辑器自定义工具栏
|
||||
valueChangedFlag: false, //vue2-editor数据值是否改变标志
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleRichEditorChangeEvent() {
|
||||
this.valueChangedFlag = true
|
||||
this.syncUpdateFormModel(this.fieldModel)
|
||||
},
|
||||
|
||||
handleRichEditorFocusEvent() {
|
||||
this.oldFieldValue = deepClone(this.fieldModel)
|
||||
},
|
||||
|
||||
handleRichEditorBlurEvent() {
|
||||
if (this.valueChangedFlag) {
|
||||
this.emitFieldDataChange(this.fieldModel, this.oldFieldValue)
|
||||
this.valueChangedFlag = false
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-select ref="fieldEditor" v-model="fieldModel" class="full-width-input"
|
||||
:disabled="field.options.disabled"
|
||||
:size="field.options.size"
|
||||
:clearable="field.options.clearable"
|
||||
:filterable="field.options.filterable"
|
||||
:allow-create="field.options.allowCreate"
|
||||
:default-first-option="allowDefaultFirstOption"
|
||||
:automatic-dropdown="field.options.automaticDropdown"
|
||||
:multiple="field.options.multiple" :multiple-limit="field.options.multipleLimit"
|
||||
:placeholder="field.options.placeholder || i18nt('render.hint.selectPlaceholder')"
|
||||
:remote="this.field.options.remote" :remote-method="remoteQuery"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
<el-option v-for="item in field.options.optionItems" :key="item.value" :label="item.label"
|
||||
:value="item.value" :disabled="item.disabled">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "select-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
allowDefaultFirstOption() {
|
||||
return (!!this.field.options.filterable && !!this.field.options.allowCreate)
|
||||
},
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initOptionItems()
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-slider ref="fieldEditor" v-model="fieldModel"
|
||||
:disabled="field.options.disabled" :show-stops="field.options.showStops"
|
||||
:min="field.options.min" :max="field.options.max" :step="field.options.step"
|
||||
:range="field.options.range" :vertical="field.options.vertical"
|
||||
@change="handleChangeEvent">
|
||||
</el-slider>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "slider-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<static-content-wrapper :designer="designer" :field="field" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<div :class="[!!designState ? 'slot-wrapper-design' : 'slot-wrapper-render']">
|
||||
<!-- -->
|
||||
<slot :name="field.options.name" v-bind:formModel="formModel"></slot>
|
||||
<!-- -->
|
||||
<!-- slot :name="field.options.name"></slot -->
|
||||
<div v-if="!!designState" class="slot-title">{{field.options.label}}</div>
|
||||
</div>
|
||||
</static-content-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StaticContentWrapper from './static-content-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "slot-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
StaticContentWrapper,
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.slot-wrapper-design {
|
||||
width: 100%;
|
||||
min-height: 26px;
|
||||
background: linear-gradient(45deg, #ccc 25%, #eee 0, #eee 50%, #ccc 0, #ccc 75%, #eee 0);
|
||||
background-size: 20px 20px;
|
||||
text-align: center;
|
||||
|
||||
.slot-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.slot-wrapper-render {
|
||||
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="field-wrapper" :class="{'design-time-bottom-margin': !!this.designer}">
|
||||
<div class="static-content-item" v-show="!field.options.hidden || (designState === true)"
|
||||
:class="[selected ? 'selected' : '', customClass]" @click.stop="selectField(field)">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<template v-if="!!this.designer">
|
||||
<div class="field-action" v-if="designer.selectedId === field.id">
|
||||
<i class="el-icon-back" :title="i18nt('designer.hint.selectParentWidget')" @click.stop="selectParentWidget(field)"></i>
|
||||
<i class="el-icon-top" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveUpWidget')"
|
||||
@click.stop="moveUpWidget(field)"></i>
|
||||
<i class="el-icon-bottom" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveDownWidget')"
|
||||
@click.stop="moveDownWidget(field)"></i>
|
||||
<i class="el-icon-delete" :title="i18nt('designer.hint.remove')" @click.stop="removeFieldWidget"></i>
|
||||
</div>
|
||||
|
||||
<div class="drag-handler background-opacity" v-if="designer.selectedId === field.id">
|
||||
<i class="el-icon-rank" :title="i18nt('designer.hint.dragHandler')"></i>
|
||||
<i>{{i18n2t(`designer.widgetLabel.${field.type}`, `extension.widgetLabel.${field.type}`)}}</i>
|
||||
<i v-if="field.options.hidden === true" class="iconfont icon-hide"></i>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
|
||||
export default {
|
||||
name: "static-content-wrapper",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
designer: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selected() {
|
||||
return !!this.designer && this.field.id === this.designer.selectedId
|
||||
},
|
||||
|
||||
customClass() {
|
||||
return !!this.field.options.customClass ? this.field.options.customClass.join(' ') : ''
|
||||
},
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
selectField(field) {
|
||||
if (!!this.designer) {
|
||||
this.designer.setSelected(field)
|
||||
this.designer.emitEvent('field-selected', this.parentWidget) //发送选中组件的父组件对象
|
||||
}
|
||||
},
|
||||
|
||||
selectParentWidget() {
|
||||
if (this.parentWidget) {
|
||||
this.designer.setSelected(this.parentWidget)
|
||||
} else {
|
||||
this.designer.clearSelected()
|
||||
}
|
||||
},
|
||||
|
||||
moveUpWidget() {
|
||||
this.designer.moveUpWidget(this.parentList, this.indexOfParentList)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
moveDownWidget() {
|
||||
this.designer.moveDownWidget(this.parentList, this.indexOfParentList)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
removeFieldWidget() {
|
||||
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>
|
||||
@import "../../../../styles/global.scss";
|
||||
|
||||
.design-time-bottom-margin {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.field-wrapper {
|
||||
position: relative;
|
||||
|
||||
.field-action{
|
||||
position: absolute;
|
||||
//bottom: -24px;
|
||||
bottom: 0;
|
||||
right: -2px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
background: $--color-primary;
|
||||
z-index: 9;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin: 0 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-handler {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
//bottom: -22px; /* 拖拽手柄位于组件下方,有时无法正常拖动,原因未明?? */
|
||||
left: -1px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
//background: $--color-primary;
|
||||
z-index: 9;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
color: #fff;
|
||||
margin: 4px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
//opacity: 1;
|
||||
background: $--color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.static-content-item {
|
||||
min-height: 20px;
|
||||
display: flex; /* 垂直居中 */
|
||||
align-items: center; /* 垂直居中 */
|
||||
|
||||
:deep(.el-divider--horizontal) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item.selected, .static-content-item.selected {
|
||||
outline: 2px solid $--color-primary;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<static-content-wrapper :designer="designer" :field="field" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<div ref="fieldEditor">{{field.options.textContent}}</div>
|
||||
</static-content-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StaticContentWrapper from './static-content-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "static-text-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
StaticContentWrapper,
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; //* static-content-wrapper已引入,还需要重复引入吗? *//
|
||||
|
||||
</style>
|
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-switch ref="fieldEditor" v-model="fieldModel"
|
||||
:disabled="field.options.disabled"
|
||||
:active-text="field.options.activeText" :inactive-text="field.options.inactiveText"
|
||||
:active-color="field.options.activeColor" :inactive-color="field.options.inactiveColor"
|
||||
:width="field.options.switchWidth"
|
||||
@change="handleChangeEvent">
|
||||
</el-switch>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "switch-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-input type="textarea" ref="fieldEditor" v-model="fieldModel"
|
||||
:disabled="field.options.disabled" :readonly="field.options.readonly"
|
||||
:size="field.options.size"
|
||||
:placeholder="field.options.placeholder" :rows="field.options.rows"
|
||||
:minlength="field.options.minLength" :maxlength="field.options.maxLength"
|
||||
:show-word-limit="field.options.showWordLimit"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent" @input="handleInputCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
</el-input>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "textarea-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
</style>
|
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-time-picker ref="fieldEditor" is-range v-model="fieldModel" class="full-width-input"
|
||||
:disabled="field.options.disabled" :readonly="field.options.readonly"
|
||||
:size="field.options.size"
|
||||
:clearable="field.options.clearable" :editable="field.options.editable"
|
||||
:format="field.options.format" value-format="HH:mm:ss"
|
||||
:start-placeholder="field.options.startPlaceholder || i18nt('render.hint.startTimePlaceholder')"
|
||||
:end-placeholder="field.options.endPlaceholder || i18nt('render.hint.endTimePlaceholder')"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
</el-time-picker>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "time-range-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<form-item-wrapper :designer="designer" :field="field" :rules="rules" :design-state="designState"
|
||||
:parent-widget="parentWidget" :parent-list="parentList" :index-of-parent-list="indexOfParentList"
|
||||
:sub-form-row-index="subFormRowIndex" :sub-form-col-index="subFormColIndex" :sub-form-row-id="subFormRowId">
|
||||
<el-time-picker ref="fieldEditor" v-model="fieldModel" class="full-width-input"
|
||||
:disabled="field.options.disabled" :readonly="field.options.readonly"
|
||||
:size="field.options.size"
|
||||
:clearable="field.options.clearable" :editable="field.options.editable"
|
||||
:format="field.options.format" value-format="HH:mm:ss"
|
||||
:placeholder="field.options.placeholder || i18nt('render.hint.timePlaceholder')"
|
||||
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent"
|
||||
@change="handleChangeEvent">
|
||||
</el-time-picker>
|
||||
</form-item-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormItemWrapper from './form-item-wrapper'
|
||||
import emitter from '@/utils/emitter'
|
||||
import i18n, {translate} from "@/utils/i18n";
|
||||
import fieldMixin from "@/components/form-designer/form-widget/field-widget/fieldMixin";
|
||||
|
||||
export default {
|
||||
name: "time-widget",
|
||||
componentName: 'FieldWidget', //必须固定为FieldWidget,用于接收父级组件的broadcast事件
|
||||
mixins: [emitter, fieldMixin, i18n],
|
||||
props: {
|
||||
field: Object,
|
||||
parentWidget: Object,
|
||||
parentList: Array,
|
||||
indexOfParentList: Number,
|
||||
designer: Object,
|
||||
|
||||
designState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
subFormRowIndex: { /* 子表单组件行索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormColIndex: { /* 子表单组件列索引,从0开始计数 */
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
subFormRowId: { /* 子表单组件行Id,唯一id且不可变 */
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
},
|
||||
components: {
|
||||
FormItemWrapper,
|
||||
},
|
||||
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
|
||||
data() {
|
||||
return {
|
||||
oldFieldValue: null, //field组件change之前的值
|
||||
fieldModel: null,
|
||||
rules: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
beforeCreate() {
|
||||
/* 这里不能访问方法和属性!! */
|
||||
},
|
||||
|
||||
created() {
|
||||
/* 注意:子组件mounted在父组件created之后、父组件mounted之前触发,故子组件mounted需要用到的prop
|
||||
需要在父组件created中初始化!! */
|
||||
this.initFieldModel()
|
||||
this.registerToRefList()
|
||||
this.initEventHandler()
|
||||
this.buildFieldRules()
|
||||
|
||||
this.handleOnCreated()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.handleOnMounted()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.unregisterFromRefList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../../styles/global.scss"; /* form-item-wrapper已引入,还需要重复引入吗? */
|
||||
|
||||
.full-width-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
</style>
|
266
src/components/form-designer/form-widget/index.vue
Normal file
@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<div class="form-widget-container">
|
||||
|
||||
<el-form class="full-height-width widget-form" :label-position="labelPosition"
|
||||
:class="[customClass, layoutType + '-layout']" :size="size" :validate-on-rule-change="false">
|
||||
|
||||
<template v-if="designer.widgetList.length === 0">
|
||||
<div class="no-widget-hint">{{i18nt('designer.noWidgetHint')}}</div>
|
||||
<!-- 下面的div用于把父容器高度撑开!!
|
||||
<div class="form-widget-list"></div>
|
||||
-->
|
||||
</template>
|
||||
|
||||
<div class="form-widget-list">
|
||||
<draggable :list="designer.widgetList" item-key="id" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 300}"
|
||||
tag="transition-group" :component-data="{name: 'fade'}"
|
||||
handle=".drag-handler" @end="onDragEnd" @add="onDragAdd" @update="onDragUpdate" :move="checkMove">
|
||||
<template #item="{ element: widget, index }">
|
||||
<template v-if="'container' === widget.category">
|
||||
<component :is="getWidgetName(widget)" :widget="widget" :designer="designer" :key="widget.id" :parent-list="designer.widgetList"
|
||||
:index-of-parent-list="index" :parent-widget="null"></component>
|
||||
</template>
|
||||
<template v-else>
|
||||
<component :is="getWidgetName(widget)" :field="widget" :designer="designer" :key="widget.id" :parent-list="designer.widgetList"
|
||||
:index-of-parent-list="index" :parent-widget="null" :design-state="true"></component>
|
||||
</template>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Draggable from 'vuedraggable'
|
||||
import '@/components/form-designer/form-widget/container-widget/index'
|
||||
import FieldComponents from '@/components/form-designer/form-widget/field-widget/index'
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "VFormWidget",
|
||||
componentName: "VFormWidget",
|
||||
mixins: [i18n],
|
||||
components: {
|
||||
Draggable,
|
||||
|
||||
...FieldComponents,
|
||||
},
|
||||
props: {
|
||||
designer: Object,
|
||||
formConfig: Object,
|
||||
optionData: { //prop传入的选项数据
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
refList: this.widgetRefList,
|
||||
formConfig: this.formConfig,
|
||||
globalOptionData: this.optionData,
|
||||
getOptionData: () => this.optionData,
|
||||
globalModel: {
|
||||
formModel: this.formModel,
|
||||
}
|
||||
}
|
||||
},
|
||||
inject: ['getDesignerConfig'],
|
||||
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();
|
||||
this.designer.loadPresetCssCode( this.getDesignerConfig().presetCssCode )
|
||||
},
|
||||
mounted() {
|
||||
this.disableFirefoxDefaultDrop() /* 禁用Firefox默认拖拽搜索功能!! */
|
||||
this.designer.registerFormWidget(this)
|
||||
},
|
||||
methods: {
|
||||
getWidgetName(widget) {
|
||||
return widget.type + '-widget'
|
||||
},
|
||||
|
||||
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() { /* 在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 {
|
||||
|
||||
:deep(.el-scrollbar__wrap), :deep(.el-scrollbar__view) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.form-widget-container {
|
||||
padding: 10px;
|
||||
background: #f1f2f3;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
.el-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;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form.PC-layout {
|
||||
//
|
||||
}
|
||||
|
||||
.el-form.Pad-layout {
|
||||
margin: 0 auto;
|
||||
max-width: 960px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 1px 10px #495060;
|
||||
}
|
||||
|
||||
.el-form.H5-layout {
|
||||
margin: 0 auto;
|
||||
width: 420px;
|
||||
border-radius: 15px;
|
||||
//border-width: 10px;
|
||||
box-shadow: 0 0 1px 10px #495060;
|
||||
}
|
||||
|
||||
.el-form.widget-form :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>
|
426
src/components/form-designer/index.vue
Normal file
@ -0,0 +1,426 @@
|
||||
<!--
|
||||
/**
|
||||
* author: vformAdmin
|
||||
* email: vdpadmin@163.com
|
||||
* website: https://www.vform666.com
|
||||
* date: 2021.08.18
|
||||
* remark: 如果要分发VForm源码,需在本文件顶部保留此文件头信息!!
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<el-container class="main-container full-height">
|
||||
<el-header class="main-header">
|
||||
<div class="float-left main-title">
|
||||
<img src="../../assets/vform-logo.png" @click="openHome">
|
||||
<span class="bold">VForm</span> {{i18nt('application.productTitle')}} <span class="version-span">Ver {{vFormVersion}}</span></div>
|
||||
<div class="float-right external-link">
|
||||
<el-dropdown v-if="showLink('languageMenu')" @command="handleLanguageChanged">
|
||||
<span class="el-dropdown-link">{{curLangName}}<i class="el-icon-arrow-down el-icon--right"></i></span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="zh-CN">{{i18nt('application.zh-CN')}}</el-dropdown-item>
|
||||
<el-dropdown-item command="en-US">{{i18nt('application.en-US')}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<a v-if="showLink('externalLink')" href="javascript:void(0)" @click="(ev) => openUrl(ev, gitUrl)" target="_blank"><svg-icon icon-class="github" />{{i18nt('application.github')}}</a>
|
||||
<a v-if="showLink('externalLink')" href="javascript:void(0)" @click="(ev) => openUrl(ev, docUrl)" target="_blank"><svg-icon icon-class="document" />{{i18nt('application.document')}}</a>
|
||||
<a v-if="showLink('externalLink')" href="javascript:void(0)" @click="(ev) => openUrl(ev, chatUrl)" target="_blank">{{i18nt('application.qqGroup')}}</a>
|
||||
<a v-if="showLink('externalLink')" href="javascript:void(0)" @click="(ev) => openUrl(ev, subScribeUrl)" target="_blank">
|
||||
{{i18nt('application.subscription')}}<i class="el-icon-top-right"></i></a>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-container>
|
||||
<el-aside class="side-panel">
|
||||
<widget-panel :designer="designer" />
|
||||
</el-aside>
|
||||
|
||||
<el-container class="center-layout-container">
|
||||
<el-header class="toolbar-header">
|
||||
<toolbar-panel :designer="designer" ref="toolbarRef">
|
||||
<template v-for="(idx, slotName) in $slots" #[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
</toolbar-panel>
|
||||
</el-header>
|
||||
<el-main class="form-widget-main">
|
||||
<el-scrollbar class="container-scroll-bar" :style="{height: scrollerHeight}">
|
||||
<v-form-widget :designer="designer" :form-config="designer.formConfig">
|
||||
</v-form-widget>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<el-aside>
|
||||
<setting-panel :designer="designer" :selected-widget="designer.selectedWidget" :form-config="designer.formConfig" />
|
||||
</el-aside>
|
||||
</el-container>
|
||||
|
||||
</el-container>
|
||||
</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/form-designer/designer"
|
||||
import {addWindowResizeHandler, deepClone, getQueryParam} from "@/utils/util"
|
||||
import {MOCK_CASE_URL, VARIANT_FORM_VERSION} from "@/utils/config"
|
||||
import i18n, { changeLocale } from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "VFormDesigner",
|
||||
componentName: "VFormDesigner",
|
||||
mixins: [i18n],
|
||||
components: {
|
||||
WidgetPanel,
|
||||
ToolbarPanel,
|
||||
SettingPanel,
|
||||
VFormWidget,
|
||||
},
|
||||
props: {
|
||||
/* 后端字段列表API */
|
||||
fieldListApi: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
|
||||
/* 禁止显示的组件名称数组 */
|
||||
bannedWidgets: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
|
||||
designerConfig: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
languageMenu: true, //是否显示语言切换菜单
|
||||
externalLink: true, //是否显示GitHub、文档等外部链接
|
||||
formTemplates: true, //是否显示表单模板
|
||||
eventCollapse: true, //是否显示组件事件属性折叠面板
|
||||
clearDesignerButton: true, //是否显示清空设计器按钮
|
||||
previewFormButton: true, //是否显示预览表单按钮
|
||||
importJsonButton: true, //是否显示导入JSON按钮
|
||||
exportJsonButton: true, //是否显示导出JSON器按钮
|
||||
exportCodeButton: true, //是否显示导出代码按钮
|
||||
generateSFCButton: true, //是否显示生成SFC按钮
|
||||
|
||||
presetCssCode: '', //设计器预设CSS样式代码
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
vFormVersion: VARIANT_FORM_VERSION,
|
||||
curLangName: '',
|
||||
|
||||
vsCodeFlag: false,
|
||||
caseName: '',
|
||||
|
||||
docUrl: 'https://www.vform666.com/document.html',
|
||||
gitUrl: 'https://github.com/vform666/variant-form',
|
||||
chatUrl: 'https://www.vform666.com/chat-group.html',
|
||||
subScribeUrl: 'https://www.vform666.com/subscribe.html',
|
||||
|
||||
scrollerHeight: 0,
|
||||
|
||||
designer: createDesigner(this),
|
||||
|
||||
fieldList: []
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
serverFieldList: this.fieldList,
|
||||
getDesignerConfig: () => this.designerConfig,
|
||||
getBannedWidgets: () => this.bannedWidgets,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.vsCodeFlag = getQueryParam('vscode') == 1
|
||||
this.caseName = getQueryParam('case')
|
||||
},
|
||||
mounted() {
|
||||
this.initLocale()
|
||||
|
||||
this.scrollerHeight = window.innerHeight - 56 - 36 + 'px'
|
||||
addWindowResizeHandler(() => {
|
||||
this.$nextTick(() => {
|
||||
this.scrollerHeight = window.innerHeight - 56 - 36 + 'px'
|
||||
})
|
||||
})
|
||||
|
||||
this.loadCase()
|
||||
|
||||
this.loadFieldListFromServer()
|
||||
},
|
||||
methods: {
|
||||
showLink(configName) {
|
||||
if (this.designerConfig[configName] === undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !!this.designerConfig[configName]
|
||||
},
|
||||
|
||||
openHome() {
|
||||
if (!!this.vsCodeFlag) {
|
||||
const msgObj = {
|
||||
cmd: 'openUrl',
|
||||
data: {
|
||||
url: 'https://www.vform666.com/'
|
||||
}
|
||||
}
|
||||
window.parent.postMessage(msgObj, '*')
|
||||
}
|
||||
},
|
||||
|
||||
openUrl(event, url) {
|
||||
if (!!this.vsCodeFlag) {
|
||||
const msgObj = {
|
||||
cmd: 'openUrl',
|
||||
data: {
|
||||
url
|
||||
}
|
||||
}
|
||||
window.parent.postMessage(msgObj, '*')
|
||||
} else {
|
||||
let aDom = event.currentTarget
|
||||
aDom.href = url
|
||||
//window.open(url, '_blank') //直接打开新窗口,会被浏览器拦截
|
||||
}
|
||||
},
|
||||
|
||||
loadCase() {
|
||||
if (!this.caseName) {
|
||||
return
|
||||
}
|
||||
|
||||
axios.get(MOCK_CASE_URL + this.caseName + '.txt').then(res => {
|
||||
if (!!res.data.code) {
|
||||
this.$message.error(this.i18nt('designer.hint.sampleLoadedFail'))
|
||||
return
|
||||
}
|
||||
|
||||
this.setFormJson(res.data)
|
||||
this.$message.success(this.i18nt('designer.hint.sampleLoadedSuccess'))
|
||||
}).catch(error => {
|
||||
this.$message.error(this.i18nt('designer.hint.sampleLoadedFail') + ':' + error)
|
||||
})
|
||||
},
|
||||
|
||||
initLocale() {
|
||||
let curLocale = localStorage.getItem('v_form_locale')
|
||||
if (!!this.vsCodeFlag) {
|
||||
curLocale = curLocale || 'en-US'
|
||||
} else {
|
||||
curLocale = curLocale || 'zh-CN'
|
||||
}
|
||||
this.curLangName = this.i18nt('application.' + curLocale)
|
||||
this.changeLanguage(curLocale)
|
||||
},
|
||||
|
||||
loadFieldListFromServer() {
|
||||
if (!this.fieldListApi) {
|
||||
return
|
||||
}
|
||||
|
||||
axios.get(this.fieldListApi.URL).then(res => {
|
||||
let labelKey = this.fieldListApi.labelKey || 'label'
|
||||
let nameKey = this.fieldListApi.nameKey || 'name'
|
||||
|
||||
res.data.forEach(fieldItem => {
|
||||
this.fieldList.push({
|
||||
label: fieldItem[labelKey],
|
||||
name: fieldItem[nameKey]
|
||||
})
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$message.error(error)
|
||||
})
|
||||
},
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getFormJson() {
|
||||
return {
|
||||
widgetList: deepClone(this.designer.widgetList),
|
||||
formConfig: deepClone(this.designer.formConfig)
|
||||
}
|
||||
},
|
||||
|
||||
clearDesigner() {
|
||||
this.$refs.toolbarRef.clearFormWidget()
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 刷新表单设计器
|
||||
*/
|
||||
refreshDesigner() {
|
||||
//this.designer.loadFormJson( this.getFormJson() ) //只有第一次调用生效??
|
||||
|
||||
let fJson = this.getFormJson()
|
||||
this.designer.clearDesigner(true) //不触发历史记录变更
|
||||
this.designer.loadFormJson(fJson)
|
||||
},
|
||||
|
||||
/**
|
||||
* 预览表单
|
||||
*/
|
||||
previewForm() {
|
||||
this.$refs.toolbarRef.previewForm()
|
||||
},
|
||||
|
||||
/**
|
||||
* 导入表单JSON
|
||||
*/
|
||||
importJson() {
|
||||
this.$refs.toolbarRef.importJson()
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出表单JSON
|
||||
*/
|
||||
exportJson() {
|
||||
this.$refs.toolbarRef.exportJson()
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出Vue/HTML代码
|
||||
*/
|
||||
exportCode() {
|
||||
this.$refs.toolbarRef.exportCode()
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成SFC代码
|
||||
*/
|
||||
generateSFC() {
|
||||
this.$refs.toolbarRef.generateSFC()
|
||||
},
|
||||
|
||||
//TODO: 增加更多方法!!
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-container.full-height {
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.el-container.center-layout-container {
|
||||
min-width: 680px;
|
||||
border-left: 2px dotted #EBEEF5;
|
||||
border-right: 2px dotted #EBEEF5;
|
||||
}
|
||||
|
||||
.el-header.main-header {
|
||||
border-bottom: 2px dotted #EBEEF5;
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
div.main-title {
|
||||
font-size: 18px;
|
||||
color: #242424;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.el-header.toolbar-header {
|
||||
font-size: 14px;
|
||||
border-bottom: 1px dotted #CCCCCC;
|
||||
height: 42px !important;
|
||||
//line-height: 42px !important;
|
||||
}
|
||||
|
||||
.el-aside.side-panel {
|
||||
width: 260px !important;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.el-main.form-widget-main {
|
||||
padding: 0;
|
||||
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container-scroll-bar {
|
||||
:deep(.el-scrollbar__wrap), :deep(.el-scrollbar__view) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
330
src/components/form-designer/setting-panel/form-setting.vue
Normal file
@ -0,0 +1,330 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="formConfig" size="mini" label-position="left" label-width="120px"
|
||||
class="setting-form" @submit.prevent>
|
||||
<el-collapse v-model="formActiveCollapseNames" class="setting-collapse">
|
||||
<el-collapse-item name="1" :title="i18nt('designer.setting.basicSetting')">
|
||||
<el-form-item :label="i18nt('designer.setting.formSize')">
|
||||
<el-select v-model="formConfig.size">
|
||||
<el-option v-for="item in formSizes" :key="item.value" :label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.labelPosition')">
|
||||
<el-radio-group v-model="formConfig.labelPosition" class="radio-group-custom">
|
||||
<el-radio-button label="left">{{i18nt('designer.setting.leftPosition')}}</el-radio-button>
|
||||
<el-radio-button label="top">{{i18nt('designer.setting.topPosition')}}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.labelAlign')">
|
||||
<el-radio-group v-model="formConfig.labelAlign" class="radio-group-custom">
|
||||
<el-radio-button label="label-left-align">{{i18nt('designer.setting.leftAlign')}}</el-radio-button>
|
||||
<el-radio-button label="label-center-align">{{i18nt('designer.setting.centerAlign')}}</el-radio-button>
|
||||
<el-radio-button label="label-right-align">{{i18nt('designer.setting.rightAlign')}}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.labelWidth')">
|
||||
<el-input-number v-model="formConfig.labelWidth" :min="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.formCss')">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editFormCss">{{i18nt('designer.setting.addCss')}}</el-button>
|
||||
</el-form-item>
|
||||
<!-- -->
|
||||
<el-form-item :label="i18nt('designer.setting.customClass')">
|
||||
<el-select v-model="formConfig.customClass" multiple filterable allow-create
|
||||
default-first-option>
|
||||
<el-option v-for="(item, idx) in cssClassList" :key="idx" :label="item" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- -->
|
||||
<el-form-item :label="i18nt('designer.setting.globalFunctions')">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editGlobalFunctions">{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="0">
|
||||
<el-divider class="custom-divider">{{i18nt('designer.setting.formSFCSetting')}}</el-divider>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.formModelName')">
|
||||
<el-input type="text" v-model="formConfig.modelName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.formRefName')">
|
||||
<el-input type="text" v-model="formConfig.refName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.formRulesName')">
|
||||
<el-input type="text" v-model="formConfig.rulesName"></el-input>
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item v-if="showEventCollapse()" name="2" :title="i18nt('designer.setting.eventSetting')">
|
||||
<el-form-item label="onFormCreated" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editFormEventHandler('onFormCreated')">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="onFormMounted" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editFormEventHandler('onFormMounted')">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
<!-- -->
|
||||
<el-form-item label="onFormDataChange" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editFormEventHandler('onFormDataChange')">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
<!-- -->
|
||||
<!--
|
||||
<el-form-item label="onFormValidate">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editFormEventHandler('onFormValidate')">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
-->
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-form>
|
||||
|
||||
<el-dialog :title="i18nt('designer.setting.editFormEventHandler')" v-model="showFormEventDialogFlag"
|
||||
v-if="showFormEventDialogFlag" :show-close="true" custom-class="small-padding-dialog"
|
||||
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
|
||||
<el-alert type="info" :closable="false" :title="'form.' + eventParamsMap[curEventName]"></el-alert>
|
||||
<code-editor :mode="'javascript'" :readonly="false" v-model="formEventHandlerCode"></code-editor>
|
||||
<el-alert type="info" :closable="false" title="}"></el-alert>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showFormEventDialogFlag = false">
|
||||
{{i18nt('designer.hint.cancel')}}</el-button>
|
||||
<el-button type="primary" @click="saveFormEventHandler">
|
||||
{{i18nt('designer.hint.confirm')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="i18nt('designer.setting.formCss')" v-model="showEditFormCssDialogFlag"
|
||||
v-if="showEditFormCssDialogFlag" :show-close="true" custom-class="small-padding-dialog"
|
||||
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
|
||||
<code-editor :mode="'css'" :readonly="false" v-model="formCssCode"></code-editor>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showEditFormCssDialogFlag = false">
|
||||
{{i18nt('designer.hint.cancel')}}</el-button>
|
||||
<el-button type="primary" @click="saveFormCss">
|
||||
{{i18nt('designer.hint.confirm')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="i18nt('designer.setting.globalFunctions')" v-model="showEditFunctionsDialogFlag"
|
||||
v-if="showEditFunctionsDialogFlag" :show-close="true" custom-class="small-padding-dialog"
|
||||
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
|
||||
<code-editor :mode="'javascript'" :readonly="false" v-model="functionsCode"></code-editor>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showEditFunctionsDialogFlag = false">
|
||||
{{i18nt('designer.hint.cancel')}}</el-button>
|
||||
<el-button type="primary" @click="saveGlobalFunctions">
|
||||
{{i18nt('designer.hint.confirm')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import CodeEditor from '@/components/code-editor/index'
|
||||
import {deepClone, insertCustomCssToHead, insertGlobalFunctionsToHtml} from "@/utils/util"
|
||||
|
||||
export default {
|
||||
name: "form-setting",
|
||||
mixins: [i18n],
|
||||
components: {
|
||||
CodeEditor,
|
||||
},
|
||||
props: {
|
||||
designer: Object,
|
||||
formConfig: Object,
|
||||
},
|
||||
inject: ['getDesignerConfig'],
|
||||
data() {
|
||||
return {
|
||||
designerConfig: this.getDesignerConfig(),
|
||||
|
||||
formActiveCollapseNames: ['1', '2'],
|
||||
|
||||
formSizes: [
|
||||
{label: 'default', value: ''},
|
||||
{label: 'large', value: 'large'},
|
||||
{label: 'medium', value: 'medium'},
|
||||
{label: 'small', value: 'small'},
|
||||
{label: 'mini', value: 'mini'},
|
||||
],
|
||||
|
||||
showEditFormCssDialogFlag: false,
|
||||
formCssCode: '',
|
||||
cssClassList: [],
|
||||
|
||||
showEditFunctionsDialogFlag: false,
|
||||
functionsCode: '',
|
||||
|
||||
showFormEventDialogFlag: false,
|
||||
formEventHandlerCode: '',
|
||||
curEventName: '',
|
||||
|
||||
eventParamsMap: {
|
||||
'onFormCreated': 'onFormCreated() {',
|
||||
'onFormMounted': 'onFormMounted() {',
|
||||
'onFormDataChange': 'onFormDataChange(fieldName, newValue, oldValue, formModel, subFormName, subFormRowIndex) {',
|
||||
//'onFormValidate': 'onFormValidate() {',
|
||||
},
|
||||
|
||||
}
|
||||
},
|
||||
created() {
|
||||
//导入表单JSON后需要重新加载自定义CSS样式!!!
|
||||
this.designer.handleEvent('form-json-imported', () => {
|
||||
this.formCssCode = this.formConfig.cssCode
|
||||
insertCustomCssToHead(this.formCssCode)
|
||||
this.extractCssClass()
|
||||
this.designer.emitEvent('form-css-updated', deepClone(this.cssClassList))
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
/* SettingPanel和FormWidget为兄弟组件, 在FormWidget加载formConfig时,
|
||||
此处SettingPanel可能无法获取到formConfig.cssCode, 故加个延时函数! */
|
||||
setTimeout(() => {
|
||||
this.formCssCode = this.formConfig.cssCode
|
||||
insertCustomCssToHead(this.formCssCode)
|
||||
this.extractCssClass()
|
||||
this.designer.emitEvent('form-css-updated', deepClone(this.cssClassList))
|
||||
}, 1200)
|
||||
},
|
||||
methods: {
|
||||
showEventCollapse() {
|
||||
if (this.designerConfig['eventCollapse'] === undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !!this.designerConfig['eventCollapse']
|
||||
},
|
||||
|
||||
editFormCss() {
|
||||
this.formCssCode = this.designer.formConfig.cssCode
|
||||
this.showEditFormCssDialogFlag = true
|
||||
},
|
||||
|
||||
extractCssClass() {
|
||||
let regExp = /\..*{/g
|
||||
let result = this.formCssCode.match(regExp)
|
||||
let cssNameArray = []
|
||||
|
||||
if (!!result && result.length > 0) {
|
||||
result.forEach((rItem) => {
|
||||
let classArray = rItem.split(',') //切分逗号分割的多个class
|
||||
if (classArray.length > 0) {
|
||||
classArray.forEach((cItem) => {
|
||||
let caItem = cItem.trim()
|
||||
if (caItem.indexOf('.', 1) !== -1) { //查找第二个.位置
|
||||
let newClass = caItem.substring(caItem.indexOf('.') + 1, caItem.indexOf('.', 1)) //仅截取第一、二个.号之间的class
|
||||
if (!!newClass) {
|
||||
cssNameArray.push(newClass.trim())
|
||||
}
|
||||
} else if (caItem.indexOf(' ') !== -1) { //查找第一个空格位置
|
||||
let newClass = caItem.substring(caItem.indexOf('.') + 1, caItem.indexOf(' ')) //仅截取第一、二个.号之间的class
|
||||
if (!!newClass) {
|
||||
cssNameArray.push(newClass.trim())
|
||||
}
|
||||
} else {
|
||||
if (caItem.indexOf('{') !== -1) { //查找第一个{位置
|
||||
let newClass = caItem.substring(caItem.indexOf('.') + 1, caItem.indexOf('{'))
|
||||
cssNameArray.push( newClass.trim() )
|
||||
} else {
|
||||
let newClass = caItem.substring(caItem.indexOf('.') + 1)
|
||||
cssNameArray.push( newClass.trim() )
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//this.cssClassList.length = 0
|
||||
this.cssClassList.splice(0, this.cssClassList.length) //清除数组必须用splice,length=0不会响应式更新!!
|
||||
this.cssClassList = Array.from( new Set(cssNameArray) ) //数组去重
|
||||
},
|
||||
|
||||
saveFormCss() {
|
||||
this.extractCssClass()
|
||||
this.designer.formConfig.cssCode = this.formCssCode
|
||||
insertCustomCssToHead(this.formCssCode)
|
||||
this.showEditFormCssDialogFlag = false
|
||||
|
||||
this.designer.emitEvent('form-css-updated', deepClone(this.cssClassList))
|
||||
},
|
||||
|
||||
editGlobalFunctions() {
|
||||
this.functionsCode = this.designer.formConfig.functions
|
||||
this.showEditFunctionsDialogFlag = true
|
||||
},
|
||||
|
||||
saveGlobalFunctions() {
|
||||
this.designer.formConfig.functions = this.functionsCode
|
||||
insertGlobalFunctionsToHtml(this.functionsCode)
|
||||
this.showEditFunctionsDialogFlag = false
|
||||
},
|
||||
|
||||
editFormEventHandler(eventName) {
|
||||
this.curEventName = eventName
|
||||
this.formEventHandlerCode = this.formConfig[eventName]
|
||||
this.showFormEventDialogFlag = true
|
||||
},
|
||||
|
||||
saveFormEventHandler() {
|
||||
this.formConfig[this.curEventName] = this.formEventHandlerCode
|
||||
this.showFormEventDialogFlag = false
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.setting-form {
|
||||
:deep(.el-form-item__label) {
|
||||
font-size: 13px;
|
||||
//text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.el-form-item--mini.el-form-item) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.radio-group-custom {
|
||||
:deep(.el-radio-button__inner) {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-divider.el-divider--horizontal {
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-collapse {
|
||||
:deep(.el-collapse-item__content) {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__header) {
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.small-padding-dialog {
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 6px 15px 12px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
331
src/components/form-designer/setting-panel/index.vue
Normal file
@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<el-container class="panel-container">
|
||||
<el-tabs :active-name="activeTab" style="height: 100%; overflow: hidden">
|
||||
<el-tab-pane :label="i18nt('designer.hint.widgetSetting')" name="1">
|
||||
<el-scrollbar class="setting-scrollbar" :style="{height: scrollerHeight}">
|
||||
|
||||
<template v-if="!!designer.selectedWidget && !designer.selectedWidget.category">
|
||||
<el-form :model="optionModel" size="mini" label-position="left" label-width="120px" class="setting-form"
|
||||
@submit.prevent>
|
||||
<el-collapse v-model="widgetActiveCollapseNames" class="setting-collapse">
|
||||
<el-collapse-item name="1" v-if="showCollapse(commonProps)" :title="i18nt('designer.setting.commonSetting')">
|
||||
<template v-for="(editorName, propName) in commonProps">
|
||||
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
|
||||
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item name="2" v-if="showCollapse(advProps)" :title="i18nt('designer.setting.advancedSetting')">
|
||||
<template v-for="(editorName, propName) in advProps">
|
||||
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
|
||||
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item name="3" v-if="showEventCollapse() && showCollapse(eventProps)" :title="i18nt('designer.setting.eventSetting')">
|
||||
<template v-for="(editorName, propName) in eventProps">
|
||||
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
|
||||
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<template v-if="(!!designer.selectedWidget && !!designer.selectedWidget.category)">
|
||||
<el-form :model="optionModel" size="mini" label-position="left" label-width="120px" class="setting-form"
|
||||
@submit.prevent>
|
||||
<el-collapse v-model="widgetActiveCollapseNames" class="setting-collapse">
|
||||
<el-collapse-item name="1" v-if="showCollapse(commonProps)" :title="i18nt('designer.setting.commonSetting')">
|
||||
<template v-for="(editorName, propName) in commonProps">
|
||||
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
|
||||
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item name="2" v-if="showCollapse(advProps)" :title="i18nt('designer.setting.advancedSetting')">
|
||||
<template v-for="(editorName, propName) in advProps">
|
||||
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
|
||||
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item name="3" v-if="showEventCollapse() && showCollapse(eventProps)" :title="i18nt('designer.setting.eventSetting')">
|
||||
<template v-for="(editorName, propName) in eventProps">
|
||||
<component v-if="hasPropEditor(propName, editorName)" :is="getPropEditor(propName, editorName)"
|
||||
:designer="designer" :selected-widget="selectedWidget" :option-model="optionModel"></component>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="!!designer" :label="i18nt('designer.hint.formSetting')" name="2">
|
||||
<el-scrollbar class="setting-scrollbar" :style="{height: scrollerHeight}">
|
||||
<form-setting :designer="designer" :form-config="formConfig"></form-setting>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-dialog :title="i18nt('designer.setting.editWidgetEventHandler')" v-model="showWidgetEventDialogFlag"
|
||||
v-if="showWidgetEventDialogFlag" :show-close="true" custom-class="small-padding-dialog"
|
||||
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
|
||||
<el-alert type="info" :closable="false" :title="eventHeader"></el-alert>
|
||||
<code-editor :mode="'javascript'" :readonly="false" v-model="eventHandlerCode"></code-editor>
|
||||
<el-alert type="info" :closable="false" title="}"></el-alert>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showWidgetEventDialogFlag = false">
|
||||
{{i18nt('designer.hint.cancel')}}</el-button>
|
||||
<el-button type="primary" @click="saveEventHandler">
|
||||
{{i18nt('designer.hint.confirm')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeEditor from '@/components/code-editor/index'
|
||||
import PropertyEditors from './property-editor/index'
|
||||
import FormSetting from './form-setting'
|
||||
import WidgetProperties from './propertyRegister'
|
||||
import {
|
||||
addWindowResizeHandler,
|
||||
} from "@/utils/util"
|
||||
import i18n from "@/utils/i18n"
|
||||
import eventBus from "@/utils/event-bus"
|
||||
|
||||
const {COMMON_PROPERTIES, ADVANCED_PROPERTIES, EVENT_PROPERTIES} = WidgetProperties
|
||||
|
||||
export default {
|
||||
name: "SettingPanel",
|
||||
componentName: "SettingPanel",
|
||||
mixins: [i18n],
|
||||
components: {
|
||||
CodeEditor,
|
||||
FormSetting,
|
||||
...PropertyEditors,
|
||||
},
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
formConfig: Object,
|
||||
},
|
||||
inject: ['getDesignerConfig'],
|
||||
data() {
|
||||
return {
|
||||
designerConfig: this.getDesignerConfig(),
|
||||
|
||||
scrollerHeight: 0,
|
||||
|
||||
activeTab: "2",
|
||||
widgetActiveCollapseNames: ['1', '3'], //['1', '2', '3'],
|
||||
formActiveCollapseNames: ['1', '2'],
|
||||
|
||||
commonProps: COMMON_PROPERTIES,
|
||||
advProps: ADVANCED_PROPERTIES,
|
||||
eventProps: EVENT_PROPERTIES,
|
||||
|
||||
showWidgetEventDialogFlag: false,
|
||||
eventHandlerCode: '',
|
||||
curEventName: '',
|
||||
eventHeader: '',
|
||||
|
||||
subFormChildWidgetFlag: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionModel: {
|
||||
get() {
|
||||
return this.selectedWidget.options
|
||||
},
|
||||
|
||||
set(newValue) {
|
||||
this.selectedWidget.options = newValue
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
'designer.selectedWidget': {
|
||||
handler(val) {
|
||||
if (!!val) {
|
||||
this.activeTab = "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'selectedWidget.options': { //组件属性变动后,立即保存表单JSON!!
|
||||
deep: true,
|
||||
handler() {
|
||||
this.designer.saveCurrentHistoryStep()
|
||||
}
|
||||
},
|
||||
|
||||
formConfig: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.designer.saveCurrentHistoryStep()
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
created() {
|
||||
eventBus.$on('editEventHandler', function (eventName, eventParams) {
|
||||
this.editEventHandler(eventName, eventParams)
|
||||
})
|
||||
|
||||
this.designer.handleEvent('form-css-updated', (cssClassList) => {
|
||||
this.designer.setCssClassList(cssClassList)
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
if (!this.designer.selectedWidget) {
|
||||
this.activeTab = "2"
|
||||
} else {
|
||||
this.activeTab = "1"
|
||||
}
|
||||
|
||||
this.scrollerHeight = window.innerHeight - 56 - 48 + 'px'
|
||||
addWindowResizeHandler(() => {
|
||||
this.$nextTick(() => {
|
||||
this.scrollerHeight = window.innerHeight - 56 - 48 + 'px'
|
||||
//console.log(this.scrollerHeight)
|
||||
})
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
showEventCollapse() {
|
||||
if (this.designerConfig['eventCollapse'] === undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !!this.designerConfig['eventCollapse']
|
||||
},
|
||||
|
||||
hasPropEditor(propName, editorName) {
|
||||
if (!editorName) {
|
||||
return false
|
||||
}
|
||||
|
||||
let originalPropName = propName.replace(this.selectedWidget.type + '-', '') //去掉组件名称前缀-,如果有的话!!
|
||||
return this.designer.hasConfig(this.selectedWidget, originalPropName)
|
||||
},
|
||||
|
||||
getPropEditor(propName, editorName) {
|
||||
let originalPropName = propName.replace(this.selectedWidget.type + '-', '') //去掉组件名称前缀-,如果有的话!!
|
||||
let ownPropEditorName = `${this.selectedWidget.type}-${originalPropName}-editor`
|
||||
//console.log(ownPropEditorName, this.$options.components[ownPropEditorName])
|
||||
if (!!this.$options.components[ownPropEditorName]) { //局部注册的属性编辑器组件
|
||||
return ownPropEditorName
|
||||
}
|
||||
|
||||
return !!this.$root.$options.components[ownPropEditorName] ? ownPropEditorName : editorName //全局注册的属性编辑器组件
|
||||
},
|
||||
|
||||
showCollapse(propsObj) {
|
||||
let result = false
|
||||
|
||||
for (let propName in propsObj) {
|
||||
if (!propsObj.hasOwnProperty(propName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (this.hasPropEditor(propName, propsObj[propName])) {
|
||||
result = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
editEventHandler(eventName, eventParams) {
|
||||
this.curEventName = eventName
|
||||
this.eventHeader = `${this.optionModel.name}.${eventName}(${eventParams.join(', ')}) {`
|
||||
this.eventHandlerCode = this.selectedWidget.options[eventName] || ''
|
||||
|
||||
// 设置字段校验函数示例代码
|
||||
if ((eventName === 'onValidate') && (!this.optionModel['onValidate'])) {
|
||||
this.eventHandlerCode = " /* sample code */\n /*\n if ((value > 100) || (value < 0)) {\n callback(new Error('error message')) //fail\n } else {\n callback(); //pass\n }\n */"
|
||||
}
|
||||
|
||||
this.showWidgetEventDialogFlag = true
|
||||
},
|
||||
|
||||
saveEventHandler() {
|
||||
this.selectedWidget.options[this.curEventName] = this.eventHandlerCode
|
||||
this.showWidgetEventDialogFlag = false
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.panel-container {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.setting-scrollbar {
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
overflow-x: hidden; /* IE浏览器隐藏水平滚动条箭头!! */
|
||||
}
|
||||
}
|
||||
|
||||
.setting-collapse {
|
||||
:deep(.el-collapse-item__content) {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__header) {
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-form {
|
||||
:deep(.el-form-item__label) {
|
||||
font-size: 13px;
|
||||
//text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.el-form-item--mini.el-form-item) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 隐藏Chrome浏览器中el-input数字输入框右侧的上下调整小箭头 */
|
||||
:deep(.hide-spin-button) input::-webkit-outer-spin-button,
|
||||
:deep(.hide-spin-button) input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none !important;
|
||||
}
|
||||
|
||||
/* 隐藏Firefox浏览器中el-input数字输入框右侧的上下调整小箭头 */
|
||||
:deep(.hide-spin-button) input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
:deep(.custom-divider.el-divider--horizontal) {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
:deep(.custom-divider-margin-top.el-divider--horizontal) {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.small-padding-dialog {
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 6px 15px 12px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<div class="option-items-pane">
|
||||
<el-radio-group v-if="(selectedWidget.type === 'radio') || ((selectedWidget.type === 'select') && !selectedWidget.options.multiple)"
|
||||
v-model="optionModel.defaultValue" @change="emitDefaultValueChange">
|
||||
<draggable tag="ul" :list="optionModel.optionItems"
|
||||
v-bind="{group:'optionsGroup', ghostClass: 'ghost', handle: '.drag-option'}">
|
||||
<template #item="{ element: option, index: idx }">
|
||||
<li>
|
||||
<el-radio :label="option.value">
|
||||
<el-input v-model="option.value" size="mini" style="width: 100px"></el-input>
|
||||
<el-input v-model="option.label" size="mini" style="width: 100px"></el-input>
|
||||
<i class="iconfont icon-drag drag-option"></i>
|
||||
<el-button circle plain size="mini" type="danger" @click="deleteOption(option, idx)"
|
||||
icon="el-icon-minus" class="col-delete-button"></el-button>
|
||||
</el-radio>
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
</el-radio-group>
|
||||
<el-checkbox-group v-else-if="(selectedWidget.type === 'checkbox') || ((selectedWidget.type === 'select') && selectedWidget.options.multiple)"
|
||||
v-model="optionModel.defaultValue" @change="emitDefaultValueChange">
|
||||
<draggable tag="ul" :list="optionModel.optionItems"
|
||||
v-bind="{group:'optionsGroup', ghostClass: 'ghost', handle: '.drag-option'}">
|
||||
<template #item="{ element: option, index: idx }">
|
||||
<li>
|
||||
<el-checkbox :label="option.value">
|
||||
<el-input v-model="option.value" size="mini" style="width: 100px"></el-input>
|
||||
<el-input v-model="option.label" size="mini" style="width: 100px"></el-input>
|
||||
<i class="iconfont icon-drag drag-option"></i>
|
||||
<el-button circle plain size="mini" type="danger" @click="deleteOption(option, idx)"
|
||||
icon="el-icon-minus" class="col-delete-button"></el-button>
|
||||
</el-checkbox>
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
</el-checkbox-group>
|
||||
<el-cascader v-else-if="(selectedWidget.type === 'cascader')"
|
||||
v-model="optionModel.defaultValue" :options="optionModel.optionItems"
|
||||
@change="emitDefaultValueChange"
|
||||
:placeholder="i18nt('render.hint.selectPlaceholder')" style="width: 100%">
|
||||
</el-cascader>
|
||||
<div v-if="(selectedWidget.type === 'cascader')">
|
||||
<el-button type="text" @click="importCascaderOptions">{{i18nt('designer.setting.importOptions')}}</el-button>
|
||||
<el-button type="text" @click="resetDefault">{{i18nt('designer.setting.resetDefault')}}</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="(selectedWidget.type === 'radio') || (selectedWidget.type === 'checkbox') || (selectedWidget.type === 'select')">
|
||||
<el-button type="text" @click="addOption">{{i18nt('designer.setting.addOption')}}</el-button>
|
||||
<el-button type="text" @click="importOptions">{{i18nt('designer.setting.importOptions')}}</el-button>
|
||||
<el-button type="text" @click="resetDefault">{{i18nt('designer.setting.resetDefault')}}</el-button>
|
||||
</div>
|
||||
|
||||
<el-dialog :title="i18nt('designer.setting.importOptions')" v-model="showImportDialogFlag"
|
||||
v-if="showImportDialogFlag" :show-close="true" custom-class="small-padding-dialog"
|
||||
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
|
||||
<el-form-item>
|
||||
<el-input type="textarea" rows="10" v-model="optionLines"></el-input>
|
||||
</el-form-item>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="large" type="primary" @click="saveOptions">{{i18nt('designer.hint.confirm')}}</el-button>
|
||||
<el-button size="large" type="" @click="showImportDialogFlag = false">{{i18nt('designer.hint.cancel')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="i18nt('designer.setting.importOptions')" v-model="showImportCascaderDialogFlag"
|
||||
v-if="showImportCascaderDialogFlag" :show-close="true" custom-class="small-padding-dialog"
|
||||
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
|
||||
<code-editor v-model="cascaderOptions" mode="json" :readonly="false"></code-editor>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="large" type="primary" @click="saveCascaderOptions">{{i18nt('designer.hint.confirm')}}</el-button>
|
||||
<el-button size="large" type="" @click="showImportCascaderDialogFlag = false">{{i18nt('designer.hint.cancel')}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Draggable from 'vuedraggable'
|
||||
import CodeEditor from '@/components/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.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默认样式 */
|
||||
}
|
||||
|
||||
li.ghost{
|
||||
background: #fff;
|
||||
border: 2px dotted $--color-primary;
|
||||
}
|
||||
|
||||
.drag-option {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.small-padding-dialog :deep(.el-dialog__body) {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.dialog-footer .el-button {
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,173 @@
|
||||
import {translate} from "@/utils/i18n"
|
||||
import emitter from '@/utils/emitter'
|
||||
|
||||
export const createInputTextEditor = function (propName, propLabelKey) {
|
||||
return {
|
||||
props: {
|
||||
optionModel: Object,
|
||||
},
|
||||
render(h) {
|
||||
return (
|
||||
<el-form-item label={translate(propLabelKey)}>
|
||||
<el-input type="text" v-model={this.optionModel[propName]} />
|
||||
</el-form-item>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createInputNumberEditor = function (propName, propLabelKey) {
|
||||
return {
|
||||
props: {
|
||||
optionModel: Object,
|
||||
},
|
||||
methods: {
|
||||
updateValue(newValue) {
|
||||
if ((newValue === undefined) || (newValue === null) || isNaN(newValue)) {
|
||||
this.optionModel[propName] = null
|
||||
} else {
|
||||
this.optionModel[propName] = Number(newValue)
|
||||
}
|
||||
},
|
||||
},
|
||||
render(h) {
|
||||
return (
|
||||
<el-form-item label={translate(propLabelKey)}>
|
||||
<el-input-number type="text" v-model={this.optionModel[propName]}
|
||||
onChange={this.updateValue} style="width: 100%" />
|
||||
</el-form-item>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createBooleanEditor = function (propName, propLabelKey) {
|
||||
return {
|
||||
props: {
|
||||
optionModel: Object,
|
||||
},
|
||||
render(h) {
|
||||
return (
|
||||
<el-form-item label={translate(propLabelKey)}>
|
||||
<el-checkbox v-model={this.optionModel[propName]} />
|
||||
</el-form-item>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createCheckboxGroupEditor = function (propName, propLabelKey, configs) {
|
||||
return {
|
||||
props: {
|
||||
optionModel: Object,
|
||||
},
|
||||
render(h) {
|
||||
return (
|
||||
<el-form-item label={translate(propLabelKey)}>
|
||||
<el-checkbox-group v-model={this.optionModel[propName]}>
|
||||
{
|
||||
configs.optionItems.map(item => {
|
||||
return <el-checkbox label={item.value}>{item.label}</el-checkbox>
|
||||
})
|
||||
}
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const createRadioGroupEditor = function (propName, propLabelKey, configs) {
|
||||
return {
|
||||
props: {
|
||||
optionModel: Object,
|
||||
},
|
||||
render(h) {
|
||||
return (
|
||||
<el-form-item label={translate(propLabelKey)}>
|
||||
<el-radio-group v-model={this.optionModel[propName]}>
|
||||
{
|
||||
configs.optionItems.map(item => {
|
||||
return <el-radio label={item.value}>{item.label}</el-radio>
|
||||
})
|
||||
}
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createRadioButtonGroupEditor = function (propName, propLabelKey, configs) {
|
||||
return {
|
||||
props: {
|
||||
optionModel: Object,
|
||||
},
|
||||
render(h) {
|
||||
return (
|
||||
<el-form-item label={translate(propLabelKey)}>
|
||||
<el-radio-group v-model={this.optionModel[propName]}>
|
||||
{
|
||||
configs.optionItems.map(item => {
|
||||
return <el-radio-button label={item.value}>{item.label}</el-radio-button>
|
||||
})
|
||||
}
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createSelectEditor = function (propName, propLabelKey, configs) {
|
||||
return {
|
||||
props: {
|
||||
optionModel: Object,
|
||||
},
|
||||
render(h) {
|
||||
return (
|
||||
<el-form-item label={translate(propLabelKey)}>
|
||||
<el-select v-model={this.optionModel[propName]}>
|
||||
{
|
||||
configs.optionItems.map(item => {
|
||||
return <el-option label={item.label} value={item.value} />
|
||||
})
|
||||
}
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createEventHandlerEditor = function (eventPropName, eventParams) {
|
||||
return {
|
||||
props: {
|
||||
optionModel: Object,
|
||||
},
|
||||
mixins: [emitter],
|
||||
methods: {
|
||||
editEventHandler() {
|
||||
this.dispatch('SettingPanel', 'editEventHandler', [eventPropName, [...eventParams]])
|
||||
},
|
||||
},
|
||||
render(h) {
|
||||
return (
|
||||
<el-form-item label={eventPropName} label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round onClick={this.editEventHandler}>
|
||||
{translate('designer.setting.addEventHandler')}</el-button>
|
||||
</el-form-item>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createEmptyEditor = function () {
|
||||
return {
|
||||
render() {
|
||||
return <div style="display: none" />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.allowCreate')">
|
||||
<el-checkbox v-model="optionModel.allowCreate"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "allowCreate-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form-item label-width="0">
|
||||
<el-divider class="custom-divider">{{i18nt('designer.setting.inputButton')}}</el-divider>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.appendButton')">
|
||||
<el-checkbox v-model="optionModel.appendButton"></el-checkbox>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "appendButton-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.appendButtonDisabled')">
|
||||
<el-checkbox v-model="optionModel.appendButtonDisabled"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "appendButtonDisabled-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.automaticDropdown')">
|
||||
<el-checkbox v-model="optionModel.automaticDropdown"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "automaticDropdown-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.border')">
|
||||
<el-checkbox v-model="optionModel.border"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "border-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.appendButtonIcon')">
|
||||
<el-input type="text" v-model="optionModel.buttonIcon"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "buttonIcon-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.buttonStyle')">
|
||||
<el-checkbox v-model="optionModel.buttonStyle"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "buttonStyle-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.clearable')">
|
||||
<el-checkbox v-model="optionModel.clearable"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "clearable-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form-item :label="i18nt('designer.setting.widgetColumnWidth')" v-show="!!subFormChildWidgetFlag">
|
||||
<el-input type="text" v-model="optionModel.columnWidth"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "columnWidth-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//subFormChildWidgetFlag: false,
|
||||
subFormChildWidgetFlag: true,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.designer.handleEvent('field-selected', (parentWidget) => {
|
||||
this.subFormChildWidgetFlag = !!parentWidget && (parentWidget.type === 'sub-form');
|
||||
//console.log('subFormChildWidgetFlag', this.subFormChildWidgetFlag)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.colOffsetTitle')">
|
||||
<el-input-number v-model.number="optionModel.offset" :min="0" :max="24"
|
||||
style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
|
||||
export default {
|
||||
name: "grid-col-offset-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.colPullTitle')">
|
||||
<el-input-number v-model.number="optionModel.pull" :min="0" :max="24"
|
||||
style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
|
||||
export default {
|
||||
name: "grid-col-pull-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.colPushTitle')">
|
||||
<el-input-number v-model.number="optionModel.push" :min="0" :max="24"
|
||||
style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
|
||||
export default {
|
||||
name: "grid-col-push-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.responsive')">
|
||||
<el-checkbox v-model="optionModel.responsive"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "grid-col-responsive-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form-item :label="i18nt('designer.setting.colSpanTitle')" v-if="!optionModel.responsive">
|
||||
<el-input-number v-model.number="optionModel.span" :min="1" :max="24"
|
||||
style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.colSpanTitle') + '(PC)'"
|
||||
v-if="!!optionModel.responsive && (formConfig.layoutType === 'PC')">
|
||||
<el-input-number v-model.number="optionModel.md" :min="1" :max="24"
|
||||
style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.colSpanTitle') + '(Pad)'"
|
||||
v-if="!!optionModel.responsive && (formConfig.layoutType === 'Pad')">
|
||||
<el-input-number v-model.number="optionModel.sm" :min="1" :max="24"
|
||||
style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.colSpanTitle') + '(H5)'"
|
||||
v-if="!!optionModel.responsive && (formConfig.layoutType === 'H5')">
|
||||
<el-input-number v-model.number="optionModel.xs" :min="1" :max="24"
|
||||
style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
|
||||
export default {
|
||||
name: "grid-col-span-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
computed: {
|
||||
formConfig() {
|
||||
return this.designer.formConfig
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form-item :label="i18nt('designer.setting.gridColHeight')">
|
||||
<el-input type="number" v-model="optionModel.colHeight" @input="inputNumberHandler"
|
||||
min="0" class="hide-spin-button"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import propertyMixin from "@/components/form-designer/setting-panel/property-editor/propertyMixin"
|
||||
|
||||
export default {
|
||||
name: "colHeight-editor",
|
||||
mixins: [i18n, propertyMixin],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form-item label-width="0">
|
||||
<el-divider class="custom-divider">{{i18nt('designer.setting.columnSetting')}}</el-divider>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.gutter')">
|
||||
<el-input-number v-model="optionModel.gutter" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.colsOfGrid')"></el-form-item>
|
||||
<el-form-item label-width="0">
|
||||
<li v-for="(colItem, colIdx) in selectedWidget.cols" :key="colIdx" class="col-item">
|
||||
<span class="col-span-title">{{i18nt('designer.setting.colSpanTitle')}}{{colIdx + 1}}</span>
|
||||
<el-input-number v-model.number="colItem.options.span" :min="1" :max="24"
|
||||
@change="(newValue, oldValue) => spanChanged(selectedWidget, colItem, colIdx, newValue, oldValue)"
|
||||
class="cell-span-input"></el-input-number>
|
||||
<el-button circle plain size="mini" type="danger" @click="deleteCol(selectedWidget, colIdx)"
|
||||
icon="el-icon-minus" class="col-delete-button"></el-button>
|
||||
</li>
|
||||
<div>
|
||||
<el-button type="text" @click="addNewCol(selectedWidget)">{{i18nt('designer.setting.addColumn')}}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "gutter-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
methods: {
|
||||
spanChanged(curGrid) {
|
||||
let spanSum = 0
|
||||
curGrid.cols.forEach((colItem) => {
|
||||
spanSum += colItem.options.span
|
||||
})
|
||||
if (spanSum > 24) {
|
||||
//this.$message.info('列栅格之和超出24')
|
||||
console.log('列栅格之和超出24')
|
||||
//TODO: 语言字符串资源化
|
||||
}
|
||||
|
||||
this.designer.saveCurrentHistoryStep()
|
||||
},
|
||||
|
||||
deleteCol(curGrid, colIdx) {
|
||||
this.designer.deleteColOfGrid(curGrid, colIdx)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
addNewCol(curGrid) {
|
||||
this.designer.addNewColOfGrid(curGrid)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
li.col-item {
|
||||
list-style: none;
|
||||
|
||||
span.col-span-title {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.col-delete-button {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.showBlankRow')">
|
||||
<el-checkbox v-model="optionModel.showBlankRow"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "showBlankRow-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.showRowNumber')">
|
||||
<el-checkbox v-model="optionModel.showRowNumber"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "showRowNumber-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.labelAlign')">
|
||||
<el-radio-group v-model="optionModel.labelAlign" class="radio-group-custom">
|
||||
<el-radio-button label="label-left-align">
|
||||
{{i18nt('designer.setting.leftAlign')}}</el-radio-button>
|
||||
<el-radio-button label="label-center-align">
|
||||
{{i18nt('designer.setting.centerAlign')}}</el-radio-button>
|
||||
<el-radio-button label="label-right-align">
|
||||
{{i18nt('designer.setting.rightAlign')}}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
|
||||
export default {
|
||||
name: "sub-form-labelAlign-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.radio-group-custom {
|
||||
:deep(.el-radio-button__inner) {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,121 @@
|
||||
<!--
|
||||
因tabs属性并不包含于options属性之中,故只能跟其他options属性之内的属性值合并设置,此处选择与customClass合并!!
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-form-item :label="i18nt('designer.setting.customClass')">
|
||||
<el-select v-model="optionModel.customClass" multiple filterable allow-create
|
||||
default-first-option>
|
||||
<el-option v-for="(item, idx) in cssClassList" :key="idx" :label="item" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="i18nt('designer.setting.tabPaneSetting')"></el-form-item>
|
||||
<el-form-item label-width="0" class="panes-setting">
|
||||
<draggable tag="ul" :list="selectedWidget.tabs" item-key="id"
|
||||
v-bind="{group:'panesGroup', ghostClass: 'ghost', handle: '.drag-option'}">
|
||||
<template #item="{ element: tpItem, index: tpIdx }">
|
||||
<li class="col-item">
|
||||
<!-- span style="margin-right: 12px">{{tpIdx + 1}}</span -->
|
||||
<el-checkbox v-model="tpItem.options.active" disabled @change="(evt) => onTabPaneActiveChange(evt, tpItem)"
|
||||
style="margin-right: 8px">{{i18nt('designer.setting.paneActive')}}</el-checkbox>
|
||||
<el-input type="text" v-model="tpItem.options.label" style="width: 155px"></el-input>
|
||||
<i class="iconfont icon-drag drag-option"></i>
|
||||
<el-button circle plain size="mini" type="danger" @click="deleteTabPane(selectedWidget, tpIdx)"
|
||||
icon="el-icon-minus" class="col-delete-button"></el-button>
|
||||
</li>
|
||||
</template>
|
||||
<div>
|
||||
<el-button type="text" @click="addTabPane(selectedWidget)">{{i18nt('designer.setting.addTabPane')}}</el-button>
|
||||
</div>
|
||||
</draggable>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import Draggable from 'vuedraggable'
|
||||
import {deepClone} from "@/utils/util";
|
||||
|
||||
export default {
|
||||
name: "tab-customClass-editor",
|
||||
componentName: 'PropertyEditor',
|
||||
mixins: [i18n],
|
||||
components: {
|
||||
Draggable,
|
||||
},
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cssClassList: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cssClassList = deepClone(this.designer.getCssClassList())
|
||||
//监听表单css代码改动事件并重新加载!
|
||||
this.designer.handleEvent('form-css-updated', (cssClassList) => {
|
||||
this.cssClassList = cssClassList
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onTabPaneActiveChange(evt, tpItem) {
|
||||
//TODO: !!!
|
||||
},
|
||||
|
||||
addTabPane(curTabs) {
|
||||
this.designer.addTabPaneOfTabs(curTabs)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
deleteTabPane(curTabs, tpIdx) {
|
||||
if (curTabs.tabs.length === 1) {
|
||||
this.$message.info(this.i18nt('designer.hint.lastPaneCannotBeDeleted'))
|
||||
return
|
||||
}
|
||||
|
||||
this.designer.deleteTabPaneOfTabs(curTabs, tpIdx)
|
||||
this.designer.emitHistoryChange()
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
li.col-item {
|
||||
list-style: none;
|
||||
|
||||
span.col-span-title {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.col-delete-button {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.panes-setting {
|
||||
ul {
|
||||
padding-inline-start: 0;
|
||||
padding-left: 0; /* 重置IE11默认样式 */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.drag-option {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
li.ghost {
|
||||
background: #fff;
|
||||
border: 2px dotted $--color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.cellHeight')">
|
||||
<el-input type="text" v-model="optionModel.cellHeight"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "cellHeight-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.cellWidth')">
|
||||
<el-input type="text" v-model="optionModel.cellWidth"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "cellWidth-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.customClass')">
|
||||
<el-select v-model="optionModel.customClass" multiple filterable allow-create
|
||||
default-first-option>
|
||||
<el-option v-for="(item, idx) in cssClassList" :key="idx" :label="item" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n";
|
||||
import {deepClone} from "@/utils/util";
|
||||
|
||||
export default {
|
||||
name: "customClass-editor",
|
||||
componentName: 'PropertyEditor',
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cssClassList: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cssClassList = deepClone(this.designer.getCssClassList())
|
||||
//监听表单css代码改动事件并重新加载!
|
||||
this.designer.handleEvent('form-css-updated', (cssClassList) => {
|
||||
this.cssClassList = cssClassList
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.defaultValue')">
|
||||
<el-input v-if="!hasConfig('optionItems')" type="text" v-model="optionModel.defaultValue"
|
||||
@change="emitDefaultValueChange"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import propertyMixin from "@/components/form-designer/setting-panel/property-editor/propertyMixin"
|
||||
|
||||
export default {
|
||||
name: "defaultValue-editor",
|
||||
mixins: [i18n, propertyMixin],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.disabled')">
|
||||
<el-checkbox v-model="optionModel.disabled"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "disabled-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.displayStyle')">
|
||||
<el-radio-group v-model="optionModel.displayStyle">
|
||||
<el-radio label="inline">{{i18nt('designer.setting.inlineLayout')}}</el-radio>
|
||||
<el-radio label="block">{{i18nt('designer.setting.blockLayout')}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "displayStyle-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.editable')">
|
||||
<el-checkbox v-model="optionModel.editable"></el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "editable-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="i18nt('designer.setting.endPlaceholder')">
|
||||
<el-input type="text" v-model="optionModel.endPlaceholder"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
|
||||
export default {
|
||||
name: "endPlaceholder-editor",
|
||||
mixins: [i18n],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,12 @@
|
||||
import emitter from '@/utils/emitter'
|
||||
|
||||
export default {
|
||||
mixins: [emitter],
|
||||
created() {},
|
||||
methods: {
|
||||
editEventHandler(eventName, eventParams) {
|
||||
this.dispatch('SettingPanel', 'editEventHandler', [eventName, [...eventParams]])
|
||||
},
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<el-form-item label="onBeforeUpload" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editEventHandler('onBeforeUpload', eventParams)">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import eventMixin from "@/components/form-designer/setting-panel/property-editor/event-handler/eventMixin"
|
||||
|
||||
export default {
|
||||
name: "onBeforeUpload-editor",
|
||||
mixins: [i18n, eventMixin],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
eventParams: ['file'],
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<el-form-item label="onBlur" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editEventHandler('onBlur', eventParams)">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import eventMixin from "@/components/form-designer/setting-panel/property-editor/event-handler/eventMixin"
|
||||
|
||||
export default {
|
||||
name: "onBlur-editor",
|
||||
mixins: [i18n, eventMixin],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
eventParams: ['event'],
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<el-form-item label="onChange" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editEventHandler('onChange', eventParams)">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import eventMixin from "@/components/form-designer/setting-panel/property-editor/event-handler/eventMixin"
|
||||
|
||||
export default {
|
||||
name: "onChange-editor",
|
||||
mixins: [i18n, eventMixin],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
eventParams: ['value', 'oldValue', 'subFormData', 'rowId'],
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<el-form-item label="onClick" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editEventHandler('onClick', eventParams)">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import eventMixin from "@/components/form-designer/setting-panel/property-editor/event-handler/eventMixin"
|
||||
|
||||
export default {
|
||||
name: "onClick-editor",
|
||||
mixins: [i18n, eventMixin],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
eventParams: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<el-form-item label="onCreated" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editEventHandler('onCreated', eventParams)">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import eventMixin from "@/components/form-designer/setting-panel/property-editor/event-handler/eventMixin"
|
||||
|
||||
export default {
|
||||
name: "onCreated-editor",
|
||||
mixins: [i18n, eventMixin],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
eventParams: [],
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<el-form-item label="onFocus" label-width="150px">
|
||||
<el-button type="info" icon="el-icon-edit" plain round @click="editEventHandler('onFocus', eventParams)">
|
||||
{{i18nt('designer.setting.addEventHandler')}}</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/utils/i18n"
|
||||
import eventMixin from "@/components/form-designer/setting-panel/property-editor/event-handler/eventMixin"
|
||||
|
||||
export default {
|
||||
name: "onFocus-editor",
|
||||
mixins: [i18n, eventMixin],
|
||||
props: {
|
||||
designer: Object,
|
||||
selectedWidget: Object,
|
||||
optionModel: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
eventParams: ['event'],
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|