vue3项目封装form表单

本文基于Element-plus实现二次封装表单组件。

我们都知道表单组件应该是后台管理系统中用得最多的组件,我们不可能每个业务都写一次表单,然后每一次修改都去各自业务中大幅修改,这样就可能导致代码重复率太高了,工作效率频频降低,所以我们需要封装起来,这样我们就可以复用,大大减少项目体积,方便项目的后期维护,提高前端娃的工作效率。由于我们使用表单都是直接使用UI组件库的组件,所以我们需要做二次封装,那么问题来了,二次封装表单组件我们需要考虑什么?

  • 特性复用:必须继承原有组件的所有特性。
  • 命名规范:二次组件名必须见名知意,我一般都是用FormPanel表单这个名。
  • 接口简单:自定义暴露出来的接口越简单越好。
  • 容易拓展:留有自定义插槽,让用户可以自己选择。
  • 功能完善:具备更完善的功能如:表单验证、动态删减表单,集成第三方的插件(富文本)…
  • 场景通用:具备多个场景使用,比如弹框嵌套表单、页面嵌套表单。

话不多说,直接展示代码,以下代码可以直接使用:

<template>
  <el-form
    :model="model"
    :inline="inline"
    :label-position="labelPosition"
    :label-width="labelWidth"
    :label-suffix="labelSuffix"
    :hide-required-asterisk="hideRequiredAsterisk"
    :require-asterisk-position="requireAsteriskPosition"
    :show-message="showMessage"
    :inline-message="inlineMessage"
    :status-icon="statusIcon"
    :validate-on-rule-change="validateOnRuleChange"
    :size="size"
    :disabled="disabled"
    :scroll-to-error="scrollToError"
    :scroll-into-view-options="scrollIntoViewOptions"
		ref="formPanelRef"
  >
		<el-row v-if="inline">
			<el-col
				:span="(item.cols && item.cols != '') ? Number(item.cols) : 24/ Number(cols)"
				v-for="(item, index) in formConfig"
        :key="index"
			>
				
				<el-form-item
					v-if="item.type === 'input' && item.type === 'textarea'"
					:label="item.label" 
          :prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<el-input
						type="textarea"
						:clearable="clearable" 
						:style="item.style ? item.style : ''" 
						v-model="model[item.fieldName]" 
						:placeholder="item.placeholder ? item.placeholder : `请输入`"
						:disabled="item.disabled ? item.disabled : false"
						:maxlength="item.maxlength ? item.maxlength : ''"
						:minlength="item.minlength ? item.minlength : ''"
						:show-word-limit="item.showWordLimit ? item.showWordLimit : false"
						:autosize="item.autosize ? item.autosize : false"
						:rows="item.rows ? item.rows : 1"
						@change="item.change"
					>
						<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}template>
						<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}template>
					el-input>
				el-form-item>
				<el-form-item
					v-else-if="item.type === 'input'"
					:label="item.label" 
          :prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<el-input
						:type="item.inputType ? item.inputType : 'text'"
						:clearable="clearable" 
						:style="item.style ? item.style : ''" 
						v-model.trim="model[item.fieldName]" 
						:placeholder="item.placeholder ? item.placeholder : `请输入`"
						:disabled="item.disabled ? item.disabled : false"
						:maxlength="item.maxlength ? item.maxlength : ''"
						:minlength="item.minlength ? item.minlength : ''"
						:show-word-limit="item.showWordLimit ? item.showWordLimit : false"
						:autosize="item.autosize ? item.autosize : false"
						:rows="item.rows ? item.rows : 1"
						@change="item.change"
					>
						<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}template>
						<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}template>
					el-input>
				el-form-item>
				
				<el-form-item 
          v-if="item.type === 'autocomplete'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
					<el-autocomplete
						:clearable="clearable" 
						:style="item.style ? item.style : ''" 
						v-model="model[item.fieldName]" 
						:disabled="item.disabled ? item.disabled : false"
						:placeholder="item.placeholder ? item.placeholder : `请输入`"
						:fetch-suggestions="item.suggestions"
						@select="item.select"
						@change="item.change"
						@keydown="inputKeydown($event, item.DisableInput)"
					>
						<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}template>
						<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}template>
					el-autocomplete>
				el-form-item>
				
				<el-form-item 
          v-if="item.type === 'inputNumber'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-input
						type="Number"
						:clearable="clearable" 
						:style="item.style ? item.style : ''" 
						v-model="model[item.fieldName]" 
						:disabled="item.disabled ? item.disabled : false"
						:placeholder="item.placeholder ? item.placeholder : `请输入`"
						@change="item.change"
						@keydown="inputKeydown($event, item.DisableInput)"
					>
						<template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}template>
						<template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}template>
					el-input>
        el-form-item>
				
					<el-form-item 
						v-if="item.type === 'inputNumberBtn'"
						:label="item.label" 
						:prop="item.fieldName"
						:rules="item.rules ? item.rules : []"
					>
						<el-input
							type="Number"
							:clearable="clearable" 
							:style="item.style ? item.style : ''" 
							v-model="model[item.fieldName]" 
							:disabled="item.disabled ? item.disabled : false"
							:placeholder="item.placeholder ? item.placeholder : `请输入`"
							@change="inputNumberBtnChange(item.fieldName,item.minNum,item.change);"
							ref="inputNumberBtnRef"
							@keydown="inputKeydown($event, item.DisableInput)"
						>
							<template  #prepend>
								<el-button :disabled="model[item.fieldName] <= 1 || model[item.fieldName] == '' || !model[item.fieldName] || model[item.fieldName] == undefined" style="width: 10px !important; min-width: 10px !important;" @click="inputNumberBtnReduce(item.fieldName,item.change)">-el-button>
							template>
							<template #append>
								<el-button style="width: 10px !important; min-width: 10px !important;" @click="inputNumberBtnAdd(item.fieldName, item.change)">+el-button>
							template>
						el-input>
					el-form-item>
				
				<el-form-item 
          v-if="item.type === 'inputrange'"
          :label="item.label" 
          :required="item.rules ? true : false"
        >
          <el-col :span="11">
            <el-form-item 
              :prop="item.fieldName[0]"
              :rules="item.rules ? item.rules : []"
            >
              <el-input
                @keydown="inputKeydown($event, item.DisableInput)"
                type="Number"
                :clearable="clearable" 
                :style="item.style ? item.style : ''" 
                v-model="model[item.fieldName[0]]" 
                :disabled="item.disabled ? item.disabled : false"
                @change="inputrangeStarChange($event, item.fieldName[0],item.fieldName[1])"
                :placeholder="item.placeholder[0] ? item.placeholder[0] : `请输入`">
              el-input>
            el-form-item>
          el-col>
          <el-col style="text-align: center;" :span="2">{{item.rangeSeparator}}el-col>
          <el-col :span="11">
            <el-form-item 
              :prop="item.fieldName[1]"
              :rules="item.rules ? item.rules : []"
            >
              <el-input
                @keydown="inputKeydown($event, item.DisableInput)"
                type="Number"
                :clearable="clearable" 
                :style="item.style ? item.style : ''" 
                v-model="model[item.fieldName[1]]" 
                :disabled="item.disabled ? item.disabled : false"
                @change="inputrangeEndChange($event, item.fieldName[0],item.fieldName[1])"
                :placeholder="item.placeholder[1] ? item.placeholder[1] : `请输入`">
              el-input>
            el-form-item>
          el-col>
        el-form-item>
				
				<el-form-item 
          v-if="item.type === 'select'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-select
						:filterable="item.filterable === undefined ? true : item.filterable"
            :style="item.style ? item.style : ''" 
            :clearable="clearable" 
            v-model="model[item.fieldName]" 
            :disabled="item.disabled ? item.disabled : false"
						:placeholder="item.placeholder ? item.placeholder : `请选择`"
						:multiple="item.multiple ? item.multiple : false"
					  :collapse-tags="item.collapseTags || false"
      			collapse-tags-tooltip
						@change="item.change"
          >
            <el-option
              v-for="(optionItem, optionIndex) in item.optionList"
              :key="optionIndex"
              :label="optionItem.label"
							:disabled="optionItem.disabled"
              :value="optionItem.value === undefined ? optionItem.label : optionItem.value">
            el-option>
          el-select>
        el-form-item>
				
        <el-form-item 
          v-if="item.type === 'date'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-date-picker
						v-model="model[item.fieldName]"
            :value-format="item.valueFormat ? item.valueFormat :'yyyy-MM-dd'"
            :format="item.valueFormat ? item.valueFormat :'yyyy-MM-dd'"
            :style="item.style ? item.style : ''" 
            :clearable="clearable"
            :disabled="item.disabled ? item.disabled : false"
						:disabled-date="item.disabledDate ? item.disabledDate : null"
            :type="item.dataType ? item.dataType : 'date'"
            :placeholder="item.placeholder ? item.placeholder : `请选择`"
            :start-placeholder="item.startPlaceholder ? item.startPlaceholder : `请选择`"
						:end-placeholder="item.endPlaceholder ? item.endPlaceholder : `请选择`"
            @change="item.change"
          >
          el-date-picker>
        el-form-item>
				
        <el-form-item 
          v-if="item.type === 'switch'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-switch
						v-model="model[item.fieldName]" 
						:disabled="item.disabled ? item.disabled : false"
						:loading="item.loading ? item.loading : false"
            :active-value="item.activeValue ? item.activeValue : true"
            :inactive-value="item.inactiveValue ? item.inactiveValue : false"
						:style="`--el-switch-on-color: ${item.activeColor}; --el-switch-off-color: ${item.inactiveColor}; --el-switch-border-color: ${item.borderColor}`"
            :placeholder="item.placeholder ? item.placeholder : `请输入`"
            :before-change="item.beforeChange ? item.beforeChange : undefined"
            @change="item.change"
          >
          el-switch>
        el-form-item>
				
        <el-form-item 
          v-if="item.type === 'radio'"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-radio-group 
            v-model="model[item.fieldName]"
						:disabled="item.disabled ? item.disabled : false"
						:border="item.border ? item.border : false"
            @change="item.change"
          >
            <el-radio 
              v-for="(optionItem, optionIndex) in item.optionList" 
              :label="JSON.stringify(optionItem)" 
              :key="optionIndex"
            >
              {{optionItem.label}}
            el-radio>
          el-radio-group>
        el-form-item>
				
        <el-form-item 
          v-if="item.type === 'checkbox' && model[item.fieldName]"
          :label="item.label" 
          :prop="item.fieldName"
          :rules="item.rules ? item.rules : []"
        >
          <el-checkbox-group 
            v-model="model[item.fieldName]"
            :disabled="item.disabled ? item.disabled : false"
						:min="item.min ? item.min : undefined"
						:max="item.max ? item.max : undefined"
						:text-color="item.textColor ? item.textColor : '#FFFFFF'"
						:fill="item.fill ? item.fill : '#3171F1'"
						@change="item.change"
          >
            <el-checkbox 
              v-for="(optionItem, optionIndex) in item.optionList" 
              :label="optionItem" 
              :key="optionIndex">
              {{optionItem.label}}
            el-checkbox>
          el-checkbox-group>
        el-form-item>
				
				<el-form-item 
					v-if="item.type === 'mapInput' && model[item.fieldName]"
					:label="item.label" 
					:prop="item.fieldName.address"
					:rules="item.rules ? item.rules : []"
				>
					<BaiduMap
						:placeholder="item.placeholder"
						:disabled="item.disabled ? item.disabled : false"
						:clearable="clearable"
						v-model:mapData="model[item.fieldName]"
					/>
				el-form-item>
				
				<el-form-item
					v-if="item.type === 'regionInput'"
					:label="item.label" 
          :prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<el-cascader
						v-model.trim="model[item.fieldName]" 
						:clearable="clearable" 
						:disabled="item.disabled ? item.disabled : false"
						:placeholder="item.placeholder ? item.placeholder : `请选择`"
						:options="regionList"
						separator="-"
						:props="{
							value: 'name',
							label: 'name',
							children: 'child'
						}"
						@change="item.change"
					/>
				el-form-item>
				
				<el-form-item
					v-if="item.type === 'upload'"
					:label="item.label" 
          :prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<UploadPanel
						:uploadType="item.uploadType"
						:maxLength="item.maxLength"
						:maxSize="item.maxSize"
						:disabled="item.disabled"
						:FileList="item.initValue"
						:dir="item.dir"
						:localFiles="item.localFiles"
						:project_id="item.project_id"
					/>
				el-form-item>
				
				<el-form-item
					v-if="item.type === 'selectProjectUser'"
					:label="item.label" 
					:prop="item.fieldName"
					:rules="item.rules ? item.rules : []"
				>
					<SelectProjectUser
						v-model:userList="model[item.fieldName]"
						:disabled="item.disabled"
						:placeholder="item.placeholder"
						:dialogTitle="item.dialogTitle"
						:separator="item.separator"
						:bottomCount="item.bottomCount"
						:change="item.change"
					/>
				el-form-item>
				
				<el-form-item 
					:label-width="item.labelWidth"
					v-if="item.type === 'show'"
					:label="item.label" 
					style="margin-bottom:10px"
					class="l_formPanel_show"
				>
					<div :style="`line-height: 20px; ${item.style}`" v-if="model[item.fieldName]" v-html="model[item.fieldName].replace(/\n/g,'
