ElementPlus中Table的改造

ElementPlus中关于Table的官方示例
  <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>

每次都得写一堆的el-table-column重复代码。为了通用性,会考虑自定义Table组件,那么Columns就需要传递Column数据构建,某些Column数据需要自定义渲染,此时要么使用h()函数要么写jsx组件。这一点都不vue!

参考AntDesignVue中关于的Table的官方示例:

 <a-table :columns="columns" :data-source="data">
    <template #headerCell="{ column }">
      <template v-if="column.key === 'name'">
        <span>
          <smile-outlined />
          Name
        </span>
      </template>
    </template>
    <template #bodyCell="{ column, record }">
      <template v-if="column.key === 'name'">
        <a>
          {{ record.name }}
        </a>
      </template>
      <template v-else-if="column.key === 'action'">
        <span>
          <a>Invite 一 {{ record.name }}</a>
          <a-divider type="vertical" />
          <a>Delete</a>
        </span>
      </template>
    </template>
  </a-table>
</template>
<script lang="ts" setup>
import { SmileOutlined } from '@ant-design/icons-vue';
const columns = [
  {
    name: 'Name',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: 'Action',
    key: 'action',
  },
];

const data = [
  {
    key: '1',
    name: 'John Brown',
  },
  {
    key: '2',
    name: 'Jim Green',
  }
];
</script>

此处暴露 #headerCell和 #bodyCell插槽以供外部传递自定义组件,列以columns数据形式构建。

自定义MyTable组件,支持自适应高度,分页,排序,多选,列显示隐藏功能

<template>
  <div class="container">
    <div class="inner_table" ref="elementRef">
      <el-table
        :data="tableData"
        :default-sort="sorter"
        :="$attrs"
        ref="tableRef"
        v-loading="loading"
        style="width: 100%"
        :max-height="state.tableHeight"
        @selection-change="handleSelectionChange"
        @sort-change="handleSortChange"
      >
        <template #default>
          <el-table-column v-if="rowSelection" type="selection" :width="30" />
          <el-table-column
            v-for="item in finalColumns"
            :key="item.prop"
            :="item"
          >
            <template #header="{ column }">
              <slot name="cellHeader" :column="column"> </slot>
            </template>
            <template #default="{ row, column }">
              <slot
                v-if="slots.cellBody"
                name="cellBody"
                :row="row"
                :column="column"
              >
                {{
                  item.formatter
                    ? item.formatter(row, column, row[item.prop])
                    : row[item.prop]?.toString()
                }}
              </slot>
            </template>
          </el-table-column>
          <el-table-column
            fixed="right"
            label="操作"
            align="center"
            :width="150"
          >
            <template #header="{ column }">
              <template v-if="slots.actionHeader">
                <slot name="actionHeader" :column="column">{{
                  column.label
                }}</slot>
              </template>
              <div
                v-else
                style="display: inline-flex; align-items: center; gap: 8px"
              >
                {{ column.label }}
                <el-popover placement="bottom" :width="column.width">
                  <template #reference>
                    <el-icon><Setting /></el-icon>
                  </template>
                  <el-checkbox-group
                    v-model="state.showColumns"
                    @change="showColumnsChange"
                  >
                    <el-checkbox
                      v-for="column in columns"
                      :label="column.label"
                      :value="column.prop"
                    />
                  </el-checkbox-group>
                </el-popover>
              </div>
            </template>
            <template #default="{ row }">
              <slot name="actionBody" :row="row"></slot>
            </template>
          </el-table-column>
        </template>
        <template #empty>
          <el-empty />
        </template>
      </el-table>
    </div>
    <div class="inner_footer" v-if="slots.footerLeft || pager">
      <div>
        <slot name="footerLeft"></slot>
      </div>
      <el-pagination
        v-model:current-page="pager.currentPage"
        v-model:page-size="pager.pageSize"
        :total="pager.total"
        :page-sizes="state.pageSizes"
        @size-change="sizeChange"
        @current-change="currentChange"
        :layout="pager.layout || 'total, sizes, prev, pager, next, jumper'"
      />
    </div>
  </div>
</template>

<!--
Slot介绍
actionHeader:操作栏头部插槽,没有时显示Label且携带列显示隐藏复选框
actionBody:操作栏每行插槽,没有时显示空
cellHeader:数据栏头部插槽,没有时显示Label
cellBody:数据栏每行插槽,没有时显示数据值
footerLeft:底部左侧附件插槽,没有时显示空
-->
<script setup>
import _ from "lodash";
import {
  ref,
  onMounted,
  onUnmounted,
  reactive,
  useSlots,
  useAttrs,
  watch,
  computed,
} from "vue";
const slots = useSlots();
const attrs = useAttrs();

const props = defineProps({
  rowSelection: { default: true, type: Boolean }, // 开启多选
  loading: { default: false, type: Boolean }, // 加载状态
  tableData: { default: [], type: Array }, // 数据
  columns: { default: [], type: Array, required: true }, // 列配置
  showColumns: { default: [], type: Array }, // 显示列
  pager: { default: { currentPage: 1, pageSize: 10, total: 0 }, type: Object }, // 分页
  sorter: { default: {}, type: Object }, // 默认排序
});

const state = reactive({
  tableHeight: 600,
  pageSizes: [10],
  showColumns: [],
});
const tableRef = ref();

