vue目錄樹組件(樹狀結構列表)


一般數據類展示內容,大多采用樹狀結構展示內容。類似效果如下:

關注微信公眾號,查看效果

在這里插入圖片描述

左側是導航分類,可以進行新建,對單項導航分享和刪除。單擊導航,在右側查詢出當前導航下所有目錄結構,可以新建目錄。新增類型分為三種,目錄可以無限嵌套,當然也可以設置層級。

頁面整體布局

頁面分為左右兩個部分。左側列表,通過路由跳轉顯示右側內容。左側列表分為上下兩塊,頂部是添加按鈕,下面是導航列表。

 

 

 

less樣式。

import "../../theme/variables.less";


.main {
    position: relative;
    height: 100%;
    overflow: hidden;
    .content {
        border: 1px solid #dcdcdc;
        position: relative;
        height: 100%;
        background: #f1f2f7;
        display: flex;
        border-radius: @borderRadius;
        .left {
            width: 240px;
            background: #fff;
            border-right: 1px solid rgba(220, 220, 220, 1);
            padding: 15px 10px;
            display: flex;
            flex-direction: column;
            overflow: auto;
            .header {
                width: 100%;
                margin-bottom: 20px;
                display: flex;
                justify-content: center;
                align-items: center;
                .btn {
                    width: 136px;
                    margin: 0 6px;
                    :global {
                        .icon {
                            margin-right: 14px;
                        }
                        .customIcon {
                            display: inline-block;
                            transform: rotate(45deg);
                        }
                    }
                }
            }
            .treeLayout {
                flex: 1;
                .item {
                    width: 100%;
                    height: 32px;
                    line-height: 32px;
                    margin-bottom: 11px;
                    position: relative;
                    .link {
                        display: flex;
                        align-items: center;
                        font-size: 14px;
                        font-family: Microsoft YaHei;
                        font-weight: 400;
                        color: rgba(51, 51, 51, 1);
                        padding-left: 21px;
                        cursor: pointer;
                        .catalogIcon {
                            font-size: 12px;
                        }
                        .text {
                            display: inline-block;
                            flex: 1;
                            margin-left: 12px;
                        }
                        .opBtn {
                            width: 46px;
                            display: flex;
                            align-items: center;
                            justify-content: space-between;
                        }
                        .operateIcon {
                            display: none;
                        }
                        &:hover {
                            color: #00a4ff;
                            .opBtn {
                                color: rgba(51, 51, 51, 1);
                            }
                            .operateIcon {
                                display: block;
                            }
                        }
                    }
                    .iconBtns {
                        position: absolute;
                        top: 28px;
                        right: 24px;
                        width: 112px;
                        background: rgba(255, 255, 255, 1);
                        box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.13);
                        border-radius: 4px;
                        z-index: 10;
                        .icon {
                            width: 100%;
                            height: 40px;
                            border-radius: 2px;
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            font-size: 12px;
                            font-family: Microsoft YaHei;
                            font-weight: 400;
                            color: rgba(51, 51, 51, 1);
                            cursor: pointer;
                            .iconName {
                                margin-left: 18px;
                            }
                            &:hover {
                                background: #e7e8e9;
                            }
                        }
                    }
                }
                .itemActive {
                    .link {
                        color: #00a4ff;
                        .opBtn {
                            color: rgba(51, 51, 51, 1);
                        }
                        .operateIcon {
                            display: block;
                        }
                    }
                }
            }
        }
        .right {
            flex: 1;
            width: 100%;
            overflow: hidden;
        }
    }
}

 

這里的導航列表,新增導航,和刪除都是調用相關接口。

目錄樹組件

頁面右側就是樹狀結構列表,通過路由跳轉攜帶catalogId參數,接口查詢出列表數據,后端返回的數據就是有層級的樹狀結構。

我認為的寫一個組件,單指這里的目錄樹組件,組件中只需要構造出頁面布局,任何的交互邏輯都不涉及,只將相關事件拋出即可。這就需要先明確好數據結構,來寫樣式布局了。

數據結構,有id,name,父級id,子節點數組,類型catalogType:1是目錄,2是場景,3是外鏈場景 ... 如下:

 

 

 

樹狀結構會涉及到遞歸,這里為了處理方便,組件中分為兩層。組件目錄結構如下:

index就是對外暴露的窗口,主要目錄樹的布局樣式是在DomNode中。先明確一下布局,目錄樹中單個一行,需要一個展開收起的圖標,當前行類型的圖標,這里業務上分三種類型,就需要以此判斷顯示不同圖標。每項最后還會有四個操作按鈕。

