Browse Source

Table: add summary row, fixed #1307, fixed #4451 (#4484)

杨奕 8 năm trước cách đây
mục cha
commit
349894d107

+ 201 - 0
examples/docs/en-US/table.md

@@ -154,12 +154,68 @@
           address: 'No. 189, Grove St, Los Angeles',
           zip: 'CA 90036'
         }],
+        tableData6: [{
+          id: '12987122',
+          name: 'Tom',
+          amount1: '234',
+          amount2: '3.2',
+          amount3: 10
+        }, {
+          id: '12987123',
+          name: 'Tom',
+          amount1: '165',
+          amount2: '4.43',
+          amount3: 12
+        }, {
+          id: '12987124',
+          name: 'Tom',
+          amount1: '324',
+          amount2: '1.9',
+          amount3: 9
+        }, {
+          id: '12987125',
+          name: 'Tom',
+          amount1: '621',
+          amount2: '2.2',
+          amount3: 17
+        }, {
+          id: '12987126',
+          name: 'Tom',
+          amount1: '539',
+          amount2: '4.1',
+          amount3: 15
+        }],
         currentRow: null,
         multipleSelection: []
       };
     },
 
     methods: {
+      getSummaries(param) {
+        const { columns, data } = param;
+        const sums = [];
+        columns.forEach((column, index) => {
+          if (index === 0) {
+            sums[index] = 'Total Cost';
+            return;
+          }
+          const values = data.map(item => Number(item[column.property]));
+          if (!values.every(value => isNaN(value))) {
+            sums[index] = '$ ' + values.reduce((prev, curr) => {
+              const value = Number(curr);
+              if (!isNaN(value)) {
+                return prev + curr;
+              } else {
+                return prev;
+              }
+            }, 0);
+          } else {
+            sums[index] = 'N/A';
+          }
+        });
+
+        return sums;
+      },
       setCurrent(row) {
         this.$refs.singleTable.setCurrentRow(row);
       },
@@ -1470,6 +1526,143 @@ When the row content is too long and you do not want to display the horizontal s
 ```
 :::
 
+### Summary row
+
+For table of numbers, you can add an extra row at the table footer displaying each column's sum.
+:::demo You can add the summary row by setting `show-summary` to `true`. By default, for the summary row, the first column does not sum anything up but always displays 'Sum' (you can configure the displayed text using `sum-text`), while other columns sum every number in that column up and display them. You can of course define your own sum behaviour. To do so, pass a method to `summary-method`, which returns an array, and each element of the returned array will be displayed in the columns of the summary row. The second table of this example is a detailed demo.
+```html
+<template>
+  <el-table
+    :data="tableData6"
+    border
+    show-summary
+    style="width: 100%">
+    <el-table-column
+      prop="id"
+      label="ID"
+      width="180">
+    </el-table-column>
+    <el-table-column
+      prop="name"
+      label="Name">
+    </el-table-column>
+    <el-table-column
+      prop="amount1"
+      sortable
+      label="Amount 1">
+    </el-table-column>
+    <el-table-column
+      prop="amount2"
+      sortable
+      label="Amount 2">
+    </el-table-column>
+    <el-table-column
+      prop="amount3"
+      sortable
+      label="Amount 3">
+    </el-table-column>
+  </el-table>
+  
+  <el-table
+    :data="tableData6"
+    border
+    height="200"
+    :summary-method="getSummaries"
+    show-summary
+    style="width: 100%; margin-top: 20px">
+    <el-table-column
+      prop="id"
+      label="ID"
+      width="180">
+    </el-table-column>
+    <el-table-column
+      prop="name"
+      label="Name">
+    </el-table-column>
+    <el-table-column
+      prop="amount1"
+      label="Cost 1 ($)">
+    </el-table-column>
+    <el-table-column
+      prop="amount2"
+      label="Cost 2 ($)">
+    </el-table-column>
+    <el-table-column
+      prop="amount3"
+      label="Cost 3 ($)">
+    </el-table-column>
+  </el-table>
+</template>
+
+<script>
+  export default {
+    data() {
+      return {
+        tableData6: [{
+          id: '12987122',
+          name: 'Tom',
+          amount1: '234',
+          amount2: '3.2',
+          amount3: 10
+        }, {
+          id: '12987123',
+          name: 'Tom',
+          amount1: '165',
+          amount2: '4.43',
+          amount3: 12
+        }, {
+          id: '12987124',
+          name: 'Tom',
+          amount1: '324',
+          amount2: '1.9',
+          amount3: 9
+        }, {
+          id: '12987125',
+          name: 'Tom',
+          amount1: '621',
+          amount2: '2.2',
+          amount3: 17
+        }, {
+          id: '12987126',
+          name: 'Tom',
+          amount1: '539',
+          amount2: '4.1',
+          amount3: 15
+        }]
+      };
+    },
+    methods: {
+      getSummaries(param) {
+        const { columns, data } = param;
+        const sums = [];
+        columns.forEach((column, index) => {
+          if (index === 0) {
+            sums[index] = 'Total Cost';
+            return;
+          }
+          const values = data.map(item => Number(item[column.property]));
+          if (!values.every(value => isNaN(value))) {
+            sums[index] = '$ ' + values.reduce((prev, curr) => {
+              const value = Number(curr);
+              if (!isNaN(value)) {
+                return prev + curr;
+              } else {
+                return prev;
+              }
+            }, 0);
+          } else {
+            sums[index] = 'N/A';
+          }
+        });
+
+        return sums;
+      }
+    }
+  };
+</script>
+```
+:::
+
 ### Table Attributes
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
@@ -1490,6 +1683,9 @@ When the row content is too long and you do not want to display the horizontal s
 | expand-row-keys | set expanded rows by this prop, prop's value is the keys of expand rows, you should set row-key before using this prop | Array | — | |
 | default-sort | set the default sort column and order. property `prop` is used to set default sort column, property `order` is used to set default sort order | Object | `order`: ascending, descending | if `prop` is set, and `order` is not set, then `order` is default to ascending |
 | tooltip-effect | tooltip `effect` property | String | dark/light | | dark |
+| show-summary | whether to display a summary row | Boolean | — | false |
+| sum-text | displayed text for the first column of summary row | String | — | Sum |
+| summary-method | custom summary method | Function({ columns, data }) | — | — |
 
 ### Table Events
 | Event Name | Description | Parameters |
@@ -1518,6 +1714,11 @@ When the row content is too long and you do not want to display the horizontal s
 | toggleRowSelection | used in multiple selection Table, toggle if a certain row is selected. With the second parameter, you can directly set if this row is selected | row, selected |
 | setCurrentRow | used in single selection Table, set a certain row selected. If called without any parameter, it will clear selection. | row |
 
+### Table Slot
+| Name | Description |
+|------|--------|
+| append | Contents to be inserted after the last row. It is still nested inside the `<tbody>` tag. You may need this slot if you want to implement infinite scroll for the table. This slot will be displayed above the summary row if there is one. |
+
 ### Table-column Attributes
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |

+ 203 - 0
examples/docs/zh-CN/table.md

@@ -194,12 +194,69 @@
           shop: '王小虎夫妻店',
           shopId: '10333'
         }],
+        tableData6: [{
+          id: '12987122',
+          name: '王小虎',
+          amount1: '234',
+          amount2: '3.2',
+          amount3: 10
+        }, {
+          id: '12987123',
+          name: '王小虎',
+          amount1: '165',
+          amount2: '4.43',
+          amount3: 12
+        }, {
+          id: '12987124',
+          name: '王小虎',
+          amount1: '324',
+          amount2: '1.9',
+          amount3: 9
+        }, {
+          id: '12987125',
+          name: '王小虎',
+          amount1: '621',
+          amount2: '2.2',
+          amount3: 17
+        }, {
+          id: '12987126',
+          name: '王小虎',
+          amount1: '539',
+          amount2: '4.1',
+          amount3: 15
+        }],
         currentRow: null,
         multipleSelection: []
       };
     },
 
     methods: {
+      getSummaries(param) {
+        const { columns, data } = param;
+        const sums = [];
+        columns.forEach((column, index) => {
+          if (index === 0) {
+            sums[index] = '总价';
+            return;
+          }
+          const values = data.map(item => Number(item[column.property]));
+          if (!values.every(value => isNaN(value))) {
+            sums[index] = values.reduce((prev, curr) => {
+              const value = Number(curr);
+              if (!isNaN(value)) {
+                return prev + curr;
+              } else {
+                return prev;
+              }
+            }, 0);
+            sums[index] += ' 元';
+          } else {
+            sums[index] = 'N/A';
+          }
+        });
+
+        return sums;
+      },
       setCurrent(row) {
         this.$refs.singleTable.setCurrentRow(row);
       },
@@ -1555,6 +1612,144 @@
 ```
 :::
 
+### 表尾合计行
+
+若表格展示的是各类数字,可以在表尾显示各列的合计。
+:::demo 将`show-summary`设置为`true`就会在表格尾部展示合计行。默认情况下,对于合计行,第一列不进行数据求合操作,而是显示「合计」二字(可通过`sum-text`配置),其余列会将本列所有数值进行求合操作,并显示出来。当然,你也可以定义自己的合计逻辑。使用`summary-method`并传入一个方法,返回一个数组,这个数组中的各项就会显示在合计行的各列中,具体可以参考本例中的第二个表格。
+```html
+<template>
+  <el-table
+    :data="tableData6"
+    border
+    show-summary
+    style="width: 100%">
+    <el-table-column
+      prop="id"
+      label="ID"
+      width="180">
+    </el-table-column>
+    <el-table-column
+      prop="name"
+      label="姓名">
+    </el-table-column>
+    <el-table-column
+      prop="amount1"
+      sortable
+      label="数值 1">
+    </el-table-column>
+    <el-table-column
+      prop="amount2"
+      sortable
+      label="数值 2">
+    </el-table-column>
+    <el-table-column
+      prop="amount3"
+      sortable
+      label="数值 3">
+    </el-table-column>
+  </el-table>
+  
+  <el-table
+    :data="tableData6"
+    border
+    height="200"
+    :summary-method="getSummaries"
+    show-summary
+    style="width: 100%; margin-top: 20px">
+    <el-table-column
+      prop="id"
+      label="ID"
+      width="180">
+    </el-table-column>
+    <el-table-column
+      prop="name"
+      label="姓名">
+    </el-table-column>
+    <el-table-column
+      prop="amount1"
+      label="数值 1(元)">
+    </el-table-column>
+    <el-table-column
+      prop="amount2"
+      label="数值 2(元)">
+    </el-table-column>
+    <el-table-column
+      prop="amount3"
+      label="数值 3(元)">
+    </el-table-column>
+  </el-table>
+</template>
+
+<script>
+  export default {
+    data() {
+      return {
+        tableData6: [{
+          id: '12987122',
+          name: '王小虎',
+          amount1: '234',
+          amount2: '3.2',
+          amount3: 10
+        }, {
+          id: '12987123',
+          name: '王小虎',
+          amount1: '165',
+          amount2: '4.43',
+          amount3: 12
+        }, {
+          id: '12987124',
+          name: '王小虎',
+          amount1: '324',
+          amount2: '1.9',
+          amount3: 9
+        }, {
+          id: '12987125',
+          name: '王小虎',
+          amount1: '621',
+          amount2: '2.2',
+          amount3: 17
+        }, {
+          id: '12987126',
+          name: '王小虎',
+          amount1: '539',
+          amount2: '4.1',
+          amount3: 15
+        }]
+      };
+    },
+    methods: {
+      getSummaries(param) {
+        const { columns, data } = param;
+        const sums = [];
+        columns.forEach((column, index) => {
+          if (index === 0) {
+            sums[index] = '总价';
+            return;
+          }
+          const values = data.map(item => Number(item[column.property]));
+          if (!values.every(value => isNaN(value))) {
+            sums[index] = values.reduce((prev, curr) => {
+              const value = Number(curr);
+              if (!isNaN(value)) {
+                return prev + curr;
+              } else {
+                return prev;
+              }
+            }, 0);
+            sums[index] += ' 元';
+          } else {
+            sums[index] = 'N/A';
+          }
+        });
+
+        return sums;
+      }
+    }
+  };
+</script>
+```
+:::
+
 ### Table Attributes
 | 参数      | 说明          | 类型      | 可选值                           | 默认值  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
@@ -1575,6 +1770,9 @@
 | expand-row-keys | 可以通过该属性设置 Table 目前的展开行,需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。| Array | — | |
 | default-sort | 默认的排序列的prop和顺序。它的`prop`属性指定默认的排序的列,`order`指定默认排序的顺序| Object | `order`: ascending, descending | 如果只指定了`prop`, 没有指定`order`, 则默认顺序是ascending |
 | tooltip-effect | tooltip `effect` 属性 | String | dark/light | | dark |
+| show-summary | 是否在表尾显示合计行 | Boolean | — | false |
+| sum-text | 合计行第一列的文本 | String | — | 合计 |
+| summary-method | 自定义的合计计算方法 | Function({ columns, data }) | — | — |
 
 ### Table Events
 | 事件名 | 说明 | 参数 |
@@ -1603,6 +1801,11 @@
 | toggleRowSelection | 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中) | row, selected |
 | setCurrentRow | 用于单选表格,设定某一行为选中行,如果调用时不加参数,则会取消目前高亮行的选中状态。 | row |
 
