Compare commits

...

No commits in common. "master" and "develop" have entirely different histories.

314 changed files with 22957 additions and 20943 deletions

View File

@ -1,11 +0,0 @@
# 页面标题
VUE_APP_TITLE = 睿展-代码生成系统
# 开发环境配置
ENV = 'development'
# 睿展-代码生成系统/开发环境
VUE_APP_CONTEXT_PATH='/variant-front'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@ -1,17 +0,0 @@
###
# @version: V1.0.0
# @Date: 2023-03-20 14:50:32
# @LastEditors: lzq
# @LastEditTime: 2023-03-20 14:51:47
# @company: 睿展数据
# @FilePath: \variant-form\.env.production
# @Descripttion:
###
# 页面标题
VUE_APP_TITLE = 睿展-代码生成系统
# 生产环境配置
ENV = 'production'
# 睿展-代码生成系统/生产环境
VUE_APP_CONTEXT_PATH='/variant-front'

View File

@ -1,19 +0,0 @@
###
# @version: V1.0.0
# @Date: 2023-03-20 14:50:34
# @LastEditors: lzq
# @LastEditTime: 2023-03-20 14:52:42
# @company: 睿展数据
# @FilePath: \variant-form\.env.staging
# @Descripttion:
###
# 页面标题
VUE_APP_TITLE = 睿展-代码生成系统
NODE_ENV = production
# 测试环境配置
ENV = 'staging'
# 睿展-代码生成系统/测试环境
VUE_APP_CONTEXT_PATH='/variant-front'

27
.gitignore vendored
View File

@ -1,28 +1 @@
.DS_Store
node_modules node_modules
/dist
/dist0/
/dist2
.VSCodeCounter
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/package-lock.json
/VariantForm.iml
yarn.lock

View File

@ -1,3 +0,0 @@
FROM nginx:1.21.1-alpine
COPY ./dist /usr/share/nginx/html/variant-front
EXPOSE 80

195
README.md
View File

