聲明:文中代碼整體思路來源於 樑灝 編著的 【Vue.JS 實戰】一書,學習過程中因覺得該組件效果不錯,比較實用,所以記錄一份並做了詳細的註釋以供學習
效果圖
代碼
- index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>表格內容排序組件</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="style.css" />
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
</head>
<body>
<div id="demo" v-cloak>
<v-table :thead="thead" :tbody="tbody"></v-table>
<button @click="handleAddData">隨機添加一條數據</button>
</div>
<script src="table.js"></script>
<script src="index.js"></script>
</body>
</html>
- index.js
var demo = new Vue({
el: '#demo',
data: {
//構造表頭
thead: [{
title: 'name',
key: 'name',
},
{
title: 'age',
key: 'age',
sortable: true,
}, {
title: 'birthday',
key: 'birthday',
sortable: true,
}, {
title: 'address',
key: 'address',
}
],
//構造表數據
tbody: [{
name: '張三',
age: 18,
birthday: '1937-10-01',
address: '深圳龍華區'
}, {
name: '李四',
age: 20,
birthday: '1999-05-18',
address: '北京海淀區',
}, {
name: '王五',
age: 23,
birthday: '1996-02-15',
}, ],
},
methods: {
/* 添加按鈕觸發事件,隨機添加一筆數據 */
handleAddData: function () {
/*隨機漢字生成*/
eval("var firstname=" + '" \\u' + (Math.round(Math.random() * 20901) + 19968).toString(16) + '"');
eval("var lastname=" + '" \\u' + (Math.round(Math.random() * 20901) + 19968).toString(16) + '"');
var starttime =
this.tbody.push({
/* 構造兩個字的隨機用戶名 */
name: firstname + lastname,
/* 隨機數[0, 20]向上取整。新學習的寫法,現學現用 */
age: ~~(Math.random() * 20) + 1,
// birthday: '2019-06-10',
/* 隨機構造一個日期,因時間戳轉換代碼太過繁瑣,且這裏並非重點,只是爲了體現排序,所以用字符串構造(還會出現2月30日) */
birthday: (1989 + ~~(Math.random() * 20)) + '-' + (~~(Math.random() * 11) + 1) + '-' + (~~(Math.random() * 29) + 1),
address: '廣東河源',
})
}
},
})
- table.js
Vue.component('vTable', {
/* 接受父組件參數 */
props: {
/* 表頭:Array 類型,默認爲空數組 */
thead: {
type: Array,
default: function () {
return [];
}
},
/* 表身:Array 類型,默認爲空數組 */
tbody: {
type: Array,
default: function () {
return [];
}
}
},
/* 組件中的數據 */
data() {
return {
/* 初始化表頭數組 */
currentHead: [],
/* 初始化表身數組 */
currentBody: [],
}
},
/* render 函數構造表格 */
render: function (createElement) {
/**
* 定義 _this 臨時存儲 Vue 實例。
*/
var _this = this;
/* 定義數組。用來存儲表頭信息,含節點 */
var ths = [];
/* 遍歷表頭信息 */
this.currentHead.forEach((ele, index) => {
/* 如果當前列允許排序 即 sortable 爲 Truely */
if (ele.sortable) {
/**
* 構造節點並push 到 ths 數組
* 節點基本結構如下:
* <th>
* <span>ele.title</span>
* <a class="on" @click="handleSortByAsc">↑</a>
* <a class="on" @click="handleSortByDesc">↓</a>
* </th>
*/
ths.push(createElement('th', [
createElement('span', ele.title),
createElement('a', {
class: {
on: ele._sortType === 'asc'
},
on: {
click: function () {
_this.handleSortByAsc(index);
}
}
}, '↑'),
createElement('a', {
class: {
on: ele._sortType === 'desc'
},
on: {
click: function () {
_this.handleSortByDesc(index);
}
}
}, '↓'),
]));
/* 如果當前列不允許排序操作,即 sortable 爲 Falsely */
} else {
/**
* 節點基本結構如下:
* <th>
* <span>ele.title</span>
* </th>
*/
ths.push(createElement('th', ele.title));
}
});
/* 定義數組。用來存儲表身信息,含節點 */
var trs = [];
/* 遍歷表身信息 */
this.currentBody.forEach((ele, index) => {
/* 循環內定義臨時數組,存儲當前 tr 的數據 */
var tds = [];
/* 遍歷表頭,根據 key 值獲取當前 tr 對應的 td 的內容,並 push 到 tds 數組*/
this.currentHead.forEach(cell => {
tds.push(createElement('td', ele[cell.key]));
});
/* 將取到的 tr 數據 push 到 tr 數組中 */
trs.push(createElement('tr', tds));
});
/**
* 返回節點。
* 節點基本結構如下:
* <table>
* <thead>
* <tr>
* ...ths數組的內容...
* </tr>
* </thead>
* <tbody>
* ...trs 數組的內容...
* </tbody>
* </table>
*/
return createElement('table', [
createElement('thead', [
createElement('tr', ths)
]),
createElement('tbody', trs)
])
},
methods: {
/* 初始化組件中表頭數組,數據源來自父組件 */
makeHead: function () {
this.currentHead = this.thead.map((col, index) => {
/* 默認排序類型爲 normal */
col._sortType = 'normal';
col._index = index;
return col;
});
},
/* 初始化組件中表身數組,數據源來自父組件 */
makeBody: function () {
this.currentBody = this.tbody.map((row, index) => {
row._index = index;
return row;
})
},
/* 正序排列 從小到大 */
handleSortByAsc: function (index) {
/* 獲取當前點擊列的 key 值 */
var key = this.currentHead[index].key;
/* 將表頭所有列的排序類型置爲默認 normal */
this.currentHead.forEach(col => {
col._sortType = 'normal';
});
/* 修改當前點擊列的排序類型爲 asc */
this.currentHead[index]._sortType = 'asc';
/* 對錶身數組進行正序排列 */
this.currentBody.sort((a, b) => {
return a[key] > b[key] ? 1 : -1;
})
},
/* 倒序排列 從大到小 */
handleSortByDesc: function (index) {
/* 獲取當前點擊列的 key 值 */
var key = this.currentHead[index].key;
/* 將表頭所有列的排序類型置爲默認 normal */
this.currentHead.forEach(col => {
col._sortType = 'normal';
});
/* 修改當前點擊列的排序類型爲 desc */
this.currentHead[index]._sortType = 'desc';
/* 對錶身數組進行倒序排列 */
this.currentBody.sort((a, b) => {
return a[key] < b[key] ? 1 : -1;
})
}
},
/* 定義監聽器 */
watch: {
/* 監聽表身數據。當表身數據發生變化時,觸發該事件 */
tbody: function () {
/* 初始化表身 */
this.makeBody();
/* 過濾排序類型不爲 normal 的表頭,即獲取指定了排序類型的字段 */
var sortedColumn = this.currentHead.filter(col => {
return col._sortType !== 'normal';
});
/* 如果指定了排序類型 */
if (sortedColumn.length > 0) {
if (sortedColumn[0]._sortType === 'asc') {
/* 正序排列 */
this.handleSortByAsc(sortedColumn[0]._index);
} else {
/* 倒序排列 */
this.handleSortByDesc(sortedColumn[0]._index);
}
}
}
},
/* 鉤子函數。當Vue實例掛載後執行 */
mounted() {
/* 初始化表頭 */
this.makeHead();
/* 初始化表身 */
this.makeBody();
},
})
- style.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 {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
white-space: nowrap;
}
table th,
table td {
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
table th a {
display: inline-block;
margin: 0 4px;
cursor: pointer;
}
table th a.on {
color: #3399FF;
}
table th a:hover {
color: #3399FF;
}