行列形式展示一组结构化的数据或内容。
适用:用于大量数据展示或需要横纵对比的场景。
不适用:非结构化数据的展示或无横纵对比诉求的场景,此时可用列表。
通用原则
- 表格中允许展示静态数据或操作;允许单列折叠展开;允许批量操作
- 高度根据内容自适应
- 表格中信息重要层级从左到右依次递减
- 左侧第一栏通常为标题,操作栏放于最右侧;标题栏和操作栏可固定,只滚动内容区
- 表头的标题保证简洁可读,不可折行,尽量避免「…」
表头可在批量操作时变为操作栏
构成

- 操作:当有滚动条的时候,表头可吸顶。
- 左侧为表格操作,默认操作数不超过 3 个,第 4 个操作收到「 更多 」中,可搭配图标,未勾选时批量操作状态为 Disable
- 右侧为简易搜索框、高级搜索开关、表格刷新按钮及展示列设置按钮
- 表头:当有滚动条的时候,表头可吸顶。
- 批量勾选(可选):默认一直出现,用户随时勾选后,批量操作按钮由 Disable 状态变为 Normal 状态
- 列标题:
- 言简意赅,不允许折行,可带单位,如「 数量(个) 」,超过可「 … 」;建议不超过 6 个汉字
- 有左右滚动条的时候,首列允许固定
- 操作列:有左右滚动条的时候,允许固定
- 内容区:允许上下左右滚动。
- 序列 / 多选(可选)
- 展开/收起(有树形结构的时候使用)
- 单行操作:操作数默认不超过 4 个,第 5 个操作收起到「 更多 」中
- 分页
行为
批量操作
- 当表格支持批量操作时,应出现 Checkbox 列
- 批量操作区出现在表格之上
- 操作按钮允许搭配图标

方式1:点击表头的全选 Checkbox
- 列表进入批量操作状态
- 所有行全部被选中
- 再次点击反选所有列,退出批量操作状态,回到正常状态

方式2:选择单列
- 选中列表最左侧的 checkbox,进入批量操作状态
- 表头的全选 Checkbox 为半选状态
- 此时点击表头全选 Checkbox 为选择所有列,再次点击表头全选 Checkbox 为清除所有选择,退出批量操作状态,回到正常状态

单列操作
- 列操作通常包括排序、筛选、拖拽列宽度等常规操作
- 表格数据为空时,批量操作均不可用
- 支持的操作有解释说明、排序、筛选
- 解释说明:移入「解释说明 icon」出现
Tooltip,对标题进行解释

- 排序:
- 点击列标题或「排序图标」即生效
- 可反复点击在正排、倒排、默认排序中切换
- 生效的时候,「排序图标」须展示在列标题右侧且高亮
- 点击列标题或「排序图标」即生效

- 筛选:
- 若当前列支持筛选,鼠标 hover 列标题出现「筛选 icon」
- 点击「筛选 icon」唤起进一步操作
- 生效的时候,「筛选 icon」须展示在列标题右侧且高亮
- 当筛选规则失效的时候,「筛选 icon」消失,并回到默认状态
- 若当前列支持筛选,鼠标 hover 列标题出现「筛选 icon」

调整列宽度
- 鼠标移入列标题栏的分割线时,可直接拖拽分隔线,调整列宽度
- 单列可根据业务需要设定最小/大宽度

单行操作
- 操作采用文字按钮
- 操作数默认不超过 4 个,第 5 个操作收起到「 更多 」中
展开/收起
- 展开收起表格取消表格斑马线
- 当列收起时,点击「 + 」展开下一级
- 当列展开时,点击「 - 」收起所有下一级

容器相关
- 三大状态:单列有 Normal、Hover、Selected 3 种状态

- 加载:内容区加载

- 空列表

样式
布局
- 可在独立页面、弹窗、滑块面板(SlidePanel)中使用
- 表格标题的对齐应与内容对齐
- 内容行:文字和图标首选左对齐,若数字内容中不会出现小数点则首选右对齐。有小数点则小数点对齐
开发
代码演示

