The general effect is shown in the figure:
1. Provincial and municipal API
The list of provinces and cities is copied on the Internet, and two properties are added
- isEdit: controls editing status
- maxexpandId: the maximum current id
export default{ maxexpandId: 95, treelist: [{ id: 1, name: "Beijing", ProSort: 1, remark: "municipality directly under the Central Government", pid: '', isEdit: false, children: [{ id: 35, name: "Chaoyang District", pid: 1, remark: '', isEdit: false, children: [] }] }{...}] }
2. El tree component basic
Let's write a hungry component step by step
<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 : To highlight a node when clicked * expand-on-click-node : Only arrows can control the expansion and contraction of the tree * render-content : Node rendering mode * default-expanded-keys : Default deployment node -->
API and node rendering components are introduced at the same time
import TreeRender from '@/components/tree_render' import api from '@/resource/api'
Then build the foundation
data(){ return{ maxexpandId: api.maxexpandId,//New node start id non_maxexpandId: api.maxexpandId,//New node start ID (not changed) isLoadingTree: false,//Load node tree setTree: api.treelist,//Node tree data defaultProps: { children: 'children', label: 'name' }, defaultExpandKeys: [],//Default expanded node list } },
Add a render method
methods: { renderContent(h,{node,data,store}){ let that = this;//Point to vue return h(TreeRender,{ props: { DATA: data,//Node data NODE: node,//Node content STORE: store,//Complete tree content }, on: {//Binding method 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){//Add node console.log(s,d,n) }, handleEdit(s,d,n){//Edit node console.log(s,d,n) }, handleDelete(s,d,n){//Delete node console.log(s,d,n) } }
3.tree_render Component basic
Render components:
<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>
Add methods corresponding to several buttons (icon: address of element UI):
export default{ props: ['NODE', 'DATA', 'STORE'], methods: { nodeAdd(s,d,n){//newly added this.$emit('nodeAdd',s,d,n) }, nodeEdit(s,d,n){//edit this.$emit('nodeEdit',s,d,n) }, nodeDel(s,d,n){//delete this.$emit('nodeDel',s,d,n) } } }
4. Change
We use isEdit to switch the display status of input and span. First, add an 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>
When editing, the buttons disappear at the same time, so when will the editing be completed?
After editing, press the Enter key to listen for the enter input of input
Click "auto focus =" when other nodes are out of focus
Click the current node range
When one of the above three points occurs, the data corresponding to the node must be isEdit = false;
- enter key
<!-- tree_render component --> <el-input @keyup.enter.native="nodeEditPass(STORE,DATA,NODE)"></el-input>
Add method:
//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>
Later, it was found that the input can be focused during the first editing, and clicking the second input will not work. The same is true with the autofocus attribute. So we need to use the native input autofocus when clicking Edit icon.
Modification method:
//tree_render component nodeEdit(s,d,n){//edit d.isEdit = true; this.$nextTick(() => { this.$refs['treeInput'+d.id].$refs.input.focus() }) this.$emit('nodeEdit',s,d,n) }
- Current node Click
Adopt the existing API of El tree node click
<!-- el-tree component --> <el-tree @node-click="handleNodeClick"></el-tree>
Add methods:
//el-tree component methods: { handleNodeClick(d,n,s){//Click node d.isEdit = false;//Discard editing status } }
Here's the problem. If you click this node in the editing state, it will also affect the input, which cannot enter the editing. Therefore, to prevent the input event from bubbling:
<!-- tree_render component --> <el-input @click.stop.native="nodeEditFocus"></el-input>
Add methods:
//tree_render component methods: { nodeEditFocus(){} }
- v-show instead of v-if
There is a new problem here. When users often edit and modify, the cost of v-if template is higher, so they use v-show instead. The latter does not support template template, so you should adjust the position appropriately:
<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. Increase
New node = > add a piece of data
- Expand parent node while adding
- Consider unlimited addition
//el-tree component handleAdd(s,d,n){//Add node console.log(s,d,n) if(n.level >=6){ this.$message.error("Only five levels are supported at most!") return false; } //Add data d.children.push({ id: ++this.maxexpandId, name: 'New node', pid: d.id, isEdit: false, children: [] }); //Expand node if(!n.expanded){ n.expanded = true; } }
Add node font bold = "add a class =" to the node how to judge whether to add?
We have a parameter maxexpandId
Give it to tree_render adds a prop:
//el-tree component renderContent(h,{node,data,store}){//Load node let that = this; return h(TreeRender,{ props: { ... maxexpandId: that.non_maxexpandId }, on: {...} }); }
Judging by 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. Delete
Synonymous with adding: delete node = > delete a piece of data
Add a node and delete it directly
Existing nodes need to be prompted to delete
Existing child nodes cannot be deleted
handleDelete(s,d,n){//Delete node console.log(s,d,n) let that = this; //Children not deleted if(d.children && d.children.length !== 0){ this.$message.error("This node has children and cannot be deleted!") return false; }else{ //Delete operation let delNode = () => { let list = n.parent.data.children || n.parent.data, //The node has the same level data, and there are no children in the top-level node _index = 99999;//index to delete list.map((c,i) => { if(d.id == c.id){ _index = i; } }) let k = list.splice(_index,1); //console.log(_index,k) this.$message.success("Delete succeeded!") } let isDel = () => { that.$confirm("Delete this node?","Tips",{ confirmButtonText: "confirm", cancelButtonText: "cancel", type: "warning" }).then(() => { delNode()//You can delete it here through ajax }).catch(() => { return false; }) } //The newly added node can be deleted directly, otherwise it must be deleted by requesting data d.id > this.non_maxexpandId ? delNode() : isDel() } }
7. Expansion
There are also some special needs, such as:
- Click to highlight the icon
.expand-tree .is-current>.el-tree-node__content .tree-btn, .expand-tree .el-tree-node__content:hover .tree-btn{ display: inline-block; }
- Add top-level node
Add button:
<!-- el-tree component --> <el-button @click="handleAddTop">Add top-level node</el-button>
//el-tree component methods: { handleAddTop(){ this.setTree.push({ id: ++this.maxexpandId, name: 'New node', pid: '', isEdit: false, children: [] }) } }
- By default, expand the first level of the tree
//el-tree component mounted(){ this.initExpand() }, methods: { initExpand(){ //This is also the purpose of isLoadingTree this.setTree.map((a) => { this.defaultExpandKeys.push(a.id) }); this.isLoadingTree = true; }, }