說說如何基於 Vue.js 實現表格元件

我們基於 Vue.js 實現一個可根據某列進行排序的表格元件。
一個表格包含表頭和資料兩部分內容。因此,我們定義兩個陣列,columns 表示表頭資訊,在 <thread>
中渲染,並可在此指定某一列是否需要排序;data 表示資料。
html:
<div id="app" v-cloak> <v-table :data="data" :columns="columns"></v-table> <button @click="add">新增</button> </div>
- 把父元件中定義的 data 與 columns 傳入 v-table 元件。
js:
Vue.component('vTable', { props: { //表頭列名稱 columns: { type: Array, default: function () { return []; } }, //資料 data: { type: Array, default: function () { return []; } } }, //為了不影響原始資料,這裡定義了相應的需要操作的資料物件 data: function () { return { currentColumns: [], currentData: [] } }, //render 實現方式 render: function (createElement) { var that = this; /** * 建立列樣式與表頭 */ var ths = [];//<th> 標籤陣列 var cols = [];//<cols> 標籤陣列 this.currentColumns.forEach(function (col, index) { if (col.width) {//建立列樣式 cols.push(createElement('col', { style: { width: col.width } })) } if (col.sortable) { ths.push(createElement('th', [ createElement('span', col.title), //升序 createElement('a', { class: { on: col.sortType === 'asc' }, on: { click: function () { that.sortByAsc(index) } } }, '↑'), //降序 createElement('a', { class: { on: col.sortType === 'desc' }, on: { click: function () { that.sortByDesc(index); } } }, '↓') ])); } else { ths.push(createElement('th', col.title)); } }); /** * 建立內容 */ var trs = [];//<tr> 標籤陣列 this.currentData.forEach(function (row) {//遍歷行 var tds = [];//<td> 標籤陣列 that.currentColumns.forEach(function (cell) {//遍歷單元格 tds.push(createElement('td', row[cell.key])); }); trs.push(createElement('tr', tds)); }); return createElement('table', [ createElement('colgroup', cols), createElement('thead', [ createElement('tr', ths) ]), createElement('tbody', trs) ]) }, methods: { //初始化表頭 initColumns: function () { this.currentColumns = this.columns.map(function (col, index) { //新建欄位,標識當前列排序型別;預設為“不排序” col.sortType = 'normal'; //新建欄位,標識當前列在陣列中的索引 col.index = index; return col; }); }, //初始化資料 initData: function () { this.currentData = this.data.map(function (row, index) { //新建欄位,標識當前行在陣列中的索引 row.index = index; return row; }); }, //排序 order: function (index, type) { this.currentColumns.forEach(function (col) { col.sortType = 'normal'; }); //設定排序型別 this.currentColumns[index].sortType = type; //設定排序函式 var sortFunction; var key = this.currentColumns[index].key; switch (type) { default://預設為 asc 排序 case 'asc': sortFunction = function (a, b) { return a[key] > b[key] ? 1 : -1; }; break; case 'desc': sortFunction = function (a, b) { return a[key] < b[key] ? 1 : -1; }; break; } this.currentData.sort(sortFunction); }, //升序 sortByAsc: function (index) { this.order(index, 'asc'); }, //降序 sortByDesc: function (index) { this.order(index, 'desc'); } }, watch: { data: function () { this.initData(); //找出排序欄位 var sortedColumn = this.currentColumns.filter(function (col) { return col.sortType !== 'normal'; }); if (sortedColumn.length > 0) { if (sortedColumn[0].sortType === 'asc') { this.sortByAsc(sortedColumn[0].index); } else { this.sortByDesc(sortedColumn[0].index); } } } }, mounted() { this.initColumns(); this.initData(); } }); var app = new Vue({ el: '#app', data: { //title 、key 與 width 必填;sortable 選填 columns: [ { title: '名稱', key: 'name', width:'60%' }, { title: '數量', key: 'num', width:'20%', sortable: true }, { title: '單價', key: 'unitPrice', width:'20%', sortable: true } ], data: [ { name: '真果粒牛奶飲品', num: 2, unitPrice: 59.9 }, { name: '蘇泊爾(SUPOR)電壓力鍋 ', num: 1, unitPrice: 378.0 }, { name: '樂事(Lay\'s)薯片', num: 3, unitPrice: 63.0 } ] }, methods:{ add:function () { this.data.push( { name: '良品鋪子 休閒零食大禮包', num: 5, unitPrice: 59.80 }); } } });
- 為了讓排序後的 columns 與 data 不影響原始資料,我們在元件的 data 中定義了相應的當前資料物件。因此在 method 中使用傳入的值,初始化這些資料物件,最後在 mounted() 呼叫這些初始化方法。
- columns 中的每一項都是包含 title(列名)、key(對應 data 中的欄位名)、width(寬度) 以及 sortable(是否可排序) 的物件。其中,只有 sortable 為可選項,如果設定為 true,則表示該列可點選排序。
- map() 會對陣列的每一項執行給定函式,返回每次函式呼叫的結果組成的陣列。
- 排序分為升序與降序,因為只能對某一列進行排序,所以是互斥操作。我們為每一列新增一個 sortType ,用於標識該列的排序型別,初始值為 normal,表示不排序。
- 因為排序欄位可能是任意列,所以我們為每一列新增一個 index,用於標識當前列在陣列中的索引。
- 在 Render 函式中,首先建立列樣式與表頭,接著建立內容。
- Render 函式中的 createElement 可以簡寫為 h,這樣程式碼會變得更簡潔:
render: function (h) { var that = this; /** * 建立列樣式與表頭 */ var ths = [];//<th> 標籤陣列 var cols = [];//<cols> 標籤陣列 this.currentColumns.forEach(function (col, index) { if (col.width) {//建立列樣式 cols.push(h('col', { style: { width: col.width } })) } if (col.sortable) { ths.push(h('th', [ h('span', col.title), //升序 h('a', { class: { on: col.sortType === 'asc' }, on: { click: function () { that.sortByAsc(index) } } }, '↑'), //降序 h('a', { class: { on: col.sortType === 'desc' }, on: { click: function () { that.sortByDesc(index); } } }, '↓') ])); } else { ths.push(h('th', col.title)); } }); /** * 建立內容 */ var trs = [];//<tr> 標籤陣列 this.currentData.forEach(function (row) {//遍歷行 var tds = [];//<td> 標籤陣列 that.currentColumns.forEach(function (cell) {//遍歷單元格 tds.push(h('td', row[cell.key])); }); trs.push(h('tr', tds)); }); return h('table', [ h('colgroup', cols), h('thead', [ h('tr', ths) ]), h('tbody', trs) ]) }
- 建立內容時,我們首先遍歷所有行,然後在迴圈內部遍歷所有列,得出
<td>
與<tr>
內容。 - 建立表頭時,對是否排序做了相應的處理,並綁定了相應的點選事件。
- 點選事件定義在 methods 中,因為升序與降序邏輯大體相同,所以又封裝了一層 order() 排序函式。
- order() 排序函式內部使用了陣列的 sort() 方法。sort() 方法會呼叫每個陣列項的 toString() 方法,然後比較得到的字串,即使陣列中的每一項是數值,比較的也是字串。這裡傳入了一個比較函式作為引數。為了相容所有瀏覽器,在比較函式中,我們返回的是 1 或者 -1。
- 排序之前,先把所有列的排序型別都設定為不排序,然後再更新當前列的排序狀態。這就會對應到 render 函式裡繫結
<a>
標籤的 class 中的 on 樣式,即當前列排序狀態會被高亮顯示。 - 表格被初始化渲染之後,如果 data 發生變化,那麼表格元件資料應該也要同步更新。因此,我們在 watch 中做了資料更新以及資料重排操作。
css:
[v-cloak] { display: none; } table { width: 100%; margin-bottom: 24px; /*合併邊框模型*/ border-collapse: collapse; border-spacing: 0; /*在空單元格周圍繪製邊框*/ empty-cells: show; border: 1px solid #e9e9e9; } table th { font: bold 14px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; background: #CAE8EA; color: #5c6b77; /*設定文字粗細*/ font-weight: 600; /*段落中的文字不進行換行*/ white-space: nowrap; border-top: 1px solid #C1DAD7; } table td, table th { padding: 8px 16px; text-align: left; border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; } table th a { /*不獨佔一行的塊級元素*/ display: inline-block; margin: 0 4px; cursor: pointer; } table th a.on { color: #3399ff; } table th a:hover { color: #3399ff; }
效果:
