http://www.lxweimin.com/p/47951125d15b
如何在table上實現一個可折疊展開子節點的table?先看下最終實現效果圖:
項目地址::源碼傳送門
demo:demo傳送門
技術棧:
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) * 10if(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)})}returnparent}
我們來對比一下扁平話的數據
原數據
扁平化后的數據
對比數據的前后,先明確(父節點: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){returnparams.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)即取出首層節點的標識。
結語
更復雜的需求是結合分頁,當從其他頁回到之前頁時,之前頁的折疊狀態要依舊保持。這里就不在說明了,因為應用場景很少。
點贊給個鼓勵喲~
文章出處:https://juejin.im/post/5be797456fb9a04a0378bb91。歡迎大家前去點贊支持~!