element-plus form表單的二次封裝

1. 首先,我們需要在components新建一個(gè)文件夾CustomForm,然后新建一個(gè)index.vue的文件
// element-plus form表單的二次封裝 自定義form表單
<template>
  <el-form :model="model" v-bind="_options" ref="formRef">
    <template v-for="(item, index) in fieldList" :key="index">
      <!-- 單選框 -->
      <el-form-item :label="item.label" v-if="item.type === 'radio'" :rules="item.rules" :prop="[item.field]">
        <el-radio-group v-model="model[item.field]" :disabled="item.disabled">
          <el-radio :label="val[item.options?.valueKey || 'value']" size="large" v-for="val in item.options?.data"
            :key="val[item.options?.valueKey || 'value']">
            {{ val[item.options?.labelkey || 'label'] }}
          </el-radio>
        </el-radio-group>
      </el-form-item>
      <!-- 復(fù)選框 -->
      <el-form-item :label="item.label" v-else-if="item.type === 'checkbox'" :rules="item.rules" :prop="[item.field]">
        <el-checkbox-group v-model="model[item.field]" :disabled="item.disabled">
          <el-checkbox v-for="c in item.options?.data" :key="c[item.options?.valueKey || 'value']"
            :label="c[item.options?.valueKey || 'value']">{{ c[item.options?.labelkey || 'label'] }}</el-checkbox>
        </el-checkbox-group>
      </el-form-item>
      <!-- 下拉框 -->
      <el-form-item :label="item.label" v-else-if="item.type === 'select'" :rules="item.rules" :prop="[item.field]">
        <el-select v-model="model[item.field]" :placeholder="item.options?.placeholder || '請選擇'" clearable>
          <el-option v-for="s in item.options?.data" :key="s[item.options?.valueKey || 'value']"
            :label="s[item.options?.labelkey || 'label']" :value="s[item.options?.valueKey || 'value']" />
        </el-select>
      </el-form-item>
      <!-- 默認(rèn)輸入框 -->
      <el-form-item :label="item.label" :rules="item.rules" :prop="[item.field]" v-else>
        <el-input v-model="model[item.field]" :readonly="item.readonly" :type="item.type ?? 'text'"
          :placeholder="item.label" :disabled="item.disabled" />
      </el-form-item>
    </template>
    <el-form-item>
      <slot name="buttons" :model="model" :formRef="formRef">
        <el-button type="primary" @click="onSubmit(formRef)">{{ _options.submitButtonText }}</el-button>
        <el-button v-if="_options.showResetButton" type="info" @click="resetForm(formRef)">
          {{ _options.resetButtonText }}
        </el-button>
        <el-button v-if="_options.showCancelButton" @click="emit('cancel')">
          {{ _options.cancelButtonText }}
        </el-button>
      </slot>
    </el-form-item>
  </el-form>
</template>
<script lang="ts" setup>
import type { FormInstance } from "element-plus";
import { ComputedRef, ref, computed } from "vue";
// 父組件傳遞的值
interface Props {
  fieldList: Form.FieldItem[];
  model?: Record<string, any>;
  options?: Form.Options;
}
// 表單的數(shù)據(jù)
const model = ref<Record<string, any>>({});
const formRef = ref<FormInstance>();
const props = defineProps<Props>();
// 設(shè)置option默認(rèn)值,如果傳入自定義的配置則合并option配置項(xiàng)
const _options: ComputedRef<Form.Options> = computed(() => {
  const option = {
    labelWidth: 120,
    labelPosition: "right",
    disabled: false,
    submitButtonText: "提交",
    resetButtonText: "重置",
    cancelButtonText: "取消",
    showResetButton: false,
    showCancelButton: false,
  };
  return Object.assign(option, props?.options);
});
interface EmitEvent {
  (e: "submit", params: any): void;
  (e: "reset"): void;
  (e: "cancel"): void;
}
const emit = defineEmits<EmitEvent>();
defineExpose({
  formRef,
});
// 根據(jù)fieldList初始化model, 如果model有傳值就用傳遞的model數(shù)據(jù)模型,否則就給上面聲明的model設(shè)置相應(yīng)的(key,value) [item.field], item.value是表單的默認(rèn)值(選填)
props.fieldList.map((item: Form.FieldItem) => {
  // 如果類型為checkbox,默認(rèn)值需要設(shè)置一個(gè)空數(shù)組
  const value = item.type === "checkbox" ? [] : "";
  props.model
    ? (model.value = props.model)
    : (model.value[item.field] = item.value || value);
});
// 提交按鈕
const onSubmit = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.validate((valid) => {
    if (valid) {
      emit("submit", model);
    } else {
      return false;
    }
  });
};
// 重置按鈕
const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.resetFields();
};
</script>
<style lang="less" scoped></style>

