【项目开发】商城 - 三级分类 - 简单笔记

目录标题

后端

业务类

java 复制代码
	// 省略其他简单的CRUD

    @Override
    public List<CategoryEntity> listWithTree() {
        // 1、查出所有分类
        List<CategoryEntity> list = baseMapper.selectList(null);
        // 2. 找出所有的一级分类
        List<CategoryEntity> level1 = list.stream()
                .filter(x -> x.getParentCid() == 0)
                .peek(x -> x.setChildren(getChildren(x, list)))
                .sorted((x1, x2) -> {
                    return (x1.getSort() == null ? 0 : x1.getSort()) - (x2.getSort() == null ? 0 : x2.getSort());
                })
                .collect(Collectors.toList());
        return level1;
    }

    /**
     * 在所有的数据list里面,查出子数据root下面的数据
     * 递归查询
     *
     * @param root 子数据
     * @param list 所有的数据
     * @return
     */
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> list) {
        List<CategoryEntity> entities = list.stream()
                .filter(x -> x.getParentCid().equals(root.getCatId()))
                .map(x -> {
                    x.setChildren(getChildren(x, list));
                    return x;
                })
                .sorted((x1, x2) -> {
                    return (x1.getSort() == null ? 0 : x1.getSort()) - (x2.getSort() == null ? 0 : x2.getSort());
                })
                .collect(Collectors.toList());
        return entities;
    }

实体类

sql 复制代码
-- auto-generated definition
create table pms_category
(
    cat_id        bigint auto_increment comment '分类id'
        primary key,
    name          char(50)  null comment '分类名称',
    parent_cid    bigint    null comment '父分类id',
    cat_level     int       null comment '层级',
    show_status   tinyint   null comment '是否显示[0-不显示,1显示]',
    sort          int       null comment '排序',
    icon          char(255) null comment '图标地址',
    product_unit  char(50)  null comment '计量单位',
    product_count int       null comment '商品数量'
)
    comment '商品三级分类' charset = utf8mb4;

前端

js 复制代码
<template>
  <div>

    <el-switch style="display: block" v-model="draggable" active-color="#13ce66" inactive-color="#ff4949"
      active-text="开启拖拽" inactive-text="关闭拖拽">
    </el-switch>

    <el-button type="success" @click="batchSave" v-if="draggable">批量保存</el-button>
    <el-button type="danger" @click="batchDelete">批量删除</el-button>

    <el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox node-key="catId"
      :default-expanded-keys="expandKey" :draggable="draggable" :allow-drop="allowDrop" @node-drop="handleDrop"
      ref="menuTree">
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">
            Append
          </el-button>
          <el-button v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => remove(node, data)">
            Delete
          </el-button>

          <el-button type="text" size="mini" @click="() => edit(data)">
            edit
          </el-button>
        </span>
      </span>
    </el-tree>

    <el-dialog :title="title" :visible.sync="dialogVisible" width="30%" :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>

      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>


