行列形式展示一组结构化的数据或内容。

适用:用于大量数据展示或需要横纵对比的场景。
不适用:非结构化数据的展示或无横纵对比诉求的场景,此时可用列表。

通用原则

  • 表格中允许展示静态数据或操作;允许单列折叠展开;允许批量操作
  • 高度根据内容自适应
  • 表格中信息重要层级从左到右依次递减
  • 左侧第一栏通常为标题,操作栏放于最右侧;标题栏和操作栏可固定,只滚动内容区
  • 表头的标题保证简洁可读,不可折行,尽量避免「…」
  • 表头可在批量操作时变为操作栏

构成

image.png

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

行为

批量操作

  • 当表格支持批量操作时,应出现 Checkbox 列
  • 批量操作区出现在表格之上
  • 操作按钮允许搭配图标

image.png

方式1:点击表头的全选 Checkbox

  • 列表进入批量操作状态
  • 所有行全部被选中
  • 再次点击反选所有列,退出批量操作状态,回到正常状态

image.png

方式2:选择单列

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

image.png

单列操作

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

image.png

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

image.png

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

image.png

调整列宽度

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

image.png

单行操作

  • 操作采用文字按钮
  • 操作数默认不超过 4 个,第 5 个操作收起到「 更多 」中

image.png

展开/收起

  • 展开收起表格取消表格斑马线
  • 当列收起时,点击「 + 」展开下一级
  • 当列展开时,点击「 - 」收起所有下一级

image.png

容器相关

  • 三大状态:单列有 Normal、Hover、Selected 3 种状态

image.png

  • 加载:内容区加载

image.png

  • 空列表

image.png

样式

布局

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

开发

代码演示

image.png

