深入浅出form-generator扩展组件

在第一篇文章中提到过表单的定制性很强,需要能扩展自己的组件form-generator作者提供了二次扩展的文章,文章很全面;本文的目的快速理解form-generator组件的扩展方式.可以先看本文,如果解决不了就看下作者的文档.
本文字数较多,可以先快速预览再细看

  • form-generator官方教程
  • 表格组件扩展
  • 将表格组件引入自己的项目

本地预览

  • 拉取仓库 https://gitee.com/starlight_day/form-generator.git
  • npm i
  • npm run dev
    ps:如果node版本太高需要在运行前运行 $env:NODE_OPTIONS=“–openssl-legacy-provider”
  • http://127.0.0.1:8080/#/qjt 请假条例子
  • http://127.0.0.1:8080/#/cat 动态数据例子

数据结构

扩展组件前必须了解组件内部的数据是如何流动的,vue程序第一步都是数据优先

每个组件都对应一个config配置项,以单行文本框为例

{
    // 1. 组件配置信息
    __config__: {
      label: '单行文本',
      labelWidth: null,
      showLabel: true,
      changeTag: true,
      tag: 'el-input',
      tagIcon: 'input',
      defaultValue: undefined,
      required: true,
      layout: 'colFormItem',
      span: 24,
      document: 'https://element.eleme.cn/#/zh-CN/component/input',
      // 正则校验规则
      regList: []
    },
    // 2. 组件的插槽属性
    __slot__: {
      prepend: '',
      append: ''
    },
    // 3. 直接赋值给组件的属性
    placeholder: '请输入',
    style: { width: '100%' },
    clearable: true,
    'prefix-icon': '',
    'suffix-icon': '',
    maxlength: null,
    'show-word-limit': false,
    readonly: false,
    disabled: false
  },

每个表单配置项有三个部分

  1. 组件配置信息
  2. 组件的插槽属性( 没使用这里不讨论 )
  3. 直接赋值给组件的属性

1和3的区别在于3上面的属性会赋值上而1上的属性不会

让我们再看下生成后的表单项(不用细看)

{
  "fields": [{
    "__config__": {
      "label": "单行文本",
      "labelWidth": null,
      "showLabel": true,
      "changeTag": true,
      "tag": "el-input",
      "tagIcon": "input",
      "defaultValue": "你好",
      "required": true,
      "layout": "colFormItem",
      "span": 24,
      "document": "https://element.eleme.cn/#/zh-CN/component/input",
      "regList": [],
      "formId": 101,
      "renderKey": "1011693530948107"
    },
    "__slot__": {
      "prepend": "",
      "append": ""
    },
    "placeholder": "请输入单行文本",
    "style": {
      "width": "100%"
    },
    "clearable": true,
    "prefix-icon": "",
    "suffix-icon": "",
    "maxlength": null,
    "show-word-limit": false,
    "readonly": false,
    "disabled": false,
    "__vModel__": "field101"
  }],
  "formRef": "elForm",
  "formModel": "formData",
  "size": "medium",
  "labelPosition": "right",
  "labelWidth": 100,
  "formRules": "rules",
  "gutter": 15,
  "disabled": false,
  "span": 24,
  "formBtns": true
}

请注意这几个属性

{
  "fields": [{
    "__config__": {
      // 双向绑定的值
      "defaultValue": "你好",
	  // 绑定到组件上的key
      "renderKey": "1011693530948107"
    },
    // 字段名
    "__vModel__": "field101"
  }]
}

数据流向

通过上面一进一出我们知道了,form-generator在中间做的是

  1. 批量产生配置项
  2. 修改配置项
    现在让我们看下form-generator是如何处理配置项数据的,从右向左看。看不清请放大
    深入浅出form-generator扩展组件_第1张图片

从上图我们知道,
首先通过点击或者拖拽的方式将config.js中的配置项转化成了唯一的表单配置项,实现了批量生产。
在修改配置项时通过两个不同的表单,渲染表单用来展示组件和修改值,编辑表单用来修改属性

RightPanel.vue

这个组件是用来操配置项的属性的

  • activeData 标识当前选择的 配置项
  • 可以通过v-model绑定例如
<template v-if="['EditTable'].includes(activeData.__config__.tag)">
  <el-divider>表格属性el-divider>
  <el-form-item label-width="100px" label="表格尺寸">
    <el-radio-group v-model="activeData.size" size="mini">
      <el-radio-button label="medium">
        默认
      el-radio-button>
      <el-radio-button label="small">
        小号
      el-radio-button>
      <el-radio-button label="mini">
        迷你
      el-radio-button>
    el-radio-group>
  el-form-item>
  <el-form-item label-width="100px" label="纵向边框">
    <el-switch
      v-model="activeData.border" size="small"
    />
  el-form-item>
template>

render.js

这个组件是用来显示组件操作值的

export default {
  props: {
    conf: {
      type: Object,
      required: true
    }
  },
  components: {
    EditTable
  },
  mounted() {
    // 动态请求数据
    catchData.call(this, this.conf)
  },
  render(h) {
    const dataObject = makeDataObject()
    const confClone = deepClone(this.conf)
    const children = this.$slots.default || []

    // 如果slots文件夹存在与当前tag同名的文件,则执行文件中的代码
    mountSlotFiles.call(this, h, confClone, children)

    // 将字符串类型的事件,发送为消息
    emitEvents.call(this, confClone)

    // 将json表单配置转化为vue render可以识别的 “数据对象(dataObject)”
    buildDataObject.call(this, confClone, dataObject)

    return h(this.conf.__config__.tag, dataObject, children)
  }
}

