IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 前端动态表单dynamicForm VUE(ant-design | 可进行兼容其它的UI组件) - 戴向天 -> 正文阅读

[JavaScript知识库]前端动态表单dynamicForm VUE(ant-design | 可进行兼容其它的UI组件) - 戴向天

大家好!我叫戴向天

QQ群:602504799

如若有不理解的,可加QQ群进行咨询了解

本文内容仅供参考,若想了解并进行相对应的扩展请加入QQ群
在这里插入图片描述
在这里插入图片描述

demo

<DynamicForm :config="dynamicConfig" :rules="rules" />

script
data(){
	return {
		 rules: {
	        carSign: [
	          {
	            validator(rule, value, callback) {
	              const { formData } = this
	              console.log('触发地点:rules.carSign.validator', value, 'formData=>', formData)
	              if (value && value.length > 5) {
	                callback(new Error('字符长度不可超出5个字符'))
	              } else {
	                callback()
	              }
	            },
	            trigger: 'change'
	          }
	        ]
	      },
	      dynamicConfig: [
        {
          gutter: 30,
          children: [
            [
              {
                gutter: 10,
                children: [
                  { type: 'select', label: '车位类别', prop: 'carType' },
                  { type: 'select', label: '车位类型', prop: 'carCategory' }
                ]
              },
              {
                gutter: 10,
                children: [
                  { label: '车位编号', prop: 'carSign', type: 'input' },
                  {
                    type: 'search',
                    span: 12,
                    label: '车位区域',
                    prop: 'carArea',
                    className: 'hide-suffix',
                    bind: { readOnly: true },
                    events: [
                      {
                        name: 'click',
                        async handler({ value, vm }) {
                          console.log(value,vm)
                        }
                      }
                    ],
                    slots(h) {
                      return h('span', {
                        slotName: 'enterButton'
                      })
                    }
                  }
                ]
              }
            ],
            {
              tag: 'div',
              span: 7,
              children: [
                {
                  type: 'button',
                  prop: 'query',
                  slots: () => '查询',
                  bind: { type: 'primary' },
                  events: [
                    {
                      name: 'click',
                      handler({ vm }) {
                        vm.setDisabled(true, ['reset', 'query', 'carType'])
                      }
                    }
                  ]
                },
                { type: 'button', prop: 'reset', slots: () => '重置', bind: {} },
                {
                  type: 'button',
                  slots: () => '展开',
                  bind: { type: 'link' },
                  events: [
                    {
                      name: 'click',
                      handler({ vm }) {
                        vm.setDisabled(false)
                      }
                    }
                  ]
                }
              ]
            }
          ]
        }
      ] 
	}
}

dynamicForm/index.js

import props from './props'
import methods from './methods'
import componentMapping from './componentMapping'
import { createVnodes, getFormData } from './utils'

export default {
  name: 'DynamicForm',
  props,
  data () {
    return {
      rightConfig: [],
      formUI: 'ant', // 该参数暂时没用,只是单纯的用来提醒
      formData: {},
      loclParams: {}
    }
  },
  methods: {
    ...methods,
    init () {
      this.rightConfig = [].concat(this.config)
      const newRules = this.getRules(this.rules)
      this.$emit('update:rules', newRules)
    }
  },
  watch: {
    config: {
      deep: true,
      handler () {
        this.init()
      }
    },
    rightConfig: {
      deep: true,
      handler (value) {
        this.formData = getFormData(this.rightConfig)
      }
    }
  },
  created () {
    this.init()
  },
  mounted () {
    this.$nextTick(() => {
      // 当表单加载完毕之后进行报出告知
      this.$emit('loaded', {
        formData: this.formData,
        vm: this
      })
    })
  },
  render (h) {
    const vm = this
    const { rightConfig, formData } = vm
    const { rules, layout } = vm.$props
    const defaultGutter = vm.$props.gutter
    return h(componentMapping['form'], {
      class: {
        'dynamic-form': true
      },
      attrs: {
        model: formData,
        rules,
        layout
      }
    }, createVnodes(h, rightConfig, { defaultGutter, vm, formData }))
  }
}

dynamicForm/props.js