表格不建议封装成组件,所以提供以下模板样式,更多API,请参考https://www.antdv.com/components/table-cn/

  1. .y-common-table-contanier{
  2. .y-common-table-operator-group{
  3. margin-bottom: 16px;
  4. .ant-btn{
  5. &+.ant-btn{
  6. margin-left: 14px;
  7. }
  8. }
  9. }
  10. .y-table-column-action-button{
  11. display: flex;
  12. align-items: center;
  13. .spacing{
  14. width: 1px;
  15. height: 8px;
  16. background-color: #ebeef5;
  17. margin: 0 8px;
  18. }
  19. }
  20. }
  1. <template>
  2. <div class="y-common-table-contanier">
  3. <div class="y-common-table-operator-group">
  4. <a-button type="primary" icon="play-circle"> 操作 </a-button>
  5. <a-button icon="poweroff"> 关机 </a-button>
  6. <a-button icon="loading"> 重启 </a-button>
  7. <a-dropdown>
  8. <a-menu slot="overlay" @click="handleMenuClick">
  9. <a-menu-item key="1"> 1st item </a-menu-item>
  10. <a-menu-item key="2"> 2nd item </a-menu-item>
  11. <a-menu-item key="3"> 3rd item </a-menu-item>
  12. </a-menu>
  13. <a-button>
  14. 更多
  15. <a-icon type="down" />
  16. </a-button>
  17. </a-dropdown>
  18. </div>
  19. <a-table bordered :components="components" :columns="columns" :data-source="data" :pagination="false">
  20. <a slot="name" slot-scope="text" class="y-button-link">{{ text }}</a>
  21. <span slot="tags" slot-scope="tags">
  22. <a-tag v-for="tag in tags" :key="tag" :color="tag === 'loser' ? 'volcano' : tag.length > 5 ? 'geekblue' : 'green'">
  23. {{ tag.toUpperCase() }}
  24. </a-tag>
  25. </span>
  26. <template v-slot:action>
  27. <div class="y-table-column-action-button">
  28. <y-button-action text="新增" />
  29. <span class="spacing"></span>
  30. <y-button-action text="修改" />
  31. <span class="spacing"></span>
  32. <y-button-action text="复制" />
  33. <span class="spacing"></span>
  34. <y-button-action text="删除" />
  35. <span class="spacing"></span>
  36. <a-dropdown :trigger="['click']">
  37. <y-button-action text="更多" />
  38. <a-menu>
  39. <a-menu-item>
  40. <a href="javascript:;">a</a>
  41. </a-menu-item>
  42. </a-menu>
  43. </a-dropdown>
  44. </div>
  45. </template>
  46. </a-table>
  47. </div>
  48. </template>
  49. <script>
  50. import Vue from 'vue';
  51. import VueDraggableResizable from 'vue-draggable-resizable';
  52. Vue.component('vue-draggable-resizable', VueDraggableResizable);
  53. const columns = [
  54. {
  55. title: '标题',
  56. dataIndex: 'name',
  57. key: 'name',
  58. scopedSlots: { customRender: 'name' },
  59. width: 200,
  60. filters: [
  61. {
  62. text: 'Joe',
  63. value: 'Joe',
  64. },
  65. {
  66. text: 'Jim',
  67. value: 'Jim',
  68. },
  69. ],
  70. // specify the condition of filtering result
  71. // here is that finding the name started with `value`
  72. onFilter: (value, record) => record.name.indexOf(value) === 0,
  73. sorter: (a, b) => a.name.length - b.name.length,
  74. },
  75. {
  76. title: 'Age',
  77. dataIndex: 'age',
  78. key: 'age',
  79. width: 200,
  80. },
  81. {
  82. title: 'AddressAddressAddressAddress',
  83. dataIndex: 'address',
  84. key: 'address',
  85. ellipsis: true,
  86. width: 200,
  87. },
  88. {
  89. title: 'Tags',
  90. key: 'tags',
  91. dataIndex: 'tags',
  92. scopedSlots: { customRender: 'tags' },
  93. width: 200,
  94. },
  95. {
  96. title: '操作',
  97. key: 'action',
  98. width: 300,
  99. scopedSlots: { customRender: 'action' },
  100. },
  101. ];
  102. const data = [
  103. {
  104. key: '1',
  105. name: '内容文本,点击可进入详情',
  106. age: 32,
  107. address: 'New York No. 1 Lake Park New York NoNew York NoNew York NoNew York NoNew York NoNew York NoNew York No',
  108. tags: ['nice', 'developer'],
  109. },
  110. {
  111. key: '2',
  112. name: '内容文本,点击可进入详情',
  113. age: 42,
  114. address: 'London No. 1 Lake Park',
  115. tags: ['loser'],
  116. },
  117. {
  118. key: '3',
  119. name: '内容文本,点击可进入详情',
  120. age: 32,
  121. address: 'Sidney No. 1 Lake Park',
  122. tags: ['cool', 'teacher'],
  123. },
  124. ];
  125. const draggingMap = {};
  126. columns.forEach((col) => {
  127. draggingMap[col.key] = col.width;
  128. });
  129. const draggingState = Vue.observable(draggingMap);
  130. const ResizeableTitle = ({ props, children }) => {
  131. let thDom = null;
  132. const { key, ...restProps } = props;
  133. const col = columns.find((col) => {
  134. const k = col.dataIndex || col.key;
  135. return k === key;
  136. });
  137. if (!col.width) {
  138. return <th {...restProps}>{children}</th>;
  139. }
  140. const onDrag = (x) => {
  141. draggingState[key] = 0;
  142. col.width = Math.max(x, 1);
  143. };
  144. const onDragstop = () => {
  145. draggingState[key] = thDom.getBoundingClientRect().width;
  146. };
  147. return (
  148. <th {...restProps} v-ant-ref={(r) => (thDom = r)} width={col.width} class="resize-table-th">
  149. {children}
  150. <vue-draggable-resizable
  151. key={col.key}
  152. class="table-draggable-handle"
  153. w={10}
  154. x={draggingState[key] || col.width}
  155. z={1}
  156. axis="x"
  157. draggable={true}
  158. resizable={false}
  159. onDragging={onDrag}
  160. onDragstop={onDragstop}
  161. ></vue-draggable-resizable>
  162. </th>
  163. );
  164. };
  165. export default {
  166. data() {
  167. this.components = {
  168. header: {
  169. cell: ResizeableTitle,
  170. },
  171. };
  172. return {
  173. data,
  174. columns,
  175. selectedRowKeys: [], // Check here to configure the default column
  176. };
  177. },
  178. methods: {
  179. onSelectChange(selectedRowKeys) {
  180. console.log('selectedRowKeys changed: ', selectedRowKeys);
  181. this.selectedRowKeys = selectedRowKeys;
  182. },
  183. handleMenuClick(e) {
  184. console.log('click', e);
  185. },
  186. },
  187. };
  188. </script>
  189. <style lang="less">
  190. .resize-table-th {
  191. position: relative;
  192. .table-draggable-handle {
  193. height: 100% !important;
  194. bottom: 0;
  195. left: auto !important;
  196. right: -5px;
  197. cursor: col-resize;
  198. touch-action: none;
  199. }
  200. }
  201. .table-draggable-handle {
  202. transform: none !important;
  203. position: absolute;
  204. }
  205. </style>