說說如何基于 Vue.js 實(shí)現(xiàn)表格組件

我們基于 Vue.js 實(shí)現(xiàn)一個可根據(jù)某列進(jìn)行排序的表格組件。

一個表格包含表頭和數(shù)據(jù)兩部分內(nèi)容托修。因此,我們定義兩個數(shù)組恒界,columns 表示表頭信息睦刃,在 <thread> 中渲染,并可在此指定某一列是否需要排序仗处;data 表示數(shù)據(jù)。

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 [];
            }
        },
        //數(shù)據(jù)
        data: {
            type: Array,
            default: function () {
                return [];
            }
        }
    },
    //為了不影響原始數(shù)據(jù)婆誓,這里定義了相應(yīng)的需要操作的數(shù)據(jù)對象
    data: function () {
        return {
            currentColumns: [],
            currentData: []
        }
    },
    //render 實(shí)現(xiàn)方式
    render: function (createElement) {
        var that = this;

        /**
         * 創(chuàng)建列樣式與表頭
         */
        var ths = [];//<th> 標(biāo)簽數(shù)組
        var cols = [];//<cols> 標(biāo)簽數(shù)組
        this.currentColumns.forEach(function (col, index) {
            if (col.width) {//創(chuàng)建列樣式
                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));
            }
        });


        /**
         * 創(chuàng)建內(nèi)容
         */
        var trs = [];//<tr> 標(biāo)簽數(shù)組
        this.currentData.forEach(function (row) {//遍歷行
            var tds = [];//<td> 標(biāo)簽數(shù)組
            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) {
                //新建字段,標(biāo)識當(dāng)前列排序類型也颤;默認(rèn)為“不排序”
                col.sortType = 'normal';
                //新建字段洋幻,標(biāo)識當(dāng)前列在數(shù)組中的索引
                col.index = index;
                return col;
            });
        },
        //初始化數(shù)據(jù)
        initData: function () {
            this.currentData = this.data.map(function (row, index) {
                //新建字段,標(biāo)識當(dāng)前行在數(shù)組中的索引
                row.index = index;
                return row;
            });
        },

        //排序
        order: function (index, type) {
            this.currentColumns.forEach(function (col) {
                col.sortType = 'normal';
            });

            //設(shè)置排序類型
            this.currentColumns[index].sortType = type;

            //設(shè)置排序函數(shù)
            var sortFunction;
            var key = this.currentColumns[index].key;
            switch (type) {
                default://默認(rèn)為 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: '數(shù)量',
                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: '良品鋪?zhàn)?休閑零食大禮包',
                num: 5,
                unitPrice: 59.80
            });
        }
    }
});
  • 為了讓排序后的 columns 與 data 不影響原始數(shù)據(jù),我們在組件的 data 中定義了相應(yīng)的當(dāng)前數(shù)據(jù)對象竭沫。因此在 method 中使用傳入的值燥翅,初始化這些數(shù)據(jù)對象,最后在 mounted() 調(diào)用這些初始化方法蜕提。
  • columns 中的每一項都是包含 title(列名)森书、key(對應(yīng) data 中的字段名)、width(寬度) 以及 sortable(是否可排序) 的對象。其中凛膏,只有 sortable 為可選項杨名,如果設(shè)定為 true,則表示該列可點(diǎn)擊排序猖毫。
  • map() 會對數(shù)組的每一項運(yùn)行給定函數(shù)台谍,返回每次函數(shù)調(diào)用的結(jié)果組成的數(shù)組。
  • 排序分為升序與降序吁断,因?yàn)橹荒軐δ骋涣羞M(jìn)行排序趁蕊,所以是互斥操作。我們?yōu)槊恳涣行略鲆粋€ sortType 胯府,用于標(biāo)識該列的排序類型介衔,初始值為 normal,表示不排序骂因。
  • 因?yàn)榕判蜃侄慰赡苁侨我饬醒卓В晕覀優(yōu)槊恳涣行略鲆粋€ index,用于標(biāo)識當(dāng)前列在數(shù)組中的索引寒波。
  • 在 Render 函數(shù)中乘盼,首先創(chuàng)建列樣式與表頭,接著創(chuàng)建內(nèi)容俄烁。
  • Render 函數(shù)中的 createElement 可以簡寫為 h绸栅,這樣代碼會變得更簡潔:
