# antdv 中 table 组件

# 1. 介绍

ant-design-vue (4.1.2) 中的 table 组件

# 2. 注意

  1. 设置 resizable 时,需要留一列不设置 resizable,否则会造成拖拽异常
  2. 设置 width 时,需要留一列不设置 width,否则会造成表头与内容对不齐

# 3. 去掉分页器

示例:

<template>
  <a-table
    :pagination="false"
  >
  </a-table>
</template>

参考:

# 4. 列宽可拖拽调整

说明:

  1. 用于固定宽度的表格,列设置的宽度是 像素值
  2. 配合 ellipsis: true,表头、单元格 不会折行
  3. 至少需要一列(通常是最后一列) 不设置宽度且不可拖拽

代码:

<template>
  <a-table
    :columns="columns"
    @resizeColumn="handleResizeColumn"
  >
  </a-table>
</template>
<script>
export default {
  data() {
    return {
      columns: [
        { title: 'name', /* ... */ width: 200, ellipsis: true, resizable: true },
        { title: 'age', /* ... */ width: 200, ellipsis: true, resizable: true },
        { title: 'gender', /* ... */ ellipsis: true, resizable: false },
      ],
    };
  },

  methods: {
    handleResizeColumn(width, column) {
      column.width = width;
    },
  }
};
</script>

# 5. 单击选中行

<template>
  <a-table
    :data-source="list"
    :custom-row="customRow"
    :row-selection="rowSelection"
    rowKey="userId"
  >

  </a-table>
</template>
<script>
export default {
  data() {
    return {
      list: [
        { userId: '001', userName: 'zhangsan', /* ... */ },
        // ...
      ],

      rowSelection: {
        type: 'radio',
        selectedRowKeys: [],
        onChange: this.onSelectChange,
      },
    };
  },

  methods: {
    customRow(record, index) {
      return {
        onClick: () => {
          this.onClickRow(record);
        },
      };
    },

    onClickRow(record) {
      this.onSelectChange([record.userId], record);
    },

    onSelectChange(selectedRowKeys, record) {
      this.rowSelection.selectedRowKeys = selectedRowKeys;
    },
  },
};
</script>
<style>
/* 隐藏 radio 那列 */
.ant-table-selection-column {
  padding: 0!important;
}
.ant-table-selection-col {
  width: 0!important;
}
.ant-radio-wrapper {
  display: none;
}
</style>

# 6. 复选框和单选框的使用

说明:

  • 需要指定 record 中的主键名称
  • 无论是 checkbox 还是 radio,onChange 的方法签名不变
  • onChange 只能获取当前分页已选择的记录
  • 如果想跨分页多选,则需要根据 onSelect、onSelectAll 中的 selected 值 设置 selectedRowKeys

示例:

<template>
  <a-table
    row-key="userId"
    :data-source="list"
    :columns="columns"
    :row-selection="{ 
      type: 'checkbox',  /* 'checkbox' | 'radio' */
      columnWidth: 32, 
      selectedRowKeys, 
      onChange,
      getCheckboxProps,
    }"
  >
    ...
  </a-table>
</template>
<script>
export default {
  data() {
    return {
      selectedRowKeys: [],

      list: [
        { userId: '1', userName: 'zhangsan' },
      ]
    }
  },

  methods: {
    onChange(selectedRowKeys, selectedRows) {
      console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
      this.selectedRowKeys = selectedRowKeys;
    },
    
    getCheckboxProps(record) {
      return {
        // 全部默认禁止选中
        // disabled: true,
        
        // 禁用某些记录
        disabled: record.status === 'disabled',
        
        // defaultChecked: record.state == 1,
      };
    }
  }
}
</script>

参考:

  • node_modules/ant-design-vue/es/table/interface.d.ts
  • node_modules/ant-design-vue/es/checkbox/interface.d.ts

# 7. 自定义渲染单元格

注意:

  • 使用 bodyCell 插槽的话,customRender 会被覆盖

示例:

