抛出问题
在实现表单搜索页面的时候,你还是在cv然后修改字段和标签属性吗?是不是每一个存在表单的页面都写着这些重复的代码。
实现展示表格数据的页面时,同样的也存在着许许多多的重复代码,仅仅是因为他们的一部分属性的不同,但是不同的页面仍然使用的是同样的代码。
<el-table :data="tableData" style="width: 100%" height="250">
<el-table-column fixed prop="date" label="Date" width="150" />
<el-table-column prop="name" label="Name" width="120" />
<el-table-column prop="state" label="State" width="120" />
<el-table-column prop="city" label="City" width="320" />
<el-table-column prop="address" label="Address" width="600" />
<el-table-column prop="zip" label="Zip" width="120" />
</el-table>
我们是不是应该简化自己的操作,来实现个高复用的组件,让我们实现搜索表单或者表格的时候,能够通过配置来完成想要的内容。
注意: 对组件不太熟练的建议先看 vue3组件的使用
说干就干
1. 提炼出表单的配置项(公共部分)
根据上面的截图来看,每一个表单项不同的就是内容标签类型、标签的属性,还有就是表单项的label属性,每一个表单项需要使用v-model进行数据的双向绑定。
因此我们能够将form-item的配置提取出来,基本的内容配置就配置好了。
type IFormType = "input" | "password" | "select" | "datepicker"
type IOptions = {
label: string
value: string
}
export interface IFormItem {
field?: string // v-model绑定到对象对应的属性
label: string
placeholder: any
type: IFormType
options?: IOptions[] // 下拉框属性
// 其他想要配置也可以配置
}
表单项的内容配置好了,同样表单的包裹标签也需要对应的diy属性,例如:样式,表单项之间的距离等。
export interface IForm {
formItems: IFormItem[] // 表单内容
labelWidth?: string
colLayout?: any // col标签的占位
itemStyle?: any // 表单项的样式
}
根据这些配置,让我们根据配置完成表单的代码实现
- 配置props,限制父组件传值的类型并设置可不传内容的默认内容
props: {
formData: {
type: Object,
required: true
},
formItems: {
type: Array as PropType<IFormItem[]>,
default: () => []
},
labelWidth: {
type: String,
default: () => "100px"
},
itemStyle: {
type: Object,
default: () => ({ padding: "10px 4px" })
},
colLayout: {
type: Object,
default: () => ({
xl: 6,
lg: 8,
md: 12,
sm: 24,
xs: 24
})
}
},
- 根据formItems的配置项,对表单项进行动态遍历渲染;绑定对应的属性。
<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" :style="itemStyle">
<template
v-if="item.type === 'input' || item.type === 'password'"
>
<el-input
:placeholder="item.placeholder"
:show-password="item.type === 'password'"
v-model="formData[`${item.field}`]"
></el-input>
</template>
<template v-else-if="item.type === 'select'">
<el-select
:placeholder="item.placeholder"
style="width: 100%"
v-model="formData[`${item.field}`]"
>
<el-option
v-for="option in item.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</el-option>
</el-select>
</template>
<template v-else-if="item.type === 'datepicker'">
<el-date-picker
v-bind="item.otherOptions"
v-model="formData[`${item.field}`]"
></el-date-picker>
</template>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
这样就能够渲染出我们想要的表单,同样想更diy实现,可以添加header和footer两个插槽,让组件更定制化。
<div class="sz-form">
<div class="header">
<slot name="header"></slot>
</div>
<el-form :label-width="labelWidth">
...
</el-form>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
实现效果如下:
<!-- 使用组件的数据双向绑定, 组件内默认使用modelValue接收 -->
<sz-form v-bind="formConfig" :formData="formData">
<template #header>
<h2>高级检索</h2>
</template>
<template #footer>
<div class="handleBtn">
<el-button icon="el-icon-refresh">重置</el-button>
<el-button
type="primary"
icon="el-icon-search"
>搜索</el-button
>
</div>
</template>
</sz-form>
2. 提炼出表格的配置项
由elementPlus的表格项显然能看出,表各项都需要绑定到数据对象的属性,label还需要写好对应的宽度width;因此可以写成这样:
<el-table
class="content"
border
:data="tableData"
style="width: 100%"
>
<template v-for="item in propList" :key="item.prop">
<el-table-column v-bind="item" align="center" show-overflow-tooltip>
{{ item.prop }}
</el-table-column>
</template>
</el-table>
可这样,真的完成了吗? 当需要在表单项内部将对应的属性变成其他样式显示,并不是单纯显示其字段,又或是不是放入属性的元素,而是放入自定义的内容;例如:按钮图片等。
这个时候就需要我们写好插槽,但是不同的表单存在不同的属性,这让插槽实现有存在着困难,如何解决的呢?
当我们不需要diy时,就是用默认显示文本属性,当需要我们给定插槽名,让我们的配置去定义插槽的名字(插槽的name也可以进行绑定)。
因此,我们需要将 {{ item.prop }} 改成插槽形式:第一个template #default是elementPlus内部的插槽,scope.row就是属于该行的对象。
<template v-for="item in propList" :key="item.prop">
<el-table-column v-bind="item" align="center" show-overflow-tooltip>
<template #default="scope">
<slot :name="item.slotName" :row="scope.row">{{
scope.row[item.prop]
}}</slot>
</template>
</el-table-column>
</template>
给配置项中添加上slotName属性标志使用的插槽名
export interface propItem {
prop?: string,
label: string,
minWidth: string,
slotName?: string
}
同样我们和表单组件一样,给表格组件添加上header和footer组件
<div class="table">
<div class="header">
<slot name="header">
<div class="title">{{ title }}</div>
<div class="handler">
<slot name="headerHandler"></slot>
</div>
</slot>
</div>
<el-table>
...
</el-table>
<div class="footer">
<slot name="footer">
<!-- 默认可以给个分页组件 -->
</slot>
</div>
</div>
写到这里就能够自定义的实现表格内部的diy,但是这其中似乎还能够抽取出公共的部分,时间部分还有按钮盒子,状态显示,似乎很多表格都需要写到,因此我们可以再次抽取一层组件,让这层组件实现一些默认插槽的实现;同样也制定一个插槽slot给上层父组件自定义插槽。 设置默认插槽数组,和传入进来propList进行匹配
// 默认插槽数组
const defaultSlots = [
"headerHandler",
"status",
"createdTime",
"updateTime",
"manage"
]
const diySlots = props.contentTableConfig?.propList.filter((item: any) => {
if (defaultSlots.includes(item.slotName)) return false
else return true
})
// 非默认的插槽数组
const otherSlotsList = ref(diySlots)
代码实现:
<sz-table
:tableData="tableData"
:pageName="pageName"
:total="total"
v-bind="contentTableConfig"
v-model:pageInfo="pageInfo"
>
<!-- 1. 默认功能插槽 -->
<!-- 上面图中的代码,默认有的功能 -->
<!-- 2. 自定义插槽 -->
<template
v-for="item in otherSlotsList"
:key="item.prop"
#[item.slotName]="scope"
>
<template v-if="item.slotName">
<slot :name="item.slotName" :row="scope.row"></slot>
</template>
</template>
</sz-table>
到这里,我们算是完成了表格组件的配置实现;下面根据示例来看看吧
示例:实现商品信息的查看
<div class="goods">
<page-content :content-table-config="tableConfig" page-name="goods">
<template #image="scope">
<el-image
style="width: 60px; height: 100px"
:src="scope.row.imgUrl"
:preview-src-list="[scope.row.imgUrl]"
></el-image>
</template>
<template #oldPrice="scope"> ¥ {{ scope.row.oldPrice }} </template>
<template #newPrice="scope"> ¥ {{ scope.row.newPrice }} </template>
</page-content>
</div>
总结
抽取组件的时候虽然比cv来的复杂,但是做许多相同且繁琐的事情,这其中肯定是有规律可以找的,找到规律并实现逻辑,这样以后每一次遇到写表单和表格的时候,就方便了许多,只需要配置项就能够获得简单的内容;也能够通过插槽来获得diy的内容。
王红元老师说:慢慢地一步步做好,这便就是快。
不要嫌抽组件麻烦,只能一时,后面使用的时候就是便捷了。