render: function (h) {
    var that = this;

    /**
     * 創(chuàng)建列樣式與表頭
     */
    var ths = [];//<th> 標(biāo)簽數(shù)組
    var cols = [];//<cols> 標(biāo)簽數(shù)組
    this.currentColumns.forEach(function (col, index) {
        if (col.width) {//創(chuàng)建列樣式
            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));
        }
    });


    /**
     * 創(chuàng)建內(nèi)容
     */
    var trs = [];//<tr> 標(biāo)簽數(shù)組
    this.currentData.forEach(function (row) {//遍歷行
        var tds = [];//<td> 標(biāo)簽數(shù)組
        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)
    ])
}
  • 創(chuàng)建內(nèi)容時,我們首先遍歷所有行页屠,然后在循環(huán)內(nèi)部遍歷所有列粹胯,得出 <td><tr> 內(nèi)容。
  • 創(chuàng)建表頭時辰企,對是否排序做了相應(yīng)的處理风纠,并綁定了相應(yīng)的點(diǎn)擊事件。
  • 點(diǎn)擊事件定義在 methods 中牢贸,因?yàn)樯蚺c降序邏輯大體相同竹观,所以又封裝了一層 order() 排序函數(shù)。
  • order() 排序函數(shù)內(nèi)部使用了數(shù)組的 sort() 方法潜索。sort() 方法會調(diào)用每個數(shù)組項的 toString() 方法臭增,然后比較得到的字符串,即使數(shù)組中的每一項是數(shù)值竹习,比較的也是字符串誊抛。這里傳入了一個比較函數(shù)作為參數(shù)。為了兼容所有瀏覽器整陌,在比較函數(shù)中芍锚,我們返回的是 1 或者 -1昔园。
  • 排序之前,先把所有列的排序類型都設(shè)置為不排序并炮,然后再更新當(dāng)前列的排序狀態(tài)默刚。這就會對應(yīng)到 render 函數(shù)里綁定 <a> 標(biāo)簽的 class 中的 on 樣式,即當(dāng)前列排序狀態(tài)會被高亮顯示逃魄。
  • 表格被初始化渲染之后荤西,如果 data 發(fā)生變化,那么表格組件數(shù)據(jù)應(yīng)該也要同步更新伍俘。因此邪锌,我們在 watch 中做了數(shù)據(jù)更新以及數(shù)據(jù)重排操作。

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;
    /*設(shè)置文本粗細(xì)*/
    font-weight: 600;
    /*段落中的文本不進(jìn)行換行*/
    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 {
    /*不獨(dú)占一行的塊級元素*/
    display: inline-block;
    margin: 0 4px;
    cursor: pointer;
}

table th a.on {
    color: #3399ff;
}

table th a:hover {
    color: #3399ff;
}

效果:


本文示例代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末癌瘾,一起剝皮案震驚了整個濱河市觅丰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妨退,老刑警劉巖妇萄,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異咬荷,居然都是意外死亡冠句,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門幸乒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懦底,“玉大人,你說我怎么就攤上這事罕扎【厶疲” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵腔召,是天一觀的道長杆查。 經(jīng)常有香客問我,道長宴咧,這世上最難降的妖魔是什么根灯? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任径缅,我火速辦了婚禮掺栅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纳猪。我一直安慰自己氧卧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布氏堤。 她就那樣靜靜地躺著沙绝,像睡著了一般搏明。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闪檬,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天星著,我揣著相機(jī)與錄音,去河邊找鬼粗悯。 笑死虚循,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的样傍。 我是一名探鬼主播横缔,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼衫哥!你這毒婦竟也來了茎刚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤撤逢,失蹤者是張志新(化名)和其女友劉穎膛锭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笛质,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泉沾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妇押。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跷究。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖敲霍,靈堂內(nèi)的尸體忽然破棺而出俊马,到底是詐尸還是另有隱情,我是刑警寧澤肩杈,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布柴我,位于F島的核電站,受9級特大地震影響扩然,放射性物質(zhì)發(fā)生泄漏艘儒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一夫偶、第九天 我趴在偏房一處隱蔽的房頂上張望界睁。 院中可真熱鬧,春花似錦兵拢、人聲如沸翻斟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽访惜。三九已至嘹履,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間债热,已是汗流浹背砾嫉。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窒篱,地道東北人焰枢。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像舌剂,于是被迫代替她去往敵國和親济锄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容