實現一個可無限摺疊的table
如何在table上實現一個可摺疊展開子節點的table?先看下最終實現效果圖:

其實這個專案在兩個月以前就以上上傳在 github 了,但當時沒有寫詳細的實現過程。自己前幾天發表的一篇技術貼 ofollow,noindex">當下拉列表資料過大時,該如何應對? 得到大家的不少支援,猶如歌曲《紅日》裡的歌詞 像紅日之火 點燃真的我 ~ 所以繼續為掘金社群做奉獻自己微不足道的力量啦 ~~
在週末聽著嗨歌寫文還是很nice的~ 嗨起來 ~ ~
技術棧:
- vue
- javascript
動手實現
為有興趣的同學先準備一下大綱目錄預覽,為了不讓同學們看入迷,讓大綱為你 指明前行的道路 !
大綱預覽
- 明確需求
- 樹形結構資料準備
- 資料扁平化 (重點)
- 層級展示
- 摺疊展開功能實現
1. 明確需求
實現一個可摺疊展開的table,看上去很迷茫的樣子。但實際上就是:
- 在 table 上點選時對其 子集 進行 隱藏 或 顯示
- 通過縮排的距離來表現層級關係
在程式碼裡很東西其實都是偽裝出來的,例如我們要實現的這個可無限摺疊的table。但在使用者操作的時候看來就是那麼回事咯 ~ ~
2. 樹形結構資料準備
這裡已經準備好了樹形結構的資料,存放於 data.js 的檔案中,節點通過 Children
連線。如標題所說,可無限摺疊,無論這裡的樹形資料有多少層級,都能給它辦妥當了。先把牛吹在這,接下來看看如何實現。

