1. 源码下载地址
1. 关于文件,图片等组件上传的设置
在使用文件和图片上传组件时需对组件的内置函数(onBeforeUpload,onUploadSuccess)在渲染阶段render设置完表单Json后进行处理。
其中所作的操作包括有:设置请求权限,设置请求地址,重写图片文件地址。
注意: 由于内置函数运行环境是在全局环境下,在与我们的页面做交互的时候异常麻烦,通常使用localStorage来操作数据,其中包括对Token和baseUrl的存取。
ref.value.setFormJson(data)
let formCom = ref.value.getFieldWidgets()
let fileArray = formCom.filter(item => item.type == 'picture-upload' || item.type == 'file-upload' )
const token:any = getToken()
localStorage.setItem('uploadAuth', token)
localStorage.setItem('baseUrl', baseUrl)
for (let index = 0; index < fileArray.length; index++) {
const element = fileArray[index];
element.field.options.onBeforeUpload =
`var currentWidget = this;
currentWidget.setUploadHeader("Authorization", window.localStorage.getItem('uploadAuth'))`
element.field.options.uploadURL = apiUrl + '/flow/form/doc/upload'
localStorage.removeItem('fileMap')
element.field.options.onUploadSuccess =
`
var currentWidget = this
var fileMap = localStorage.getItem('fileMap') ? JSON.parse(localStorage.getItem('fileMap')) : []
file.url = window.localStorage.getItem('baseUrl') + result.data.filePath
fileMap.push({ id: file.uid, url: file.url })
localStorage.setItem('fileMap', JSON.stringify(fileMap))
currentWidget.fieldModel.forEach((item, index) => {
fileMap.forEach(element => {
if (element.id == item.uid) {
item.url = element.url
}
});
});
`
2.改造下拉选项框和级联选择
常规情况下,我们通常需要用到的选项都是从后端服务器请求回来的,vform暂时并没有为我们提供填写Api入口和其他参数信息的功能,于是我们可以对其源码进行扩展。
重点:对组件变量属性的扩展以及对组件UI属性填写的扩展,最后在业务界面中的渲染阶段与文件等组件一起统一做一次初始化。
options: {
name: '',
label: '',
defaultValue: '',
placeholder: '',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
clearable: true,
filterable: false,
requestApi: '', // 新增属性,请求地址
optionItems: [
{label: 'select 1', value: 1, children: [{label: 'child 1', value: 11}]},
{label: 'select 2', value: 2},
{label: 'select 3', value: 3},
],
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
<div class="el-form-item el-form-item--small" style="margin: 10px 0;">
<label class="el-form-item__label" style="width: 120px;">请求数据地址</label>
<div class="el-form-item__content">
<div class="el-input el-input--small">
<input class="el-input__inner" type="text" v-model="optionModel.requestApi" autocomplete="off">
</div>
</div>
</div>
for (let index = 0; index < selectArray.length; index++) {
const element = selectArray[index];
if (element.field.options.requestApi) {
let params = element.field.options.requestJSON ? JSON.parse(element.field.options.requestJSON) : {}
if (element.field.options.requestType == 'post' || !element.field.options.requestType) {
defHttp.post({ url: element.field.options.requestApi, params })
.then(res => {
if (element.field.options.mapValue) {
let map = JSON.parse(element.field.options.mapValue)
res.forEach(item => {
item.label = item[map.label]
item.value = item[map.value]
});
}
element.field.options.optionItems = res
})
} else {
defHttp.get({ url: element.field.options.requestApi, params })
.then(res => {
if (element.field.options.mapValue) {
let map = JSON.parse(element.field.options.mapValue)
res.forEach(item => {
item.label = item[map.label]
item.value = item[map.value]
});
}
element.field.options.optionItems = res
})
}
}
}
三、二次开发组件
扩展schema文件,如定义一个alert的schema( src/extension/sample/extension-schema.js)
JSON Schema解释说明:
type:字段组件的类型名称,必须唯一,不能跟已有组件重复;
icon:容器图标名称,可以去iconfont.cn下载所需的svg文件,放入src/icons/svg目录即可(自动加载);
formItemFlag:是否嵌套于el-form-item组件内,因el-alert并不需要显示字段标签,故此处设置为false;
options:组件属性对象,每一个属性值对应一个属性编辑器。
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: '',
}
}
编写字段组件的SFC文件,字段组件在设计期和运行期共用,故只需要编写一个SFC文件,命名规则需严格遵守:组件名称-widget。如定义一个alert组件的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>```
加载组件options的属性编辑器。
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', []))
每次对源码进行修改后都需要重新打包为lib再引入项目。
修改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')
如果使用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/
}
},
})
可能存在的问题及解决方法
如果使用Vite作为Vue项目的构建工具,Vite会将预构建的依赖缓存到 node_modules/.vite 目录下,如果发现编译打包的VForm3组件复制到 lib/vform 之后没有自动更新,请手工删除新项目里的 node_modules/.vite 目录。
插件往往只为我们提供一种通用的解决方案,但是正常情况下与我们的需求不一定是切合的,所以要善于观察代码,学习别人的设计逻辑,解剖插件源码的结构,寻找出自己需要的东西并将其进行扩展。