1. 程式人生 > >VUE餓了麼樹形控制元件新增增刪改功能

VUE餓了麼樹形控制元件新增增刪改功能

轉自:https://segmentfault.com/a/1190000011574698#articleHeader2

element-ui樹形控制元件:地址

在原文件中有個案例是有新增和刪除功能,但是後來發現其修改的資料並不能直接影響到樹形資料,所以採用了 render-content 的API重新寫了個元件。
寫個開發的步驟,所以文章比較長emmm


2018.07.25更新

elementUI ^2.2.0提供了一個slot的自定義節點方法,相關程式碼已在github更新,原理一樣。


大致效果如圖:
圖片描述


1.省市API

在網上覆制了個省市的list,有兩個屬性是新增的

  • isEdit :控制編輯狀態
  • maxexpandId :為現下id的最大值
export default{
    maxexpandId: 95,
    treelist: [{  
        id: 1,  
        name: "北京市",  
        ProSort: 1,  
        remark: "直轄市",
        pid: '',
        isEdit: false,
        children: [{
            id: 35,
name: "朝陽區", pid: 1, remark: '', isEdit: false, children: [] }] }{...}] }

2.el-tree Component基本

咱們一步步來,先寫個餓了麼的元件

<template>
    <el-tree ref="expandMenuList" class="expand-tree"
        v-if
="isLoadingTree" :data="setTree" node-key="id" highlight-current :props="defaultProps" :expand-on-click-node="false" :render-content="renderContent" :default-expanded-keys="defaultExpandKeys">
</el-tree> </template> <!-- * highlight-current :為了點選時節點高亮 * expand-on-click-node : 只能箭頭控制樹形的展開收縮 * render-content : 節點渲染方式 * default-expanded-keys :預設展開節點 -->

同時引入API和節點渲染的元件

import TreeRender from '@/components/tree_render'
import api from '@/resource/api'

然後搭建好基礎

data(){
  return{
    maxexpandId: api.maxexpandId,//新增節點開始id
    non_maxexpandId: api.maxexpandId,//新增節點開始id(不更改)
    isLoadingTree: false,//是否載入節點樹
    setTree: api.treelist,//節點樹資料
    defaultProps: {
      children: 'children',
      label: 'name'
    },
    defaultExpandKeys: [],//預設展開節點列表
  }
},

添加個渲染的method

methods: {
    renderContent(h,{node,data,store}){
      let that = this;//指向vue
      return h(TreeRender,{
        props: {
          DATA: data,//節點資料
          NODE: node,//節點內容
          STORE: store,//完整樹形內容
        },
        on: {//繫結方法
          nodeAdd: ((s,d,n) => that.handleAdd(s,d,n)),
          nodeEdit: ((s,d,n) => that.handleEdit(s,d,n)),
          nodeDel: ((s,d,n) => that.handleDelete(s,d,n))
        }
      });
    },
    handleAdd(s,d,n){//增加節點
      console.log(s,d,n)
    },
    handleEdit(s,d,n){//編輯節點
      console.log(s,d,n)
    },
    handleDelete(s,d,n){//刪除節點
      console.log(s,d,n)
    }
}

3.tree_render Component基本

渲染元件:

<template>
    <span class="tree-expand">
        <span class="tree-label">
            <span>{{DATA.name}}</span>
        </span>
        <span class="tree-btn">
            <i class="el-icon-plus" @click.stop="nodeAdd(STORE,DATA,NODE)"></i>
            <i class="el-icon-edit" @click.stop="nodeEdit(STORE,DATA,NODE)"></i>
            <i class="el-icon-delete" @click.stop="nodeDel(STORE,DATA,NODE)"></i>
        </span>
    </span>
</template>

新增好幾個按鈕(element-ui自帶icon:地址)對應的方法:

export default{
    props: ['NODE', 'DATA', 'STORE'],
    methods: {
      nodeAdd(s,d,n){//新增
        this.$emit('nodeAdd',s,d,n)
      },
      nodeEdit(s,d,n){//編輯
        this.$emit('nodeEdit',s,d,n)
      },
      nodeDel(s,d,n){//刪除
        this.$emit('nodeDel',s,d,n)
      }
    }
}

