Vue+ElementUI реализует динамическую отрисовку формы и визуальную настройку.

внешний интерфейс Vue.js

написать впереди

Несколько дней назад столкнулся с необходимостью динамической отрисовки форм.Запишите сюда, может вам поможет.

Эта статья посвящена тому, чтобы разобраться с идеей динамического рендеринга, а что касается суперформы, поскольку она не идеальна, я не буду вдаваться в подробности.

GitHub: GitHub.com/Bowen круто/ это…

Динамический рендеринг

Динамический рендеринг должен иметь асинхронные данные, которые выглядят так:

{
  "inline": true,
  "labelPosition": "right",
  "labelWidth": "",
  "size": "small",
  "statusIcon": true,
  "formItemList": [
    {
      "type": "input",
      "label": "姓名",
      "disable": false,
      "readonly": false,
      "value": "",
      "placeholder": "请输入姓名",
      "rules": [],
      "key": "name",
      "subtype": "text"
    },
    {
      "type": "radio",
      "label": "性别",
      "value": "",
      "button": false,
      "border": true,
      "rules": [],
      "key": "gender",
      "options": [
        {
          "value": "1",
          "label": "男",
          "disabled": false
        },
        {
          "value": "0",
          "label": "女",
          "disabled": false
        }
      ]
    }
  ]
}

Затем вам нужно отобразить этот json следующим образом:

Данные для окончательной формы отправки выглядят следующим образом:

{
    "name": "Genji",
    "gender": "1"
}

Тогда наша цель — инкапсулировать такой компонент:

<dynamic-form :config="someConfig" v-model="someData" />

выполнить

Прежде чем начать, вам нужно знатьv-modelкак это работает:

<input v-model="something">

Это не что иное, как синтаксический сахар для следующего примера:

<input
  :value="something"
  @input="something = $event.target.value">

Поняв это, давайте реализуем этот компонент шаг за шагом.

Сначала отправьте конфигурацию наel-form:

<template>
  <el-form 
    class="dynamic-form" 
    :inline="formConfig.inline" 
    :model="value" 
    :label-position="formConfig.labelPosition" 
    :label-width="formConfig.labelWidth" 
    :size='formConfig.size' 
    :status-icon="formConfig.statusIcon">

    <slot/>

  </el-form>
</template>

<script>
export default {
  props: {
    formConfig: {
      type: Object,
      required: true
    },
    value: {
      type: Object,
      required: true
    }
  },
}
</script>

Второй шаг – установить значение по умолчанию.

потому что в каждомform-itemпонадобится одинv-model, поэтому каждое поле обязательно будет иметь значение до рендеринга. Здесь следует отметить одну вещь: не изменяйте напрямую переданный родительский компонент в компоненте.prop, так что здесь мы используем{...this.value}Сделайте быструю копию и не забудьте в конце уведомить родительский компонент. код показывает, как показано ниже:

export default {
  props: {
    formConfig: {...},
    value: {...},
  },
  methods: {
    setDefaultValue() {
      const formData = { ...this.value }
      // 设置默认值
      this.formConfig.formItemList.forEach(({ key, value }) => {
        if (formData[key] === undefined || formData[key] === null) {
          formData[key] = value
        }
      })
      this.$emit('input', formData)
    }
  },
  mounted() {
    this.setDefaultValue()
  },
}

Третий шаг, рендерингform-item.

Как визуализировать следующие данные, с которыми мы знакомыel-form-item?

{
    "type": "input",
    "label": "姓名",
    "disable": false,
    "readonly": false,
    "value": "",
    "placeholder": "请输入姓名",
    "rules": [],
    "key": "name",
    "subtype": "text"
}

Во-первых, используйтеvueВстроенныйcomponentКомпоненты, которые можно записать так:

<el-form-item>
    <component :is="`el-${item.type}`" />
</el-form-item>

Во-вторых, используйтеv-ifСудя по одному:

<el-form-item>
    <el-input v-if="item.type === 'input'" />
    <span v-else>未知控件类型</span>
</el-form-item>

Учитывая, что логика обработки каждой формы контроля сильно различается, арендодатель выбирает второй метод.

Согласно этой идее, мы инкапсулируемdynamic-form-item, получитьitem, который оказываетel-form-item:

<template>
  <el-form-item :rules="item.rules" :label="item.label" :prop="item.key">

    <el-input 
        v-if="item.type==='input'" 
        v-bind="$attrs" v-on="$listeners" 
        :type="item.subtype" 
        :placeholder="item.placeholder" 
        :disabled="item.disable" 
        :readonly="item.readonly" 
        :autosize="item.autosize"></el-input>

    <el-select 
        v-else-if="item.type==='select'" 
        v-bind="$attrs" v-on="$listeners"
        :multiple="item.multiple" 
        :disabled="item.disabled" 
        :multiple-limit="item.multipleLimit">
            <el-option 
                v-for="o in item.options" 
                :key="o.value" 
                :label="o.label" 
                :value="o.value" 
                :disabled="o.disabled">
            </el-option>
    </el-select>
    
    ...

    <span v-else>未知控件类型</span>

  </el-form-item>