@ -1,195 +1,2 @@
# Variant Form # variant-form
#### 一款高效的Vue低代码表单可视化设计一键生成源码享受更多摸鱼时间。
![image](https://ks3-cn-beijing.ksyuncs.com/vform-static/img/vform_demo.gif)
<br/>
### 立即体验
[在线Demo](http://120.92.142.115/)
### 立即体验VForm Pro高级版提供商业支持
[Pro Demo](https://vform666.com/pages/pro/)
### 视频教程集合:
[B站观看](https://space.bilibili.com/626932375)
### Vue 3正式版已发布
[立即进入](https://gitee.com/vdpadmin/variant-form3-vite)
### 🎉🎉基于Vant组件库的Mobile版本已发布🎉🎉
[立即进入](https://vform666.com/vform-mobile.html)
### 友情链接
[Fantastic-admin](https://hooray.gitee.io/fantastic-admin/) —— 一款开箱即用的 Vue 中后台管理系统框架支持Vue2/Vue3
[REBUILD](https://getrebuild.com/) —— 高度可定制化的企业管理系统
<br/>
### 功能一览
```
> 拖拽式可视化表单设计;
> 支持PC、Pad、H5三种布局
> 支持运行时动态加载表单;
> 支持表单复杂交互控制;
> 支持自定义CSS样式
> 支持自定义校验逻辑;
> 支持国际化多语言;
> 兼容IE 11浏览器
> 可导出Vue组件、HTML源码
> 可导出Vue的SFC单文件组件
> 支持开发自定义组件;
> 支持响应式自适应布局;
> 支持VS Code插件
> 更多功能等你探究...
```
### 安装依赖
```
npm install --registry=https://registry.npm.taobao.org
```
### 开发调试
```
npm run serve
```
### 生产打包
```
npm run build
```
### 表单设计器 + 表单渲染器打包
```
npm run lib
```
### 表单渲染器打包
```
npm run lib-render
```
### 浏览器兼容性
```Chrome及同内核的浏览器如QQ浏览器、360浏览器等等Edge, FirefoxSafariIE 11```
<br/>
### 跟Vue项目集成
<br/>
#### 1. 安装包
```bash
npm i vform-builds
```
```bash
yarn add vform-builds
```
<br/>
#### 2. 引入并全局注册VForm组件
```javascript
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui' //引入element-ui库
import VForm from 'vform-builds' //引入VForm库
import 'element-ui/lib/theme-chalk/index.css' //引入element-ui样式
import 'vform-builds/dist/VFormDesigner.css' //引入VForm样式
Vue.config.productionTip = false
Vue.use(ElementUI) //全局注册element-ui
Vue.use(VForm) //全局注册VForm(同时注册了v-form-designer和v-form-render组件)
new Vue({
render: h => h(App),
}).$mount('#app')
```
<br/>
#### 3. 在Vue模板中使用表单设计器组件
```html
<template>
<v-form-designer></v-form-designer>
</template>
<script>
export default {
data() {
return {
}
}
}
</script>
<style lang="scss">
body {
margin: 0; /* 如果页面出现垂直滚动条则加入此行CSS以消除之 */
}
</style>
```
<br/>
#### 4. 在Vue模板中使用表单渲染器组件
```html
<template>
<div>
<v-form-render :form-json="formJson" :form-data="formData" :option-data="optionData" ref="vFormRef">
</v-form-render>
<el-button type="primary" @click="submitForm">Submit</el-button>
</div>
</template>
<script>
export default {
data() {
return {
formJson: {"widgetList":[],"formConfig":{"labelWidth":80,"labelPosition":"left","size":"","labelAlign":"label-left-align","cssCode":"","customClass":"","functions":"","layoutType":"PC","onFormCreated":"","onFormMounted":"","onFormDataChange":""}},
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>
```
<br/>
### 资源链接
<hr>
文档官网:<a href="https://www.vform666.com/" target="_blank">https://www.vform666.com/</a>
在线演示:<a href="http://120.92.142.115/" target="_blank">http://120.92.142.115/</a>
Gitee仓库<a href="https://gitee.com/vdpadmin/variant-form" target="_blank">https://gitee.com/vdpadmin/variant-form</a>
Github仓库<a href="https://github.com/vform666/variant-form" target="_blank">https://github.com/vform666/variant-form</a>
VS Code插件<a href="https://www.vform666.com/pages/plugin/" target="_blank">https://www.vform666.com/pages/plugin/</a>
更新日志:<a href="https://www.vform666.com/changelog.html" target="_blank">https://www.vform666.com/changelog.html</a>
订阅Pro版<a href="https://www.vform666.com/pages/pro/" target="_blank">https://www.vform666.com/pages/pro/</a>
技术交流群:扫如下二维码加群
![image](https://vform2022.ks3-cn-beijing.ksyuncs.com/vchat_qrcode.png)

View File

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

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

View File

@ -1,29 +0,0 @@
import VFormRender from '@/components/form-render/index.vue'
import {loadExtension} from "@/extension/extension-loader"
import axios from "axios"
loadExtension()
VFormRender.install = function (Vue) {
Vue.component(VFormRender.name, VFormRender)
}
const components = [
VFormRender
]
const install = (Vue) => {
window.axios = axios
components.forEach(component => {
Vue.component(component.name, component)
})
}
if (typeof window !== 'undefined' && window.Vue) { /* script方式引入时主动调用install方法 */
install(window.Vue);
}
export default {
install,
VFormRender
}

View File

@ -1,41 +0,0 @@
import axios from 'axios'
import VFormDesigner from '@/components/form-designer/index.vue'
import VFormRender from '@/components/form-render/index.vue'
import {loadExtension} from "@/extension/extension-loader"
import '@/utils/directive'
import '@/icons'
import '@/iconfont/iconfont.css'
loadExtension()
VFormDesigner.install = function (Vue) {
Vue.component(VFormDesigner.name, VFormDesigner)
}
VFormRender.install = function (Vue) {
Vue.component(VFormRender.name, VFormRender)
}
const components = [
VFormDesigner,
VFormRender
]
const install = (Vue) => {
window.axios = axios
components.forEach(component => {
Vue.component(component.name, component)
})
}
if (typeof window !== 'undefined' && window.Vue) { /* script方式引入时主动调用install方法 */
install(window.Vue);
}
export default {
install,
VFormDesigner,
VFormRender
}

View File

@ -1,10 +0,0 @@
{
"######": "本文件用于解决IDEA无法识别Vue项目@符号的问题",
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

View File

@ -1,8 +0,0 @@
# Variant Form 许可条款 1.0
1. 免责声明任何情况下根据任何法律本作者不对用户因使用VariantForm产生的侵权、数据损坏丢失、软硬件故障和违法犯罪等问题承担任何责任
2. 禁止任何用户对VariantForm进行简单包装后即声称为自己的产品、销售源码获利
3. VariantForm为开源项目获取到源代码的用户可自由修改源码供自身开发使用可分发build构建后的库代码也可分发VariantForm源代码需保留文件头部的作者声明本作者保留VariantForm的原始著作权
4. 个人或公司用户均可将VariantForm项目应用于商业项目开发为支持本项目持续开发请尽量购买VariantForm Pro版源码订阅更新服务
5. 如果你不同意本许可条款请勿使用VariantForm任何情况下一旦实际使用VariantForm则代表你已确定完全同意本许可条款
6. 条款内容结束。

13939
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
{
"name": "variant-form",
"version": "2.2.9",
"private": false,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lib": "vue-cli-service build --report --target lib --dest dist/lib --name VFormDesigner install.js",
"lib-render": "vue-cli-service build --report --target lib --dest dist/lib-render --name VFormRender install-render.js",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.1",
"clipboard": "^2.0.8",
"core-js": "^3.6.5",
"element-ui": "^2.15.1",
"file-saver": "^2.0.5",
"vue": "^2.6.11",
"vue-router": "^3.6.5",
"vue2-editor": "^2.10.2",
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"ace-builds": "^1.4.12",
"babel-eslint": "^10.1.0",
"babel-polyfill": "^6.26.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"mvdir": "^1.0.21",
"sass": "^1.45.1",
"sass-loader": "^8.0.2",
"svg-sprite-loader": "^5.2.1",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,11 +0,0 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 B

View File

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

View File

@ -1,962 +0,0 @@
/**
* author: vformAdmin
* email: vdpadmin@163.com
* website: https://www.vform666.com
* date: 2021.08.18
* remark: 如果要分发VForm源码需在本文件顶部保留此文件头信息
*/
import {deepClone, generateId, getDefaultFormConfig, overwriteObj} from "@/utils/util"
import {containers, advancedFields, basicFields, customFields} from "@/components/form-designer/widget-panel/widgetsConfig.js"
import {VARIANT_FORM_VERSION} from "@/utils/config"
export function createDesigner(vueInstance) {
let defaultFormConfig = deepClone( getDefaultFormConfig() )
return {
widgetList: [],
formConfig: {cssCode: ''},
selectedId: null,
selectedWidget: null,
selectedWidgetName: null, //选中组件名称(唯一)
vueInstance: vueInstance,
formWidget: null, //表单设计容器
cssClassList: [], //自定义样式列表
historyData: {
index: -1, //index: 0,
maxStep: 20,
steps: [],
},
initDesigner(resetFormJson) {
this.widgetList = []
this.formConfig = deepClone(defaultFormConfig)
//输出版本信息和语雀链接
console.info(`%cVariantForm %cVer${VARIANT_FORM_VERSION} %chttps://www.yuque.com/visualdev/vform`,
"color:#409EFF;font-size: 22px;font-weight:bolder",
"color:#999;font-size: 12px",
"color:#333"
)
if (!resetFormJson) {
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)
formConfig: deepClone(defaultFormConfig)
}
},
loadFormJson(formJson) {
let modifiedFlag = false
if (!!formJson && !!formJson.widgetList) {
this.formWidget.clearWidgetRefList()
this.widgetList = formJson.widgetList
modifiedFlag = true
}
if (!!formJson && !!formJson.formConfig) {
//this.formConfig = importObj.formConfig
overwriteObj(this.formConfig, formJson.formConfig) /* 用=赋值会导致inject依赖注入的formConfig属性变成非响应式 */
modifiedFlag = true
}
if (modifiedFlag) {
this.emitEvent('form-json-imported', []) // 通知其他组件
}
return modifiedFlag
},
setSelected(selected) {
if (!selected) {
this.clearSelected()
return
}
this.selectedWidget = selected
if (!!selected.id) {
this.selectedId = selected.id
this.selectedWidgetName = selected.options.name
}
},
updateSelectedWidgetNameAndLabel(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
let wgType = evt.draggedContext.element.type
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
},
checkFieldMove(evt) {
if (!!evt.draggedContext && !!evt.draggedContext.element) {
let wgCategory = evt.draggedContext.element.category
let wgType = evt.draggedContext.element.type + ''
//console.log('wgType======', wgType)
if (!!evt.to) {
if ((evt.to.className === 'sub-form-table') && (wgType === 'slot')) {
//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
},
upgradeWidgetConfig(oldWidget) {
let newWidget = null
if (!!oldWidget.category) {
newWidget = this.getContainerByType(oldWidget.type)
} else {
newWidget = this.getFieldWidgetByType(oldWidget.type)
}
if (!newWidget || !newWidget.options) {
return
}
Object.keys(newWidget.options).forEach(ck => {
if (!oldWidget.hasOwnProperty(ck)) {
oldWidget.options[ck] = deepClone(newWidget.options[ck])
}
})
},
upgradeFormConfig(oldFormConfig) {
Object.keys(this.formConfig).forEach(fc => {
if (!oldFormConfig.hasOwnProperty(fc)) {
oldFormConfig[fc] = deepClone(this.formConfig[fc])
}
})
},
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.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)
},
handleEvent(evtName, callback) { //用于兄弟组件接收事件
this.vueInstance.$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属性变成非响应式 */
}
},
}
}

View File

@ -1,107 +0,0 @@
<!--
/**
* 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 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 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>

View File

@ -1,93 +0,0 @@
export default {
inject: ['getGlobalDsv'],
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()
this.designer.emitEvent('field-selected', this.widget)
},
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) {
const widgetRefName = this.designer.selectedWidgetName
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)
this.designer.setSelected(nextSelected)
this.designer.formWidget.deleteWidgetRef(widgetRefName) //删除组件ref
this.designer.emitHistoryChange()
})
}
},
setWidgetOption(optionName, optionValue) { //通用组件选项修改API
if (this.widget.options.hasOwnProperty(optionName)) {
this.widget.options[optionName] = optionValue
}
},
}
}

View File

@ -1,314 +0,0 @@
<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.native.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler" @end="(evt) => onGridDragEnd(evt, widget.widgetList)"
@add="(evt) => onGridDragAdd(evt, widget.widgetList)"
@update="onGridDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<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>
</template>
</transition-group>
</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'
import refMixinDesign from "@/components/form-designer/refMixinDesign"
export default {
name: "GridColWidget",
componentName: "GridColWidget",
mixins: [i18n, refMixinDesign],
inject: ['refList'],
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.initRefList()
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()
this.designer.emitEvent('field-selected', this.widget)
},
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; /* marginoffsetpushpull */
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>

View File

@ -1,93 +0,0 @@
<!--
/**
* 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.native.stop="selectWidget(widget)">
<template v-for="(colWidget, colIdx) in widget.cols">
<grid-col-widget :widget="colWidget" :designer="designer" :key="colWidget.id" :parent-list="widget.cols"
:index-of-parent-list="colIdx" :parent-widget="widget"
: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"
import refMixinDesign from "@/components/form-designer/refMixinDesign"
export default {
name: "grid-widget",
componentName: 'ContainerWidget',
mixins: [i18n, containerMixin, refMixinDesign],
inject: ['refList'],
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: {
//
},
created() {
this.initRefList()
},
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>

View File

@ -1,24 +0,0 @@
import Vue from 'vue'
const requireComponent = require.context('./', false, /\w+\.vue$/)
/**
* 容器组件时递归组件且内部可以嵌套其他容器局部注册会找不到组件必须注册为全局组件原因不明
* begin
*
let comps = {}
requireComponent.keys().map(fileName => {
let comp = requireComponent(fileName).default;
comps[comp.name] = comp
})
export default comps;
end */
/* 全局注册!! */
requireComponent.keys().map(fileName => {
let comp = requireComponent(fileName).default;
Vue.component(comp.name, comp)
})

View File

@ -1,124 +0,0 @@
<!--
/**
* 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.native.stop="selectWidget(widget)">
<draggable :list="tab.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler"
@add="(evt) => onContainerDragAdd(evt, tab.widgetList)"
@update="onContainerDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in tab.widgetList">
<template v-if="'container' === subWidget.category">
<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>
</template>
</transition-group>
</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'
import refMixinDesign from "@/components/form-designer/refMixinDesign"
export default {
name: "tab-widget",
componentName: 'ContainerWidget',
mixins: [i18n, containerMixin, refMixinDesign],
inject: ['refList'],
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: {
//
},
created() {
this.initRefList()
},
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>

View File

@ -1,347 +0,0 @@
<template>
<td class="table-cell" :class="[selected ? 'selected' : '', customClass]"
:style="{width: widget.options.cellWidth + '!important' || '', height: widget.options.cellHeight + '!important' || '', 'word-break': !!widget.options.wordBreak ? 'break-all' : 'normal'}"
:colspan="widget.options.colspan || 1" :rowspan="widget.options.rowspan || 1"
@click.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" class="draggable-div" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler" @end="(evt) => onTableDragEnd(evt, widget.widgetList)"
@add="(evt) => onTableDragAdd(evt, widget.widgetList)"
@update="onTableDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<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>
</template>
</transition-group>
</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>
<el-dropdown-menu slot="dropdown">
<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>
</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'
import refMixinDesign from "@/components/form-designer/refMixinDesign"
export default {
name: "TableCellWidget",
componentName: "TableCellWidget",
mixins: [i18n, refMixinDesign],
inject: ['refList'],
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: {
//
},
created() {
this.initRefList()
},
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()
this.designer.emitEvent('field-selected', this.widget)
},
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>

View File

@ -1,116 +0,0 @@
<!--
/**
* 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"
import refMixinDesign from "@/components/form-designer/refMixinDesign"
export default {
name: "table-widget",
componentName: 'ContainerWidget',
mixins: [i18n, containerMixin, refMixinDesign],
inject: ['refList'],
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: {
//
},
created() {
this.initRefList()
},
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;
::v-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>

View File

@ -1,87 +0,0 @@
<template>
<static-content-wrapper :designer="designer" :field="field" :design-state="designState" :display-style="field.options.displayStyle"
: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.native="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', //FieldWidgetbroadcast
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() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.registerToRefList()
this.initEventHandler()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* static-content-wrapper *//
</style>

View File

@ -1,120 +0,0 @@
<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"
:show-all-levels="showFullPath"
:props="{ checkStrictly: field.options.checkStrictly, multiple: field.options.multiple, expandTrigger: 'hover' }"
@visible-change="hideDropDownOnClick" @expand-change="hideDropDownOnClick"
: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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
showFullPath() {
return (this.field.options.showAllLevels === undefined) || !!this.field.options.showAllLevels
},
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initOptionItems()
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
/* 开启任意级节点可选后点击radio隐藏下拉框 */
hideDropDownOnClick() {
setTimeout(() => {
document.querySelectorAll(".el-cascader-panel .el-radio").forEach((el) => {
el.onclick = () => {
this.$refs.fieldEditor.dropDownVisible = false //
}
})
}, 100)
},
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,105 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initOptionItems()
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
</style>

View File

@ -1,99 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,104 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,103 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,83 +0,0 @@
<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', //FieldWidgetbroadcast
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() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.registerToRefList()
this.initEventHandler()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* static-content-wrapper *//
</style>

View File

@ -1,613 +0,0 @@
import {deepClone} from "@/utils/util"
import FormValidators from '@/utils/validators'
export default {
inject: ['refList', 'formConfig', 'getGlobalDsv', '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 ------------------//
getPropName() {
if (this.subFormItemFlag && !this.designState) {
return this.subFormName + "." + this.subFormRowIndex + "." + this.field.options.name + ""
} else {
return this.field.options.name
}
},
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() {
this.$on('setFormData', (newFormData) => {
//console.log('formModel of globalModel----------', this.globalModel.formModel)
if (!this.subFormItemFlag) {
this.setValue(newFormData[this.field.options.name])
}
})
this.$on('field-value-changed', (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])
}
})
/* 监听重新加载选项事件 */
this.$on('reloadOptionItems', (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')) {
/* 异步更新option-data之后globalOptionData不能获取到最新值改用provide的getOptionData()方法 */
const newOptionItems = this.getOptionData()
if (!!newOptionItems && newOptionItems.hasOwnProperty(this.field.options.name)) {
if (!!keepSelected) {
this.reloadOptions(newOptionItems[this.field.options.name])
} else {
this.loadOptions(newOptionItems[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 && this.field.options.hidden) {
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.field.options.requiredHint || 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 = (rule, value, callback) => {
let tmpFunc = new Function('rule', 'value', 'callback', this.field.options.onValidate)
return tmpFunc.call(this, rule, value, callback)
}
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) { /* input的清除输入小按钮会同时触发handleChangeEvent、handleInputCustomEvent */
this.syncUpdateFormModel(value)
this.emitFieldDataChange(value, this.oldFieldValue)
//number组件一般不会触发focus事件故此处需要手工赋值oldFieldValue
this.oldFieldValue = deepClone(value) /* oldFieldValue需要在initFieldModel()方法中赋初值!! */
/* 主动触发表单的单个字段校验,用于清除字段可能存在的校验错误提示 */
this.dispatch('VFormRender', 'fieldValidation', [this.getPropName()])
},
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)
/* 主动触发表单的单个字段校验,用于清除字段可能存在的校验错误提示 */
this.dispatch('VFormRender', 'fieldValidation', [this.getPropName()])
if (!!this.field.options.onInput) {
let customFn = new Function('value', this.field.options.onInput)
customFn.call(this, value)
}
},
emitAppendButtonClick() {
if (!!this.designState) { //设计状态不触发点击事件
return
}
if (!!this.field.options.onAppendButtonClick) {
let customFn = new Function(this.field.options.onAppendButtonClick)
customFn.call(this)
} else {
/* 必须调用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() {
if (!!this.subFormItemFlag) { //跳过子表单组件
return
}
let defaultValue = this.field.options.defaultValue
this.setValue(defaultValue)
//清空上传组件文件列表
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)
},
/**
* 返回radio/checkbox/select/cascader的选项数据
* @returns 选择项数组
*/
getOptions() {
return this.field.options.optionItems
},
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)
},
setUploadData(name, value) {
this.$set(this.uploadData, name, value)
},
setToolbar(customToolbar) {
this.customToolbar = customToolbar
},
/**
* 是否子表单内嵌的组件
* @returns {boolean}
*/
isSubFormItem() {
return !!this.parentWidget ? this.parentWidget.type === 'sub-form' : false
},
/**
* 动态增加自定义css样式
* @param className
*/
addCssClass(className) {
if (!this.field.options.customClass) {
this.field.options.customClass = [className]
} else {
this.field.options.customClass.push(className)
}
},
/**
* 动态移除自定义css样式
* @param className
*/
removeCssClass(className) {
if (!this.field.options.customClass) {
return
}
let foundIdx = -1
this.field.options.customClass.map((cc, idx) => {
if (cc === className) {
foundIdx = idx
}
})
if (foundIdx > -1) {
this.field.options.customClass.splice(foundIdx, 1)
}
},
//--------------------- 以上为组件支持外部调用的API方法 end ------------------//
}
}

View File

@ -1,303 +0,0 @@
<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="handleUploadError">
<div slot="tip" class="el-upload__tip"
v-if="!!field.options.uploadTip">{{field.options.uploadTip}}</div>
<i slot="default" class="el-icon-plus avatar-uploader-icon"></i>
<template #file="{ file }">
<div class="upload-file-list">
<span class="upload-file-name" :title="file.name">{{file.name}}</span>
<a :href="file.url" download="" target="_blank">
<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, file.url, file.uid)"></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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: [],
rules: [],
uploadHeaders: {},
uploadData: {
key: '', //
//token: '', //token
//policy: '', //policy
//authorization: '', //
},
fileList: [], //
uploadBtnHidden: false,
styleVariables: {
'--select-file-action': selectFileText,
},
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
handleFileExceed() {
let uploadLimit = this.field.options.limit
this.$message.warning( this.i18nt('render.hint.uploadExceed').replace('${uploadLimit}', uploadLimit) )
},
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
},
updateFieldModelAndEmitDataChangeForUpload(fileList, customResult, defaultResult) {
let oldValue = deepClone(this.fieldModel)
if (!!customResult && !!customResult.name && !!customResult.url) {
this.fieldModel.push({
name: customResult.name,
url: customResult.url
})
} else if (!!defaultResult && !!defaultResult.name && !!defaultResult.url) {
this.fieldModel.push({
name: defaultResult.name,
url: defaultResult.url
})
} else {
this.fieldModel = deepClone(fileList)
}
this.syncUpdateFormModel(this.fieldModel)
this.emitFieldDataChange(this.fieldModel, oldValue)
},
handleFileUpload(res, file, fileList) {
if (file.status === 'success') {
let customResult = null
if (!!this.field.options.onUploadSuccess) {
let mountFunc = new Function('result', 'file', 'fileList', this.field.options.onUploadSuccess)
customResult = mountFunc.call(this, res, file, fileList)
}
this.updateFieldModelAndEmitDataChangeForUpload(fileList, customResult, res)
if (!!customResult && !!customResult.name) {
file.name = customResult.name
} else {
file.name = file.name || res.name || res.fileName || res.filename
}
if (!!customResult && !!customResult.url) {
file.url = customResult.url
} else {
file.url = file.url || res.url
}
this.fileList = deepClone(fileList)
this.uploadBtnHidden = fileList.length >= this.field.options.limit
}
},
updateFieldModelAndEmitDataChangeForRemove(deletedFileIdx, fileList) {
let oldValue = deepClone(this.fieldModel)
this.fieldModel.splice(deletedFileIdx, 1)
this.syncUpdateFormModel(this.fieldModel)
this.emitFieldDataChange(this.fieldModel, oldValue)
},
removeUploadFile(fileName, fileUrl, fileUid) {
let foundIdx = -1
let foundFile = null
this.fileList.forEach((file, idx) => {
if ((file.name === fileName) && ((file.url === fileUrl) || (!!fileUid && file.uid === fileUid))) {
foundIdx = idx
foundFile = file
}
})
if (foundIdx >= 0) {
this.fileList.splice(foundIdx, 1)
this.updateFieldModelAndEmitDataChangeForRemove(foundIdx, this.fileList)
this.uploadBtnHidden = this.fileList.length >= this.field.options.limit
if (!!this.field.options.onFileRemove) {
let customFn = new Function('file', 'fileList', this.field.options.onFileRemove)
customFn.call(this, foundFile, this.fileList)
}
}
},
handleUploadError(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 ::v-deep .el-upload.el-upload--text {
color: $--color-primary;
font-size: 12px;
.el-icon-plus:after {
content: var(--select-file-action);
}
}
.hideUploadDiv {
::v-deep div.el-upload--picture-card { /* 隐藏最后的图片上传按钮 */
display: none;
}
::v-deep div.el-upload--text { /* 隐藏最后的文件上传按钮 */
display: none;
}
::v-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>

View File

@ -1,331 +0,0 @@
<!--
/**
* 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 && (!field.options.hidden || (designState === true))"
:label="label" :label-width="labelWidth + 'px'"
:title="field.options.labelTooltip"
:rules="rules" :prop="getPropName()"
:class="[selected ? 'selected' : '', labelAlign, customClass, field.options.required ? 'required' : '']"
@click.native.stop="selectField(field)">
<span v-if="!!field.options.labelIconClass" slot="label" 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>
<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 !!this.designState ? 5 : 0 //5便
}
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) {
const fieldRefName = this.designer.selectedWidgetName
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)
this.designer.setSelected(nextSelected)
this.designer.formWidget.deleteWidgetRef(fieldRefName) //ref
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;
::v-deep .el-form-item__label {
white-space: nowrap;
text-overflow: ellipsis;
}
::v-deep .el-form-item__content {
//position: unset; /* TODO: */
}
span.custom-label i {
margin: 0 3px;
}
/* 隐藏Chrome浏览器中el-input数字输入框右侧的上下调整小箭头 */
::v-deep .hide-spin-button input::-webkit-outer-spin-button,
::v-deep .hide-spin-button input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
}
/* 隐藏Firefox浏览器中el-input数字输入框右侧的上下调整小箭头 */
::v-deep .hide-spin-button input[type="number"] {
-moz-appearance: textfield;
}
}
.required ::v-deep .el-form-item__label::before {
content: '*';
color: #F56C6C;
margin-right: 4px;
}
.static-content-item {
min-height: 20px;
display: flex; /* 垂直居中 */
align-items: center; /* 垂直居中 */
::v-deep .el-divider--horizontal {
margin: 0;
}
}
.el-form-item.selected, .static-content-item.selected {
outline: 2px solid $--color-primary;
}
::v-deep .label-left-align .el-form-item__label {
text-align: left;
}
::v-deep .label-center-align .el-form-item__label {
text-align: center;
}
::v-deep .label-right-align .el-form-item__label {
text-align: right;
}
</style>

View File

@ -1,82 +0,0 @@
<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', //FieldWidgetbroadcast
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() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.registerToRefList()
this.initEventHandler()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* static-content-wrapper *//
</style>

View File

@ -1,10 +0,0 @@
const requireComponent = require.context('./', false, /\w+\.vue$/)
let comps = {}
requireComponent.keys().map(fileName => {
let comp = requireComponent(fileName).default;
comps[comp.name] = comp
})
export default comps;

View File

@ -1,112 +0,0 @@
<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">
<el-button slot="append" v-if="field.options.appendButton" :disabled="field.options.disabled || field.options.appendButtonDisabled"
:class="field.options.buttonIcon" @click.native="emitAppendButtonClick"></el-button>
</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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
inputType() {
if (this.field.options.type === 'number') {
return 'text' //inputtypenumberv-model
}
return this.field.options.type
},
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
</style>

View File

@ -1,103 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,270 +0,0 @@
<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" :on-preview="handlePicturePreview"
:before-upload="beforePictureUpload"
:on-success="handlePictureUpload" :on-error="handleUploadError" :on-remove="handlePictureRemove">
<div slot="tip" class="el-upload__tip"
v-if="!!field.options.uploadTip">{{field.options.uploadTip}}</div>
<i class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<el-dialog title="" v-if="showPreviewDialogFlag" :visible.sync="showPreviewDialogFlag"
v-dialog-drag append-to-body width="60%"
:show-close="true" custom-class="drag-dialog small-padding-dialog"
:close-on-click-modal="true" :close-on-press-escape="true" :destroy-on-close="true">
<img :src="previewUrl" width="100%" alt="" />
</el-dialog>
</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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: [],
rules: [],
uploadHeaders: {},
uploadData: {
key: '', //
//token: '', //token
//policy: '', //policy
//authorization: '', //
},
fileList: [], //
uploadBtnHidden: false,
previewUrl: '',
showPreviewDialogFlag: false,
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
handlePictureExceed() {
let uploadLimit = this.field.options.limit
this.$message.warning( this.i18nt('render.hint.uploadExceed').replace('${uploadLimit}', uploadLimit) )
},
handlePicturePreview(file) {
this.previewUrl = file.url
this.showPreviewDialogFlag = true
},
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.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
},
updateFieldModelAndEmitDataChangeForUpload(fileList, customResult, defaultResult) {
let oldValue = deepClone(this.fieldModel)
if (!!customResult && !!customResult.name && !!customResult.url) {
this.fieldModel.push({
name: customResult.name,
url: customResult.url
})
} else if (!!defaultResult && !!defaultResult.name && !!defaultResult.url) {
this.fieldModel.push({
name: defaultResult.name,
url: defaultResult.url
})
} else {
this.fieldModel = deepClone(fileList)
}
this.syncUpdateFormModel(this.fieldModel)
this.emitFieldDataChange(this.fieldModel, oldValue)
},
handlePictureUpload(res, file, fileList) {
if (file.status === 'success') {
let customResult = null
if (!!this.field.options.onUploadSuccess) {
let customFn = new Function('result', 'file', 'fileList', this.field.options.onUploadSuccess)
customResult = customFn.call(this, res, file, fileList)
}
this.updateFieldModelAndEmitDataChangeForUpload(fileList, customResult, res)
this.fileList = deepClone(fileList)
this.uploadBtnHidden = fileList.length >= this.field.options.limit
}
},
updateFieldModelAndEmitDataChangeForRemove(file, fileList) {
let oldValue = deepClone(this.fieldModel)
let foundFileIdx = -1
this.fileList.map((fi, idx) => {
if ((fi.name === file.name) && ((fi.url === file.url) || (!!fi.uid && fi.uid === file.uid))) { /* 这个判断有问题?? */
foundFileIdx = idx
}
})
if (foundFileIdx > -1) {
this.fieldModel.splice(foundFileIdx, 1)
}
this.syncUpdateFormModel(this.fieldModel)
this.emitFieldDataChange(this.fieldModel, oldValue)
},
handlePictureRemove(file, fileList) {
this.updateFieldModelAndEmitDataChangeForRemove(file, fileList)
this.fileList = deepClone(fileList) //this.fileList = fileList
this.uploadBtnHidden = fileList.length >= this.field.options.limit
if (!!this.field.options.onFileRemove) {
let customFn = new Function('file', 'fileList', this.field.options.onFileRemove)
customFn.call(this, file, fileList)
}
},
handleUploadError(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 {
::v-deep div.el-upload--picture-card { /* 隐藏最后的图片上传按钮 */
display: none;
}
::v-deep div.el-upload--text { /* 隐藏最后的文件上传按钮 */
display: none;
}
::v-deep div.el-upload__tip { /* 隐藏最后的文件上传按钮提示 */
display: none;
}
}
</style>

View File

@ -1,105 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initOptionItems()
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
</style>

View File

@ -1,102 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,123 +0,0 @@
<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 {VueEditor} from 'vue2-editor'
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', //FieldWidgetbroadcast
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
// VueEditor: resolve => { //
// require(['vue2-editor'], ({VueEditor}) => resolve(VueEditor))
// }
},
inject: ['refList', 'formConfig', 'globalOptionData', 'globalModel'],
data() {
return {
oldFieldValue: null, //fieldchange
fieldModel: null,
rules: [],
customToolbar: [], //
valueChangedFlag: false, //vue2-editor
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
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>

View File

@ -1,123 +0,0 @@
<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="field.options.remote" :remote-method="remoteMethod"
@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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
allowDefaultFirstOption() {
return (!!this.field.options.filterable && !!this.field.options.allowCreate)
},
remoteMethod() {
if (!!this.field.options.remote && !!this.field.options.onRemoteQuery) {
return this.remoteQuery
} else {
return undefined
}
},
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initOptionItems()
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,100 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,102 +0,0 @@
<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', //FieldWidgetbroadcast
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() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.registerToRefList()
this.initEventHandler()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
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>

View File

@ -1,196 +0,0 @@
<template>
<div class="field-wrapper" :class="{'design-time-bottom-margin': !!this.designer}" :style="{display: displayStyle}">
<div class="static-content-item" v-if="!field.options.hidden || (designState === true)" :style="{display: displayStyle}"
: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
},
displayStyle: {
type: String,
default: 'block'
},
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) {
const fieldRefName = this.designer.selectedWidgetName
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.formWidget.deleteWidgetRef(fieldRefName) //ref
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; /* 垂直居中 */
::v-deep .el-divider--horizontal {
margin: 0;
}
}
.el-form-item.selected, .static-content-item.selected {
outline: 2px solid $--color-primary;
}
</style>

View File

@ -1,83 +0,0 @@
<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" :style="!!field.options.fontSize ? `font-size: ${field.options.fontSize};`: ''">
<pre :style="{'white-space': !!field.options.preWrap ? 'pre-wrap' : 'pre', 'text-align': !!field.options.textAlign ? field.options.textAlign : 'left'}">{{field.options.textContent}}</pre></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', //FieldWidgetbroadcast
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() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.registerToRefList()
this.initEventHandler()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* static-content-wrapper *//
</style>

View File

@ -1,101 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,99 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
</style>

View File

@ -1,104 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,103 +0,0 @@
<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', //FieldWidgetbroadcast
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, //fieldchange
fieldModel: null,
rules: [],
}
},
computed: {
},
beforeCreate() {
/* 这里不能访问方法和属性!! */
},
created() {
/* mountedcreatedmountedmountedprop
需要在父组件created中初始化 */
this.initFieldModel()
this.registerToRefList()
this.initEventHandler()
this.buildFieldRules()
this.handleOnCreated()
},
mounted() {
this.handleOnMounted()
},
beforeDestroy() {
this.unregisterFromRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../../../styles/global.scss"; //* form-item-wrapper *//
.full-width-input {
width: 100% !important;
}
</style>

View File

@ -1,272 +0,0 @@
<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">
<div v-if="designer.widgetList.length === 0" class="no-widget-hint">{{i18nt('designer.noWidgetHint')}}</div>
<draggable :list="designer.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 300}"
handle=".drag-handler" @end="onDragEnd" @add="onDragAdd" @update="onDragUpdate" :move="checkMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(widget, index) in designer.widgetList">
<template v-if="'container' === widget.category">
<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>
</transition-group>
</draggable>
</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: () => ({})
},
globalDsv: {
type: Object,
default: () => ({})
},
},
provide() {
return {
refList: this.widgetRefList,
formConfig: this.formConfig,
getGlobalDsv: () => this.globalDsv, //
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.getDesignerConfig().resetFormJson );
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()
this.designer.emitEvent('field-selected', null)
},
onDragUpdate() { /* 在VueDraggable内拖拽组件发生位置变化时会触发update未发生组件位置变化不会触发 */
this.designer.emitHistoryChange()
},
checkMove(evt) {
return this.designer.checkWidgetMove(evt)
},
getFormData() {
return this.formModel
},
getWidgetRef(widgetName, showError = false) {
let foundRef = this.widgetRefList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('render.hint.refNotFound') + widgetName)
}
return foundRef
},
getSelectedWidgetRef() {
let wName = this.designer.selectedWidgetName
return this.getWidgetRef(wName)
},
clearWidgetRefList() {
Object.keys(this.widgetRefList).forEach(key => {
delete this.widgetRefList[key]
})
},
deleteWidgetRef(widgetRefName) {
delete this.widgetRefList[widgetRefName]
},
}
}
</script>
<style lang="scss" scoped>
.container-scroll-bar {
::v-deep .el-scrollbar__wrap, ::v-deep .el-scrollbar__view {
overflow-x: hidden;
}
}
.form-widget-container {
padding: 10px;
background: #f1f2f3;
overflow-x: hidden;
overflow-y: auto;
.el-form.full-height-width {
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 ::v-deep .el-row {
padding: 2px;
border: 1px dashed rgba(170, 170, 170, 0.75);
}
}
.grid-cell {
min-height: 30px;
border-right: 1px dotted #cccccc;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>

View File

@ -1,500 +0,0 @@
<!--
/**
* 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')" :hide-timeout="2000" @command="handleLanguageChanged">
<span class="el-dropdown-link">{{curLangName}}<i class="el-icon-arrow-down el-icon--right"></i></span>
<el-dropdown-menu slot="dropdown">
<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>
</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" :global-dsv="globalDsv" 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" :global-dsv="globalDsv" ref="formRef">
</v-form-widget>
</el-scrollbar>
</el-main>
</el-container>
<el-aside>
<setting-panel :designer="designer" :selected-widget="designer.selectedWidget"
:form-config="designer.formConfig" :global-dsv="globalDsv" />
</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,
getAllContainerWidgets,
getAllFieldWidgets,
getQueryParam, traverseAllWidgets
} from "@/utils/util"
import {MOCK_CASE_URL, VARIANT_FORM_VERSION} from "@/utils/config"
import i18n, { changeLocale } from "@/utils/i18n"
import axios from "axios"
import SvgIcon from '@/components/svg-icon'
export default {
name: "VFormDesigner",
componentName: "VFormDesigner",
mixins: [i18n],
components: {
WidgetPanel,
ToolbarPanel,
SettingPanel,
VFormWidget,
SvgIcon,
},
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, //
widgetNameReadonly: false, //
clearDesignerButton: true, //
previewFormButton: true, //
importJsonButton: true, //JSON
exportJsonButton: true, //JSON
exportCodeButton: true, //
generateSFCButton: true, //SFC
toolbarMaxWidth: 420, //
toolbarMinWidth: 300, //
presetCssCode: '', //CSS
resetFormJson: false, //
}
}
},
/* 全局数据源变量 */
globalDsv: {
type: Object,
default: () => ({})
},
},
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/pages/chat-group/',
subScribeUrl: 'https://www.vform666.com/pages/pro/',
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
}
let headers = this.fieldListApi.headers || {}
axios.get(this.fieldListApi.URL, {'headers': headers}).then(res => {
let labelKey = this.fieldListApi.labelKey || 'label'
let nameKey = this.fieldListApi.nameKey || 'name'
this.fieldList.splice(0, this.fieldList.length) //
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()
},
/**
* 获取所有字段组件
* @returns {*[]}
*/
getFieldWidgets(widgetList = null) {
return !!widgetList ? getAllFieldWidgets(widgetList) : getAllFieldWidgets(this.designer.widgetList)
},
/**
* 获取所有容器组件
* @returns {*[]}
*/
getContainerWidgets(widgetList = null) {
return !!widgetList ? getAllContainerWidgets(widgetList) : getAllContainerWidgets(this.designer.widgetList)
},
/**
* 升级表单json以补充最新的组件属性
* @param formJson
*/
upgradeFormJson(formJson) {
if (!formJson.widgetList || !formJson.formConfig) {
this.$message.error('Invalid form json!')
return
}
traverseAllWidgets(formJson.widgetList, (w) => {
this.designer.upgradeWidgetConfig(w)
})
this.designer.upgradeFormConfig(formJson.formConfig)
return formJson
},
getWidgetRef(widgetName, showError = false) {
return this.$refs['formRef'].getWidgetRef(widgetName, showError)
},
getSelectedWidgetRef() {
return this.$refs['formRef'].getSelectedWidgetRef()
},
//TODO:
}
}
</script>
<style lang="scss" scoped>
.el-container.main-container {
background: #fff;
::v-deep aside { /* 防止aside样式被外部样式覆盖 */
margin: 0;
padding: 0;
background: inherit;
}
}
.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 {
::v-deep .el-scrollbar__wrap, ::v-deep .el-scrollbar__view {
overflow-x: hidden;
}
}
</style>