const $props = {
  config: {
    type: Array,
    default: () => [],
    note: '表单的配置信息'
  },
  rules: {
    type: Object,
    default: () => ({}),
    note: '效验规则,具体的参考相对应的UI框架'
  },
  gutter: {
    type: Number,
    default: null,
    note: '默认的间距'
  },
  layout: {
    type: String,
    types: {
      string: ['horizontal', 'vertical', 'inline']
    },
    default: 'horizontal',
    note: '表单布局'
  }
}

export default $props

dynamicForm/methods.js

import Moment from 'moment'

const refName = 'form'
const defaultFormat = 'YYYY-MM-DD HH:mm:ss'
export default {
  // 获取表单
  getForm () {
    return this.$refs[refName]
  },
  /**
   * 通过指定的字段名进行获取相对应的数据
   * @param {String} fieldName;
   * @returns {Any};
   */
  getFieldValue (fieldName) {
    return this.getForm().getFieldValue(fieldName)
  },
  /**
   * 将规则中validator进行处理进行this指向处理
   * 注意的是进行props传参的时候请进行加上sync
   * 例如:rules.sync="rulesConfig"
   * @param {Object} rules;
   * @returns {Object};
   */
  getRules (rules = {}) {
    return Object.keys(rules).reduce((obj, key) => {
      const newRules = [].concat(rules[key])
      obj[key] = newRules.map((item) => {
        if ('validator' in item && typeof item.validator === 'function') {
          const validator = item.validator
          item.validator = (...agrs) => {
            this.$nextTick(() => {
              const newAgrs = [...agrs]
              const rule = newAgrs.shift()
              newAgrs.shift()
              const value = this.formData[key]
              validator.call(this, rule, value, ...newAgrs)
            })
          }
        }
        return item
      })
      return obj
    }, {})
  },
  /**
   * 判断是不是Moment对象
   * @param {Any} data;
   * @returns {Boolean};
   */
  isMoment (data) {
    return typeof data === 'object' && '_isAMomentObject' in data && data._isAMomentObject
  },
  /**
   * 获取最终的表单参数值
   * @param {Object} result;
   * @returns {Object};
   */
  getFieldsValue (result) {
    const data = result || this.formData || {}
    const keys = Object.keys(data)
    return keys.reduce((obj, key) => {
      const config = this.getConfig(this.rightConfig, key)
      if (config) {
        const { bind = {} } = config
        const isMoment = this.isMoment(obj[key])
        const isArray = Array.isArray(obj[key])
        if (isMoment) {
          obj[key] = obj[key].format(bind.format || defaultFormat)
        } else if (isArray) {
          const allIsMoment = obj[key].every(item => this.isMoment(item))
          if (allIsMoment) {
            obj[key] = obj[key].map(item => item.format(bind.format || defaultFormat))
          }
        }
      }
      return obj
    }, data)
  },
  /**
   * 表单效验
   * @returns {Promise};
   */
  validateFields () {
    return new Promise((resolve) => {
      this.getForm().validate((err, values) => resolve(err ? null : this.getFieldsValue(values)))
    })
  },
  /**
   * 获取表单的数据包含有local
   * @param {Boolean} bool;
   * bool 为true的时候则是带有效验的,为false则是不效验直接获取数据
   * @param {Array<String>} filterKeys;
   * filterKeys 过滤掉指定的字段信息
   * @returns {Object | Promise};
   */
  async getParams (bool, filterKeys = []) {
    let params = null
    if (bool) {
      params = await this.validateFields()
    } else {
      params = this.getFieldsValue()
    }
    if (params) {
      params = {
        ...params,
        ...this.getLocalParams()
      }
      if (filterKeys.length > 0) {
        params = Object.keys(params).reduce((obj, key) => {
          if (filterKeys.indexOf(key) < 0) {
            obj[key] = params[key]
          }
          return obj
        }, {})
      }
    }
    return params
  },
  /**
   * 设置禁用效果
   * @param {Boolean} disabled; 默认false
   * @param {String | Array<String>} props 默认null 给指定的prop或者指定prop数组进行设置禁用状态,当为null值的时候,则是代表全部prop;
   * @returns {this};
   */
  setDisabled (disabled = false, props = null) {
    let propsArr = Object.keys(this.formData)
    if (props) {
      props = Array.isArray(props) ? props : [props]
      propsArr = propsArr.filter(key => props.indexOf(key) >= 0)
    }
    propsArr.forEach(prop => {
      const config = this.getConfig(this.rightConfig, prop)
      config.bind = {
        ...(config.bind || {}),
        disabled
      }
      this.configChange(this.rightConfig, { prop, config, type: 'replace' })
    })
    return this
  },
  /**
   * 设置options
   * @param {String} propName;
   * @param {Array<key,value>} options;
   * @returns {this};
   */
  setOptions (propName, options) {
    const config = this.getConfig(this.rightConfig, propName)
    config.bind = {
      ...(config.bind || {}),
      options
    }
    this.configChange(this.rightConfig, { prop, config, type: 'replace' })
    return this
  },
  /**
   * 批量设置options
   * @param {Object} obj;
   * @returns {this};
   */
  setOptionsMap (obj = {}) {
    Object.keys(obj).forEach(key => {
      if (key in this.formData && Array.isArray(obj[key])) {
        this.setOptions(key, obj[key])
      }
    })
    return this
  },
  // 重置表单
  reset () {
    this.loclParams = {}
    this.getForm().resetFields()
    return this
  },
  /**
   * 设置当前form的作用域数据信息
   * @param {String} key;
   * @param {*} value;
   * @returns {this};
   */
  setLocalParams (key, value) {
    this.$set(this.loclParams, key, value)
    return this
  },
  /**
   * 获取当前form的作用域数据信息
   * @param {String} key 当key有值的时候将会获取指定的数据,若没有则进行获取作用域的所有数据;
   * @returns {*};
   */
  getLocalParams (key) {
    return key ? this.loclParams[key] : this.loclParams
  },
  /**
   * 设置表单数据 键值对的方式
   * @param {String} key;
   * @param {Any} value;
   * @returns {this};
   */
  setData (key, value) {
    if (key in this.formData) {
      const { type } = this.getConfig(this.rightConfig, key)
      if (['date', 'month'].indexOf(type) >= 0) {
        value = new Moment(value)
      } else if (type === 'range') {
        value = (value || []).map(val => new Moment(val))
      }
      this.formData[key] = value
    }
    return this
  },
  /**
   * 批量设置表单数据
   * @param {Object} obj;
   * @returns {this};
   */
  setDataMap (obj = {}) {
    Object.keys(obj || {}).forEach(key => this.setData(key, obj[key]))
    return this
  },
  /**
   * 获取配置信息
   * @param {Array} arr;
   * @param {String} prop;
   * @returns {Object};
   */
  getConfig (arr, prop) {
    return arr.reduce((obj, item) => {
      if (obj) {
        return obj
      }
      const isArray = Array.isArray(item)
      if (isArray) {
        return this.getConfig(item, prop)
      } else if (item.children && Array.isArray(item.children) && item.children.length > 0) {
        return this.getConfig(item.children, prop)
      } else if (item.prop === prop) {
        return item
      }
    }, null)
  },
  /**
   * 改变配置信息
   * @param {Array} arr;
   * @param {String} param.prop;
   * @param {Object} param.config;
   * @param {String} param.type;
   * @returns {undefined};
   */
  configChange (arr, { prop, config, type }) {
    let key = null
    arr.forEach((item, index) => {
      const isArray = Array.isArray(item)
      if (isArray) {
        this.configChange(item, { prop, config, type })
      } else if (item.children && Array.isArray(item.children) && item.children.length > 0) {
        this.configChange(item.children, { prop, config, type })
      } else if (item.prop === prop) {
        key = index
      }
    })
    if (key !== null) {
      if (type === 'replace') {
        arr.splice(key, 1, config)
      } else if (type === 'delete') {
        arr.splice(key, 1)
      } else if (type === 'appendBefore') {
        arr.splice(key - 2, 0, config)
      } else if (type === 'appendAfter') {
        arr.splice(key + 1, 0, config)
      } else if (type === 'update') {
        arr.splice(key, 1, {
          ...arr[key],
          ...config
        })
      }
    }
  },
  /**
   * 替换配置信息
   * @param {String} prop 指定的字段;
   * @param {Object} config 配置信息(同$Props.config的单个配置一致);
   * @returns {this};
   */
  replace (prop, config) {
    if (prop && config) {
      this.configChange(this.rightConfig, { prop, config, type: 'replace' })
    }
    return this
  },
  /**
   * 插入行
   * @param {Array<Number>} indexs;
   * @param {Array<Object> | Object} config;
   * @returns {this};
   */
  insert (indexs, config) {
    let obj = this.rightConfig
    const lastIndex = indexs.pop()
    res.forEach(num => { obj = obj[num] })
    obj.splice(lastIndex - 1, 0, config)
    return this
  },
  /**
   * 删除指定的prop
   * @param {String} prop;
   * @returns {this};
   */
  delete (prop) {
    if (prop) {
      this.configChange(this.rightConfig, { prop, type: 'delete' })
    }
    return this
  },
  /**
   * 添加配置信息,该添加是直接进行添加一个一维数组
   * @param {Object} config;
   * @returns {this};
   */
  push (config) {
    if (config) {
      this.rightConfig.push(config)
    }
    return this
  },
  /**
   * 添加到指定prop字段前面
   * @param {String} prop;
   * @param {Object} config;
   * @returns {this};
   */
  appendBefore (prop, config) {
    if (prop && config) {
      this.configChange(this.rightConfig, { prop, config, type: 'appendBefore' })
    }
    return this
  },
  /**
   * 添加到指定prop字段后面
   * @param {String} prop;
   * @param {Object} config;
   * @returns {this};
   */
  appendAfter (prop, config) {
    if (prop && config) {
      this.configChange(this.rightConfig, { prop, config, type: 'appendAfter' })
    }
    return this
  }
}

