目录

1. element-plus

1.1 功能描述

1.2 优缺点

1.3 全屏按钮组件

1.4 表格行密度组件

1.5 ElementPlus 表格组件

1.5.1 表格组件接收的数据 props

1.5.2 表格组件发送的事件 emit

1.5.3 表格顶部操作行

1.5.4 表格模板封装

2. vxe

2.1 功能介绍

2.2 vxe-table 表格组件

2.2.1 vxe-table 数据重载

2.2.2  表格组件发送的事件 emit

2.2.3 vxe-table 常见 API

2.2.4 表格模板封装

3. surely-table

3.1 功能介绍

3.2 surely-table 表格组件

3.2.1 表格组件发送的事件 emit

3.2.2 surely-table 常见 API

3.2.3 表格模板封装

3.2.4 调整 surely-table 样式


1. element-plus

Table 表格 | Element Plusa Vue 3 based component library for designers and developershttps://element-plus.gitee.io/zh-CN/component/table.html

1.1 功能描述

表头:

  • 左侧操作按钮 —— 设置表格密度、设置表格全屏
  • 右侧操作按钮 —— 针对 表格多行数据 进行操作
  • 可以添加表格标题
  • 可以调整 表头行 对齐方式
  • 可以筛选

表身:

  • 表格操作列 —— 针对 表格单行数据 进行操作
  • 可以调整 表身行 对齐方式
  • 可以自定义单元格样式

1.2 优缺点

  • 优点:较为常见的一种表格,配置相对简单
  • 缺点:数据量大时非常卡顿

1.3 全屏按钮组件

可以通过以下方式控制表格全屏状态:

  • ESC 按键
  • 点击切换全屏按钮 
<!--
 * @Description: 表格全屏按钮
 * @Author: lyrelion
 * @Date: 2022-04-12 10:41:17
 * @LastEditors: lyrelion
 * @LastEditTime: 2022-09-29 09:11:12
-->

<template>
  <el-tooltip
    effect="dark"
    :content="fullScreen ? '取消全屏' : '全屏'"
    placement="top"
    :auto-close="1000"
  >
    <span class="tooltip-outline-none" @click="handleSetFullScreen()">
      <el-icon v-if="!fullScreen" class="pointer tooltip-icon" :size="18">
        <full-screen />
      </el-icon>
      <i v-if="fullScreen" class="fa fa-compress pointer tooltip-icon"></i>
    </span>
  </el-tooltip>
</template>

<script lang="ts">
import {
  reactive,
  toRefs,
  defineComponent,
  onMounted,
  onUnmounted,
} from 'vue';

export default defineComponent({
  name: 'TFullScreenButton',
  emits: ['full-screen-change'],
  setup(props, { emit }) {
    // 响应式数据
    const state = reactive({
      // 是否全屏
      fullScreen: false,
    });

    /**
     * esc 按键事件
     */
    const keydownEvent = (event: KeyboardEvent) => {
      if (document.fullscreenElement) {
        document.exitFullscreen();
      }
      if (event.keyCode === 27) {
        state.fullScreen = false;
      }
      // 发送事件,切换全屏状态
      emit('full-screen-change', state.fullScreen);
    };

    /**
     * esc 窗口尺寸变化事件
     */
    const windowResizeEvent = () => {
      state.fullScreen = document.fullscreen;
      // 发送事件,切换全屏状态
      emit('full-screen-change', state.fullScreen);
    };

    /**
     * 设置控制全屏显示状态值
     */
    const handleSetFullScreen = () => {
      // 设置状态值
      state.fullScreen = !state.fullScreen;
      // 浏览器全屏模式
      if (state.fullScreen && document.body.requestFullscreen) {
        document.body.requestFullscreen();
      } else if (document.fullscreenElement) {
        // 浏览器退出全屏模式
        document.exitFullscreen();
      }
      // 发送事件,切换全屏状态
      emit('full-screen-change', state.fullScreen);
    };

    onMounted(async () => {
      // 监听 esc 按键事件  退出全屏
      window.addEventListener('keyup', keydownEvent, false);
      // 监听窗口尺寸改变事件  退出全屏
      window.addEventListener('resize', windowResizeEvent, false);
    });

    onUnmounted(() => {
      // 移除监听事件
      window.removeEventListener('keyup', keydownEvent);
      window.removeEventListener('resize', windowResizeEvent);
    });

    return {
      ...toRefs(state),
      handleSetFullScreen,
    };
  },
});
</script>