3. 資料扁平化
條件梳理
樹形結構資料扁平化並不難,難點在於在扁平化的時候要做的處理。這裡深入梳理一下:
- 我們將一個樹節點的所有父節點稱為 family ,好比我們要知道的自己的大領導、二領導、直系領導...因為他們的命令咱們都得執行。在這個表單中就是當父節點或更高的祖輩節點發命令時,做一個懂事的子節點當然要聽話辦事。
__family
欄位就是用來存放所有所有的父節點的陣列。在扁平化資料是通過 陣列欄位__family
收集其所有 父節點 。這樣一來,就像族譜一樣,就能清晰知道該節點的所有父節點。 至於如和摺疊收縮呢? 我們通過一個foldList
陣列來記錄要 摺疊的節點 ,也稱為 死亡名單 。如此一來,只要 包含父節點標識 的 節點 就乖乖的隱藏吧。
- 每個成員都因該是唯一的,那麼我們在遍歷時都應該為每個成員新增一個唯一標識
__identity
。正是上一個步驟中說的 節點標誌 。
- 為了明確知道各個節點之間的層級關係,通過
__level
明確層級關係。那麼我們規定__level
的值越小等級越高。__level=0
代表首層節點。
- 既然是無限層級,肯定是用遞迴來實現了。
formatConversion()
方法實現遞迴。 那何時跳出當前遍歷的遞迴呢 ?當前節點沒有子集時Children.length = 0
的時候跳出本次遞迴,進行下一次遞迴。該方法可能需要花點時間理解,程式碼裡註釋已寫的比較詳細,有問題歡迎留言溝通~~
資料扁平化
/********************************* ** Fn: formatConversion ** Intro: 將樹形介面資料扁平化 ** @params: parent 為當前累計的陣列也是最後返回的陣列 ** @params: children 為當前節點仍需繼續扁平子節點的資料 ** @params: index 預設等於0, 用於在遞迴中進行累計疊加 用於層級標識 ** @params: family 裝有當前包含元素自身的所有父級 身份標識 ** @params: elderIdentity 父級的唯一身份標識 ** Author: zyx *********************************/ formatConversion (parent, children, index = 0, family = [], elderIdentity = 'x') { // children如果長度等於0,則代表已經到了最低層 // let page = (this.startPage - 1) * 10 if (children.length > 0) { children.map((x, i) => { // 設定 __level 標誌位 用於展示區分層級 Vue.set(x, '__level', index) // 設定 __family 為家族關係 為所有父級,包含本身在內 Vue.set(x, '__family', [...family, elderIdentity + '_' + i]) // 本身的唯一標識可以理解為個人的身份證咯 一定唯一。 Vue.set(x, '__identity', elderIdentity + '_' + i) parent.push(x) // 如果仍有子集,則進行遞迴 if (x.Children.length > 0) this.formatConversion(parent, x.Children, index + 1, [...family, elderIdentity + '_' + i], elderIdentity + '_' + i) }) } return parent } 複製程式碼
我們來對比一下扁平話的資料
- 原資料

- 扁平化後的資料

對比資料的前後,先明確(父節點: Name: "App"
), (子節點: Name: "企業查詢"
)。我們先看看__level欄位,分別對應0和1,沒問題。 __family
包含了本身節點。 __identity
的個格式就是 字首 x 加上在 資料中的位置 。例如節點 App 在資料中的位置是第一個節點,那對應的 __identity
即是 x_0
。 企業查詢 位於 App 節點下的第一個元素, 則在父節點的標識的基礎上追加一位0 ,那對應的 __identity
即是 x_0_0
。其他依次類推。一切已準備妥當~~
4. 層級展示
如圖所示:

如果展示層級呢?利用css即可實現
- 字型圖標準備: 這裡先補充一點,這裡涉及兩個字型圖示,圖中紅色框標註的,資源存放於src目錄下的iconfont目錄下中。 層級最低的欄位時不需要展示該圖示呢 , 該如何判斷呢? 通過判斷當前節點是否還有子集
params.Children.length === 0
即可判斷。若沒有子集則不設定字型圖示即可。 點選時 還需要對 圖示進行切換 ,這又如何實現呢?前臺提到過的 死亡名單foldList
,如果該存在該名單中,代表該資料已經被摺疊,那返回摺疊圖示。否則返回展開圖示。如程式碼:
//html <i :class="toggleFoldingClass(scope.row)"></i> //js methods: /********************************* ** Fn: toggleFoldingClass ** Intro: 如果子集長度為0,則不返回字型圖示。 ** Intro: 如果子集長度為不為0,根據foldList是否存在當前節點的標識返回相應的摺疊或展開圖示 ** Intro: 關於class說明:permission_placeholder返回一個佔位符,具體檢視class ** @params: params 當前行的資料物件 ** Author: zyx *********************************/ toggleFoldingClass (params) { return params.Children.length === 0 ? 'permission_placeholder' : (this.foldList.indexOf(params.__identity) === -1 ? 'iconfont icon-minus-square-o' : 'iconfont icon-plussquareo') }, 複製程式碼
- 層級展示:
__level
欄位就是這時候發揮用處。__level
值越大,則將margin-left
值增大。如程式碼:
<p :style="`margin-left: ${scope.row.__level * 20}px;`">... 複製程式碼
基本的樣子已經有了,那接下來實現點選功能。
5. 摺疊展開功能實現
記錄點選的節點標識
通過 死亡名單 foldList
來記錄點選。點選事件在點選摺疊展開圖示時觸發 toggleFoldingStatus(scope.row)
事件,前面也說過, foldList
存在的標識,對於所有的子節點都要隱藏起來。如果 foldList
沒有資料,則全部展開,萬事大吉。程式碼如圖所示,就是那麼簡單。
//html <i@click="toggleFoldingStatus(scope.row)" class="permission_toggleFold" :class="toggleFoldingClass(scope.row)"></i> //js methods /********************************* ** Fn: toggleFoldingStatus ** Intro: 切換展開 還是摺疊 ** @params: params 當前點選行的資料 ** Author: zyx *********************************/ toggleFoldingStatus (params) { this.foldList.includes(params.__identity) ? this.foldList.splice(this.foldList.indexOf(params.__identity), 1) : this.foldList.push(params.__identity) }, 複製程式碼
摺疊展開功能實現
萬事俱備,只欠東風。最後一件要做的大事就是根據 死亡名單 foldList
來進行顯示和隱藏資料。 這裡藉助el-table中的 row-style
實現。同學們也可以自己實現。來看下官方的說明

該方法就是為table中的每一行資料設定樣式。
/********************************* ** Fn: toggleDisplayTr ** Intro: 該方法會對每一行資料都做判斷 如果foldList 列表中的元素 也存在與當前行的 __family列表中則該行不展示 ** @params: ** Author: zyx *********************************/ toggleDisplayTr ({row, index}) { for (let i = 0; i < this.foldList.length; i++) { let item = this.foldList[i] // 如果foldList中元素存在於 row.__family中,則該行隱藏。如果該行的自身標識等於隱藏元素,則代表該元素就是摺疊點 if (row.__family.includes(item) && row.__identity !== item) return 'display:none;' } return '' }, 複製程式碼
- 在來看看效果
錦上添花
如果資料太多的話,一個層級層級的去搜索確實也麻煩,所以那麼我們再來新增兩個按鈕 全部摺疊 和 全部展開 好了。 諮詢分析一下,其實這個功能很簡單。還是關於前面提到過的 死亡名單 foldList
。
- 全部展開 : 如果
foldList
為空,則萬事大吉,資料全部展開。 - 全部摺疊 :我們的設計是隻要存在於死亡名單的所有包含該標識的節點都要隱藏。那麼只要將所有的首層節點新增進去就可以了。
this.foldList = this.tableListData.map(x => x.__identity)
即取出首層節點的標識。
結語
更復雜的需求是結合分頁,當從其他頁回到之前頁時,之前頁的摺疊狀態要依舊保持。這裡就不在說明了,因為應用場景很少。
- 點贊給個鼓勵喲~
- 又是下午3點了,該回去吃飯午飯了。