+### Table Slot
+| name | 说明 |
+|------|--------|
+| append | 插入至表格最后一行之后的内容,仍然位于 `<tbody>` 标签内。如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。若表格有合计行,该 slot 会位于合计行之上。 |
+
 ### Table-column Attributes
 | 参数      | 说明          | 类型      | 可选值                           | 默认值  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |

+ 2 - 0
packages/table/src/table-body.js

@@ -77,6 +77,8 @@ export default {
                   </tr>)
                 : ''
               ]
+            ).concat(
+              this._self.$parent.$slots.append
             ).concat(
               <el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip>
             )

+ 6 - 0
packages/table/src/table-column.js

@@ -364,6 +364,12 @@ export default {
         this.columnConfig.fixed = newVal;
         this.owner.store.scheduleLayout();
       }
+    },
+
+    sortable(newVal) {
+      if (this.columnConfig) {
+        this.columnConfig.sortable = newVal;
+      }
     }
   },
 

+ 142 - 0
packages/table/src/table-footer.js

@@ -0,0 +1,142 @@
+export default {
+  name: 'ElTableFooter',
+
+  render(h) {
+    const sums = [];
+    this.columns.forEach((column, index) => {
+      if (index === 0) {
+        sums[index] = this.sumText;
+        return;
+      }
+      const values = this.store.states.data.map(item => Number(item[column.property]));
+      const precisions = [];
+      let notNumber = true;
+      values.forEach(value => {
+        if (!isNaN(value)) {
+          notNumber = false;
+          let decimal = ('' + value).split('.')[1];
+          precisions.push(decimal ? decimal.length : 0);
+        }
+      });
+      const precision = Math.max.apply(null, precisions);
+      if (!notNumber) {
+        sums[index] = values.reduce((prev, curr) => {
+          const value = Number(curr);
+          if (!isNaN(value)) {
+            return parseFloat((prev + curr).toFixed(precision));
+          } else {
+            return prev;
+          }
+        }, 0);
+      } else {
+        sums[index] = '';
+      }
+    });
+
+    return (
+      <table
+        class="el-table__footer"
+        cellspacing="0"
+        cellpadding="0"
+        border="0">
+        <colgroup>
+          {
+            this._l(this.columns, column =>
+              <col
+                name={ column.id }
+                width={ column.realWidth || column.width }
+              />)
+          }
+          {
+            !this.fixed && this.layout.gutterWidth
+              ? <col name="gutter" width={ this.layout.scrollY ? this.layout.gutterWidth : '' }></col>
+              : ''
+          }
+        </colgroup>
+        <tbody>
+          <tr>
+          {
+            this._l(this.columns, (column, cellIndex) =>
+              <td
+                colspan={ column.colSpan }
+                rowspan={ column.rowSpan }
+                class={ [column.id, column.headerAlign, column.className || '', this.isCellHidden(cellIndex, this.columns) ? 'is-hidden' : '', !column.children ? 'is-leaf' : '', column.labelClassName] }>
+                <div class={ ['cell', column.labelClassName] }>
+                {
+                  this.summaryMethod ? this.summaryMethod({ columns: this.columns, data: this.store.states.data })[cellIndex] : sums[cellIndex]
+                }
+                </div>
+              </td>
+            )
+          }
+          {
+            !this.fixed && this.layout.gutterWidth
+              ? <td class="gutter" style={{ width: this.layout.scrollY ? this.layout.gutterWidth + 'px' : '0' }}></td>
+              : ''
+          }
+          </tr>
+        </tbody>
+      </table>
+    );
+  },
+
+  props: {
+    fixed: String,
+    store: {
+      required: true
+    },
+    layout: {
+      required: true
+    },
+    summaryMethod: Function,
+    sumText: String,
+    border: Boolean,
+    defaultSort: {
+      type: Object,
+      default() {
+        return {
+          prop: '',
+          order: ''
+        };
+      }
+    }
+  },
+
+  computed: {
+    isAllSelected() {
+      return this.store.states.isAllSelected;
+    },
+
+    columnsCount() {
+      return this.store.states.columns.length;
+    },
+
+    leftFixedCount() {
+      return this.store.states.fixedColumns.length;
+    },
+
+    rightFixedCount() {
+      return this.store.states.rightFixedColumns.length;
+    },
+
+    columns() {
+      return this.store.states.columns;
+    }
+  },
+
+  methods: {
+    isCellHidden(index, columns) {
+      if (this.fixed === true || this.fixed === 'left') {
+        return index >= this.leftFixedCount;
+      } else if (this.fixed === 'right') {
+        let before = 0;
+        for (let i = 0; i < index; i++) {
+          before += columns[i].colSpan;
+        }
+        return before < this.columnsCount - this.rightFixedCount;
+      } else {
+        return (index < this.leftFixedCount) || (index >= this.columnsCount - this.rightFixedCount);
+      }
+    }
+  }
+};

