VForm3开发文档
# 介绍
VForm3是一款基于Vue 3.x的低代码表单,支持Element Plus
UI库,定位为前端开发人员提供快速搭建表单、实现表单交互和数据收集的功能。
VForm由表单设计器VFormDesigner和表单渲染器VFormRender两部分构成,VFormDesigner通过拖拽组件方式生成JSON格式的表单对象,VFormRender负责将表单JSON渲染为Vue组件。
# 编译打包和项目集成
# 拉取仓库源码
Gitee:https://gitee.com/shend/vform3.x-pro.git
拉取源码后,运行yarn或npm install安装依赖包。
# 设计器VFormDesigner打包
1.运行 npm run lib 即可完成打包,内含v-form-designer、v-form-render两个组件,输出的库文件如下:
将选中style.css文件改名为designer.style.css。
2.在项目根目录新建lib/vform,将上述两个选中文件复制到vform目录,目录结构如下所示:
- 修改main.js文件,引入组件(假设库文件位于项目根目录下的lib/vform目录):
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus' //引入element-plus库
import 'element-plus/dist/index.css' //引入element-plus样式
import VForm3 from '@/../lib/vform/designer.umd.js'
import '../lib/vform/designer.style.css'
const app = createApp(App)
app.use(ElementPlus) //全局注册element-plus
app.use(VForm3) //全局注册VForm3,同时注册了v-form-designer、v-form-render等组件
app.mount('#app')
醒目提示:你如果的Vue 3项目使用TS(TypeScript),则应该增加如下配置内容:
在项目src目录下新建types目录,新增一个index.d.ts,文件内容仅一行: declare module "@/../lib/vform/designer.umd.js"
- 如果使用Vite作为构建工具,则需要配置vite.config.js中的optimizeDeps和build属性,如下所示:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": resolve(__dirname, 'src'), //@路径别名
},
extensions: ['.js', '.vue', '.json', '.ts'] //使用路径别名时想要省略的后缀名,可以自己增减
},
optimizeDeps: {
include: ['@/../lib/vform/designer.umd.js'] //此处路径必须跟main.js中import路径完全一致!
},
build: {
/* 其他build生产打包配置省略 */
//...
commonjsOptions: {
include: /node_modules|lib/ //这里记得把lib目录加进来,否则生产打包会报错!!
}
},
})
- 在Vue模板中使用组件
<template>
<v-form-designer></v-form-designer>
</template>
<script setup>
</script>
<style lang="scss">
body {
margin: 0; /* 如果页面出现垂直滚动条,则加入此行CSS以消除之 */
}
</style>
- 指定AceEditor的动态加载路径
代码编辑器组件AceEditor大量使用了动态按需加载js技术,仅当需要时才动态加载所需js、css文件,目前AceEditor库文件部署在VForm官方的CDN服务器上,如果你要在生产环境使用VForm,建议私有部署AceEditor。
私有部署方法:将node_modules/ace-builds/src-min-noconflict目录下的所有文件上传到web服务器或CDN。
私有部署AceEditor后,需要同步修改utils/config.js文件如下一行代码:
export const ACE_BASE_PATH = '部署后的ace打包库文件URL'
- 设计器样式复位
当设计器嵌入其他项目(如Admin Web类框架),设计器的样式可能会被外部样式污染,导致显示错位、美观度降低。出现这种情况时,只需在v-form-designer之外包裹一个div容器,并在该div上进行样式复位。具体需要复位哪些样式,可跟VForm 3 Demo演示版本进行对比找出。
# 渲染器VFormRender打包
提示:如果表单设计器(v-form-designer)和表单渲染器(v-form-render)在同一个项目中使用,并不需要单独打包渲染器,因为上述打包设计器生成的库文件已经包含v-form-render组件。
仅当表单渲染器和设计器在不同项目中使用时,才需要单独打包表单渲染器,单独打包的目的是为了降低所生成的库文件大小。
- 运行 npm run lib-render 即可完成打包,内含v-form-render组件,输出的库文件为如下3个:
将选中style.css文件改名为render.style.css。
- 在新项目根目录新建lib/vform,将上述两个文件复制到vform目录,目录结构如下所示:
- 修改main.js文件,引入组件(假设库文件位于项目根目录下的lib/vform目录):
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus' //引入element-plus库
import 'element-plus/dist/index.css' //引入element-plus样式
import VForm3Render from '@/../lib/vform/render.umd.js'
import '../lib/vform/render.style.css'
const app = createApp(App)
app.use(ElementPlus) //全局注册element-plus
app.use(VForm3Render) //注册v-form-render等组件
app.mount('#app')
醒目提示:你如果的Vue 3项目使用TS(TypeScript),则应该增加如下配置内容:
在项目src目录下新建types目录,新增一个index.d.ts,文件内容仅一行: declare module "@/../lib/vform/render.umd.js"
- 如果使用Vite作为构建工具,则需要配置vite.config.js中的optimizeDeps和build属性,如下所示
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": resolve(__dirname, 'src'), //@路径别名
},
extensions: ['.js', '.vue', '.json', '.ts'] //使用路径别名时想要省略的后缀名,可以自己增减
},
optimizeDeps: {
include: ['@/../lib/vform/render.umd.js'] //此处路径必须跟main.js中import路径完全一致!
},
build: {
/* 其他build生产打包配置省略 */
//...
commonjsOptions: {
include: /node_modules|lib/ //这里记得把lib目录加进来,否则生产打包会报错!!
}
},
})
- 在Vue模板中使用组件
重要提示:如果表单json对象是通过后台接口异步获取到的,用form-json属性传值则会导致表单校验提示异常,需要调用setFormJson(xxx)方法
<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 setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
/* 注意:formJson是指表单设计器导出的json,此处演示的formJson只是一个空白表单json!! */
const formJson = reactive({"widgetList":[],"formConfig":{"modelName":"formData","refName":"vForm","rulesName":"rules","labelWidth":80,"labelPosition":"left","size":"","labelAlign":"label-left-align","cssCode":"","customClass":"","functions":"","layoutType":"PC","jsonVersion":3,"onFormCreated":"","onFormMounted":"","onFormDataChange":"","onFormValidate":""}})
const formData = reactive({})
const optionData = reactive({})
const vFormRef = ref(null)
const submitForm = () => {
vFormRef.value.getFormData().then(formData => {
// Form Validation OK
alert( JSON.stringify(formData) )
}).catch(error => {
// Form Validation failed
ElMessage.error(error)
})
}
</script>
# 可能存在的问题及解决方法
如果使用Vite作为Vue项目的构建工具,Vite会将预构建的依赖缓存到 node_modules/.vite 目录下,如果发现编译打包的VForm3组件复制到 lib/vform 之后没有自动更新,请手工删除新项目里的 node_modules/.vite 目录后重新运行项目
# VForm组件接口文档
# 表单组件
# 表单设计器——VFormDesigner
# 属性(prop)
属性名称 | 属性值说明 | 默认值 |
---|---|---|
field-list-api | 配置获取后端字段列表接口对象 | 默认为null接口对象格式为:{ URL: '字段列表接口请求地址', nameKey: 'fieldName', labelKey: 'fieldLabel'}详情参见:字段接口文档 |
banned-widgets | 配置禁止设计器显示的组件 | 默认为[],数据格式为组件type名称的数组,比如:['table', 'rate', 'switch'] |
designer-config | 配置设计器初始化界面显示设置 | 默认值为:{//是否显示语言切换菜单languageMenu: true,//是否显示GitHub、文档等外部链接externalLink: true,//是否显示表单模板formTemplates: true,//是否禁止修改唯一名称widgetNameReadonly: false,//是否显示组件事件属性折叠面板eventCollapse: true,//是否显示清空设计器按钮clearDesignerButton: true,//是否显示预览表单按钮previewFormButton: true,//是否显示导入JSON按钮importJsonButton: true,//是否显示导出JSON器按钮exportJsonButton: true,//是否显示导出代码按钮exportCodeButton: true,//是否显示生成SFC按钮generateSFCButton: true,//工具按钮栏最大宽度(单位px)//如新增按钮后不可见,请调大//如右侧空白区域过大,请调小toolbarMaxWidth: 420,//工具栏按钮最小宽度(单位px)//值必须小于toolbarMaxWidthtoolbarMinWidth: 300,//表单设计器预设CSS代码presetCssCode: '',//表单设计器初始化自动清空内容/* 如设置为true,则刷新页面时也会清空设计器画布区域,慎用!*/resetFormJson: false,//设置自定义产品名称(仅Pro)productName: '',//设置自定义产品标题(仅Pro)productTitle: '',//是否显示顶部LOGO条(仅Pro)logoHeader: true,} |
global-dsv | 全局数据源变量(Pro版支持)dsv即Datasource Variables(数据源变量)缩写。 | 默认值为{},数据格式为:{ "myTestURL": "xxx", "token": "aabbcc", "countLimit": 600 //可以自由添加键值对}在数据源中可使用DSV["myTestURL"]、DSV["token"]、DSV["countLimit"]获取上述变量值。 |
form-templates | 表单模板配置 | 默认值为null,使用内置模板。配置值为数组,格式为:[{ title: '表单模板名称', imgUrl: '模板缩略图URL', jsonStr: '表单模板json字符串', //优先级高于jsonUrl jsonUrl: '表单模板对象读取URL', //优先级低于jsonStr description: ‘表单模板描述文字’, i18n: { //优先级高于title和description 'zh-CN': { title: '中文title', description: ‘中文描述文字’, }, 'en-US': { title: '英文title', description: ‘英文描述文字’, } } }, ... ] |
# 插槽
名称 | 说明 |
---|---|
customToolButtons | 用于定制表单设计器的自定义工具按钮,位于默认工具按钮的右侧。 |
# 原生事件
原生事件可以在Vue模板中用@事件名称或JS代码中调用Vue实例的$on添加事件处理方法。
事件名称 | 说明 | 回调参数 |
---|---|---|
暂无 |
# 交互事件
交互事件可在表单设计器中编写事件处理代码。
事件名称 | 说明 | 回调参数 |
---|---|---|
暂无 |
# API方法
方法名 | 说明 | 参数 |
---|---|---|
changeLanguage | 修改界面显示语言 | string:'zh-CN'、'en-US' |
setFormJson | 设置表单JSON对象 | string/object |
getFormJson | 获取表单JSON对象 | — |
clearDesigner | 清空设计器画布 | — |
refreshDesigner | 刷新设计器画布(几乎用不到) | — |
previewForm | 打开--预览表单--弹窗 | — |
importJson | 打开--导入表单JSON--弹窗 | — |
exportJson | 打开--导出表单JSON--弹窗 | — |
exportCode | 打开--导出Vue/HTML代码--弹窗 | — |
generateSFC | 打开--生成SFC组件代码--弹窗 | — |
getFieldWidgets | 获取表单JSON中的所有字段,返回对象数组,格式如下: [ { type: String, name: String, field: Object }, ..., ] | — 无参数 注意:该方法获取的是formJson中的字段JSON定义,并非渲染后的字段组件。 |
getContainerWidgets | 获取表单JSON中的所有容器,返回对象数组,格式如下: [ { type: String, name: String, container: Object }, ..., ] | — 无参数 注意:该方法获取的是formJson中的容器JSON定义,并非渲染后的容器组件。 |
getWidgetRef | 获取设计器组件实例 | (widgetName, showError): widgetName,组件名称 showError=true/false,如组件不存在是否显示错误 |
getSelectedWidgetRef | 获取设计器选中的组件实例 | — |
buildFormDataSchema | 构建并返回表单数据结构对象 | — |
addDataSource | 增加数据源对象 | dsObj,数据源对象 { dataSourceId: '1x2y3z98765', //确保表单内唯一不重复 uniqueName: '', requestURL: '', requestURLType: 'String', description: '', headers: [ ], params: [ ], data: [ ], requestMethod: 'get', configHandlerCode: ' return config', dataHandlerCode: ' return result.data.data;', errorHandlerCode: ' $message.error(error.message);', dataSetEnabled: false, // 是否开启多数据集 dataSets: [ ], }, |
getFormTemplates | 获取全部表单模板 | 返回表单模板数组 |
clearFormTemplates | 清空全部表单模板 | — |
addFormTemplate | 追加新的表单模板 | formTemplate,表单模板对象 { title: '表单模板名称', imgUrl: '模板缩略图URL', jsonStr: '表单模板json字符串', //优先级高于jsonUrl jsonUrl: '表单模板对象读取URL', //优先级低于jsonStr description: ‘表单模板描述文字’, i18n: { //优先级高于title和description 'zh-CN': { title: '中文title', description: ‘中文描述文字’, }, 'en-US': { title: '英文title', description: ‘英文描述文字’, } } } |
# 表单渲染器——VFormRender
# 属性(prop)
属性名称 | 属性值说明 | 默认值 |
---|---|---|
form-json | 表单对象JSON 注意:如表单Json是从后端接口异步获取到的,请使用下述的API方法——setFormJson()赋值,使用form-json属性传值则会导致表单校验异常或表单渲染不成功。 | 无默认值 |
option-data | 表单radio、checkbox、select、cascader组件的选择项集合 | 默认值为空对象:{}, 属性值格式如下: { 'gender': [ {label: '男', value: '1'}, {label: '女', value: '2'} ], 'paymentType': [ {label: '支付宝', value: 'alipay'}, {label: '微信', value: 'wechat'} ] } |
form-data | 表单数据对象 注意:form-data属性通常用于表单初始化时给表单传递回显数据,如需多次修改表单数据或发现表单部分数据失去响应式更新,请调用下述的setFormData(formData)方法。 | 默认值为空对象:{}, 属性值格式如下: { 'gender': '2', 'paymentType': 'wechat' 'subForm01': [ {'price': '88.00', 'count': '12'}, {'price': '199.00', 'count': '16'} ] } |
global-dsv | 全局数据源变量(Pro版支持) dsv即Datasource Variables(数据源变量)缩写。 | 默认值为{},数据格式为: { "myTestURL": "xxx", "token": "aabbcc", "countLimit": 600 } 在数据源中可使用 DSV["myTestURL"]、 DSV["token"]、 DSV["countLimit"] 获取上述变量值。 |
# 原生事件
原生事件可以在Vue模板中用@事件名称或JS代码中调用Vue实例的$on添加事件处理方法。
事件名称 | 说明 | 回调参数 |
---|---|---|
buttonClick | button组件被点击时触发 注意:如果button组件的交互事件onClick属性不为空,则不会触发buttonClick原生事件,两者不会同时生效。 | buttonWidget:被点击的按钮组件 |
appendButtonClick | input组件的附加按钮被点击时触发 | widget:input组件 |
formChange | 表单数据改变后触发 | (fieldName, newValue, oldValue, formModel, subFormName, subFormRowIndex): fieldName,触发本事件的字段名 newValue,字段新值 oldValue,字段旧值 formModel,表单数据对象 如果是子表单数据改变,则还会返回下述两个回调参数: subFormName, 子表单名称 subFormRowIndex, 子表单行索引 |
# 交互事件
交互事件可在表单设计器中编写事件处理代码。
事件名称 | 说明 | 回调参数 |
---|---|---|
onFormCreated | 表单创建后触发 | — |
onFormMounted | 表单挂载后触发 | — |
onFormDataChange | 表单数据改变后触发 | (fieldName, newValue, oldValue, formModel, subFormName, subFormRowIndex):fieldName,触发本事件的字段名newValue,字段新值oldValue,字段旧值formModel,表单数据对象如果是子表单数据改变,则还会返回下述两个回调参数subFormName, 子表单名称subFormRowIndex, 子表单行索引 |
# API方法
方法名 | 说明 | 参数 |
---|---|---|
changeLanguage | 修改界面显示语言 | string:'zh-CN'、'en-US' |
setFormJson | 动态加载表单JSON对象 | string/object |
getWidgetRef | 获取容器或字段组件 | (widgetName, showError): widgetName,组件名称 showError=true/false,如组件不存在是否显示错误 |
getNativeForm | 获取内嵌的el-form组件 | — |
validateForm | 表单数据是否验证通过 | callback(valid),回调函数,接受valid参数(true/false) |
getFormData | 获取表单数据对象 | (needValidation = true),获取表单数据时是否开启表单校验,默认开启 |
setFormData | 设置表单数据对象 | formData,表单数据JSON对象 |
getFieldValue | 获取表单单个字段值 | fieldName,字段名称 |
setFieldValue | 设置表单单个字段值 | (fieldName, fieldValue):字段键值对 |
reloadOptionData | 刷新选项类型字段的选项数据(选项数据是从option-data属性传入的) | widgetNames,组件名称,格式为字符串或字符串数组,不传该参数则刷新所有选项类型字段 |
getSubFormValues | 获取子表单数据对象 | subFormName,子表单名称 |
disableForm | 禁用表单编辑 | — |
enableForm | 启用表单编辑 | — |
resetForm | 重置表单数据,并清除校验状态 | — |
disableWidgets | 一个或多个组件禁用编辑 | widgetNames,组件名称,格式为字符串或字符串数组 |
enableWidgets | 一个或多个组件启用编辑 | widgetNames,组件名称,格式为字符串或字符串数组 |
hideWidgets | 隐藏一个或多个组件 | widgetNames,组件名称,格式为字符串或字符串数组 |
showWidgets | 显示一个或多个组件 | widgetNames,组件名称,格式为字符串或字符串数组 |
getFieldWidgets | 获取表单JSON中的所有字段,返回对象数组,格式如下: [ { type: String, name: String, field: Object }, ..., ] | — 无参数 注意:该方法获取的是formJson中的字段JSON定义,并非渲染后的字段组件,如需获取字段组件,需要进一步调用getWidgetRef()方法。 |
getContainerWidgets | 获取表单JSON中的所有容器,返回对象数组,格式如下: [ { type: String, name: String, container: Object }, ..., ] | — 无参数 注意:该方法获取的是formJson中的容器JSON定义,并非渲染后的容器组件,如需获取容器组件,需要进一步调用getWidgetRef()方法。 |
addEC | 增加外部组件实例引用,以便在VForm组件内部调用外部组件方法。 EC即external component(外部组件)缩写。 | (componentName, externalComponent): componentName, 外部组件名称 externalComponent, 外部组件实例 |
hasEC | 判断是否存在外部组件实例 | componentName, 外部组件名称 |
getEC | 获取外部组件实例 | componentName, 外部组件名称 |
getGlobalDsv | 获取全局数据源变量 | — 无参数 |
showDialog | 显示弹出窗口 | (dialogName, formData,extraData):dialogName,弹窗唯一名称formData,弹窗表单回填数据extraData,外部数据,用于其他逻辑处理 |
executeDataSource | 执行数据源请求 | dsName,数据源名称localDsv,本地DSV变量,localDsv对象会自动跟全局数据源对象globalDsv合并,合并之后传递给数据源,数据源设置处可通过DSV对象获取 |
getDialogOrDrawerRef | 弹窗、抽屉组件在渲染阶段,会动态创建一个v-form-render组件,弹窗和抽屉内的组件由此动态创建的v-form-render负责渲染。本方法可以获取表单所在的弹窗、抽屉组件实例,以便调用弹窗、抽屉组件的API方法。 | — 无参数 |
# 容器组件
# 容器类型
# 功能对比
容器类型 | 功能特点 | 备注 |
---|---|---|
栅格Grid | 支持24栅格列,可响应式布局,支持容器嵌套 | 不可按像素指定宽度或高度 |
表格Table | 不支持响应式布局,可合并行或列,支持容器嵌套 | 可按像素指定行高或列宽 |
页签Tab | 支持多页签展示内容,支持容器嵌套 | |
卡片Card | 支持容器嵌套 | |
子表单 SubForm | 单行式布局,不支持容器嵌套,只能嵌套字段组件 | 支持多个子表单 (注:子表单组件包含在Pro版本中) |
数据表格 DataTable | 多行多列展示数据 | (注:数据表格组件包含在Pro版本中) |
# 交互事件
交互事件可在表单设计器中编写事件处理代码。
事件名称 | 说明 | 回调参数 |
---|---|---|
onSubFormRowAdd | 子表单新增行时触发 | (subFormData, newRowId):新增后的子表单数据,新增行Id(非行索引) |
onSubFormRowInsert | 子表单插入行时触发 | (subFormData, newRowId):插入后的子表单数据,插入行Id(非行索引) |
onSubFormRowDelete | 子表单删除行时触发 | (subFormData, deletedDataRow):删除后的子表单数据,删除行数据 |
onSubFormRowChange | 子表单新增行、插入行、删除行时触发 | (subFormData):变化后的子表单数据 |
# API 方法
方法名 | 说明 | 参数 |
---|---|---|
getFormRef | 获取VFormRender组件 | — |
getWidgetRef | 获取容器或字段组件 | (widgetName, showError): 组件名称,如组件不存在是否显示错误 |
setHidden | 设置是否隐藏容器 | true/false |
activeTab | 激活Tab页签指定页 | tabIndex:tab页索引 |
disableTab | 禁用Tab页签指定页 | tabIndex:tab页索引 |
enableTab | 恢复Tab页签指定页 | tabIndex:tab页索引 |
hideTab | 隐藏Tab页签指定页 | tabIndex:tab页索引 |
showTab | 显示Tab页签指定页 | tabIndex:tab页索引 |
disableSubFormRow | 禁用子表单指定行 | rowIndex:行索引 |
enableSubFormRow | 恢复子表单指定行 | rowIndex:行索引 |
disableSubForm | 禁用子表单 | — |
enableSubForm | 恢复子表单 | — |
resetSubForm | 重置子表单数据为空 | — |
getSubFormValues | 获取子表单数据 | — |
setSubFormValues | 设置子表单数据 | 参数为键值对数组,格式如下[{name: 'Jack', age: 19},{name: 'Rose', age: 18}] |
getGlobalDsv | 获取全局数据源变量 | — 无参数 |
# 字段组件
# 字段组件类型
# 字段组件类型名
字段类型 | 类型名(type) | 备注 |
---|---|---|
单行输入 | input | |
多行输入 | textarea | |
计数器 | number | |
单选项 | radio | |
多选项 | checkbox | |
下拉选项 | select | |
时间 | time | |
时间范围 | time-range | |
日期 | date | |
日期范围 | date-range | |
开关 | switch | |
评分 | rate | |
颜色选择器 | color | |
滑块 | slider | |
静态文字 | static-text | |
HTML | html-text | |
按钮 | button | |
分隔线 | divider | |
图片 | picture-upload | |
文件 | file-upload | |
富文本 | rich-editor | |
级联选择 | cascader | |
# 交互事件
交互事件可在表单设计器中编写事件处理代码。
事件名称 | 说明 | 回调参数 |
---|---|---|
onCreated | 组件创建后触发 | — |
onMounted | 组件挂载后触发 | — |
onClick | 按钮被点击触发 | — |
onInput | 输入框值改变时触发 | value:当前输入值 |
onChange | 组件数据(v-model)改变时触发 | (value, oldValue, subFormData, rowId): value,当前数据值 oldValue,变化之前的数据值 如果字段位于子表单内,还会返回下述两个回调参数: subFormData,组件所在子表单数据 rowId,组件所在子表单的行Id(非行索引) |
onFocus | 获得焦点时触发 | event:事件 |
onBlur | 失效焦点时触发 | event:事件 |
onValidate | 组件数据校验时触发 | (rule, value, callback): rule,组件校验规则 value,组件数据值 callback,校验回调函数 |
onBeforeUpload | 图片或文件组件开始上传时触发 | file:上传文件 |
onUploadSuccess | 图片或文件组件上传成功后触发 | (result, file, fileList): result,上传处理结果 file,当前上传文件 fileList,上传文件列表 |
onUploadError | 图片或文件组件上传失败后触发 | (error, file, fileList): error,错误信息 file,当前上传文件 fileList,上传文件列表 |
onFileRemove | 删除图片或文件时触发 | (file, fileList): file,被删除文件 fileList,文件列表 |
onRemoteQuery | 下拉选项组件(Select)远程搜索时触发 | keyword:搜索词 |
# API方法
方法名 | 说明 | 参数 |
---|---|---|
getFormRef | 获取VFormRender组件 | — |
getWidgetRef | 获取容器或字段组件 | (widgetName, showError): widgetName,组件名称 showError=true/false,如组件不存在是否显示错误 |
setValue | 设置组件数据值 | newValue,组件数据值 |
getValue | 获取组件数据值 | — |
resetField | 重置组件为默认值,并清除组件校验状态 | — |
setReadonly | 设置组件是否只读,仅input、textarea、time、time-range、date、date-range等部分组件支持 | true/false |
setDisabled | 设置组件是否禁用 | true/false |
setAppendButtonVisible | 设置组件附加按钮是否可见,仅支持input | true/false |
setAppendButtonDisabled | 设置组件附加按钮是否禁用,仅支持input | true/false |
setHidden | 设置组件是否隐藏 | true/false |
setRequired | 设置组件是否必填 | true/false |
setLabel | 设置组件标签文字(label) | newLabel,新标签文字 |
focus | 让组件获得输入焦点 | — |
loadOptions | 加载选择项,仅支持radio/checkbox/select/cascader组件 | options,选项数组,格式为: [ {label: '选项1', value: '1'}, {label: '选项2', value: '2'}, {label: '选项3', value: '3'} ] |
getOptions | 返回radio/checkbox/select/cascader组件的选择项数组 | — |
disableOption | 禁用指定选择项,仅支持radio/checkbox/select/cascader组件 | optionValue,被禁用的选项值 |
enableOption | 启用指定选择项,仅支持radio/checkbox/select/cascader组件 | optionValue,被启用的选项值 |
setUploadHeader | 设置文件或图片组件请求头(Header) | (name, value):请求头键值对 |
setUploadData | 设置文件或图片组件上传参数 | (name, value):参数键值对 |
setToolbar | 设置富文本组件默认工具栏按钮 | customToolbar,工具按钮数组,详细参见Vue2Editor文档 |
setWidgetOption | 设置组件属性 | (optionName, optionValue): optionName,组件属性名 optionValue,属性值 |
getFieldEditor | 获取组件内嵌的Element UI表单组件 | — |
getGlobalDsv | 获取全局数据源变量 | — 无参数 |
# 表单设计的一般流程
# 确定表单元素布局
选择合适的容器组件进行组合排放:栅格Grid、表格Table、标签页Tab、子表单SubForm;
# 选择表单组件、排版定位
拖放合适的字段组件放置于容器组件合适的位置,并对字段组件命名,确保组件名称在当前表单中唯一;
# 设置组件属性
如有需求,设置字段组件合适的属性或表单全局属性(注意:组件同名属性优先级高于表单全局属性);
# 处理组件交互逻辑
如有需求,选择合适的组件事件,编写交互处理代码;
# 设置组件自定义样式
如有需求,可添加表单CSS自定义样式,并在组件中应用自定义样式;
# 导出表单代码
导出表单JSON代码应用于VFormRender组件,也可直接导出Vue组件代码或HTML源码,此外也支持生成SFC组件源码。
# 使用手册
# 后端集成
# 保存表单对象及渲染表单
# 保存表单对象
实现思路:
- 配置v-form-designer的customToolButtons插槽,新增一个“保存”按钮;
- 新增一个saveFormJson()方法,通过ref调用getFormJson()方法获取表单json对象,并提交给后台接口;
- 点击“保存”按钮时,调用上述saveFormJson()方法;
- 收工。
<template>
<div id="app">
<v-form-designer ref="vfDesigner" :field-list-api="fieldListApi" :banned-widgets="testBanned"
:designer-config="designerConfig">
<!-- 自定义按钮插槽演示 -->
<template #customToolButtons>
<el-button type="text" @click="saveFormJson"><i class="el-icon-finished" />保存</el-button>
</template>
</v-form-designer>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
fieldListApi: {
URL: 'https://www.fastmock.site/mock/2de212e0dc4b8e0885fea44ab9f2e1d0/vform/listField',
labelKey: 'fieldLabel',
nameKey: 'fieldName'
},
testBanned: [
//'sub-form',
//'alert',
],
designerConfig: {
languageMenu: true,
//externalLink: false,
//formTemplates: false,
//eventCollapse: false,
//clearDesignerButton: false,
//previewFormButton: false,
//presetCssCode: '.abc { font-size: 16px; }',
}
}
},
methods: {
saveFormJson() {
let formJson = this.$refs.vfDesigner.getFormJson()
//TODO: 将formJson提交给后端保存接口,需自行实现!!
this.$message.success('表单已保存!')
},
}
}
</script>
<style lang="scss">
#app {
height: 100%;
}
</style>
# 渲染表单
实现思路:
- 如表单中有保存于后端的选项数据(对应radio、check、select、cascader四类组件),从后端接口获取后赋值给v-form-render的option-data属性(可参见选项数据加载 (opens new window));
- 如表单需要显示后端已保存的数据对象,从后端接口获取后赋值给v-form-render的form-data属性,如只需显示空白表单,则form-data属性可传入空对象{};
- 从后端接口获取表单json对象fjson,调用v-form-render对象的setFormJson(fjson)方法加载表单;
- 上述3步,应在mounted事件钩子中完成。如表单在对话框中显示,则以上3步,应在对话框显示之前完成;
- 完工。
<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">提交表单</el-button>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
formJson: {"widgetList":[],"formConfig":{"modelName":"formData","refName":"vForm","rulesName":"rules","labelWidth":80,"labelPosition":"left","size":"","labelAlign":"label-left-align","cssCode":"","customClass":"","functions":"","layoutType":"PC","onFormCreated":"","onFormMounted":"","onFormDataChange":"","onFormValidate":""}},
formData: {},
optionData: {}
}
},
mounted() {
//从后端接口获取表单的选项数据(如表单中无选项类型字段、则跳过此步骤),并赋值给optionData变量,需自行实现!!
axios.get(SERVER_URL + '/get-option-data').then(res => {
if (res.code == 200) {
this.optionData = res.data
}
//从后端接口获取已保存的数据对象(如只显示空白表单、则跳过此步骤),并赋值给formData变量,需自行实现!!
axios.get(SERVER_URL + '/get-form-data').then(res => {
let newFormData = null
if (res.code == 200) {
newFormData = res.data
}
//从后端接口获取表单json对象,然后调用v-form-render对象的setFormJson(xxx)加载表单,需自行实现!!
axios.get(SERVER_URL + '/get-form-json').then(res => {
if (res.code == 200) {
this.$refs.vFormRef.setFormJson(res.data)
this.$nextTick(() => {
this.$refs.vFormRef.setFormData(newFormData)
})
}
}).catch(err => {
//
})
}).catch(err => {
//
})
}).catch(err => {
//
})
},
methods: {
submitForm() {
this.$refs.vFormRef.getFormData().then(formData => {
// Form Validation OK
alert( JSON.stringify(formData) )
}).catch(error => {
// Form Validation failed
this.$message.error(error)
})
}
}
}
</script>
<style scoped>
</style>
# 设计器集成后端字段接口
可以从后端获取到字段信息,用于表单数据绑定。
# 查询单表
可以查询指定单个表格的数据
选择单表数据,然后选择对应的数据库表
唯一名称 字段可以绑定前面所选数据库表的字段
# 查询主从表
表结构选择主从表单,主表只能选择一个,从表可以添加多个。 从表表名:所选择的数据库从表的表名 外键字段:所选择的数据库从表的主键 关联主键:主表的主键(无需手动选择)
在单行子表单组件中选择要绑定的数据库表
子表单内的表单项就可以选择所选表的字段
# 表单的状态控制
通常来讲,表单会有三种状态:新建、编辑、查看,大体对应CRUD中的三种业务逻辑,即Create、Update、Read。
表单在新建状态、编辑状态,数据绑定字段都是可操作、可录入的,新建和编辑的差别在于表单数据对象的id是否有值。通常新建表单的数据提交到后端,由后端负责生成id值,编辑时读取后端接口,返回的数据对象包含id值。
因此要区分表单的新建和编辑状态就变得很简单,只需要在表单中增加一个单行输入字段,并将字段唯一名称设置为后端对应的id属性名称,然后将该字段设置为隐藏,相当于在表单中增加了一个id隐藏字段,如下图所示:
# 新建状态表单操作
- 首先调用后端新建接口(如果有的话,没有则不调用),获取到表单数据对象formData;
- 调用setFormData(formData)方法,完成新建状态的表单数据展示;
- 点击表单提交按钮,调用getFormData()方法,利用axios跟后端接口交互,将表单数据保存到数据库。
# 编辑状态表单操作
操作基本同上,区别在于从后端获取到的formData对象包含id值。
- 首先调用后端数据读取接口,获取到表单数据对象formData;
- 调用setFormData(formData)方法,完成编辑状态的表单数据展示;
- 点击表单提交按钮,调用getFormData()方法,利用axios跟后端接口交互,将表单数据保存到数据库。
# 查看状态表单操作
- 首先调用后端数据读取接口,获取到表单数据对象formData;
- 调用setFormData(formData)方法,加载表单数据展示;
- 接着调用disableForm()方法,将表单设置为禁止编辑状态;
- 如需从查看状态切换到编辑状态,则调用方法enableForm()。
# 栅格布局与表格布局
# 栅格布局
通过标准的24分栏,可以简单快捷地创建表单布局,轻松实现单列、两列、三列、四列排版布局。
栅格分栏之间可以设置间隔,栅格宽度根据父容器宽度自适应。
栅格布局可以嵌入字段组件或其他容器组件。
栅格内嵌套另一个栅格,可以实现类似于表格的合并行效果。
# 表格布局
类似于Excel的表格,可以实现行、列合并,可以指定单元格的行高、列宽。
表格布局可以嵌入字段组件或其他容器组件。
# 栅格容器与多终端响应式布局
- 效果演示动图
- 设置方法
通过开启栅格容器中栅格列的“响应式布局”属性,可以实现多终端的响应式布局适配。
栅格容器整行宽度共24栅格,因此栅格宽度设置为8占据三分之一行宽,设置为12则占据一半行宽,设置为24占据整行宽度。
a. 适配PC终端(视口像素宽度 >= 992px):
b. 适配Pad终端(768px <= 视口像素宽度 < 992px):
c. 适配H5终端(视口像素宽度 < 768px):
- 可能存在的问题及解决方法
当栅格列设置为响应式布局后,栅格列会被追加float样式(float: left),如同一栅格布局内多个栅格列的默认高度不一致时,则会导致布局错位,如下图所示:
解决方法:
给栅格布局设置栅格列统一高度即可解决(单位为像素),如下图所示:
# 表单及组件属性设置
# 表单属性设置
包含表单基本属性、事件属性设置。
# 组件属性设置
组件拥有丰富的可设置属性,大部分情况下使用组件的默认属性就能很好地完成工作,一般只需要设置组件名称(确保名称唯一)、字段标签、数据校验规则,如果有交互逻辑则编写事件属性的处理代码。
组件基本属性:
注意:组件的同名属性优先级高于表单属性,比如字段标签对齐方式、组件大小、标签宽度。
组件高级属性,默认为收起状态:
组件事件属性:
# 数据校验
字段的数据校验支持三种校验方式:是否必填校验、正则表达式校验、自定义校验。
# 必填校验
方法一:通过表单设计器的字段属性设置面板,设置“必填字段”属性为选中状态;
方法二:可以调用组件的API方法setRequired(true/false),动态设置组件是否必填;
正则表达式校验
通过表单设计器的字段属性设置面板,设置“字段校验”属性,该属性内嵌了几种常见的正则校验(实现代码在src/utils/validators.js),该属性也可以输入自定义的正则表达式文本(输入后请按回车键确认);
设置“校验失败提示”属性,填入校验失败提醒内容。
# 自定义校验
选中字段后,在表单设计器的字段属性设置面板选择事件属性“onValidate”,编写校验代码。
如下示例代码,可校验字段值是否介于0~100,超出范围则校验失败:
if (value === '') { //空值不校验
callback()
return
}
if ((value > 100) || (value < 0)) {
callback(new Error('error message')) //校验失败
} else {
callback(); //校验通过
}
# 选择项设置与加载
此处的选择项是指radio单选项、checkbox多选项、select下拉选项以及cascader级联选择组件的可选择项目。
选择项支持3种设置方法:
# 5.1. 使用表单设计器管理选择项
表单设计阶段,可通过组件属性设置面板操作:
提示:
可以批量导入选择项,每一行以英文逗号分隔,如不包含英文逗号,则选择项的value和label同值:
# 使用API方法动态加载选择项
表单运行阶段,可调用组件自身的API方法loadOptions(options)动态加载选择项,选择项格式为:
[
{label: '选项1', value: '1'},
{label: '选项2', value: '2'},
{label: '选项3', value: '3'}
]
示例:
在表单的 onFormMounted 事件中通过axios调用后端接口,动态加载产品类型,代码如下图:
运行效果如下图:
表单JSON如下,复制JSON,在表单设计器中选择“导入JSON”即可看到效果:
{
"widgetList": [
{
"type": "select",
"icon": "select-field",
"formItemFlag": true,
"options": {
"name": "productType",
"label": "产品类型",
"labelAlign": "",
"defaultValue": "",
"placeholder": "",
"columnWidth": "200px",
"size": "",
"labelWidth": null,
"labelHidden": false,
"disabled": false,
"hidden": false,
"clearable": true,
"filterable": false,
"allowCreate": false,
"remote": false,
"automaticDropdown": false,
"multiple": false,
"multipleLimit": 0,
"optionItems": [],
"required": false,
"validation": "",
"validationHint": "",
"customClass": "",
"labelIconClass": null,
"labelIconPosition": "rear",
"labelTooltip": null,
"onCreated": "",
"onMounted": "",
"onRemoteQuery": "",
"onChange": "",
"onFocus": "",
"onBlur": "",
"onValidate": ""
},
"id": "select87612"
}
],
"formConfig": {
"labelWidth": 80,
"labelPosition": "left",
"size": "",
"labelAlign": "label-left-align",
"cssCode": "",
"customClass": "",
"functions": "",
"layoutType": "PC",
"onFormCreated": "",
"onFormMounted": " let productTypeR = this.getWidgetRef('productType')/n /n axios.get(/"https://www.fastmock.site/mock/2de212e0dc4b8e0885fea44ab9f2e1d0/vform/getOptions/").then(function(res) {/n //console.log(res.data)/n productTypeR.loadOptions(res.data)/n }).catch(function(error) {/n console.log(error)/n })",
"onFormDataChange": ""
}
}
# 使用表单option-data属性导入选择项
表单运行阶段,通过VFormRender的option-data属性一次性导入多个组件的选择项,属性值格式为:
{
'gender': [
{label: '男', value: '1'},
{label: '女', value: '2'}
],
'paymentType': [
{label: '支付宝', value: 'alipay'},
{label: '微信', value: 'wechat'}
]
}
需要注意的是,option-data属性加载的选择项不是响应式更新的,仅在表单初始化时被组件加载,因此当option-data属性指向的选择项数据有更新后,应该手动调用v-form-render组件的reloadOptionData方法刷新。
# 使用数据源加载选择项
VForm Pro提供了选项的数据源配置功能:参考第9点(数据源管理)
# 图片与文件上传
图片、文件上传一般由前端上传到后端服务器中
# 上传后端服务器
- 获取到后端服务器的上传地址,默认值:/api/FormFileUpload/UploadFile
- 后端接口在处理文件上传时,如果需要获取上传请求的cookie信息,则需要选中“发送cookie凭证”,默认选中
# 图片、文件上传成功后的表单数据处理
通常情况下,图片、文件上传到云平台或后端服务器后会被重新命名,前端的表单数据需要收集新的文件名以及图片预览URL或文件下载URL,这时候须通过组件的交互事件onUploadSuccess编写处理代码。
在onUploadSuccess事件中返回这样一个对象就可以:
return {
name: result[0].name,
url: result[0].url
}
默认上传成功回调:
# 编辑、查看状态的数据回显处理
跟所有字段类型组件一样,在表单编辑及查看状态下,图片、文件上传组件的回显数据也来自于表单数据对象formData,该数据对象通常由form-data属性传入,或者通过setFormData()方法赋值。
不太一样的是,图片、文件上传组件的回显数据要求如下格式,即包含name、url两个属性的对象数组:
[
{
name: '员工手册.docx',
url: 'https://files.mydomain.com/upload/202201/员工手册.docx'
},
{
name: '在路上.jpg',
url: 'https://photos.mydomain.com/upload/202202/在路上.jpg'
}
]
# 子表单
子表单是一种实现一对多关系中明细数据展示、编辑的容器组件。
子表单内只能放置字段组件,不能放置其他容器组件。
一个表单可以包含多个子表单组件。
子表单支持交互事件和API方法,可以实现复杂的交互逻辑。
通过交互事件和API方法,可实现子表单的自动更新小计和累计功能
# 交互事件
交互事件可在表单设计器中编写事件处理代码。
事件名称 | 说明 | 回调参数 |
---|---|---|
onSubFormRowAdd | 子表单新增行时触发 | (subFormData, newRowIndex): 新增后的子表单数据,新增行索引 |
onSubFormRowInsert | 子表单插入行时触发 | (subFormData, newRowIndex): 插入后的子表单数据,新增行索引 |
onSubFormRowDelete | 子表单删除行时触发 | (subFormData, deletedDataRow): 删除后的子表单数据,删除行数据 |
onSubFormRowChange | 子表单新增行、插入行、删除行时触发 | (subFormData): 变化后的子表单数据 |
# API方法
方法名 | 说明 | 参数 |
---|---|---|
getFormRef | 获取VFormRender组件 | — |
getWidgetRef | 获取容器或字段组件 | (widgetName, showError): 组件名称,如组件不存在是否显示错误 |
setHidden | 设置是否隐藏容器 | true/false |
disableSubFormRow | 禁用子表单指定行 | rowIndex:行索引 |
enableSubFormRow | 恢复子表单指定行 | rowIndex:行索引 |
disableSubForm | 禁用子表单 | — |
enableSubForm | 恢复子表单 | — |
resetSubForm | 重置子表单数据为空 | — |
getSubFormValues | 获取子表单数据 | — |
# 数据表格
数据表格data-table是对el-table组件和el-pagination组件的封装,实现了数据表格的可视化设计,主要用于大数据量的展示场景。
# 效果展示
- 表格可视化设计
- 表格列编辑,可拖拽调整列顺序
- 操作按钮设置
# 原生事件
原生事件可以在Vue模板中用@事件名称或JS代码中调用Vue实例的$on添加事件处理方法。
事件名称 | 说明 | 回调参数 |
---|---|---|
dataTablePageSizeChange | 分页大小改变时触发 | (dataTable, pageSize, currentPage): dataTable,数据表格组件实例 pageSize,分页大小 currentPage,当前页码 |
dataTablePageChange | 页码改变时触发 | (dataTable, pageSize, currentPage): dataTable,数据表格组件实例 pageSize,分页大小 currentPage,当前页码 |
dataTableSelectionChange | 选择记录改变时触发 | (dataTable, selection, selectedIndices): dataTable,数据表格组件实例 selection,选中记录 selectedIndices,选中行索引数组 |
operationButtonClick | 操作按钮点击时触发 | (dataTable, buttonName, rowIndex, row): dataTable,数据表格组件实例 buttonName,被点击按钮名称 rowIndex,被点击按钮所在行索引 row,被点击按钮所在行数据对象 |
# 交互事件
交互事件可在表单设计器中编写事件处理代码。
注意:对于同一事件名称,交互事件和原生事件不会同时触发,交互事件处理代码的执行优先级高于原生事件。
事件名称 | 说明 | 回调参数 |
---|---|---|
onPageSizeChange | 选择记录改变时触发 | (pageSize, currentPage): pageSize,分页大小 currentPage,当前页码 |
onCurrentPageChange | 页码改变时触发 | (pageSize, currentPage): pageSize,分页大小 currentPage,当前页码 |
onSelectionChange | 选择记录改变时触发 | (selection, selectedIndices): selection,选中记录 selectedIndices,选中行索引数组 |
onOperationButtonClick | 点击操作按钮触发 | (buttonName, rowIndex, row): buttonName,被点击按钮名称 rowIndex,被点击按钮所在行索引 row,被点击按钮所在行数据对象 |
onHideOperationButton | 设置操作按钮是否隐藏时回调 注意:该回调事件必须返回true或false,以控制按钮隐藏或显示。 | (buttonConfig, rowIndex, row): buttonConfig,按钮配置对象 rowIndex,按钮所在行索引 row,按钮所在行数据对象 |
onDisableOperationButton | 设置操作按钮是否禁用时回调 注意:该回调事件必须返回true或false,以控制按钮隐藏或显示。 | (buttonConfig, rowIndex, row): buttonConfig,按钮配置对象 rowIndex,按钮所在行索引 row,按钮所在行数据对象 |
onGetOperationButtonLabel | 设置操作按钮label时回调 注意:该回调事件必须返回字符串类型。 | (buttonConfig, rowIndex, row): buttonConfig,按钮配置对象 rowIndex,按钮所在行索引 row,按钮所在行数据对象 |
onHeaderClick | 列表头点击时触发 | (column, event): column,点击位置所在列 event,点击事件 |
onRowClick | 行点击时触发 | (row, column, event): row,点击位置所在行 column,点击位置所在列 event,点击事件 |
onRowDoubleClick | 行双击时触发 | (row, column, event): row,点击位置所在行 column,点击位置所在列 event,点击事件 |
onCellClick | 单元格点击时触发 | (row, column, cell, event): row,点击位置所在行 column,点击位置所在列 cell,点击位置所在单元格 event,点击事件 |
onCellDoubleClick | 单元格双击时触发 | (row, column, cell, event): row,点击位置所在行 column,点击位置所在列 cell,点击位置所在单元格 event,点击事件 |
onGetRowClassName | 获取行css样式名称时回调 注意:该回调事件必须返回字符串类型,即行css样式名称。 此外还需注意:如果开启了斑马线效果,则斑马线效果优先级更高。 | (row, rowIndex): row,行 rowIndex,行索引 |
onGetSpanMethod | 合并单元格时回调 注意:该回调事件必须返回数组或对象,详情请参见element-ui组件库el-table的具体使用文档: 点此打开 (opens new window) | (row, column, rowIndex, columnIndex): row,行 column,列 rowIndex,行索引 columnIndex,列索引 |
# API方法
方法名 | 说明 | 参数 |
---|---|---|
getFormRef | 获取VFormRender组件 | — |
getWidgetRef | 获取容器或字段组件 | (widgetName, showError): 组件名称,如组件不存在是否显示错误 |
setHidden | 设置是否隐藏容器 | true/false |
————————— | ————————— | ————————— |
setTableColumn | 设置表格列对象 | tableColumns, 数据格式参见widgetsConfig.js |
setTableData | 设置表格数据 | tableData, 数据格式参见widgetsConfig.js |
getTableData | 获取表格数据 | — |
setPagination | 设置分页对象 | pagination,数据格式为: { currentPage: 1, pageSize: 15, total: 188 } |
getSelectedRow | 获取选中行数据,返回对象数组 | — |
getSelectedIndex | 获取选中行索引,返回数组 | — |
setTableColumnsHidden | 设置列隐藏或显示 | (columnNames, hiddenFlag): columnNames,列名称,字符串或字符串数组 hiddenFlag,true隐藏,false显示 |
loadColumnsFromDS | 从数据源加载表格列 | (localDsv, dsName): localDsv,本地数据源变量,默认值为空对象{},本地数据源变量自动和全局数据源合并后传递给数据源 dsName,数据源名称,无默认值 |
loadDataFromDS | 从数据源加载表格数据 | (localDsv, dsName): localDsv,本地数据源变量,默认为空对象{},本地数据源变量自动和全局数据源合并后传递给数据源 dsName,数据源名称,默认值为空字符串,不传此参数则使用表格绑定的数据源 |
# 数据源
数据表格支持绑定数据源,绑定数据源后,表格数据首次加载、改变分页大小、翻页时会自动执行数据源请求,从后端获取表格数据。
需要注意的是,数据表格在执行数据源请求时,会自动给全局数据源变量DSV绑定pageSize、currentPage两个变量值,在设置数据源时可通过DSV['pageSize']、DSV['currentPage']获取变量值。
# 数据源管理
数据源是VForm对动态获取后端数据功能的一种封装,基于axios实现,使用非常简单方便,支持数据源变量(DSV),具备相当的灵活性
# 数据源属性说明
唯一名称:必填属性,是数据源的唯一标识,建议使用大小写字母、数字进行组合;
请求地址:即后端接口的URL,支持字符串常量或变量表达式;
变量表达式,是指可以用DSV对象属性(或VFR对象)和其他字符串进行拼接运算,比如:
DSV['API_SERVER'] + '/getCityList'
DSV对象,一般通过v-form-designer或v-form-render的global-dsv属性传值进来
VFR对象,即v-form-render组件实例,可以调用其API方法
描述信息:补充说明数据源的功能或使用要求,在选择数据源时作为参考;
请求方法:对应get、post、put、delete;
请求头(headers):对应axios的headers属性,支持变量表达式,可从DSV中取值;
参数(params):对应axios的params属性,支持变量表达式,可从DSV中取值;
发送数据(data):对应axios的data属性,支持变量表达式,可从DSV中取值;
请求配置:是一个回调函数,可以在发送请求之前再次修改config对象,参数说明如下:
config,是上述headers、params、data等属性的组合对象
isSandbox,是否测试数据源状态,测试数据源是为true,其他状态为false
DSV,数据源变量对象,通过v-form-designer或v-form-render的global-dsv属性传值进来
VFR,即v-form-render组件实例,可以调用其API方法
数据处理:是一个回调函数,可以对获取到的数据结果进行加工处理,参数说明如下:
result,是axios请求返回的result对象
isSandbox,是否测试数据源状态,测试数据源是为true,其他状态为false
DSV,数据源变量对象,通过v-form-designer或v-form-render的global-dsv属性传值进来
VFR,即v-form-render组件实例,可以调用其API方法
# 测试数据源
DSV数据源变量:默认显示是通过global-dsv属性传递的数据,在测试数据源时DSV是可以修改的。
值得注意的是,给DSV增加新的键值对,其属性名称必须以双引号包括(这可能是AceEditor编辑器的要求)。
此外还有一点需要提示,测试数据源时,isSandBox的值为true,这个参数会传递给上述的请求配置、数据处理和错误处理三个回调函数。
当数据源绑定到组件上时,组件请求数据源时,会自动给DSV对象增加一个键值对,格式如下:
widgetName: '组件唯一名称'
那么DSV.widgetName
在设置数据源时可作为变量表达式使用;很明显,在测试数据源的时候,DSV对象是没有widgetName
属性值的,这意味着如果你在数据源中使用了DSV.widgetName
变量表达式,那么测试数据源的时候需要你手工为DSV对象增加widgetName
键值对,如下所示:
{
/* 略过DSV已有数据... */
"widgetName": 'userNameInput'
}
# 如何传递请求token
通过v-form-designer或v-form-render的global-dsv属性传递,比如给globalDsv对象增加一个token:
{
"token": '8888aaaa000xxxxcccc6666'
}
那么在数据源设置时,就可以通过DSV.token
获取到该值。
注意:global-dsv属性是响应式更新的,这意味着globalDsv对象中的值可以动态修改,数据源在执行时总是使用globalDsv最新的数据。
# 为组件绑定数据源
目前支持数据源绑定的组件有:单选项radio、多选项checkbox、下列选项select、级联选择cascader以及数据表格data-table。
# 自定义CSS样式
自定义样式在表单运行期间生效,可以覆盖表单或组件原有样式。
自定义样式会被添加到页面的style节点,全局生效。
添加自定义样式时,应注意样式class名称不要与已有样式名称冲突。
# 重要提示
自定义CSS样式代码会被直接插入到文档对象document的style节点,不存在转译过程,因此不支持SCSS或LESS语法,请按浏览器标准编写CSS样式代码。
如需将SCSS转译为CSS代码,可使用这个在线转译网站:
https://www.sassmeister.com/
# 示例
下面通过添加自定义样式,将必填字段的label设置为粗体。
- 添加表单全局CSS代码,如下所示:
- 选中字段,并设置必填属性,然后在字段的高级属性“自定义CSS样式”选择“bold-label”:
- 预览后即可看到效果:
# 自定义函数
在组件的交互事件中,为了实现预期的效果,可能会多次编写大量重复代码,这时候可以将重复代码提取出来,变成一个独立的自定义函数,方便在交互事件中多次调用。
提示:
- 需要注意的是,自定义函数会被追加到window对象,变成全局函数,所以命名上应考虑不会与其他全局函数名称冲突;
- 自定义函数在表单运行期间执行,javascript代码不会经历Babel编译这一步,如果需要在IE 浏览器中正常运行,请按IE 浏览器的javascript兼容语法编写自定义函数。
# 调试交互事件JavaScript代码
交互事件的JS代码在表单运行期间执行,执行原理是:通过Function对象创建一个匿名函数,传入事件参数后执行。
如果交互事件中编写了较多代码,一旦代码没有实现预定逻辑,则需要对代码进行调试,这时候可以用debugger关键字:在需要插入断点的位置输入“debugger”,则浏览器执行到此处代码时会自动进入调试状态。
# 示例
演示效果:当单选项点击后,获取到当前点击值后,然后进入调试状态,查看当前点击值
在单选项组件的onChange事件处理代码合适的位置加入debugger关键字:
预览表单、并按F12键打开Chrome开发者工具,然后点击单选项组件,即刻进入了调试状态:
可以看到事件处理代码被封装在一个匿名函数中运行,函数中包含事件传入的各个参数,当前断点停留于debugger位置,可以查看变量值,或者继续往下调试执行代码。
# 前端代码生成
VForm 2支持三种前端代码生成:Vue组件、HTML源码、SFC组件,以下对三种生成的代码进行简单对比。
代码类型 | 依赖包 | 备注 | |
---|---|---|---|
Vue组件 | vue2、element-ui、variant-form | 适合Vue2项目 | |
HTML源码 | vue2、element-ui、variant-form | 适合历史遗留Web项目 | |
SFC组件 | Vue2版 | vue2、element-ui | 适合Vue2项目 |
Vue3版 | vue3、element-plus | 适合Vue3项目 |
根据上表对比可以发现,生成的SFC组件代码可以脱离VForm运行,因为SFC组件相当于把JSON表单转化为手写的vue2 + element-ui代码或vue3+ element-plus代码。
SFC组件和Vue组件(含HTML源码)的区别在于,SFC组件失去了动态表单的所有功能,包括交互逻辑控制、API方法调用、事件响应等等,无法满足在运行时变更表单的需求。
# VS Code插件
vform-maker是Variant Form的VS Code插件实现。vform-maker提供可视化的表单设计功能,支持一键导出为Vue2或Vue3代码,为前端开发提效,轻松享受更多摸鱼时间。
安装
在VS Code扩展搜索vform-maker
或vformAdmin
即可找到该插件,秒速安装!
# 计算公式
VForm 3 Pro 3.3.0版本新增了计数器字段的计算公式设计,可以用可视化方式实现常见的数值逻辑计算。
使用计算公式,需要注意以下几点(为方便叙述,以下将开启了计算公式的计数器组件称为计算字段):
1.计算字段的公式表达式,可以使用其他字段、运算符、数学函数,也可以使用其他计算字段:
如在公式表达式使用中其他计算字段,应保证字段的先后顺序,其他计算字段应在当前计算字段之前已被计算出结果。
特别提示:在计算公式中包含函数时,如果函数入参是子表单字段,仅支持单个子表单字段作为函数入参,而不支持多个子表单字段组合在一起的表达式作为函数入参。
举例说明:
比如“总价”是主表单字段,“单价”、“数量”均为子表单字段,“总价”字段的计算公式不能设置为:SUM([单价] * [数量])。
解决方法:
在子表单中再增加“小计”字段,设置其计算公式为:[单价] * [数量],然后设置“总价”字段的计算公式为:SUM([小计])。
2.计算字段的公式表达式使用其他字段时,存在4种字段关联关系:
- a.当前字段和其他字段同属于主表单字段
字段关系属于平行关系,公式表达式中不可以使用聚合类数学函数,比如求和、取平均值等等。
- b.当前字段和其他字段同属于同一子表单的字段
同上,字段关系属于平行关系,公式表达式中不可以使用聚合类数学函数,比如求和、取平均值等等。
注意:当前字段的公式表达式不能使用不属于同一子表单的其他子表单字段,但可以使用主表单字段。
- c.当前字段是主表单字段,其他字段属于子表单字段
字段关系属于主从关系,公式表达式中子表单字段必须以聚合类函数包裹,比如求和、取平均值等等
- d.当前字段是子表单字段,其他字段为主表单字段
字段关系属于从主关系,公式表达式中不可以使用聚合类数学函数,比如求和、取平均值等等。
- 计算字段公式表达式的完备性和准确性,由开发者保证,开发者应在设计表单阶段完成计算公式的测试,检查是否符合预期效果。计算公式一旦出现错误,可能会影响到整个表单的正常渲染。
# 二次开发指南
# 二次开发说明
# VForm源码目录结构说明
# 二次开发文档目录结构
1. 容器组件开发
1.1 定义容器JSON Schema
1.2 开发设计状态的容器组件
1.2.1 容器组件SFC文件编写
1.2.2 插入Draggable组件
1.2.3 接收组件拖放事件
1.2.4 创建并注册组件属性设计器
1.3 开发运行状态的容器组件
1.3.1 容器组件SFC文件编写
1.3.2 增加交互事件和API方法
2. 字段组件开发
2.1 定义字段JSON Schema
2.2 字段组件SFC文件编写
2.3 创建并注册组件属性设计器
2.4 增加交互事件和API方法
3. 代码自动生成
# VForm组件⼆次开发指南
VForm的组件库分为两类,第⼀类是容器组件,主要负责表单元素的布局排列,第⼆类为字段组件,主
要负责数据收集或内容展示。容器组件可以嵌套其他容器或字段组件,字段组件可以放置于容器之内或
者表单上(即整个表单也相当于⼀个⼤容器)。
# 容器组件开发
⾸先拉取VForm源码:
VS Code导⼊源码项⽬,并执⾏ npm install 安装依赖包,安装依赖完成之后执⾏ npm run serve
进⼊开发模式;
- 打开 src/extension/sample/extension-schema.js,可看到card容器定义的JSON Schema如下:
export const cardSchema = {
type: 'card',
category: 'container',
icon: 'card',
widgetList: [],
options: {
name: '',
label: 'card',
hidden: false,
cardWidth: '100%', //卡⽚宽度,百分⽐或像素宽度
shadow: 'never', //是否显示阴影
customClass: '', //⾃定义css样式
}
}
JSON Schema解释说明:
type:容器组件的类型名称,必须唯⼀,不能跟已有组件重复;
icon:容器图标名称,可以去iconfont.cn下载所需的svg⽂件,放⼊src/icons/svg⽬录即可(⾃动加
载);
category:容器组件必须设置为'container';
widgetList:容器内存放的组件列表,初始值必须为[];
options:组件属性对象,每⼀个属性值对应⼀个属性编辑器。
- 编写card容器的SFC组件⽂件,容器组件对应两个状态:设计期状态可以接收组件拖拽,运⾏期状
态负责组件渲染,所以需要编写两个SFC⽂件,命名规则需严格遵守:容器名称-widget(设计
期)、容器名称-item(运⾏期);
card-widget.vue代码如下:
<template>
<container-wrapper :designer="designer" :widget="widget" :parent-widget="parentWidget" :parent-list="parentList"
:index-of-parent-list="indexOfParentList">
<el-card :key="widget.id" class="card-container" @click.stop="selectWidget(widget)"
:shadow="widget.options.shadow" :style="{width: widget.options.cardWidth + '!important' || ''}"
:class="[selected ? 'selected' : '', !!widget.options.folded ? 'folded' : '', customClass]">
<template #header>
<div class="clear-fix">
<span>{{widget.options.label}}</span>
<i v-if="widget.options.showFold" class="float-right"
:class="[!widget.options.folded ? 'el-icon-arrow-down' : 'el-icon-arrow-up']"
@click="toggleCard"></i>
</div>
</template>
<draggable :list="widget.widgetList" item-key="id" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler" tag="transition-group" :component-data="{name: 'fade'}"
@add="(evt) => onContainerDragAdd(evt, widget.widgetList)"
@update="onContainerDragUpdate" :move="checkContainerMove">
<template #item="{ element: subWidget, index: swIdx }">
<div class="form-widget-list">
<template v-if="'container' === subWidget.category">
<component :is="subWidget.type + '-widget'" :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></component>
</template>
<template v-else>
<component :is="subWidget.type + '-widget'" :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></component>
</template>
</div>
</template>
</draggable>
</el-card>
</container-wrapper>
</template>
封装了Element UI的el-card组件,重点看4~14⾏代码,如何跟JSON Schema的属性进⾏结合,其他部分代码复制粘贴即可。
card-item.vue代码如下:
<template>
<container-item-wrapper :widget="widget">
<el-card :key="widget.id" class="card-container" :class="[!!widget.options.folded ? 'folded' : '', customClass]"
:shadow="widget.options.shadow" :style="{width: widget.options.cardWidth + '!important' || ''}"
:ref="widget.id" v-show="!widget.options.hidden">
<template #header>
<div class="clear-fix">
<span>{{widget.options.label}}</span>
<i v-if="widget.options.showFold" class="float-right"
:class="[!widget.options.folded ? 'el-icon-arrow-down' : 'el-icon-arrow-up']" @click="toggleCard"></i>
</div>
</template>
<template v-if="!!widget.widgetList && (widget.widgetList.length > 0)">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<component :is="subWidget.type + '-item'" :widget="subWidget" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget">
<!-- 递归传递插槽!!! -->
<template v-for="slot in Object.keys($slots)" v-slot:[slot]="scope">
<slot :name="slot" v-bind="scope"/>
</template>
</component>
</template>
<template v-else>
<component :is="subWidget.type + '-widget'" :field="subWidget" :designer="null" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget">
<!-- 递归传递插槽!!! -->
<template v-for="slot in Object.keys($slots)" v-slot:[slot]="scope">
<slot :name="slot" v-bind="scope"/>
</template>
</component>
</template>
</template>
</template>
</el-card>
</container-item-wrapper>
</template>
重点看3-12⾏代码,其他部分代码复制粘贴即可。
- 加载options对应的属性编辑器,对应表单设计器右侧的SettingPanel⾯板,具体代码参⻅
extension-loader.js:
PERegister.registerCPEditor(app, 'card-folded', 'card-folded-editor',
PEFactory.createBooleanEditor('folded', 'extension.setting.cardFolded'))
PERegister.registerCPEditor(app, 'card-showFold', 'card-showFold-editor',
PEFactory.createBooleanEditor('showFold', 'extension.setting.cardShowFold'))
PERegister.registerCPEditor(app, 'card-cardWidth', 'card-cardWidth-editor',
PEFactory.createInputTextEditor('cardWidth', 'extension.setting.cardWidth'))
let shadowOptions = [
{label: 'never', value: 'never'},
{label: 'hover', value: 'hover'},
{label: 'always', value: 'always'},
]
PERegister.registerCPEditor(app, 'card-shadow', 'card-shadow-editor',
PEFactory.createSelectEditor('shadow', 'extension.setting.cardShadow',
{optionItems: shadowOptions}))
代码解释:此处只加载了cardWidth、shadow两个属性的编辑器组件,因为其他属性编辑器在VForm框
架内已经预先加载了。以上使⽤到的字符串资源分别位于src/lang/zh-CN_extension.js、
src/lang/en-US_extension.js两个⽂件中。
- 容器组件开发完毕,扩展组件的加载由extension-loader.js的loadExtension()⽅法执⾏,该⽅法在
main.js中被调⽤。
# 字段组件开发
本节通过开发一个简单的alert组件讲解如何实现字段组件、静态内容组件的扩展开发。
步骤1、2同上。
- 打开 src/extension/sample/extension-schema.js,可看到alert组件定义的JSON Schema如下:
export const alertSchema = {
type: 'alert',
icon: 'alert',
formItemFlag: false, //是否需嵌套于el-form-item
options: {
name: '',
title: 'Good things are coming...',
type: 'info',
description: '',
closable: true,
closeText: '',
center: true,
showIcon: false,
effect: 'light',
hidden: false,
onClose: '',
customClass: '',
}
}
JSON Schema解释说明:
type:字段组件的类型名称,必须唯一,不能跟已有组件重复;
icon:容器图标名称,可以去iconfont.cn下载所需的svg文件,放入src/icons/svg目录即可(自动加载);
formItemFlag:是否嵌套于el-form-item组件内,因el-alert并不需要显示字段标签,故此处设置为false;
options:组件属性对象,每一个属性值对应一个属性编辑器。
- 编写字段组件的SFC文件,字段组件在设计期和运行期共用,故只需要编写一个SFC文件,命名规则需严格遵守:组件名称-widget。
alert-widget.vue代码如下:
<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-alert ref="fieldEditor" :title="field.options.title" :type="field.options.type"
:description="field.options.description" :closable="field.options.closable"
:center="field.options.center" :close-text="field.options.closeText"
:show-icon="field.options.showIcon" :effect="field.options.effect" @close="handelCloseCustomEvent"></el-alert>
</static-content-wrapper>
</template>
<script>
//...
methods: {
handelCloseCustomEvent() {
if (!!this.field.options.onClose) {
let closeFn = new Function(this.field.options.onClose)
closeFn.call(this)
}
}
}
//...
</script>
因为formItemFlag设置为false,故此处必须使用static-content-wrapper包裹组件(如formItemFlag设置为true,则应使用form-item-wrapper包裹组件),5~8行代码对应options属性值的动态绑定,handelCloseCustomEvent方法处理了自定义事件onClose交互逻辑。
5.加载options对应的属性编辑器,对应表单设计器右侧的SettingPanel面板,具体代码参见extension-loader.js:
PERegister.registerCPEditor(app, 'alert-title', 'alert-title-editor',
PEFactory.createInputTextEditor('title', 'extension.setting.alertTitle'))
let typeOptions = [
{label: 'success', value: 'success'},
{label: 'warning', value: 'warning'},
{label: 'info', value: 'info'},
{label: 'error', value: 'error'},
]
PERegister.registerCPEditor(app, 'alert-type', 'alert-type-editor',
PEFactory.createSelectEditor('type', 'extension.setting.alertType',
{optionItems: typeOptions}))
PERegister.registerCPEditor(app, 'alert-description', 'alert-description-editor',
PEFactory.createInputTextEditor('description', 'extension.setting.description'))
PERegister.registerCPEditor(app, 'alert-closable', 'alert-closable-editor',
PEFactory.createBooleanEditor('closable', 'extension.setting.closable'))
PERegister.registerCPEditor(app, 'alert-closeText', 'alert-closeText-editor',
PEFactory.createInputTextEditor('closeText', 'extension.setting.closeText'))
PERegister.registerCPEditor(app, 'alert-center', 'alert-center-editor',
PEFactory.createBooleanEditor('center', 'extension.setting.center'))
PERegister.registerCPEditor(app, 'alert-showIcon', 'alert-showIcon-editor',
PEFactory.createBooleanEditor('showIcon', 'extension.setting.showIcon'))
let effectOptions = [
{label: 'light', value: 'light'},
{label: 'dark', value: 'dark'},
]
PERegister.registerCPEditor(app, 'alert-effect', 'alert-effect-editor',
PEFactory.createRadioButtonGroupEditor('effect', 'extension.setting.effect',
{optionItems: effectOptions}))
PERegister.registerEPEditor(app, 'alert-onClose', 'alert-onClose-editor',
PEFactory.createEventHandlerEditor('onClose', []))
上述代码了共加载了9个属性编辑器,其中包含onClose事件编辑器,其他属性使用预先加载的默认属性编辑器。以上使用到的字符串资源分别位于src/lang/zh-CN_extension.js、src/lang/en-US_extension.js两个文件中。
- 字段组件开发完毕,扩展组件的加载由extension-loader.js的loadExtension()方法执行,该方法在main.js中被调用。
# SFC代码生成
如果你最终使用VFormRender渲染表单JSON,则此部分内容完全可以跳过,无须实现该功能。
- 编写容器组件SFC生成器方法
实现思路很简单,根据容器的JSON配置生成对应的Element UI组件模板代码,打开src/extension/samples/extension-sfc-generator.js,可看到card容器的SFC生成器方法如下:
export const cardTemplateGenerator = function (cw, formConfig) {
const wop = cw.options
const headerAttr = `header="${wop.label}"`
const classAttr = buildClassAttr(cw)
const styleAttr = !!wop.cardWidth ? `style="{width: ${wop.cardWidth} !important}"` : ''
const shadowAttr = `shadow="${wop.shadow}"`
const vShowAttr = !!wop.hidden ? `v-show="false"` : ''
const cardTemplate =
`<div class="card-container">
<el-card ${headerAttr} ${classAttr} ${styleAttr} ${shadowAttr} ${vShowAttr}>
${
cw.widgetList.map(wItem => {
if (wItem.category === 'container') {
return buildContainerWidget(wItem, formConfig)
} else {
return buildFieldWidget(wItem, formConfig)
}
}).join('')
}
</el-card>
</div>`
return cardTemplate
}
SFC代码生成大量使用了ES6的模板字符串功能,极大地简化了代码生成逻辑。
- 编写字段组件SFC生成器方法
打开src/extension/samples/extension-sfc-generator.js,可看到alert组件的SFC生成器方法如下:
export const alertTemplateGenerator = function(fw, formConfig) {
const wop = fw.options
const titleAttr = `title="${wop.title}"`
const typeAttr = `type=${wop.type}`
const descriptionAttr = !!wop.description ? `description="${wop.description}"` : ''
const closableAttr = `:closable="${wop.closable}"`
const closeTextAttr = !!wop.closeText ? `close-text="${wop.closeText}"` : ''
const centerAttr = `:center="${wop.center}"`
const showIconAttr = `:show-icon="${wop.showIcon}"`
const effectAttr = `effect="${wop.effect}"`
const alertTemplate =
`<el-alert ${titleAttr} ${typeAttr} ${descriptionAttr} ${closableAttr} ${closeTextAttr} ${centerAttr}
${showIconAttr} ${effectAttr}>
</el-alert>`
return alertTemplate
}
实现思路同上,根据组件的JSON配置生成相应的Element UI组件模板代码。
- 注册上述两种代码生成器
在extension-loader.js中注册SFC代码生成器,每个代码生成器都需注册。
// ...
registerCWGenerator('card', cardTemplateGenerator) //注册card容器组件的代码生成器
// ...
// ...
registerFWGenerator('alert', alertTemplateGenerator) //注册alert组件的代码生成器
// ...