表格不建议封装成组件,所以提供以下模板样式,更多API,请参考https://www.antdv.com/components/table-cn/
.y-common-table-contanier{.y-common-table-operator-group{margin-bottom: 16px;.ant-btn{&+.ant-btn{margin-left: 14px;}}}.y-table-column-action-button{display: flex;align-items: center;.spacing{width: 1px;height: 8px;background-color: #ebeef5;margin: 0 8px;}}}
<template><div class="y-common-table-contanier"><div class="y-common-table-operator-group"><a-button type="primary" icon="play-circle"> 操作 </a-button><a-button icon="poweroff"> 关机 </a-button><a-button icon="loading"> 重启 </a-button><a-dropdown><a-menu slot="overlay" @click="handleMenuClick"><a-menu-item key="1"> 1st item </a-menu-item><a-menu-item key="2"> 2nd item </a-menu-item><a-menu-item key="3"> 3rd item </a-menu-item></a-menu><a-button>更多<a-icon type="down" /></a-button></a-dropdown></div><a-table bordered :components="components" :columns="columns" :data-source="data" :pagination="false"><a slot="name" slot-scope="text" class="y-button-link">{{ text }}</a><span slot="tags" slot-scope="tags"><a-tag v-for="tag in tags" :key="tag" :color="tag === 'loser' ? 'volcano' : tag.length > 5 ? 'geekblue' : 'green'">{{ tag.toUpperCase() }}</a-tag></span><template v-slot:action><div class="y-table-column-action-button"><y-button-action text="新增" /><span class="spacing"></span><y-button-action text="修改" /><span class="spacing"></span><y-button-action text="复制" /><span class="spacing"></span><y-button-action text="删除" /><span class="spacing"></span><a-dropdown :trigger="['click']"><y-button-action text="更多" /><a-menu><a-menu-item><a href="javascript:;">a</a></a-menu-item></a-menu></a-dropdown></div></template></a-table></div></template><script>import Vue from 'vue';import VueDraggableResizable from 'vue-draggable-resizable';Vue.component('vue-draggable-resizable', VueDraggableResizable);const columns = [{title: '标题',dataIndex: 'name',key: 'name',scopedSlots: { customRender: 'name' },width: 200,filters: [{text: 'Joe',value: 'Joe',},{text: 'Jim',value: 'Jim',},],// specify the condition of filtering result// here is that finding the name started with `value`onFilter: (value, record) => record.name.indexOf(value) === 0,sorter: (a, b) => a.name.length - b.name.length,},{title: 'Age',dataIndex: 'age',key: 'age',width: 200,},{title: 'AddressAddressAddressAddress',dataIndex: 'address',key: 'address',ellipsis: true,width: 200,},{title: 'Tags',key: 'tags',dataIndex: 'tags',scopedSlots: { customRender: 'tags' },width: 200,},{title: '操作',key: 'action',width: 300,scopedSlots: { customRender: 'action' },},];const data = [{key: '1',name: '内容文本,点击可进入详情',age: 32,address: 'New York No. 1 Lake Park New York NoNew York NoNew York NoNew York NoNew York NoNew York NoNew York No',tags: ['nice', 'developer'],},{key: '2',name: '内容文本,点击可进入详情',age: 42,address: 'London No. 1 Lake Park',tags: ['loser'],},{key: '3',name: '内容文本,点击可进入详情',age: 32,address: 'Sidney No. 1 Lake Park',tags: ['cool', 'teacher'],},];const draggingMap = {};columns.forEach((col) => {draggingMap[col.key] = col.width;});const draggingState = Vue.observable(draggingMap);const ResizeableTitle = ({ props, children }) => {let thDom = null;const { key, ...restProps } = props;const col = columns.find((col) => {const k = col.dataIndex || col.key;return k === key;});if (!col.width) {return <th {...restProps}>{children}</th>;}const onDrag = (x) => {draggingState[key] = 0;col.width = Math.max(x, 1);};const onDragstop = () => {draggingState[key] = thDom.getBoundingClientRect().width;};return (<th {...restProps} v-ant-ref={(r) => (thDom = r)} width={col.width} class="resize-table-th">{children}<vue-draggable-resizablekey={col.key}class="table-draggable-handle"w={10}x={draggingState[key] || col.width}z={1}axis="x"draggable={true}resizable={false}onDragging={onDrag}onDragstop={onDragstop}></vue-draggable-resizable></th>);};export default {data() {this.components = {header: {cell: ResizeableTitle,},};return {data,columns,selectedRowKeys: [], // Check here to configure the default column};},methods: {onSelectChange(selectedRowKeys) {console.log('selectedRowKeys changed: ', selectedRowKeys);this.selectedRowKeys = selectedRowKeys;},handleMenuClick(e) {console.log('click', e);},},};</script><style lang="less">.resize-table-th {position: relative;.table-draggable-handle {height: 100% !important;bottom: 0;left: auto !important;right: -5px;cursor: col-resize;touch-action: none;}}.table-draggable-handle {transform: none !important;position: absolute;}</style>
