谷粒商城实战笔记-55-商品服务-API-三级分类-修改-拖拽数据收集

文章目录

这一节的主要内容是收集拖拽后的要更新的节点的数据,逻辑比较复杂。

  • 1,拖拽后结点的parentCid的更新;
  • 2,拖拽后结点的父节点下所有结点的sort排序属性的变化;
  • 3,拖拽后结点及其子节点catLevel的变化;

所有这些数据都用收集到,然后发送给后台更新。

拖拽结束后,触发拖拽结束事件,所以要在el-tree上绑定这个事件。

事件函数nodeDrop定义如下。

然后定义一个全局数组,用来收集需要更新的节点数据。

一,拖拽后结点的parentCid的更新

clike 复制代码
	  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 假设是数据库中用来标识父节点的一个字段,通常在树形结构的数据模型中使用。

代码中的逻辑分为两种情况:

  1. 当拖动节点放置在目标节点之前 (prev) 或之后 (next):

    javascript 复制代码
    if (dropPosition === "prev" || dropPosition === "next") {
      draggingNode.parentCid = dropNode.data.parentCid;
    }

    在这种情况下,拖动的节点将变为与目标节点同级,但它们共同的父节点会成为新的父节点。因此,draggingNodeparentCid 被设置为 dropNode.data.parentCid,即目标节点的父节点ID。这种写法还兼容了没有父节点的情况,没有父节点时,parentCid为0。

  2. 当拖动节点放置在目标节点内部 (inner):

    javascript 复制代码
    else if (dropPosition === "inner") {
      draggingNode.parentCid = dropNode.data.cid;
    }

    如果拖动的节点被放置在目标节点内部,那么它将成为目标节点的子节点。因此,draggingNodeparentCid 被设置为 dropNode.data.cid,即目标节点自身的ID。

这段代码确保了在拖拽结束后,树形结构在逻辑上保持一致,parentCid 的更新反映了新的父子关系。这在维护树形数据结构的完整性方面非常重要,特别是在需要同步这些变化到后端数据库的场景下。

二,拖拽后结点的父节点下所有结点的sort排序属性的变化

clike 复制代码
	  // 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 的父节点是否改变来决定哪些节点的排序需要被更新。

更新排序的逻辑

  1. 确定旧的和新的兄弟节点集合:

    • siblingNodesOld 是拖动前 draggingNode 的兄弟节点集合,即它原本的同级节点。
    • siblingNodesNew 是拖动后 draggingNode 的兄弟节点集合,这取决于拖动的目标位置(放置在目标节点之前、之后还是内部)。
  2. 判断是否需要更新新的兄弟节点集合:

    • 如果 draggingNode 的父节点ID(parentCid)没有改变,意味着拖动只改变了顺序而没有改变层级,此时只需要更新旧的兄弟节点集合的排序。
    • 如果 draggingNode 的父节点ID改变了,意味着节点的层级发生了变化,此时需要更新旧的和新的兄弟节点集合的排序。
  3. 更新兄弟节点的排序:

    • 遍历 siblingNodesOldsiblingNodesNew 集合,将每个节点的 sort 属性设置为其在集合中的索引。这是因为索引反映了节点在数组中的位置,可以作为排序的依据。

代码分析

  • siblingNodesNew 的赋值中,对于放置在目标节点之前或之后的情况,代码似乎直接将 dropNode.data.parentCid 赋值给了 siblingNodesNew,但实际上 dropNode.data.parentCid 不应直接作为节点集合,这看起来像是一个错误或代码片段的不完整展示。正确的做法应该是获取 dropNode 的父节点的子节点集合。

  • 对于放置在目标节点内部的情况,代码正确地从 dropNode 的父节点获取了子节点集合。

三,拖拽后结点及其子节点catLevel的变化

clike 复制代码
// 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的定义如下:

clike 复制代码
// 递归更新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

javascript 复制代码
var catLevelNeedUpdate = true;
if (draggingNode.data.catLevel === dropNode.data.catLevel && (dropPosition === "prev" || dropPosition === "next")) {
  catLevelNeedUpdate = false;
}

如果拖动节点和目标节点的层级相同,并且拖动位置是目标节点的前后,那么 catLevel 不需要更新,因为拖动操作不会改变节点的层级深度。

获取拖动后的新节点

接下来,代码尝试获取拖动操作完成后的拖动节点 draggingNodeAfterDrop,这通常是通过查找父节点的子节点列表来完成的:

javascript 复制代码
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

javascript 复制代码
this.updateCatLevel(draggingNodeAfterDrop, dropNode.data.catLevel + deep);

updateCatLevel 方法如下:

javascript 复制代码
// 递归更新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

完整代码

clike 复制代码
<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>
相关推荐
遇印记2 小时前
大二java学习笔记:二维数组
java·笔记·学习
bnsarocket4 小时前
Verilog和FPGA的自学笔记6——计数器(D触发器同步+异步方案)
笔记·fpga开发·verilog·自学·硬件编程
LK_075 小时前
【Open3D】Ch.3:顶点法向量估计 | Python
开发语言·笔记·python
li星野5 小时前
打工人日报#20251011
笔记·程序人生·fpga开发·学习方法
摇滚侠5 小时前
Spring Boot 3零基础教程,yml配置文件,笔记13
spring boot·redis·笔记
QT 小鲜肉5 小时前
【个人成长笔记】在Ubuntu中的Linux系统安装 anaconda 及其相关终端命令行
linux·笔记·深度学习·学习·ubuntu·学习方法
QT 小鲜肉5 小时前
【个人成长笔记】在Ubuntu中的Linux系统安装实验室WIFI驱动安装(Driver for Linux RTL8188GU)
linux·笔记·学习·ubuntu·学习方法
急急黄豆6 小时前
MADDPG学习笔记
笔记·学习
Chloeis Syntax6 小时前
栈和队列笔记2025-10-12
java·数据结构·笔记·
QZ_orz_freedom7 小时前
学习笔记--文件上传
java·笔记·学习