+ 2 - 1
packages/table/src/table-layout.js

@@ -83,7 +83,8 @@ class TableLayout {
       this.fixedBodyHeight = this.scrollX ? height - this.gutterWidth : height;
     } else {
       const headerHeight = this.headerHeight = headerWrapper.offsetHeight;
-      const bodyHeight = height - headerHeight;
+      const ratio = this.table.showSummary && this.table.data && this.table.data.length > 0 ? 2 : 1;
+      const bodyHeight = height - ratio * headerHeight + (this.table.showSummary ? 1 : 0);
       if (this.height !== null && (!isNaN(this.height) || typeof this.height === 'string')) {
         this.bodyHeight = bodyHeight;
       }

+ 41 - 1
packages/table/src/table.vue

@@ -36,6 +36,17 @@
         <span class="el-table__empty-text"><slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot></span>
       </div>
     </div>
+    <div class="el-table__footer-wrapper" ref="footerWrapper" v-if="showSummary && data && data.length > 0">
+      <table-footer
+        :store="store"
+        :layout="layout"
+        :border="border"
+        :sum-text="sumText || t('el.table.sumText')"
+        :summary-method="summaryMethod"
+        :default-sort="defaultSort"
+        :style="{ width: layout.bodyWidth ? layout.bodyWidth + 'px' : '' }">
+      </table-footer>
+    </div>
     <div class="el-table__fixed" ref="fixedWrapper"
       v-if="fixedColumns.length > 0"
       :style="[
@@ -65,6 +76,16 @@
           :style="{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' }">
         </table-body>
       </div>
+      <div class="el-table__fixed-footer-wrapper" ref="fixedFooterWrapper" v-if="showSummary && data && data.length > 0">
+        <table-footer
+          fixed="left"
+          :border="border"
+          :sum-text="sumText || t('el.table.sumText')"
+          :summary-method="summaryMethod"
+          :store="store"
+          :layout="layout"
+          :style="{ width: layout.fixedWidth ? layout.fixedWidth + 'px' : '' }"></table-footer>
+      </div>
     </div>
     <div class="el-table__fixed-right" ref="rightFixedWrapper"
       v-if="rightFixedColumns.length > 0"
@@ -96,6 +117,16 @@
           :style="{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' }">
         </table-body>
       </div>
+      <div class="el-table__fixed-footer-wrapper" ref="rightFixedFooterWrapper" v-if="showSummary && data && data.length > 0">
+        <table-footer
+          fixed="right"
+          :border="border"
+          :sum-text="sumText || t('el.table.sumText')"
+          :summary-method="summaryMethod"
+          :store="store"
+          :layout="layout"
+          :style="{ width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '' }"></table-footer>
+      </div>
     </div>
     <div class="el-table__fixed-right-patch"
       v-if="rightFixedColumns.length > 0"
@@ -114,6 +145,7 @@
   import TableLayout from './table-layout';
   import TableBody from './table-body';
   import TableHeader from './table-header';
+  import TableFooter from './table-footer';
   import { mousewheel } from './util';
 
   let tableIdSeed = 1;
@@ -155,6 +187,12 @@
         default: true
       },
 