1.4 表格行密度组件

使用 el-dropdown 组装出一个下拉框

用户选中紧凑程度后,组件抛出用户所选信息

表格组件会接收此组件抛出的密度信息

<!--
 * @Description: 表格密度设置按钮
 * @Author: lyrelion
 * @Date: 2022-04-12 10:18:34
 * @LastEditors: lyrelion
 * @LastEditTime: 2022-04-12 10:40:56
-->

<template>
  <el-dropdown trigger="click" @command="setTableDensity">
    <el-tooltip content="密度" effect="dark" placement="top">
      <el-icon class="pointer tooltip-icon" :size="18">
        <operation />
      </el-icon>
    </el-tooltip>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item
          v-for="item in [
            { label: '正常', value: 'small' },
            { label: '紧凑', value: 'mini' },
            { label: '宽松', value: 'medium' },
          ]"
          :key="item.label"
          :class="{ active: tableSize === item.value }"
          :command="item.value"
        >
          {{ item.label }}
        </el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script lang="ts">
import { reactive, toRefs, defineComponent } from 'vue';

export default defineComponent({
  name: 'TDensityButton',
  emits: ['table-size-change'],
  setup(props, { emit }) {
    // 响应式数据
    const state = reactive({
      // 表格尺寸
      tableSize: 'mini',
    });

    /**
     * 设置表格尺寸(密度)
     * @param command 表格尺寸(密度)
     */
    const setTableDensity = (command: string) => {
      state.tableSize = command;
      // 发送事件,告诉父组件表单尺寸改变了
      emit('table-size-change', state.tableSize);
    };

    return {
      ...toRefs(state),
      setTableDensity,
    };
  },
});
</script>

1.5 ElementPlus 表格组件

1.5.1 表格组件接收的数据 props

表头配置项数据类型

  // 表格表头配置项
  interface TableHeaderOptions {
    prop?: string; // 当前列对应键名
    label: string; // 当前列对应表头
    width?: string | number; // 当前列对应宽度
    minWidth?: string | number; // 对应列的最小宽度
    align?: string; // 当前列数据对齐方式
    headerAlign?: string; // 当前列表头对齐方式
    fixed?: true | 'left' | 'right' | ''; // 当前列固定方式
    sortable?: string | boolean | undefined; // 排序方式
    slot?: string | undefined; // 插槽
    type?: string; // 表格类型
    action?: boolean; // 是否为操作列
  }

具体接收的 props 如下所示

还写了 inheritAttrs: true, 用于继承 elementPlus 组件本身就能接收的属性

  <!-- <span>表格组件接收到的 $attrs: {{ $attrs }}</span> -->

  inheritAttrs: true,

  props: {
    // 表格数据
    tableData: {
      type: Array,
      required: true,
      default: () => [],
    },
    // 表头配置项
    tableHeaderOpitons: {
      type: Array as PropType<TableHeaderOptions[]>,
      required: true,
      default: () => [],
    },
    // 判断是否禁用多选方法(返回true不禁用)
    selectable: {
      type: Function,
      default: (row: any, index: number) => true,
    },
    // 页码
    pageNum: {
      type: Number,
      default: 1,
    },
    // 每页条数
    pageSize: {
      type: Number,
      default: 10,
    },
    // 是否开启序号列(开启的话,必须传入每页条数/页码)
    openIndexColumn: {
      type: Boolean,
      default: false,
    },
    // 是否开启选择列
    openSelectColumn: {
      type: Boolean,
      default: false,
    },
    // 要修改背景的列 index 数组
    changeBgColumnIndex: {
      type: Array,
      default: () => [],
    },
    // 要修改的背景色
    changeBgColumnColor: {
      type: String,
      default: '#F5F7FA',
    },
    // 操作列是否显示
    showOperationColumn: {
      type: Boolean,
      default: true,
    },
    // 表格顶部左侧列显隐控制
    showTableLeftBtn: {
      type: Object,
      default: () => ({
        densityBtn: true,
        fullScreenBtn: true,
      }),
    },
    // 是否开启斑马纹
    openStripe: {
      type: Boolean,
      default: true,
    },
  },

1.5.2 表格组件发送的事件 emit