4.改

我們用isEdit來切換inputspan的顯示狀態,首先加個input:

<!-- tree_render component -->
<template>
    <span class="tree-expand">
        <span class="tree-label" v-if="DATA.isEdit">
            <el-input class="edit" size="mini"
            :ref="'treeInput'+DATA.id"
            v-model="DATA.name"></el-input>
        </span>
        <template v-else>
            <span class="tree-label">
                <span>{{DATA.name}}</span>
            </span>
            <span class="tree-btn" v-show="!DATA.isEdit">
                <i class="el-icon-plus" @click.stop="nodeAdd(STORE,DATA,NODE)"></i>
                <i class="el-icon-edit" @click.stop="nodeEdit(STORE,DATA,NODE)"></i>
                <i class="el-icon-delete" @click.stop="nodeDel(STORE,DATA,NODE)"></i>
            </span>
        </template>
    </span>
</template>

編輯的時候按鈕同時消失,那麼什麼時候編輯完成呢?

  • 編輯完按enter鍵=》監聽input的enter輸入
  • 點選其他節點=》input失焦-blur=》編輯時自動聚焦-focus
  • 點選當前節點範圍

當以上三點發生一項,節點對應的data都要isEdit = false;

  1. enter鍵

    <!-- tree_render component -->
    <el-input @keyup.enter.native="nodeEditPass(STORE,DATA,NODE)"></el-input>

    新增方法:

    //tree_render component
    methods: {
        nodeEditPass(s,d,n){
            d.isEdit = false;
        }
    }
  2. focus or blur

    <!-- tree_render component -->
    <el-input @blur="nodeEditPass(STORE,DATA,NODE)"></el-input>

    後來發現第一次編輯時能讓input聚焦,點選第二個input就不起作用了,加了autofocus屬性也同樣如此。所以我們要在點選編輯icon的時候,用原生的input autofocus
    修改方法:

    //tree_render component
    nodeEdit(s,d,n){//編輯
      d.isEdit = true;
      this.$nextTick(() => {
        this.$refs['treeInput'+d.id].$refs.input.focus()
      })
      this.$emit('nodeEdit',s,d,n)
    }
  3. 當前節點點選
    採用el-tree已有的API——node-click

    <!-- el-tree component -->
    <el-tree @node-click="handleNodeClick"></el-tree>

    新增methods:

    //el-tree component
    methods: {
        handleNodeClick(d,n,s){//點選節點
          d.isEdit = false;//放棄編輯狀態
        }
    }

    問題來了,如果在編輯狀態下點選此節點也同樣會影響input,這就無法進入編輯,所以要阻止input事件冒泡

    <!-- tree_render component -->
    <el-input @click.stop.native="nodeEditFocus"></el-input>

    新增methods:

    //tree_render component
    methods: {
        nodeEditFocus(){}
    }
  4. v-show代替v-if

這裡有個新的問題,當用戶經常編輯修改,v-if模板的開銷更高,所以改用v-show。而後者不支援template模板,所以要適當調整一下位置:

<template>
    <span class="tree-expand">
        <span class="tree-label" v-show="DATA.isEdit">
            <el-input class="edit" size="mini" autofocus
            v-model="DATA.name"
            :ref="'treeInput'+DATA.id"
            @click.stop.native="nodeEditFocus"
            @blur="nodeEditPass(STORE,DATA,NODE)"
            @keyup.enter.native="nodeEditPass(STORE,DATA,NODE)"></el-input>
        </span>
        <span v-show="!DATA.isEdit">
            <span>{{DATA.name}}</span>
        </span>
        <span class="tree-btn" v-show="!DATA.isEdit">
            <i class="el-icon-plus" @click.stop="nodeAdd(STORE,DATA,NODE)"></i>
            <i class="el-icon-edit" @click.stop="nodeEdit(STORE,DATA,NODE)"></i>
            <i class="el-icon-delete" @click.stop="nodeDel(STORE,DATA,NODE)"></i>
        </span>
    </span>
