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

文章目录

  • 前言
  • 一、配置化的前提
  • 二、配置的相关组件
    • 1、新建form.vue组件
    • 2、新建input.vue组件
    • 3、新建select.vue组件
    • 4、新建v-html.vue组件
    • 5、新建upload.vue组件
    • 6、新建switch.vue组件
    • 7、新建radio.vue组件
    • 8、新建checkbox.vue组件
    • 9、新建date.vue组件
    • 10、新建time-picker.vue组件
    • 11、新建cascader.vue组件
    • 12、新建/ueditor/组件
    • 13、新建/kind-editor组件
  • 二、配置的表单及使用


前言

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

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

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

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

先展示实际效果图
基于element-plus定义表单配置化_第1张图片


一、配置化的前提

表单内一般包含

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

二、配置的相关组件

以下文件均建立在 @/componentsform-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>

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>

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]">
      
    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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

  • 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'
}

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>
  • 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
    }
  }
};
  • editor/index.vue
<template>
  <div class="kindeditor">
    <textarea :id="id" name="content">{{ state.outContent }}textarea>
  div>
template>
<script lang="ts" src="./index.ts" />
<style scoped>
.kindeditor {
  width: 100%;
}
style>
  • 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 } } }

二、配置的表单及使用

  • 定义表单配置类
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 CancalBookingEntity {
  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 CancalBookingEntity;
  • 引入上传配置数据并应用form组件
<FormList
   class="register-info-form"
   ref="FormList"
   :fields="businessInquiry.formFields"
   :formData="businessInquiry.formData"
   :rules="businessInquiry.formRules"
   labelWidth="120px"
   />
export default {
  setup() {
  	const FormListRef = ref()
    const businessInquiry =  reactive(new BusinessInquiry())
    const validate = throttle(() => {
      companyInfo.value.$refs.formData.validate((valid: boolean) => {
        if (valid) ...
      })
    }, 1500)
    return {
      businessInquiry,
      FormList,
      validate 
    }
  },
}

以上配置完即可显示配置好的表单啦~ ,更多详细细节后续有时间慢慢加上,然后再附上案例项目
基于element-plus定义表单配置化_第2张图片


你可能感兴趣的:(vue.js,javascript,前端)