emits: [
  'table-sort-change', // 表格列排序事件
  'table-choose-change', // 表格行选中事件
  'table-cell-click', // 单元格点击事件
  'update:fullScreen', // 更新全屏状态
],

表格列排序事件:需要根据业务,重新调整拼接出来的排序依据字段 orderBy

  // 表格排序接受的参数
  interface TableSortColumn {
    order: string;
    prop: string;
    column: {
      property: string;
      sortable: string;
      label: string;
      [key: string]: unknown;
    };
  }

    /**
     * 表格排序
     * @param sortColumn
     */
    const onSortChange = (sortColumn: CommonBaseCrud.TableSortColumn) => {
      let orderBy = '';
      if (sortColumn && sortColumn.column && sortColumn.column.sortable) {
        orderBy = `${sortColumn.column.sortable} ${sortColumn.order.replace('ending', '')}`;
      } else {
        orderBy = '';
      }
      emit('table-sort-change', orderBy);
    };

表格行选中事件:将选择的表格行数据抛出去

    // 响应式数据
    const state = reactive({
      // 用户选择的列表项数组
      multipleSelection: [] as any,
    });

    /**
     * 表格多选改变
     * @description 当选择多条表格数据时,被选中的数据会被存储到 state 响应数据中去
     * @param val 当前选中的全部表格数据(以数组的形式进行存储)
     */
    const onSelectChange = (val: any) => {
      state.multipleSelection = val;
      emit('table-choose-change', state.multipleSelection);
    };

单元格点击事件:将点击的单元格数据抛出去

    /**
     * 处理单元格点击事件
     */
    const onCellClick = (row: any, column: any, event: any, cell: any) => {
      emit('table-cell-click', { row, column, event, cell });
    };

1.5.3 表格顶部操作行

左侧按钮(表格密度、表格全屏)

右侧按钮组(添加表格数据、多条删除、下载导出、授权等)

    <el-row
      v-if="showOperationColumn"
      align="middle"
      justify="space-between"
      type="flex"
    >
      <!-- 左侧按钮组(密度、全屏) -->
      <el-col :span="8">
        <!-- 表格密度设置按钮 -->
        <t-density-button @table-size-change="setTableDensity" />
        <!-- 表格全屏设置按钮 -->
        <t-full-screen-button @full-screen-change="handleSetFullScreen" />
      </el-col>

      <!-- 右侧按钮组 -->
      <el-col :span="16">
        <div class="flex flex-end">
          <!-- 表格按钮组 - 插槽 -->
          <slot name="tableBtnSlot"></slot>
        </div>
      </el-col>
    </el-row>

设置表格尺寸(密度)、控制全屏状态:

    // 响应式数据
    const state = reactive({
      // 表格尺寸
      tableSize: 'mini',
      // 是否全屏
      fullScreen: false,
    });

    /**
     * 设置表格尺寸(密度)
     * @param command 表格尺寸(密度)
     */
    const setTableDensity = (command: string) => {
      state.tableSize = command;
      setTableBodyTop();
    };

    /**
     * 控制全屏状态
     */
    const handleSetFullScreen = (val: any) => {
      state.fullScreen = val;
      emit('update:fullScreen', val);
    };

element 计算的高度,没有带上小数,导致边框线被压着,需要重新设置(此方法在 onMounted 、重新设置表格密度等 需要重新渲染表格 的状态下,需要执行)

    /**
     * element 计算的高度,没有带上小数,导致边框线被压着,需要重新设置
     */
    const setTableBodyTop = () => {
      setTimeout(() => {
        const tableDom = document.getElementsByClassName('el-table') as any;
        if (tableDom && tableDom.length > 0) {
          for (let i = 0; i < tableDom.length; i++) {
            // 获取高度
            const tableHeaderDom = tableDom[i].getElementsByClassName('el-table__header-wrapper')[0];
            const tableHeaderHeight = tableHeaderDom.getBoundingClientRect().height + 'px';
            // 设置top值
            const tableBody = tableDom[i].getElementsByClassName('el-table__fixed-body-wrapper');
            if (tableBody && tableBody[0]) {
              tableBody[0].style.top = tableHeaderHeight;
            }
            if (tableBody && tableBody[1]) {
              tableBody[1].style.top = tableHeaderHeight;
            }
            const tablePatch = tableDom[i].getElementsByClassName('el-table__fixed-right-patch');
            if (tablePatch && tablePatch[0]) {
              tablePatch[0].style.height = tableHeaderHeight;
            }
          }
        }
      }, 200);
    };

