前言
为什么需要二次封装
开发后台管理系统,会接触到很多表格和表单,一但表格表单多起来,仅仅只需要一小部分改变,都需要在中重写一大堆代码,许多重复逻辑,我们可以把重复逻辑抽离
出来二次封装一个组件 使用,减少在开发中需要编写的代码。
为什么需要定制化
每个项目或业务都有自己的特点和需求,可能需要特定的样式、交互行为或功能。通过定制化组件,可以根据具体需求进行定制提高复用性
,使得组件更符合业务场景,而且定制化可以使组件更加贴近实际业务需求。
需要了解的前置知识
vue3中插槽slot与template
v-bind的使用
v-bind="$attrs"
el-table的基本属性
不多废话,创建文件Hytable,开始代码
这是一个el-plus基础表格
vue
//Hytable.vue
<template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
</template>
<script lang="ts" setup>
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
]
</script>
一般的表格数据(TableData)是在父组件中请求返回的
vue
//HyTable.vue
<template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
</template>
<script lang="ts" setup>
import requestTabledata from '../api/index.ts'
interface TableProps{
requestApi?: (params: any) => Promise<any>;
}
// 接受父组件参数,配置默认值
const props = withDefaults(defineProps<TableProps>(), {
requestApi: requestTableData
});
let tableData= ref([])
const getTableList = async (api:any) => {
let { data } = await api();
tableData.value = data
};
onMounted(() => {
getTableList(props.requestApi)
})
</script>
我们可以看到el-table-column有很多重复的代码,如何让他根据父组件的传递值来动态的生成列的配置呢
我们先来看下table-column的属性 这里我们可以用\<template>来包裹el-table-column组件再用v-for来遍历
为什么要使用<template>来包裹,因为等下需要根据列配置用v-if判断el-table-column是否存在列配置type或prop属性,父组件需要传递列配置。
vue
//HyTable.vue
<template>
<el-table :data="tableData" style="width: 100%" row-key="id">
<template v-for="item in columnData" :key="columnData.item">
<el-table-column
v-if="item.type && ['selection','index','expand'].includes(item.type)"
v-bind="item"
:align="'center'"
:reserve-selection="item.type == 'selection'">
<template #default="scope">
<template v-if="item.type === 'expand'">
<slot :name="item.type" v-bind="scope" />
</template>
</template>
</el-table-column>
<el-table-column v-if="!item.type && item.prop" v-bind="item" :align="'center'" >
<template #default="scope">
<slot :name="item.prop" v-bind="scope" :row="scope.row">
{{ scope.row[item.prop] }}
</slot>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script setup lang="ts" name="Hytable">
import { onMounted, ref } from "vue";
import {requestTableData} from "../../api/index.ts"
interface TableProps{
columns: any,
requestApi?: (params?: any) => Promise<any>;
}
const props = withDefaults(defineProps<TableProps>(), {
columns: [],
requestApi: requestTableData
});
// 将列设置响应化
const columnData = ref(props.columns);
let tableData= ref([])
const getTableList = async (api:any) => {
let { data } = await api();
tableData.value = data
};
onMounted(() => {
getTableList(props.requestApi)
})
</script>
增加无数据和表格最后一行的插槽
vue
<template>
<el-table :data="tableData" style="width: 100%">
<template v-for="item in columnData" :key="columnData.item">
<el-table-column
v-if="item.type && ['selection','index','expand'].includes(item.type)"
v-bind="item"
:align="'center'"
:reserve-selection="item.type == 'selection'">
<template #default="scope">
<template v-if="item.type === 'expand'">
<slot :name="item.type" v-bind="scope" />
</template>
</template>
</el-table-column>
<el-table-column v-if="!item.type && item.prop" v-bind="item" :align="'center'" >
<template #default="scope">
<slot :name="item.prop" v-bind="scope" :row="scope.row">
{{ scope.row[item.prop] }}
</slot>
</template>
</el-table-column>
</template>
<!-- 这是el-table组件自带的插槽 -->
<template #append>
<slot name="append" />
</template>
<!-- 无数据 -->
<template #empty>
<div class="table-empty">
<slot name="empty">
<div>暂无数据</div>
</slot>
</div>
</template>
</el-table>
</template>
基本封装已经完成了 实现根据父组件传入数据来实现表格的行与列
让我们来使用一波
vue
//父组件
<template>
<Hytable :columns="columnData" >
<template #expand="scope">
{{scope.row}}
</template>
<template #gender="scope">
<el-button type="primary" plain>
{{ scope.row.gender}}
</el-button>
</template>
<template #operation>
<el-button
type="primary"
link
:icon="View"
>查看</el-button
>
<el-button
type="primary"
link
:icon="EditPen"
>编辑</el-button
>
<el-button
type="primary"
link
:icon="Delete"
>删除</el-button
>
</template>
</HyTable>
</template>
<script setup lang="ts">
import Hytable from '../components/testTable/index.vue';
import {Delete, EditPen, View} from "@element-plus/icons-vue";
import { ElMessage} from "element-plus";
const columnData =
[
{ type: 'selection', fixed: 'left', width: 70 },
{ type: 'expand', label: 'Expand', width: 80 },
{ prop: 'name', label: '姓名', search: { el: 'input' } },
{ prop: 'email', label: '邮箱' },
{ prop: 'address', label: '居住地址' },
{ prop:'gender', label: '性别'},
{prop: 'state', label:'用户状态'},
{ prop: 'operation', label: '操作', fixed: 'right', width: 300 },
]
</script>
来看实现效果
基本封装已经可以了,接下来可以增加些功能 如滚动条和内容溢出处理
js
// Hytable.vue
<template>
...
<el-table-column v-if="!item.type && item.prop" v-bind="item" :align="'center'" show-overflow-tooltip="showOverflowTooltip">
<template #default="scope">
<slot :name="item.prop" v-bind="scope" :row="scope.row">
{{ scope.row[item.prop] }}
</slot>
</template>
</el-table-column>
...
<slot name="pagination" v-if="pagination">
<el-pagination
:background="true"
:current-page="pageable.pageNum"
:page-size="pageable.pageSize"
:page-sizes="[10, 25, 50, 100]"
:total="pageable.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination>
</slot>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import {requestTableData} from "../../api/index.ts"
import { reactive } from "vue";
import { toRefs } from "vue";
import { computed } from "vue";
interface TableProps{
columns: any,
requestApi?: (params?: any) => Promise<any>;
pagination?: boolean,
showOverflowTooltip?: boolean;
}
const props = withDefaults(defineProps<TableProps>(), {
columns: [],
requestApi: requestTableData,
pagination: true,
showOverflowTooltip:true,
});
const columnData = ref(props.columns);
// let tableData= ref([])
onMounted(() => {
getTableList(props.requestApi)
})
const handlePage:()=>any = ()=>{
const state = reactive({
tableData: [],
pageable: {
pageNum:1,
pageSize:10,
total: 0,
}
})
const getTableList = async (api:any) => {
let { data } = await api();
state.tableData = data
state.pageable.total = data.length
};
const handleSizeChange = (val:any) => {
state.pageable.pageNum = 1;
state.pageable.pageSize = val;
getTableList(props.requestApi)
}
const handleCurrentChange = (val: any) => {
state.pageable.pageNum = val;
getTableList(props.requestApi)
}
// pageable.total = data
return{
...toRefs(state),
getTableList,
handleSizeChange,
handleCurrentChange,
}
}
const {tableData,pageable,handleSizeChange,handleCurrentChange,getTableList} = handlePage()
const processTableData = computed(() => {
if (!props.pagination) return tableData.value
return tableData.value.slice(
pageable.value.pageNum - 1) * pageable.value.pageSize,
pageable.value.pageSize * pageable.value.pageNum
)
})
</script>
看看效果
额。。。还有很多可以加的比如增删改查、查询表单、虚拟滚动之类 慢慢更新吧