• 基于element-plus定义表单配置化



    前言

    网站基本离不开表单提交,结合之前保险项目及各种检验平台,都是需要填写大量数据,均涉及多个不同表单提交。

    所以表单提交配置化是很有必要的 – 基于面向对象

    也有很多业务需求需要表格行编辑,可参考 基于element-plus定义表格行内编辑配置化

    本文以vue3+element-plus为例,由于时间有限,部分配置可结合项目实际扩展

    先展示实际效果图
    在这里插入图片描述


    一、配置化的前提

    表单内一般包含

    • 需要什么字段
    • 字段填写形式(输入框、下拉框、多级联动、开关、日期、附件上传、富文本编辑器等等)
    • 字段校验(是否必填、存在特殊校验,例如邮箱格式等)
    • 字段排序及各种属性配置

    二、配置的相关组件

    以下文件均建立在 @/components/form-configuration目录下

    1、新建form.vue组件

    用于接收配置信息

    <template>
      <el-form 
        class="customForm"
        :model="fields"
        :rules="rules"
        ref="formData"
        :size="size"
        :disabled="disabled"
        :validate-on-rule-change="validateOnRuleChange"
        :hide-required-asterisk="hideAsterisk"
        :label-position="labelPosition"
        :label-width="labelWidth">
        <el-row :gutter="gutter">
          <el-col v-for="(item, index) in formData" v-show="item.show" :key="item.field + '-' + index" :span="item.colSize">
            <el-form-item :prop="item.field" :label="item.title" :class="item.class">
              <component v-model:content="fields[item.field]" v-model="fields[item.field]" :property="{...item.property, name: item.field}" :is="item.type" @fieldChange="(val) => change(val, item.field)" />
            el-form-item>
          el-col>
        el-row>
      el-form>
    template>
    <script>
    import Input from '@/components/form-configuration/input.vue'
    import Select from '@/components/form-configuration/select.vue'
    import Vhtml from '@/components/form-configuration/v-html.vue'
    import Upload from '@/components/form-configuration/upload.vue'
    import Switch from '@/components/form-configuration/switch.vue'
    import Radio from '@/components/form-configuration/radio.vue'
    import Checkbox from '@/components/form-configuration/checkbox.vue'
    import Date from '@/components/form-configuration/date.vue'
    import TimePicker from '@/components/form-configuration/time-picker.vue'
    import Cascader from '@/components/form-configuration/cascader.vue'
    import UEditor from '@/components/form-configuration/ueditor/index.vue'
    import KindEditor from '@/components/form-configuration/kind-editor/index.vue'
    import { defineComponent, reactive, ref, watch, computed, nextTick } from 'vue'
    
    
    export default {
      components: {
        Input,
        Select,
        Vhtml,
        Upload,
        Switch,
        Radio,
        Checkbox,
        Date,
        TimePicker,
        Cascader,
      },
      props: {
        disabled: {
          type: Boolean,
          default: false // 
        },
        size: {
          type: String,
          default: 'default' // "", "default", "small", "large"
        },
        gutter: {
          type: Number,
          default: 20
        },
        formData: {
          type: Object,
          default() {
            return {}
          }
        },
        hideAsterisk: {
          type: Boolean,
          default: false
        },
        validateOnRuleChange: {
          type: Boolean,
          default: false
        },
        fields: {}, // form相关字段值
        rules: {}, // form校验
        labelWidth: {
          type: String,
          default: '180px'
        },
        labelPosition: {
          type: String,
          default: 'right', // left/right/top
        }
      },
      setup(props, { emit }) {
        const change = (val, field) => {
          emit('change', {
            val, field
          }) // 触发
        }
        return {
          change
        }
      }
    }
    script>
    <style lang="less" scoped>
    style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    2、新建input.vue组件

    <template>
      <el-input v-model="input"
        :type="fieldProperty.type"
        :clearable="fieldProperty.clearable"
        :placeholder="fieldProperty.placeholder" 
        :maxlength="fieldProperty.maxlength"
        :readonly="fieldProperty.readonly"
        @blur="blur"
        @change="change"
        autocomplete="on"
        :name="fieldProperty.name"
        :disabled="fieldProperty.disabled" />
    template>
    <script lang="ts">
    import { computed } from 'vue'
    export default {
      props: {
        modelValue: [String, Number], // 组件绑定值
        content: [String, Number], // 组件绑定值
        property: {
          type: Object,
          default() {
            return {
              
            }
          }
        }
      },
      setup(props, { emit }) {
        const fieldProperty = computed(() => {
          return {
            placeholder: '请输入', // 提示语
            maxlength: -1, // 可输入最大长度
            readonly: false, // 是否只读
            disabled: false, // 是否可输入
            clearable: false, // 是否可清空输入框
            type: 'text', // 输入框类型 input | textarea | password | text
            ...props.property
          }
        })
        const input = computed({
          get() {
            return props.modelValue
          },
          set(val) {
            emit('update:modelValue', val)
          }
        })
        const change = (val: any) => {
          emit('fieldChange', val) // 触发
        }
        const blur = () => {
          emit('fieldBlur', input.value) // 触发
        }
        return {
          input,
          change,
          blur,
          fieldProperty
        }
      },
    }
    script>
    <style lang="less" scoped>style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    3、新建select.vue组件

    <template>
      <el-select v-model="val"
        ref="selectRef"
        :multiple="fieldProperty.multiple"
        :filterable="fieldProperty.filterable"
        :clearable="fieldProperty.clearable"
        :disabled="fieldProperty.disabled"
        @change="change"
        :placeholder="fieldProperty.placeholder">
        <el-option v-for="(item, index) in fieldProperty.data"
          :key="item[fieldProperty.value] + item[fieldProperty.label] + '-' + index"
          :label="item[fieldProperty.label]" :value="item[fieldProperty.value]">
          { item[fieldProperty.label] }} -->
        el-option>
      el-select>
    template>
    <script lang="ts">
    import { computed } from 'vue'
    export default {
      props: {
        modelValue : [String, Number, Array],
        property: {
          type: Object,
          default() {
            return {
              
            }
          }
        }
      },
      setup(props, { emit }) {
        const fieldProperty = computed(() => {
          return {
            placeholder: '请选择', // 提示语
            data: [], // 下拉可选数据
            label: 'label', // 选择框的文案字段
            value: 'value', // 选择框的值字段
            multiple: false, // 是否多选
            filterable: true, // 是否可搜索
            clearable: false, // 是否可以清空选项
            disabled: false, // 是否禁用
            ...props.property
          }
        })
        const val = computed({
          get() {
            return props.modelValue 
          },
          set(val) {
            emit('update:modelValue', val) // 触发
          }
        })
        const change = (val: any) => {
          emit('fieldChange', val) // 触发
        }
        return {
          val,
          fieldProperty,
          change
        } 
      }
    }
    script>
    <style lang="less" scoped>
    style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    4、新建v-html.vue组件

    <template>
      <div v-html="val" class="pre-line" style="line-height: 26px">div>
    template>
    <script>
    export default {
      props: {
        modelValue: [String, Number]
      },
      model: {
        prop: 'modelValue', // 指定 v-model 要绑定的参数叫什么名字,来自于 props 中定义的参数
        event: 'change' // 指定要触发的事件名字,将被用于 $emit
      },
      data() {
        return {
        }
      },
      computed: {
        val: {
          // 这里的计算属性使用了 getter、setter,可以简化代码
          // 可参见链接 https://cn.vuejs.org/v2/guide/computed.html#%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E7%9A%84-setter
          get() {
            return this.modelValue
          },
          set(val) {
            this.$emit('change', val) // 触发
          }
        }
      },
      methods: {}
    }
    script>
    <style lang="less" scoped>
    style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    5、新建upload.vue组件

    <template>
      <el-upload
        ref="upload"
        class="upload-demo"
        :file-list="val"
        :multiple="fieldProperty.multiple"
        :limit="fieldProperty.limit"
        :disabled="fieldProperty.disabled"
        action="#"
        :auto-upload="false"
        :on-exceed="handleExceed"
        :on-preview="handlePreview"
        :on-change="handleChange"
        :accept="fieldProperty.accept"
        :on-remove="handleRemove">
        <el-button type="primary" :loading="fieldProperty.loading">{{ fieldProperty.btnName }}el-button>
        <div class="el-upload__tip">{{ fieldProperty.tips}}div>
      el-upload>
    template>
    <script>
    import { watch, reactive, computed, ref } from 'vue'
    import { ElMessage, genFileId } from 'element-plus'
    import { DownloadFileAPI } from "@/server/Base"
    import { formDataDownFile } from "@/utils"
    export default {
      props: {
        modelValue: [String, Number, Array], // 组件绑定值
        property: {
          type: Object,
          default() {
            return {}
          }
        }
      },
      setup(props, { emit }) {
        const upload = ref()
        const fieldProperty = computed(() => {
          return {
            multiple: false, // 是否可多选
            limit: 10, // 依次最大可上传
            btnName: '点击上传', // 出发上传按钮文案
            tips: '', // 上传文案提示
            accept: '', // 文件上传类型
            disabled: false,
            maximum: 20, // 默认最大 20M ; 1M = 1024b * 1024KB
            loading: false,
            ...props.property
          }
        })
        const val = computed({
          get() {
            return props.modelValue 
          },
          set(val) {
            emit('update:modelValue', val) // 触发
          }
        })
        const handleChange = (file, fileList) => {
          const fileSuffix = file.name.substring(file.name.lastIndexOf(".") + 1).toLocaleLowerCase();
          const whiteList = fieldProperty.value.accept.toLocaleLowerCase().split(',')
          if (whiteList.indexOf('.'+fileSuffix) === -1 && fieldProperty.value.accept) {
            val.value = fileList.filter((fie) => fie.uid !== file.uid)
            ElMessage.error('The file type can only be ' + fieldProperty.value.accept)
            return false;
          }
          if (file.size > fieldProperty.value.maximum * 1024 * 1024) {
            val.value = fileList.filter((fie) => fie.uid !== file.uid)
            ElMessage.error('The maximum upload size is ' + fieldProperty.value.maximum + 'M')
            return
          }
          val.value = fileList
          emit('fieldChange', { file, index: fileList.length - 1, fileList}) // 触发当前上传附件
        }
        const handleRemove = (file, fileList) => {
          val.value = fileList
          emit('fieldChange', fileList) // 触发
        }
        const handlePreview = (file) => {
          const params = {
            fullName: file.url || file.filePath?.FullName,
            oriFileName: file.name || file.filePath?.OriginalName
          }
          const action = DownloadFileAPI(params)
          formDataDownFile(action, params)
        }
        const handleExceed = (files) => {
          console.log('100000', fieldProperty.multiple)
          if (!fieldProperty.multiple) {
            upload.value.clearFiles()
            const file = files[0]
            file.uid = genFileId()
            upload.value.handleStart(file) 
          }
        }
        return {
          handleChange,
          handleRemove,
          handleExceed,
          handlePreview,
          fieldProperty,
          upload,
          val
        }
      }
    }
    script>
    <style lang="less" scoped>
    style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    6、新建switch.vue组件

    <template>
      <el-switch v-model="val"
        :disabled="fieldProperty.disabled"
        :active-color="fieldProperty.activeColor"
        :inactive-color="fieldProperty.inactiveColor">
      el-switch>
    template>
    <script>
    import { computed, reactive} from 'vue'
    export default {
      name: 'SmSwitch',
      props: {
        modelvalue: [Boolean],
        property: {
          type: Object,
          default() {
            return {}
          }
        }
      },
      setup(props, { emit }) {
        const fieldProperty = reactive({
          activeColor: '#13ce66',
          inactiveColor: '#DCDFE6',
          disabled: false,
          ...props.property
        })
        const val = computed({
          get() {
            return props.modelvalue
          },
          set(val) {
            emit('update:modelvalue', val) // 触发
          }
        })
        return {
          val,
          fieldProperty
        }
      }
    }
    script>
    <style lang="less" scoped>style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    7、新建radio.vue组件

    <template>
      <el-radio-group v-model="val" class="ml-4" :disabled="fieldProperty.disabled" :size="fieldProperty.size">
        <el-radio v-for="(item, index) in fieldProperty.data" :key="item + index + RandomNumber()" :label="item[fieldProperty.value]">{{ item[fieldProperty.label] }}el-radio>
      el-radio-group>
    template>
    <script lang="ts">
    import { computed, reactive} from 'vue'
    import { RandomNumber } from '@/utils'
    export default {
      name: 'Radio',
      props: {
        modelvalue: [Boolean],
        property: {
          type: Object,
          default() {
            return {}
          }
        }
      },
      setup(props, { emit }) {
        const fieldProperty = reactive({
          readonly: false,
          disabled: false,
          label: 'label', // 选择框的文案字段
          value: 'value', // 选择框的值字段
          data: [],
          size: 'default', // 'large' | 'default' | 'small'
          ...props.property
        })
        const val = computed({
          get() {
            return props.modelvalue
          },
          set(val) {
            emit('update:modelvalue', val) // 触发
          }
        })
        return {
          val,
          fieldProperty,
          RandomNumber
        }
      }
    }
    script>
    <style lang="less" scoped>style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    8、新建checkbox.vue组件

    <template>
      <el-checkbox-group
        v-if="fieldProperty.data.length"
        v-model="val"
        :disabled="fieldProperty.disabled"
        @change="handleCheckedChange"
      >
        <el-checkbox
          v-for="(item, index) in fieldProperty.data"
          :key="item.label + index + RandomNumber()"
          :disabled="item.disabled"
          :label="item.value"
        >
          {{ item.label }}
        el-checkbox>
      el-checkbox-group>
      <el-checkbox
        v-else
        v-model="val"
        :disabled="fieldProperty.disabled"
        :label="fieldProperty.label"
      />
    template>
    <script lang="ts">
    import { computed, reactive } from "vue";
    import { RandomNumber } from "@/utils";
    import type { CheckboxData } from "@/interface/form";
    export default {
      name: "Radio",
      props: {
        modelvalue: [Array, Boolean],
        property: {
          type: Object,
          default() {
            return {};
          },
        },
      },
      setup(props, { emit }) {
        const fieldProperty = computed(() => {
          return {
            readonly: false,
            disabled: false,
            label: "label", // 单选框的文案字段
            data: [] as Array<CheckboxData>,
            size: "default", // 'large' | 'default' | 'small'
            ...props.property,
          };
        });
        const handleCheckedChange = () => {};
        const val = computed({
          get() {
            return props.modelvalue;
          },
          set(val) {
            emit("update:modelvalue", val); // 触发
          },
        });
        return {
          val,
          RandomNumber,
          handleCheckedChange,
          fieldProperty,
        };
      },
    };
    script>
    <style lang="less" scoped>style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    9、新建date.vue组件

    <template>
      <el-date-picker
        style="width: 100%"
        v-model="date"
        :type="fieldProperty.type"
        :disabled="fieldProperty.disabled"
        :range-separator="fieldProperty.ngeSeparator"
        :placeholder="fieldProperty.placeholder"
        :start-placeholder="fieldProperty.startPlaceholder"
        :disabled-date="fieldProperty.disabledDate"
        :end-placeholder="fieldProperty.endPlaceholder"
        @change="change"
      />
    template>
    <script lang="ts">
    import { computed } from 'vue'
    import { useI18n } from 'vue-i18n'
    export default {
      props: {
        modelValue: [String, Number], // 组件绑定值
        content: [String, Number], // 组件绑定值
        property: {
          type: Object,
          default() {
            return {}
          }
        }
      },
      setup(props, { emit }) {
        const { t } = useI18n()
        const fieldProperty = computed(() => {
          return {
            startPlaceholder: t('Startdate'), // 提示语
            endPlaceholder: t('Enddate'), // 提示语
            placeholder: t('Selectdate'), // 提示语
            readonly: false, // 是否只读
            disabled: false, // 是否可输入
            ngeSeparator: t('To'), 
            type: 'date', // year / month / date / dates / datetime / week / datetimerange / daterange / monthrange
            disabledDate: (time: Date) => {
              const beginDateVal = props.property.EffectDate
              if(beginDateVal){
                return time.valueOf()<new Date(beginDateVal).valueOf()- 24*60*60*1000
              }
              const endDateVal = props.property.endDate
              if(endDateVal){
                return time.valueOf()  > new Date(beginDateVal).valueOf()- 24*60*60*1000
              }
            },
            ...props.property
          }
        })
        const date = computed({
          get() {
            return props.modelValue
          },
          set(val) {
            emit('update:modelValue', val)
          }
        })
        
        const change = (val: any) => {
          emit('fieldChange', val) // 触发
        }
        return {
          date,
          change,
          fieldProperty
        }
      },
    }
    script>
    <style lang="less" scoped>style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    10、新建time-picker.vue组件

    <template>
      <el-time-picker
        style="width: 100%"
        v-model="date"
        :is-range="fieldProperty.type"
        :disabled="fieldProperty.disabled"
        :range-separator="fieldProperty.ngeSeparator"
        :placeholder="fieldProperty.placeholder"
        :start-placeholder="fieldProperty.startPlaceholder"
        :end-placeholder="fieldProperty.endPlaceholder"
      />
    template>
    <script lang="ts">
    import { computed } from 'vue'
    import { useI18n } from 'vue-i18n'
    export default {
      props: {
        modelValue: [String, Number], // 组件绑定值
        content: [String, Number], // 组件绑定值
        property: {
          type: Object,
          default() {
            return {}
          }
        }
      },
      setup(props, { emit }) {
        const { t } = useI18n()
        const fieldProperty = computed(() => {
          return {
            startPlaceholder: t('Startdate'), // 提示语
            endPlaceholder: t('Enddate'), // 提示语
            placeholder: t('Selectdate'), // 提示语
            readonly: false, // 是否只读
            disabled: false, // 是否可输入
            ngeSeparator: t('To'), 
            type: true, // true / false
            ...props.property
          }
        })
        const date = computed({
          get() {
            return props.modelValue
          },
          set(val) {
            emit('update:modelValue', val)
          }
        })
        return {
          date,
          fieldProperty
        }
      },
    }
    script>
    <style lang="less" scoped>style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    11、新建cascader.vue组件

    <template>
      <el-cascader
        v-model="value"
        :filterable="fieldProperty.filterable"
        :placeholder="fieldProperty.placeholder"
        :disabled="fieldProperty.disabled"
        :clearable="fieldProperty.clearable"
        :options="fieldProperty.data"
        :collapse-tags="true"
        :collapse-tags-tooltip="true"
        @change="handleChange"
      />
    template>
    <script lang="ts">
    import { computed, reactive, watch } from 'vue'
    export default {
      props: {
        modelValue: Array, // 组件绑定值
        content: Array, // 组件绑定值
        property: {
          type: Object,
          default() {
            return {}
          }
        }
      },
      setup(props, { emit }) {
        const fieldProperty = computed(() => {
          return {
            placeholder: '请选择', // 提示语
            disabled: false, // 是否可输入
            clearable: true, // 是否支持清空选项
            filterable: true, // 是否可搜索
            data: [],
            ...props.property
          }
        })
        const value = computed({
          get() {
            return props.modelValue
          },
          set(val) {
            emit('update:modelValue', val)
          }
        })
        const handleChange = (val) => {
          console.log('handleChange', val)
        }
        return {
          value,
          fieldProperty,
          handleChange
        }
      },
    }
    script>
    <style lang="less" scoped>style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    12、新建/ueditor/组件

    • index.vue
    <template>
      <vue-ueditor-wrap
        v-model="content"
        :config="VueUeditorWrapConfig"
        :editor-id="editorId"
      >vue-ueditor-wrap>
    template>
    <script>
    import { default as VueUeditorWrapConfig } from "./ueditor.js";
    import { RandomNumber } from '@/utils'
    export default {
      props: {
        value: [String],
        contentDetail: {
          type: String,
          default: "",
        },
        property: {
          type: Object,
          default() {
            return {};
          },
        },
      },
      model: {
        prop: "value", // 指定 v-model 要绑定的参数叫什么名字,来自于 props 中定义的参数
        event: "change", // 指定要触发的事件名字,将被用于 $emit
      },
      data() {
        return {
          show: false,
          VueUeditorWrapConfig,
          isPosting: false,
          edit_show: true,
          index: 0,
          editorId: 'editor' + RandomNumber()
        };
      },
      computed: {
        content: {
          get() {
            console.log('10000', this.value)
            return this.value;
          },
          set(val) {
            this.$emit("change", val);
          },
        },
      },
    };
    script>
    <style lang="less" scoped>
    /* 弹框输入框长度 */
    .lengthWidth {
      width: 80%;
    }
    
    .editor_if {
      width: 100%;
      height: 400px;
    }
    
    .edui1 {
      z-index: 90 !important;
    }
    style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • ueditor.js
    // let env = process.env.NODE_ENV || ''
    let ueditorPath = ''
    // if (env === 'development') {
    //   ueditorPath = '/static/UEditor/'
    // } else if (env === 'production') {
    //   ueditorPath = '/static/UEditor/'
    // } else {
    //   ueditorPath = '/static/UEditor/'
    // }
    ueditorPath = '/ueditor/'
    export default {
      UEDITOR_HOME_URL: ueditorPath,
      maximumWords: 1000 * 100,
      // toolbars: [
      //   [
      //     'source', '|', 'undo', 'redo', '|',
      //     'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|',
      //     'rowspacingtop', 'rowspacingbottom', 'lineheight', '|',
      //     'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|',
      //     'directionalityltr', 'directionalityrtl', 'indent', '|',
      //     'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|',
      //     'imagenone', 'imageleft', 'imageright', 'imagecenter', '|',
      //     'horizontal', 'date', 'time',
      //     'inserttable', 'simpleupload', 'preview',
      //   ]
      // ],
      toolbars: [
        [
          'source', '|', 'undo', 'redo', '|',
          'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|',
          'rowspacingtop', 'rowspacingbottom', 'lineheight', '|',
          'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|',
          'directionalityltr', 'directionalityrtl', 'indent', '|',
          'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|',
          'imagenone', 'imageleft', 'imageright', 'imagecenter', '|',
          'horizontal'
        ]
      ],
      enableAutoSave: false,
      elementPathEnabled: false,
      disabledTableInTable: false,
      serverUrl: '' // 暂无接口 APP_CONFIG.baseURL + '/file/upload'
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    13、新建/kind-editor组件

    • index.vue
    <template>
      <vue3-kind-editor
        :id="editorId"
        :height="fieldProperty.height"
        :width="fieldProperty.width"
        v-model="content"
        :items="fieldProperty.items"
        :loadStyleMode="fieldProperty.loadStyleMode">
      vue3-kind-editor>
    template>
    <script lang="ts" src="./index.js" />
    <style lang="less" scoped>
    /* 弹框输入框长度 */
    .lengthWidth {
      width: 80%;
    }
    
    .editor_if {
      width: 100%;
      height: 400px;
    }
    
    .edui1 {
      z-index: 90 !important;
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • index.js
    
    // import Vue3KindEditor from '@zhj-target/vue3-kind-editor'
    import { computed } from 'vue';
    import Vue3KindEditor from './editor/index.vue'
    import { RandomNumber } from '@/utils'
    window._instances = []
    export default {
      components: {
        Vue3KindEditor,
      },
      props: {
        value: [String],
        property: {
          type: Object,
          default() {
            return {};
          },
        },
      },
      model: {
        prop: "value", // 指定 v-model 要绑定的参数叫什么名字,来自于 props 中定义的参数
        event: "change", // 指定要触发的事件名字,将被用于 $emit
      },
      setup(props, { emit }) {
        const fieldProperty = computed(() => {
          return {
            height: '150px',
            width: '100%',
            loadStyleMode: false,
            items: [
              'preview', 'template', '|', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold', 'italic', 'underline',
              'removeformat', '|', 'justifyleft', 'justifycenter', 'justifyright', 'insertorderedlist',
              'insertunorderedlist', '|', 'link'
            ],
            ...props.property
          }
        })
        const content = computed({
          get() {
            return props.value
          },
          set(val) {
            emit('update:value', val) // 触发
          }
        })
        const editorId = computed(() => {
          return 'editor' + RandomNumber()
        })
        return {
          editorId,
          content,
          fieldProperty
        }
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • editor/index.vue
    <template>
      <div class="kindeditor">
        <textarea :id="id" name="content" v-html="state.outContent">textarea>
      div>
    template>
    <script lang="ts" src="./index.ts" />
    <style scoped>
    .kindeditor {
      width: 100%;
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • editor/index.js
    
    import { onMounted, reactive, watch } from "vue"
    import '@public/kind-editor/themes/default/default.css'
    import '@public/kind-editor/kindeditor-all.js'
    import '@public/kind-editor/lang/zh-CN.js'
    import '@public/kind-editor/lang/en.js'
    export default {
      name: 'vue3-kind-editor',
      props: {
        modelValue: {
          type: String,
          default: ''
        },
        id: {
          type: String,
          required: true
        },
        width: {
          type: String
        },
        height: {
          type: String
        },
        minWidth: {
          type: Number,
          default: 650
        },
        minHeight: {
          type: Number,
          default: 100
        },
        items: {
          type: Array,
          default: function () {
            return [
              'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'code', 'cut', 'copy', 'paste',
              'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright',
              'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript',
              'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/',
              'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold',
              'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image',
              'media', 'insertfile', 'table', 'hr', 'pagebreak',
              'anchor', 'link', 'unlink',
            ]
          }
        },
        noDisableItems: {
          type: Array,
          default: function () {
            return ['source', 'fullscreen']
          }
        },
        filterMode: {
          type: Boolean,
          default: true
        },
        htmlTags: {
          type: Object,
          default: function () {
            return {
              font: ['color', 'size', 'face', '.background-color'],
              span: ['style'],
              div: ['class', 'align', 'style'],
              table: ['class', 'border', 'cellspacing', 'cellpadding', 'width', 'height', 'align', 'style'],
              'td,th': ['class', 'align', 'valign', 'width', 'height', 'colspan', 'rowspan', 'bgcolor', 'style'],
              a: ['class', 'href', 'target', 'name', 'style'],
              embed: ['src', 'width', 'height', 'type', 'loop', 'autostart', 'quality',
                'style', 'align', 'allowscriptaccess', '/'],
              img: ['src', 'width', 'height', 'border', 'alt', 'title', 'align', 'style', '/'],
              hr: ['class', '/'],
              br: ['/'],
              'p,ol,ul,li,blockquote,h1,h2,h3,h4,h5,h6': ['align', 'style'],
              'tbody,tr,strong,b,sub,sup,em,i,u,strike': []
            }
          }
        },
        wellFormatMode: {
          type: Boolean,
          default: true
        },
        resizeType: {
          type: Number,
          default: 2
        },
        themeType: {
          type: String,
          default: 'default'
        },
        langType: {
          type: String,
          default: 'en'
        },
        designMode: {
          type: Boolean,
          default: true
        },
        fullscreenMode: {
          type: Boolean,
          default: false
        },
        basePath: {
          type: String
        },
        themesPath: {
          type: String
        },
        pluginsPath: {
          type: String,
          default: ''
        },
        langPath: {
          type: String
        },
        minChangeSize: {
          type: Number,
          default: 5
        },
        loadStyleMode: {
          type: Boolean,
          default: true
        },
        urlType: {
          type: String,
          default: ''
        },
        newlineTag: {
          type: String,
          default: 'p'
        },
        pasteType: {
          type: Number,
          default: 2
        },
        dialogAlignType: {
          type: String,
          default: 'page'
        },
        shadowMode: {
          type: Boolean,
          default: true
        },
        zIndex: {
          type: Number,
          default: 811213
        },
        useContextmenu: {
          type: Boolean,
          default: true
        },
        syncType: {
          type: String,
          default: 'form'
        },
        indentChar: {
          type: String,
          default: '\t'
        },
        cssPath: {
          type: [String, Array]
        },
        cssData: {
          type: String
        },
        bodyClass: {
          type: String,
          default: 'ke-content'
        },
        colorTable: {
          type: Array
        },
        afterCreate: {
          type: Function
        },
        afterChange: {
          type: Function
        },
        afterTab: {
          type: Function
        },
        afterFocus: {
          type: Function
        },
        afterBlur: {
          type: Function
        },
        afterUpload: {
          type: Function
        },
        uploadJson: {
          type: String
        },
        fileManagerJson: {
          type: Function
        },
        allowPreviewEmoticons: {
          type: Boolean,
          default: true
        },
        allowImageUpload: {
          type: Boolean,
          default: true
        },
        allowFlashUpload: {
          type: Boolean,
          default: true
        },
        allowMediaUpload: {
          type: Boolean,
          default: true
        },
        allowFileUpload: {
          type: Boolean,
          default: true
        },
        allowFileManager: {
          type: Boolean,
          default: false
        },
        fontSizeTable: {
          type: Array,
          default: function () {
            return ['9px', '10px', '12px', '14px', '16px', '18px', '24px', '32px']
          }
        },
        imageTabIndex: {
          type: Number,
          default: 0
        },
        formatUploadUrl: {
          type: Boolean,
          default: true
        },
        fullscreenShortcut: {
          type: Boolean,
          default: false
        },
        extraFileUploadParams: {
          type: Array,
          default: function () {
            return []
          }
        },
        filePostName: {
          type: String,
          default: 'imgFile'
        },
        fillDescAfterUploadImage: {
          type: Boolean,
          default: false
        },
        afterSelectFile: {
          type: Function
        },
        pagebreakHtml: {
          type: String,
          default: '
    '
    }, allowImageRemote: { type: Boolean, default: true }, autoHeightMode: { type: Boolean, default: false }, fixToolBar: { type: Boolean, default: false }, tabIndex: { type: Number } }, setup(props: any, { emit }: any) { const state = reactive({ editor: {} as any, outContent: props.modelValue }) watch(() => props.modelValue, (val: any) => { state.editor && val !== state.outContent && state.editor.html(val) }) watch(() => state.outContent, (val: any) => { emit('update:modelValue', val) }) onMounted(async() => { state.editor = (window as any).KindEditor.create('#' + props.id, { width: props.width, height: props.height, minWidth: props.minWidth, minHeight: props.minHeight, items: props.items, noDisableItems: props.noDisableItems, filterMode: props.filterMode, htmlTags: props.htmlTags, wellFormatMode: props.wellFormatMode, resizeType: props.resizeType, themeType: props.themeType, langType: props.langType, designMode: props.designMode, fullscreenMode: props.fullscreenMode, basePath: props.basePath, themesPath: props.cssPath, pluginsPath: props.pluginsPath, langPath: props.langPath, minChangeSize: props.minChangeSize, loadStyleMode: props.loadStyleMode, urlType: props.urlType, newlineTag: props.newlineTag, pasteType: props.pasteType, dialogAlignType: props.dialogAlignType, shadowMode: props.shadowMode, zIndex: props.zIndex, useContextmenu: props.useContextmenu, syncType: props.syncType, indentChar: props.indentChar, cssPath: props.cssPath, cssData: props.cssData, bodyClass: props.bodyClass, colorTable: props.colorTable, afterCreate: props.afterCreate, afterChange: function () { props.afterChange state.outContent = this.html() }, afterTab: props.afterTab, afterFocus: props.afterFocus, afterBlur: props.afterBlur, afterUpload: props.afterUpload, uploadJson: props.uploadJson, fileManagerJson: props.fileManagerJson, allowPreviewEmoticons: props.allowPreviewEmoticons, allowImageUpload: props.allowImageUpload, allowFlashUpload: props.allowFlashUpload, allowMediaUpload: props.allowMediaUpload, allowFileUpload: props.allowFileUpload, allowFileManager: props.allowFileManager, fontSizeTable: props.fontSizeTable, imageTabIndex: props.imageTabIndex, formatUploadUrl: props.formatUploadUrl, fullscreenShortcut: props.fullscreenShortcut, extraFileUploadParams: props.extraFileUploadParams, filePostName: props.filePostName, fillDescAfterUploadImage: props.fillDescAfterUploadImage, afterSelectFile: props.afterSelectFile, pagebreakHtml: props.pagebreakHtml, allowImageRemote: props.allowImageRemote, autoHeightMode: props.autoHeightMode, fixToolBar: props.fixToolBar, tabIndex: props.tabIndex }) }) return { state } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358

    二、配置的表单及使用

    • 定义表单配置类
    interface Rules {
      [key: string]: Array<Rule>;
    }
    interface Rule {
      required: boolean;
      message?: string;
      trigger: string;
      validator?: Function;
    }
    interface unKnow {
      [key: string]: any;
    }
    type DefaultFields = unKnow;
    interface FormData {
      [key: string]: FormDataInfo;
    }
    interface FormDataInfo {
      title?: string;
      field?: string;
      colSize: number;
      show: boolean;
      type: string;
      class?: Array<string>;
      zIndex?: number;
      property: FieldProperty;
    }
    type SelectList = unKnow;
    interface FieldProperty {
      disabled?: boolean;
      readonly?: boolean;
      placeholder?: string;
      maxlength?: number;
      label?: string;
      value?: string;
      filterable?: boolean;
      clearable?: boolean;
      data?: Array<SelectList>;
      multiple?: boolean;
      btnName?: string;
      activeColor?: string;
      inactiveColor?: string;
      img?: string;
      TitleT?: string;
      time?: number;
      type?: string | number;
      search?: boolean;
      del?: boolean;
      sameAsSupplier?: boolean;
      loading?: boolean;
      limit?: number;
      accept?: string;
    }
    class FormConfigurationExampleEntity {
      public formRules: Rules = {};
      public formFields: DefaultFields = {};
      public formData: FormData = {};
      constructor() {
        this.formFields = {
          Input: "",
          Select: "",
          Vhtml: "
    color
    "
    , Upload: "", Switch: "", Radio: "0", Checkbox: true, Date: "", TimePicker: "", Cascader: "", UEditor: "", KindEditor: "", }; this.formData = { Input: { type: "Input", colSize: 12, show: true, class: [], title: "Input", field: "Input", property: { type: "text", placeholder: "text", }, }, Reason: { type: "Select", colSize: 12, show: true, class: [], title: "Select", field: "Select", property: { data: [ { label: "请选择", value: "", }, { label: "Select", value: "Select", }, ], label: "label", value: "value", }, }, Vhtml: { type: "Vhtml", colSize: 12, show: true, class: [], title: "Vhtml", field: "Vhtml", property: {}, }, upload: { title: "upload:", field: "upload", type: "Upload", colSize: 12, show: true, property: { readonly: false, multiple: true, btnName: "Browse...", }, }, Switch: { title: "Switch:", field: "Switch", type: "Switch", colSize: 12, show: true, property: { readonly: false, multiple: true, btnName: "Browse...", }, }, Radio: { type: "Radio", colSize: 12, show: true, title: "Radio", field: "Radio", class: [], property: { data: [ { label: "男", value: "1" }, { label: "女", value: "0" }, ], }, }, Checkbox: { type: "Checkbox", colSize: 12, show: true, class: [], title: "Checkbox", field: "Checkbox", property: { label: "", }, }, Date: { type: "Date", colSize: 12, show: true, class: [], title: "Date", field: "Date", property: { placeholder: "Date", type: "daterange", }, }, TimePicker: { type: "TimePicker", colSize: 12, show: true, class: [], title: "TimePicker", field: "TimePicker", property: { placeholder: "TimePicker", }, }, Cascader: { type: "Cascader", colSize: 12, show: true, class: [], title: "Cascader", field: "Cascader", property: { data: [ { value: "CN", label: "中国", children: [ { value: "CN", label: "中国", children: [ { value: "CN", label: "中国", }, ], }, ], }, ], }, }, UEditor: { type: 'UEditor', colSize: 24, show: true, class: [], title: 'UEditor', field: 'UEditor', property: { placeholder: 'UEditor', } }, KindEditor: { type: 'KindEditor', colSize: 24, show: true, class: [], title: 'KindEditor', field: 'KindEditor', property: { placeholder: 'KindEditor', } }, }; } } export default FormConfigurationExampleEntity;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 引入上传配置数据并应用form组件
    <template>
      <FormList
        class="register-info-form"
        ref="FormListRef"
        :fields="formEntity.formFields"
        :formData="formEntity.formData"
        :rules="formEntity.formRules"
        labelWidth="120px"
      />
    template>
    
    <script lang="ts" src="./index.ts">script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    import { throttle } from "lodash-es";
    import { defineComponent, reactive, ref } from "vue";
    import FormConfigurationExampleEntity from "./field";
    import FormList from "../form.vue";
    
    export default defineComponent({
      components: {
        FormList,
      },
      setup() {
        const FormListRef = ref();
        const formEntity = reactive(new FormConfigurationExampleEntity());
        const validate = throttle(() => {
          FormListRef.value.$refs.formData.validate((valid: boolean) => {});
        }, 1500);
        return {
          formEntity,
          FormListRef,
          validate,
        };
      },
    });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    以上配置完即可显示配置好的表单啦~ ,更多详细细节后续有时间慢慢加上,然后再附上案例项目
    在这里插入图片描述


  • 相关阅读:
    96. 不同的二叉搜索树
    花6000报了测试培训班,3个月后我成功“骗”进了腾讯,月薪拿17k
    【LeetCode-简单】121. 买卖股票的最佳时机(详解)
    SQL语句
    OpenHarmony 设备环境查询:Environment
    2022杭电多校联赛第十场 题解
    【计算机网络】https的工作原理以及和http的区别
    技术管理进阶——管理者如何进行梯队设计及建设
    戏说领域驱动设计(廿三)——工厂
    算法 盛水最多容器-(反向双指针)
  • 原文地址:https://blog.csdn.net/weiCong_Ling/article/details/134326141