当前位置:   article > 正文

ElementUI-tree拖拽功能与节点自定义_class="custom-tree-node

class="custom-tree-node

前言

在管理端会遇到多分类时,要求有层次展示出来,并且每个分类有额外的操作。例如:添加分类、编辑分类、删除、拖到分类等。

下面将会记录这样的一个需求实习过程。

了解需求

  1. 分类展示按层级展示
  2. 分类根据特定的参数展示可以操作的按钮,分类的操作有增、删、改
  3. 分类还支持拖拽功能,并不是所有的分类都支持拖拽
  4. 点分类去执行别的操作。例如:刷新数据(不实现)
  5. 增加分类之后刷新分类数据,当前选择的分类为增加的分类
  6. 删除分类后回到上一级分类
  7. 右击分类和点击操作按钮均可以弹出操作弹窗
  8. 点击分类前的箭头可展开和折叠分类

效果图

  • 分类展示

  • 分类操作的弹窗

组件库

采用ElementUI 中的 Tree树形控件、Dropdown下拉菜单

开始编码

搭建tree 组件

  •   html 部分:
  1. <el-tree :data="classifyData" node-key="id" draggable ref="tree" :accordion="false"
  2. auto-expand-parent :default-expanded-keys="[checkedId]" :props="defaultProps"
  3. :allow-drop="allowDrop" :allow-drag="allowDrag"
  4. @node-drag-start="handleDragStart" @node-drop="handleDrop"
  5. @node-click="nodeClick" @node-contextmenu="rightClick"
  6. :show-checkbox="false" :check-strictly="true" >
  7. <div class="custom-tree-node" slot-scope="{ node, data }">
  8. <span>{{ data.name }}</span>
  9. <span>
  10. <el-dropdown type="primary" trigger="click" :ref="'messageDrop'+data.id" @visible-change="controlCheckedKeys">
  11. <span class="el-dropdown-link" @click.stop="setSeletKey(data.id)">
  12. <img src="~@/../more-active.png" v-if="checkedKeys == data.id" class="myicon-opt" />
  13. <img src="~@/../more.png" v-else class="myicon-opt" />
  14. </span>
  15. <el-dropdown-menu slot="dropdown">
  16. <el-dropdown-item v-if="data.is_add_classify">
  17. <div @click="openClassify(data.id,'新增子分类')">
  18. <img src="~@/../add.png" class="myicon-opt"/>
  19. 新增子分类
  20. </div>
  21. </el-dropdown-item>
  22. <el-dropdown-item v-if="data.is_edit_sort">
  23. <div @click="editClassify(data)">
  24. <img src="~@/../edit.png" class="myicon-opt" />
  25. 修改
  26. </div>
  27. </el-dropdown-item>
  28. <el-dropdown-item v-if="data.is_edit_sort">
  29. <div @click="delBefore(data.id,data.parent_id)">
  30. <img src="~@/../del.png" class="myicon-opt" />
  31. 删除
  32. </div>
  33. </el-dropdown-item>
  34. </el-dropdown-menu>
  35. </el-dropdown>
  36. </span>
  37. </div>
  38. </el-tree>
  • css
  1. <style lang="stylus" scoped>
  2. .active{
  3. background: #F2F6F9;
  4. color: #409EFF;
  5. }
  6. .classify{
  7. padding : 0 16px;
  8. height: 40px;
  9. font-family: PingFangSC-Medium;
  10. font-weight: 500;
  11. font-size: 15px;
  12. line-height:40px;
  13. }
  14. .el-tree ::v-deep {
  15. .el-tree-node__content{
  16. @extend .classify;
  17. &:hover{
  18. @extend .active;
  19. }
  20. .el-tree-node__expand-icon.is-leaf{
  21. // display:none
  22. margin-left:-12px
  23. }
  24. }
  25. .is-checked > .el-tree-node__content{
  26. @extend .active;
  27. }
  28. }
  29. .custom-tree-node{
  30. display: flex;
  31. justify-content: space-between;
  32. width: 100%;
  33. }
  34. .myicon-opt{
  35. vertical-align: middle;
  36. width: 16px;
  37. height: 16px;
  38. }
  39. </style>
  • js
  1. <script>
  2. export default {
  3. props:{
  4. activeId:{
  5. type:[String,Number],
  6. default:''
  7. },
  8. classifyData:{
  9. type:Array,
  10. default:[]
  11. }
  12. },
  13. watch:{
  14. activeId: {
  15. handler(v,o){
  16. // v 值为0时, 0 == '' 值为true
  17. if (typeof v == 'number') {
  18. this.checkedId = v
  19. this.$nextTick(()=>{
  20. this.$refs.tree.setCheckedKeys([v])
  21. })
  22. }
  23. },
  24. immediate:true,
  25. deep:true
  26. },
  27. },
  28. data() {
  29. return {
  30. checkedId:'',
  31. checkedKeys:'',
  32. defaultProps: {
  33. children: 'child',
  34. label: 'name'
  35. },
  36. classifyCofig:{
  37. flag:false,
  38. Id: '',
  39. title:'',
  40. value:''
  41. },
  42. }
  43. },
  44. methods: {
  45. // 点击分类名称
  46. nodeClick(data,node){
  47. this.checkedId = data.id
  48. this.$refs.tree.setCheckedKeys([data.id])
  49. node.expanded = true
  50. this.$emit('selectId',data.id)
  51. // console.log('node',data.id,node.parent)
  52. let addId = [ data.id]
  53. if(node.parent.parent != null) this.selectNode(addId,node.parent)
  54. // console.log('addId',addId)
  55. this.$emit('selectaddId', addId)
  56. },
  57. // 获取多层级的父类id加入到数组下标为0的位置
  58. selectNode(id,node){
  59. id.unshift(node.data.id)
  60. if(node.parent.parent != null){
  61. this.selectNode(id,node.parent)
  62. }
  63. },
  64. // 右击分类
  65. rightClick(event,data, Node, element){
  66. setTimeout(()=>{
  67. this.checkedKeys = data.id
  68. this.$refs['messageDrop'+data.id].show()
  69. })
  70. },
  71. // 点击操作按钮
  72. setSeletKey(k){
  73. setTimeout(()=>{
  74. this.checkedKeys = k
  75. })
  76. },
  77. // 下拉菜单的异步监听,打开(true)还是隐藏(flase)
  78. controlCheckedKeys(flag){
  79. if(!flag){
  80. this.checkedKeys = ''
  81. }
  82. },
  83. // 节点开始拖拽时触发的事件
  84. handleDragStart(node) {
  85. if(!node.data.is_edit_sort){
  86. return false
  87. }
  88. },
  89. // 拖拽成功完成时触发的事件
  90. handleDrop(draggingNode, dropNode, dropType) {
  91. if(dropType == 'none') return
  92. // 准备排序参数可自行更改
  93. let params = {
  94. pk1: draggingNode.data.id,
  95. pk2: dropNode.data.id,
  96. direction:dropType == 'before' ? -1 : 1
  97. }
  98. this.orderClassify(params)
  99. },
  100. /**
  101. * 拖拽时判定目标节点能否被放置。
  102. * @param {*} draggingNode
  103. * @param {*} dropNode
  104. * @param {*} type 参数有三种情况:'prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
  105. */
  106. allowDrop(draggingNode, dropNode, type) {
  107. if (draggingNode.level === dropNode.level) {
  108. if (draggingNode.data.parent_id === dropNode.data.parent_id && dropNode.data.is_edit_sort) {
  109. // 向上拖拽 || 向下拖拽
  110. return type === "prev" || type === "next"
  111. }
  112. } else {
  113. // 不同级进行处理
  114. return false
  115. }
  116. },
  117. //判断节点能否被拖拽
  118. allowDrag(draggingNode) {
  119. if(!draggingNode.data.is_edit_sort){
  120. return false
  121. }
  122. return true
  123. },
  124. async orderClassify(params){
  125. // 发送排序请求
  126. },
  127. setClassCofig(flag,id,title,value){
  128. this.classifyCofig['flag'] = flag
  129. this.classifyCofig['Id'] = id
  130. this.classifyCofig['title'] = title
  131. this.classifyCofig['value'] = value
  132. },
  133. openClassify(pid,txt){
  134. this.setClassCofig(true,pid, txt ? txt : '新增分类','')
  135. },
  136. editClassify(row){
  137. this.setClassCofig(true,row.id, '修改分类', row.name)
  138. },
  139. closeAdd(){
  140. this.setClassCofig(false,'', '', '')
  141. },
  142. // 新增/修改分类
  143. async sureClassify(params){
  144. let {value,Id} = this.classifyCofig
  145. // 通过value的值判断当前是新增还是修改
  146. // 刷新分类,cid 新分类的id
  147. let refresh = { }
  148. if(value){
  149. refresh.flag = false
  150. }else{
  151. refresh.flag = true
  152. }
  153. // 准备参数,发送请求
  154. // 请求成功后执行
  155. this.setClassCofig(false,'', '', '')
  156. refresh.cid = value? this.checkedId : res.data.data.id
  157. this.$emit('refreshClass',refresh)
  158. },
  159. //判断分类是否可以删除
  160. async delBefore(id,pid){
  161. //1.自定义判断是否可以删除,
  162. //2.可以删去执行删除操作,
  163. this.sureDelete(id,pid)
  164. },
  165. //删除分类,删除后回到上一级
  166. async sureDelete(id,pid){
  167. //1.准备删除的接口使用数据
  168. //2.发起请求,请求成功后执行下面代码
  169. this.setClassCofig(false,'', '', '')
  170. let refresh = {
  171. flag: true,
  172. cid: pid
  173. }
  174. this.$emit('refreshClass',refresh)
  175. },
  176. }
  177. };
  178. </script>