')"
>
div> el-form-item> el-col> <el-col :span="24/ Number(cols)"><slot name="after">slot>el-col> el-row> <template v-else> <el-row v-for="(item, index) in formConfig" :key="index" > <el-col :span="(item.cols && item.cols != '') ? Number(item.cols) : 24" > <el-form-item v-if="item.type === 'input' && item.inputType === 'textarea'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-input type="textarea" :clearable="clearable" :style="item.style ? item.style : ''" v-model="model[item.fieldName]" :placeholder="item.placeholder ? item.placeholder : `请输入`" :disabled="item.disabled ? item.disabled : false" :maxlength="item.maxlength ? item.maxlength : ''" :minlength="item.minlength ? item.minlength : ''" :show-word-limit="item.showWordLimit ? item.showWordLimit : false" :autosize="item.autosize ? item.autosize : false" :rows="item.rows ? item.rows : (item.inputType === 'textarea' ? 2 : 1)" @change="item.change" > <template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}template> <template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}template> el-input> el-form-item> <el-form-item v-else-if="item.type === 'input'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-input :type="item.inputType ? item.inputType : 'text'" :clearable="clearable" :style="item.style ? item.style : ''" v-model.trim="model[item.fieldName]" :placeholder="item.placeholder ? item.placeholder : `请输入`" :disabled="item.disabled ? item.disabled : false" :maxlength="item.maxlength ? item.maxlength : ''" :minlength="item.minlength ? item.minlength : ''" :show-word-limit="item.showWordLimit ? item.showWordLimit : false" :autosize="item.autosize ? item.autosize : false" :rows="item.rows ? item.rows : (item.inputType === 'textarea' ? 2 : 1)" @change="item.change" > <template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}template> <template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}template> el-input> el-form-item> <el-form-item v-if="item.type === 'autocomplete'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-autocomplete :clearable="clearable" :style="item.style ? item.style : ''" v-model.trim="model[item.fieldName]" :disabled="item.disabled ? item.disabled : false" :placeholder="item.placeholder ? item.placeholder : `请输入`" :fetch-suggestions="item.suggestions" :show-word-limit="item.showWordLimit ? item.showWordLimit : false" @select="item.select" @change="item.change" @keydown="inputKeydown($event, item.DisableInput)" > <template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}template> <template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}template> el-autocomplete> el-form-item> <el-form-item v-if="item.type === 'inputNumber'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-input type="Number" :clearable="clearable" :style="item.style ? item.style : ''" v-model="model[item.fieldName]" :disabled="item.disabled ? item.disabled : false" :placeholder="item.placeholder ? item.placeholder : `请输入`" @change="item.change" @keydown="inputKeydown($event, item.DisableInput)" > <template v-if="item.prefix && item.prefix != ''" #prefix>{{item.prefix}}template> <template v-if="item.suffix && item.suffix != ''" #suffix>{{item.suffix}}template> el-input> el-form-item> <el-form-item v-if="item.type === 'inputNumberBtn'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-input type="Number" :clearable="clearable" :style="item.style ? item.style : ''" v-model="model[item.fieldName]" :disabled="item.disabled ? item.disabled : false" :placeholder="item.placeholder ? item.placeholder : `请输入`" @change="inputNumberBtnChange(item.fieldName,item.minNum,item.change);" ref="inputNumberBtnRef" @keydown="inputKeydown($event, item.DisableInput)" > <template #prepend> <el-button :disabled="model[item.fieldName] <= 1 || model[item.fieldName] == '' || !model[item.fieldName] || model[item.fieldName] == undefined" style="width: 10px !important; min-width: 10px !important;" @click="inputNumberBtnReduce(item.fieldName,item.change)">-el-button> template> <template #append> <el-button style="width: 10px !important; min-width: 10px !important;" @click="inputNumberBtnAdd(item.fieldName, item.change)">+el-button> template> el-input> el-form-item> <el-form-item v-if="item.type === 'inputrange'" :label="item.label" :required="item.rules ? true : false" > <el-col :span="11"> <el-form-item :prop="item.fieldName[0]" :rules="item.rules ? item.rules : []" > <el-input @keydown="inputKeydown($event, item.DisableInput)" type="Number" :clearable="clearable" :style="item.style ? item.style : ''" v-model="model[item.fieldName[0]]" :disabled="item.disabled ? item.disabled : false" @change="inputrangeStarChange($event, item.fieldName[0],item.fieldName[1])" :placeholder="item.placeholder[0] ? item.placeholder[0] : `请输入`"> el-input> el-form-item> el-col> <el-col style="text-align: center;" :span="2">{{item.rangeSeparator}}el-col> <el-col :span="11"> <el-form-item :prop="item.fieldName[1]" :rules="item.rules ? item.rules : []" > <el-input @keydown="inputKeydown($event, item.DisableInput)" type="Number" :clearable="clearable" :style="item.style ? item.style : ''" v-model="model[item.fieldName[1]]" :disabled="item.disabled ? item.disabled : false" @change="inputrangeEndChange($event, item.fieldName[0],item.fieldName[1])" :placeholder="item.placeholder[1] ? item.placeholder[1] : `请输入`"> el-input> el-form-item> el-col> el-form-item> <el-form-item v-if="item.type === 'select'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-select :default-first-option="item.defaultFirstOption ? item.defaultFirstOption : false" :filterable="item.filterable === undefined ? true : item.filterable" :allow-create="item.allowcreate ? item.allowcreate : false" :style="item.style ? item.style : ''" :clearable="clearable" v-model="model[item.fieldName]" :disabled="item.disabled ? item.disabled : false" :placeholder="item.placeholder ? item.placeholder : `请选择`" :multiple="item.multiple ? item.multiple : false" :collapse-tags="item.collapseTags || false" collapse-tags-tooltip @change="item.change" @remove-tag="item.remove" > <el-option v-for="(optionItem, optionIndex) in item.optionList" :key="optionIndex" :label="optionItem.label" :disabled="optionItem.disabled" :value="optionItem.value === undefined ? optionItem.label : optionItem.value"> el-option> el-select> el-form-item> <el-form-item v-if="item.type === 'date'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-date-picker v-model="model[item.fieldName]" :value-format="item.valueFormat ? item.valueFormat :'yyyy-MM-dd'" :format="item.valueFormat ? item.valueFormat :'yyyy-MM-dd'" :style="item.style ? item.style : ''" :clearable="clearable" :disabled="item.disabled ? item.disabled : false" :disabled-date="item.disabledDate ? item.disabledDate : null" :type="item.dataType ? item.dataType : 'date'" :placeholder="item.placeholder ? item.placeholder : `请选择`" :start-placeholder="item.startPlaceholder ? item.startPlaceholder : `请选择`" :end-placeholder="item.endPlaceholder ? item.endPlaceholder : `请选择`" @change="item.change" > el-date-picker> el-form-item> <el-form-item v-if="item.type === 'switch'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-switch v-model="model[item.fieldName]" :disabled="item.disabled ? item.disabled : false" :loading="item.loading ? item.loading : false" :active-value="item.activeValue ? item.activeValue : true" :inactive-value="item.inactiveValue ? item.inactiveValue : false" :style="`--el-switch-on-color: ${item.activeColor}; --el-switch-off-color: ${item.inactiveColor}; --el-switch-border-color: ${item.borderColor}`" :placeholder="item.placeholder ? item.placeholder : `请输入`" :before-change="item.beforeChange ? item.beforeChange : undefined" @change="item.change" > el-switch> el-form-item> <el-form-item v-if="item.type === 'radio'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-radio-group v-model="model[item.fieldName]" :disabled="item.disabled ? item.disabled : false" :border="item.border ? item.border : false" @change="item.change" > <el-radio v-for="(optionItem, optionIndex) in item.optionList" :label="JSON.stringify(optionItem)" :key="optionIndex" > {{optionItem.label}} el-radio> el-radio-group> el-form-item> <el-form-item v-if="item.type === 'checkbox' && model[item.fieldName]" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-checkbox-group v-model="model[item.fieldName]" :disabled="item.disabled ? item.disabled : false" :min="item.min ? item.min : undefined" :max="item.max ? item.max : undefined" :text-color="item.textColor ? item.textColor : '#FFFFFF'" :fill="item.fill ? item.fill : '#3171F1'" @change="item.change" > <el-checkbox v-for="(optionItem, optionIndex) in item.optionList" :label="optionItem" :key="optionIndex"> {{optionItem.label}} el-checkbox> el-checkbox-group> el-form-item> <el-form-item v-if="item.type === 'mapInput' && model[item.fieldName]" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <BaiduMap :placeholder="placeholder" :disabled="item.disabled ? item.disabled : false" :clearable="clearable" v-model:mapData="model[item.fieldName]" /> el-form-item> <el-form-item v-if="item.type === 'regionInput'" :label="item.label" :prop="item.fieldName" :rules="item.rules ? item.rules : []" > <el-cascader v-model.trim="model[item.fieldName]" :clearable="clearable" :disabled="item.disabled ? item.disabled : false" :placeholder="item.placeholder ? item.placeholder : `请选择`" :options="regionList" separator="-" :props="{ value: 'name', label: 'name', children: 'child' }" @change="item.change" /> el-form-item> <el-form-item :label-width="item.labelWidth" v-if="item.type === 'show'" :label="item.label" style="margin-bottom:10px" class="l_formPanel_show" > <div :style="`line-height: 20px; ${item.style}`" v-if="model[item.fieldName]" v-html="model[item.fieldName].replace(/\n/g,'
')"
>
div> el-form-item> el-col> el-row> template> el-form> template> <script> import { reactive,toRefs,onMounted, getCurrentInstance, watch} from 'vue'; import region from '../../assets/json/region'; export default { name: '', props: { formConfig: { // 表单的渲染数据,必须 type: Array, default: ()=> { return []; }, required: true, }, // model: { // 表单数据对象 // type: Object, // default: ()=> { // return {}; // } // }, // rules: { // 表单验证规则 // type: Object, // default: ()=> { // return {}; // } // }, cols: { // 每一行显示数量,必须可以被24整除 type: [String,Number], default: 2 }, inline: { // 行内表单模式 type: Boolean, default: false }, labelPosition: { // 表单域标签的位置, 当设置为 left 或 right 时,则也需要设置 label-width 属性 type: String, default: 'right' }, labelWidth: { // 标签的长度,例如 '50px'。 作为 Form 直接子元素的 form-item 会继承该值。 可以使用 auto。 type: [String,Number], default: '100px' }, labelSuffix: { // 表单域标签的后缀 type: String, default: '' }, hideRequiredAsterisk: { // 是否隐藏必填字段标签旁边的红色星号。 type: Boolean, default: false }, requireAsteriskPosition: { // 星号的位置。'left' | 'right' type: String, default: 'left' }, showMessage: { // 是否显示校验错误信息 type: Boolean, default: true }, inlineMessage: { // 是否以行内形式展示校验信息 type: Boolean, default: false }, statusIcon: { // 是否在输入框中显示校验结果反馈图标 type: Boolean, default: false }, validateOnRuleChange: { // 是否在 rules 属性改变后立即触发一次验证 type: Boolean, default: true }, size: { // 用于控制该表单内组件的尺寸 '' | 'large' | 'default' | 'small' type: String, default: '' }, disabled: { // 是否禁用该表单内的所有组件。 如果设置为 true, 它将覆盖内部组件的 disabled 属性 type: Boolean, default: false }, scrollToError: { // 当校验失败时,滚动到第一个错误表单项 type: Boolean, default: true }, scrollIntoViewOptions: { //当校验有失败结果时,滚动到第一个失败的表单项目 object/ boolean type: [Object, Boolean], default: '' }, clearable: { // 是否可清空 type: Boolean, default: true }, }, components: { UploadPanel, SelectProjectUser }, setup(props) { const { proxy } = getCurrentInstance(); // 相当于之前的this const data = reactive({ model: {}, regionList: region, }); const createFormkey = (type)=> { let formKeyList = {}; props.formConfig.map(item => { if(typeof item.fieldName == 'string') { formKeyList[item.fieldName] = item.initValue === undefined ? '' : item.initValue; } else { // 处理范围输入的key formKeyList[item.fieldName[0]] = item.initValue[0] ? item.initValue[0] : ''; formKeyList[item.fieldName[1]] = item.initValue[1] ? item.initValue[1] : ''; } }); if(type && type == 'update'){ // 更新组件内容保留历史数据 for(let key in data.model){ props.formConfig.map(item => { if(item.fieldName == key){ formKeyList[key] = data.model[key]; } }) } } data.model = formKeyList; }; const inputKeydown = (e, DisableInput)=> { // 数字类型输入框禁止输入不合法字符 let key = e.key; let DisableInputStr = ''; if(DisableInput){ DisableInputStr = DisableInput; } else { DisableInputStr = 'e,+,-,Shift' } // console.log(key, '---key') if(DisableInputStr.indexOf(key) > -1) { e.returnValue = false; return false }; return true; }; /** * @description: 清除表单验证 * proxy.$refs.FormPanelRef.clearValidate(); * @return {*} * @author: */ const clearValidate = () => { return proxy.$refs.formPanelRef.clearValidate(); }; /** * @description: 清除指定数据,不穿清除全部 * proxy.$refs.FormPanelRef.clearKey(['keyName']); * @return {*} * @author: */ const clearKey = (key) => { if(key&&key.length>0){ props.formConfig.map(item => { key.map(keyItem => { if(item.fieldName == keyItem){ data.model[keyItem] = item.initValue; } }) }) } else { createFormkey(); } }; /** * @description: 清除数据和表单验证 * proxy.$refs.FormPanelRef.clear(); * @return {*} * @author: */ const clear = () => { clearValidate(); clearKey(); }; /** * @description: 设置数据 * proxy.$refs.FormPanelRef.set('key', 'value'); * @return {*} * @author: */ const set = (key, value) => { data.model[key] = value; }; /** * @description: 得到数据 * proxy.$refs.FormPanelRef.get('key'); * @return {*} * @author: */ const get = (key) => { return data.model[key]; }; /** * @description: 验证表单并输入结果通过 * proxy.$refs.FormPanelRef.FormPanelValidate().then((res)=> { console.log(res,) }) * @return {*} * @author: */ const validate = ()=> { // 验证结果并输出 return new Promise((resolve, reject) => { proxy.$refs.formPanelRef.validate((valid) => { if(valid) { let formData = JSON.parse(JSON.stringify(proxy.model)) resolve(formData); } else { return false; } }) }) }; const inputrangeStarChange = (e, skey, eKey)=> { // 范围查询输入框初始值 if(proxy.model[eKey] && Number(proxy.model[eKey]) <= Number(e)) { if(proxy.model[eKey] > 1) { proxy.model[skey] = Number(proxy.model[eKey]) - 1; } else { proxy.model[skey] = 0; } } }; const inputrangeEndChange = (e, skey, eKey)=> { // 范围查询输入框结束 if(proxy.model[skey] && Number(proxy.model[skey]) >= Number(e)) { proxy.model[eKey] = Number(proxy.model[skey]) + 1; } }; const inputNumberBtnChange = (key, minNum, change)=> { if(minNum && data.model[key] && data.model[key] != '' && data.model[key] != null && data.model[key] != undefined){ if(Number(data.model[key]) < Number(minNum)){ data.model[key] = Number(minNum) } }; change(data.model[key]); } const inputNumberBtnAdd = (key, change)=> { // 数字加 if(data.model[key] == '' || !data.model[key] || data.model[key] == null || data.model[key] == undefined){ data.model[key] = 1; } else { data.model[key] = Number(data.model[key]) + 1 }; change(data.model[key]); }; const inputNumberBtnReduce = (key,change)=> { // 数字减 data.model[key] = Number(data.model[key]) - 1; change(data.model[key]); }; onMounted(() => { createFormkey(); }); watch(props.formConfig,(newVal,oldVal)=>{ createFormkey('update'); }); return { ...toRefs(data), inputKeydown, clearValidate, validate, inputrangeStarChange, inputrangeEndChange, inputNumberBtnAdd, inputNumberBtnReduce, inputNumberBtnChange, clearKey, clear, set, get, }; } } script> <style lang='scss' scoped> :deep(.el-form-item){ width: 100%; .el-input{ width: 100% !important; } .el-select{ width: 100% !important; } .el-cascader{ width: 100% !important; } } :deep(.el-input__prefix){ margin-right: 5px; } :deep(.el-input__suffix){ margin-left: 5px; } style> <style> /* // 取消input的上下箭头 */ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button{ -webkit-appearance: none !important; margin: 0; } /* 火狐 */ input[type="number"]{ -moz-appearance: textfield; appearance: textfield; } style>

接下来我来告诉大家怎么使用:

#### FormPanel 组件调用

`组件参数API`

- formConfig{Array | 表单面板组件数据数组,支持动态赋值(数据数组必须是新的引用)}
- inline{Boolean | 是否启用行内表单, 默认值 false}
- cols{Number | 每行显示多小列,默认是 2,注意:只能是被 24 整除的值}
- labelWidth{Number | label 标签的宽度,默认是 120}

`formConfig 公用字段配置项`

- type{String | 类型,支持 input-输入框 / inputNumber-数字类型输入框 / inputrange-区间输入 / select-下拉框 / date-日期选择 / switch-开关 / radio-单选 / checkbox-多选框 / mapInput-地图 / regionInput-省市区选择 / upload-上传}
- label{String | 标题}
- fieldName{String | 字段名称}
- initValue{All | 初始值}
- placeholder{String | 提示文字}
- rules{Array | 验证提示文字}
- style{Object | 内容样式}
- disabled{Boolean | 是否禁用}
- change{Function | 触发函数}

`示例代码`

```bash
# template
<template>
  <FormPanel 
    :formConfig="formConfig" 
    ref="FormRef"
    :inline="true"
    :cols="4"
  />
</template>

# js
export default {
  data() {
    return {
      formConfig: this.createFormConfig(),
    };
  },
  methods: {
    createFormConfig() {
      return [
        {
          type: 'input', // 普通输入框
          inputType: 'text', // 类型, text/textarea
          label: '字符串:', // 标签文本
          cols: '', // 自定义列
          fieldName: 'stringName', // 字段名称
          placeholder: '请输入商品名称', // 提示文字
          initValue: '', // 默认值
          rules: [{ required: false, message: '请选择所属分类', trigger: 'blur' }],
          style: {}, // 自定义样式
          disabled: false, // 是否禁用 boolean
          maxlength: '', //最大输入长度 string/number
          minlength: '', // 最小输入长度 number
          showWordLimit: false, // 	是否显示统计字数, 只在 type 为 'text' 或 'textarea' 的时候生效 boolean
          rows: 2, // 输入框行数,仅 inputType 为 'textarea' 时有效 number
          autosize: false, // textarea 高度是否自适应,仅 type 为 'textarea' 时生效。 可以接受一个对象,比如: { minRows: 2, maxRows: 6 }
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'input', // 普通输入框
          inputType: 'textarea', // 类型, text/textarea
          label: '文本类型', // 标签文本
          cols: '', // 自定义列
          fieldName: 'textareaName', // 字段名称
          placeholder: '请输入商品名称', // 提示文字
          initValue: '', // 默认值
          rules: [{ required: false, message: '请选择所属分类', trigger: 'blur' }],
          style: {}, // 自定义样式
          disabled: false, // 是否禁用 boolean
          maxlength: '', //最大输入长度 string/number
          minlength: '', // 最小输入长度 number
          showWordLimit: false, // 	是否显示统计字数, 只在 type 为 'text' 或 'textarea' 的时候生效 boolean
          rows: 2, // 输入框行数,仅 inputType 为 'textarea' 时有效 number
          autosize: { // 默认使用, 优先级高于rows
            minRows: 1, maxRows: 2
          }, // textarea 高度是否自适应,仅 type 为 'textarea' 时生效。 可以接受一个对象,比如: { minRows: 2, maxRows: 6 }
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'inputNumber',
          label: '数字类型 :',
          fieldName: 'numeberName',
          placeholder: '请输入上门人数',
          initValue: '',
          prefix: '¥', // 插入左侧的内容
          suffix: '人', // 插入右侧的内容
          DisableInput: 'e,+,-,Shift,.', // 禁止输入的值,默认值e,+,-,Shift
          rules: [{ required: false, message: '请输入上门人数', trigger: 'blur' }],
          disabled: false, // 是否禁用
          maxlength: '', //最大输入长度
          minlength: '', // 最小输入长度
          showWordLimit: false, // 	是否显示统计字数, 只在 type 为 'text' 或 'textarea' 的时候生效
          rows: 2, // 输入框行数,仅 inputType 为 'textarea' 时有效
          autosize: false, // textarea 高度是否自适应,仅 type 为 'textarea' 时生效。 可以接受一个对象,比如: { minRows: 2, maxRows: 6 }
          style: {
            
          },
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
        type: 'inputNumberBtn', // 加减号input
        label: '持续时长:',
        fieldName: 'planed_duration',
        placeholder: '请输入持续时长',
        initValue: '',
        DisableInput: 'e,+,-,Shift,.',
        style: 'width: 220px !important;',
        disabled: false, // 是否禁用
        rules: [{ required: true, message: '请选择持续时长', trigger: 'blur' }],
      },
        {
          type: 'inputrange',
          label: '区间输入',
          fieldName: ['min_price', 'max_price'],
          placeholder: ['请输入', '请输入'],
          initValue: ['', ''],
          rangeSeparator: '至',
          rules: [{ required: false, message: '请输入', trigger: 'blur' }],
          disabled: false, // 是否禁用
          maxlength: '', //最大输入长度
          minlength: '', // 最小输入长度
          showWordLimit: false, // 	是否显示统计字数, 只在 type 为 'text' 或 'textarea' 的时候生效
          rows: 2, // 输入框行数,仅 inputType 为 'textarea' 时有效
          autosize: false, // textarea 高度是否自适应,仅 type 为 'textarea' 时生效。 可以接受一个对象,比如: { minRows: 2, maxRows: 6 }
        },
        {
          type: 'select',
          label: '下拉框:',
          fieldName: 'selectName',
          placeholder: '请选中所属分类',
          initValue: '',
          optionList: [{ label: '全部', value: '1' }, { label: '待确认', value: '2' }],
          multiple: false, // 是否多选, 多选时initValue类型是数组
          collapseTags: true, // 多选时是否合并文字,默认合并
          allowcreate: true, //是否创建新的条目
          defaultFirstOption: true, //是否默认第一选项,此字段为true时,按下回车就可以选中当前选项列表中第一个选项
          style: {width: '200px'},
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'date',
          label: '日期选择:',
          fieldName: 'dateName',
          placeholder: '请选择日期',
          startPlaceholder: '', // 范围选择时开始日期的占位内容
          endPlaceholder: '', // 范围选择时结束日期的占位内容
          initValue: '',
          dataType: 'monthrange', // 	year(年-YYYY)/month(月-YYYY-MM)/date(日-YYYY-MM-DD)/dates(多日期选择)/datetime(日期时间-YYYY-MM-DD hh:mm:ss)/ week(周-ww 周)/datetimerange(日期时间段-YYYY-MM-DD hh:mm:ss)/daterange(日期段-YYYY-MM-DD)/ monthrange(月份范围-YYYY-MM)
          valueFormat: 'YYYY-MM', // 日期格式默认年月日
          disabled: false, // 是否禁用

          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'switch',
          label: '开关:',
          fieldName: 'switchName',
          initValue: '0',
          placeholder: '',
          disabled: false, // 是否禁用
          loading: false, // 是否显示loading
          activeValue: '1', // 打开时的值
          inactiveValue: '0', // 关闭时的值
          activeColor: '#3171F1', // 打开时的颜色
          inactiveColor: '#C0CCDA', // 关闭时的颜色
          borderColor: '', // 开关边框颜色
          validateEvent: true, // 切换时是否触发表单校验
          beforeChange: () => { // switch 状态改变前的钩子, 返回 false 或者返回 Promise 且被 reject 则停止切换
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                return resolve(true)
                return reject(new Error('Error'))
              }, 1000)
            })
          },
          change: (val) => { // 	switch 状态发生变化时的回调函数
            console.log(val, 'val--------')
          }
        },
        {
          type: 'radio',
          label: '单选框:',
          fieldName: 'radioName',
          initValue: '',
          disabled: false, // 是否禁用
          border: false, // 是否显示边框
          optionList: [{ label: '全部', value: '1' }, { label: '待确认', value: '2' }], // 选项数组,返回值为整个对象
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'checkbox',
          label: '多选框:',
          fieldName: 'checkboxName',
          placeholder: '请选择日期',
          initValue: [],
          disabled: false, // 是否禁用
          min: undefined, // 可被勾选的 checkbox 的最小数量
          max: 999, // 可被勾选的 checkbox 的最大数量
          textColor: '#FFFFFF', // 当按钮为活跃状态时的字体颜色
          fill: '#3171F1', // 当按钮为活跃状态时的边框和背景颜色
          optionList: [{ label: '全部', value: '1' }, { label: '待确认', value: '2' }],
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'mapInput',
          label: '地图',
          fieldName: 'mapInputName',
          placeholder: '请选择定位',
          initValue: {
            province: '', // 省
            city: '', // 市
            area: '', // 区
            address: '', // 详细地址
            lng: 0,  // 定位横坐标
            lat: 0, // 定位纵坐标
          },
          disabled: false, // 是否禁用
          rules: [{ required: true, message: '请选择定位', trigger: 'blur' }],
        },
        {
          type: 'regionInput', // 普通输入框
          label: '省市区:', // 标签文本
          cols: '', // 自定义列
          fieldName: 'regionInputName', // 字段名称
          placeholder: '请选择', // 提示文字
          initValue: '', // 默认值
          rules: [{ required: false, message: '请选择所', trigger: 'blur' }],
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        {
          type: 'upload', // 普通输入框
          label: '上传', // 标签文本
          cols: '', // 自定义列
          fieldName: 'uploadName', // 字段名称
          initValue: [], // 默认值
          uploadType: 'uploadFile',// 组件的类型, 支持 uploadFile(上传图频文件),uploadImg(上传图片)showFile(仅展示)  默认值: uploadFile}
          maxLength: 10, // 上传数量,默认值10
          maxSize: 1000, // 默认1000M
          dir: '', // 存储文件的目录,后端提供
          disabled: false,
          rules: [{ type: 'array', required: true, message: '请上传', trigger: 'change' }],
          change: (val) => {
            console.log(val, 'val--------')
          }
        },
        { // 选择项目人员
          type: 'selectProjectUser',
          label: '审批人:', // 标签文本
          fieldName: 'user_data', // 字段名称
          placeholder: '请选择', // 提示文字
          dialogTitle: '选择审批人', // 弹窗名称
          separator: '>', // 分隔符
          bottomCount: true, // 启用底部计数
          initValue: [], // 默认值
        },
        {
          type: 'show',
          label: '仅展示:',
          fieldName: 'show',
          initValue: '展示的内容'
        },
      ]
    },
  }
};

你可能感兴趣的:(Element-UI,Plus,Vue3.0,vue.js)