dynamicForm/componentMapping.js

/**
 * 组件名称简写映射配置信息
 */
const componentMapping = {
  'form': 'a-form-model',
  'form-item': 'a-form-model-item',
  'row': 'a-row',
  'col': 'a-col',
  'input': 'a-input',
  'select': 'a-select',
  'checkbox': 'a-checkbox-group',
  'radio': 'a-radio-group',
  'textarea': 'a-textarea',
  'upload': 'a-upload-dragger',
  'switch': 'a-switch',
  'search': 'a-input-search',
  'number': 'a-input-number',
  'date': 'a-date-picker',
  'month': 'a-month-picker',
  'range': 'a-range-picker',
  'button': 'a-button'
}

export default componentMapping

dynamicForm/utils.js


import componentMapping from './componentMapping'

/**
 * 创建事件信息
 * @param {VueNode} vm;
 * @param {Object} item;
 * @returns {Object};
 */
export function createEvent (vm, item) {
  const propName = item.prop
  const type = item.type
  const events = item.events || []
  const res = events.reduce((obj, next) => {
    const { name, method, handler } = next
    if (handler || method) {
      const isFn = typeof handler === 'function'
      const isStr = typeof method === 'string'
      obj[name] = (...args) => {
        if (isFn) {
          return handler({
            value: vm.formData[propName],
            config: item,
            vm: vm,
            args
          })
        } else if (isStr) {
          vm.$parent.operationHandler({
            value: vm.formData[propName],
            config: item,
            vm: vm,
            args
          })
        }
      }
    }
    return obj
  }, {})
  const data = {
    value: vm.formData[propName],
    vm,
    args: arguments
  }
  // 以下部分主要是做v-model处理
  if (['select', 'date', 'range', 'month'].indexOf(type) >= 0) {
    const change = res.change
    res.change = (res) => {
      vm.formData[propName] = res
      if (change) {
        change(data)
      }
    }
  } else {
    const input = res.input
    res.input = (res) => {
      const isEvent = res.toString().indexOf('Event') > 0
      vm.formData[propName] = isEvent ? res.target.value : res
      if (input) {
        input(data)
      }
    }
  }

  return res
}

