vue3 + antd二次封装a-table组件

前置条件

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>
  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值