寫完Form組件的代碼后,會報(bào)紅線,F(xiàn)orm.XXXXX 找不到,這個(gè)是Form表單的全局類型聲明。

聲明文件在下方,直接復(fù)制進(jìn)項(xiàng)目中, 紅色警告自然消失。

聲明文件可以直接放在src下即可。(因?yàn)楹罄m(xù)我們項(xiàng)目可能需要二次封裝多個(gè)組件,例如table, pagination, date-picker等,所以在此我們新建一個(gè)type文件夾,里面再創(chuàng)建各個(gè)組件的聲明文件)

// src/type/form/index.d.ts
declare namespace Form {
  type ItemType = 'password' | 'text' | 'textarea' | 'radio' | 'checkbox' | 'select'
  // 當(dāng)FiledItem的type === 'radio' | 'checkbox'時(shí),options的參數(shù)類型
  interface IFieldOptions {
    labelkey?: string,
    valueKey?: string,
    placeholder?: string,
    data: Recode<string, any>[]
  }
  interface Options {
    labelWidth?: string | number,
    labelPosition?: 'left' | 'right' | 'top',
    disabled?: boolean,
    size?: 'large' | 'small' | 'default',
    showResetButton?: boolean, // 是否展示重置按鈕
    showCancelButton?: boolean, // 是否展示取消按鈕
    submitButtonText?: string,
    resetButtonText?: string,
    cancelButtonText?: string
  }
  interface FieldItem {
    label: string,
    field: string,
    type?: ItemType,
    value?: any,
    placeholder?: string,
    disabled?: boolean,
    readonly?: boolean,
    options?: IFieldOptions,
    rules?: RuleItem[]
  }
  interface RuleItem {
    type?: RuleType;
    required?: boolean;
    pattern?: RegExp | string;
    min?: number;
    max?: number;
    len?: number;
    enum?: Array<string | number | boolean | null | undefined>;
    whitespace?: boolean;
    fields?: Record<string, Rule>;
    options?: ValidateOption;
    defaultField?: Rule;
    transform?: (value: Value) => Value;
    message?: string | ((a?: string) => string);
    asyncValidator?: (rule: InternalRuleItem, value: Value, callback: (error?: string | Error) => void, source: Values, options: ValidateOption) => void | Promise<void>;
    validator?: (rule: InternalRuleItem, value: Value, callback: (error?: string | Error) => void, source: Values, options: ValidateOption) => SyncValidateResult | void;
    trigger?: 'blur' | 'change'
  }
}
2. 然后我們需要配置form的基本信息(基本表單信息,驗(yàn)證規(guī)則,Options等)
// 自定義驗(yàn)證郵箱方法
const checkEmail = (rule: any, value: any, callback: any) => {
  if (!value) callback(new Error('Please input the email'))
  const regExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(com|cn|net)$/
  regExp.test(value) ? callback() : callback(new Error('Please input the correct email address'))
}
// // 自定義驗(yàn)證表單配置數(shù)據(jù)
// export const validationFormFieldList = [
//     { label: '姓名', field: 'name', rules: [{ required: true, message: 'name is required' }] },
//     { label: '郵箱', field: 'email', rules: [{ required: true, validator: checkEmail }] },
// ] as Form.FieldItem[]

