前置条件
vue版本 v3.3.11
ant-design-vue版本 v4.1.1
内容梗概
二次封装a-table
组件,大大提高工作效率和降低项目维护成本;
先看效果图
代码区域
utils.js
文件
// 用于模拟接口请求
export const getRemoteTableData = (data = [], time = 1000) => {
return new Promise((resolve) => {
setTimeout(() => {
const retObj = {
list: data,
total: 100,
pageSize: 10,
pageNum: 1,
}
resolve(retObj)
}, time)
})
}
// 指定范围随机数
export const getRandomNumber = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
// 判断空值
export const isEmpty = (value) => {
if (Array.isArray(value)) {
return !value.length
} else if (Object.prototype.toString.call(value) === "[object Object]") {
return !Object.keys(value).length
} else {
return [null, undefined, ''].includes(value)
}
}
// 数字格式化
export const formatNumber = (num) =>
num ? (num + "").replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, "$&,") : (isEmpty(num) ? '' : 0)
// 百分比格式化
export const formatPercent = (percent, n = 2) => isEmpty(percent) ? '' : `${(+percent).toFixed(n)}%`
// 金额格式化
export const formatMoney = (num) => isEmpty(num) ? "" : formatNumber(num.toFixed(2))
my-table.vue
组件
<template>
<div>
<div class="attach-buttons-wrap">
<a-button
style="margin-right: 10px"
v-bind="btn.props"
@click="btn.onclick({ selectedRowKeys })"
v-for="btn in attachButtons"
:key="btn.name"
:disabled="
typeof btn?.props?.disabled === 'function'
? btn?.props?.disabled(selectedRowKeys)
: btn?.props?.disabled ?? false
"
>{{ btn.name }}</a-button
>
</div>
<a-table
v-bind="{
loading: localLoading,
pagination: localPagination,
rowSelection: localRowSelection,
rowKey: 'id',
...$attrs,
}"
:dataSource="dataSource"
>
<!-- 自定义渲染单元格 -->
<template #bodyCell="{ text, record, index, column }">
<!-- 操作列 -->
<template v-if="column.dataIndex === 'operation'">
<a
v-for="(item, itemIndex) in column.buttons"
:key="itemIndex"
@click="item.onclick({ text, record, index, column })"
:style="{
marginRight: '12px',
display: btnShow({ item, text, record, index, column })
? 'inline-block'
: 'none',
...(item.style || {}),
}"
>{{ item.name }}</a
>
</template>
<!-- 序号 -->
<template v-if="column.type === 'serial'">
{{
(localPagination.current - 1) * localPagination.pageSize + index + 1
}}
</template>
<!-- 百分比 -->
<template v-else-if="column.type === 'percent'">{{
formatPercent(text)
}}</template>
<!-- 数值 -->
<template v-else-if="column.type === 'number'">{{
formatNumber(text)
}}</template>
<!-- 金额 -->
<template v-else-if="column.type === 'money'">{{
formatMoney(text)
}}</template>
<!-- 列插槽 -->
<template v-else-if="columnSlots.includes(column.dataIndex)">
<template v-for="slot in columnSlots" :key="slot">
<slot
:name="`column-${slot}`"
v-bind="{ text, record, index, column }"
></slot>
</template>
</template>
<!-- 自定义列渲染 -->
<template v-else-if="typeof column?.customRender === 'function'">
<!-- 渲染customRender -->
<component
:is="column.customRender"
v-bind="{ text, record, index, column }"
></component>
</template>
</template>
<!-- 插槽透传 -->
<template v-for="(value, name) in $slots" v-slot:[name]="slotProps">
<slot :name="name" v-bind="slotProps"></slot>
</template>
</a-table>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, useSlots, unref } from "vue";
import { formatPercent, formatMoney, formatNumber } from "@/common/utils";
const props = defineProps({
data: {
type: [Function, Array],
default: () => [],
},
// 附属操作按钮
attachButtons: {
type: Array,
default: () => [],
},
});
const emits = defineEmits(["refresh"]);
const slots = useSlots();
const columnSlots = ref([]);
const createColumnSlots = () => {
columnSlots.value = Object.keys(slots)
.filter((x) => x.indexOf("column") !== -1)
.map((x) => x.split("-")[1]);
};
const btnShow = computed(
() =>
({ item, text, record, index, column }) =>
typeof item?.show == "function"
? item.show({ text, record, index, column })
: item.show ?? true
);
// 列表数据
const dataSource = ref([]);
// 分页
const localPagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showTotal: (total) => `共 ${total} 条记录`,
showSizeChanger: true,
showQuickJumper: true,
onChange: (current, size) => {
localPagination.current = current;
localPagination.pageSize = size;
loadData({ current, pageSize: size });
},
pageSizeOptions: ["10", "20", "30", "40", "50"],
});
// 是否分页
const isPagination = ref(false);
// loading状态
const localLoading = ref(false);
const selectedRowKeys = ref([]);
// 选择列
const onSelectChange = (rowKeys) => {
selectedRowKeys.value = rowKeys;
};
const localRowSelection = computed(() => {
return {
selectedRowKeys: unref(selectedRowKeys),
onChange: onSelectChange,
};
});
const loadData = (pagination) => {
localLoading.value = true;
const params = isPagination.value
? {
pageNo: pagination?.current
? pagination.current
: localPagination.current,
pageSize: pagination?.pageSize
? pagination.pageSize
: localPagination.pageSize,
}
: {};
if (!props.data) {
dataSource.value = [];
return;
}
if (Array.isArray(props.data)) {
dataSource.value = props.data;
localLoading.value = false;
} else {
props
.data(params)
.then((retObj) => {
const { list, total, pageSize, pageNum } = retObj;
isPagination.value = retObj.hasOwnProperty("list");
if (isPagination.value) {
localPagination.total = total || 0;
localPagination.pageSize = pageSize;
localPagination.pageNum = pageNum;
dataSource.value = list?.length ? list : [];
if (list?.length === 0 && localPagination.current > 1) {
localPagination.current--;
loadData();
}
} else {
dataSource.value = retObj?.length ? retObj : [];
}
})
.finally(() => (localLoading.value = false));
}
};
// 刷新表格数据
const refresh = (isInit = false) => {
// 页码重置1
if (isInit) {
localPagination.current = 1;
localPagination.total = 0;
emits("refresh");
}
loadData();
};
onMounted(() => {
createColumnSlots();
loadData();
});
defineExpose({ refresh });
</script>
<style lang="scss" scoped>
.attach-buttons-wrap {
margin-bottom: 10px;
}
</style>
使用该组件
<template>
<div style="padding: 40px">
<MyTable
ref="myTable"
:data="getDataFromApi"
:columns="columns"
:attachButtons="attachButtons"
>
<template #column-tags="{ record }">
<a-tag v-for="tag in record.tags" :key="tag">
{{ tag.toUpperCase() }}
</a-tag>
</template>
<template #headerCell="{ column }">
<template v-if="column.dataIndex === 'tags'">测试表头(列插槽)</template>
</template>
</MyTable>
</div>
</template>
<script lang="jsx" setup>
import MyTable from "@/components/table/index.vue";
import { ref } from "vue";
import { getRemoteTableData, getRandomNumber } from "@/common/utils";
const myTable = ref(null);
const columns = [
{
title: "序号",
dataIndex: "name",
type: "serial",
},
{
title: "姓名",
dataIndex: "name",
},
{
title: "年龄",
dataIndex: "age",
},
{
title: "性别",
dataIndex: "sex",
},
{
title: "士兵数量",
dataIndex: "score",
type: "number",
},
{
title: "铁骑兵占比",
dataIndex: "percent",
type: "percent",
},
{
title: "军费",
dataIndex: "price",
type: "money",
},
{
title: "标签",
dataIndex: "tags",
},
{
title: "自定义渲染列",
dataIndex: "custom",
customRender: ({record}) => <div><a>{record.name} </a><span style="color: green">年龄:{record.age}</span></div>
},
{
title: "操作",
dataIndex: "operation",
buttons: [
{
name: "查看",
onclick: ({ record }) => {
console.log("查看", record);
},
show: ({ record }) => record.name === "诸葛亮4",
},
{
name: "编辑",
onclick: ({ record }) => {
console.log("编辑", record);
},
},
{
name: "删除",
onclick: ({ record }) => {
console.log("删除", record);
},
style: {
color: "red",
},
},
],
width: 180,
},
];
const getDataFromApi = async () => {
let getData = Array.from({ length: 10 }, (_, i) => i).map((x, i) => ({
id: getRandomNumber(1, 10000000000000),
name: `诸葛亮${i + 1}`,
age: getRandomNumber(10, 100),
sex: `男`,
price: getRandomNumber(1000, 100000),
percent: getRandomNumber(1, 100),
score: getRandomNumber(100000, 1000000000),
tags: ["OK", "YES"],
}));
return await getRemoteTableData([...getData]);
};
const attachButtons = ref([
{
name: "刷新",
onclick: () => myTable.value.refresh(true)
},
{
name: "批量删除",
onclick: ({selectedRowKeys}) => {console.log('selectedRowKeys', selectedRowKeys)},
props: {
type: 'primary',
danger: true,
disabled: (selectedRowKeys) => !selectedRowKeys.length
}
},
{
name: "导出Excel",
onclick: () => {console.log('导出文件')},
props: {
type: 'primary',
},
}
])
</script>