使用tree组件

  • html
  1. <PersonalTree :activeId="currentClassfiyId" :classifyData="classifyData"
  2. @selectId="changeSelectId" @selectaddId="setAddId" @refreshClass="refreshClass"/>
  • js
  1. <script>
  2. // 在此处引入tree组件命名为customTree
  3. export default{
  4. components:{customTree},
  5. data(){
  6. return{
  7. currentClassfiyId:'',
  8. addClassifyId:[],
  9. classifyData:[],
  10. }
  11. },
  12. mounted(){
  13. this.getClassList(true)
  14. },
  15. methods:{
  16. async getClassList(flagScene,cid){
  17. // console.log(flagScene,cid)
  18. // 发送请求,获取全部分类
  19. this.classifyData = res.data.data.classify
  20. this.currentClassfiyId = cid || this.classifyData?.[0].id
  21. if(flagScene){
  22. // 可以去获取内容
  23. }
  24. }
  25. },
  26. refreshClass({flag,cid}){
  27. // 去刷新分类列表
  28. this.getClassList(flag,cid)
  29. },
  30. setAddId(val){
  31. this.addClassifyId = val
  32. },
  33. changeSelectId(id){
  34. this.currentClassfiyId = id
  35. // 可以去获取内容
  36. },
  37. }
  38. }
  39. </script>