<script>
export default {
  data() {
    return {
      pCid: [],
      draggable: false,
      updateNodes: [],
      maxLevel: 0,
      title: "",
      dialogType: "",
      // 添加的实体类
      category: {
        catId: null,
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        icon: "",
        productUnit: "",
      },
      dialogVisible: false,
      menus: [],
      defaultProps: {
        children: "children",
        label: "name",
      },
      expandKey: [],
    };
  },
  activated() {
    this.getMenus();
  },
  methods: {
    // 获取到当前【列表】
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        console.log("成功获取到菜单数据", data.list);
        this.menus = data.list;
      });
    },

    //  添加 提示框
    append(data) {
      // 清空数据
      this.category.name = "";
      this.category.catId = null;
      this.category.icon = "";
      this.category.productUnit = "";
      this.category.showStatus = 1;
      this.category.sort = 0;
      // 获取到需要的数据
      this.category.parentCid = data.catId;
      this.category.catLevel = data.catLevel * 1 + 1;
      // 打开 "分类信息 " 对话框
      this.dialogVisible = true;
      console.log("打开添加的提示框:", data);
      // 设置对话框类型:add
      this.dialogType = "add";
      // 设置对话框标题:添加分类
      this.title = "添加分类";
    },

    // 修改 提示框
    edit(data) {
      console.log("当前菜单的详情", data);
      // 展示 "分类信息 " 对话框
      this.dialogVisible = true;
      // 设置对话框类型:edit
      this.dialogType = "edit";
      // 设置对话框标题:修改分类
      this.title = "修改分类";
      // 获取到需要展示的数据:
      // 1. 发送请求获取到最新的数据
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get",
        params: this.$http.adornParams({}),
      }).then(({ data }) => {
        console.log("需要回显的数据", data);
        this.category.name = data.category.name;
        this.category.catId = data.category.catId;
        this.category.icon = data.category.icon;
        this.category.productUnit = data.category.productUnit;
        this.category.parentCid = data.category.parentCid;
      });
    },

    // 【删除】
    remove(node, data) {
      console.log("remove", node, data);

      var ids = [data.catId];

      this.$confirm(`此操作将永久删除【${data.name}】菜单, 是否继续?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          // 发送删除后端请求
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false),
          }).then(({ data }) => {
            // 发送成功:
            // 1. 弹出提示框
            this.$message({
              message: "删除成功",
              type: "success",
            });
            // 2. 删除成功以后,再获取菜单
            this.getMenus();
            // 3. 设置需要默认展开的菜单
            this.expandKey = [node.parent.data.catId];
          });
        })
        .catch(() => { });
    },

    // 【添加】
    addCategory() {
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false),
      }).then(({ data }) => {
        // 添加成功:
        // 1. 弹出提示框
        this.$message({
          message: "保存成功",
          type: "success",
        });
        // 2. 关闭对话框
        this.dialogVisible = false;
        // 2. 操作成功以后,再获取菜单
        this.getMenus();
        // 4. 设置需要默认展开的菜单
        this.expandKey = [this.category.parentCid];
      });

      console.log("添加三级分类:", this.category);
    },

    // 【修改】
    editCategory() {
      var { catId, name, icon, productUnit } = this.category;
      var data = {
        catId: catId,
        name: name,
        icon: icon,
        productUnit: productUnit,
      };
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData(data, false),
      }).then(({ data }) => {
        // 1. 弹出提示框
        this.$message({
          message: "修改成功",
          type: "success",
        });
        // 2. 关闭对话框
        this.dialogVisible = false;
        // 2. 操作成功以后,再获取菜单
        this.getMenus();
        // 4. 设置需要默认展开的菜单
        this.expandKey = [this.category.parentCid];
      });
    },

    // 通过不同的对话框类型 转发 不同的请求
    submitData() {
      if (this.dialogType == "add") {
        this.addCategory();
      }
      if (this.dialogType == "edit") {
        this.editCategory();
      }
    },

    // 拖拽树级节点
    allowDrop(draggingNode, dropNode, type) {
      // 1. 被拖动的当前节点以及所在的父节点总层数不能大于3
      // 1) 被拖动的当前节点总层数
      this.countNodeLevel(draggingNode);
      // 2) 当前正在拖动的节点 + 父节点所在的深度不大于3
      let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
      console.log("拖拽树级节点深度", deep);
      if (type == "inner") {
        // console.log(`this.maxLevel: ${this.maxLevel} ; draggingNode.data.catLevel : ${draggingNode.data.catLevel}; dropNode.level : ${dropNode.level}`);
        return deep + dropNode.level <= 3;
      } else {
        return deep + dropNode.parent.level <= 3;
      }
      console.log("拖拽树级节点", draggingNode, dropNode, type);
    },

    // 统计被拖动的当前节点总层数
    countNodeLevel(node) {
      // 找到所有子节点,求出最大深度
      if (node.childNodes != null && node.childNodes.length > 0) {
        for (let i = 0; i < node.childNodes.length; i++) {
          if (node.childNodes[i].level > this.maxLevel) {
            this.maxLevel = node.childNodes[i].level;
          }
          this.countNodeLevel(node.childNodes[i]);
        }
      }
    },

    // 拖拽树级节点成功后的响应事件
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log(
        "拖拽树级节点成功后的响应事件: ",
        draggingNode,
        dropNode,
        dropType
      );

      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 {
        pCid = dropNode.data.catId;
        siblings = dropNode.childNodes;
      }

      this.pCid.push(pCid);

      // 2. 当前节点最新的最新排序
      for (let i = 0; i < siblings.length; i++) {
        if (siblings[i].data.catId == draggingNode.data.catId) {
          // 如果遍历的是当前正在拖拽的结点
          let catLevel = draggingNode.level;
          // 拖拽子节点的等级 != 正在拖拽节点的等级
          if (siblings[i].level != draggingNode.level) {
            // 3. 当前节点的最新层级
            // 当前节点的层级发生变化
            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("updateNodes", this.updateNodes);
    },

    // 修改节点树层级
    updateChildNodeLevel(node) {
      if (node.childNodes.length > 0) {
        for (let i = 0; i < node.childNodes.length; i++) {
          var cNode = node.childNodes[i].data;
          this.updateNodes.push({
            catId: cNode.catId,
            catLevel: node.childNodes[i].level,
          });
          this.updateChildNodeLevel(node.childNodes[i]);
        }
      }
    },

    batchSave() {
      // 4. 发送请求
      this.$http({
        url: this.$http.adornUrl('/product/category/update/sort'),
        method: 'post',
        data: this.$http.adornData(this.updateNodes, false)
      }).then(({ data }) => {
        this.$message({
          message: "修改成功",
          type: "success",
        });
        this.getMenus();
        this.expandKey = this.pCid;
        // 清空数据
        this.updateNodes = [];
        this.maxLevel = 0;
        // this.pCid = 0;
      });
    },

    // 批量删除
    batchDelete() {
      let catIds = [];
      let checkNodes = this.$refs.menuTree.getCheckedNodes();
      console.log("被选中的节点为 ", checkNodes);
      for (let i = 0; i < checkNodes.length; i++) {
        catIds.push(checkNodes[i].catId);
      }
      this.$confirm(`此操作将永久删除菜单, 是否继续?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        this.$http({
          url: this.$http.adornUrl('/product/category/delete'),
          method: 'post',
          data: this.$http.adornData(catIds, false)
        }).then(({ data }) => {
          this.$message({
            message: "批量删除成功",
            type: "success",
          });
          this.getMenus();
        });
      }).catch(() => {

      });
    },
  },
};
</script>

<style lang="scss" scoped></style>

最终实现效果

排序变化

  1. 拖拽实现
  2. 使用开关来表示是否开启拖拽功能

批量删除

相关推荐
一头生产的驴9 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao16 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78719 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野6 小时前
【Java|集合类】list遍历的6种方式
java·python·list
二进制person7 小时前
Java SE--方法的使用
java·开发语言·算法
小阳拱白菜8 小时前
java异常学习
java
FrankYoou9 小时前
Jenkins 与 GitLab CI/CD 的核心对比
java·docker