</template>

<script>
export default {
  props: {
    item: {
      type: Object,
      required: true
    }
  }
}
</script>

советы: используйтеv-bind="$attrs" v-on="$listeners"Может легко пересылать родительский компонентv-modelИнструкции см. в разделе Компоненты высшего порядка vue.

Наконец, мы можем зациклить и вывести полную форму:

<dynamic-form-item
    v-for="item in formConfig.formItemList"
    :key="item.key"
    v-if="value[item.key]!==undefined"
    :item="item"
    :value="value[item.key]"
    @input="handleInput($event, item.key)" />

здесь нельзя использоватьv-model="value[item.key]", как было сказано выше, props нельзя модифицировать напрямую в компоненте, так что давайте сюдаinput事件.

methods: {
    handleInput(val, key) {
      // 这里element-ui没有上报event,直接就是value了
      this.$emit('input', { ...this.value, [key]: val })
    },
    setDefaultValue() {...}
},

Полный кодовый адрес:src/components/dynamic-form/form.vue

расширения

1. Цифровой дисплей, ограничение количества знаков после запятой

element-uiЯ не использовал эту функцию, но я думаю, что она довольно распространена, поэтому используйтеel-inputВручную упаковалinput-number:

<!--普通使用-->
<input-number 
    v-model="someNumber"
    :min="1" 
    :max="99" 
    :decimal1="2" 
    append="元"></input-number>
    
<!--在dynamic-form-item中的应用-->
<input-number 
    v-else-if="item.type==='number'" 
    v-bind="$attrs" v-on="$listeners" 
    :min="item.min" 
    :max="item.max" 
    :decimal1="item.decimal1" 
    :append="item.append" 
    :prepend="item.prepend" 
    :disabled="item.disabled"></input-number>

Полный код:src/components/dynamic-form/input-number.vue

2. Асинхронная проверка

выгода отasync-validator, мы можем легко настроить правила проверки.

в конфигурации

{
    "type": "input",
    ...
    "rules":[
        {
            "sql": "SELECT {key} FROM balabala",
            "message": "xx已被占用",
            "trigger": "blur"
        }
    ]
}

существуетdynamic-form-itemкомпонент, траверсаitem.rules, преобразовать проверку sql в пользовательскуюvalidatorфункция:

<template>
    <el-form-item :rules="Rules" >
        ...
    </el-form-item>
</template>

<script>
import request from '@/utils/request'
export default {
  props: {
    item: {...}
  },
  computed: {
    Rules() {
      const rules = this.item.rules
      if (rules === undefined) return undefined
      const R = []
      rules.forEach(rule => {
        if (rule.sql) {
          const validator = (rule2, value, callback) => {
            // 根据项目自行修改
            request('/api/validate', 'POST', {
              key: rule2.field,
              value,
              sql: rule.sql.replace(/{key}/ig, rule2.field)
            })
              .then(res => {
                callback(!res || undefined)
              })
              .catch(err => {
                this.$message.error(err.message)
                callback(false)
              })
          }
          R.push({ validator, message: rule.message, trigger: 'blur' })
        } else {
          R.push(rule)
        }
      })
      return R
    }
  },
}
</script>

3. Быстрая настройка провинций и городов

благодарныйelement-china-area-dataавтор.

В конфигурации:

{
    "type": "cascader",
    ...
    "areaShortcut": "provinceAndCityData"
}

существуетdynamic-form-itemВ компоненте:

<template>
    <el-form-item>
    
        ...
        
        <el-cascader 
            :options="item.options || require('element-china-area-data')[item.areaShortcut]"
            ></el-cascader>
    </el-form-item>
</template>

4. Загрузить вариант с удаленного

в том числе, но не ограничиваетсяradio,checkbox,cascader,select.

В конфигурации:

{
    "type": "checkbox",
    ...
    "optionsUrl": "/api/some/options"
}

существуетdynamic-form-itemВ компоненте:

<template>
    <el-form-item>
    
        ...
        
        <el-select>
            <el-option 
                v-for="o in item.options || ajaxOptions"
                ></el-option>
        </el-select>

    </el-form-item>
</template>

<script>
import request from '@/utils/request'
export default {
  props: {
    item: {...}
  },
  computed: {...},
  data() {
    return {
      ajaxOptions: []
    }
  },
  created() {
    const { optionsUrl, key, type } = this.item
    if (optionsUrl) {
      // 根据项目自行修改
      request(`${optionsUrl}?key=${key}`, 'GET')
        .then(res => {
          this.ajaxOptions = res
        })
        .catch(err => { this.$message.error(err.message) })
    }
  }
}
</script>

над

Я впервые пишу статью, надеюсь, она поможет всем, и предложения также приветствуются. Повторите пост в конце статьиАдрес GitHub, если можно поставить Звезду, было бы очень хорошо =)