1.5.4 表格模板封装

选择列(多选按钮):

  • 根据接收的 props openSelectColumn 决定是否渲染;
  • 根据 selectable 决定是否为选择列,此列永远固定在首列;

序号列:

  • 根据接收的 props openIndexColumn && pageSize && pageNum 决定是否渲染;
  • 此列内容固定,采用 (pageNum - 1) * pageSize + scope.$index + 1 动态计算当前行索引;

普通列:

  • 使用 template 遍历 props tableNormalOptions 表头配置;
  • 给 el-table-column 动态绑定 key、prop、label、width、min-width、align、header-align、fixed、sortable、show-overflow-tooltip、resizable 等属性;
  • 关于 resizable 属性 —— 对应列是否可以通过拖动改变宽度(需要在 el-table 上设置 border 属性为真)

普通列插槽:

  • 如果使用插槽,则用户可以自定义展示内容
  • 如果不用插槽,则默认对展示信息做判空处理,并增加 title 鼠标滑过显示完整信息的属性
<!-- 自定义列内容格式 -->
<template v-if="tOptions.slot" #default="scope">
  <slot :name="tOptions.slot" :scope="scope"></slot>
</template>
<!-- 默认展示格式(此处做了判空处理, 如果为空, 则展示小短横线) -->
<template v-else #default="scope">
  <span :title="`${isNoVal(scope.row[tOptions.prop])}`">
    {{ `${numFont(isNoVal(scope.row[tOptions.prop]))}` }}
  </span>
</template>
    /**
     * 是否有值
     */
    const isNoVal = (val?: any) => {
      let newVal = val;
      if (!val && val !== 0) {
        newVal = '-';
      }
      return newVal;
    };

文字溢出隐藏:

  • 如果用户使用了插槽,则不展示 tooltip(因为合并数据很多的话,会导致 tooltip 显示在最顶部,影响展示效果)
  • 如果用户没有使用插槽,则根据是否超过 20 个字,决定是否展示 tooltip;超过 20 个字了就手动截断(这个是根据项目业务决定的)
    :show-overflow-tooltip="tOptions.slot ? false : tooltipBool"

    // 响应式数据
    const state = reactive({
      // 控制tooltip的显示
      tooltipBool: false,
    });

    /**
     * 值是否超过20个字 超过用...代替
     */
    const numFont = (val?:string) => {
      let newVal = val;
      if (newVal) {
        if (newVal.length > 20) {
          const vals = newVal.slice(21);
          newVal = newVal.replace(vals, '...');
          state.tooltipBool = true;
        } else {
          state.tooltipBool = false;
        }
      }
      return newVal;
    };

多级表头:根据表头配置单项中,是否包含 child 属性决定,整体实现逻辑和单表头一致

操作列:不用于表格顶部的操作列,表格内部的操作列通常是针对单行数据进行操作,所以它里面直接放了个插槽

如何区分表格 操作列 / 普通列 的数据呢?他们都是放在 tableNormalOptions 中的,参考下方:

    // 过滤 操作项 之后的列配置
    const tableNormalOptions = computed(() => props.tableHeaderOpitons.filter((item) => !item.action));
    // 操作项列配置
    const tableActionOptions = computed(() => props.tableHeaderOpitons.find((item) => item.action));

    /*
     * console.log('过滤 操作项 之后的配置', tableNormalOptions.value);
     * console.log('操作项', tableActionOptions.value);
     */

