前置条件
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-buttonstyle="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-tablev-bind="{loading: localLoading,pagination: localPagination,rowSelection: localRowSelection,rowKey: 'id',...$attrs,}":dataSource="dataSource"><!-- 自定义渲染单元格 --><template #bodyCell="{ text, record, index, column }"><!-- 操作列 --><template v-if="column.dataIndex === 'operation'"><av-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) => {// 页码重置1if (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"><MyTableref="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>