关于扩展自定义表单Vform3

一、认识Vform3源码

1. 源码下载地址

  • Gitee仓库
  • Github仓库

2. 源码结构
关于扩展自定义表单Vform3_第1张图片

二、使用Vform3的技巧

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属性填写的扩展,最后在业务界面中的渲染阶段与文件等组件一起统一做一次初始化。

  1. 改造widgetsConfig.js文件中对应组件的属性,比如新增一个请求地址的属性。
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: '',
    },
  1. 对组件的自定义界面(option-items-setting)进行改造,新增input输入框,开放Api入口,如新增以下代码
<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>
  1. 在渲染阶段发起请求,为每个Api属性不为空的组件进行数据渲染。
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
                })
            }
          }
        }

三、二次开发组件

  1. 扩展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: '',
      }
    }
    
  2. 编写字段组件的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>```
    
    
  3. 加载组件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再引入项目。

  1. 运行npm run lib后,生成以下文件
    关于扩展自定义表单Vform3_第2张图片

  2. 更正style.css文件名称为designer.css后在正在开发的项目中根目录建lib文件夹
    关于扩展自定义表单Vform3_第3张图片

  3. 修改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')
    
  4. 如果使用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 目录。

四、结语

插件往往只为我们提供一种通用的解决方案,但是正常情况下与我们的需求不一定是切合的,所以要善于观察代码,学习别人的设计逻辑,解剖插件源码的结构,寻找出自己需要的东西并将其进行扩展。

你可能感兴趣的:(Vue,JavaScript,javascript,vue.js,js,vue)