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> | ||||||