自定义节点内容: 要实现这些效果直接参照它自带的代码就行了
slot-scope:={node,data}, 这里使用了结构表达式,传入了node 对象和data,以方便后面的节点使用。
设置好以后运行页面,发现当点击Appen/Delete 的时候也会进行菜单的伸缩,我们的目的是点击箭头才进行菜单的伸缩,点击其他数据是不会进行菜单的伸缩,所以还要再加一个参数。
只有是一级菜单或者二级菜单才能有Append,因为我们总共只有三级菜单;只有当前菜单没有子节点的时候才能删除节点。 然后还要为菜单加上单选框,添加show-checkbox 属性,Element-UI 模块单选框自带全选和全不选功能。再为每一个菜单天上nodekey,让程序更好的识别每一个菜单,有更好的性能,而每一个节点都有一个"catId" 属性,这个就可以拿来做nodekey. 接下来就要编写“点击按钮,发送相应请求” 显示用后端直接请求,那么就要用到Postman 工具,这个p50 视频直接下载。 然后发送请求 但是注意:我们删除ID时,要看看有没有被其他地方进行关联的。有的话还不能删这个ID 所以我们要改写批量删除的方法。
TODO:将要做但是还没做得事。
但是老师用的是逻辑删除(Mybatis-plus 自带的功能),即用数据表的一个字段来表示是否该数据是否被删除。 show_status 就能表示该状态。1:不删除。 0:删除。 1.在全局配置文件中配置逻辑删除(可省略): 注意:全局配置文件中配置的是1:删除。0:不删除 2.给Bean的属性加上逻辑删除注解@TableLogic 这个给Bean 的属性单独配置规则: 1: 不删除,0:删除
配置到这里,就搞定了后端与数据库的交互了。现在写前端发送给后端删除的请求。 因为在CategoryController 中是需要接受POST 请求。所以前端发送的也要是POST 请求。 增加效果:在点击删除时要跳出弹框“是否删除”,然后删除成功后要有提示框:删除成功。 是否删除弹框: 点击“确定”:执行then() 中的方法。点击"取消":执行catch() 中的方法
删除成功后的提示框:
删除完成以后,菜单不要收缩,要继续伸展。(即菜单的父节点在第二次查询菜单时再次展开) 此时需要用到属性:default-expanded-keys,并且为该属性绑定数据 这个数据是需要动态生成的
新增:点击Append ,弹出对话框,输入菜单名字,然后点击确定,将该菜单加入到列表中。 先实现功能:点击Append ,弹出对话框 el-dialog: 对话框的标签,只要他的:visible.sync= true,那么这个对话框就会出现。而我需要的是在点击Append 之后再出现。所以在Append 的点击函数中进行该属性的设置。
此时就能实现弹出对话框。然后在对话框中设置信息录入框的设置。 输入框的就是标签,为该标签绑定了一个category 对象。 name 属性绑定的就是输入框中的默认值。 然后现在要实现的就是,点击“确定”按钮,将输入框中的信息送到数据库。 但是送到数据库中的对象数据需要附上这么多的属性字段。 name 可以由输入框得到。 parent_id 可以通过点击Append 按钮时,获取当前节点的信息,里面的catId,就是要添加菜单的parentId, cat_level 可以通过点击Append 按钮时,获取当前节点的信息,将信息中的cat_level+1,即可得到要添加菜单的cat_level
因为直接取出来的数据不知道是字符串还是整型,所以在+1 之前先 cat_level *1 ,这样就能确保现在的操作数是整数。
因为CategoryController 中使用@ResponseBody,所以要将请求的数据写在请求体中,所以要用post 请求。并且里面是以JSON 的格式进行传输。
在请求发送成功以后要提示出信息框,里面显示“菜单保存成功”,这里可以借用删除菜单的信息提示框。 然后在信息提示框提示信息以后,当前的输入框要消失,所以把参数置为false。
修改功能: 因为修改功能和添加功能需要用到同一个对话框与按钮,所以给data 数据添加一个标记属性:dialogType,当点击Append 按钮时,此时对象标记dialogType=“append”;当点击Edit 时,此时data 的 dialogType = “Edit” 当点击Edit 按钮时要触发edit() 为什么edit() 中会有那么多的后端得到的data 数据的赋值? 我的理解是:一开始点击Edit() 初始化category 对象时,所有数据都是默认值,当在修改对话框中修改数据时,此时修改的数据只能有三个: name, productUnit, sort。当数据发送到后端时,new 出一个CategoryEntity 对象时,没有修改的数据也是默认值,那么当这份后端数据传输到数据库时,就会把数据库中原本不想修改的数据也会被后端的默认数据所修改,这是不行的,所以当点击edit 按钮时,从后端data 获取categoryEntity 的所有数据,并赋值给前端的category 对象。
当点击修改框的确定按钮时,触发editCategory()。这里做了一手准备:传过的数据不是category 对象,而是把需要修改的数据从category 结构出来然后组成一个新对象传过去。
节点拖拽: 注意点: 最终拖拽节点的目的地level <= 3; draggable: 开启拖拽功能
allowDrop(draggingNode, dropNode,type); 在拖拽是触发的函数。 draggingNode: 被拖拽时的当前节点信息 dropNode: 拖拽到目的节点的信息 type: 被拖拽后的位置 这个判断能否被拖拽的标准就是:最终拖拽节点的目的地level <= 3 deep: 拖拽节点的层数,即自己加上字节点有几层。 maxLevel : 如果有子节点,那么maxLevel 一定是子节点里面的;如果没有子节点,那么maxLevel 就是自己的catLevel 然后用 deep = maxLevel - 拖拽节点的层级 + 1 ,得出拖拽节点的层数 如果有子节点,则获取子节点的catLevel 作为maxLevel 以上就是一个节点能否被拖拽的函数。
拖拽成功后,需要收集拖拽后节点的信息,然后再修改拖拽节点的子节点信息,因为。那么就要拖拽成功后处理节点的函数,里面的参数包含了我们所有想要的信息
handleDrop(draggingNode, dropNode, dropType, ev) { console.log("handleDrop: ", draggingNode, dropNode, dropType); //1. 当前节点最新的父节点id let pCid = 0; //兄弟节点的集合,包括拖拽节点 let siblings = null; //当拖拽节点是拖拽到目标节点的前面或者后面,那么 //目标节点就是拖拽节点的兄弟节点,父类都一样 //如果拖拽节点是拖拽到目标节点的里面,那么目标节点 //就是拖拽节点的父节点 if (dropType == "before" || dropType == "after") { //当拖拽节点拖拽到level 1的位置,此时父Id 是页面无法反馈(undefined)的 //所以要进行一个判断 pCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId; siblings = dropNode.parent.childNodes; } else { pCid = dropNode.data.catId; siblings = dropNode.childNodes; } //2. 当前拖拽节点的最新顺序:当前父节点的子节点顺序 for (let i = 0; i < siblings.length; i++) { if (siblings[i].data.catId == draggingNode.data.catId) { let catLevel = draggingNode.level; if (siblings[i].level != draggingNode.level) { //当前节点的层级信息发生改变 catLevel = siblings[i].level; //修改完当前节点的层级,修改它子节点的层级 this.updateChildNodLevel(siblings[i]); } //如果遍历的是当前正在拖拽的节点,还要为该节点添加父节点的Id 信息 this.updatedNodes.push({ catId: siblings[i].data.catId, sort: i, parentCid: pCid, catLevel: catLevel, }); } else { //如果遍历的不是正在拖拽的节点,不用修改父节点的Id this.updatedNodes.push({ catId: siblings[i].data.catId, sort: i }); } } //3. 当前拖拽节点的最新层级 console.log("updateNodes:", this.updatedNodes); this.$http({ url: this.$http.adornUrl("/product/category/update/sort"), method: "post", data: this.$http.adornData(this.updatedNodes, false), }).then(({ data }) => { this.$message({ message: "菜单顺序等修改成功", type: "success", }); //刷新菜单 this.getMenus(); this.expandedKey = [pCid]; this.updatedNodes = []; this.maxLevel = 0; }); },sibings: 拖拽节点成功后,所有的兄弟节点和节点的信息。 updateNodes: 记录所有进行修改过的信息,放到一个数组中,统一发送到数据库进行修改 因为updateNodes 要被两个方法使用,所以要成为全局变量。 对应后台接收到的数据,因为是updateNodes 是数组,所以参数也要变成数组。要用@ResponseBody 注释,前端就要用POST 请求。
完善拖拽功能: 1.因为加入了拖拽功能,所以每次点击页面的节点菜单的时候都可能会不经意拖动节点,所以要用一个开关来进行控制拖拽功能,当开启开关,拖拽功能打开;关闭开关,拖拽功能关闭。
开启拖拽的功能参数与开关参数绑定为同一个
2.每拖拽一次按钮,都要与数据库进行一次交互,这样占用资源比较多。那么可以用一个按钮进行开启后台数据的请求,即当拖拽完节点以后,按下该按钮,同一将拖拽修改后的数据发送的数据库。 把原先拖拽功能的发送请求,挪到该按钮的函数中 这里要注意两点,1:因为拖拽所产生的数据都由updateNodes[] 进行保存,并且只有按下批量保存按钮才能与数据库进行数据交互,那么此时拖拽功能中的算法就不能用data 中的数据了,因为它里面的数据是需要实时和数据库进行交互的,转而要使用当前节点下的数据,即不是data 中的数据。 并且之前的算法中有出现deep 是负数的情况,加上绝对值就没有负数了。
然后因为每次在按下批量保存的按钮之前都有大量的拖拽操作,而保存以后,又要重新展开这些拖拽节点的父节点,所以此时pCid 要成为一个成员变量,并且还要是一个数组。
批量删除功能: 批量删除需要识别的节点状态有三种:全选,半选,全不选 全选 半选 全不选
对应Element-ui 也有tree 标签的方法,即只要是选中了标签,那么就是触发这个方法。 先给tree 标签起个名 用button 按钮绑定该方法,即按下该按钮时才触发批量删除,才将要删除的信息发给数据库 该getCheckedNodes() 就是当tree 节点发生全选,全不选,半选情况时会触发该方法,并返回选中的节点数据。