View File

@ -1,28 +0,0 @@
export default {
methods: {
initRefList() {
if ((this.refList !== null) && !!this.widget.options.name) {
this.refList[this.widget.options.name] = this
}
},
getWidgetRef(widgetName, showError = false) {
let foundRef = this.refList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('render.hint.refNotFound') + widgetName)
}
return foundRef
},
/* 该方法用于组件重名检查!! */
registerToRefList(oldRefName) {
if ((this.refList !== null) && !!this.widget.options.name) {
if (!!oldRefName) {
delete this.refList[oldRefName]
}
this.refList[this.widget.options.name] = this
}
},
}
}

View File

@ -1,354 +0,0 @@
<template>
<div>
<el-form :model="formConfig" size="mini" label-position="left" label-width="120px"
class="setting-form" @submit.native.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')" :visible.sync="showFormEventDialogFlag"
v-if="showFormEventDialogFlag" :show-close="true" class="small-padding-dialog" append-to-body v-dialog-drag
: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" ref="ecEditor"></code-editor>
<el-alert type="info" :closable="false" title="}"></el-alert>
<div slot="footer" 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>
</el-dialog>
<el-dialog :title="i18nt('designer.setting.formCss')" :visible.sync="showEditFormCssDialogFlag"
v-if="showEditFormCssDialogFlag" :show-close="true" class="small-padding-dialog" append-to-body v-dialog-drag
: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>
<div slot="footer" 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>
</el-dialog>
<el-dialog :title="i18nt('designer.setting.globalFunctions')" :visible.sync="showEditFunctionsDialogFlag"
v-if="showEditFunctionsDialogFlag" :show-close="true" class="small-padding-dialog" append-to-body v-dialog-drag
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<code-editor :mode="'javascript'" :readonly="false" v-model="functionsCode" ref="gfEditor"></code-editor>
<div slot="footer" 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>
</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() {
//JSONCSS
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() {
/* SettingPanelFormWidget, FormWidgetformConfig
此处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) //splicelength=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() {
const codeHints = this.$refs.gfEditor.getEditorAnnotations()
let syntaxErrorFlag = false
if (!!codeHints && (codeHints.length > 0)) {
codeHints.forEach((chItem) => {
if (chItem.type === 'error') {
syntaxErrorFlag = true
}
})
if (syntaxErrorFlag) {
this.$message.error(this.i18nt('designer.setting.syntaxCheckWarning'))
return
}
}
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() {
const codeHints = this.$refs.ecEditor.getEditorAnnotations()
let syntaxErrorFlag = false
if (!!codeHints && (codeHints.length > 0)) {
codeHints.forEach((chItem) => {
if (chItem.type === 'error') {
syntaxErrorFlag = true
}
})
if (syntaxErrorFlag) {
this.$message.error(this.i18nt('designer.setting.syntaxCheckWarning'))
return
}
}
this.formConfig[this.curEventName] = this.formEventHandlerCode
this.showFormEventDialogFlag = false
},
}
}
</script>
<style lang="scss" scoped>
.setting-form {
::v-deep .el-form-item__label {
font-size: 13px;
//text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
::v-deep .el-form-item--mini.el-form-item {
margin-bottom: 6px;
}
.radio-group-custom {
::v-deep .el-radio-button__inner {
padding-left: 12px;
padding-right: 12px;
}
}
.custom-divider.el-divider--horizontal {
margin: 10px 0;
}
}
.setting-collapse {
::v-deep .el-collapse-item__content {
padding-bottom: 6px;
}
::v-deep .el-collapse-item__header {
font-style: italic;
font-weight: bold;
}
}
.small-padding-dialog {
::v-deep .el-dialog__body {
padding: 6px 15px 12px 15px;
}
}
</style>

View File

@ -1,367 +0,0 @@
<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.native.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.native.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')" :visible.sync="showWidgetEventDialogFlag"
v-if="showWidgetEventDialogFlag" :show-close="true" class="small-padding-dialog" append-to-body v-dialog-drag
: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" ref="ecEditor"></code-editor>
<el-alert type="info" :closable="false" title="}"></el-alert>
<div slot="footer" 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>
</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 { propertyRegistered } from "@/components/form-designer/setting-panel/propertyRegister";
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,
globalDsv: {
type: Object,
default: () => ({})
},
},
provide() {
return {
isSubFormChildWidget: () => this.subFormChildWidgetFlag,
}
},
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() {
this.$on('editEventHandler', function (eventName, eventParams) {
this.editEventHandler(eventName, eventParams)
})
this.designer.handleEvent('form-css-updated', (cssClassList) => {
this.designer.setCssClassList(cssClassList)
})
//
this.designer.handleEvent('field-selected', (parentWidget) => {
this.subFormChildWidgetFlag = !!parentWidget && (parentWidget.type === 'sub-form');
//console.log('subFormChildWidgetFlag', this.subFormChildWidgetFlag)
})
},
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
}
/* alert组件注册了两个type属性编辑器跳过第一个type属性编辑器只显示第二个alert-type属性编辑器 */
if (propName.indexOf('-') <= -1) {
let uniquePropName = this.selectedWidget.type + '-' + propName
if (propertyRegistered(uniquePropName)) {
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() {
const codeHints = this.$refs.ecEditor.getEditorAnnotations()
let syntaxErrorFlag = false
if (!!codeHints && (codeHints.length > 0)) {
codeHints.forEach((chItem) => {
if (chItem.type === 'error') {
syntaxErrorFlag = true
}
})
if (syntaxErrorFlag) {
this.$message.error(this.i18nt('designer.setting.syntaxCheckWarning'))
return
}
}
this.selectedWidget.options[this.curEventName] = this.eventHandlerCode
this.showWidgetEventDialogFlag = false
},
}
}
</script>
<style lang="scss" scoped>
.panel-container {
padding: 0 8px;
}
.setting-scrollbar {
::v-deep .el-scrollbar__wrap {
overflow-x: hidden; /* IE浏览器隐藏水平滚动条箭头 */
}
}
.setting-collapse {
::v-deep .el-collapse-item__content {
padding-bottom: 6px;
}
::v-deep .el-collapse-item__header {
font-style: italic;
font-weight: bold;
}
}
.setting-form {
::v-deep .el-form-item__label {
font-size: 13px;
//text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
::v-deep .el-form-item--mini.el-form-item {
margin-bottom: 6px;
}
}
/* 隐藏Chrome浏览器中el-input数字输入框右侧的上下调整小箭头 */
::v-deep .hide-spin-button input::-webkit-outer-spin-button,
::v-deep .hide-spin-button input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
}
/* 隐藏Firefox浏览器中el-input数字输入框右侧的上下调整小箭头 */
::v-deep .hide-spin-button input[type="number"] {
-moz-appearance: textfield;
}
::v-deep .custom-divider.el-divider--horizontal {
margin: 10px 0;
}
::v-deep .custom-divider-margin-top.el-divider--horizontal {
margin: 20px 0;
}
.small-padding-dialog {
::v-deep .el-dialog__body {
padding: 6px 15px 12px 15px;
}
}
</style>

View File

@ -1,234 +0,0 @@
<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'}">
<li v-for="(option, idx) in optionModel.optionItems" :key="idx">
<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>
</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'}">
<li v-for="(option, idx) in optionModel.optionItems" :key="idx">
<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>
</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')" :visible.sync="showImportDialogFlag"
v-if="showImportDialogFlag" :show-close="true" class="small-padding-dialog" append-to-body
: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>
<div slot="footer" 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>
</el-dialog>
<el-dialog :title="i18nt('designer.setting.importOptions')" :visible.sync="showImportCascaderDialogFlag"
v-if="showImportCascaderDialogFlag" :show-close="true" class="small-padding-dialog" append-to-body
: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>
<div slot="footer" 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>
</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 ::v-deep .el-dialog__body {
padding: 10px 15px;
}
.dialog-footer .el-button {
width: 100px;
}
</style>

View File

@ -1,173 +0,0 @@
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-switch 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" />
}
}
}

