第五节:Form组件封装和基于Form组件的PageSearch、PageModal组件的封装
一. 整体说明
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。