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
來切換input
和span
的顯示狀態,首先加個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;
-
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; } }
-
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) }
-
當前節點點選
採用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(){} }
-
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.增
新增節點 =》新增一條資料
- 新增的同時展開父節點
- 是否考慮無限新增
//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.刪
跟新增同義:刪除節點 =》刪除一條資料
- 新增節點直接刪除
- 已有節點需提示再刪除
- 已有子級節點不能刪除
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.拓展
還有一些特別的需求,例如:
-
點選高亮的時候顯示icon
.expand-tree .is-current>.el-tree-node__content .tree-btn, .expand-tree .el-tree-node__content:hover .tree-btn{ display: inline-block; }
-
新增頂級節點
新增按鈕:<!-- 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: [] }) } }
-
預設展開樹形第一級
//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>