整体效果:

    <el-table
      ref="tableRef"
      :data="tableData"
      :cell-style="changeCellStyle"
      :span-method="spanMethod"
      :size="tableSize"
      tooltip-effect="dark"
      sortable
      border
      stripe
      v-bind="$attrs"
      @selection-change="onSelectChange"
      @sort-change="onSortChange"
      @cell-click="onCellClick"
    >
      <!-- 选择列(多选按钮) -->
      <el-table-column
        v-if="openSelectColumn"
        :type="'selection'"
        :header-align="'center'"
        :align="'center'"
        :fixed="'left'"
        :selectable="selectable"
        :resizable="false"
      ></el-table-column>

      <!-- 序号列 -->
      <el-table-column
        v-if="openIndexColumn && pageSize && pageNum"
        :type="'index'"
        :align="'center'"
        :fixed="'left'"
        :label="'序号'"
        :width="55"
        :min-width="55"
        :resizable="false"
      >
        <template #default="scope">
          <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
        </template>
      </el-table-column>

      <!-- 普通列 -->
      <template
        v-for="(tOptions, tIndex) of tableNormalOptions"
        :key="tIndex"
      >
        <!-- 单表头 -->
        <el-table-column
          v-if="!tOptions.child"
          :key="tIndex"
          :prop="tOptions.prop"
          :label="tOptions.label"
          :width="tOptions.width ? tOptions.width : ''"
          :min-width="tOptions.minWidth ? tOptions.minWidth : ''"
          :align="tOptions.align ? tOptions.align : 'center'"
          :header-align="tOptions.headerAlign ? tOptions.headerAlign : 'center'"
          :fixed="tOptions.fixed ? tOptions.fixed : false"
          :sortable="tOptions.sortable ? tOptions.sortable : false"
          :show-overflow-tooltip="tOptions.slot ? false : tooltipBool"
          :resizable="tOptions.resizable !== null ? tOptions.resizable : true"
        >
          <!-- 自定义列内容格式 -->
          <template v-if="tOptions.slot" #default="scope">
            <slot :name="tOptions.slot" :scope="scope"></slot>
          </template>
          <!-- 默认展示格式(此处做了判空处理, 如果为空, 则展示小短横线) -->
          <template v-else #default="scope">
            <span :title="`${isNoVal(scope.row[tOptions.prop])}`">
              {{ `${numFont(isNoVal(scope.row[tOptions.prop]))}` }}
            </span>
          </template>
        </el-table-column>

        <!-- 多表头 -->
        <el-table-column v-else :label="tOptions.label" :header-align="'center'">
          <el-table-column
            v-for="(childOptions, cIndex) of tOptions.child"
            :key="cIndex"
            :prop="childOptions.prop"
            :label="childOptions.label"
            :width="childOptions.width ? childOptions.width : ''"
            :min-width="childOptions.minWidth ? childOptions.minWidth : ''"
            :align="childOptions.align ? childOptions.align : 'center'"
            :header-align="childOptions.headerAlign ? childOptions.headerAlign : 'center'"
            :sortable="childOptions.sortable ? childOptions.sortable : false"
            :show-overflow-tooltip="childOptions.slot ? false : true"
            :resizable="childOptions.resizable !== null ? childOptions.resizable : true"
          >
            <!-- 自定义列内容格式 -->
            <template v-if="childOptions.slot" #default="scope">
              <slot :name="childOptions.slot" :scope="scope"></slot>
            </template>
            <!-- 默认展示格式(此处做了判空处理, 如果为空, 则展示小短横线) -->
            <template v-else #default="scope">
              <span :title="`${isNoVal(scope.row[childOptions.prop])}`">
                {{ `${isNoVal(scope.row[childOptions.prop])}` }}
              </span>
            </template>
          </el-table-column>
        </el-table-column>
      </template>

      <!-- 操作列 -->
      <el-table-column
        v-if="tableActionOptions"
        :width="tableActionOptions.width ? tableActionOptions.width : ''"
        :min-width="tableActionOptions.minWidth ? tableActionOptions.minWidth : ''"
        :header-align="'center'"
        :align="'center'"
        :fixed="tableActionOptions.fixed ? tableActionOptions.fixed : 'right'"
        label="操作"
        :resizable="false"
      >
        <template #default="scope">
          <slot :name="'actionSolt'" :scope="scope"></slot>
        </template>
      </el-table-column>
    </el-table>

2. vxe

vxe-table v4https://vxetable.cn/#/table/scroll/scroll

 

2.1 功能介绍

针对 elementplus 数据量大时,页面卡顿的问题,推荐使用 vxe-table 解决(因为免费^_^)

虚拟滚动(最大可以支撑 10w 列、30w 行)

为了实现组件的通用性,vxe-table 组件的 全屏按钮、密度控制按钮、表格组件接收的 props、表格组件发出的事件 emits 等封装逻辑,基本参考 1 中的内容,此处不做赘述

优点:性能比 ElementPlus 更高

缺点:当合并行高度 超过 表格高度时,会出现一片空白

2.2 vxe-table 表格组件

2.2.1 vxe-table 数据重载

要等 vxe 组件加载完成后,渲染数据,也就是执行 vxe.reloadData(tableData);

    const xTable = ref({} as any);

    onMounted(() => {
      const tableRef = xTable.value;
      if (tableRef) {
        console.log(tableRef, ' === vxe 表格实例 tableReftableReftableRef');
        tableRef.reloadData(props.tableData);
      }
    });

    // 监听数据的变化,重新加载数据
    watch(
      () => props.tableData,
      () => {
        const tableRef = xTable.value;
        if (tableRef) {
          tableRef.reloadData(props.tableData);
        }
      },
      {
        deep: true,
      },
    );

vxe-table 重载 reloadData() 执行时,还原滚动条 scrollTop 的位置:

	const scrollY = this.$refs.showTable.$refs.xTable.$el.scrollTop;

    this.$nextTick(() => {
      this.$refs.showTable.reloadData(this.tableData);
      setTimeout(() => {
        this.$refs.showTable.$refs.xTable.$el.scrollTop = scrollY;
      }, 100);
    })

注意事项:

  • scrollTop 的值,需要精准的定位到滚动条所属的标签内,这样才能获得正确的值,否则都是 0
  • 不能缺少 nextTick,如果还是不生效,就加一下 setTimeout

2.2.2  表格组件发送的事件 emit

表格行选中事件:将选择的表格行数据抛出去

    /**
     * 表格多选改变
     * @description 当选择多条表格数据时,被选中的数据会被存储到 state 响应数据中去
     * @param val 当前选中的全部表格数据(以数组的形式进行存储)
     */
    const onSelectChange = (val: any) => {
      if (Array.isArray(val)) {
        state.multipleSelection = val;
      } else {
        state.multipleSelection = val.records;
      }
      emit('table-choose-change', state.multipleSelection);
    };

单元格点击事件:将点击的单元格数据抛出去

    /**
     * 处理单元格点击事件
     */
    const onCellClick = (cellInfos: any) => {
      emit('table-cell-click', cellInfos);
    };

2.2.3 vxe-table 常见 API

row-config —— 行配置信息

useKey

是否需要为每一行的 VNode 设置 key 属性(非特殊情况下不需要使用)

boolean

 

false

v4.2.0

keyField

自定义行数据唯一主键的字段名(默认自动生成)

string

 

_X_ROW_KEY

v4.2.0

isCurrent

当鼠标点击行时,是否要高亮当前行

boolean

 

false

v4.1.5

isHover

当鼠标移到行时,是否要高亮当前行

boolean

 

false

v4.1.5

height

只对 show-overflow 有效,每一行的高度

number

 

 

 

seq-config —— 序号配置项

startIndex

请使用 seqMethod

number

seqMethod

自定义序号的方法,返回处理后的值

({ row, rowIndex, column, columnIndex }) => number

 

 

:seq-config="{ seqMethod: setSeqStartIndex }"

    /**
     * @description: 设置序列号的值
     * @param {*} data 单条数据
     * @return {*}返回处理后的值
     */
    const setSeqStartIndex = (data: { rowIndex: number }) => {
      const { rowIndex } = data;
      return (props.pageNum - 1) * props.pageSize + rowIndex + 1;
    };

column-config —— 列配置信息

useKey

是否需要为每一列的 VNode 设置 key 属性(非特殊情况下不需要使用)

boolean

 

isCurrent

当鼠标点击列头时,是否要高亮当前列

boolean

 

isHover

当鼠标移到列头时,是否要高亮当前头

boolean

 

resizable

每一列是否启用列宽调整

boolean

false

width

每一列的宽度

number, string

auto, px, %

minWidth

每一列的最小宽度

number, string

auto, px, %

2.2.4 表格模板封装

    <vxe-table
      ref="xTable"
      :size="tableSize"
      border
      stripe
      show-overflow
      height="500px"
      max-height="500px"
      :row-config="{ isHover: true, useKey: true }"
      :seq-config="{ seqMethod: setSeqStartIndex }"
      :column-config="{ resizable: true }"
      v-bind="$attrs"
      :span-method="spanMethod"
      @sort-change="onSortChange"
      @checkbox-change="onSelectChange"
      @checkbox-all="onSelectChange"
      @cell-click="onCellClick"
    >
      <!-- 选择列(多选按钮) -->
      <vxe-column
        v-if="openSelectColumn"
        type="checkbox"
        width="48"
        align="center"
        fixed="left"
        :resizable="false"
      ></vxe-column>
      <!-- 序号列 -->
      <vxe-column
        v-if="openIndexColumn && pageSize && pageNum"
        type="seq"
        title="序号"
        width="52"
        fixed="left"
        align="center"
        :resizable="false"
      >
      </vxe-column>
      <!-- 普通列 -->
      <template v-for="(tOptions, tIndex) of tableNormalOptions" :key="tIndex">
        <!-- 单表头 -->
        <vxe-column
          v-if="!tOptions.child"
          :key="tIndex"
          :title="tOptions.label"
          :field="tOptions.prop"
          :width="tOptions.width ? tOptions.width : ''"
          :min-width="tOptions.minWidth ? tOptions.minWidth : ''"
          :header-align="tOptions.headerAlign ? tOptions.headerAlign : 'center'"
          :align="tOptions.align ? tOptions.align : 'center'"
          :fixed="tOptions.fixed ? tOptions.fixed : ''"
          :sortable="tOptions.sortable ? true : false"
          :sort-by="tOptions.sortable ? tOptions.sortable : ''"
          :resizable="tOptions.resizable !== null ? tOptions.resizable : true"
        >
          <!-- 自定义列内容格式 -->
          <template v-if="tOptions.slot" #default="scope">
            <slot :name="tOptions.slot" :scope="scope"></slot>
          </template>
          <!-- 默认展示格式(此处做了判空处理, 如果为空, 则展示小短横线) -->
          <template v-else #default="scope">
            {{ `${isNoVal(scope.row[tOptions.prop])}` }}
          </template>
        </vxe-column>

        <!-- 多表头 -->
        <vxe-colgroup
          v-else
          :title="tOptions.label"
          :header-align="tOptions.headerAlign ? tOptions.headerAlign : 'center'"
        >
          <vxe-column
            v-for="(childOptions, cIndex) of tOptions.child"
            :key="cIndex"
            :title="childOptions.label"
            :field="childOptions.prop"
            :width="childOptions.width ? childOptions.width : ''"
            :min-width="childOptions.minWidth ? childOptions.minWidth : ''"
            :header-align="childOptions.headerAlign ? childOptions.headerAlign : 'center'"
            :align="childOptions.align ? childOptions.align : 'center'"
            :fixed="childOptions.fixed ? childOptions.fixed : ''"
            :sortable="childOptions.sortable ? true : false"
            :sort-by="childOptions.sortable ? childOptions.sortable : ''"
            :resizable="childOptions.resizable !== null ? childOptions.resizable : true"
            :span-method="spanMethod"
          >
            <!-- 自定义列内容格式 -->
            <template v-if="childOptions.slot" #default="scope">
              <slot :name="childOptions.slot" :scope="scope"></slot>
            </template>
            <!-- 默认展示格式(此处做了判空处理, 如果为空, 则展示小短横线) -->
            <template v-else #default="scope">
              {{ `${isNoVal(scope.row[childOptions.prop])}` }}
            </template>
          </vxe-column>
        </vxe-colgroup>
      </template>
      <!-- 操作列 -->
      <vxe-column
        v-if="tableActionOptions"
        :width="tableActionOptions.width ? tableActionOptions.width : ''"
        :min-width="tableActionOptions.minWidth ? tableActionOptions.minWidth : ''"
        title="操作"
        fixed="right"
        align="center"
        :resizable="false"
      >
        <template #default="scope">
          <slot :name="'actionSolt'" :scope="scope"></slot>
        </template>
      </vxe-column>
    </vxe-table>

<style>
  .vxe-table--fixed-right-wrapper{
    height: 100% !important;
  }
</style>

 

 

3. surely-table

Surely Vuehttps://www.surely.cool/

3.1 功能介绍

// surely-table

npm i --save @surely-vue/table

// xe-utils JavaScript 函数库、工具类

npm install xe-utils

// ant-design-vue

npm install ant-design-vue --save

优点:多行合并不再卡顿

缺点: 收费(╯T□T)╯︵┻━┻,当然了免费也可以用,但是会有水印及控制台报错……

