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

deniro發表於2019-02-17

說說如何基於 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;
}
複製程式碼

效果:

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


本文示例程式碼

相關文章