// 表單配置示例
export const exampleForm = {
  base: [
    { label: '姓名', field: 'name', disabled: false },
    { label: '性別', field: 'gender', type: 'radio', options: { data: [{ label: '男', value: 1 }, { label: '女', value: 0 }] } },
    {
      label: '愛好',
      field: 'hobbies', type: 'checkbox',
      options: {
        data: [
          { label: '吃飯', value: 1 },
          { label: '睡覺', value: 2 },
          { label: '寫代碼', value: 3 }
        ]
      }
    },
    {
      label: '工作', field: 'job', type: 'select',
      options: {
        data: [{ label: '吃飯', value: 1 }, { label: '睡覺', value: 2 }, { label: '寫代碼', value: 3 }]
      }
    },
    { label: '密碼', field: 'password', type: 'password', placeholder: '這是一個(gè)密碼輸入框' },
    { label: '只讀', field: 'readonly', readonly: true, placeholder: '這是一個(gè)只讀輸入框' },
    { label: '留言板', field: 'summary', type: 'textarea', placeholder: '留言板' },
  ],
  customkeyForm: [
    { label: '標(biāo)題', field: 'name' },
    { label: '性別', field: 'gender', type: 'radio', options: { labelkey: 'title', valueKey: 'val', data: [{ title: '男', val: 1 }, { title: '女', val: 0 }] } },
  ],
  ruleForm: [
    { label: '姓名', field: 'name', rules: [{ required: true, message: 'name is required' }] },
    { label: '郵箱', field: 'email', rules: [{ required: true, validator: checkEmail }] },
  ]
} as Record<string, Form.FieldItem[]>

export const Options = {
  // 自定義form1表單
  form1: {
    showResetButton: true,
    showCancelButton: false,
    resetButtonText: "重置1212",
  }
}

3. 接下來,我們就到了使用環(huán)節(jié)
// src/views/form/index.vue
<template>
    <el-card class="mb-5">
        <template #header> 基本表單 </template>
        <custom-form :fieldList="fieldList" :model="model" @submit="handleBaseSubmit">
            <!-- 如果不使用默認(rèn)的按鈕可以使用插槽自定義內(nèi)容, 插槽返回的model就是當(dāng)前表單的數(shù)據(jù) -->
            <!-- <template #buttons="{ model }">
                    <el-button">提交</el-button>
                </template> -->
        </custom-form>
    </el-card>
</template>
<script lang="ts" setup>
    import { exampleForm } from '@/config/form' 
    import { ref } from 'vue'
    // 本項(xiàng)目EasyForm組件自動引入,如復(fù)制此代碼,需根據(jù)路徑引入Form組件后使用
    const fieldList: Form.FieldItem[] = exampleForm.base
    const model = ref<Record<string, any>>({
        name: '張三',
        gender: 1,
        hobbies: [1],
        job: 3,
        readonly: '只讀輸入框',
        summary: '尤雨溪懂個(gè)錘子vue是什么梗'
    })
    /**
     * 注意: model數(shù)據(jù)模型非必填項(xiàng),如果僅僅是用于數(shù)據(jù)收集,model參數(shù)可以不用填,表單的submit事件會返回所有搜集的數(shù)據(jù)對象
     *       如果是編輯的情況下,頁面需要回顯數(shù)據(jù),則model數(shù)據(jù)模型必須要填寫
     */
    const handleBaseSubmit = (model: Record<string, any>) => {
        console.log(model.value)
    }
</script>
<style lang="less" scoped></style>

此時(shí)運(yùn)行項(xiàng)目,我們可以得到的界面

基礎(chǔ)表單
image.png

自定義key

// src/views/form/index.vue
<template>
    <el-card class="mb-5">
        <template #header> 自定義key </template>
        <custom-form :fieldList="customKeyFieldList" :model="model2" />
    </el-card>
