文章目录
- 一,拖拽后结点的parentCid的更新
- 二,拖拽后结点的父节点下所有结点的sort排序属性的变化
- 更新排序的逻辑
- 代码分析
- 三,拖拽后结点及其子节点catLevel的变化
- 判断是否需要更新 `catLevel`
- 获取拖动后的新节点
- 更新 `catLevel`
- 完整代码
这一节的主要内容是收集拖拽后的要更新的节点的数据,逻辑比较复杂。
- 1,拖拽后结点的parentCid的更新;
- 2,拖拽后结点的父节点下所有结点的sort排序属性的变化;
- 3,拖拽后结点及其子节点catLevel的变化;
所有这些数据都用收集到,然后发送给后台更新。
拖拽结束后,触发拖拽结束事件,所以要在el-tree上绑定这个事件。
事件函数nodeDrop定义如下。
然后定义一个全局数组,用来收集需要更新的节点数据。
一,拖拽后结点的parentCid的更新
var draggingNodeNewPId = 0;
// 1,更新draggingNode的parentCid,根据dropPosition的不同值,分为两种情况
if (dropPosition === "before" || dropPosition === "after") {
draggingNodeNewPId = dropNode.data.parentCid;
} else {
draggingNodeNewPId = dropNode.data.cid;
}
这段代码是在处理 el-tree
组件中拖拽结束后的逻辑,特别是更新被拖动节点的 parentCid
字段,以反映新的父节点关系。parentCid
假设是数据库中用来标识父节点的一个字段,通常在树形结构的数据模型中使用。
代码中的逻辑分为两种情况:
-
当拖动节点放置在目标节点之前 (
prev
) 或之后 (next
):if (dropPosition === "prev" || dropPosition === "next") { draggingNode.parentCid = dropNode.data.parentCid; }
在这种情况下,拖动的节点将变为与目标节点同级,但它们共同的父节点会成为新的父节点。因此,
draggingNode
的parentCid
被设置为dropNode.data.parentCid
,即目标节点的父节点ID。这种写法还兼容了没有父节点的情况,没有父节点时,parentCid为0。 -
当拖动节点放置在目标节点内部 (
inner
):else if (dropPosition === "inner") { draggingNode.parentCid = dropNode.data.cid; }
如果拖动的节点被放置在目标节点内部,那么它将成为目标节点的子节点。因此,
draggingNode
的parentCid
被设置为dropNode.data.cid
,即目标节点自身的ID。
这段代码确保了在拖拽结束后,树形结构在逻辑上保持一致,parentCid
的更新反映了新的父子关系。这在维护树形数据结构的完整性方面非常重要,特别是在需要同步这些变化到后端数据库的场景下。
二,拖拽后结点的父节点下所有结点的sort排序属性的变化
// 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 !== 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);
这段代码涉及到了在 el-tree
拖拽操作后更新节点排序(sort
属性)的逻辑,以便反映节点在树结构中的新位置。这里主要关注两个方面:更新拖动节点 draggingNode
及其子节点的排序,以及根据 draggingNode
的父节点是否改变来决定哪些节点的排序需要被更新。
更新排序的逻辑
-
确定旧的和新的兄弟节点集合:
siblingNodesOld
是拖动前draggingNode
的兄弟节点集合,即它原本的同级节点。siblingNodesNew
是拖动后draggingNode
的兄弟节点集合,这取决于拖动的目标位置(放置在目标节点之前、之后还是内部)。
-
判断是否需要更新新的兄弟节点集合:
- 如果
draggingNode
的父节点ID(parentCid
)没有改变,意味着拖动只改变了顺序而没有改变层级,此时只需要更新旧的兄弟节点集合的排序。 - 如果
draggingNode
的父节点ID改变了,意味着节点的层级发生了变化,此时需要更新旧的和新的兄弟节点集合的排序。
- 如果
-
更新兄弟节点的排序:
- 遍历
siblingNodesOld
和siblingNodesNew
集合,将每个节点的sort
属性设置为其在集合中的索引。这是因为索引反映了节点在数组中的位置,可以作为排序的依据。
- 遍历
代码分析
-
在
siblingNodesNew
的赋值中,对于放置在目标节点之前或之后的情况,代码似乎直接将dropNode.data.parentCid
赋值给了siblingNodesNew
,但实际上dropNode.data.parentCid
不应直接作为节点集合,这看起来像是一个错误或代码片段的不完整展示。正确的做法应该是获取dropNode
的父节点的子节点集合。 -
对于放置在目标节点内部的情况,代码正确地从
dropNode
的父节点获取了子节点集合。
三,拖拽后结点及其子节点catLevel的变化
// 3,更新draggingNode及其子节点的catLevel
var catLevelNeedUpdate = true;
if (draggingNode.data.catLevel === dropNode.data.catLevel && (dropPosition === "before" || dropPosition === "after")) {
catLevelNeedUpdate = false;
}
var draggingNodeAfterDrop = {};
if (catLevelNeedUpdate) {
var draggingParentNodeAfterDrop = {};
if (dropPosition === "before" || dropPosition === "after") {
draggingParentNodeAfterDrop = dropNode.parent;
} else {
draggingParentNodeAfterDrop = dropNode;
}
draggingParentNodeAfterDrop.childNodes.forEach((child) => {
if (child.cid === draggingNode.data.cid) {
draggingNodeAfterDrop = child;
}
})
// 递归变量draggingNodeAfterDrop及其childNodes,将其data属性的cateLevel置为level属性值
this.updateCatLevel(draggingNodeAfterDrop);
}
updateCatLevel的定义如下:
// 递归更新draggingNode及其子节点的catLevel
updateCatLevel(node) {
node.catLevel = node.level;
if (node.childNodes && node.childNodes.length > 0) {
node.childNodes.forEach((child) => {
this.updateCatLevel(child);
});
}
},
这段代码的主要作用是,在 el-tree
组件的拖拽操作完成后,更新拖动节点 draggingNode
及其所有子节点的 catLevel
属性,以反映新的层级关系。catLevel
通常用于表示节点在树结构中的层级深度,这对于保持树结构的一致性和完整性是非常重要的。
判断是否需要更新 catLevel
首先,代码检查是否需要更新 catLevel
:
var catLevelNeedUpdate = true;
if (draggingNode.data.catLevel === dropNode.data.catLevel && (dropPosition === "prev" || dropPosition === "next")) {
catLevelNeedUpdate = false;
}
如果拖动节点和目标节点的层级相同,并且拖动位置是目标节点的前后,那么 catLevel
不需要更新,因为拖动操作不会改变节点的层级深度。
获取拖动后的新节点
接下来,代码尝试获取拖动操作完成后的拖动节点 draggingNodeAfterDrop
,这通常是通过查找父节点的子节点列表来完成的:
var draggingNodeAfterDrop = {};
if (catLevelNeedUpdate) {
var draggingParentNodeAfterDrop = {};
if (dropPosition === "prev" || dropPosition === "next") {
draggingParentNodeAfterDrop = dropNode.parent;
} else {
draggingParentNodeAfterDrop = dropNode;
}
draggingParentNodeAfterDrop.childNodes.forEach((child) => {
if (child.cid === draggingNode.data.cid) {
draggingNodeAfterDrop = child;
}
})
}
这里,draggingParentNodeAfterDrop
根据拖动位置确定,然后遍历其子节点找到具有相同 cid
的节点,即拖动后的拖动节点。
更新 catLevel
最后,如果需要更新 catLevel
,调用 updateCatLevel
方法来递归更新拖动节点及其所有子节点的 catLevel
:
this.updateCatLevel(draggingNodeAfterDrop, dropNode.data.catLevel + deep);
updateCatLevel
方法如下:
// 递归更新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);
});
}
},
在这个方法中,node.catLevel
被设置为 node.level
,后者通常反映了节点的实际层级深度。如果节点有子节点,递归地调用 updateCatLevel
方法来更新所有子节点的 catLevel
。
完整代码
<template>
<div>
<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
>
<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 {
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);
var draggingNodeNewPId = 0;
// 1,更新draggingNode的parentCid,根据dropPosition的不同值,分为两种情况
if (dropPosition === "before" || dropPosition === "after") {
draggingNodeNewPId = dropNode.data.parentCid;
} else {
draggingNodeNewPId = dropNode.data.cid;
}
// 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 !== 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);
}
this.nodesInfoAfterDrop.push({catId: draggingNode.data.catId, parentCid: draggingNodeNewPId});
console.log(this.nodesInfoAfterDrop);
// 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 = [draggingNodeNewPId]; // 重置展开节点
},
});
} 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 null;
}
// 递归查询
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>