使用iview动态创建form表单

https://github.com/viewweiwu/iview-form

<script>
const getPrefix = (tag, lib) => {
  let iviewMap = {
    'form': 'i-form',
    'form-item': 'form-item',
    'input': 'i-input',
    'select': 'i-select',
    'option': 'i-option',
    'checkbox': 'checkbox',
    'checkbox-group': 'checkbox-group',
    'date-picker': 'date-picker',
    'time-picker': 'time-picker',
    'radio': 'radio',
    'radio-group': 'radio-group',
    'switch': 'i-switch',
    'slider': 'slider',
    'button': 'i-button',
    'row': 'row',
    'col': 'i-col',
    'input-number': 'input-number',
    'cascader': 'cascader'
  }
  let elementMap = {
    'form': 'el-form',
    'form-item': 'el-form-item',
    'input': 'el-input',
    'select': 'el-select',
    'option': 'el-option',
    'checkbox': 'el-checkbox',
    'checkbox-group': 'el-checkbox-group',
    'date-picker': 'el-date-picker',
    'time-picker': 'el-time-picker',
    'radio': 'el-radio',
    'radio-group': 'el-radio-group',
    'switch': 'el-switch',
    'slider': 'el-slider',
    'button': 'el-button',
    'row': 'el-row',
    'col': 'el-col',
    'input-number': 'el-input-number',
    'cascader': 'el-cascader'
  }

  return lib === 'iview' ? iviewMap[tag] : elementMap[tag]
}

