基于element-ui进行组件的二次封装
组件文档
1.FormItem
引用方式
import FormItem from "@/components/FormItem";
参数列表
type | 表单元素类型 | string | |||
prop | 表单字段名称 | String | - | ||
value | 表单双向绑定的值 | String, Number, Boolean, Array, Object | |||
label | 表单lebel | String | |||
placeholder | 表单元素中的提示语 | String | |||
disabled | 表单元素是否禁用 | Boolean | false | ||
options | 表单元素type类型是select时需要传入该字段 | Array | |||
width | 表单元素宽度,注意要带上px单位 | String | |||
suffix | 输入框尾部内容,输入框尾部内容,只对 type="text" 有效 | ||||
clearable | 表单元素是否可以清空 | Boolean | true | ||
filterable | select 是否可搜索 | Boolean | false | ||
activeText | switch 打开时的文字描述 | String | 是 | ||
inactiveText | switch 关闭时的文字描述 | String | 否 | ||
size | 输入框尺寸,只在 type!="textarea" 时有效 | String | medium / small / mini |
注意:表单需要的属性非常多,上面参数列表没必要一一穷尽,我们可以使用v-bind="$attrs"将props中没有定义的属性,透传到表单元素上。示例
<el-input
v-if="type === 'input'"
:size="size || 'mini'"
v-model.trim="localValue"
:placeholder="placeholder || '请输入内容'"
:clearable="clearable"
v-bind="$attrs"
:style="[width ? { width: width } : '']"
>
<span v-if="suffix" slot="suffix">{{ suffix }}</span>
</el-input>
支持渲染的标签
- Input
- InputNumber
- Select
- DatePicker (date,month,daterange)
- Switch
代码示例见FilterForm
2.FilterForm 过滤表单
引用方式
import FilterForm from "@/components/FilterForm";
参数列表
formConfig | 过滤条件表单项配置 | Array | - | [] |
formData | 表单双向绑定的数据 | Object | - | {} |
inline | 行内表单模式 | Boolean | true/false | false |
labelWidth | 表单域标签的宽度,例如 '50px'。作为 Form 直接子元素的 form-item 会继承该值。支持 auto。 | String | - | "80px" |
方法
handleSubmit | 点击查询按钮调用该方法,触发父组件身上的submit事件,把formData传出去 | - |
handleReset | 点击重置按钮调用该方法 ,实际调用el-form自身的resetFields对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 。触发父组件身上的submit事件,把formData传出去 | - |
Slots插槽
formExOperation | 过滤表单操作按钮,默认只配置了查询和重置,如果想要增加像导出等操作,可以通过formExOperation配置 |
注意:如果一个组件的插槽过多,我们可以通过另一个属性$lots,这个属性表示父组件传入的所有的插槽,是一个对象。键名对应着插槽名,键值是一个vnode数组,如下:
代码示例,父向子传递多个插槽,并传递了数据:
<el-input ref="refInput" v-bind="$attrs">
<template v-for="(_value, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}" />
</template>
</el-input>
代码示例见FilterTablePagination
3.BasicTable 基于el-table对表格组件进行二次封装
参数列表
tableData | 表格显示的数据 | Array | - | [] |
columns | 表格的列数据 | Array | - | [] |
loading | 表格数据加载中的loading效果 | Boolean | - | false |
height | 设置height可以固定表头 | Number | - | - |
multipleSelection | 表格多选时需要传入此参数,会存放表格多选时选中的row | Array | - | [] |
注意:表格需要的属性非常多,比如row-class-name可以设置表格行的隔行变色。上面参数列表没必要一一穷尽,我们可以使用v-bind="$attrs"将props中没有定义的属性,透传到el-table元素上。示例
<el-table :data="tableData" ref="basicTableRef" style="width: 100%" :height="height" @selection-change="handleSelectionChange" v-loading="loading" v-bind="$attrs">
</el-table>
如果组件又套了一层组件,如果需要透传el-table表格属性,这里要加上v-bind="tableProps",像FilterTablePagination中的组件使用BasicTable组件,代码示例如下:
<basic-table :tableData="tableProps.tableData" :columns="tableProps.columns" :multipleSelection="tableProps.multipleSelection" v-bind="tableProps">
<!-- 表格操作列 -->
<template #tableOperation="{ row, column, $index }">
<slot name="tableOperation" :row="row" :$index="$index" :column="column"></slot>
</template>
</basic-table>
方法
handleSelectionChange | 表格的events selection-change上绑定该方法 ,当选择项发生变化时会触发该事件。实际会触发父上的selectionChange把selection参数传出去 | selection。 |
clearAll | 点击清空时调用 ,实际调用el-table自身的clearSelection清空多选表格用户的选择,父要调用直接使用ref调用子组件身上的方法 | - |
Slots插槽
item.prop | 表格列prop,是一个动态插槽。如果某一列的内容需要自定义可以使用插槽,比如某一列的内容可以编辑 |
item.prop +"_header" | 表格列header,是一个动态插槽。如果某一列的表头需要自定义可以使用插槽,比如某一列的表头需要展示prop和相关图标 |
注意
1.如果需要对表格单元格内容进行格式化时需要给el-table-column添加formatter方法,此方法不能直接封装在slot插槽中。所以在封装中设置了2个列,根据column配置中是否传入custom字段进行区分。如果custom为true则使用自定义列,比如可以设置可编辑的单元格之类的。如果不传custom则使用普通列,列上配置formatter属性。代码如下:
<el-table :data="tableData" ref="basicTableRef" style="width: 100%" border @selection-change="handleSelectionChange" v-loading="loading" :header-cell-class-name="getHeaderCellClassName || ''">
<template v-for="(item,index) in columns">
<el-table-column v-if="['index','selection'].includes(item.type)" :type="item.type" :key="index" :label="item.label" :align="item.align || 'center'" v-bind="item.attrs || {}" width="55" :selectable="item.selectable">
</el-table-column>
<!-- 自定义列模板 columns数组中item需要定义custom字段 -->
<el-table-column v-else-if="item.custom" :width="item.width" v-bind="item.attrs || {}" :key="item.prop+'custom'" :show-overflow-tooltip='item.showOverflowTooltip || false'>
<template slot="header" slot-scope="{$index,column}">
<slot :name='item.prop +"_header"' :$index='$index' :column="column" :label="item.label">{{item.label}}</slot>
</template>
<template slot-scope="{$index,row,column}">
<slot :name="item.prop" :$index='$index' :row="row" :column="{...column,...item}">
{{row[item.prop]}}
</slot>
</template>
</el-table-column>
<el-table-column v-else :prop="item.prop" :label="item.label" :formatter="item.formatter" :width="item.width" v-bind="item.attrs || {}" :key="item.prop">
<template slot="header" slot-scope="{$index,row,column}">
<slot :name='item.prop +"_header"' :$index='$index' :column="column" :label="item.label">{{item.label}}</slot>
</template>
</el-table-column>
</template>
</el-table>
将formatter封装在插槽中,类似这样的column项会报错:
{
prop: 'status',
label: '项目估值状态',
attrs: {'min-width': 96},
formatter: (row, column, cellValue, index) => {
return this.$createElement('el-tag', {
props: {
type: this.calculateByValueStatus(cellValue).statusType,
hit: true
}
}, this.calculateByValueStatus(cellValue).statusText)
}
},
代码示例见FilterTablePagination
4.Pagination 基于el-pagination 对分页组件进行二次封装
currentPage | 当前页 | Number | - | 1 |
pageSize | 每页条数 | Number | - | 10 |
total | 列表总数 | Number | - | 0 |
像其他的一些配置比如pageSizes,layout属性维护在组件的data中,方便统一维护
方法
onSizeChange | 当切换每页显示条数时触发,每次切换会把currentPage置为1。并且会直接改变父组件的pageSize。此处会触发父组件的page-change事件 | - |
onCurrentChange | 当切换页码时触发;并且会直接改变父组件的currentPage。此处会触发父组件的page-change事件 | - |
注意:由于FilterTablePagination中使用该组件的方式是.sync。所以已经实现了双向绑定
<Pagination :currentPage.sync='paginationProps.currentPage' :pageSize.sync='paginationProps.pageSize' :total='paginationProps.total'/>
至此,一个页面的三个部分:过滤条件查询,表格展示,分页就已经完成封装了,分别对应FilterForm,BasicTable,Pagination。
这里我们还进行了一个组合式的封装,可以针对大部分普通的表格展示页面,如果有一些复杂的表格,比如表格单元格可编辑,表格行可编辑,这里还没有封装,可以后期根据业务需求扩展。
5.FilterTablePagination
基于FilterForm,BasicTable,Pagination三个组件组合而成的。
参数列表
formProps | 过滤表格组件需要传递的props集合 | Object | - | - |
tableProps | 表格组件需要传递的props集合 | Object | - | - |
paginationProps | 分页组件需要传递的props集合 | Object - | - |
Events (传递给子组件的方法)
submit | 该事件是FilterForm组件会触发的。此处因为是组合组件,实际也是会进行转发,会再触发父组件传过来的loadData方法,进行数据请求 | - |
reset | 该事件是FilterForm组件会触发的。此处因为是组合组件,实际也是会进行转发,会再触发父组件传过来的loadData方法,进行数据请求 | - |
page-change | 该事件是Pagination组件会触发的。此处因为是组合组件,实际也是会进行转发,会再触发父组件传过来的loadData方法,进行数据请求 | - |
使用方式
见页面 filterTablePaginationPage 业务中需要关注的地方
-
1.传入表单,表格,分页需要的相关配置项
-
2.传入请求列表的参数
-
3.传入请求列表的方法loadData
-
4.如果表格有操作项,比如新增,编辑,删除,导出等操作,
所以上面业务中一些通用的数据和方法可以封装在一个mixin中进行逻辑复用。见FilterTablePaginationMixin.js中
6.新增编辑弹窗 模块
此处直接放在业务中,放在对应的modules文件夹中,拆分为表单和弹窗2部分。
- 1.表单专注于表单自身的业务逻辑,比如表单新增和编辑的项,表单校验,表单提交,代码示例
async handleSubmit() {
try {
const valid = await this.$refs.formRef.validate();
if (!valid) return Promise.resolve(false);
let res;
if (this.formKey === "add") {
console.log('mock 新增api ,入参是:',this.formData)
res = await this.mockSaveApi(this.formData);
} else {
console.log('mock 编辑api ,入参是:',this.formData)
res = await this.mockUpdateApi(this.formData);
}
if (res.data.code !== 0) {
this.formKey === "add"
? this.$message.error("新增失败," + res.data.msg)
: this.$message.error("更新失败," + res.data.msg);
return Promise.resolve(false);
}
return Promise.resolve(true);
} catch (error) {
console.error("error---", error);
}
},
上面示例是因为业务中新增和编辑虽然用的是同一个接口,但请求方式不同,新增是post,修改是put.在api中写了对应的方法。所以做了区分。实际可以根据业务修改。
- 2.弹窗专注于弹窗自身的逻辑,比如弹窗展示,打开,关闭弹窗。其中注意确认按钮是弹窗中的功能,点击确认需要先校验表单,成功后才能关闭弹窗,重新加载列表数据。代码示例
// 弹窗确认逻辑 是先提交表单,提交成功后关闭弹窗,加载数据
async handleConfirm() {
const isSubmitSuccess = await this.$refs.addEditFormRef.handleSubmit();
if (!isSubmitSuccess) return;
this.closeDialog();
this.$emit("loadData");
}