一. 整體說明
1. 整體規划
首先利用el-form組件,封裝一個 ypf-form組件,可以實現通過傳入配置,顯示各種Form表單。
然后封裝 page-search 搜索框組件,該組件基於 YpfForm
然后封裝 page-Modal 彈框組件,該組件基於 YpfForm
各組件的調用次序如下:
ypf-form:表單組件 → page-search:搜索框組件 → user.vue:用戶管理頁面 → search.config.ts 搜索框的配置文件
ypf-form: 表單組件 → page-modal:彈框組件 → user.vue:用戶管理頁面 → modal.config.ts 彈框的配置文件
2. 組件介紹
(1). ypf-form
form表單組件,主要支持 'input' | 'password' | 'select' | 'datepicker' 四種表單組件。
(2). page-search
頁面搜索框組件, 處理表格的搜索業務
(3). page-modal
頁面彈框組件 ,主要用來處理新增 和 編輯 等彈框邏輯
二. Form組件封裝
1. 封裝思路
(1). 首先該組件分為三部分,頂部是name為header的插槽,底部是name為footer的插槽,中間部分是利用el-form組成各種表單元素。(一般頂部header插槽調用者用來存放標題之類,footer插槽調用者用來存放按鈕之類)
(2). 該組件接收的樣式和布局方面的參數有:
A. labelWidth : 表單域的寬度,綁定在最外層el-form上
B. colLayout:表單的響應式布局
C. itemStyle:表單子元素的樣式,綁定在el-form-item上的style屬性里
(3). 該組件接收的表單類別和表單屬性的參數為:formItems,詳細參數如下, 具體分析:fieId、type、label、placeholder為表單通用元素,rules為表單驗證規則,一些特有的屬性通過otherOptions配置,通過isHidden配合v-if控制是否顯示。
type IFormType = 'input' | 'password' | 'select' | 'datepicker'; export interface IFormItem { // 標識id field: string; // 表單類型,支持上述四種類型 type: IFormType; // 表單名稱 label: string; // 輸入框占位文本 placeholder?: any; // 表單校驗規則 rules?: any[]; // 針對select選擇框使用 options?: any[]; // 針對不同標簽特有屬性 otherOptions?: any; // 動態控制form中某個元素的顯示和隱藏 isHidden?: boolean; }
傳入的配置文件search.config.ts代碼如下:
IForm接口
export interface IForm { formItems: IFormItem[]; labelWidth?: string; colLayout: any; itemLayout: any; }
配置代碼

import { IForm } from '@/base-ui/form'; export const searchFormConfig: IForm = { labelWidth: '120px', itemLayout: { padding: '5px 5px', }, colLayout: { span: 8, }, formItems: [ { field: 'id', type: 'input', label: 'id', placeholder: '請輸入id', otherOptions: { size: 'small', }, }, { field: 'name', type: 'input', label: '用戶名', placeholder: '請輸入用戶名', }, { field: 'realname', type: 'input', label: '真實姓名', placeholder: '請輸入真實姓名', }, { field: 'cellphone', type: 'input', label: '電話號碼', placeholder: '請輸入電話號碼', }, { field: 'enable', type: 'select', label: '用戶狀態', placeholder: '請選擇用戶狀態', options: [ { title: '啟用', value: 1 }, { title: '禁用', value: 0 }, ], }, { field: 'createAt', type: 'datepicker', label: '創建時間', otherOptions: { startPlaceholder: '開始時間', endPlaceholder: '結束時間', type: 'daterange', }, }, ], };
(4). 該組件接收到底表單元素的內容值得參數為:modelValue
父組件調用該組件的時候,如果通過v-model綁定一個值,那么子組件默認就是通過modelValue來接收,這里組件通過v-model來綁定值,然后通過watch 監聽,通過 emit('update:modelValue', newValue);對外暴露新值。
封裝代碼分享:

<template> <div class="ypf-form"> <div class="header"> <slot name="header"></slot> </div> <el-form :label-width="labelWidth"> <el-row> <template v-for="item in formItems" :key="item.label"> <el-col v-bind="colLayout"> <el-form-item v-if="!item.isHidden" :label="item.label" :rules="item.rules" :style="itemStyle"> <template v-if="item.type === 'input' || item.type === 'password'"> <el-input v-bind="item.otherOptions" v-model="myFormData[`${item.field}`]" :placeholder="item.placeholder" :show-password="item.type === 'password'" /> </template> <template v-else-if="item.type === 'select'"> <el-select v-bind="item.otherOptions" v-model="myFormData[`${item.field}`]" :placeholder="item.placeholder" style="width: 100%" > <el-option v-for="option in item.options" :key="option.value" :value="option.value"> {{ option.title }} </el-option> </el-select> </template> <template v-else-if="item.type === 'datepicker'"> <el-date-picker style="width: 100%" v-bind="item.otherOptions" v-model="myFormData[`${item.field}`]" ></el-date-picker> </template> </el-form-item> </el-col> </template> </el-row> </el-form> <div class="footer"> <slot name="footer"></slot> </div> </div> </template> <script lang="ts"> import { defineComponent, PropType, ref, watch } from 'vue'; import { IFormItem } from '../types'; export default defineComponent({ props: { // v-model默認接收值就是modelValue modelValue: { type: Object, required: true, }, // form各種表單元素 formItems: { type: Array as PropType<IFormItem[]>, default: () => [], }, // 表單域標簽的寬度 labelWidth: { type: String, default: '100px', }, // 表單子元素的樣式 itemStyle: { type: Object, default: () => ({ padding: '5px 5px' }), }, // el-row 和 el-cow響應式布局 colLayout: { type: Object, default: () => ({ xl: 6, // >1920px lg: 8, md: 12, sm: 24, xs: 24, }), }, }, emits: ['update:modelValue'], setup(props, { emit }) { // 1. 獲取傳遞的數據{ ...props.modelValue } 是object對象 const myFormData = ref({ ...props.modelValue }); // 2. 監聽變化,對外傳遞 watch( myFormData, (newValue) => { emit('update:modelValue', newValue); }, { deep: true, }, ); return { myFormData }; }, }); </script> <style scoped lang="less"> .ypf-form { padding-top: 5px; } </style>
2. 重難點剖析
(1). v-model綁定
(詳細用法可參考:https://www.cnblogs.com/yaopengfei/p/15347532.html) 【先仔細看!!!】
對於el-input這個子組件而言:
v-model="myFormData[`${item.field}`]" ,是一個語法糖,相當於兩步操作:① 綁定元素value(element plus中叫modelvalue)的同時,② 監聽其value的變化。
等價於:
A. :modelValue="modelValue[`${item.field}`]" @update:modelValue="handleValueChange($event, item.field)" [PS. 這里的$event就是變化后最新值]
B. :modelValue="modelValue[`${item.field}`]" @input="handleValueChange($event, item.field)" (select標簽是: @change="handleValueChange($event, item.field)")
A.:modelValue="modelValue[`${item.field}`]" @update:modelValue="handleValueChange($event, item.field)" [PS. 這里的$event就是變化后最新值]
B.:modelValue="modelValue[`${item.field}`]" @input="handleValueChange($event, item.field)" (select標簽是: @change="handleValueChange($event, item.field)")
注意:如果父組件用v-model=“xxx”綁定一個值,子組件需要用 modelValue來接收,這是一個內置默認值。
組件封裝寫法2:

<template> <div class="hy-form"> <div class="header"> <slot name="header"></slot> </div> <el-form :label-width="labelWidth"> <el-row> <template v-for="item in formItems" :key="item.label"> <el-col v-bind="colLayout"> <el-form-item :label="item.label" :rules="item.rules" :style="itemStyle"> <template v-if="item.type === 'input' || item.type === 'password'"> <!-- 特別注意,下面的$event就是修改后的值 --> <el-input v-bind="item.otherOptions" :placeholder="item.placeholder" :show-password="item.type === 'password'" :modelValue="modelValue[`${item.field}`]" @update:modelValue="handleValueChange($event, item.field)" /> </template> <template v-else-if="item.type === 'select'"> <el-select v-bind="item.otherOptions" :placeholder="item.placeholder" :modelValue="modelValue[`${item.field}`]" @update:modelValue="handleValueChange($event, item.field)" style="width: 100%" > <el-option v-for="option in item.options" :key="option.value" :value="option.value"> {{ option.title }} </el-option> </el-select> </template> <template v-else-if="item.type === 'datepicker'"> <el-date-picker style="width: 100%" v-bind="item.otherOptions" :modelValue="modelValue[`${item.field}`]" @update:modelValue="handleValueChange($event, item.field)" ></el-date-picker> </template> </el-form-item> </el-col> </template> </el-row> </el-form> <div class="footer"> <slot name="footer"></slot> </div> </div> </template> <script lang="ts"> import { defineComponent, PropType, ref, watch } from 'vue'; import { IFormItem } from '../types'; export default defineComponent({ props: { // v-model默認接收值就是modelValue modelValue: { type: Object, required: true, }, // form各種表單元素 formItems: { type: Array as PropType<IFormItem[]>, default: () => [], }, // 表單域標簽的寬度 labelWidth: { type: String, default: '100px', }, // 表單子元素的樣式 itemStyle: { type: Object, default: () => ({ padding: '5px 5px' }), }, // el-row 和 el-cow響應式布局 colLayout: { type: Object, default: () => ({ xl: 6, // >1920px lg: 8, md: 12, sm: 24, xs: 24, }), }, }, emits: ['update:modelValue'], setup(props, { emit }) { const handleValueChange = (newValue: any, field: string) => { // 后面相當於 [field]屬性在 prop.modelvalue中已經包含了,這里相當於合並了 emit('update:modelValue', { ...props.modelValue, [field]: newValue }); }; return { handleValueChange }; }, }); </script> <style scoped lang="less"> .hy-form { padding-top: 5px; } </style>
組件封裝寫法3:

<template> <div class="hy-form"> <div class="header"> <slot name="header"></slot> </div> <el-form :label-width="labelWidth"> <el-row> <template v-for="item in formItems" :key="item.label"> <el-col v-bind="colLayout"> <el-form-item :label="item.label" :rules="item.rules" :style="itemStyle"> <template v-if="item.type === 'input' || item.type === 'password'"> <!-- 特別注意,下面的$event就是修改后的值 --> <el-input v-bind="item.otherOptions" :placeholder="item.placeholder" :show-password="item.type === 'password'" :modelValue="modelValue[`${item.field}`]" @input="handleValueChange($event, item.field)" /> </template> <template v-else-if="item.type === 'select'"> <el-select v-bind="item.otherOptions" :placeholder="item.placeholder" :modelValue="modelValue[`${item.field}`]" @change="handleValueChange($event, item.field)" style="width: 100%" > <el-option v-for="option in item.options" :key="option.value" :value="option.value"> {{ option.title }} </el-option> </el-select> </template> <template v-else-if="item.type === 'datepicker'"> <el-date-picker style="width: 100%" v-bind="item.otherOptions" :modelValue="modelValue[`${item.field}`]" @change="handleValueChange($event, item.field)" ></el-date-picker> </template> </el-form-item> </el-col> </template> </el-row> </el-form> <div class="footer"> <slot name="footer"></slot> </div> </div> </template> <script lang="ts"> import { defineComponent, PropType, ref, watch } from 'vue'; import { IFormItem } from '../types'; export default defineComponent({ props: { // v-model默認接收值就是modelValue modelValue: { type: Object, required: true, }, // form各種表單元素 formItems: { type: Array as PropType<IFormItem[]>, default: () => [], }, // 表單域標簽的寬度 labelWidth: { type: String, default: '100px', }, // 表單子元素的樣式 itemStyle: { type: Object, default: () => ({ padding: '5px 5px' }), }, // el-row 和 el-cow響應式布局 colLayout: { type: Object, default: () => ({ xl: 6, // >1920px lg: 8, md: 12, sm: 24, xs: 24, }), }, }, emits: ['update:modelValue'], setup(props, { emit }) { const handleValueChange = (newValue: any, field: string) => { // 后面相當於 [field]屬性在 prop.modelvalue中已經包含了,這里相當於合並了 emit('update:modelValue', { ...props.modelValue, [field]: newValue }); }; return { handleValueChange }; }, }); </script> <style scoped lang="less"> .hy-form { padding-top: 5px; } </style>
(2). v-bind綁定一個對象,可以直接把該對象上的屬性綁定到該input元素上
<el-input v-bind="item.otherOptions"/>
如果item.otherOptions為
otherOptions: { size: 'small', maxlength: "200", },
那么最終渲染后的代碼為:
<el-input size="small" maxlength="200" />
(3). 具名插槽
(詳細用法可參考:https://www.cnblogs.com/yaopengfei/p/15338752.html)
下面代碼是 名字為header的插槽
<div class="header"> <slot name="header"></slot> </div>
父組件在調用的時候,只需要在 <template>上加個 #header,可以寫插槽中的內容了,替換子組件slot的位置 。(在父組件中,調用插槽的時候可以寫在任何位置,沒有先后)
三. page-search組件封裝
1. 封裝思路
(1). 通過v-model將內容對象處理后的內容對象 formData綁定給ypf-form,從而實現雙向綁定。
(2). 接收到表單數據為 searchFormConfig,然后再通過v-bind將其內部的屬性綁定到 ypf-form 組件上。
(3). 引用ypf-form組件,#header插槽放標題 "搜索",#footer插槽放兩個按鈕,搜索 和 重置 。
A. 搜索:先清空formData對象,然后通過 emit('resetBtnClick'); 對外暴露,供父組件調用
B. 重置:通過 emit('queryBtnClick', formData.value);對外暴露,供父組件調用。
組件代碼分享:

<template> <div class="page-search"> <ypf-form v-bind="searchFormConfig" v-model="formData"> <template #header> <h1 class="header">搜索區</h1> </template> <template #footer> <div class="handle-btns"> <el-button type="primary" size="small" @click="handleResetClick">重置</el-button> <el-button type="success" size="small" @click="handleQueryClick">搜索</el-button> </div> </template> </ypf-form> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; import YpfForm from '@/base-ui/form'; export default defineComponent({ props: { searchFormConfig: { type: Object, required: true, }, }, components: { YpfForm, }, emits: ['resetBtnClick', 'queryBtnClick'], setup(props, { emit }) { // 組件綁定的v-model數據 // let formData = ref({ // id: '', // name: '', // password: '', // sport: '', // createTime: '', // }); // 優化1:上述formData中的屬性,不需要再寫了,完全可以用searchFormConfig配置中的field屬性即可 // 即屬性由配置中的field動態決定 const formItems = props.searchFormConfig?.formItems ?? []; const formOriginData: any = {}; for (const item of formItems) { formOriginData[item.field] = ''; } const formData = ref(formOriginData); // 2. 重置按鈕事件 const handleResetClick = () => { // 2.1 置空 // 寫法1:(特別注意,遍歷對象是 for-in)--(對應form組件中寫法) for (const key in formOriginData) { formData.value[`${key}`] = formOriginData[key]; // 等價於 (實際上就是遍歷置空) // formData.value[`${key}`] = ''; } // 寫法2:(對應form_test1組件中寫法) // formData.value = formOriginData; // 2.2 對外反饋 emit('resetBtnClick'); }; // 3. 搜索按鈕事件 const handleQueryClick = () => { emit('queryBtnClick', formData.value); }; return { formData, handleResetClick, handleQueryClick }; }, }); </script> <style scoped lang="less"> .header { font-size: 15px; text-align: left; padding-left: 10px; } .handle-btns { text-align: right; padding: 0 10px 10px 0; } </style>
2. 重難點剖析
(1). 父組件通過v-model傳值,實現雙向綁定,其中v-model相當於省略了什么?
對於父組件而言,通過v-model綁定一個值傳給子組件,當在子組件中修改這個綁定值得時候,父組件中綁定到這個值會自動更新!!! 當子組件中的內容改變時,對父組件而言,v-model干了兩件事:① 綁定value值 ② 拿到更新后的值,給父組件原值賦值
如下:三種寫法等價(其中handle1中就是把$event賦值給message)
(2). 綁定的FormData屬性,由配置文件動態創建?
我們這里給ypf-form組件通過v-model綁定的 formData 是根據配置中的field動態創建而來的。
配置代碼如下:

import { IForm } from '@/base-ui/form'; export const searchFormConfig: IForm = { labelWidth: '120px', itemLayout: { padding: '5px 5px', }, colLayout: { span: 8, }, formItems: [ { field: 'id', type: 'input', label: 'id', placeholder: '請輸入id', otherOptions: { size: 'small', maxlength: '200', }, }, { field: 'name', type: 'input', label: '用戶名', placeholder: '請輸入用戶名', }, { field: 'realname', type: 'input', label: '真實姓名', placeholder: '請輸入真實姓名', }, { field: 'cellphone', type: 'input', label: '電話號碼', placeholder: '請輸入電話號碼', }, { field: 'enable', type: 'select', label: '用戶狀態', placeholder: '請選擇用戶狀態', options: [ { title: '啟用', value: 1 }, { title: '禁用', value: 0 }, ], }, { field: 'createAt', type: 'datepicker', label: '創建時間', otherOptions: { startPlaceholder: '開始時間', endPlaceholder: '結束時間', type: 'daterange', }, }, ], };
formData代碼組裝代碼如下:
// 優化1:上述formData中的屬性,不需要再寫了,完全可以用searchFormConfig配置中的field屬性即可 // 即屬性由配置中的field動態決定 const formItems = props.searchFormConfig?.formItems ?? []; const formOriginData: any = {}; for (const item of formItems) { formOriginData[item.field] = ''; } const formData = ref(formOriginData);
擴展: formData的其它寫法
寫法1. formData直接寫個空對象綁定即可,因為實現了雙向綁定,自動就可以綁定屬性值。(后面page-modal組件就是這種寫法)
// 默認空對象,然后雙向綁定,input標簽中輸入值,formData中就有了這個屬性值(新寫法!!) const formData = ref<any>({});
寫法2. 考慮在search.config.js中增加一個屬性value,用來存放各個form表單的值,就不需要單獨再綁定內容了。【尚未實際測試,需要綜合考慮???】
(3).重置邏輯的兩種寫法,各有什么區別?
寫法1:
當ypf-form組件中是通過v-model綁定,通過watch監聽的時候,父組件page-search中,需要逐個屬性清空來實現重置。
ypf-form組件的寫法:

<template> <div class="ypf-form"> <div class="header"> <slot name="header"></slot> </div> <el-form :label-width="labelWidth"> <el-row> <template v-for="item in formItems" :key="item.label"> <el-col v-bind="colLayout"> <el-form-item v-if="!item.isHidden" :label="item.label" :rules="item.rules" :style="itemStyle"> <template v-if="item.type === 'input' || item.type === 'password'"> <el-input v-bind="item.otherOptions" v-model="myFormData[`${item.field}`]" :placeholder="item.placeholder" :show-password="item.type === 'password'" /> </template> <template v-else-if="item.type === 'select'"> <el-select v-bind="item.otherOptions" v-model="myFormData[`${item.field}`]" :placeholder="item.placeholder" style="width: 100%" > <el-option v-for="option in item.options" :key="option.value" :value="option.value"> {{ option.title }} </el-option> </el-select> </template> <template v-else-if="item.type === 'datepicker'"> <el-date-picker style="width: 100%" v-bind="item.otherOptions" v-model="myFormData[`${item.field}`]" ></el-date-picker> </template> </el-form-item> </el-col> </template> </el-row> </el-form> <div class="footer"> <slot name="footer"></slot> </div> </div> </template> <script lang="ts"> import { defineComponent, PropType, ref, watch } from 'vue'; import { IFormItem } from '../types'; export default defineComponent({ props: { // v-model默認接收值就是modelValue modelValue: { type: Object, required: true, }, // form各種表單元素 formItems: { type: Array as PropType<IFormItem[]>, default: () => [], }, // 表單域標簽的寬度 labelWidth: { type: String, default: '100px', }, // 表單子元素的樣式 itemStyle: { type: Object, default: () => ({ padding: '5px 5px' }), }, // el-row 和 el-cow響應式布局 colLayout: { type: Object, default: () => ({ xl: 6, // >1920px lg: 8, md: 12, sm: 24, xs: 24, }), }, }, emits: ['update:modelValue'], setup(props, { emit }) { // 1. 獲取傳遞的數據{ ...props.modelValue } 是object對象 const myFormData = ref({ ...props.modelValue }); // 2. 監聽變化,對外傳遞 watch( myFormData, (newValue) => { emit('update:modelValue', newValue); }, { deep: true, }, ); return { myFormData }; }, }); </script> <style scoped lang="less"> .ypf-form { padding-top: 5px; } </style>
重置的寫法:
const handleResetClick = () => { // 2.1 置空 // 寫法1:(特別注意,遍歷對象是 for-in)--(對應form組件中寫法) for (const key in formOriginData) { formData.value[`${key}`] = ''; } // 2.2 對外反饋 emit('resetBtnClick'); };
擴展:為什么直接 formData={},不生效呢?
因為在 ypf-form子組件中,const myFormData = ref({ ...props.modelValue }); 最終v-model綁定的是myFormData(而不是modelvalue值),其中 { ...props.modelValue } ,是做了一個淺拷貝,把淺拷貝后的對象賦值給了myFormData,所以在父組件中,直接修改formData的值,影響到是子組件中的modelValue值,而無法直接影響到{ ...props.modelValue }淺拷貝后的數據。
寫法2:
當ypf-form組件中使用 :modelValue綁定值,通過@update:modelValue監聽值的變化的時候,父組件page-search中,重置按鈕的邏輯可以使用 formData={},來清空。
ypf-form組件的寫法

<template> <div class="hy-form"> <div class="header"> <slot name="header"></slot> </div> <el-form :label-width="labelWidth"> <el-row> <template v-for="item in formItems" :key="item.label"> <el-col v-bind="colLayout"> <el-form-item :label="item.label" :rules="item.rules" :style="itemStyle"> <template v-if="item.type === 'input' || item.type === 'password'"> <!-- 特別注意,下面的$event就是修改后的值 --> <el-input v-bind="item.otherOptions" :placeholder="item.placeholder" :show-password="item.type === 'password'" :modelValue="modelValue[`${item.field}`]" @update:modelValue="handleValueChange($event, item.field)" /> </template> <template v-else-if="item.type === 'select'"> <el-select v-bind="item.otherOptions" :placeholder="item.placeholder" :modelValue="modelValue[`${item.field}`]" @update:modelValue="handleValueChange($event, item.field)" style="width: 100%" > <el-option v-for="option in item.options" :key="option.value" :value="option.value"> {{ option.title }} </el-option> </el-select> </template> <template v-else-if="item.type === 'datepicker'"> <el-date-picker style="width: 100%" v-bind="item.otherOptions" :modelValue="modelValue[`${item.field}`]" @update:modelValue="handleValueChange($event, item.field)" ></el-date-picker> </template> </el-form-item> </el-col> </template> </el-row> </el-form> <div class="footer"> <slot name="footer"></slot> </div> </div> </template> <script lang="ts"> import { defineComponent, PropType, ref, watch } from 'vue'; import { IFormItem } from '../types'; export default defineComponent({ props: { // v-model默認接收值就是modelValue modelValue: { type: Object, required: true, }, // form各種表單元素 formItems: { type: Array as PropType<IFormItem[]>, default: () => [], }, // 表單域標簽的寬度 labelWidth: { type: String, default: '100px', }, // 表單子元素的樣式 itemStyle: { type: Object, default: () => ({ padding: '5px 5px' }), }, // el-row 和 el-cow響應式布局 colLayout: { type: Object, default: () => ({ xl: 6, // >1920px lg: 8, md: 12, sm: 24, xs: 24, }), }, }, emits: ['update:modelValue'], setup(props, { emit }) { const handleValueChange = (newValue: any, field: string) => { // 后面相當於 [field]屬性在 prop.modelvalue中已經包含了,這里相當於合並了 emit('update:modelValue', { ...props.modelValue, [field]: newValue }); }; return { handleValueChange }; }, }); </script> <style scoped lang="less"> .hy-form { padding-top: 5px; } </style>
重置的寫法:
// 2. 重置按鈕事件 const handleResetClick = () => { // 2.1 置空 // 寫法2:(對應form_test1組件中寫法) // formData.value = formOriginData; // 寫法2也可以下面這種寫法 formData.value = {}; // 2.2 對外反饋 emit('resetBtnClick'); };
四. page-modal組件封裝
1. 封裝思路
(1). 該組件由 dialog 和 ypf-form 兩個組件構成,dialog的結構為:ypf-form組件、默認插槽、調用dialog的footer插槽,各部分的作用為:
A. ypf-form組件:用於展示各種form表單
B. 默認插槽:用於對外提供額外的內容,比如:新增角色的時候,除了表單外,還需要一個tree,用來顯示權限,就可以放到這個額外的插槽里。
C. 調用#footer插槽:用來處理確定 和 取消按鈕。
(2). 接收父組件傳過來的值有:
A. modalConfig:用來v-bind綁定給ypf-form組件,顯示form表單內容和配置form表單中的樣式
B. pageName:用來后續拼接請求地址的 (如:user、roles 等)
C. dialogConfig:用來v-bind綁定給el-dialog組件,控制el-dialog樣式的
D. defaultInfo:用來接收新增或編輯打開彈框傳遞過來的內容,處理編輯顯示 和 新增關閉清空問題
E. otherInfo:主要是用於父組件調用page-modal的默認插槽,插槽中需要傳遞的內容 (比如:新增角色下面的樹)
(3). el-dialog組件配置幾個通用的屬性:
A. 通過v-model綁定dialogVisible屬性,通過該屬性的true 或 false,控制彈框的顯示 or 隱藏。
B. 設置destory-on-close,關閉就銷毀屬性。header
(如果有)footer
(如果有)不會被銷毀
C. @closed監聽關閉,在里面可以處理清空邏輯。(本次組件封裝沒有使用)
(4). ypf-form組件,通過v-model綁定一個一個空的formData數據,通過父子組件的雙向綁定,可以實現自動填充。
組件代碼分享:

<template> <div class="page-modal"> <el-dialog v-model="dialogVisible" v-bind="dialogConfig" destroy-on-close @closed="clearContent"> <ypf-form v-bind="modalConfig" v-model="formData"></ypf-form> <!-- 默認插槽 --> <slot></slot> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false" size="small">取消</el-button> <el-button type="primary" @click="handleConfirmClick" size="small">確定</el-button> </span> </template> </el-dialog> </div> </template> <script lang="ts"> import { defineComponent, ref, watch } from 'vue'; import YpfForm from '@/base-ui/form'; import { useStore } from '@/store'; export default defineComponent({ components: { YpfForm, }, props: { // 用來存放form表單種類及其表單屬性→給ypf-form組件綁定 modalConfig: { type: Object, required: true, }, // 主要是用來后續拼接請求地址的 pageName: { type: String, required: true, }, // 用來配置el-dialog屬性的 dialogConfig: { type: Object, default: () => ({ title: '新增', width: '600px', center: false, }), }, // 用來接收新增 or 編輯傳遞過來的對象 defaultInfo: { type: Object, default: () => ({}), }, // 額外的參數,比如角色新增 下面樹選中的節點 otherInfo: { type: Object, default: () => ({}), }, }, setup(props) { // dialog數據 const dialogVisible = ref(false); // 默認空對象,然后雙向綁定,input標簽中輸入值,formData中就有了這個屬性值(新寫法!!) const formData = ref<any>({}); // 重點理解此處watch的作用 // 如何實現了編輯的顯示功能 和 新增輸入內容,關閉彈框內容消失的功能 // 說明:打開新增 或 編輯 彈框的時候,在其方法里都會給defaultInfo賦值,新增是 proxy{},編輯是 含內容的對象,都相當於defaultInfo發生了改變 // 所以會觸發這里watch監聽,下面的newValue就是傳遞過來的新值;對於新增,空對象就相當於清空了;對於編輯有內容,相當於回顯了。 watch( () => props.defaultInfo, (newValue) => { // console.log('監控defaultInfo', newValue); for (const item of props.modalConfig.formItems) { formData.value[`${item.field}`] = newValue[`${item.field}`]; } }, ); // 確定事件 const store = useStore(); const handleConfirmClick = () => { dialogVisible.value = false; // 判斷defaultInfo是否有元素 if (Object.keys(props.defaultInfo).length) { // 編輯 console.log('編輯用戶'); store.dispatch('system/editPageDataAction', { pageName: props.pageName, editData: { ...formData.value, ...props.otherInfo }, id: props.defaultInfo.id, }); } else { // 新建 console.log('新建用戶'); store.dispatch('system/createPageDataAction', { pageName: props.pageName, newData: { ...formData.value, ...props.otherInfo }, }); } }; // 關閉清空事件(這是方案2,上面的watch監聽也可以實現清空) const clearContent = () => { // formData.value = {}; // console.log('測試清空,但不執行哦'); }; return { dialogVisible, formData, handleConfirmClick, clearContent, }; }, }); </script> <style scoped lang="less"></style>
2. 重難點剖析
(1). page-modal組件中如何綁定對象監聽ypf-form中的內容變化?
通過v-model給ypf-form組件綁定一個空的formData,從而實現雙向數據綁定。
(2). 如何實現新增彈框打開清空 和 編輯彈框打開回顯 呢?
(3). 如何實現新增 和 編輯 公用一個彈框的確認邏輯?
通過判斷defaultInfo有無值,來區分新增 or 編輯的確認邏輯,從而調用不同的接口
(4). 如何實現各個模塊(如: 用戶/角色) 通用的新增和編輯邏輯?
A. 首先user.vue中調用page-modal的時候,需要傳入pageName屬性
B. page-modal中將pageName屬性傳遞到vuex中(另外需要吧 defaultInfo 和 otherInfo 合並也傳遞過去)
C. vuex中拿到這個pageName,進行簡單的封裝調用service方法
D. 最后需要接口也配合對應的規則即可
(5). 如何實現彈框中select類型的下拉框賦值?
這個需要在父組件中實現,比如在user.vue中調用page-modal, 默認的model.config.js文件導入的對象需要處理一下,給其中的options屬性賦值select下拉框中需要的內容,最后需要用computed包裹一下,然后在通過v-bind綁定給page-modal組件。
(6). 如何實現新增顯示密碼框 編輯隱藏密碼框呢?
這個同樣需要在父組件中實現,比如在user.vue中調用page-modal組件,新增 或者 編輯 的邏輯中,需要動態遍歷到password所在的配置,將其isHidder屬性對應的設置為true or false。
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。