View File

@ -1,24 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.allowCreate')">
<el-switch v-model="optionModel.allowCreate"></el-switch>
</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>

View File

@ -1,28 +0,0 @@
<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-switch v-model="optionModel.appendButton"></el-switch>
</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>

View File

@ -1,23 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.appendButtonDisabled')">
<el-switch v-model="optionModel.appendButtonDisabled"></el-switch>
</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>

View File

@ -1,23 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.automaticDropdown')">
<el-switch v-model="optionModel.automaticDropdown"></el-switch>
</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>

View File

@ -1,23 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.border')">
<el-switch v-model="optionModel.border"></el-switch>
</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>

View File

@ -1,23 +0,0 @@
<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>

View File

@ -1,23 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.buttonStyle')">
<el-switch v-model="optionModel.buttonStyle"></el-switch>
</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>

View File

@ -1,23 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.clearable')">
<el-switch v-model="optionModel.clearable"></el-switch>
</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>

View File

@ -1,26 +0,0 @@
<template>
<div>
<el-form-item :label="i18nt('designer.setting.widgetColumnWidth')" v-show="!!isSubFormChildWidget()">
<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,
},
inject: ['isSubFormChildWidget'],
}
</script>
<style scoped>
</style>

View File

@ -1,24 +0,0 @@
<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>

