功能
-
render渲染
-
formatter 格式化数据
-
工具栏(如:ColumnSet列设置、Refresh刷新)
-
分页
-
根据屏幕变化表格高度自适应
-
个人封装简单封装table,不想过渡封装,如有需要,自行根据业务需求加以封装
效果
目录
render 封装
/*
* @Author: vhen
* @Date: 2024-03-24 22:59:41
* @LastEditTime: 2024-03-24 22:59:51
* @Description: 现在的努力是为了小时候吹过的牛逼!
* @FilePath: \vhen-vue3-admin-pro\src\components\Table\src\render.ts
*
*/
export default {
props: {
row: Object,
render: Function,
index: Number,
column: {
type: Object,
default: null,
},
},
setup: function (props: any, context: any) {
return () =>
props.render({
...props,
...context.attrs,
})
},
}
ColumnSet 列设置封装
<template>
<el-dropdown trigger="click">
<el-button icon="Setting" size="default" circle></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<span class="title">列设置</span>
<Draggable class="table-column-setting" v-model="state.columnSet" item-key="prop">
<template #item="{ element, index }">
<div>
<el-icon><Operation /></el-icon>
<el-checkbox
:checked="!element.hidden"
@click.native.stop
:disabled="element.disabled"
@change="(checked) => checkColumn(checked, index)"
>
{{ element.label }}
</el-checkbox>
</div>
</template>
</Draggable>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script lang="ts" setup name="ColumnSet">
import { onMounted, reactive, watch } from "vue";
import Draggable from "vuedraggable";
const props = defineProps({
columns: {
type: Array,
default: () => [],
},
});
const emits = defineEmits(["columnSetting"]);
const state: any = reactive({
columnSet: [],
});
/**
* @description 初始化列设置
*/
const initColumnSet = () => {
const columnSet = props.columns.map((item: any) => {
return {
label: item.label,
prop: item.prop,
hidden: false,
disabled: false,
};
});
return columnSet;
};
/**
* 选择列显示或隐藏
* @param checked
* @param index
*/
const checkColumn = (checked, index) => {
state.columnSet[index].hidden = !checked;
let obj: any = {
show: [],
hide: [],
};
state.columnSet.map((item) => {
let key = item.hidden ? "hide" : "show";
obj[key].push(item.hidden);
});
if (obj.show.length < 3) {
state.columnSet.map((item, key) => {
if (!item.hidden) {
state.columnSet[key].disabled = true;
}
});
} else {
state.columnSet.map((item, key) => {
if (!item.hidden) {
state.columnSet[key].disabled = false;
}
});
}
};
onMounted(() => {
state.columnSet = initColumnSet();
});
watch(
() => state.columnSet,
(val) => {
emits("columnSetting", val);
},
{ deep: true },
);
</script>
<style lang="scss">
.el-dropdown-menu {
padding: 10px;
font-size: 14px;
.el-dropdown-menu__item {
display: flex;
flex-direction: column;
align-items: flex-start;
.title {
font-weight: bold;
margin-bottom: 5px;
}
.table-column-setting {
display: flex;
flex-direction: column;
max-height: 300px;
overflow-y: auto;
.el-checkbox {
.el-checkbox__input.is-checked + .el-checkbox__label {
color: #262626;
}
}
}
}
}
</style>
SimpleTable 表格封装
<template>
<div class="table-container">
<div class="table-tool" flex justify-between items-center h-11 p2 v-if="tool">
<div class="table-tool-left">
<slot name="ToolLeft"></slot>
</div>
<div class="table-tool-right">
<slot name="ToolRight"></slot>
<ColumnSet :columns="columns" @columnSetting="(v) => (state.columnSet = v)" mr-2 />
<el-button
type="primary"
cursor="pointer"
:icon="Refresh"
circle
@click="handleSizeChange"
/>
</div>
</div>
<div class="table-body">
<el-table
ref="tableRef"
v-bind="$attrs"
:data="tableData"
:height="tableHeight"
border
v-loading="loading"
:header-cell-style="{ background: '#f5f7fa' }"
>
<template v-if="$slots.append" slot="append">
<slot name="append"></slot>
</template>
<template v-for="item in renderColumns">
<el-table-column
v-if="['selection', 'index'].includes(item.prop)"
:key="`${item.prop}-type`"
:type="item.prop"
width="50"
v-bind="{ ...{ align: 'center' }, ...item }"
>
</el-table-column>
<el-table-column
v-else-if="item.render"
:key="item.prop + '-render'"
show-overflow-tooltip
v-bind="{ ...{ align: 'center' }, ...item }"
>
<template #default="{ row, $index }">
<Render
:column="item"
:index="$index"
:render="item.render"
:row="row"
v-bind="$attrs"
/>
</template>
</el-table-column>
<el-table-column
v-else-if="item.formatter"
:key="item.prop + '-formatter'"
show-overflow-tooltip
v-bind="{ align: 'center', ...item }"
>
<template #default="{ row }">
{{ item.formatter(row) }}
</template>
</el-table-column>
<el-table-column
v-else
:key="item.prop + '-default'"
show-overflow-tooltip
v-bind="{ align: 'center', ...item }"
></el-table-column>
</template>
<!-- 操作 -->
<el-table-column
v-if="$slots.action"
align="center"
fixed="right"
label="操作"
:width="actionWidth"
>
<template #default="scope">
<slot name="action" :scope="scope"></slot>
</template>
</el-table-column>
</el-table>
</div>
<div class="table-footer" flex justify-end items-center h-10 v-if="pageable">
<el-pagination
v-model:current-page="pageable.pageNum"
v-model:page-size="pageable.pageSize"
:page-sizes="pageSizes"
@size-change="handleSizeChange"
@current-change="initTableData"
layout="sizes, prev, pager, next"
background
small
:total="pageable.total"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { useEventListener } from "@/hooks/event/useEventListener";
import { Refresh } from "@element-plus/icons-vue";
import { computed, onMounted, reactive, ref } from "vue";
import ColumnSet from "./component/ColumnSet.vue";
import Render from "./render";
interface ColumnItem {
[key: string]: any;
}
interface PropsType {
api: any; // 接口封装函数
queryData?: object; // 请求参数
columns: ColumnItem[]; // 表格列配置
tool?: boolean; // 是否显示工具栏
pageable?: boolean; // 是否显示分页
actionWidth?: number; // 操作栏宽度
columnSet?: boolean; // 是否显示列设置
refresh?: boolean; // 是否显示刷新按钮
defaultPageSize?: number; // 默认的每页数量
pageSizes?: number[]; // 分页大小
pageParams?: {
// 分页参数
pageNum: number;
pageSize: number;
};
response?: {
// 请求响应返回项
data: string;
total: string;
};
onSuccess?: (res: any) => void; // 请求成功回调
onError?: (err: any) => void; // 请求失败回调
}
const props = withDefaults(defineProps<PropsType>(), {
columns: () => [],
tool: true,
pageable: true,
actionWidth: 200,
defaultPageSize: 10,
pageSizes: () => [10, 20, 50, 100],
pageParams: () => ({
pageNum: 1,
pageSize: 10,
}),
response: () => ({
data: "data",
total: "total",
}),
});
const tableRef = ref();
const loading = ref(false);
const tableHeight = ref<string | number>("100%");
const tableData = ref<any[]>([]);
const pageable = reactive({
pageNum: 1,
pageSize: 10,
total: 0,
});
let state = reactive({
columnSet: [],
});
/**
* @description: 列设置
*/
const renderColumns = computed(() => {
return state.columnSet.length > 0
? state.columnSet.reduce((prev: any, cur: any) => {
if (!cur.hidden) {
let columnByProp: any = props.columns.reduce((prev: any, cur: any) => {
prev[cur.prop] = cur;
return prev;
}, {});
prev.push(columnByProp[cur.prop]);
}
return prev;
}, [])
: props.columns;
});
/**
* @description: 初始化表格数据
*/
const initTableData = () => {
// 表格滚动条重置
tableRef.value.setScrollTop(0);
tableRef.value.setScrollLeft(0);
loading.value = true;
props
.api({ ...props.queryData, ...props.pageParams })
.then((res: any) => {
loading.value = false;
tableData.value = res[props.response.data];
pageable.total = res[props.response.total];
props.onSuccess?.(res);
})
.catch((err: any) => {
loading.value = false;
props.onError?.(err);
})
.finally(() => {
loading.value = false;
});
};
const calculateTableHeight = () => {
let mainDom = document.querySelector(".vhen-layout-main") as HTMLElement;
tableHeight.value = mainDom.offsetHeight - 86 - 40; // 86 = 顶部工具栏高度 + 底部分页高度 + mainDom上下padding
};
const handleSizeChange = (toFirstPage = true) => {
if (toFirstPage) {
pageable.pageNum = 1;
}
initTableData();
};
onMounted(() => {
pageable.pageSize = props.defaultPageSize;
initTableData();
calculateTableHeight();
});
useEventListener({
el: window,
name: "resize",
listener: () => {
calculateTableHeight();
},
});
defineExpose({
init: handleSizeChange,
});
</script>
<style lang="scss" scoped>
.table-tool {
border-width: 1px 1px 0 1px;
border-style: solid;
border-color: var(--el-border-color-lighter);
}
</style>
组件使用
<template>
<div>
<SimpleTable :api="initData" :columns="columns">
<template #ToolLeft>
<div>用户表</div>
</template>
<template #action="scope">
<el-button type="primary" @click="handleEdit(scope)">编辑</el-button>
<el-button type="danger" @click="handelDelete(scope)">删除</el-button>
</template>
</SimpleTable>
</div>
</template>
<script lang="tsx" setup>
import { SimpleTable } from "@/components/Table";
const columns = [
{ label: "#", hidden: false, prop: "selection" },
{ label: "姓名", hidden: false, prop: "name" },
{ label: "性别", prop: "sex", hidden: false, formatter: (row) => (row.sex ? "男" : "女") },
{
label: "地址",
prop: "address",
hidden: false,
},
{
label: "状态",
width: 100,
hidden: false,
render(props) {
const { row } = props;
return (
<div>
{row.state ? (
<el-tag effect="dark" type="success">
启用
</el-tag>
) : (
<el-tag effect="dark" type="warning">
禁用
</el-tag>
)}{" "}
</div>
);
},
},
{ label: "日期", hidden: false, prop: "date" },
];
const tableData = [
{
name: "小莫",
sex: 0,
address: "福建",
state: 0,
date: "2016-05-03",
},
{
name: "小紫",
sex: 0,
address: "深圳",
state: 1,
date: "2016-05-02",
},
{
name: "小白",
sex: 1,
address: "北京",
state: 1,
date: "2016-05-04",
},
{
name: "小红",
sex: 0,
address: "上海",
state: 0,
date: "2016-05-01",
},
];
const initData = () => {
return new Promise((resolve) => {
let newArr: any = tableData;
for (let i = 0; i < 100; i++) {
newArr.push(tableData[Math.floor(Math.random() * 4) + 1]);
}
setTimeout(() => {
let data = {
data: newArr,
total: 100,
};
resolve(data);
}, 1000);
});
};
const handleEdit = (row: any) => {
console.log("Edit", row);
};
const handelDelete = (row: any) => {
console.log("Delete", row);
};
</script>