Element UI Table組件基本使用(官方文檔)
Sortable.js 官方文檔
實現步驟
1. 安裝SortableJS
通過npm安裝:
npm install sortablejs --save
或使用國內CDN(推薦):
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.14.0/Sortable.min.js"></script>
2. 基礎拖拽功能實現
在Vue組件中,通過ref獲取Table的body部分,初始化Sortable實例:
<template>
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
border
style="width: 100%">
<el-table-column type="index" width="50"></el-table-column>
<el-table-column prop="name" label="名稱"></el-table-column>
<el-table-column prop="order" label="排序"></el-table-column>
</el-table>
</template>
<script>
import Sortable from 'sortablejs'
export default {
data() {
return {
tableData: [
{ id: 1, name: '項目A', order: 1 },
{ id: 2, name: '項目B', order: 2 },
{ id: 3, name: '項目C', order: 3 }
],
sortable: null
}
},
mounted() {
this.initSortable()
},
beforeDestroy() {
if (this.sortable) {
this.sortable.destroy()
}
},
methods: {
initSortable() {
// 獲取Table的tbody元素
const tbody = this.$refs.dragTable.$el.querySelector('.el-table__body-wrapper tbody')
this.sortable = new Sortable(tbody, {
// 拖拽時的動畫效果
animation: 150,
// 拖拽結束後的回調
onEnd: (evt) => {
// 原索引
const oldIndex = evt.oldIndex
// 新索引
const newIndex = evt.newIndex
// 處理數據排序
this.handleDataSort(oldIndex, newIndex)
}
})
},
handleDataSort(oldIndex, newIndex) {
// 複製原數組
const newArray = [...this.tableData]
// 刪除原位置元素並插入新位置
const [removed] = newArray.splice(oldIndex, 1)
newArray.splice(newIndex, 0, removed)
// 更新排序號
newArray.forEach((item, index) => {
item.order = index + 1
})
// 更新數據
this.tableData = newArray
// 這裏可以添加保存到後端的API調用
// this.saveSortOrder(newArray)
}
}
}
</script>
3. 實現原理詳解
拖拽排序功能的實現主要分為三個核心步驟:
3.1 初始化Sortable實例
在Vue的mounted生命週期鈎子中,通過Table組件的ref獲取到表格的DOM元素,並找到包含行數據的tbody元素。SortableJS通過監聽這個tbody元素來實現拖拽功能。
關鍵代碼位於packages/table/src/table.vue的渲染結構中,表格主體使用了.el-table__body-wrapper類包裹,其中的tbody就是我們需要監聽的目標元素。
3.2 拖拽事件處理
SortableJS提供了豐富的事件回調,我們主要使用onEnd事件在拖拽結束後觸發數據更新。拖拽過程中,SortableJS會自動處理DOM元素的位置變化,我們只需要關注數據層面的調整。
3.3 數據排序與同步
當拖拽結束後,通過oldIndex和newIndex確定數據移動的方向和距離,然後調整數據數組中元素的順序,並更新排序號。最後可以選擇將新的排序結果同步到後端數據庫。
4. 高級功能擴展
4.1 禁用特定行拖拽
有時我們需要禁止某些行的拖拽功能,可以通過Sortable的filter配置實現:
initSortable() {
const tbody = this.$refs.dragTable.$el.querySelector('.el-table__body-wrapper tbody')
this.sortable = new Sortable(tbody, {
animation: 150,
// 過濾不可拖拽的行
filter: '.no-drag',
// 拖拽結束後的回調
onEnd: (evt) => {
this.handleDataSort(evt.oldIndex, evt.newIndex)
}
})
}
然後在Table組件中為特定行添加no-drag類:
<el-table
ref="dragTable"
:data="tableData"
row-key="id"
:row-class-name="rowClassName"
border
style="width: 100%">
<!-- 列定義 -->
</el-table>
methods: {
rowClassName({row}) {
// 對id為2的行禁用拖拽
return row.id === 2 ? 'no-drag' : ''
}
}
4.2 拖拽時樣式自定義
通過CSS可以自定義拖拽過程中的樣式:
/* 拖拽過程中的行樣式 */
.el-table__body tr.sortable-ghost {
opacity: 0.8;
background-color: #f5f5f5;
}
/* 拖拽時的佔位符樣式 */
.el-table__body tr.sortable-placeholder {
background-color: #e9f7ef;
border: 1px dashed #409eff;
}
/* 禁止拖拽的行樣式 */
.el-table__body tr.no-drag {
opacity: 0.6;
cursor: not-allowed;
}
4.3 結合後端實現持久化
在實際應用中,我們需要將排序結果保存到後端,實現數據持久化:
methods: {
async handleDataSort(oldIndex, newIndex) {
// 處理數據排序(同上)
// ...
// 保存到後端
try {
await this.$api.saveSortOrder(newArray.map(item => ({
id: item.id,
order: item.order
})))
this.$message.success('排序已保存')
} catch (error) {
this.$message.error('保存失敗,請重試')
// 保存失敗時恢復原排序
this.tableData = [...this.originalData]
}
}
}
5. 性能優化建議
對於數據量較大的表格,建議添加以下優化措施:
- 虛擬滾動:結合Element UI的InfiniteScroll(packages/infinite-scroll)實現虛擬滾動,只渲染可見區域的行。
- 節流處理:如果需要在拖拽過程中實時更新某些數據,可以對更新函數進行節流處理:
import { throttle } from 'throttle-debounce'
// 在methods中
updateDuringDrag: throttle(100, function(row, position) {
// 實時更新邏輯
})
- 禁用不必要的動畫:對於數據量超過100行的表格,可以考慮關閉Sortable的animation選項以提高性能。
6. 常見問題解決方案
6.1 拖拽後表格行高度異常
這通常是由於Table組件的高度計算問題導致的,可以在數據更新後調用Table的doLayout方法重新計算佈局:
this.$nextTick(() => {
this.$refs.dragTable.doLayout()
})
相關代碼位於packages/table/src/table.vue的doLayout方法:
doLayout() {
if (this.shouldUpdateHeight) {
this.layout.updateElsHeight();
}
this.layout.updateColumnsWidth();
}
6.2 固定列(fixed)拖拽問題
當表格使用了fixed列時,拖拽可能會出現視覺錯位。解決方案是同時監聽固定列和主表格的拖拽事件:
initSortable() {
// 主表格tbody
const mainTbody = this.$refs.dragTable.$el.querySelector('.el-table__body-wrapper tbody')
// 左側固定列tbody
const fixedLeftTbody = this.$refs.dragTable.$el.querySelector('.el-table__fixed-body-wrapper tbody')
// 右側固定列tbody
const fixedRightTbody = this.$refs.dragTable.$el.querySelector('.el-table__fixed-right-body-wrapper tbody')
// 為三個tbody都初始化Sortable
[mainTbody, fixedLeftTbody, fixedRightTbody].forEach(tbody => {
if (tbody) {
new Sortable(tbody, {
// 配置同上,但只在主表格上處理數據更新
onEnd: (evt) => {
if (tbody === mainTbody) {
this.handleDataSort(evt.oldIndex, evt.newIndex)
}
}
})
}
})
}