文章目录
- 一,56-商品服务-API-三级分类-修改-拖拽功能完成
- 二,57-商品服务-API-三级分类-修改-批量拖拽效果
- 1,增加按钮
- 2,多次拖拽一次保存
- 完整代码
在构建商品服务API中的三级分类修改功能时,拖拽排序是一个直观且高效的管理方式。通过允许用户直接在界面上调整分类的顺序,可以显著提升用户体验和操作效率。在实现这一功能的过程中,我们不仅关注了单个分类的即时更新,还考虑到了多级分类同时变化的情况,以及如何批量处理这些变化。
在上一节中,我们已经完成了拖拽功能的基础部分,包括对分类顺序、级别和父节点的更新逻辑。现在,我们将进一步优化该功能,以提供更完善的用户体验。
首先,增加了一个可控制的开关按钮,用于启动或禁用拖拽功能。这不仅增强了系统的灵活性,也避免了在不需要进行分类调整时可能出现的误操作。用户可以根据实际需求选择是否启用拖拽模式,这为操作者提供了更大的自主权。
其次,为了提高效率,我们引入了“批量保存拖拽”按钮。当用户进行了多次拖拽操作后,不必逐一保存每个分类的变动,而是可以通过点击这个按钮一次性提交所有更改。这样不仅简化了工作流程,还减少了网络请求次数,提高了系统响应速度。
在实现批量保存功能时,我们使用了Vue.js框架中的$http方法来发起POST请求,将包含所有变动信息的数组nodesInfoAfterDrop发送给后端。后端接收到请求后,利用updateBatchById方法批量更新数据库中的分类信息。如果更新成功,前端会显示成功的消息,并自动重新加载分类列表,确保界面与数据库状态保持一致。
此外,为了保证数据的准确性,在批量保存后,我们还重置了一些关键变量,如expandedKeys和draggingNodeNewPId,确保下一次操作时从一个干净的状态开始。这些细节上的考虑,使得整个拖拽和保存流程更加健壮和可靠。
综上所述,通过增加控制开关和批量保存功能,我们的商品服务API中的三级分类修改功能得到了显著增强,不仅提升了用户体验,也提高了管理效率,是系统功能完善的重要一步。
一,56-商品服务-API-三级分类-修改-拖拽功能完成
上一节已经把需要更新的数据都封装好了,三种结点的数据需要更新。
- ① 顺序发生变化,需要更新sort值
- ② 分类级别发生变化,需要更新catLevel
- ③ 父节点发生变化,需要更新parentCid
后端接口代码,根据cid批量更新。
@RequestMapping("/update/sort")
public R update(@RequestBody CategoryEntity[] categorys){
categoryService.updateBatchById(Arrays.asList(categorys));
return R.ok();
}
二,57-商品服务-API-三级分类-修改-批量拖拽效果
基于上一节,做两个优化。
1,增加按钮
增加一个开关,控制拖拽功能的开启,只有开启了拖拽功能,才能拖拽。
2,多次拖拽一次保存
增加一个按钮用来批量保存拖拽后的节点信息。
<el-button @click="batchSaveDrag" v-if="draggable"
>批量保存拖拽</el-button
>
并绑定一个click事件。
batchSaveDrag() {
// 4,调用后端接口,将数组nodesInfoAfterDrop发送给后端
this.$http({
url: this.$http.adornUrl("/product/category/update/sort"),
method: "post",
data: this.$http.adornData(this.nodesInfoAfterDrop, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
console.log("删除成功,关闭消息提示");
this.getMenus(); // 重新获取数据
this.expandedKeys = [this.draggingNodeNewPId]; // 重置展开节点
this.draggingNodeNewPId = 0; // 重置����的新parentCid
this.nodesInfoAfterDrop = [];
},
});
} else {
this.$message.error(data.msg);
}
});
},
完整代码
<template>
<div>
<el-switch
style="display: block"
v-model="draggable"
active-color="#13ce66"
inactive-color="#ff4949"
active-text="开启拖拽"
inactive-text="关闭拖拽"
>
</el-switch>
<el-button @click="batchSaveDrag" v-if="draggable"
>批量保存拖拽</el-button
>
<el-tree
node-key="catId"
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
:default-expanded-keys="expandedKeys"
:allow-drop="allowDrag"
@node-drop="nodeDrop"
:draggable="draggable"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
v-if="node.level <= 2"
size="mini"
@click="() => append(data)"
>
Append
</el-button>
<el-button size="mini" @click="() => edit(data)"> Edit </el-button>
<el-button
v-if="node.childNodes.length == 0"
type="text"
size="mini"
@click="() => remove(node, data)"
>
Delete
</el-button>
</span>
</span>
</el-tree>
<el-dialog
:title="dialogTitle"
:visible.sync="dialogFormVisible"
:close-on-click-modal="false"
>
<el-form :model="category">
<el-form-item label="分类名称">
<el-input v-model="category.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="图标">
<el-input v-model="category.icon" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="计量单位">
<el-input
v-model="category.productUnit"
autocomplete="off"
></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="submitCategory">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
components: {},
props: {},
data() {
return {
draggingNodeNewPId: 0,
draggable: false,
nodesInfoAfterDrop: [],
dialogTitle: "", // 编辑窗口标题,新增分类,修改分类
dialogType: "", // 编辑窗口类型,create表示append,edit表示edit
dialogFormVisible: false,
menus: [],
category: {
name: "",
parentCid: 0,
catLevel: 0,
sort: 0,
showStatus: 1,
icon: "",
productUnit: "",
catId: null,
},
expandedKeys: [],
defaultProps: {
children: "children",
label: "name",
},
};
},
methods: {
nodeDrop(draggingNode, dropNode, dropPosition) {
console.log("draggingNode:", draggingNode, dropNode, dropPosition);
// 1,更新draggingNode的parentCid,根据dropPosition的不同值,分为两种情况
if (dropPosition === "before" || dropPosition === "after") {
this.draggingNodeNewPId = dropNode.data.parentCid;
} else {
this.draggingNodeNewPId = dropNode.data.cid;
}
console.log("draggingNodeNewPId:", this.draggingNodeNewPId, dropNode.data.parentCid, dropNode.data.catId);
// 2,更新draggingNode及其子节点的sort
// 2.1如果draggingNode的parentCid没有更新,只需要重排draggingNode的兄弟结点的sort;
// 2.2如果draggingNode的parentCid有更新,不仅需要重排draggingNode的原来的兄弟结点的sort,还需要重排draggingNode新的兄弟结点的sort
var siblingNodesOld = [];
var siblingNodesNew = [];
// draggingNode.parent为空,所以要根据parentCid找到其parent。
// 写一个根据cid从menus中查询结点的函数
let draggingNodeOldParentNode = this.getNodeByCid(
draggingNode.data.parentCid,
this.getLevel1Nodes(dropNode)
);
console.log("draggingNodeOldParentNode:", draggingNodeOldParentNode);
siblingNodesOld = draggingNodeOldParentNode.childNodes;
console.log("siblingNodesOld:", siblingNodesOld);
if (draggingNode.data.parentCid !== this.draggingNodeNewPId) {
if (dropPosition === "before" || dropPosition === "after") {
siblingNodesNew = dropNode.parent.childNodes;
} else {
siblingNodesNew = dropNode.childNodes;
}
}
for (var i = 0; i < siblingNodesOld.length; i++) {
this.nodesInfoAfterDrop.push({
catId: siblingNodesOld[i].data.catId,
sort: i,
});
}
console.log(
"update sortu0....",
siblingNodesNew,
siblingNodesOld,
dropNode,
dropPosition
);
for (var i = 0; i < siblingNodesNew.length; i++) {
this.nodesInfoAfterDrop.push({
catId: siblingNodesNew[i].data.catId,
sort: i,
});
}
console.log(
"update sort....",
siblingNodesNew,
siblingNodesOld,
dropNode,
dropPosition
);
// 3,更新draggingNode及其子节点的catLevel
var catLevelNeedUpdate = true;
if (
draggingNode.data.catLevel === dropNode.data.catLevel &&
(dropPosition === "before" || dropPosition === "after")
) {
catLevelNeedUpdate = false;
}
var draggingNodeAfterDrop = null;
if (catLevelNeedUpdate) {
var draggingParentNodeAfterDrop = {};
if (dropPosition === "before" || dropPosition === "after") {
draggingParentNodeAfterDrop = dropNode.parent;
} else {
draggingParentNodeAfterDrop = dropNode;
}
draggingParentNodeAfterDrop.childNodes.forEach((child) => {
if (child.data.catId === draggingNode.data.catId) {
draggingNodeAfterDrop = child;
}
});
// 递归变量draggingNodeAfterDrop及其childNodes,将其data属性的cateLevel置为level属性值
this.updateCatLevel(draggingNodeAfterDrop);
}
console.log("draggingNodeNewPId", this.draggingNodeNewPId)
this.nodesInfoAfterDrop.push({
catId: draggingNode.data.catId,
parentCid: this.draggingNodeNewPId,
});
console.log(this.nodesInfoAfterDrop);
},
batchSaveDrag() {
// 4,调用后端接口,将数组nodesInfoAfterDrop发送给后端
this.$http({
url: this.$http.adornUrl("/product/category/update/sort"),
method: "post",
data: this.$http.adornData(this.nodesInfoAfterDrop, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
console.log("删除成功,关闭消息提示");
this.getMenus(); // 重新获取数据
this.expandedKeys = [this.draggingNodeNewPId]; // 重置展开节点
this.draggingNodeNewPId = 0; // 重置����的新parentCid
this.nodesInfoAfterDrop = [];
},
});
} else {
this.$message.error(data.msg);
}
});
},
getLevel1Nodes(node) {
var tmpNode = node;
while (tmpNode.parent) {
tmpNode = tmpNode.parent;
}
return tmpNode.childNodes;
},
// 写一个根据cid从menus中查询结点的函数
getNodeByCid(cid, nodes) {
if (cid === 0) {
return nodes[0].parent;
}
// 递归查询
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].data.catId === cid) {
return nodes[i];
}
if (nodes[i].childNodes && nodes[i].childNodes.length > 0) {
var node = this.getNodeByCid(cid, nodes[i].childNodes);
if (node) {
return node;
}
}
}
return null;
},
// 递归更新draggingNode及其子节点的catLevel
updateCatLevel(node) {
if (!node) {
return;
}
this.nodesInfoAfterDrop.push({
catId: node.data.catId,
catLevel: node.level,
});
if (node.childNodes && node.childNodes.length > 0) {
node.childNodes.forEach((child) => {
this.updateCatLevel(child);
});
}
},
allowDrag(draggingNode, dropNode, dropPosition) {
var deep = this.countDraggingNodeDeep(draggingNode);
// 根据dropPosition结合dropNode.catLevel来判断draggingNode新位置的位置是否合法
if (dropPosition === "prev" || dropPosition === "next") {
return dropNode.data.catLevel + deep - 1 <= 3;
} else if (dropPosition === "inner") {
return dropNode.data.catLevel + deep <= 3;
}
},
// 递归计算draggingNode子树的深度
countDraggingNodeDeep(draggingNode) {
var deep = 0;
if (draggingNode.childNodes && draggingNode.childNodes.length > 0) {
debugger;
draggingNode.childNodes.forEach((child) => {
deep = Math.max(deep, this.countDraggingNodeDeep(child));
});
}
return deep + 1;
},
append(data) {
console.log(data);
this.dialogType = "create";
this.dialogTitle = "新增分类";
this.dialogFormVisible = true;
this.category = {
name: "",
parentCid: data.catId,
catLevel: data.level + 1,
sort: 0,
showStatus: 1,
};
},
edit(data) {
console.log(data);
this.dialogType = "edit";
this.dialogTitle = "修改分类";
this.dialogFormVisible = true;
// 根据catId查询最新数据
this.$http({
url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
method: "get",
data: this.$http.adornData({ catId: data.catId }, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.category = { ...data.data };
} else {
this.$message.error(data.msg);
}
});
},
submitCategory() {
if (this.dialogType === "create") {
this.addCategory();
} else if (this.dialogType === "edit") {
this.updateCategory();
}
},
updateCategory() {
var { catId, name, icon, productUnit } = this.category;
console.log(this.category);
this.$http({
url: this.$http.adornUrl("/product/category/update"),
method: "post",
data: this.$http.adornData({ catId, name, icon, productUnit }, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "修改成功",
type: "success",
duration: 1500,
onClose: () => {
console.log("修改成功,关闭消息提示");
this.dialogFormVisible = false;
this.getMenus(); // 重新获取数据
this.expandedKeys = [
this.category.parentCid == 0
? this.category.catId
: this.category.parentCid,
]; // 重置展开节点
console.log(this.expandedKeys);
},
});
} else {
this.$message.error(data.msg);
}
});
},
addCategory() {
this.$http({
url: this.$http.adornUrl("/product/category/save"),
method: "post",
data: this.$http.adornData(this.category, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "添加成功",
type: "success",
duration: 1500,
onClose: () => {
console.log("添加成功,关闭消息提示");
this.dialogFormVisible = false;
this.getMenus(); // 重新获取数据
this.expandedKeys = [this.category.parentCid]; // 重置展开节点
},
});
} else {
this.$message.error(data.msg);
}
});
},
remove(node, data) {
console.log(node, data);
var ids = [node.data.catId];
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${
ids.length == 1 ? "删除" : "批量删除"
}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
console.log("删除成功,关闭消息提示");
this.getMenus(); // 重新获取数据
this.expandedKeys = [node.parent.data.catId]; // 重置展开节点
},
});
} else {
this.$message.error(data.msg);
}
});
})
.catch(() => {});
},
// 获取分类数据
getMenus() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
console.log(data);
this.dataListLoading = false;
this.menus = data.data;
});
},
},
created() {
this.getMenus(); // 获取分类数据
},
};
</script>
<style scoped>
</style>