</template>

5.增

新增節點 =》新增一條資料

  1. 新增的同時展開父節點
  2. 是否考慮無限新增
//el-tree component
handleAdd(s,d,n){//增加節點
  console.log(s,d,n)
  if(n.level >=6){
    this.$message.error("最多隻支援五級!")
    return false;
  }
  //新增資料
  d.children.push({
    id: ++this.maxexpandId,
    name: '新增節點',
    pid: d.id,
    isEdit: false,
    children: []
  });
  //展開節點
  if(!n.expanded){
    n.expanded = true;
  }
}

新增節點字型加粗 =》給節點新增一個class =》 如何判斷是否新增?
我們有一個引數maxexpandId
tree_render新增一個prop

//el-tree component
renderContent(h,{node,data,store}){//載入節點
  let that = this;
  return h(TreeRender,{
    props: {
      ...
      maxexpandId: that.non_maxexpandId
    },
    on: {...}
  });
}

根據id判斷:

//tree_render component
props: ['NODE', 'DATA', 'STORE', 'maxexpandId']
<!-- tree_render component -->
<span v-show="!DATA.isEdit" 
:class="[DATA.id > maxexpandId ? 'tree-new tree-label' : 'tree-label']"
:ref="'treeLabel'+DATA.id">
    <span>{{DATA.name}}</span>
</span>
.tree-expand .tree-label.tree-new{
    font-weight:600;
}

6.刪

跟新增同義:刪除節點 =》刪除一條資料

  1. 新增節點直接刪除
  2. 已有節點需提示再刪除
  3. 已有子級節點不能刪除
handleDelete(s,d,n){//刪除節點
  console.log(s,d,n)
  let that = this;
  //有子級不刪除
  if(d.children && d.children.length !== 0){
    this.$message.error("此節點有子級,不可刪除!")
    return false;
  }else{
    //刪除操作
    let delNode = () => {
      let list = n.parent.data.children || n.parent.data,
      //節點同級資料,頂級節點時無children
        _index = 99999;//要刪除的index
      list.map((c,i) => {
        if(d.id == c.id){
          _index = i;
        }
      })
      let k = list.splice(_index,1);
      //console.log(_index,k)
      this.$message.success("刪除成功!")
    }
    let isDel = () => {
      that.$confirm("是否刪除此節點?","提示",{
        confirmButtonText: "確認",
        cancelButtonText: "取消",
        type: "warning"
      }).then(() => {
        delNode()//此處可通過ajax做刪除操作
      }).catch(() => {
        return false;
      })
    }
    //新增節點直接刪除,否則要通過請求資料刪除
    d.id > this.non_maxexpandId ? delNode() : isDel()
  }
}

7.拓展

還有一些特別的需求,例如:

  1. 點選高亮的時候顯示icon

    .expand-tree .is-current>.el-tree-node__content .tree-btn,
    .expand-tree .el-tree-node__content:hover .tree-btn{
      display: inline-block;
    }
  2. 新增頂級節點
    新增按鈕:

    <!-- el-tree component -->
    <el-button @click="handleAddTop">新增頂級節點</el-button>

    新增methods:

    //el-tree component
    methods: {
      handleAddTop(){
        this.setTree.push({
          id: ++this.maxexpandId,
          name: '新增節點',
          pid: '',
          isEdit: false,
          children: []
        })
      }
    }
  3. 預設展開樹形第一級

    //el-tree component
    mounted(){
      this.initExpand()
    },
    methods: {
      initExpand(){
        //isLoadingTree用意也是在此
        this.setTree.map((a) => {
          this.defaultExpandKeys.push(a.id)
        });
        this.isLoadingTree = true;
      },
    }

8.github

還有些具體的樣式都放在github

如有錯漏,歡迎指正╰( ◕ ▽ ◕ )╯

            </div>