Foreword
When multiple categories are encountered on the management side, they are required to be displayed hierarchically, and each category has additional operations. For example: add category, edit category, delete, drag to category, etc.
The following will record such a demand internship process.
Understand needs
- Classified display displayed by level
- Categories display operable buttons based on specific parameters. Category operations include add, delete, and change.
- Categories also support drag and drop, but not all categories support drag and drop.
- Click Category to perform other operations. For example: refresh data (not implemented)
- After adding a category, refresh the category data. The currently selected category is the added category.
- After deleting a category, return to the previous category
- Right-click the category and click the operation button to pop up the operation pop-up window
- Click the arrows in front of categories to expand and collapse categories
Renderings
- Classification display
- Category operation pop-up window
Component library
Using the Tree tree control and Dropdown menu in ElementUI
- Tree tree control: Element – The world’s most popular Vue UI framework
- Dropdown dropdown menu dropdown: Element – The world’s most popular Vue UI framework
Start coding
Build tree component
- html part:
<el-tree :data="classifyData" node-key="id" draggable ref="tree" :accordion="false" auto-expand-parent :default-expanded-keys="[checkedId]" :props="defaultProps" :allow-drop="allowDrop" :allow-drag="allowDrag" @node-drag-start="handleDragStart" @node-drop="handleDrop" @node-click="nodeClick" @node-contextmenu="rightClick" :show-checkbox="false" :check-strictly="true" > <div class="custom-tree-node" slot-scope="{ node, data }"> <span>{<!-- -->{ data.name }}</span> <span> <el-dropdown type="primary" trigger="click" :ref="'messageDrop' + data.id" @visible-change="controlCheckedKeys"> <span class="el-dropdown-link" @click.stop="setSeletKey(data.id)"> <img src="~@/../more-active.png" v-if="checkedKeys == data.id" class="myicon-opt" /> <img src="~@/../more.png" v-else class="myicon-opt" /> </span> <el-dropdown-menu slot="dropdown"> <el-dropdown-item v-if="data.is_add_classify"> <div @click="openClassify(data.id,'Add subcategory')"> <img src="~@/../add.png" class="myicon-opt"/> Add subcategory </div> </el-dropdown-item> <el-dropdown-item v-if="data.is_edit_sort"> <div @click="editClassify(data)"> <img src="~@/../edit.png" class="myicon-opt" /> Revise </div> </el-dropdown-item> <el-dropdown-item v-if="data.is_edit_sort"> <div @click="delBefore(data.id,data.parent_id)"> <img src="~@/../del.png" class="myicon-opt" /> delete </div> </el-dropdown-item> </el-dropdown-menu> </el-dropdown> </span> </div> </el-tree>
- css
<style lang="stylus" scoped> .active{ background: #F2F6F9; color: #409EFF; } .classify{ padding: 0 16px; height: 40px; font-family: PingFangSC-Medium; font-weight: 500; font-size: 15px; line-height:40px; } .el-tree ::v-deep { .el-tree-node__content{ @extend .classify; &:hover{ @extend .active; } .el-tree-node__expand-icon.is-leaf{ // display:none margin-left:-12px } } .is-checked > .el-tree-node__content{ @extend .active; } } .custom-tree-node{ display: flex; justify-content: space-between; width: 100%; } .myicon-opt{ vertical-align: middle; width: 16px; height: 16px; } </style>
- js
<script> export default { props:{ activeId:{ type:[String,Number], default:'' }, classifyData:{ type:Array, default:[] } }, watch:{ activeId: { handler(v,o){ // When v value is 0, 0 == '' value is true if (typeof v == 'number') { this.checkedId = v this.$nextTick(()=>{ this.$refs.tree.setCheckedKeys([v]) }) } }, immediate: true, deep:true }, }, data() { return { checkedId:'', checkedKeys:'', defaultProps: { children: 'child', label: 'name' }, classifyCofig:{ flag:false, Id: '', title:'', value:'' }, } }, methods: { // Click on the category name nodeClick(data,node){ this.checkedId = data.id this.$refs.tree.setCheckedKeys([data.id]) node.expanded = true this.$emit('selectId',data.id) // console.log('node',data.id,node.parent) let addId = [data.id] if(node.parent.parent != null) this.selectNode(addId,node.parent) // console.log('addId',addId) this.$emit('selectaddId', addId) }, // Get the multi-level parent class id and add it to the array subscript 0 selectNode(id,node){ id.unshift(node.data.id) if(node.parent.parent != null){ this.selectNode(id,node.parent) } }, // Right click on category rightClick(event,data, Node, element){ setTimeout(()=>{ this.checkedKeys = data.id this.$refs['messageDrop' + data.id].show() }) }, // Click the action button setSeletKey(k){ setTimeout(()=>{ this.checkedKeys = k }) }, // Asynchronous monitoring of drop-down menu, open (true) or hidden (flase) controlCheckedKeys(flag){ if(!flag){ this.checkedKeys = '' } }, // Event triggered when the node starts dragging handleDragStart(node) { if(!node.data.is_edit_sort){ return false } }, // Event triggered when dragging is successfully completed handleDrop(draggingNode, dropNode, dropType) { if(dropType == 'none') return //Preparation sorting parameters can be changed by yourself let params = { pk1: draggingNode.data.id, pk2: dropNode.data.id, direction:dropType == 'before' ? -1 : 1 } this.orderClassify(params) }, /** * Determine whether the target node can be placed when dragging. * @param {*} draggingNode * @param {*} dropNode * @param {*} The type parameter has three situations: 'prev', 'inner' and 'next', which respectively means placing before the target node, inserting into the target node and placing after the target node. */ allowDrop(draggingNode, dropNode, type) { if (draggingNode.level === dropNode.level) { if (draggingNode.data.parent_id === dropNode.data.parent_id & amp; & amp; dropNode.data.is_edit_sort) { // Drag up || Drag down return type === "prev" || type === "next" } } else { // Process at different levels return false } }, //Determine whether the node can be dragged allowDrag(draggingNode) { if(!draggingNode.data.is_edit_sort){ return false } return true }, async orderClassify(params){ //Send sorting request }, setClassCofig(flag,id,title,value){ this.classifyCofig['flag'] = flag this.classifyCofig['Id'] = id this.classifyCofig['title'] = title this.classifyCofig['value'] = value }, openClassify(pid,txt){ this.setClassCofig(true,pid, txt ? txt : 'Add new category','') }, editClassify(row){ this.setClassCofig(true,row.id, 'Modify classification', row.name) }, closeAdd(){ this.setClassCofig(false,'', '', '') }, //Add/modify categories async sureClassify(params){ let {value,Id} = this.classifyCofig // Determine whether the current addition or modification is based on the value of value // Refresh the category, cid is the id of the new category let refresh = { } if(value){ refresh.flag = false }else{ refresh.flag = true } // Prepare parameters and send request // Executed after the request is successful this.setClassCofig(false,'', '', '') refresh.cid = value? this.checkedId : res.data.data.id this.$emit('refreshClass',refresh) }, //Determine whether the category can be deleted async delBefore(id,pid){ //1. Customize to determine whether it can be deleted, //2. You can delete and perform the delete operation. this.sureDelete(id,pid) }, //Delete the category and return to the previous level after deletion async sureDelete(id,pid){ //1. Interface usage data to be deleted //2. Initiate a request and execute the following code after the request is successful. this.setClassCofig(false,'', '', '') let refresh = { flag: true, cid: pid } this.$emit('refreshClass',refresh) }, } }; </script>
Use tree component
- html
<PersonalTree :activeId="currentClassfiyId" :classifyData="classifyData" @selectId="changeSelectId" @selectaddId="setAddId" @refreshClass="refreshClass"/>
- js
<script> //Introduce the tree component here and name it customTree export default{ components:{customTree}, data(){ return{ currentClassfiyId:'', addClassifyId:[], classifyData:[], } }, mounted(){ this.getClassList(true) }, methods:{ async getClassList(flagScene,cid){ // console.log(flagScene,cid) //Send a request to get all categories this.classifyData = res.data.data.classify this.currentClassfiyId = cid || this.classifyData?.[0].id if(flagScene){ // You can get the content } } }, refreshClass({flag,cid}){ //To refresh the category list this.getClassList(flag,cid) }, setAddId(val){ this.addClassifyId = val }, changeSelectId(id){ this.currentClassfiyId = id // You can get the content }, } } </script>
classifyData data:
[{ "id": 1033, "name": "First level classification", "parent_id": 0, "level": 1, "child": [ { "id": 1036, "name": "aaaaaaaaa", "parent_id": 1033, "level": 2, "child": [], "is_edit_sort": true, "is_add_classify": true, "is_add_scene": true }, { "id": 1035, "name": "aaaaa", "parent_id": 1033, "level": 2, "child": [ { "id": 1037, "name": "a-1", "parent_id": 1035, "level": 3, "child": [ { "id": 1040, "name": "a-1-3", "parent_id": 1037, "level": 4, "child": [], "is_edit_sort": true, "is_add_classify": false, "is_add_scene": true }, { "id": 1038, "name": "a-1-1", "parent_id": 1037, "level": 4, "child": [], "is_edit_sort": true, "is_add_classify": false, "is_add_scene": true } ], "is_edit_sort": true, "is_add_classify": true, "is_add_scene": true } ], "is_edit_sort": true, "is_add_classify": true, "is_add_scene": true } ], "is_edit_sort": true, "is_add_classify": true, "is_add_scene": true },{ "id": 1032, "name": "Test category b", "parent_id": 0, "level": 1, "child": [], "is_edit_sort": true, "is_add_classify": true, "is_add_scene": true },{ "id": 1015, "name": "No operation area", "parent_id": 0, "level": 1, "child": [], "is_edit_sort": false, "is_add_classify": false, "is_add_scene": false }]
If this helps you, please bookmark + follow! ! !