export default {
  name: 'iview-form',
  props: {
    // 是否启用 grid 布局
    grid: {
      type: [Array, Number]
    },
    // grid 间距
    gutter: {
      type: Number
    },
    // formItem 项
    formList: {
      type: Array,
      default: () => []
    },
    // 是否显示整个控制按钮
    notCtrl: {
      type: Boolean,
      default: false
    },
    // 是否开启 input 标签默认
    enterSubmit: {
      type: Boolean,
      default: false
    },
    // 默认 ui 库
    lib: {
      type: String,
      default: 'iview'
    },
    // 默认标签宽度
    'label-width': {
      type: Number,
      default: 100
    },
    // 默认内容宽度
    'content-width': {
      type: [Number, String],
      default: 240
    },
    // submit 按钮文本
    submitText: {
      type: String,
      default: '提交'
    },
    // 重置按钮文本
    resetText: {
      type: String,
      default: '重置'
    },
    // 是否拥有 提交 按钮
    hasSubmitBtn: {
      type: Boolean,
      default: true
    },
    // 是否拥有 重置 按钮
    hasResetBtn: {
      type: Boolean,
      default: true
    },
    // 原生 form 标签上的 props
    options: {
      type: Object
    },
    // 开启全局 clearable
    clearable: {
      type: Boolean,
      default: true
    },
    // 文本框默认字符个数
    maxlength: {
      type: [Number, String],
      default: 20
    },
    // 多行文本框默认字符个数
    textareaMaxlength: {
      type: Number,
      default: 256
    },
    // 是否全局 disabled
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      form: this.initForm()
    }
  },
  render(h) {
    return h(getPrefix('form', this.lib), {
      props: {
        model: this.form,
        rules: this.rules,
        'label-width': this.lib === 'iview' ? this['labelWidth'] : this['labelWidth'] + 'px',
        ...this.options
      },
      ref: 'form',
      nativeOn: {
        submit(e) {
          e.preventDefault()
          e.stopPropagation()
        }
      }
    }, [
      this.$slots.prepend,
      this.renderFormList(h),
      !this.notCtrl && this.renderSubmit(h),
      this.$slots.default
    ])
  },
  computed: {
    rules() {
      let rules = {}
      this.formList.forEach(item => {
        if (item.rule !== undefined) {
          rules[item.key] = item.rule
        }
      })
      return rules
    },
    gridNum() {
      return this.grid
    }
  },
  methods: {
    // 默认值
    initForm() {
      let form = {}
      let map = {
        'input': '',
        'select': null,
        'checkbox': false,
        'checkbox-group': [],
        'date': new Date(),
        'datetime': new Date(),
        'daterange': [],
        'datetimerange': [],
        'time': '',
        'radio': false,
        'radio-group': '',
        'slider': 0,
        'switch': false,
        'input-number': 0,
        'cascader': []
      }
      this.formList.forEach(item => {
        let defaultValue = ''
        defaultValue = item.defaultValue !== undefined ? item.defaultValue : map[item.type]
        if (item.key) {
          form[item.key] = defaultValue
        }
      })
      return form
    },
    getHypeScript () {
      return this.$parent.$createElement
    },
    renderFormList(h) {
      let list = []
      let grid = this.grid
      // 处理 grid 为不同值时
      if (typeof grid === 'number') {
        list = this.getFormListByNumber(h)
      } else if (Array.isArray(grid)) {
        if (grid.every(item => !Array.isArray(item))) {
          list = this.getFormListByArray(h)
        } else {
          list = this.getFormListByGrid(h)
        }
      } else {
        list = this.getFormList(h)
      }
      return list
    },
    getFormList(h) {
      return this.formList.map(item => {
        return this.getFormItem(h, item, this.getContent(h, item))
      })
    },
    // 当 grid 为数字时
    getFormListByNumber(h) {
      let list = []
      // 过滤 grid
      let grid = ~~Math.abs(this.grid)
      if (grid < 1) grid = 1

      for (let i = 0; i < this.formList.length; i += grid) {
        let childrenList = []
        // 获取当前分成几列 grid 为 number 时
        for (let j = 0; j < grid && i + j < this.formList.length; j++) {
          let children = this.formList[i + j]
          if (!children) break
          let childrenItem = this.getFormItem(h, children, this.getContent(h, children))
          let childrenParts = h(getPrefix('col', this.lib), {
            props: {
              span: 24 / grid
            }
          }, [
            childrenItem
          ])
          childrenList.push(childrenParts)
        }
        let row = this.getRow(h, childrenList)
        list.push(row)
      }
      return list
    },
    // 当 grid 为一维数组时
    getFormListByArray(h) {
      let list = []
      let gridIndex = 0
      for (let i = 0; i < this.formList.length;) {
        let childrenList = []
        let grid = this.grid[gridIndex]
        for (let j = 0; j < grid; j++) {
          let children = this.formList[i + j]
          if (!children) break
          let childrenItem = this.getFormItem(h, children, this.getContent(h, children))
          let childrenParts = h(getPrefix('col', this.lib), {
            props: {
              span: 24 / grid
            }
          }, [
            childrenItem
          ])
          childrenList.push(childrenParts)
        }
        let row = this.getRow(h, childrenList)
        list.push(row)
        gridIndex += 1
        i += grid
      }
      return list
    },
    // 当 grid 为二维数组
    getFormListByGrid(h) {
      let list = []
      let gridIndex = 0
      for (let i = 0; i < this.formList.length;) {
        let childrenList = []
        let grid = this.grid[gridIndex]
        if (!grid) grid = [1]
        for (let j = 0; j < grid.length; j++) {
          let children = this.formList[i + j]
          if (!children) break
          let childrenItem = this.getFormItem(h, children, this.getContent(h, children))
          let childrenParts = h(getPrefix('col', this.lib), {
            props: {
              span: grid[j]
            }
          }, [
            childrenItem
          ])
          childrenList.push(childrenParts)
        }
        let row = this.getRow(h, childrenList)
        list.push(row)
        gridIndex += 1
        i += grid.length
      }
      return list
    },
    getRow (h, childrenList) {
      return h(getPrefix('row', this.lib), {
        props: {
          gutter: this.gutter
        }
      }, childrenList)
    },
    getContent(h, item) {
      let content
      switch (item.type) {
        case 'input':
          content = this.renderInput(h, item)
          break
        case 'select':
          content = this.renderSelect(h, item)
          break
        case 'checkbox':
          content = this.renderCheckbox(h, item)
          break
        case 'checkbox-group':
          content = this.renderCheckboxGroup(h, item)
          break
        case 'date':
          content = this.renderDatePicker(h, item)
          break
        case 'datetime':
          content = this.renderDatePicker(h, item)
          break
        case 'daterange':
          content = this.renderDateRange(h, item)
          break
        case 'datetimerange':
          content = this.renderDateRange(h, item)
          break
        case 'time':
          content = this.renderTimePicker(h, item)
          break
        case 'radio':
          content = this.renderRadio(h, item)
          break
        case 'radio-group':
          content = this.renderRadioGroup(h, item)
          break
        case 'switch':
          content = this.renderSwitch(h, item)
          break
        case 'slider':
          content = this.renderSlider(h, item)
          break
        case 'input-number':
          content = this.renderInputNumber(h, item)
          break
        case 'cascader':
          content = this.renderCascader(h, item)
          break
        default:
          if (typeof item.renderContent === 'function') {
            content = item.renderContent(this.getHypeScript(), item, this.form)
          }
          break
      }
      return content
    },
    getFormItem(h, item, content) {
      if (item.isShow === false) return
      else if (typeof item.isShow === 'function') {
        if (item.isShow(this.form, item) === false) {
          return
        }
      }
      if (typeof item.render === 'function') {
        return item.render(this.getHypeScript(), item, this.form)
      } else {
        let settings = {
          props: {
            prop: item.key
          }
        }
        return h(getPrefix('form-item', this.lib), Object.assign(settings, item.settings), [
          this.renderTitle(h, item, this.form),
          content
        ])
      }
    },
    // 渲染 title
    renderTitle(h, item) {
      return <span slot="label">
        {
          item.required === true
            ? <span style="color: font">*</span>
            : ''
        }
        {
          typeof item.renderTitle === 'function'
            ? <span>{item.renderTitle(h, item, this.form)}</span>
            : <span>{item.title}</span>
        }
      </span>
    },
    // 渲染提交 按钮
    renderSubmit(h) {
      let btns = []
      if (this.hasSubmitBtn) {
        btns.push(h(getPrefix('button', this.lib), {
          props: {
            type: 'primary'
          },
          on: {
            click: this.submit
          }
        }, this.submitText))
      }
      if (this.hasResetBtn) {
        btns.push(h(getPrefix('button', this.lib), {
          style: {
            'margin-left': '10px'
          },
          on: {
            click: this.reset
          }
        }, this.resetText))
      }

      return h(getPrefix('form-item', this.lib), btns)
    },
    // 渲染 input
    renderInput(h, item) {
      let props = item.props || {}
      let attrs = item.attrs || {}
      // 让 element-ui 在 props 里也可以设置 placeholder
      if (props.placeholder) {
        attrs.placeholder = props.placeholder
      }

      // 让 element-ui 在 props 里也可以设置 maxlength
      if (props.type !== 'textarea') {
        attrs.maxlength = +props.maxlength || +this.maxlength
      } else {
        // textarea 长度
        attrs.maxlength = +props.maxlength || +this.textareaMaxlength
      }

      item.attrs = attrs

      let tag = {
        h,
        item,
        tagName: getPrefix('input', this.lib),
        props: {
          clearable: this.clearable,
          ...props
        },
        nativeOn: {
          keydown: (e) => {
            if (e.keyCode === 13 && this.enterSubmit && props.type !== 'textarea') {
              this.submit()
            }
          }
        }
      }
      return this.generateTag(tag)
    },
    // 渲染 select
    renderSelect(h, item) {
      let tag = {
        h,
        item,
        tagName: getPrefix('select', this.lib),
        props: {
          clearable: this.clearable,
          ...(item.props || {})
        },
        children: item.options.map(option => {
          return h(getPrefix('option', this.lib), {
            props: {
              label: option.text,
              value: option.value
            }
          }, [
            typeof item.renderOption === 'function'
              ? item.renderOption(h, option, item)
              : item.text
          ])
        })
      }
      return this.generateTag(tag)
    },
    // 渲染 单个checkbox
    renderCheckbox(h, item) {
      let props = item.props || {}
      if (item.border) {
        props.border = true
      }
      let tag = {
        h,
        item,
        tagName: getPrefix('checkbox', this.lib),
        props,
        children: item.text
      }
      return this.generateTag(tag)
    },
    // 渲染 checkbox group
    renderCheckboxGroup(h, item) {
      let tag = {
        h,
        item,
        tagName: getPrefix('checkbox-group', this.lib),
        props: item.props || {},
        children: item.options.map(option => {
          return h(getPrefix('checkbox', this.lib), {
            props: {
              border: item.border,
              label: option.value
            }
          }, option.text)
        })
      }
      return this.generateTag(tag)
    },
    // 渲染 datepicker
    renderDatePicker(h, item) {
      let tag = {
        h,
        item,
        tagName: getPrefix('date-picker', this.lib),
        props: {
          clearable: this.clearable,
          type: item.type,
          ...(item.props || {})
        }
      }
      return this.generateTag(tag)
    },
    // 渲染范围的 daterange
    renderDateRange(h, item) {
      // 处理 datetimerange 可能宽度不够的问题
      if (item.type === 'datetimerange') {
        item.width = item.width || 360
      }
      let tag = {
        h,
        item,
        tagName: getPrefix('date-picker', this.lib),
        props: {
          clearable: this.clearable,
          type: item.type,
          ...(item.props || {})
        }
      }
      return this.generateTag(tag)
    },
    renderTimePicker(h, item) {
      let tag = {
        h,
        item,
        tagName: getPrefix('time-picker', this.lib),
        props: {
          clearable: this.clearable,
          type: item.type,
          ...(item.props || {})
        }
      }
      return this.generateTag(tag)
    },
    // 渲染 radio
    renderRadio(h, item) {
      let props = item.props || {}
      if (item.border) {
        props.border = true
      }
      let tag = {
        h,
        item,
        tagName: getPrefix('radio', this.lib),
        props,
        children: item.text
      }
      return this.generateTag(tag)
    },
    // 渲染 radio group
    renderRadioGroup(h, item) {
      let tag = {
        h,
        item,
        tagName: getPrefix('radio-group', this.lib),
        props: item.props || {},
        children: item.options.map(option => {
          return h(getPrefix('radio', this.lib), {
            props: {
              border: item.border,
              label: option.value
            }
          }, option.text)
        })
      }
      return this.generateTag(tag)
    },
    // 渲染 switch
    renderSwitch(h, item) {
      let tag = {
        h,
        item,
        tagName: getPrefix('switch', this.lib),
        props: item.props || {}
      }
      return this.generateTag(tag)
    },
    // 渲染 slider
    renderSlider(h, item) {
      let tag = {
        h,
        item,
        tagName: getPrefix('slider', this.lib),
        props: item.props || {}
      }
      return this.generateTag(tag)
    },
    // 渲染 slider
    renderInputNumber(h, item) {
      let tag = {
        h,
        item,
        tagName: getPrefix('input-number', this.lib),
        props: item.props || {}
      }
      return this.generateTag(tag)
    },
    // 渲染 cascader
    renderCascader(h, item) {
      let props = item.props || {}
      let tag = {
        h,
        item,
        tagName: getPrefix('cascader', this.lib)
      }
      if (this.lib === 'iview') {
        props.data = this.getCascaderOptions(item.options)
      } else {
        props.options = this.getCascaderOptions(item.options)
      }
      tag.props = props
      return this.generateTag(tag)
    },
    // 转换 cascader options
    getCascaderOptions(options = []) {
      let list = JSON.stringify(options)
      list = list.replace(/"text":/g, '"label":')
      return JSON.parse(list)
    },
    // 生产 tag
    generateTag({h, item, tagName, props, children, on = {}, nativeOn = {}}) {
      let currProps = {
        value: this.form[item.key],
        min: 0,
        max: 9999999,
        ...props,
        disabled: this.disabled || item.disabled
      }
      let attrs = item.attrs || {}
      let width = null

      let itemOn = item.on || {}
      let itemNativeOn = item.nativeOn || {}

      // 忽略这些标签的宽度设置
      let ignoreMap = {
        'switch': true,
        'checkbox': true,
        'checkbox-group': true,
        'radio': true,
        'radio-group': true,
        'input-number': true
      }

      if (!ignoreMap[item.type]) {
        let w = item.width || this['contentWidth']
        if (typeof w === 'string' && (w.indexOf('%') >= 0 || w === 'auto')) {
          width = w
        } else {
          width = w + 'px'
        }
      }

      return h(tagName, {
        props: currProps,
        attrs,
        style: {
          width
        },
        on: {
          ...itemOn,
          input: (value) => {
            value = this.formatDateValue(value, item)
            this.form[item.key] = value
            this.emitInput(value, item)
          },
          ...on
        },
        nativeOn: {
          ...itemNativeOn,
          ...nativeOn
        }
      }, children)
    },
    // 格式化日期返回,避免 null 的出现
    formatDateValue(value, item) {
      switch (item.type) {
        case 'date':
        case 'datetitme':
          if (!value) {
            value = ''
          }
          break
        case 'daterange':
        case 'datetimerange':
          if (!value) {
            value = ['', '']
          }
          break
      }
      return value
    },
    // 触发 item onInput 事件
    emitInput(value, item) {
      if (typeof item.onInput === 'function') {
        item.onInput(value, item, this.form)
      }
    },
    // 提交事件
    submit() {
      this.$refs.form.validate(valid => {
        this.$emit('submit', this.getForm(), valid)
      })
    },
    // 清空 form 表单
    reset() {
      this.clear()
      this.form = this.initForm()
      this.$refs.form.resetFields()
    },
    // 清空验证
    clear() {
      this.$refs.form.clearValidate && this.$refs.form.clearValidate()
    },
    // 根据 key 获取 value
    getFormBykey(key) {
      return this.form[key]
    },
    // 获取整个 form
    getForm() {
      return {
        ...this.form
      }
    },
    // 设值
    setForm(form) {
      for (let key in form) {
        this.form[key] = form[key]
      }
    },
    validateField (props, callback) {
      this.$refs.form.validateField(props, callback)
    }
  }
}
</script>

  

import iViewForm from './iview-form'

const install = (Vue) => {
  Vue.component(iViewForm.name, iViewForm)
}

if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

export default iViewForm

  

<template>
  <div>
    <iViewForm @submit="onSubmit" :formList="formList"></iViewForm>
  </div>
</template>

<script>
import iViewForm from "./Form/iview-form.vue";

export default {
  components: {
    iViewForm
  },
  data() {
    return {
      formList: [
        {
          title: "姓名",
          type: "input",
          key: "name"
        },
        {
          title: "时间",
          type: "date",
          key: "date"
        },
      ]
    };
  },
  methods: {
    onSubmit(form, valid) {
      console.log(form, valid);
    }
  }
};
</script>

  

posted @ 2022-07-25 14:58  谁动了我的苹果  阅读(857)  评论(0编辑  收藏  举报