vue3 elemetPlus 简单封装el-table表格与分页

290 阅读3分钟

功能

  • render渲染

  • formatter 格式化数据

  • 工具栏(如:ColumnSet列设置、Refresh刷新)

  • 分页

  • 根据屏幕变化表格高度自适应

  • 个人封装简单封装table,不想过渡封装,如有需要,自行根据业务需求加以封装

效果

image.png

目录

image.png

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>

image.png