classifyData的数据:

  1. [{
  2. "id": 1033,
  3. "name": "一级分类",
  4. "parent_id": 0,
  5. "level": 1,
  6. "child": [
  7. {
  8. "id": 1036,
  9. "name": "aaaaaaaaa",
  10. "parent_id": 1033,
  11. "level": 2,
  12. "child": [],
  13. "is_edit_sort": true,
  14. "is_add_classify": true,
  15. "is_add_scene": true
  16. },
  17. {
  18. "id": 1035,
  19. "name": "aaaaa",
  20. "parent_id": 1033,
  21. "level": 2,
  22. "child": [
  23. {
  24. "id": 1037,
  25. "name": "a-1",
  26. "parent_id": 1035,
  27. "level": 3,
  28. "child": [
  29. {
  30. "id": 1040,
  31. "name": "a-1-3",
  32. "parent_id": 1037,
  33. "level": 4,
  34. "child": [],
  35. "is_edit_sort": true,
  36. "is_add_classify": false,
  37. "is_add_scene": true
  38. },
  39. {
  40. "id": 1038,
  41. "name": "a-1-1",
  42. "parent_id": 1037,
  43. "level": 4,
  44. "child": [],
  45. "is_edit_sort": true,
  46. "is_add_classify": false,
  47. "is_add_scene": true
  48. }
  49. ],
  50. "is_edit_sort": true,
  51. "is_add_classify": true,
  52. "is_add_scene": true
  53. }
  54. ],
  55. "is_edit_sort": true,
  56. "is_add_classify": true,
  57. "is_add_scene": true
  58. }
  59. ],
  60. "is_edit_sort": true,
  61. "is_add_classify": true,
  62. "is_add_scene": true
  63. },{
  64. "id": 1032,
  65. "name": "测试分类b",
  66. "parent_id": 0,
  67. "level": 1,
  68. "child": [],
  69. "is_edit_sort": true,
  70. "is_add_classify": true,
  71. "is_add_scene": true
  72. },{
  73. "id": 1015,
  74. "name": "无操作区",
  75. "parent_id": 0,
  76. "level": 1,
  77. "child": [],
  78. "is_edit_sort": false,
  79. "is_add_classify": false,
  80. "is_add_scene": false
  81. }]

如有帮到您,请收藏+关注哦!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/490222
推荐阅读
相关标签
  

闽ICP备14008679号