<template>
  <a-table
    :columns="columns"
  >
    <template #bodyCell="{ column }">

      <!-- age 列上使用 customRender 不生效  -->
      <template v-if="column.key === 'age'">
        <CheckOutlined />
      </template>

    </template>
  </a-table>
</template>
<script lang="tsx">
import { defineComponent } from 'vue';

export default defineComponent({
  data() {
    return {
      columns: [
        // ...
        {
          title: 'Sheet Name',
          width: 100,
          dataIndex: 'sheetName',
          key: 'sheetName',
          
          /**
           * @param column - {title: 'Sheet Name', width: 100, dataIndex: 'sheetName', key: 'sheetName', customRender}
           */
          customRender({ text, record, index, column }) {
            /*
             处理 tsx 报错误的问题:
             
              tsconfig.json

                  "noImplicitAny": false,
             */
            return (
              <div>{ text }</div>
            );
          },
        },
      ]
    };
  },
});
</script>

# 8. 表头排序功能

<template>
  <a-table
    :columns="columns"
    :show-sorter-tooltip="false"
  />
</template>
<script lang="tsx">
import { defineComponent } from 'vue';

export default defineComponent({
  data() {
    return {
      columns: [
        // ...
        {
          title: 'Sheet Name',
          width: 100,
          dataIndex: 'sheetName',
          key: 'sheetName',
          
          sorter: (prevRecord, currRecord) => {
            const result = prevRecord.sheetName > prevRecord.sheetName

            return result ? 1 : -1;
          }
        },
      ]
    };
  },
});
</script>

# 9. 固定高度

说明:

  • 设置的高度不是 数据表格整体的高度,仅仅是 记录的容器的高度

示例:

<template>
  <a-table
    :scroll={ y: '200px' }
  >
  </a-table>
</template>

# 10. 排序

示例:

<template>
  <a-table
    :columns="columns"
    @change="handleChange"
  />
</template>
<script lang="ts">
import type { SorterResult } from 'ant-design-vue/es/table/interface';

export default {
  data() {
    return {
      columns: [
        { 
          title: 'Timestamp', 
          dataIndex: 'timeStamp', 
          key: 'timeStamp', 
          sorter: true, // 后台排序,侦听通过 change 事件
          defaultSortOrder: 'descend', // null | 'ascend' | 'descend'
        },
      ]
    }
  },
  
  methods: {
    handleChange(pagination, filters, sorter: SorterResult) {
      const {
        field, // timeStamp
        order, // 'descend' | 'ascend' | null;
      } = sorter;
    }
  }
}
</script>

# 11. 设置行的样式类

说明:

  • 额外添加 样式类名称

示例:

<template>
  <a-table
    :row-class-name="rowClassName"
  />
</template>
<script lang="ts">
import type { SorterResult } from 'ant-design-vue/es/table/interface';

export default {
  methods: {
    rowClassName({ __status }: IRecord, index) {
      if (__status === UploadStatus.Uploaded) {
        return 'lw-is-uploaded';
      }
      
      if (__status === UploadStatus.NotUploaded) {
        return 'lw-is-not-uploaded';
      }

      return '';
    },
  }
}
</script>

# 12. 设置单元格的样式

示例:

<template>
  <a-table
    :columns="columns"
  />
</template>
<script lang="ts">
export default {
  data() {
    return {
      columns: [
        { 
          title: 'Timestamp', 
          dataIndex: 'timeStamp', 
          key: 'timeStamp', 
          customCell: this.customCell,
        },
      ]
    }
  },
  
  methods: {
    customCell(record) {
      let className = '';

      if (record.xyz === 0 ) {
        className = 'lw-table-cell-danger';
      }

      return {
        class: className,
      }
    },
  }
}
</script>

# 13. 拖拽排序

说明:

  • 通过 dnd 相关 API 完成行的拖拽排序

示例:

<template>
  <a-table
    class="a-table-dnd"
    :custom-row="customRow"
  />
</template>
<script lang="ts">

interface IRecord {
  [key: string]: string,
  index: number,
}

export default {
  data() {
    return {
      dnd: {
        enabled: false,
        sourceRecord: {} as IRecord,
        targetRecord: {} as IRecord,
      },
    }
  },
  
  methods: {
    customRow(record: IRecord, index: number) {
      return {
        // 鼠标移入
        onMouseenter: (event: any) => {
          event.target.draggable = this.dnd.enabled; // 让你要拖动的行可以拖动,默认不可以
        },

        // 开始拖拽
        onDragstart: (event: MouseEvent) => {
          event.stopPropagation();
          this.dnd.sourceRecord = record;
        },

        // 拖动元素经过的元素
        onDragover: (event: MouseEvent) => {
          event.preventDefault();
        },

        // 拖动到达目标元素
        onDragenter: (event: MouseEvent) => {
          event.preventDefault();
          event.stopPropagation();

          // 此时 record 为进入的“行”的数据,高亮 进入的行
          const rowElements = document.querySelectorAll('.a-table-dnd .ant-table-row');

          Array.from(rowElements || []).forEach((rowElement, i) => {
            if (i === index) {
              rowElement.classList.add('ant-table-row-selected');
            } else {
              rowElement.classList.remove('ant-table-row-selected');
            }
          });
        },
        // 鼠标松开(如果在表格外松开,则不触发这个事件)
        onDrop: (event: MouseEvent) => {
          event.stopPropagation();
          
          this.dnd.targetRecord = record;

          // - 交换两个行

          const { sourceRecord, targetRecord } = this.dnd;
          
          const sourceIndex = this.LIST.findIndex((item) => item.index === sourceRecord.index);
          const targetIndex = this.LIST.findIndex((item) => item.index === targetRecord.index);

          this.LIST[sourceIndex] = targetRecord;
          this.LIST[targetIndex] = sourceRecord;

          this.LIST.forEach((item, i) => {
            item.index = i + 1;
          });
        },
        // 拖拽结束(无论是否在表格外松开鼠标,一定会调用)
        onDragend() {
          // 清除高亮效果
          const rowElements = document.querySelectorAll('.a-table-dnd .ant-table-row');

          Array.from(rowElements || []).forEach((rowElement) => {
            rowElement.classList.remove('ant-table-row-selected');
          });
        },
      };
    },
  }
}
</script>

参考:

# 14. 列过滤

示例:

<template>
  <a-table
    :columns="columns"
  >
    <template #customFilterIcon="{ column }">
      <!-- 
        column.filterList.length === 0
          true: 显示高亮的 过滤图标,标志已设置过滤条件
          false: 显示普通的 过滤图标
      -->
    </template>

    <template #customFilterDropdown="{ confirm, column }" >
      <!-- 
        执行 confirm() 会关闭过滤容器弹窗
      -->
      <FilterPanel
        v-model:value="column.filterList"
        :field-title="column.title"
        :field-name="column.key"
        @confirm="confirmFilters(confirm)"
        @reset="resetFilters(confirm)"
      />
    </template>

  </a-table>
</template>
<script lang="ts">
export default {
  data() {
    return {

      columns: [
        { 
          title: 'name', dataIndex: 'name', key: 'name', 
          customFilterDropdown: true, // antdv 的属性,标志该列 自定义过滤 
          filterList: [], // 自定义属性,用于存放过滤规则
        },
      ]
    }
  },
  
  methods: {
    confirmFilters(closePanel: () => void) {
      closePanel();
      this.doFilterList();
    },

    resetFilters(closePanel: () => void) {
      closePanel();
      this.doFilterList();
    },

    doFilterList() {
      const { columns } = this;

      this.LIST = (this as any).result.filter((record) => {
        const isMatched = columns.every((column: any) => {
          const filterList: IFilterRecord[] = column.filterList || [];
          return isMatchFilterList(record[column.key], filterList);
        });

        return isMatched;
      });
    },
    
  }
}
</script>
本章目录