在原文档中有个案例是有新增和删除功能,但是后来发现其修改的数据并不能直接影响到树形数据,所以采用了 render-content
的 API 重新写了个组件。
写个开发的步骤,所以文章比较长 emmm
大致效果如图:
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; }, }
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于