参考:commit bf2ac1e8f190cab892b4ae285427206bf2a40ced 添加大数据量表格(合并)示例

3.2 surely-table 表格组件

3.2.1 表格组件发送的事件 emit

单元格点击事件:将点击的单元格数据抛出去

    /**
     * 处理单元格点击事件
     */
    const handleCellClick = (record:any) => ({
      onClick: () => {
        emit('table-cell-click', record);
      }, // 点击行
    });

3.2.2 surely-table 常见 API

bordered —— 是否展示外边框和列边框 boolean

columns —— 表格列的配置描述 array

https://www.surely.cool/doc/api#columnicon-default.png?t=M85Bhttps://www.surely.cool/doc/api#column

dataSource —— 数据数组 object[]

scroll —— 表格是否可滚动,也可以指定滚动区域的宽、高 object

pagination —— 分页器

rowKey —— 表格行 key 的取值,可以是字符串或一个函数 string|Function(record):string

customCell —— 设置单元格属性, column 如配置了 customCell, 优先使用 column.customCell   Function(obj: {record: any; rowIndex: number; column: ColumnType})

autoHeaderHeight —— 是否自动表头高度,开启后会全量加载表头部分,有一定的性能损耗

3.2.3 表格模板封装

使用这个表格主要是为了轻松实现合并,所以这个表格不加 多选、操作列 了

    <s-table
      :animate-rows="false"
      bordered
      :columns="tableHeaderOpitons"
      :data-source="tableData"
      :scroll="{ y: 400 }"
      :pagination="false"
      :row-key="(record,index)=>{return index}"
      :custom-cell="handleCellClick"
      stripe
      auto-header-height
      size="small"
    >
      <template #bodyCell="{ column, record, index }">
        <slot v-if="column.slot" :name="column.slot" :scope="{ column, record, index }"></slot>
        <template v-else>
          {{ `${numFont(isNoVal(record[column.dataIndex]))}` }}
        </template>
      </template>
    </s-table>

3.2.4 调整 surely-table 样式

.surely-table-wrapper {
  /* 外边框 */
  .surely-table-bordered {
    border: 1px solid var(--border-color1);
  }

  /* 表头颜色 */
  .surely-table-header-cell {
    background-color: var(--table-header-background-color);
  }

  /* 鼠标滑过颜色 */
  .surely-table-row.surely-table-row-hover {
    background: none !important;
  }

  .surely-table-row.surely-table-row-hover .surely-table-cell:not(.surely-table-cell-hidden) {
    background-color: var(--table-hover-background) !important;
  }

  /* 斑马线颜色 */
  .surely-table.surely-table-stripe .surely-table-body .surely-table-row-odd:not(.surely-table-row-selected) {
    background: none;
  }

  .surely-table.surely-table-stripe .surely-table-body .surely-table-row-odd:not(.surely-table-row-selected) .surely-table-cell:not(.surely-table-cell-hidden) {
    background-color: var(--table-stripe-background);
  }

  .surely-table-fix-right {
    background: #FFF;
  }

  .surely-table-cell {
    border-color: var(--border-color1);
  }

  .surely-table-header {
    position: relative;
    z-index: 5;
    margin-bottom: -1px;
  }

  .surely-table-header-scrollbar {
    background: var(--table-header-background-color);

    &:after {
      background: var(--border-color1);
    }
  }

  .surely-table-body {
    .surely-table-cell {
      border-top: 1px solid var(--border-color1);
      border-bottom: none;
    }

    .surely-table-center {
      .surely-table-cell {
        border-left: 1px solid var(--border-color1);
      }

      .surely-table-cell:not(:last-child) {
        margin-left: -1px;
        border-right: none;
      }
    }
  }

  .surely-table-header-cell {
    color: var(--font-color1);
    font-size: 0.875rem;
  }

  .surely-table-row {
    color: var(--font-color2);
    font-size: 0.875rem;
  }
}

.surely-table-cell-hidden {
  border: none !important;
  visibility: initial;
}

.surely-table-cell-hidden .surely-table-cell-inner {
  visibility: hidden;
}

.surely-table > div:not([class]),
.surely-table-body > div:not([class]) {
  visibility: hidden;
}

//  提示框
.ant-tooltip {
  font-size: 12px;

  .ant-tooltip-inner {
    min-height: auto;
  }
}

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