由于数据量过大,且需求是不允许分页,需要大量操作数据,需要使用el-table-v2虚拟表格来完成,但是他的样式以及功能与el-table有些差异,查找很多文档,并没有与我的需求相吻合的,需要自己来完成,记录一下,方便大家参考
成果截图
代码放在下面了
基本结构
<div v-loading="loading" style="height: calc(100vh - 84px - 140px)">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
:cache="8"
:columns="columns"
:data="sysInvoiceList"
:width="width"
:height="height"
:row-height="58"
fixed
@scroll="tableScroll"
/>
</template>
</el-auto-resizer>
</div>
ElDicTag是我的内部组件,请注意不要引入、使用
import { TableV2FixedDir, Column } from 'element-plus';
import ElDicTag from '@/components/DictTag/index.vue';
import { CaretTop, CaretBottom } from '@element-plus/icons-vue';
自定义选择列单元格组件
function SelectionCell({ value, indeterminate = false, onChange, disabled = false }) {
return h(ElCheckbox, {
onChange: onChange,
modelValue: value,
preventDefault: true,
indeterminate,
disabled,
onClick: (e) => e.stopPropagation() //阻止冒泡
});
}
自定义单元格排序组件
function SortIconCell(title: string, key) {
let { isAsc, orderByColumn } = queryParams.value;
return h(
'div',
{
class: 'customize_header',
onClick: ($event) => {
customizeSortChange({ $event, prop: key });
}
},
[
h('div', {}, title),
h(
'div',
{
class: 'sort_view'
},
[
h('i', {
class: ['sort-caret ascending', { 'is_active_sort_button': isAsc == 'ascending' && orderByColumn == key }],
onClick: ($event) => {
customizeSortChange({ $event, order: 'ascending', prop: key });
}
}),
h('i', {
class: ['sort-caret descending', { 'is_active_sort_button': isAsc == 'descending' && orderByColumn == key }],
onClick: ($event) => {
customizeSortChange({ $event, order: 'descending', prop: key });
}
})
]
)
]
);
}
自定义单元格排序方法
const toggleOrder = ({ order, prop }) => {
let { orderByColumn } = queryParams.value;
if (orderByColumn != prop) return 'descending';
let sortOrders = ['descending', 'ascending', undefined];
const index = sortOrders.indexOf(order);
if (prop == 'djrq') sortOrders.pop();
return sortOrders[index == sortOrders.length - 1 ? 0 : index + 1];
};
const customizeSortChange = ({ $event, order, prop }) => {
event.stopPropagation();
let { isAsc, orderByColumn } = queryParams.value;
let isActive = isAsc == order && orderByColumn == prop;
queryParams.value.isAsc = isActive ? undefined : order || toggleOrder({ order: isAsc, prop });
queryParams.value.orderByColumn = isActive ? undefined : prop;
queryParams.value.pageNum = 1;
getList();
};
colums配置
const columns: Column[] = [
{
key: 'selection',
width: 55,
fixed: TableV2FixedDir.LEFT,
align: 'center',
cellRenderer: ({ rowData }) => {
//必须添加这一句不然出现渲染错误,页面展示的就是复选框选中跟着滚动条一起动
if (rowData.checked == undefined) rowData.checked = false;
const onChange = (value: boolean) => {
rowData.checked = value;
value ? ids.value.push(rowData.id) : (ids.value = ids.value.filter((e) => e != rowData.id));
single.value = ids.value.length != 1;
multiple.value = !ids.value.length;
};
return h(SelectionCell, { value: rowData.checked, onChange });
},
headerCellRenderer: () => {
const _data = sysInvoiceList.value;
const onChange = (value) => {
sysInvoiceList.value = _data.map((row) => {
row.checked = value;
return row;
});
ids.value = sysInvoiceList.value.filter((item) => item.checked).map((item) => item.id);
single.value = ids.value.length != 1;
multiple.value = !ids.value.length;
};
const allSelected = _data.length > 0 ? _data.every((row) => row.checked) : false;
const containsChecked = _data.some((row) => row.checked);
return h(SelectionCell, {
value: allSelected,
intermediate: containsChecked && !allSelected,
onChange,
disabled: _data.length > 0 ? false : true
});
}
},
{
dataKey: 'djbh',
key: 'djbh',
width: 150,
align: 'center',
class: 'table_djbh',
fixed: TableV2FixedDir.LEFT,
headerCellRenderer: ({ column }) => SortIconCell('单据编号', column.dataKey)
},
{
dataKey: 'khmc',
key: 'khmc',
width: 200,
align: 'center',
headerCellRenderer: ({ column }) => SortIconCell('购方名称', column.dataKey)
},
{ dataKey: 'khsh', key: 'khsh', width: 200, align: 'center', headerCellRenderer: ({ column }) => SortIconCell('购方税号', column.dataKey) },
{
dataKey: 'fplxdm',
key: 'fplxdm',
title: '发票类型',
width: 150,
align: 'center',
cellRenderer: ({ rowData }) => {
return h(ElDicTag, {
options: sys_invoice_type.value,
value: rowData.fplxdm
});
}
},
{
dataKey: 'status',
key: 'status',
title: '开票状态',
width: 100,
align: 'center',
cellRenderer: ({ rowData }) => {
return h(ElDicTag, {
options: sys_invoice_kpzt.value,
value: rowData.status
});
}
},
{
dataKey: 'kz1',
key: 'kz1',
title: '渠道',
width: 100,
align: 'center',
cellRenderer: ({ rowData }) => {
return h(ElDicTag, {
options: sys_invoice_vtweg.value,
value: rowData.kz1
});
}
},
{
dataKey: 'kz2',
key: 'kz2',
title: '工厂',
width: 100,
align: 'center',
cellRenderer: ({ rowData }) => {
return h(ElDicTag, {
options: sys_invoice_werks.value,
value: rowData.kz2
});
}
},
{ dataKey: 'khlxfs', key: 'khlxfs', title: '购方联系方式', width: 140, align: 'center' },
{ dataKey: 'xfsh', key: 'xfsh', title: '销方税号', width: 200, align: 'center' },
{
dataKey: 'djrq',
key: 'djrq',
title: '单据日期',
width: 170,
align: 'center',
headerCellRenderer: ({ column }) => SortIconCell('单据日期', column.dataKey)
},
{ dataKey: 'je', key: 'je', title: '净值', width: 100, align: 'center', headerCellRenderer: ({ column }) => SortIconCell('净值', column.dataKey) },
{
dataKey: 'jshj',
key: 'jshj',
title: '价税合计',
width: 100,
align: 'center',
headerCellRenderer: ({ column }) => SortIconCell('价税合计', column.dataKey)
},
{ dataKey: 'se', key: 'se', title: '税额', width: 100, align: 'center', headerCellRenderer: ({ column }) => SortIconCell('税额', column.dataKey) },
{
dataKey: 'tax',
key: 'tax',
title: '税率',
width: 100,
align: 'center',
cellRenderer: ({ rowData }) => {
return h(ElDicTag, {
options: sys_invoice_sl.value,
value: rowData.tax
});
}
},
{
dataKey: 'yxj',
key: 'yxj',
title: '操作',
width: 146,
align: 'center',
style: { border: 'none' },
fixed: TableV2FixedDir.RIGHT,
headerCellRenderer: ({ column }) => {
return h('div', { class: 'small-padding' }, ['操作', h('div', { class: 'icon_view', onClick: () => (drawer.value = true) }, h(Filter))]);
},
cellRenderer: ({ rowData }) => {
return h('div', {}, [
h(ElButton, { type: 'primary', size: 'small', onClick: () => handleLook(rowData) }, () => '查看'),
h(ElButton, { type: 'primary', size: 'small', onClick: () => handleUpdate(false, rowData) }, () => '编辑')
]);
}
}
];
计算滚动条位置来判断是否展示固定列的阴影样式
let tableV2Style = {
none: null,
left: '2px 0 4px 0 rgba(0, 0, 0, 0.06)',
right: '-2px 0 4px 0 rgba(0, 0, 0, 0.06)',
clientWidth: 0,
scrollWidth: 0
};
const tableScroll = ({ scrollLeft }) => {
if (!scrollLeft) {
let domLeft = document.getElementsByClassName('el-table-v2__left')[0];
domLeft.style.boxShadow = tableV2Style.none = 'none';
} else if (scrollLeft + tableV2Style.scrollWidth >= tableV2Style.clientWidth) {
let domRight = document.getElementsByClassName('el-table-v2__right')[0];
domRight.style.boxShadow = tableV2Style.none = 'none';
} else {
if (!tableV2Style.none) return;
let domLeft = document.getElementsByClassName('el-table-v2__left')[0];
let domRight = document.getElementsByClassName('el-table-v2__right')[0];
tableV2Style.none = null;
domLeft.style.boxShadow = tableV2Style.left;
domRight.style.boxShadow = tableV2Style.right;
}
};
onMounted(async () => {
await getList();
// 通过异步获取dom元素,防止获取为空
setTimeout((v) => {
let dom = document.getElementsByClassName('el-table-v2__header-wrapper')[0];
tableV2Style.clientWidth = dom.children[0].clientWidth;
tableV2Style.scrollWidth = dom.clientWidth;
}, 100);
});
设置样式
::v-deep {
.el-auto-resizer {
border: 1px solid #ebeef5;
}
.el-table-v2__row-cell.is-align-center {
border-right: 1px solid #ebeef5;
}
.el-table-v2__header-wrapper {
position: relative;
background-color: #f8f8f9;
.icon_view {
position: absolute;
background-color: #fff;
width: 27px;
height: 27px;
border-bottom-left-radius: 20px;
top: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
:first-child {
height: 15px;
width: 15px;
margin-left: 3px;
}
}
}
.el-table-v2__header-cell {
border-right: 1px solid #ebeef5;
background-color: #f8f8f9;
color: #515a6e !important;
}
.el-table-v2__cell-text {
color: #606266 !important;
}
.el-table-v2__left {
}
.el-table-v2__right {
box-shadow: -2px 0 4px 0 rgba(0, 0, 0, 0.06);
}
.customize_header {
width: 100%;
height: 100%;
display: flex;
cursor: pointer;
justify-content: center;
align-items: center;
}
.sort_view {
align-items: center;
display: flex;
flex-direction: column;
height: 14px;
overflow: initial;
position: relative;
vertical-align: middle;
width: 24px;
top: 1px;
}
.sort-caret {
border: 5px solid transparent;
height: 0;
left: 7px;
position: absolute;
width: 0;
}
.sort-caret.ascending {
border-bottom-color: #a8abb2;
top: -5px;
}
.sort-caret.descending {
border-top-color: #a8abb2;
bottom: -3px;
}
.is_active_sort_button.ascending {
border-bottom-color: #409eff;
}
.is_active_sort_button.descending {
border-top-color: #409eff;
}
}
目前功能全部实现,Element-plus内部有bug我已经向官方提出,正在修改中