</template>
<script lang="ts" setup>
    import { exampleForm } from '@/config/form'
    import { ref } from 'vue'
    // import EasyForm from '@/components/EasyForm/index.vue'
    // 本項(xiàng)目EasyForm組件自動引入,如復(fù)制此代碼,需根據(jù)路徑引入Form組件后使用
    const customKeyFieldList: Form.FieldItem[] = exampleForm.customkeyForm
    const model2 = ref<Record<string, any>>({
        name: '自定義key',
        gender: 1
    })
    /**
     * 注意: 如果使用到checkbox,radio,或者select等組件,需要傳入組件額外需要的數(shù)據(jù),本組件默認(rèn)設(shè)定的讀取數(shù)據(jù)的字段是 label, value
     *       可參考下方聲明文件 FiledItem options的參數(shù)類型描述
     *       比如,當(dāng)前傳入的data數(shù)據(jù)字段名和label、value不匹配,可使用預(yù)留的參數(shù) labelkey, valueKey指定字段名
     *         customkeyForm: [
                    { label: '標(biāo)題', field: 'name' },
                    { label: '性別', field: 'gender', type: 'radio', options: { labelkey: 'title', valueKey: 'val', data: [{ title: '男', val: 1 }, { title: '女', val: 0 }] } },
                ],
     */
    const handleBaseSubmit = (model: Record<string, any>) => {
        console.log(model.value)
    }
</script>
<style lang="less" scoped></style>

界面效果如下
image.png

自定義表單驗(yàn)證

// src/views/form/index.vue
<template>
    <el-card class="mb-5">
        <template #header> 自定義驗(yàn)證的表單 (使用slot自定義按鈕) </template>
        <custom-form :fieldList="ruleFieldList">
            <!-- 如果不使用默認(rèn)的按鈕可以使用插槽自定義內(nèi)容, 插槽返回的model就是當(dāng)前表單的數(shù)據(jù), formRef是當(dāng)前表單的FormInstance -->
            <template #buttons="{ model, formRef }">
                <el-button @click="handleSubmit(model, formRef)">保存</el-button>
            </template>
        </custom-form>
    </el-card>
</template>
<script lang="ts" setup>
    import type { FormInstance } from 'element-plus'
    import { exampleForm } from '@/config/form' 
    // import EasyForm from '@/components/EasyForm/index.vue'
    // 本項(xiàng)目EasyForm組件自動引入,如復(fù)制此代碼,需根據(jù)路徑引入Form組件后使用
    const ruleFieldList: Form.FieldItem[] = exampleForm.ruleForm
    /**
     *  如果用到了表單驗(yàn)證,又使用slot自定義按鈕的話,需要自行實(shí)現(xiàn)驗(yàn)證邏輯
     *  組件內(nèi)部已經(jīng)集成驗(yàn)證,及重置邏輯。表單驗(yàn)證建議使用內(nèi)置的提交按鈕。當(dāng)通過驗(yàn)證規(guī)則,內(nèi)置提交按鈕才會出發(fā)submit事件
     */
    // 下方是使用slot自定義按鈕,需要自己實(shí)現(xiàn)驗(yàn)證邏輯
    const handleSubmit = (model: any, formEl: FormInstance | undefined) => {
        if (!formEl) return
        formEl.validate((valid) => {
            if (valid) {
                console.log('submit!', model)
            } else {
                console.log('error submit!')
                return false
            }
        })
    }
</script>
<style lang="less" scoped></style>

頁面效果如下
image.png

4. 如果我們需要根據(jù)不同表單,展示不一樣的效果,我們可以通過options去設(shè)置

比如,由于我們在customForm中,默認(rèn)是不展示重置和取消按鈕的

// 改變這兩個(gè)值的屬性,可顯示隱藏按鈕
showResetButton: false,
showCancelButton: false,

從下面參數(shù)介紹,我們可以看到options是一個(gè)對象,所以我們可以這樣寫

<template>
  <div class="home">
    <el-card class="mb-5">
      <template #header> 基本表單 </template>
      <custom-form :fieldList="ruleFieldList" :options="options" @submit="handleSubmit">
        <!-- 如果不使用默認(rèn)的按鈕可以使用插槽自定義內(nèi)容, 插槽返回的model就是當(dāng)前表單的數(shù)據(jù) -->
        <!-- <template #buttons="{ model, formRef }">
          <el-button @click="handleSubmit(model, formRef)">保存</el-button>
        </template> -->
      </custom-form>
    </el-card>
  </div>