我们可以看到render.js是一个vue组件,不过不是vue文件而是通过render函数和h函数来返回虚拟DOM
h函数的具体可以看渲染函数,简单理解就是h( 标签名,标签属性,子元素 )
使用h函数根据__config__.tag返回特定的组件
标签属性就是绑定了诸如 style、attribute、on、slot等信息的对象。这里我们主要注意on上面会绑定一个input事件我们就是通过它来更新数据的。

数据流向总结

  1. 通过config.js设置配置信息
  2. 通过defaultValue和@input进行绑定值
  3. 通过RightPanel操作值
    通过理解数据流向我们就知道我们怎样扩展自己的组件了。下面通过一个案例来感受一下

案例 – 照片墙

假如我需要一个这样的照片墙组件

深入浅出form-generator扩展组件_第2张图片

1. 先写一个组件

该组件和正常组件一样,只不过通过value和input更新数据

代码

<template>
  <div class="pw-list">
    <div v-for="(item, index) in imageList" :key="index" class="pw-item" :style="{width:width+'px',height:height+'px'}">
      <el-image
        :src="item"
        fit="cover"
        :style="{width:width+'px',height:height+'px'}"
        :preview-src-list="imageList"
      />
      <div class="mask">
        <i class="el-icon-search" @click="(e)=>{handlePreview(index,e)}" />
        <i class="el-icon-delete" @click="handleDelete(index)" />
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: [Array, String],
      default: () => []
    },
    width: {
      type: [Number, String],
      default: 300
    },
    height: {
      type: [Number, String],
      default: 168
    }
  },
  emits: ['input'],
  data() {
    return {
    }
  },
  computed: {
    imageList() {
      if (this.value instanceof Array) {
        return JSON.parse(JSON.stringify(this.value))
      }
      return this.value.split(',')
    }
  },
  created() {

  },
  methods: {
    // 点击预览--该功能依赖于preview-src-list
    handlePreview(index, e) {
      // 获取父节点
      const { parentNode } = e.target.parentNode
      // 获取图片
      const imgNode = parentNode.querySelector('img')
      // 触发图片点击事件
      imgNode.dispatchEvent(new Event('click'))
    },
    // 点击删除
    handleDelete(index) {
      this.imageList.splice(index, 1)
      if (this.value instanceof Array) {
        this.$emit('input', JSON.parse(JSON.stringify(this.imageList)))
      } else {
        this.$emit('input', this.imageList.join(','))
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.pw-list {
  display: flex;
  gap: 5px;
  padding: 5px 5px;
  flex-wrap: wrap;
  border: 1px solid #eee;
}
.pw-item {
  position:relative;
  border-radius: 8px;
  overflow:hidden;
  .mask{
    display:none;
    position:absolute;
    top:0;left:0;
    width:100%;
    height:100%;
    background:rgba(0,0,0,.3);
    color:white;
    font-size:20px;
    z-index:10;
  }
  &:hover .mask{
    display:flex;
    align-items: center;
    justify-content: center;
    gap: 15px;
  }
  i{
    cursor:pointer;
  }
}
</style>

正常调用效果
深入浅出form-generator扩展组件_第3张图片

2. 在config.js中写配置信息

  {
    __config__: {
      // 标签名
      label: '照片墙',
      // 标签
      tag: 'PhotoWall',
      // icon
      tagIcon: 'component',
      // 默认数据
      defaultValue: [
        'https://cdn.pixabay.com/photo/2018/01/14/23/12/nature-3082832_640.jpg',
        'https://cdn.pixabay.com/photo/2018/08/21/23/29/forest-3622519_640.jpg',
        'https://cdn.pixabay.com/photo/2013/10/02/23/03/mountains-190055_640.jpg',
        'https://cdn.pixabay.com/photo/2017/12/15/13/51/polynesia-3021072_640.jpg'
      ],
      // 布局方式
      layout: 'colFormItem',
      // 布局宽度
      span: 24,
      // 是否显示label
      showLabel: true,
      // label宽度
      labelWidth: null
    },
    // 图片宽度
    width: 300,
    // 图片高度
    height: 168
  }

3. 在render.js中注册一下组件

如果组件非全局组件必须注册,否则无法正常渲染

import PhotoWall from '@/components/customer/PhotoWall.vue'
components: {
  PhotoWall
},

效果

添加修改图片尺寸功能

这里需要修改RightPanel.vue文件了

<template v-if="['PhotoWall'].includes(activeData.__config__.tag)">
  <el-form-item label="图片宽度">
    <el-input v-model="activeData.width" type="number" placeholder="请输入图片宽度" size="mini" />
  el-form-item>
  <el-form-item label="图片高度">
    <el-input v-model="activeData.height" type="number" placeholder="请输入图片高度" size="mini" />
  el-form-item>
template>

到这里就结束了,如果对你有帮助就点个赞鼓励一下把~

你可能感兴趣的:(form-generator,vue.js,javascript,前端)