元件化頁面:封裝el-tree
一直在開發平臺類的專案。上一個專案開發的時候,依舊是每個頁面上覆制貼上,儘管頁面上的寫法規範都定義了,但依舊讓人難受,於是開始把那些常用的功能封裝成為可複用的元件,偷懶和易於維護是我想要的。
寫在前面
本文主要講的是封裝element-ui的tree元件,通過講其內建的屬性方法暴露出來,以及加上我們自定義的屬性和方法,實現一個可以在不同場景下複用的元件,因為目前只用在了個人業務上,所以或許需求不一定能滿足大家,但希望這個思路能對大家有所幫助
設計元件
自定義欄位
很有必要在聊元件之前,先將設計的欄位貼出來,在後面會根據使用依次說明設計欄位的用處。
自定義屬性
- initTree // Boolean 樹是否完成初始化載入,未完成為false
- loadType // Number 載入方式 1: 正常通過介面載入 2: 懶載入 3: 傳入資料
- refresh// Number用來重新整理的欄位
- defaultClickedAsyc // Number | String 預設點選(對樹刪除編輯新增時的臨時儲存,在樹重新整理後賦值這些資料的)
- defaultHighLightAsyc // Number | String 預設高亮(對樹刪除編輯新增時的臨時儲存,在樹重新整理後賦值這些資料的)
- defaultExpandedAsyc // Array 預設展開(對樹刪除編輯新增時的臨時儲存,在樹重新整理後賦值這些資料的)
- treeData // Array 樹渲染資料(非懶載入時由外部渲染)
- baseData // Array 樹的基礎資料,從元件中獲取到
- loadInfo // Object 正常載入相關配置
- lazyInfo // Object 懶載入相關配置
- leftClickData // Object 左鍵點選資料
- rightClickData // Object 右鍵點選資料
- rightMenuList // Array 右鍵選單配置
展開loadInfo 欄位
當loadType 為1時,需要配置loadInfo 欄位。
loadInfo: { key: 'id', // 節點唯一標識欄位 label: 'name', // 節點顯示欄位 api: getAllApi, // 獲取資料的介面 params: { data: [{ key: 'type', value: 1 }], type: 'query' }, resFieldList: ['content'] // 資料所在欄位 }
api是要去請求的介面
params是介面獲取資料的引數,型別分為url和query,url表示跟隨在路徑後面的引數,例如/:id,只有一個,query則表示key和value型別的引數,可以為多個
resFieldList是資料響應成功之後所在的欄位,比如資料在res.content.data,則需要傳入['content', 'data'], 在元件內部會通過拼接得到對應的資料
key是獲取到的資料的唯一標識欄位,如果是懶載入,元件內部會對這個key在做處理以保證key的唯一性
label是獲取到的資料在樹中要顯示的欄位名稱
展開lazyInfo 欄位
當loadType 為2時,需要配置lazyInfo 欄位。
lazyInfo: [ { key: 'id', // 節點唯一標識欄位 label: 'name', // 節點顯示欄位 type: 1, // 資料型別 api: getAllApi, // 獲取資料的介面 params: { key: 'pid', value: 1, type: 'url' }, // 獲取資料的引數 resFieldList: ['content'] // 資料所在欄位 }, { key: 'id', label: 'name', type: 2, api: getAllApi, params: { key: 'pid', value: '', type: 'url' }, resFieldList: ['content'], // 資料所在欄位 leaf: true } ]
和正常通過介面載入不一樣,懶載入因為節點的不確定性以及其自身的靈活,所以載入配置為一個數組,陣列的每一項對應著載入節點的配置,這邊單獨拿出一項來進行分析。
api是要去請求的介面
params是介面獲取資料的引數,型別分為url和query,url表示跟隨在路徑後面的引數,例如/:id,只有一個,query則表示key和value型別的引數,可以為多個,懶載入的key和value和普通載入是不同的,引數是從上一個介面返回資料中得到的
resFieldList是資料響應成功之後所在的欄位,比如資料在res.content.data,則需要傳入['content', 'data'], 在元件內部會通過拼接得到對應的資料
key是獲取到的資料的唯一標識欄位,如果是懶載入,元件內部會對這個key在做處理以保證key的唯一性
label是獲取到的資料在樹中要顯示的欄位名稱
type是懶載入中需要設定的,我們對當前介面返回的資料設定為什麼型別,標識,易於頁面上去做一些邏輯操作
leaf是設定為當前載入的節點為葉子節點
el-tree自帶屬性
-
nodeKey// Number | String 樹的node-key
- defaultClicked // Object 預設點選 (設定為物件,保證資料能被監聽到)
- defaultHighLight // Number | String 高亮節點
- defaultExpanded // Array 預設展開
- clickNode // Boolean 是否手風琴點選
- expandAll // Boolean 是否展開全部
- checkBox // Boolean 是否有選中框
- checkStrictly // Boolean 在有複選框的時候,是否·遵循父子不互關聯
- draggable // Boolean 是否可拖拽
- ........
元件原來自帶的屬性這邊不一一列舉,因為是元件的二次封裝,所以原來自帶的屬性首先我們是需要將它們暴露出去的,或者經過自己的加工後暴露出去。
設計載入方式
根據日常使用,樹載入一般分為兩種方式,1. 傳入資料 2. 懶載入 , 個人是又將方式又重新定義了一下,分為三種,1. 正常通過介面載入 2. 懶載入 3. 傳入資料 , 下面會通過引數設計和使用具體來分析這三種載入方式。
通過正常介面載入
當設定loadType為1時,則表示為正常介面載入,配置loadInfo。
載入程式碼完整如下:
// 正常通過介面載入 initData () { // 非正常載入 if (this.loadType !== 1) return // 載入loading this.treeLoading = true const treeProps = this.treeProps const loadInfo = this.loadInfo const params = loadInfo.params || {} let data if (params.type === 'url') { data = params.value } else if (params.type === 'query') { data = {} params.data.forEach(item => { data[item.key] = item.value }) } else { // console.log('沒有傳引數型別') } loadInfo.api(data).then(res => { let arr = [] if (res.success) { let resData = res const resFieldList = loadInfo.resFieldList // 得到定義的響應成功的資料欄位 for (let i = 0; i < resFieldList.length; i++) { resData = resData[resFieldList[i]] } // 資料處理 arr = JSON.parse(JSON.stringify(resData)) arr.forEach(item => { // 保證重新整理之後key的唯一 item.key = item[loadInfo.key] item[treeProps.label] = item[loadInfo.label] }) // 得到資料後把資料給到父級,方便父級用到 this.$emit('update:baseData', arr) // 設定預設高亮 if (this.defaultHighLight || this.defaultHighLight === 0) { this.$nextTick(() => { this.$refs.TreeComponent.setCurrentKey(this.defaultHighLight) }) } // 設定預設點選 if ((this.defaultClicked && (this.defaultClicked.id || this.defaultClicked.id === 0))) { // 頁面初始化,設定預設點選項, 並將點選事件派發到父級 this.$emit('handleEvent', 'leftClick', { data: this.getSelectData(loadInfo.key, this.baseData, this.defaultClicked.id) || {}}) } } else { this.$message({ showClose: true, message: res.message, type: res.success ? 'success' : 'error', duration: 2000 }) } // 載入loading this.treeLoading = false }).catch(() => { // 載入loading this.treeLoading = false }) }
懶載入
當設定loadType為2時,則表示為懶載入,配置lazyInfo。
元件內部方法獲取到相關配置,然後呼叫介面以及配置得到對應的資料,從而可以實現第二個載入型別,通過懶載入載入樹狀資料。
載入完整程式碼如下:
// 懶載入資料 handleLoadNode (node, resolve) { // 非懶載入 if (this.loadType !== 2) return // 載入loading if (node.level === 0) { this.treeLoading = true } // 存下每個懶載入的資料 this.$set(this.nodeInfoList, 'node' + node.level, { node, resolve }) // 懶載入延遲時間 const timeStamp = 100 const treeProps = this.treeProps const levelInfo = this.lazyInfo[node.level] const params = levelInfo.params; let data if (params.type === 'url') { data = this.refreshLevel > 0 ? node.data[levelInfo.key] : params.value || params.value === 0 ? params.value : node.data[levelInfo.key] } else if (params.type === 'query') { params.data.forEach(item => { data = {} data[item.key] = item.default || node.data[item.value] }) } else { console.log('沒有傳引數型別') } levelInfo.api(data).then(res => { let arr = [] if (res.success) { let resData = res const resFieldList = levelInfo.resFieldList // 得到定義的響應成功的資料欄位 for (let i = 0; i < resFieldList.length; i++) { resData = resData[resFieldList[i]] } // 資料處理 arr = JSON.parse(JSON.stringify(resData)) arr.forEach(item => { // 保證key的唯一 item.key = levelInfo.type + item[levelInfo.key] item['level' + node.level + 'data'] = node.data item[treeProps.label] = item[levelInfo.label] item.type = levelInfo.type item[treeProps.isLeaf] = levelInfo.leaf }) // 得到資料後把資料給到父級,方便父級用到 this.$emit('update:baseData', [...this.baseData, ...arr]) // 設定預設高亮 if (this.defaultHighLight || this.defaultHighLight === 0) { this.$nextTick(() => { this.$refs.TreeComponent.setCurrentKey(this.defaultHighLight) }) } // 設定預設點選 if ((this.defaultClicked && (this.defaultClicked.id || this.defaultClicked.id === 0))) { // 頁面初始化,設定預設點選項, 並將點選事件派發到父級 this.$emit('handleEvent', 'leftClick', { data: this.getSelectData(levelInfo.key, this.baseData, this.defaultClicked.id) || {}}) } } else { this.$message({ showClose: true, message: res.message, type: res.success ? 'success' : 'error', duration: 2000 }) } // 延遲載入,保證載入動畫 setTimeout(() => { resolve(arr) }, timeStamp) // 載入loading if (node.level === 0) { this.treeLoading = false } }).catch(() => { // 延遲載入,保證載入動畫 setTimeout(() => { resolve([]) }, timeStamp) // 載入loading if (node.level === 0) { this.treeLoading = false } }) }
傳入資料
傳入資料就是簡單將樹結構資料傳入元件中,這個為第三種方式的載入。
設計右鍵選單
樹元件的右鍵選單,可以減少頁面上多餘的按鈕,通過右鍵互動確實省了很多事,所以這個功能是必須要封裝的。
分析問題
- 右鍵點選,有兩種型別,一種是點選在樹上,一種是點選在樹節點上,需要怎麼處理?
- 右鍵點選選單如何生成,當點選選單在頁面底部的時候看不見了怎麼處理?
- 如何設定動態的右鍵選單,根據當前不同的節點,不同的資料,生成自定義的選單?
解決問題
- 分別為樹和節點設定右鍵,分別處理
- 右鍵選單生成,需要獲取到當前點選的位置,然後在當前點選位置生成選單即可,點選的時候根據選單長度判斷到瀏覽器底部的距離,來判斷生成選單是向下展開還是向上展開的即可。
- 設定動態選單需要元件和外部頁面配置,這個單獨講
動態的右鍵選單
在不同的業務下,不同的介面下,不同的資料結構,點選節點如何生成對應的右鍵選單,這裡來分析一波。
從右鍵點選到選單點選到事件完成,一共分成了五個步驟:
- 右鍵點選樹或者樹節點,將點選的資訊派發到父級頁面
- 父級頁面根據當前點選的資料,生成對應的選單資料結構,傳入到元件
- 元件內部接收到選單資料,迴圈出對應的dom顯示出來
- 點選元件生成的對應的右鍵選單,元件內部將當前點選的選單選項派發給父級頁面
- 父級頁面根據右鍵選單的型別完成對應要執行的邏輯
一個流程下來,才算是一個完整的右鍵選單生成過程。
結論,我們只需要在樹元件內部做右鍵點選的派發和右鍵選單的點選派發事件,即可完成動態的右鍵選單。
最後
呃呃,本來想完整的把一個二次封裝el-tree詳細分享以下的,但覺得完整的分析還是過於麻煩,因為設計的欄位和功能還是有些多,要詳細的把設計的欄位和相關用意說清楚還是比較麻煩,這邊就主要希望能把思路給到大家,幫助大家在日常開發中能夠更加高效率的完成工作。