+      showSummary: Boolean,
+
+      sumText: String,
+
+      summaryMethod: Function,
+
       rowClassName: [String, Function],
 
       rowStyle: [Object, Function],
@@ -176,6 +214,7 @@
 
     components: {
       TableHeader,
+      TableFooter,
       TableBody,
       ElCheckbox
     },
@@ -204,10 +243,11 @@
       },
 
       bindEvents() {
-        const { headerWrapper } = this.$refs;
+        const { headerWrapper, footerWrapper } = this.$refs;
         const refs = this.$refs;
         this.bodyWrapper.addEventListener('scroll', function() {
           if (headerWrapper) headerWrapper.scrollLeft = this.scrollLeft;
+          if (footerWrapper) footerWrapper.scrollLeft = this.scrollLeft;
           if (refs.fixedBodyWrapper) refs.fixedBodyWrapper.scrollTop = this.scrollTop;
           if (refs.rightFixedBodyWrapper) refs.rightFixedBodyWrapper.scrollTop = this.scrollTop;
         });

+ 1 - 0
packages/theme-default/src/common/var.css

@@ -418,6 +418,7 @@
   --table-border-color: color(var(--border-color-base) h(-3) s(27%) l(90%));
   --table-text-color: var(--color-base-black);
   --table-header-background: var(--color-extra-light-gray);
+  --table-footer-background: var(--color-dark-white);
 
   /* Pagination
  -------------------------- */

+ 35 - 8
packages/theme-default/src/table.css

@@ -199,7 +199,9 @@
 
       box-shadow: -1px 0 8px #d3d4d6;
 
-      .el-table__fixed-header-wrapper, .el-table__fixed-body-wrapper {
+      .el-table__fixed-header-wrapper,
+      .el-table__fixed-body-wrapper,
+      .el-table__fixed-footer-wrapper {
         left: auto;
         right: 0;
       }
@@ -217,6 +219,19 @@
       }
     }
 