const emits = defineEmits([
  "sizeChange",
  "pageChange",
  "selectChange",
  "sortChange",
  "update:showColumns",
]);

const elementRef = ref(null);
// 动态监听表单容器并计算可分页选项
const resizeObserver = new ResizeObserver((entries) => {
  for (let entry of entries) {
    const cr = entry.contentRect;
    if (cr.height) {
      state.tableHeight = cr.height;

      const showHeader =
        attrs["show-header"] === undefined ? true : attrs["show-header"];
      // 动态计算最小的分页size
      const headerHeight = showHeader ? 40 : 0;
      const size = Math.floor((cr.height - headerHeight) / 41.7);
      emits("sizeChange", size);
      const pageSizeOptions = [size];
      let nextSize = Math.ceil(size / 10) * 10;
      if (nextSize != size) {
        pageSizeOptions.push(nextSize);
      }
      while (nextSize * 2 < 200) {
        nextSize = nextSize * 2;
        pageSizeOptions.push(nextSize);
      }
      state.pageSizes = pageSizeOptions;
    }
  }
});

watch(
  () => props.showColumns,
  (v) => {
    state.showColumns = v;
  }
);

onMounted(() => {
  if (elementRef.value) {
    resizeObserver.observe(elementRef.value);
  }
  state.showColumns = props.showColumns.length
    ? props.showColumns
    : _.map(props.columns, "prop");
});

onUnmounted(() => {
  resizeObserver.disconnect();
});

// 显示/隐藏列控制后的最终显示列
const finalColumns = computed(() => {
  return _.filter(props.columns, (o) => state.showColumns.includes(o.prop));
});
// 显示列数据改变
const showColumnsChange = (value) => {
  emits("update:showColumns", value);
};

// 页码改变
function currentChange(value) {
  emits("pageChange", value);
}

// 页数量改变
function sizeChange(value) {
  emits("sizeChange", value);
}

// 选中行改变
const handleSelectionChange = (val) => {
  emits("selectChange", val);
};

// 排序改变
const handleSortChange = (val) => {
  emits("sortChange", val);
};

// 清除选项
function clearSelects() {
  tableRef.value.refElTable.clearSelection();
}
// 清楚排序
function clearSort() {
  tableRef.value.refElTable.clearSort();
}
// 对外暴露函数
defineExpose({
  clearSelects,
  clearSort,
});
</script>

<style lang="less" scoped>
.container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;

  .inner_footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px;
  }

  .inner_table {
    height: calc(100% - 48px);

    :deep(.el-table .el-table__cell) {
      height: 41.5px;
      padding: 0px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }
}
</style>

使用方法

 

 <MyTable
        :tableData="state.tableData"
        :columns="state.columns"
        :loading="state.loading"
        :pager="state.pager"
        v-model:showColumns="state.showColumns"
        @selectChange="
          (v) => {
            state.selects = v;
          }
        "
        @sizeChange="sizeChange"
        @pageChange="pageChange"
      >
        <template #cellHeader="{ column }">
          <template v-if="column.rawColumnKey === 'name'">
            <el-icon><Clock /></el-icon> {{ column.label }}
          </template>
        </template>
       <template #cellBody="{ row, column }">
          <template v-if="column.rawColumnKey === 'name'">
          <a>{{name}}</a>
          </template>
        </template>
        <template #actionBody="{ row }">
          <div class="operation">
            <el-button link type="primary" >执行</el-button>
            <el-button
              link
              type="primary"
              @click="download(row)"
              >下载</el-button>
          </div>
        </template>
      </MyTable>

<script setup>
import { watch, onMounted, reactive, ref, nextTick } from "vue";
import { getFilesList } from "@/services/files.js";
import MyTable from "@/components/MyTable.vue";
import _ from "lodash";

const state = reactive({
  loading: false,
  pager: {
    currentPage: 1,
    pageSize: 0,
    total: 0,
  },
  columns: [
    {
      prop: "name",
      label: "名称",
      showOverflowTooltip: true,
      align: "center",
    },
  ],
  showColumns: [],
  tableData: [],
  selects: [], // 选中的行
});

// 获取路径下所有文件
const getFiles = async () => {
  if (state.pager.pageSize) {
    state.loading = true;
    const res = await getFilesList({
      page: state.pager.currentPage,
      pageSize: state.pager.pageSize,
    });
    if (res.success) {
      state.tableData = res.data.items;
      state.pager.total = res.data.itemTotal;
    }
    state.loading = false;
  }
};

// 根据路径查询其下文件列表
const search = async () => {
  await getFiles();
};

onMounted(() => {
  const storageColumns = localStorage.getItem("upgrade_columns");
  if (storageColumns) {
    state.showColumns = JSON.parse(storageColumns);
  }
  search();
});

watch(
  () => state.showColumns,
  _.debounce((v) => {
    localStorage.setItem("upgrade_columns", JSON.stringify(v));
  }, 1000)
);

const sizeChange = (v) => {
  state.pager.currentPage = 1;
  state.pager.pageSize = v;
  search();
};
const pageChange = (v) => {
  state.pager.currentPage = v;
  search();
};
</script>
posted @ 2025-11-20 11:20  卓扬  阅读(1)  评论(0)    收藏  举报