最后
最后写上我自己一直喜欢的一句名言:
世界上只有一种真正的英雄主义就是在认清生活真相之后仍然热爱它
效果:
- 由于可能存在批量删除的功能,因此需要加上复选框。给
tree加上show-checkbox属性。
<el-tree
:expand-on-click-node="false"
:data="menus"
:props="defaultProps"
show-checkbox
效果:
- 为
tree添加node-key属性,用来标识唯一性
<el-tree
:expand-on-click-node="false"
:data="menus"
:props="defaultProps"
show-checkbox
node-key="catId">
========================================================================
- 查看初始删除接口
查看gulimall-product中的CategoryController.java中,有逆向生成的删除接口。
接收的参数为@RequestBody中的需要删除的Id数组。
注意:@RequestBody为请求体中的内容,只有POST请求有。
SpringMVC会自动将请求体的数据(JSON)转为对应的对象。
删除之前,需要检查当前删除的菜单,是否被别的地方引用。
- 自定义删除方法
自定义方法categoryService.removeMenuByIds,并在接口中定义该方法,在实现类中对方法进行实现。
- 完善删除方法
调用baseMapper.deleteBatchIds()方法进行批量删除。
由于暂时不确定哪里需要引用菜单,因此,将方法定义好后,添加
// TODO:检查当前删除的菜单,是否被别的地方引用。
备注当前事务待做。
@Override
public void removeMenuByIds(List asList) {
// TODO:检查当前删除的菜单,是否被别的地方引用。
baseMapper.deleteBatchIds(asList);
}
- 使用逻辑删除
-
物理删除:在数据库中将满足条件的数据删除,删除了数据就不存在了。
-
逻辑删除:在数据库中使用某一个字段作为标识类,表示是否被删除。
baseMapper.deleteBatchIds(singletonList)方法是物理删除,直接将源数据删除了,并不好。
因此,使用数据库中字段show_status来标识数据是否被删除。
==========================================================================
- 配置全局规则(可以省略)
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
- 实体类字段加上
@TableLogic逻辑删除注解
@TableLogic
private Integer deleted;
可以在@TableLogic定义自己的规则
@TableLogic(value = "1", delval = "0")
private Integer showStatus;
- 重启项目测试
未删除状态下show_status为1
Postman测试删除1431号数据
结果:
========================================================================
接口已经完善好了,需要进行前端点击Delete发送请求删除。
- 完善
category.vue中的remove方法,发送POST请求,传递需要删除的ID数组。
move(node, data) {
// 1. 获取点击ID
var ids = [data.catId];
// 2. 发起POST请求,调用接口删除数据
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
// 3. 请求成功后,重新请求所有菜单并显示。
this.getMenu();
});
},
- 细节优化
-
点击删除的时候,弹出确认提示窗。使用
MessageBox组件 -
删除成功后,弹出删除成功消息提示:使用
message组件 -
删除成功后,菜单树不自动关闭,仍然展开。
为tree绑定default-expanded-keys属性。设置默认展开值。当删除操作完成之后,控制绑定值为父节点idnode.parent.data.catId来展开树。
<el-tree
:expand-on-click-node="false"
:data="menus"
:props="defaultProps"
show-checkbox
node-key="catId"
:default-expanded-keys="expendKey"
- 优化后remove代码
remove(node, data) {
// 1. 获取点击ID
var ids = [data.catId];
// 2. 弹窗确认
this.{data.name}】菜单?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
// 3. 发起POST请求,调用接口删除数据
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
// 4. 成功提示
this.$message({
type: "success",
message: "菜单删除成功",
});
// 5. 请求成功后,重新请求所有菜单并显示。
this.getMenu();
// 6. 设置需要默认展开菜单
this.expendKey = [node.parent.data.catId];
});
})
.catch(() => {
this.$message({
type: "info",
message: "已取消删除",
});
});
},
- 删除效果
完成删除菜单功能后category.vue完整代码
======================================================================
目标:点击添加菜单后,弹出对话窗,填写添加菜单名称,点击确定,完成添加。
- 弹出对话窗:使用
Dialog组件实现,Dialog内嵌套Form实现数据收集
- 保存数据
逆向生成工程中已经存在保存接口,直接利用POST发起请求即可
- 发起POST请求
addCategory() {
// 1. 发起请求
this.$http({
url: this.$http.adornUrl("/product/category/save"),
method: "post",
data: this.$http.adornData(this.category, false),
}).then(({ data }) => {
// 2. 关闭对话窗
this.dialogVisible = false;
this.category = {
name: "",
parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
};
// 3. 提示保存成功
this.$message({
type: "success",
message: "菜单保存成功",
});
// 4. 刷新菜单数据并展示之前节点
this.getMenu();
this.expendKey = [node.parent.data.catId];
});
},
- 测试
完成添加菜单功能后category.vue完整代码
======================================================================
需求:为操作按钮后添加编辑菜单按钮,点击编辑菜单后,显示弹窗回显选择内容,动态输入新的内容编辑后点击提交完成编辑。
- 实现页面效果,增加
编辑菜单按钮
<el-button type="text" size="mini" @click="() => edit(data)">编辑菜单
-
复用添加功能中的
Dialog并进行完善 -
测试
完成编辑菜单功能后category.vue完整代码
======================================================================
通过拖拽效果,动态更改菜单层级与父节点等关系。
- 开启拖拽功能:通过
draggable属性,开启拖拽
<el-tree
:expand-on-click-node="false"
:data="menus"
:props="defaultProps"
show-checkbox
node-key="catId"
:default-expanded-keys="expendKey"
draggable
- 拖拽判断:通过
allow-drop属性,判定目标节点能否被放置。
因为是三层结构,所以自定义判断函数判断被拖动的当前节点以及所在的父节点总层数不能大于3。
<el-tree
:expand-on-click-node="false"
:data="menus"
:props="defaultProps"
show-checkbox
node-key="catId"
:default-expanded-keys="expendKey"
draggable
:allow-drop="allowDrop"
allowDrop(draggingNode, dropNode, type) {
//被拖动的当前节点以及所在的父节点总层数不能大于3
// 1. 判断被拖动的当前节点总层数
this.countNodeLevel(draggingNode.data);
// 当前正在拖动的节点 + 父节点所在的深度不大于3即可
let deep = this.maxLevel - draggingNode.data.catLevel + 1;
if (type === "inner") return deep + dropNode.level <= 3;
else return deep + dropNode.parent.level <= 3;
},
/**
- 求当前节点的最大子节点深度
*/
countNodeLevel(node) {
// 找到所有子节点,求出最大深度
const length = node.children.length;
if (node.children !== null && length > 0) {
for (let i = 0; i < length; i++) {
if (node.children[i].catLevel > this.maxLevel) {
this.maxLevel = node.children[i].catLevel;
}
this.countNodeLevel(node.children[i]);
}
}
},
- 拖拽数据收集(父节点、层级、排序)
- 监听拖拽成功事件:使用
node-drop监听拖拽成功完成时触发的事件
<el-tree
:expand-on-click-node="false"
:data="menus"
:props="defaultProps"
show-checkbox
node-key="catId"
:default-expanded-keys="expendKey"
draggable
:allow-drop="allowDrop"
@node-drop="handleDrop"
- 完善
handleDrop事件
// 拖拽完成事件
handleDrop(draggingNode, dropNode, dropType, ev) {
let pCid = 0;
let siblings = null;
// 1. 当前拖拽节点的最新父节点id
if (dropType === "before" || dropType === "after") {
pCid =
dropNode.parent.data.catId === undefined
? 0
: dropNode.parent.data.catId;
siblings = dropNode.parent.childNodes;
} else if (dropType === "inner") {
pCid = dropNode.data.catId;
siblings = dropNode.childNodes;
}
// 2. 当前拖拽节点的最新排序
// 3. 当前拖拽节点的最新层级
for (let i = 0; i < siblings.length; i++) {
if (siblings[i].data.catId === draggingNode.data.catId) {
// 如果遍历的是当前正在拖拽的节点
let catLevel = draggingNode.level;
if (siblings[i].level !== catLevel) {
// 当前节点层级发生了变化
catLevel = siblings[i].level;
// 修改子节点的层级
this.updateChildNodeLevel(siblings[i]);
}
this.updateNodes.push({
catId: siblings[i].data.catId,
sort: i,
parentCid: pCid,
catLevel: catLevel,
});
} else {
this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
}
}
console.log(this.updateNodes);
},
// 更新孩子节点层级
updateChildNodeLevel(node) {
const length = node.childNodes.length;
if (length > 0) {
for (let i = 0; i < length; i++) {
let cNode = node.childNodes[i].data;
this.updateNodes.push({
catId: cNode.catId,
catLevel: node.childNodes[i].level,
});
this.updateChildNodeLevel(node.childNodes[i]);
}
}
},
- 拖拽更新后将数据发给后台更新数据库
- 在
CategoryController.java中编写接口实现数据更新
updateBatchById为自带的自动生成的批量修改接口,根据id批量修改。
/**
- 批量修改
*/
@RequestMapping("/update/sort")
public R updateSort(@RequestBody CategoryEntity[] category) {
categoryService.updateBatchById(Arrays.asList(category));
return R.ok();
}
- Postman测试批量修改接口
效果:
- 前端调用接口实现
// 4. 发起请求
this.$http({
url: this.$http.adornUrl("/product/category/update/sort"),
method: "post",
data: this.$http.adornData(this.updateNodes, false),
}).then(({ data }) => {
this.$message({
type: "success",
message: "菜单顺序等修改成功",
});
// 刷新菜单并设置默认展开菜单
this.getMenu();
this.expendKey = [pCid];
// 设置默认值
this.updateNodes = [];
this.maxLevel = 0;
});
完成拖拽修改菜单功能后category.vue完整代码
====================================================================
- 拖拽功能如果一直开着容易不小心点击造成意外拖拽:通过
switch开关来控制是否开启拖拽功能
<el-switch
v-model="draggable"
active-text="开启拖拽"
inactive-text="关闭拖拽"
style="margin-bottom: 15px"
<el-tree
···
:draggable="draggable"
···
- 如果每次拖拽都与数据库交互,不仅繁琐,而且消耗性能:通过
Button按钮控制是否上传
<el-button v-if="draggable" @click="batchSave" type="success"
批量保存</el-button
// 拖拽保存
batchSave() {
// 发起请求
this.$http({
url: this.$http.adornUrl("/product/category/update/sort"),
method: "post",
data: this.$http.adornData(this.updateNodes, false),
}).then(({ data }) => {
this.$message({
type: "success",
message: "菜单顺序等修改成功",
});
// 刷新菜单并设置默认展开菜单
this.getMenu();
this.expendKey = [this.pCid];
// 设置默认值
this.updateNodes = [];
this.maxLevel = 0;
});
},
由于不再是每次拖拽都与数据库进行交互,因此在判断能否进行交互的时候,不能再用数据库的数据进行判断,而应判断拖动数据在树中真正层级。
完成优化后category.vue完整代码
====================================================================
通过按钮,实现批量删除
<el-button @click="batchDelete" type="danger">批量删除
通过ref标识组件
<el-tree
···
ref="menuTree"
使用tree的getCheckedNodes()方法获取选中节点
let checkedNodes = this.$refs.menuTree.getCheckedNodes();
调用/product/category/delete批量删除
is.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(catIds, false),
}).then(({ data }) => {
this.$message({
type: "success",
message: "菜单批量删除成功",
});
this.getMenu();
});
===========================================================================
三级分类增删改查效果基本完成