zoukankan      html  css  js  c++  java
  • 文档驱动 —— 表单组件(四):基于Ant Design Vue封装一些表单域控件

    开源代码

    https://github.com/naturefwvue/nf-vue3-ant

    优缺点本来是写在最后的,但是博文写的似乎有点太长了,估计大家没时间往下看,于是就把有缺点写在前面了,不喜欢可以先跳过。

    缺点

    灵活性肯定是没有了,封装的还是有些过度,灵活度大大降低,没有使用slot,想加点啥目前是不可能的,等以后需要了再说,毕竟这个项目才刚刚开始。
    (为啥缺点只有一条?那不那啥吗,基于ant design vue封装的,他们都那么强大了,还能有啥缺点?封装后除了失去灵活性还能差啥?)

    优点

    1. 简洁,代码很少,做好meta就可以了,另外meta也不需要手写,有个小工具可以辅助创建。
    2. 风格统一,代码就是这样,当需要写新的表单的时候,也不需要复制粘贴,只需要弄个meta就行了,想变风格都变不了。
    3. 可以统一修改升级。UI版本升级了,VUE版本升级了,咋办?改一下组件内部代码即可,调用组件的代码并不需要修改。这样还怕升级了吗?
    4. 可以跨UI,甚至跨框架。之前看了一下element,本来想用的,但是不支持vue3.0只好作罢。element的使用方式也是大同小异,那么我基于element也封装一套组件,保证外部使用方式一致,那么是不是可以做到UI随便切换了呢?
    5. 便于项目升级。项目打包发布后,如果需求有变更,一般修改完后需要重新打包发布。而我们的项目是通过 meta 来控制表单的,也就是说如果有变动,那么改json文件即可,而json可以通过ajax来加载,不用打包到项目里面。

    为啥还要封装

    ant design vue 都已经提供那么的组件了,还不够用吗?为啥还要折腾

    首先antdv 是一个非常强大UI库,提供了很强大的功能和漂亮的UI,使用方面也是非常的灵活,不仅有Form表单,还有各种Data Entry组件,非常灵活。只是鱼和熊掌不能兼得,antdv为了灵活而牺牲了一些简洁性。

    select

    比如a-select,官网代码如下:(有删减)

    <template>
      <div>
        <a-select
          v-model:value="value1"
          style=" 120px"
          @focus="focus"
          ref="select"
          @change="handleChange"
        >
          <a-select-option value="jack">
            Jack
          </a-select-option>
          <a-select-option value="lucy">
            Lucy
          </a-select-option>
          <a-select-option value="disabled" disabled>
            Disabled
          </a-select-option>
          <a-select-option value="Yiminghe">
            yiminghe
          </a-select-option>
        </a-select>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        window.test = this;  // 话说这个是干嘛用的?
        return {
          value1: 'lucy'
        };
      },
      methods: {
        focus() {
          console.log('focus');
        },
        handleChange(value) {
          console.log(`selected ${value}`);
        },
      },
    };
    </script>
    

    首先要设置一些属性,然后还要逐行设置 a-select-option,是不是有点麻烦?

    form

    再来看一下form的官网示例:(七个字段的简单表单)

    <template>
      <a-form :model="form" :label-col="labelCol" :wrapper-col="wrapperCol">
        <a-form-item label="Activity name">
          <a-input v-model:value="form.name" />
        </a-form-item>
        <a-form-item label="Activity zone">
          <a-select v-model:value="form.region" placeholder="please select your zone">
            <a-select-option value="shanghai">
              Zone one
            </a-select-option>
            <a-select-option value="beijing">
              Zone two
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item label="Activity time">
          <a-date-picker
            v-model:value="form.date1"
            show-time
            type="date"
            placeholder="Pick a date"
            style=" 100%;"
          />
        </a-form-item>
        <a-form-item label="Instant delivery">
          <a-switch v-model:checked="form.delivery" />
        </a-form-item>
        <a-form-item label="Activity type">
          <a-checkbox-group v-model:value="form.type">
            <a-checkbox value="1" name="type">
              Online
            </a-checkbox>
            <a-checkbox value="2" name="type">
              Promotion
            </a-checkbox>
            <a-checkbox value="3" name="type">
              Offline
            </a-checkbox>
          </a-checkbox-group>
        </a-form-item>
        <a-form-item label="Resources">
          <a-radio-group v-model:value="form.resource">
            <a-radio value="1">
              Sponsor
            </a-radio>
            <a-radio value="2">
              Venue
            </a-radio>
          </a-radio-group>
        </a-form-item>
        <a-form-item label="Activity form">
          <a-input v-model:value="form.desc" type="textarea" />
        </a-form-item>
        <a-form-item :wrapper-col="{ span: 14, offset: 4 }">
          <a-button type="primary" @click="onSubmit">
            Create
          </a-button>
          <a-button style="margin-left: 10px;">
            Cancel
          </a-button>
        </a-form-item>
      </a-form>
    </template>
    
    <script>
    export default {
      data() {
        return {
          labelCol: { span: 4 },
          wrapperCol: { span: 14 },
          form: {
            name: '',
            region: undefined,
            date1: undefined,
            delivery: false,
            type: [],
            resource: '',
            desc: '',
          },
        };
      },
      methods: {
        onSubmit() {
          console.log('submit!', this.form);
        },
      },
    };
    </script>
    

    在Form表单里面也是这样的设置方式,而表单里面有很多各种各样的控件,一个一个写起来实在是太累。看这样的代码有点眼晕,似乎也不太便于维护,不知道大家是怎么编写和维护的。

    大家都知道我很懒,我想用v-for来做表单,这样即使一百个字段也是一个for搞定,这样代码就简单多了。

    那么如何实现呢?

    如何封装?

    vue的思路就是——数据驱动,那么我就把这个思路做的更彻底一点,——让数据驱动dom的属性

    统一标签名称

    想要for循环,标签必须统一,a-input、a-select等等都不一样,这还怎么循环?所以要先做一个统一的组件,以便于for循环。然后内部再分为多种不同的组件,这样便于维护,要不然代码都写到一起就太乱了。
    于是结构就是这样:

    结构图

    统一属性

    除了标签之外,属性也要一致,否则还是不能for。那么怎么办呢?不同的控件需要的属性都不一样呀,这个好办,我们整合成两个就行

    v-model value

    这个必须单独拿出来。

    meta

    其他的属性都统一放在这里,把这个东东传递进去就好,然后内部识别领取自己的属性。这样就搞定了。

    代码

    我们来看meta的结构。

    meta

    以input为例,其他都大同小异

    props: {
        modelValue: String,
        meta: {
          type: Object,
          default: () => {
            return {
              controlId: Number, // 编号,区别同一个表单里的其他控件
              colName: String, // 字段名称
              controlType: Number, // 用类型编号表示type
              isClear: { // 连续添加时是否恢复默认值
                type: Boolean,
                default: false
              },
              defaultValue: String, // 默认值
              autofocus: { // 是否自动获得焦点
                type: Boolean,
                default: false
              },
              required: { // 必填
                type: Boolean,
                default: true
              },
              disabled: {
                // 是否禁用
                type: Boolean,
                default: false
              },
              readonly: { // 只读
                type: Boolean,
                default: false
              },
              pattern: String, // 用正则做验证。
              placeholder: String,
              title: String, // 提示信息
              maxlength: Number, // 最大字符数
              autocomplete: { // off
                type: String,
                default: 'on'
              }
            }
          }
        }
      },
    

    不同类型的组件,会有所调整。

    input

    模板部分

    <template>
      <div class="components-input-demo-presuffix">
        <a-input
          :id="'id' + meta.controlId"
          :name="'c' + meta.controlId"
          :value="modelValue"
          :autofocus="meta.autofocus"
          :disabled="meta.disabled"
          :readonly="meta.readonly"
          :placeholder="meta.placeholder"
          :title="meta.title"
          :maxlength="meta.maxlength"
          :autocomplete="meta.autocomplete"
          :key="'ckey_'+meta.controlId"
          size="small"
          @input="myInput"
          >
        </a-input>
      </div>
    </template>
    

    先把需要的属性,通过meta都给绑定上

    js

    <script>
    export default {
      name: 'nf-form-input',
      model: {
        prop: 'modelValue',
        event: 'input'
      },
      props: {
        modelValue: String,
        meta: {
          type: Object,
          default: () => {
            return {
              controlId: Number, // 编号,区别同一个表单里的其他控件
              colName: String, // 字段名称
              controlType: Number, // 用类型编号表示type
              isClear: { // 连续添加时是否恢复默认值
                type: Boolean,
                default: false
              },
              defaultValue: String, // 默认值
              autofocus: { // 是否自动获得焦点
                type: Boolean,
                default: false
              },
              required: { // 必填
                type: Boolean,
                default: true
              },
              disabled: {
                // 是否禁用
                type: Boolean,
                default: false
              },
              readonly: { // 只读
                type: Boolean,
                default: false
              },
              pattern: String, // 用正则做验证。
              placeholder: String,
              title: String, // 提示信息
              maxlength: Number, // 最大字符数
              autocomplete: { // off
                type: String,
                default: 'on'
              }
            }
          }
        }
      },
      methods: {
        myInput: function (e) {
          var returnValue = e.target.value
          var colName = this.meta.colName // event.target.getAttribute('colname')
          this.$emit('update:modelValue', returnValue) // 返回给调用者
          this.$emit('getvalue', returnValue, colName) // 返回给中间组件
        }
      }
    }
    </script>
    

    这样我们只要做好meta,就可以完全控制控件了。其他控件也是类似的思路,就不一一贴代码了。

    form-Item

    组件分的太零碎,使用的时候很麻烦,那么怎么办呢?再做个组件整合一下。

    <template>
      <span class="hello">
        <nfArea v-if="meta.controlType === 100" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfUrl v-else-if="meta.controlType === 105" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfInput v-else-if="meta.controlType <= 119" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfNumber v-else-if="meta.controlType === 131" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfSlider v-else-if="meta.controlType === 132" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfDatetime v-else-if="meta.controlType <= 149" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfUpload v-else-if="meta.controlType <= 159" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfColor v-else-if="meta.controlType === 160" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfCheck v-else-if="meta.controlType === 180" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfChecks v-else-if="meta.controlType === 182" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfRadios v-else-if="meta.controlType === 183" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
        <nfSelect v-else-if="meta.controlType <= 191" :modelValue="modelValue" @change="myChange" @getvalue="sendValue" :meta="meta"/>
        <nfInputMore v-else-if="meta.controlType === 200" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
      </span>
    </template>
    

    很笨的方法,挨个类型判断。这里使用了魔数,大概会被喷,不过早就习惯了。

    <script>
    import nfArea from './nf-form-textarea.vue' // 100
    import nfInput from './nf-form-input.vue' // 100-107
    import nfUrl from './nf-form-input-url.vue' // 105
    import nfNumber from './nf-form-number.vue' // 131
    import nfSlider from './nf-form-numslider.vue' // 132
    import nfDatetime from './nf-form-datetime.vue' // 140-144
    import nfUpload from './nf-form-upload.vue' // 150-151
    import nfColor from './nf-form-color.vue' // 160
    import nfCheck from './nf-form-check.vue' // 180
    import nfChecks from './nf-form-checks.vue' // 182
    import nfRadios from './nf-form-radios.vue' // 183
    import nfSelect from './nf-form-select.vue' // 190
    import nfInputMore from './nf-form-inputmore.vue' // 200
    
    export default {
      name: 'nf-form-item',
      components: {
        nfInput,
        nfUrl,
        nfArea,
        nfNumber,
        nfSlider,
        nfDatetime,
        nfUpload,
        nfColor,
        nfCheck,
        nfChecks,
        nfRadios,
        nfSelect,
        nfInputMore
      },
      props: {
        modelValue: Object,
        meta: Object
      },
      methods: {
        myChange: function (value) {
          this.$emit('change', value)
          this.$emit('update:modelValue', value)
        },
        sendValue: function (value, colName) {
          this.$emit('update:modelValue', value)
          this.$emit('getvalue', value, colName) // 返回给中间组件
        }
      }
    }
    </script>
    

    在这里统一注册各种零散的组件,使用的时候就不用想,到底要用哪种组件了。

    表单

    好了,准备工作都做好了,我们可以开始for循环了。

    找了半天,antdv没有提供单纯的table,只好手动找class了,于是代码变成了这样。

        <div class="ant-table ant-table-body ant-table-default ant-table-bordered" >
          <table>
            <colgroup><col style=" 30%; min- 30%;"><col>
            </colgroup>
            <tbody class="ant-table-tbody">
              <tr v-for="(item,index) in metaInfo" :key="index">
                <td align="right" style="padding:10px 10px;height:20px">
                  {{item.title}}:
                </td>
                <td align="left" style="padding:10px 10px;height:20px">
                  <nfInput v-model="modelValue[item.colName]" :meta="item" />
                </td>
              </tr>
            </tbody>
          </table>
        </div>
    

    代码行数和控件(字段)数量无关。代码数量也和有多少表单无关。

    是不是看起来一点都不像一个表单?代码是不是少的有点可怜?
    nfInput 控件有两个属性v-model 和 meta,他会根据meta自动创建需要的dom,并且绑定属性。当然实际干活的是vue和antdv,我只是做了一种尝试。

    <script>
    import { ref } from 'vue'
    import nfInput from '@/components/nf-form/nf-form-item.vue'
    
    export default {
      name: 'FormDemo',
      components: {
        nfInput
      },
      setup () {
        const json = require('./FormDemo.json') // 加载meta信息,json格式
        const modelValue = ref({}) // 放数据的model
        const metaInfo = ref(json.companyForm) // 表单需要的meta信息
        const myClick = (key) => {
          // 更换表单的meta
          metaInfo.value = json[key]
          // 动态创建model
          modelValue.value = {}
          for (var k in metaInfo.value) {
            var item = metaInfo.value[k]
            modelValue.value[item.colName] = ''
          }
        }
        myClick('companyForm')
        return {
          modelValue,
          metaInfo,
          myClick
        }
      }
    }
    </script>
    

    meta,单独的json文件

    meta并不需要写在代码里,因为实在是太长了。可以写在单独的json文件里面,这样便于加载。另外也可以做成ajax加载的方式,这样项目发布后,如何需求有变动,需要调整表单的话,那么只需要单独修改json文件即可,不用重新打包发布。

    {
        "companyForm":{
            "1000":{
                "controlId": 1000,
                "colName": "companyName",
                "controlType": 101,
                "isClear": true,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "placeholder": "请输入公司名称",
                "title": "公司名称",
                "autocomplete": "on",
                "size": 30,
                "maxlength": 100,
                "optionList": [] 
            },
            "1001":{
                "controlId": 1001,
                "colName": "companyCode",
                "controlType": 131,
                "isClear": true,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "placeholder": "公司邮编",
                "title": "公司邮编",
                "autocomplete": "on",
                "min": 100000,
                "max": 999999,
                "step": 1,
                "maxlength": 6,
                "optionList": [] 
            },
            "1002":{
                "controlId": 1002,
                "colName": "legalPerson",
                "controlType": 101,
                "isClear": true,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "placeholder": "请输入法人姓名",
                "title": "法人",
                "autocomplete": "on",
                "size": 20,
                "maxlength": 50,
                "optionList": [] 
            },
            "1003":{
                "controlId": 1003,
                "colName": "liaisonMan",
                "controlType": 101,
                "isClear": true,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "placeholder": "请输入联系人姓名",
                "title": "联系人",
                "autocomplete": "on",
                "size": 20,
                "maxlength": 50,
                "optionList": []
            },
            "1004": {
                "controlId": "1004",
                "colName": "address",
                "controlType": 101,
                "isClear": true,
                "defaultValue": "",
                "autofocus": false,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "placeholder": "请输入公司地址",
                "title": "公司地址",
                "autocomplete": "on",
                "size": 30,
                "maxlength": 50,
                "optionKey": "",
                "optionList": [
                ]
              },
              "1005": {
                "controlId": "1005",
                "colName": "telphone",
                "controlType": 103,
                "isClear": true,
                "defaultValue": "",
                "autofocus": false,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "placeholder": "请输入公司电话",
                "title": "公司电话",
                "autocomplete": "on",
                "size": 30,
                "maxlength": 50,
                "optionKey": "",
                "optionList": [
                ]
              },
              "1006": {
                "controlId": "1006",
                "colName": "URL",
                "controlType": 105,
                "isClear": true,
                "defaultValue": "",
                "autofocus": false,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "placeholder": "https://www.",
                "title": "公司网址",
                "autocomplete": "on",
                "size": 30,
                "maxlength": 50,
                "optionKey": "",
                "optionList": [
                ]
              },
              "1007": {
                "controlId": "1007",
                "colName": "Email",
                "controlType": 104,
                "isClear": true,
                "defaultValue": "",
                "autofocus": false,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "placeholder": "@",
                "title": "公司邮件",
                "autocomplete": "on",
                "size": 30,
                "maxlength": 50,
                "optionKey": "",
                "optionList": [
                ]
              },
              "1008": {
                "controlId": 1008,
                "colName": "type",
                "title": "公司类型",
                "controlType": 190,
                "isClear": true,
                "defaultValue": "",
                "autofocus": false,
                "disabled": false,
                "required": true,
                "pattern": "",
                "class": "",
                "optionList": [
                  { "value": 1, "title": "有限责任公司" },
                  { "value": 2, "title": "股份有限责任公司" },
                  { "value": 3, "title": "个人独资企业" },
                  { "value": 4, "title": "合伙企业" },
                  { "value": 5, "title": "个体工商户" }
                ]
              },
              "1009": {
                "controlId": 1009,
                "colName": "createDate",
                "controlType": 140,
                "isClear": true,
                "defaultValue": "",
                "autofocus": false,
                "disabled": false,
                "required": true,
                "readonly": false,
                "pattern": "",
                "class": "",
                "title": "成立日期",
                "min": "1910-01-01",
                "max": "2999-12-31",
                "step": 1
              }
        }
    }
    

    数据和代码分离,是不是很完美。

    为啥不直接用antdv提供的 Form 表单?

    这个嘛,思路不太一样。好吧,其实是官网的代码,在本地还没有调试成功,等研究明白了还是会用的。

  • 相关阅读:
    spring bean的作用域
    Web前端开发CSS规范总结
    前端技术都包含哪些?
    Web安全常见问题及解决方法
    如何让手游更省带宽,耗电量更少?TBR渲染架构解析!
    新手学Java,有哪些入门知识点?
    如何让手游内存占用更小?从内存消耗iOS实时统计开始
    动作游戏老是卡?试试从这些方面提升流畅度
    开发者必知!2020年大前端发展趋势解读
    游戏编程入门! 想成为专业的游戏开发程序员需要掌握哪些?
  • 原文地址:https://www.cnblogs.com/jyk/p/13686742.html
Copyright © 2011-2022 走看看