ElementPlus中Table的改造
ElementPlus中关于Table的官方示例
<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>
每次都得写一堆的el-table-column重复代码。为了通用性,会考虑自定义Table组件,那么Columns就需要传递Column数据构建,某些Column数据需要自定义渲染,此时要么使用h()函数要么写jsx组件。这一点都不vue!
参考AntDesignVue中关于的Table的官方示例:
<a-table :columns="columns" :data-source="data">
<template #headerCell="{ column }">
<template v-if="column.key === 'name'">
<span>
<smile-outlined />
Name
</span>
</template>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<a>
{{ record.name }}
</a>
</template>
<template v-else-if="column.key === 'action'">
<span>
<a>Invite 一 {{ record.name }}</a>
<a-divider type="vertical" />
<a>Delete</a>
</span>
</template>
</template>
</a-table>
</template>
<script lang="ts" setup>
import { SmileOutlined } from '@ant-design/icons-vue';
const columns = [
{
name: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Action',
key: 'action',
},
];
const data = [
{
key: '1',
name: 'John Brown',
},
{
key: '2',
name: 'Jim Green',
}
];
</script>
此处暴露 #headerCell和 #bodyCell插槽以供外部传递自定义组件,列以columns数据形式构建。
自定义MyTable组件,支持自适应高度,分页,排序,多选,列显示隐藏功能
<template>
<div class="container">
<div class="inner_table" ref="elementRef">
<el-table
:data="tableData"
:default-sort="sorter"
:="$attrs"
ref="tableRef"
v-loading="loading"
style="width: 100%"
:max-height="state.tableHeight"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
>
<template #default>
<el-table-column v-if="rowSelection" type="selection" :width="30" />
<el-table-column
v-for="item in finalColumns"
:key="item.prop"
:="item"
>
<template #header="{ column }">
<slot name="cellHeader" :column="column"> </slot>
</template>
<template #default="{ row, column }">
<slot
v-if="slots.cellBody"
name="cellBody"
:row="row"
:column="column"
>
{{
item.formatter
? item.formatter(row, column, row[item.prop])
: row[item.prop]?.toString()
}}
</slot>
</template>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
align="center"
:width="150"
>
<template #header="{ column }">
<template v-if="slots.actionHeader">
<slot name="actionHeader" :column="column">{{
column.label
}}</slot>
</template>
<div
v-else
style="display: inline-flex; align-items: center; gap: 8px"
>
{{ column.label }}
<el-popover placement="bottom" :width="column.width">
<template #reference>
<el-icon><Setting /></el-icon>
</template>
<el-checkbox-group
v-model="state.showColumns"
@change="showColumnsChange"
>
<el-checkbox
v-for="column in columns"
:label="column.label"
:value="column.prop"
/>
</el-checkbox-group>
</el-popover>
</div>
</template>
<template #default="{ row }">
<slot name="actionBody" :row="row"></slot>
</template>
</el-table-column>
</template>
<template #empty>
<el-empty />
</template>
</el-table>
</div>
<div class="inner_footer" v-if="slots.footerLeft || pager">
<div>
<slot name="footerLeft"></slot>
</div>
<el-pagination
v-model:current-page="pager.currentPage"
v-model:page-size="pager.pageSize"
:total="pager.total"
:page-sizes="state.pageSizes"
@size-change="sizeChange"
@current-change="currentChange"
:layout="pager.layout || 'total, sizes, prev, pager, next, jumper'"
/>
</div>
</div>
</template>
<!--
Slot介绍
actionHeader:操作栏头部插槽,没有时显示Label且携带列显示隐藏复选框
actionBody:操作栏每行插槽,没有时显示空
cellHeader:数据栏头部插槽,没有时显示Label
cellBody:数据栏每行插槽,没有时显示数据值
footerLeft:底部左侧附件插槽,没有时显示空
-->
<script setup>
import _ from "lodash";
import {
ref,
onMounted,
onUnmounted,
reactive,
useSlots,
useAttrs,
watch,
computed,
} from "vue";
const slots = useSlots();
const attrs = useAttrs();
const props = defineProps({
rowSelection: { default: true, type: Boolean }, // 开启多选
loading: { default: false, type: Boolean }, // 加载状态
tableData: { default: [], type: Array }, // 数据
columns: { default: [], type: Array, required: true }, // 列配置
showColumns: { default: [], type: Array }, // 显示列
pager: { default: { currentPage: 1, pageSize: 10, total: 0 }, type: Object }, // 分页
sorter: { default: {}, type: Object }, // 默认排序
});
const state = reactive({
tableHeight: 600,
pageSizes: [10],
showColumns: [],
});
const tableRef = ref();
const emits = defineEmits([
"sizeChange",
"pageChange",
"selectChange",
"sortChange",
"update:showColumns",
]);
const elementRef = ref(null);
// 动态监听表单容器并计算可分页选项
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
const cr = entry.contentRect;
if (cr.height) {
state.tableHeight = cr.height;
const showHeader =
attrs["show-header"] === undefined ? true : attrs["show-header"];
// 动态计算最小的分页size
const headerHeight = showHeader ? 40 : 0;
const size = Math.floor((cr.height - headerHeight) / 41.7);
emits("sizeChange", size);
const pageSizeOptions = [size];
let nextSize = Math.ceil(size / 10) * 10;
if (nextSize != size) {
pageSizeOptions.push(nextSize);
}
while (nextSize * 2 < 200) {
nextSize = nextSize * 2;
pageSizeOptions.push(nextSize);
}
state.pageSizes = pageSizeOptions;
}
}
});
watch(
() => props.showColumns,
(v) => {
state.showColumns = v;
}
);
onMounted(() => {
if (elementRef.value) {
resizeObserver.observe(elementRef.value);
}
state.showColumns = props.showColumns.length
? props.showColumns
: _.map(props.columns, "prop");
});
onUnmounted(() => {
resizeObserver.disconnect();
});
// 显示/隐藏列控制后的最终显示列
const finalColumns = computed(() => {
return _.filter(props.columns, (o) => state.showColumns.includes(o.prop));
});
// 显示列数据改变
const showColumnsChange = (value) => {
emits("update:showColumns", value);
};
// 页码改变
function currentChange(value) {
emits("pageChange", value);
}
// 页数量改变
function sizeChange(value) {
emits("sizeChange", value);
}
// 选中行改变
const handleSelectionChange = (val) => {
emits("selectChange", val);
};
// 排序改变
const handleSortChange = (val) => {
emits("sortChange", val);
};
// 清除选项
function clearSelects() {
tableRef.value.refElTable.clearSelection();
}
// 清楚排序
function clearSort() {
tableRef.value.refElTable.clearSort();
}
// 对外暴露函数
defineExpose({
clearSelects,
clearSort,
});
</script>
<style lang="less" scoped>
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.inner_footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
}
.inner_table {
height: calc(100% - 48px);
:deep(.el-table .el-table__cell) {
height: 41.5px;
padding: 0px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
</style>
使用方法
<MyTable
:tableData="state.tableData"
:columns="state.columns"
:loading="state.loading"
:pager="state.pager"
v-model:showColumns="state.showColumns"
@selectChange="
(v) => {
state.selects = v;
}
"
@sizeChange="sizeChange"
@pageChange="pageChange"
>
<template #cellHeader="{ column }">
<template v-if="column.rawColumnKey === 'name'">
<el-icon><Clock /></el-icon> {{ column.label }}
</template>
</template>
<template #cellBody="{ row, column }">
<template v-if="column.rawColumnKey === 'name'">
<a>{{name}}</a>
</template>
</template>
<template #actionBody="{ row }">
<div class="operation">
<el-button link type="primary" >执行</el-button>
<el-button
link
type="primary"
@click="download(row)"
>下载</el-button>
</div>
</template>
</MyTable>
<script setup>
import { watch, onMounted, reactive, ref, nextTick } from "vue";
import { getFilesList } from "@/services/files.js";
import MyTable from "@/components/MyTable.vue";
import _ from "lodash";
const state = reactive({
loading: false,
pager: {
currentPage: 1,
pageSize: 0,
total: 0,
},
columns: [
{
prop: "name",
label: "名称",
showOverflowTooltip: true,
align: "center",
},
],
showColumns: [],
tableData: [],
selects: [], // 选中的行
});
// 获取路径下所有文件
const getFiles = async () => {
if (state.pager.pageSize) {
state.loading = true;
const res = await getFilesList({
page: state.pager.currentPage,
pageSize: state.pager.pageSize,
});
if (res.success) {
state.tableData = res.data.items;
state.pager.total = res.data.itemTotal;
}
state.loading = false;
}
};
// 根据路径查询其下文件列表
const search = async () => {
await getFiles();
};
onMounted(() => {
const storageColumns = localStorage.getItem("upgrade_columns");
if (storageColumns) {
state.showColumns = JSON.parse(storageColumns);
}
search();
});
watch(
() => state.showColumns,
_.debounce((v) => {
localStorage.setItem("upgrade_columns", JSON.stringify(v));
}, 1000)
);
const sizeChange = (v) => {
state.pager.currentPage = 1;
state.pager.pageSize = v;
search();
};
const pageChange = (v) => {
state.pager.currentPage = v;
search();
};
</script>

浙公网安备 33010602011771号