這里把事件簡化了,只分了兩個事件,一個是展開收起,一個是一系列編輯操作,傳個type參數作為區分。

tabNode(node: ITree) {
    this.$emit("tabNode", node);
},


// 操作
doNode(node: ITree, type: string, index: number) {
    this.$emit("doNode", node, type, index);
},

 

index文件中引用DomNode,相關的接收的參數和拋出去的事件,和DomNode一致。

// index布局
<div class="treeLayout">
    <DomNode
        v-for="(item, index) in trees"
        :key="index"
        :node="item"
        @tabNode="tabNode"
        @doNode="doNode"
        :index="index"
    ></DomNode>
</div>


// 接收的參數
props: {
    trees: {
        type: Array as () => ITree[],
        default: [],
    },
    activeId: {
        type: String,
        default: "",
    },
},
頁面右側實現

引用catalogTree組件。

<catalog-tree
  :trees="treeList"
  @tabNode="tabNode"
  @doNode="doNode"
></catalog-tree>

 

前文已經提過,目錄數據是后端返回的,那么treeList就是后端返回值res.data。但操作tabNode和doNode這兩個方法,需要將treeList數組轉換成map對象。

因為需要自定義添加一些字段,這些字段只作為前端交互操作邏輯使用,所以后端返回值中不會攜帶。

需要給每一項數據添加isOpen字段,用來判斷展開收起狀態。level字段,用來實現上移下移操作。

先來構造這個catalogMap,定義個方法setCatalogMap,需要的參數有存放結果的treeMap,原數據treeList數組。

setCatalogMap,很簡單的一個遞歸。

拿到map對象,就可以實現tabNode和doNode這兩個方法。

// 切換狀態
tabNode(node: ITree) {
    if (node.isOpen) {
        this.treeMap[node.catalogId].isOpen = false;
    } else {
        this.treeMap[node.catalogId].isOpen = true;
    }
},


// 編輯等一系列操作,按照類型區分
doNode(node: ITree, type: string, index: number) {
    switch (type) {
        case "up":
            // 上移
            this.doUp(node, index);
            break;
        case "down":
            // 下移
            this.doDown(node, index);
            break;
        case "edit":
            // 編輯
            this.doEdit(node.catalogId);
            break;
        case "delete":
            // 刪除
            this.doDelete(node);
            break;
    }
},

 

有認真看的話,會發現,並沒有在哪里定義isOpen屬性,怎么就在tabNode方法中使用了。

因為我還沒有寫。

拿到map對象,循環做個判斷,用來保持isOpen狀態。

Object.keys(treeMap).forEach((key) => {
    const item = treeMap[key];
    if (this.treeMap[key]) {
        item.isOpen = this.treeMap[key].isOpen;
    } else {
        item.isOpen = true;
    }
});

 

doNode中的四個方法,編輯和刪除就是調個接口,主要是上移下移操作,前端實現數據的排序,最后將最新的數據返回給后端保存,doSaveSort方法調接口保存。

上代碼,好好琢磨琢磨。

doUp(node: ICatalogModel, index: number) {
    if (index === 0) {
        return;
    }
    const parentId: string = node.catalogParent as string;
    const parentItem: ICatalogModel = this.treeMap[parentId];


    let dataList: ICatalogModel[] = [];
    // 如果為空則是頂級
    if (parentItem) {
        if (parentItem.catalogTreeVoList) {
            dataList = parentItem.catalogTreeVoList;
        }
    } else {
        dataList = this.treeList;
    }
    const item = dataList[index];
    dataList.splice(index, 1);
    dataList.splice(index - 1, 0, item);
    this.doSaveSort(dataList);
},


doDown(node: ICatalogModel, index: number) {
    const parentId: string = node.catalogParent as string;
    const parentItem: ICatalogModel = this.treeMap[parentId];
    // 如果為空則是頂級
    let dataList: ICatalogModel[] = [];
    if (parentItem) {
        if (parentItem.catalogTreeVoList) {
            // 最后一個不能下移
            if (parentItem.catalogTreeVoList.length === (index + 1)) {
                return;
            } else {
                dataList = parentItem.catalogTreeVoList;
            }
        }
    } else {
        // 一級最后一個不能下移
        if ( this.treeList.length === (index + 1)) {
            return;
        }
        dataList = this.treeList;
    }
    const item = dataList[index];
    dataList.splice(index, 1);
    dataList.splice(index + 1, 0, item);
    this.doSaveSort(dataList);
},

總結

樹狀結構列表,首先需要明確數據結構,必備的字段id,name,父級id,children數組,根據數據結構,使用遞歸構建布局。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM