文章目录
- 一,拖拽后结点的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新的兄弟结点的sortvar 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及其子节点的catLevelvar 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及其子节点的catLevelupdateCatLevel(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-treenode-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-buttonv-if="node.level <= 2"size="mini"@click="() => append(data)">Append</el-button><el-button size="mini" @click="() => edit(data)"> Edit </el-button><el-buttonv-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-inputv-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表示editdialogFormVisible: 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新的兄弟结点的sortvar 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及其子节点的catLevelvar 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及其子节点的catLevelupdateCatLevel(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>