/**
 * tag标签的子级节点创建
 * @param {createElement} h;
 * @param {Vue} vm;
 * @param {Array} children;
 * @returns {Array<vnode>};
 */
export function createTagVnodes (h, vm, children = []) {
  return children.map(child => {
    const { prop, bind = {}, type, slots, innerHTML } = child
    return h(componentMapping[type] || type, {
      attrs: {
        ...(bind || {})
      },
      domProps: {
        innerHTML
      },
      on: createEvent(vm, child)
    }, slots && slots(h))
  })
}

/**
 * 计算间距
 * @param {Array} array;
 * @returns {Number};
 */
export function getSpan (array = []) {
  const defaultSpan = 24 / array.length
  const isAllArray = array.every(node => Array.isArray(node))
  if (isAllArray) {
    return defaultSpan
  }
  const surplusSpan = array.filter(item => !Array.isArray(item)).reduce((total, node) => total - (node.span || defaultSpan), 24)
  const childrenArray = array.filter(node => Array.isArray(node))
  return surplusSpan / childrenArray.length
}

/**
 * 获取相对应的placeholder
 * @param {String} label;
 * @param {String} type;
 * @returns {String | Array};
 */
export function getPlaceholder (label, type) {
  const isCheck = ['select',
    'checkbox',
    'radio',
    'upload',
    'switch',
    'search',
    'date',
    'month'].indexOf(type) > -1
  const isRange = type === 'range'
  if (isRange) {
    return ['开始时间', '结束时间']
  }
  return (isCheck ? '请选择' : '请输入') + (label || '')
}