View File

@ -1,24 +0,0 @@
<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>

View File

@ -1,24 +0,0 @@
<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>

View File

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

View File

@ -1,48 +0,0 @@
<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>

View File

@ -1,28 +0,0 @@
<template>
<div>
<el-form-item :label="i18nt('designer.setting.gridColHeight')">
<el-input type="number" v-model="optionModel.colHeight" @input.native="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>

View File

@ -1,81 +0,0 @@
<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>

View File

@ -1,23 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.showBlankRow')">
<el-switch v-model="optionModel.showBlankRow"></el-switch>
</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>

View File

@ -1,23 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.showRowNumber')">
<el-switch v-model="optionModel.showRowNumber"></el-switch>
</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>

View File

@ -1,36 +0,0 @@
<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 {
::v-deep .el-radio-button__inner {
padding-left: 12px;
padding-right: 12px;
}
}
</style>

View File

@ -1,119 +0,0 @@
<!--
因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"
v-bind="{group:'panesGroup', ghostClass: 'ghost', handle: '.drag-option'}">
<li v-for="(tpItem, tpIdx) in selectedWidget.tabs" :key="tpIdx" 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>
<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>

View File

@ -1,23 +0,0 @@
<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>

View File

@ -1,23 +0,0 @@
<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>

View File

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

View File

@ -1,40 +0,0 @@
<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>

View File

@ -1,25 +0,0 @@
<template>
<el-form-item v-if="!hasConfig('optionItems')" :label="i18nt('designer.setting.defaultValue')">
<el-input 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>

View File

@ -1,23 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.disabled')">
<el-switch v-model="optionModel.disabled"></el-switch>
</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>

View File

@ -1,26 +0,0 @@
<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>

View File

@ -1,24 +0,0 @@
<template>
<el-form-item :label="i18nt('designer.setting.editable')">
<el-switch v-model="optionModel.editable"></el-switch>
</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>

View File

@ -1,23 +0,0 @@
<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>

Some files were not shown because too many files have changed in this diff Show More