+    @e fixed-footer-wrapper {
+      position: absolute;
+      left: 0;
+      bottom: 0;
+      z-index: 3;
+
+      & tbody td {
+        border-top: 1px solid var(--table-border-color);
+        background-color: var(--table-footer-background);
+        color: var(--table-text-color);
+      }
+    }
+
     @e fixed-body-wrapper {
       position: absolute;
       left: 0;
@@ -225,21 +240,33 @@
       z-index: 3;
     }
 
-    @e header-wrapper, body-wrapper {
+    @e header-wrapper, body-wrapper, footer-wrapper {
       width: 100%;
     }
 
-    @e header, body {
+    @e footer-wrapper {
+      margin-top: -1px;
+      td {
+        border-top: 1px solid var(--table-border-color);
+      }
+    }
+
+    @e header, body, footer {
       table-layout: fixed;
     }
 
-    @e header-wrapper {
+    @e header-wrapper, footer-wrapper {
       overflow: hidden;
 
       & thead div {
         background-color: var(--table-header-background);
         color: var(--table-text-color);
       }
+
+      & tbody td {
+        background-color: var(--table-footer-background);
+        color: var(--table-text-color);
+      }
     }
 
     @e body-wrapper {
@@ -263,7 +290,7 @@
       word-wrap: normal;
       text-overflow: ellipsis;
       display: inline-block;
-      line-height: 20px;
+      line-height: 30px;
       vertical-align: middle;
       width: 100%;
       box-sizing: border-box;
@@ -281,7 +308,7 @@
       margin-left: 5px;
       margin-top: -2px;
       width: 16px;
-      height: 34px;
+      height: 30px;
       overflow: initial;
     }
 
@@ -296,7 +323,7 @@
       z-index: 2;
 
       &.ascending {
-        top: 11px;
+        top: 9px;
         border-top: none;
         border-right: 5px solid transparent;
         border-bottom: 5px solid var(--color-light-silver);
@@ -304,7 +331,7 @@
       }
 
       &.descending {
-        bottom: 11px;
+        bottom: 9px;
         border-top: 5px solid var(--color-light-silver);
         border-right: 5px solid transparent;
         border-bottom: none;

+ 2 - 1
src/locale/lang/bg.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Няма данни',
       confirmFilter: 'Потвърди',
       resetFilter: 'Изчисти',
-      clearFilter: 'Всички'
+      clearFilter: 'Всички',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Няма данни'

+ 2 - 1
src/locale/lang/ca.js

@@ -84,7 +84,8 @@ export default {
       emptyText: 'Sense Dades',
       confirmFilter: 'Confirmar',
       resetFilter: 'Netejar',
-      clearFilter: 'Tot'
+      clearFilter: 'Tot',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Sense Dades'

+ 2 - 1
src/locale/lang/cz.js

@@ -87,7 +87,8 @@ export default {
       emptyText: 'Žádná data',
       confirmFilter: 'Potvrdit',
       resetFilter: 'Resetovat',
-      clearFilter: 'Vše'
+      clearFilter: 'Vše',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Žádná data'

+ 2 - 1
src/locale/lang/da.js

@@ -84,7 +84,8 @@ export default {
       emptyText: 'Ingen data',
       confirmFilter: 'Bekræft',
       resetFilter: 'Nulstil',
-      clearFilter: 'Alle'
+      clearFilter: 'Alle',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Ingen data'

+ 2 - 1
src/locale/lang/de.js

@@ -86,7 +86,8 @@ export default {
       emptyText: 'Keine Daten',
       confirmFilter: 'Anwenden',
       resetFilter: 'Zurücksetzen',
-      clearFilter: 'Alles '
+      clearFilter: 'Alles ',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Keine Daten'

+ 2 - 1
src/locale/lang/el.js

@@ -81,7 +81,8 @@ export default {
       emptyText: 'Χωρίς Δεδομένα',
       confirmFilter: 'Επιβεβαίωση',
       resetFilter: 'Επαναφορά',
-      clearFilter: 'Όλα'
+      clearFilter: 'Όλα',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Χωρίς Δεδομένα'

+ 2 - 1
src/locale/lang/en.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'No Data',
       confirmFilter: 'Confirm',
       resetFilter: 'Reset',
-      clearFilter: 'All'
+      clearFilter: 'All',
+      sumText: 'Sum'
     },
     tree: {
       emptyText: 'No Data'

+ 2 - 1
src/locale/lang/es.js

@@ -84,7 +84,8 @@ export default {
       emptyText: 'Sin Datos',
       confirmFilter: 'Confirmar',
       resetFilter: 'Limpiar',
-      clearFilter: 'Todo'
+      clearFilter: 'Todo',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Sin Datos'

+ 2 - 1
src/locale/lang/fa.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'اطلاعاتی وجود ندارد',
       confirmFilter: 'تایید',
       resetFilter: 'حذف',
-      clearFilter: 'همه'
+      clearFilter: 'همه',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'اطلاعاتی وجود ندارد'

+ 2 - 1
src/locale/lang/fi.js

@@ -81,7 +81,8 @@ export default {
       emptyText: 'Ei tietoja',
       confirmFilter: 'Vahvista',
       resetFilter: 'Tyhjennä',
-      clearFilter: 'Kaikki'
+      clearFilter: 'Kaikki',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Ei tietoja'

+ 2 - 1
src/locale/lang/fr.js

@@ -84,7 +84,8 @@ export default {
       emptyText: 'Aucune donnée',
       confirmFilter: 'Confirmer',
       resetFilter: 'Réinitialiser',
-      clearFilter: 'Tous'
+      clearFilter: 'Tous',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Aucune donnée'

+ 2 - 1
src/locale/lang/id.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Tidak Ada Data',
       confirmFilter: 'Konfirmasi',
       resetFilter: 'Atur Ulang',
-      clearFilter: 'Semua'
+      clearFilter: 'Semua',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Tidak Ada Data'

+ 2 - 1
src/locale/lang/it.js

@@ -84,7 +84,8 @@ export default {
       emptyText: 'Nessun dato',
       confirmFilter: 'Conferma',
       resetFilter: 'Reset',
-      clearFilter: 'Tutti'
+      clearFilter: 'Tutti',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Nessun dato'

+ 2 - 1
src/locale/lang/ja.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'データなし',
       confirmFilter: '確認',
       resetFilter: '初期化',
-      clearFilter: 'すべて'
+      clearFilter: 'すべて',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'データなし'

+ 2 - 1
src/locale/lang/ko.js

@@ -85,7 +85,8 @@ export default {
       emptyText: '데이터 없음',
       confirmFilter: '확인',
       resetFilter: '초기화',
-      clearFilter: '전체'
+      clearFilter: '전체',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: '데이터 없음'

+ 2 - 1
src/locale/lang/nb-NO.js

@@ -84,7 +84,8 @@ export default {
       emptyText: 'Ingen Data',
       confirmFilter: 'Bekreft',
       resetFilter: 'Tilbakestill',
-      clearFilter: 'Alle'
+      clearFilter: 'Alle',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Ingen Data'

+ 2 - 1
src/locale/lang/nl.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Geen data',
       confirmFilter: 'Bevestigen',
       resetFilter: 'Reset',
-      clearFilter: 'Alles'
+      clearFilter: 'Alles',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Geen data'

+ 2 - 1
src/locale/lang/pl.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Brak danych',
       confirmFilter: 'Potwierdź',
       resetFilter: 'Resetuj',
-      clearFilter: 'Wszystko'
+      clearFilter: 'Wszystko',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Brak danych'

+ 2 - 1
src/locale/lang/pt-br.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Sem dados',
       confirmFilter: 'Confirmar',
       resetFilter: 'Limpar',
-      clearFilter: 'Todos'
+      clearFilter: 'Todos',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Sem dados'

+ 2 - 1
src/locale/lang/pt.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Sem dados',
       confirmFilter: 'Confirmar',
       resetFilter: 'Limpar',
-      clearFilter: 'Todos'
+      clearFilter: 'Todos',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Sem dados'

+ 2 - 1
src/locale/lang/ru-RU.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Нет данных',
       confirmFilter: 'Подтвердить',
       resetFilter: 'Сбросить',
-      clearFilter: 'Все'
+      clearFilter: 'Все',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Нет данных'

+ 2 - 1
src/locale/lang/sk.js

@@ -83,7 +83,8 @@ export default {
       emptyText: 'Žiadne dáta',
       confirmFilter: 'Potvrdiť',
       resetFilter: 'Zresetovať',
-      clearFilter: 'Všetko'
+      clearFilter: 'Všetko',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Žiadne dáta'

+ 2 - 1
src/locale/lang/sv-SE.js

@@ -81,7 +81,8 @@ export default {
       emptyText: 'Inga Data',
       confirmFilter: 'Bekräfta',
       resetFilter: 'Återställ',
-      clearFilter: 'Alla'
+      clearFilter: 'Alla',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Inga Data'

+ 2 - 1
src/locale/lang/th.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'ไม่พบข้อมูล',
       confirmFilter: 'ยืนยัน',
       resetFilter: 'รีเซ็ต',
-      clearFilter: 'ทั้งหมด'
+      clearFilter: 'ทั้งหมด',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'ไม่พบข้อมูล'

+ 2 - 1
src/locale/lang/tk.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Maglumat ýok',
       confirmFilter: 'Tassykla',
       resetFilter: 'Arassala',
-      clearFilter: 'Hemmesi'
+      clearFilter: 'Hemmesi',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Maglumat ýok'

+ 2 - 1
src/locale/lang/tr-TR.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Veri yok',
       confirmFilter: 'Onayla',
       resetFilter: 'Reset',
-      clearFilter: 'Hepsi'
+      clearFilter: 'Hepsi',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Veri yok'

+ 2 - 1
src/locale/lang/ua.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Нема даних',
       confirmFilter: 'Підтвердити',
       resetFilter: 'Скинути',
-      clearFilter: 'Все'
+      clearFilter: 'Все',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Нема даних'

+ 2 - 1
src/locale/lang/vi.js

@@ -85,7 +85,8 @@ export default {
       emptyText: 'Không có dữ liệu',
       confirmFilter: 'Xác nhận',
       resetFilter: 'Làm mới',
-      clearFilter: 'Xóa hết'
+      clearFilter: 'Xóa hết',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: 'Không có dữ liệu'

+ 2 - 1
src/locale/lang/zh-CN.js

@@ -85,7 +85,8 @@ export default {
       emptyText: '暂无数据',
       confirmFilter: '筛选',
       resetFilter: '重置',
-      clearFilter: '全部'
+      clearFilter: '全部',
+      sumText: '合计'
     },
     tree: {
       emptyText: '暂无数据'

+ 2 - 1
src/locale/lang/zh-TW.js

@@ -85,7 +85,8 @@ export default {
       emptyText: '暫無資料',
       confirmFilter: '篩選',
       resetFilter: '重置',
-      clearFilter: '全部'
+      clearFilter: '全部',
+      sumText: 'Sum' // to be translated
     },
     tree: {
       emptyText: '暫無資料'