前言
入职新公司接近一个月了,公司的项目后台管理系统居多,看了公司的代码,基本没有对顶部的搜索栏部分和表格的二次封装,只是直接使用了Element提供的组件,直接使用当然的就代码看起来很直观,不过就是可能会导致代码会很长,最近接手了公司的项目,其中有一个页面有1500多行,说实话我有点受不了,所以就试着封装一下搜索栏和表格,这次先讲table的封装,搜索栏的后面看时间。
二次封装是否有必要,其实看自己,封装的话前期的时间成本比较高,掘金上也有吐槽二次封装的弊端# 拒绝!封装el-table,请别再用JSON数组来配置列了,这个没有好坏,主要是经历比较重要。
正文
封装table组件的需求点,我就称二次封装的table是CommonTable了,如下图。
也许还要更多的需求点,就看你咋扩展了。
- 自定义表格表头
- 自定义element提供列,比如单选,序号
- 正常的列显示
- 自定义列表内容,比如操作这一栏等
熟悉element-table提供的表格的话,你基本就知道这里面最重要的就是tableData和prop值了,那么基本就是围绕着这俩个属性封装了。
tableData值好说,就在页面接口请求,返回值,然后传给CommonTable就是。
prop的话,看下图,有好多个columnn,prop不同,所以确认还要传一个数组给CommonTable
需求点3
基本代码如下,tableData和columns就是我们要传给CommonTable的属性,可以看到我在el-table标签和el-table-column标签上写上了 v-bind,至于$attrs是啥意思,请阅读vue官方,主要是为了将element提供的api也应用到CommonTable中,比如element给el-table标签提供了许多方法等。
<el-table :data="tableData" v-bind="$attrs">
<template v-for="(item, index) in columns" :key="item.prop + index">
<el-table-column v-bind="{ ...item }">
...
</el-table-column>
</template>
</el-table>
<script setup>
const prop = defineProps({
tableData: {
type: Array,
default() {
return [];
},
},
column: {
type: Array,
default: () => [],
},
});
const tableData = ref([]);
const columns = ref([]);
const typeLists = ref(["slotHeader", "slot", "dataMap", "slotBtn"]);
//为啥要watch在重新赋值一遍呢,其实只是为了保证数据最新罢了,不做可能也可以
watch(
() => prop.tableData,
(newVal, oldVal) => {
tableData.value = newVal;
},
{ deep: true, immediate: true }
);
watch(
() => prop.columns,
(newVal, oldVal) => {
columns.value = newVal;
},
{ deep: true, immediate: true }
);
</script>
columns是一个数组,遍历循环这个数据,v-bind="{ ...item }会是我们传入的属性,从上图可以看到el-table-column需要一个label和prop,所以columns的数据是这样的
到这基本就实现了上面的需求点3了
const column = ref([
{ label: '姓名', prop: "realName"},
{ label: '手机号码', prop: "mobile"},
{ label: '用户类型', prop: "userRiskType" }
{...}
]);
需求点2
这里主要是增加一个type属性,比较简单
el-table-column这里我还加了一些比较常用的属性,单独拎出来
<el-table :data="tableData" v-bind="$attrs">
<template v-for="(item, index) in columns" :key="item.prop + index">
<el-table-column v-bind="{ ...item }"
:align="item.align ? item.align : 'center'"
:header-align="item.headerAlign ? item.headerAlign : 'center'"
:show-overflow-tooltip="item.showTooltip ? item.showTooltip : false"
:fixed="item.fixed ? item.fixed : false">
...
</el-table-column>
<!-- 格式化类型,例如第一列单选框 -->
<el-table-column v-if="item.type" :key="item.prop + index" v-bind="{ ...item }" />
</template>
</el-table>
const column = ref([
{ type: "selection",prop: "select"},//新增一列
{ label: '姓名', prop: "realName"},
{ label: '手机号码', prop: "mobile"},
{ label: '用户类型', prop: "userRiskType" }
{...}
]);
需求点1
element原生的自定义表头写法是这样的,所以我们使用插槽控制一下自定义的内容就行,结合到我们的组件代码中
<el-table-column align="right">
<template #header>
<el-input v-model="search" size="small" placeholder="Type to search" />
</template>
</el-table-column>
如下,定义一个columnType,只有当columns数组项中有slotHeader才可以实现,通过具名插槽,同时也可以传入row,如果表头涉及列表数据的话。如果不传,就默认返回label表头。
<el-table :data="tableData" v-bind="$attrs">
<template v-for="(item, index) in columns" :key="item.prop + index">
<el-table-column v-bind="{ ...item }"
:align="item.align ? item.align : 'center'"
:header-align="item.headerAlign ? item.headerAlign : 'center'"
:show-overflow-tooltip="item.showTooltip ? item.showTooltip : false"
:fixed="item.fixed ? item.fixed : false">
<!-- 表头 -->
<template #header="{ row }">
<slot v-if="item.columnType === 'slotHeader'" :name="item.prop + 'Header'" :row="row"></slot>
<template v-else>{{ item.label }}</template>
</template>
</el-table-column>
</template>
</el-table>
具体使用如下,需求点1基本就完成了
<CommonTable :table-data="tableData" :columns="columns" >
//注意插槽prop属性+'Header'
<template #WAHeader>
<el-button> WA </el-button>
</template>
</CommonTable>
const column = ref([
{prop: "WA",label: "WA",minWidth: "105",columnType: "slotHeader"}
)]
需求点4
自定义列表格的内容,这里也是插槽,先看原生如下,其实和自定义表头差不多,不过这里可以根据自己的业务进行直接的扩展。
<el-table-column label="结果描述" align="center" prop="resultDesc">
<template slot-scope="scope">
//自定义内容
<dict-tag :options="dict.type.pay_order_result_desc" :value="scope.row.resultDesc"/>
</template>
</el-table-column>
根据自己的业务代码进行扩展,比如很多时候进行字典映射的匹配,公共的按钮等
<el-table :data="tableData" v-bind="$attrs">
<template v-for="(item, index) in columns" :key="item.prop + index">
<el-table-column v-bind="{ ...item }">
<!-- 自定义内容 -->
<template #default="{ row }">
<!-- 默认返回 -->
<template v-if="!item.columnType">
{{ row[item.prop] }}
</template>
<!-- 插槽 -->
<slot v-else-if="item.columnType === 'slot' || item.columnType === 'slotHeader'"
:name="item.prop" :row="row" />
<!-- 业务扩展,数据字典映射 -->
<template v-else-if="item.columnType === 'dataMap'">
{{ item.dataMap[row[item.prop]] || "" }}</template>
</template>
<el-table-column>
</template>
</el-table>
具体使用如下,自此4个需求点基本完成。
<CommonTable:table-data="tableData" :column="column" @clickHandle="clickHandle">
//通过上面的代码可以知道prop值就是我们的具名插槽
<template #operate="{ row }">
<el-button>编辑</el-button>
<el-button>删除</el-button>
</template>
</CommonTable>
完整代码及使用
CommonTable
<!--
isUnShow:控制表格列的隐藏和展示,默认展示
showTooltip:悬浮弹窗
fixed:表格列浮动
columnType:slotHeader表头自定义、slot表格某列自定义、dataMap数据映射
-->
<template>
<el-table :data="tableDataLocal" v-bind="$attrs">
<template v-for="(item, index) in columnLocal" :key="item.prop + index">
<el-table-column
v-if="!item.isUnShow && !item.type && (!item.columnType || typeLists.indexOf(item.columnType) > -1)"
v-bind="{ ...item }"
:align="item.align ? item.align : 'center'"
:header-align="item.headerAlign ? item.headerAlign : 'center'"
:show-overflow-tooltip="item.showTooltip ? item.showTooltip : false"
:fixed="item.fixed ? item.fixed : false">
<!-- 表头 -->
<template #header="{ row }">
<slot v-if="item.columnType === 'slotHeader'" :name="item.prop + 'Header'" :row="row"></slot>
<template v-else>{{ item.label }}</template>
</template>
<!-- 插槽 -->
<template #default="{ row, $index }">
<template v-if="!item.columnType">
{{ row[item.prop] }}
</template>
<slot v-else-if="item.columnType === 'slot' || item.columnType === 'slotHeader'" :name="item.prop" :row="row" :$index="$index" />
<!-- 数据字典映射 -->
<template v-else-if="item.columnType === 'dataMap'">{{ item.dataMap[row[item.prop]] || "" }}</template>
</template>
</el-table-column>
<!-- 格式化类型,例如第一列单选框 -->
<el-table-column v-if="item.type" :key="item.prop + index" v-bind="{ ...item }" :align="item.align ? item.align : 'left'" />
</template>
</el-table>
</template>
<script setup>
const prop = defineProps({
tableData: {
type: Array,
default() {
return [];
},
},
column: {
type: Array,
default: () => [],
},
});
const tableDataLocal = ref([]);
const columnLocal = ref([]);
const typeLists = ref(["slotHeader", "slot", "dataMap"]);
watch(
() => prop.tableData,
(newVal, oldVal) => {
tableDataLocal.value = newVal;
},
{ deep: true, immediate: true }
);
watch(
() => prop.column,
(newVal, oldVal) => {
columnLocal.value = newVal;
},
{ deep: true, immediate: true }
);
</script>
<style lang="scss" scoped></style>
index.vue
<template>
<div class="app-container">
<CommonTable v-loading="loading" :table-data="tableData" :column="column" @selection-change="selectionChange">
<template #WAHeader>
<el-button> WA </el-button>
</template>
<template #WA="{ row }">
<el-button> WA </el-button>
</template>
<template #operate="{ row }">
<el-button>编辑</el-button>
<el-button>删除</el-button>
</template>
</CommonTable>
</div>
</template>
<script setup>
import CommonTable from "@/components/CommonTable";
const loading = ref(false);
const tableData = ref([]);
const column = ref([
{ prop: "select", type: "selection", minWidth: "100", fixed: "left" },
{
prop: "WA",
label: "WA",
minWidth: "105",
columnType: "slotHeader",
},
{
prop: "mobile",
label: "电话",
minWidth: "105",
},
{
prop: "applyDate",
label: "反馈时间",
minWidth: "105",
},
{
prop: "text",
label: "问题描述",
minWidth: "105",
},
{
prop: "operate",
label: "操作",
minWidth: "105",
columnType: "slot",
},
]);
// 获取列表
const getList = () => {
loading.value = true;
getList({ ...param.value })
.then(({ code, rows, total }) => {
if (code === 200) {
tableData.value = rows;
queryParams.total = total;
}
})
.finally(() => (loading.value = false));
};
getList();
</script>
最后
我还封装了CommonSearch,有空在写写
上面的代码应该是可以直接用的
是否二次封装,看你自己哇,观点不同,如果能提高你的工作的效率,我觉得那就是个好东西,至于你封装的组件其他人看不看得懂,看不懂不是才好hhh。还有这个CommonTable还看不懂,那就。。。。