看着很朴素是不是哈哈哈哈哈写得还挺费劲呢~
需求:
1.表格分页始终居于页面底部;
2.监听页面高度变化,实现表格高度自适应。
整体思路:
1.页面通常分为三部分:固定框架部分,搜索部分,表格部分;
2.用监听到的页面高度,减去固定的框架高度,再减去搜索部分的高度,即为表格高度;
3.给表格设置动态高度,监听页面resize,当页面高度发生变化时,表格高度随即改变,以保证分页在页面最底部;
4.表格的数据需要做些处理。
后期完善:
1.每列的min-width一般按照表头的字数给定,根据具体情况,涉及到时间及身份证号类的数据,要展示完全;
组件表格:
1.el-table-column采用v-for的方式;
2.有些不是常规显示的行,先判断,再做成插槽的形式根据每页不同自行设置。
其中会用到监听客户屏幕高度的方法,和防抖的方法debounce ↓
/**
* 监听浏览器屏幕高度
* @return {Number}
*/
export function getDynamicHeight(ref) {
let fixedHeight = 132;
let containerHeight = window.innerHeight - fixedHeight || document.documentElement.clientHeight - fixedHeight || document.body.clientHeight - fixedHeight;
let listHeight = ref ? containerHeight - parseInt(window.getComputedStyle(ref).height) : containerHeight;
return {
listHeight
}
};
/**
* 防抖:从别的框架抄来的。。。一看注释就很专业绝对不是我自己写的哈哈哈哈哈哈哈
* @param {Function} func
* @param {number} wait
* @param {boolean} immediate
* @return {*}
*/
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result
const later = function () {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function (...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
组件BaseTable正式代码 👇
<template>
<div>
<div class="list-container">
<el-table :data="data.list" v-loading="loading" border :height="listHeight">
<!--
↓ 表格行,较常用的仅展示的部分
一些内容必须显示完全,需要设置min-width
因为是固定值,放在computed中判断即可
不用显示完全的行可以使用show-overflow-tooltip将溢出部分...
其中listInfo部分就是表格显示时需要用到的label和prop,
每页都不同,需要根据视图与数据不同进行转换
-->
<el-table-column
v-for="(row, index) in listInfo"
:key="row.label"
:label="row.label"
:prop="row.prop"
v-if="!row.slot"
:min-width="columnMinWidth(row.prop, row.label)"
show-overflow-tooltip
>
</el-table-column>
<!--
↓ 操作栏:通常位于最右侧
操作栏通常有三种规格,一个两个三个按钮,对应不同的min-width
判断条件放computed中
常规按钮“编辑”和“删除”放在组件中,可以选择需不需要
如果有其它的需求可以在页面中用slot自行设置
-->
<el-table-column
v-else-if="row.slot === 'operation'"
:label="row.label"
fixed="right"
:min-width="operationMinWidth(row.operationNumber)"
>
<template v-slot="scope">
<slot name="operation" :row="data.list[scope.$index]" />
<el-button v-if="commonOperation.edit" @click="handleEdit(data.list[scope.$index])" type="text">编辑</el-button>
<el-button
v-if="commonOperation.del"
@click="handleDelete(data.list[scope.$index])"
type="text"
class="btn-row-delete"
>删除</el-button
>
</template>
</el-table-column>
<!--
↓ 有图片的列
-->
<el-table-column v-else-if="row.slot === 'image'" :label="row.label" :min-width="columnMinWidth(row.prop, row.label)">
<template v-slot="scope">
<slot name="image" :row="data.list[scope.$index]" />
</template>
</el-table-column>
<!--
为可能出现的其它情况预留的默认插槽
-->
<el-table-column v-else :label="row.label" :min-width="columnMinWidth(row.prop, row.label)">
<template v-slot="scope">
<slot :row="data.list[scope.$index]" />
</template>
</el-table-column>
</el-table>
<!--
↓ 分页
-->
<el-pagination
background
:currentPage="params.pageNum"
:page-size="params.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="data.total"
@size-change="handlePageSizeChange"
@current-change="handleCurrentPageChange"
>
</el-pagination>
</div>
</div>
</template>
<script>
// 引入方法:获取列表高度的方法
import { getListHeight } from '@/utils/utils';
// 引入方法:防抖
import { debounce } from '@/utils/utils';
export default {
props: {
// 页面中使用此组件,需要更新数据时,将signal的值赋反即可
signal: Boolean,
/**
* ↓ 将组件中需要展示的值放在一个对象里
* list是列表数据
* total是分页需要用到的数据总条数
*/
data: {
type: Object,
required: true,
default: () => {
return { list: [], total: 0 };
}
},
// ↓ 获取数据需要用到的pageNum和pageSize
params: {
type: Object,
required: true,
default: () => {
return {
pageNum: 1,
pageSize: 10
};
}
},
loading: Boolean,
// ↓ 需要把父页面通过接口获取的数据进行转换,是一个对象数组
listInfo: Array,
/**
* ↓ 操作栏常规按钮:删除、编辑
* 可以设置需不需要常规按钮
*/
commonOperation: {
type: Object,
default: () => {
return { del: false, edit: false };
}
}
},
data() {
return {
// 列表高度
listHeight: 0
};
},
computed: {
/**
* ↓ 操作栏最小宽度
* 涉及一个小知识~~
* 需要传参的computed,需要用return一个function的形式
*/
operationMinWidth() {
return (num) => {
// 一个按钮60,再多的话依次往下加30
return 60 + (num - 1) * 30;
};
},
// 需要完全展示的列需要设置最小宽度
columnMinWidth() {
return (prop, label) => {
// 每列的minWidth根据label长度,再加表格cell左右的padding
let columnMinWidth = label.length * 14 + 20;
let str = `${prop}`;
/**
* 涉及到时间的属性名,有各种,不过基本上都会有time
* 先将属性名都转换成小写字母,再indexOf查看有没有time
*/
let timeNum = str.toLowerCase().indexOf('time');
/**
* 涉及到身份证号(我们一般都是idNumber)
* 因为后台的数据也不规范哈哈哈所以先转成小写先
*/
let idNum = str.toLowerCase().indexOf('idnumber');
if (timeNum != -1) {
return 135;
}
if (idNum != -1) {
return 160;
}
return columnMinWidth;
};
}
},
methods: {
monitorScreen() {
let resize = debounce(() => {
this.listHeight = getListHeight().listHeight;
}, 100);
resize();
// 页面监听
window.addEventListener('resize', resize, true);
// 组件销毁前移除页面监听事件
this.$once('hook:beforeDestroy', () => {
window.removeEventListener('resize', resize, true);
});
},
handlePageSizeChange(val) {
this.params.pageSize = val;
this.$emit('changeParamas', {
type: 'pageSize',
value: val
});
},
handleCurrentPageChange(val) {
this.params.pageNum = val;
this.$emit('changeParamas', {
type: 'pageNum',
value: val
});
},
handleDelete(row) {
this.$confirm('此操作不可恢复,是否继续?', '提示', {
type: 'warning'
}).then(() => {
this.$emit('deleteRow', row);
});
},
handleEdit(row) {
this.$emit('editRow', row);
}
},
mounted() {
this.monitorScreen();
}
};
</script>
需要用到该组件的父级页面代码 👇
我实在是写不动注释了。。都是语义化,强行写注释也是翻译凑合看吧哈哈哈哈哈哈哈
<template>
<div>
<BaseTable
:loading="loading"
:params="params"
:data="data"
:listInfo="listInfo"
:commonOperation="{ del: true, edit: true }"
@changeParamas="changeParamas"
@deleteRow="handleDelete"
@editRow="handleEdit"
>
<template #image="{ row }">
<img :src="`data:image/png;base64,${row.thumurl}`" class="table-image" v-if="row.thumurl" />
<div v-else class="">暂无</div>
</template>
<template #operation="{ row }">
<el-button type="text" @click="handleDetail(row)">详情</el-button>
</template>
<template #default="{ row }">
<div>默认插槽,可以传值哟~</div>
</template>
</BaseTable>
</div>
</template>
<script>
import BaseTable from '@/components/page/BaseTable';
import { getServiceList } from '@/api/serviceApi';
export default {
components: {
BaseTable
},
data() {
return {
params: {
pageNum: 1,
pageSize: 10
},
data: {
list: [],
total: 0
},
loading: false,
listInfo: []
};
},
methods: {
async getData() {
this.loading = true;
const { content: data } = await getServiceList(this.params);
this.data.list = data.list;
this.data.total = data.total;
/**
* listInfo的数据转换:
* 和接口配合,将label和prop设置好后
* 需要用插槽的数据们将prop去掉,加一个slot
* eg: 图片的slot值为image,操作的slot为operation
* 后续如果有其它特别的列,可以再多写一个default
*/
this.listInfo = [
{
label: '服务名称',
prop: 'title'
},
{
label: '服务图片',
slot: 'image'
},
{
label: 'DEEFAULT SLOT',
slot: 'other'
},
{
label: '创建时间',
prop: 'createTime'
},
{
label: '操作',
slot: 'operation',
operationNumber: 3
}
];
this.loading = false;
},
changeParamas(val) {
val.type === 'pageNum' ? (this.params.pageNum = val.value) : (this.params.pageSize = val.value);
this.getData();
},
handleDetail(row) {
console.log(row, '详情行数据');
},
handleDelete(row) {
console.log(row, '删除行数据');
},
handleEdit(row) {
console.log(row, '编辑行数据');
}
},
mounted() {
this.getData();
}
};
</script>
<style scoped lang="scss">
.table-image {
width: 30px;
}
</style>