</template>

<script lang="ts" setup>
import type { FormInstance } from "element-plus";
import { exampleForm, Options } from "@/config/form";
import { ref } from "vue";
// 本項(xiàng)目EasyForm組件自動引入,如復(fù)制此代碼,需根據(jù)路徑引入Form組件后使用
const options: Form.Options = Options.form1;
const ruleFieldList: Form.FieldItem[] = exampleForm.ruleForm;
console.log("options", options);
/**
 * 注意: model數(shù)據(jù)模型非必填項(xiàng),如果僅僅是用于數(shù)據(jù)收集,model參數(shù)可以不用填,表單的submit事件會返回所有搜集的數(shù)據(jù)對象
 *       如果是編輯的情況下,頁面需要回顯數(shù)據(jù),則model數(shù)據(jù)模型必須要填寫
 */
const handleSubmit = (model: Record<string, any>) => {
  console.log(model.value)
}
</script>

頁面效果如下
image.png

參數(shù)介紹

Form 屬性

參數(shù) 說明 類型 是否必填 默認(rèn)值
model 表單數(shù)據(jù)對象 Record<string, any>
options 自定義配置 object
fieldList formItem 配置數(shù)組 Array<object>

Options 配置項(xiàng)

參數(shù) 說明 類型 是否必填 默認(rèn)值
labelWidth 標(biāo)簽的長度,例如 ‘50px’。 作為 Form 直接子元素的 form-item 會繼承該值。 可以使用 auto。 string / number
labelPosition 表單域標(biāo)簽的位置, 當(dāng)設(shè)置為 left 或 right 時(shí),則也需要設(shè)置 label-width 屬性‘left’ / ‘right’ / ‘top’ right
size 用于控制該表單內(nèi)組件的尺寸 large / default /small
disabled 是否禁用該表單內(nèi)的所有組件。 如果設(shè)置為 true, 它將覆蓋內(nèi)部組件的 disabled 屬性。 boolean false
submitButtonText 提交按鈕默認(rèn)顯示的文本內(nèi)容 string 提交
resetButtonText 重置按鈕默認(rèn)顯示的文本內(nèi)容 string 重置
cancelButtonText 取消按鈕默認(rèn)顯示的文本內(nèi)容 string 取消
showResetButton 是否顯示重置按鈕 boolean
showCancelButton 是否顯示取消按鈕 boolean

fieldItem 配置項(xiàng)

參數(shù) 說明 類型 是否必填 默認(rèn)值
field model 的鍵名 string
label 標(biāo)簽文本 string
type 當(dāng)前 fieldItem 的類型 ‘password’ / ‘text’ / ‘textarea’ / ‘radio’ / ‘checkbox’ / ‘select’ text
value 默認(rèn)顯示的值 any
placeholder 輸入框占位文本 string
disabled 是否禁用 boolean false
options 如果 type=‘checkbox’ / ‘radio’ / 'select’時(shí),需傳入此配置項(xiàng)。格式參考 fieldItem options 配置項(xiàng) object -
rules 表單驗(yàn)證規(guī)則。格式參考element-plus form 表單 或者參數(shù)類型聲明 Array<RuleItem> -

fieldItem options 配置項(xiàng)

參數(shù) 說明 類型 是否必填 默認(rèn)值
labelkey label 自定義字段名 string label
value value 自定義字段名 string value
placeholder 當(dāng) fieldItem type= 'select’時(shí),選擇框的提示語 string -
data type=‘checkbox’ / ‘radio’ / 'select’時(shí), 需要的數(shù)據(jù) Array<object> -

Form 插槽

插槽名 說明 插槽作用域
buttons 自定義按鈕區(qū)域的內(nèi)容 { model, formRef }

Form 事件

事件名 說明 回調(diào)參數(shù)
submit 點(diǎn)擊默認(rèn)的提交按鈕觸發(fā) model
cancel 點(diǎn)擊取消按鈕觸發(fā) -
reset 重置該表單項(xiàng),將其值重置為初始值,并移除校驗(yàn)結(jié)果 -
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容