/**
 * 创建vnode节点
 * @param {createElement} h;
 * @param {Array} row;
 * @param {Number} param.defaultGutter;
 * @param {vue} param.vm;
 * @param {Object} param.formData;
 * @returns {vnodes};
 */
export function createVnodes (h, row = [], { defaultGutter, vm, formData }) {
  return row.map((column = {}) => {
    const {
      children = [], // Array
      gutter// row的间距
    } = column
    const defaultSpan = 24 / children.length
    return h(componentMapping['row'], {
      attrs: { gutter: gutter || defaultGutter || 0 }
    }, children.map(child => {
      const isArray = Array.isArray(child)
      const { prop = null, span, tag, hideLabel, label, labelCol, wrapperCol, required, type, bind = {}, events = [], slots, className, style } = child
      let childVnodes = slots ? slots(h, child) : []
      if (!Array.isArray(childVnodes)) {
        childVnodes = [childVnodes]
      }
      return h(componentMapping['col'], {
        attrs: {
          span: isArray ? getSpan(children) : (span || defaultSpan)
        }
      }, [isArray ? createVnodes(h, child, { defaultGutter, vm, formData }) : [h(tag || componentMapping['form-item'], {
        class: { hideLabel },
        attrs: { label: hideLabel ? ' ' : label, labelCol, wrapperCol, required, prop },
        style: { ...style }
      }, tag ? createTagVnodes(h, vm, child.children) : [
        h(componentMapping[type] || type, {
          class: className,
          attrs: {
            ...bind,
            value: formData[prop],
            placeholder: (bind || {}).placeholder || getPlaceholder(label, type)
          },
          on: createEvent(vm, child)
        }, childVnodes)
      ])]]
      )
    }))
  })
}

/**
 * 获取配置参数
 * @param {Array} config;
 * @returns {Object};
 */
export function getFormData (config = []) {
  return config.reduce((obj, next) => {
    const isArray = Array.isArray(next)
    const hasChildren = next.children && Array.isArray(next.children)
    if (isArray) {
      obj = {
        ...obj,
        ...getFormData(next)
      }
    } else if (hasChildren) {
      obj = {
        ...obj,
        ...getFormData(next.children)
      }
    } else if (next.prop) {
      obj[next.prop] = undefined
    }

    return obj
  }, {})
}

ant-design的辅助样式代码

.dynamic-form {
  &.ant-form-inline {
    .ant-form-item {
      display: flex;
      margin-bottom: 24px;
      .ant-form-item-label {
        flex-shrink: 0;
      }
      .ant-form-item-control-wrapper {
        flex-grow: 1;
      }
    }
  }
  .ant-col {
    overflow: hidden;
    .ant-form-item {
      width: 100%;
    }
  }
  // 隐藏表单标签的文字
  .ant-row.ant-form-item.hideLabel > .ant-form-item-label > label {
    color: transparent;
  }
  .ant-form-item {
    display: flex;
    .ant-form-item-control-wrapper {
      flex-grow: 1;
    }
  }
  .ant-form-item-control > span,
  .ant-form-item-control > span > span {
    width: 100%;
    display: inline-block;
  }
  .hide-suffix > span.ant-input-suffix {
    display: none;
  }

  .hide-suffix > input.ant-input {
    padding-right: 11px !important;
  }
}

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-10 14:27:32  更:2021-07-10 14:27:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/9 9:47:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码