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

news2024/11/27 9:40:45

目录标题

  • 后端
    • 业务类
    • 实体类
  • 前端
  • 最终实现效果
    • 排序变化
    • 批量删除

后端

业务类


	// 省略其他简单的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;
    }

实体类


-- 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;

前端

<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. 使用开关来表示是否开启拖拽功能
    请添加图片描述

批量删除

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/778157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NOAA官网下载的气象雷达原始数据转化为NC文件详细步骤

一、准备工作 1.先在NOAA官网下载好气象雷达原始数据 NOAA官网下载气象雷达资料详细步骤_珞瑜的博客-CSDN博客 下载好的雷达数据有两种类型Level-2和Level-3。 如上图所示,为气象雷达数据的Level-2产品,站点名字:K

springboot mybatis-plus 多数据源配置(HikariCP)

1.导入依赖jar <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgres…

iOS--虚拟内存

参考文章 要想了解什么是VM Regions&#xff0c;就得先了解什么是虚拟内存。当我们向系统申请内存时&#xff0c;系统并不会给你返回物理内存的地址&#xff0c;而是给你一个虚拟内存地址。每个进程都拥有相同大小的虚拟地址空间&#xff0c;对于32位的进程&#xff0c;可以拥有…

【N32L40X】学习笔记06-串口dma空闲中断+dma接收数据

串口dma 8 个可独立配置的 DMA 通道。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VchCudlf-1689952378831)(./picture/dma.png)] 实例代码 串口dma使用的是串口绑定方式实现串口的dma数据传输 bsp_uart_dma.h #ifndef _BSP_UART_DMA_H_ #def…

STM32(HAL库)驱动(1.44寸)TFT-LCD彩屏

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 屏幕引脚配置 2.3 项目生成 3、KEIL端程序整合 3.1 LCD驱动添加 3.2 函数修改 3.2.1 lcd.h修改 3.2.2 lcd_innit.h 修改 3.2.3 lcd.c修改 3.2.4 lcd_inut.c修改 3.3 主函数代码 3.3…

网络安全(零基础)自学

一、网络安全基础知识 1.计算机基础知识 了解了计算机的硬件、软件、操作系统和网络结构等基础知识&#xff0c;可以帮助您更好地理解网络安全的概念和技术。 2.网络基础知识 了解了网络的结构、协议、服务和安全问题&#xff0c;可以帮助您更好地解决网络安全的原理和技术…

Spring Cloud Alibaba 集成 Skywalking 链路追踪

Spring Cloud Alibaba 集成 Skywalking 链路追踪 简介 skywalking 是一个国产开源框架&#xff0c;2015 年由吴晟开源 &#xff0c; 2017 年加入 Apache 孵化器。skywalking 是分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容器&#xff08;Doc…

前端 | ( 十)HTML5简介及相关新增属性 | 尚硅谷前端html+css零基础教程2023最新

学习来源&#xff1a;尚硅谷前端htmlcss零基础教程&#xff0c;2023最新前端开发html5css3视频 文章目录 &#x1f4da;HTML5简介&#x1f407;什么是HTML5&#x1f407;HTML5 优势&#x1f407;HTML5兼容性 &#x1f4da;新增语义化标签&#x1f407;新增布局标签&#x1f407…

怎样原生制作lis的CentOS容器镜像

本文介绍从一个空白的裸机CentOS自己构造检验允许的docker环境。来达到运行环境的高度定制&#xff0c;而不是只能依赖VS或者微软或者数据库厂商提供的镜像当做基础制作。更容易理解基础原理。最终输出产物为lisnew.tar&#xff0c;一个开箱即用的lis运行环境。 制作的整个过程…

自动驾驶分级和技术架构

标题SAE 和 NHTSA自动驾驶分级 当前全球汽车行业中两个最权威的分级系统由美国国家公路交通安全管理局&#xff08;NHTSA&#xff09;和国际自动化工程师协会(SAE)提出。2013年&#xff0c;NHTSA将驾驶自动化的描述分为5个层级。2014年1月&#xff0c;SAE制定J3016自动驾驶分级…

【深度学习-神经网络架构-通俗易懂的入门课程】

文章目录 深度学习与AI的关系机器学习的流程机器学习的核心以及问题深度学习要解决的问题模型如何搭建&#xff1f;特征如何提取&#xff1f;为什么要深度学习&#xff1f; 深度学习的应用深度学习的问题计算机视觉任务分类与检索如何实现分类 神经网络基础线性函数损失函数防止…

Golang 中的可测试示例函数(Example Function)详解

Golang 可测试示例含函数 (Example Function) 示例函数类似于单元测试函数&#xff0c;但没有 *testing 类型的参数。编写示例函数也是很容易的&#xff1a; 创建对应的测试文件&#xff1a;在 Go 项目的源代码目录下创建一个新的文件&#xff08;和被测代码文件在同一个包&…

Java 知识合集 | 多线程与并发

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

C++初阶之内存分布

C/C内存管理 C/C内存分布C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free1.malloc和free2.calloc3.realloc4.常见的动态内存错误 C内存管理方式1.new/delete操作内置类型2.new和delete操作自定义类型3.new和malloc使用上的区别 operator new与operator delete函…

设计模式: 23重设计模式

设计模式 设计模式概述设计模式的概念设计模式的组成23种设计模式设计模式与软件架构设计模式分类创建型设计模式结构型设计模式行为型设计模式 设计模式概述 在20世纪70年代&#xff0c;Christopher Alexander 提出了城市建筑的模式&#xff0c;他认为&#xff1a;模式是描述…

Windows的内部结构API

Windows内部结构 由于 Windows 机器构成了企业基础设施的大部分&#xff0c;红队需要了解 Windows 的内部结构以及如何&#xff08;滥用&#xff09;使用它们。在制作攻击性工具或漏洞利用时&#xff0c;红队可以滥用这些来帮助规避和利用。 进程 进程维护并代表程序的执行&…

Abaqus 中的步进、增量、迭代和尝试概念 硕迪科技

Abaqus 中的步进、增量、迭代和尝试等可能会在概念上让 Abaqus 初学者感到困惑。清楚地了解分析步骤、荷载增量和迭代之间的区别非常重要。在这篇文章中快速了解 Abaqus 步骤和增量迭代。 在ABAQUS中&#xff0c;步进增量迭代是解决非线性问题的一种数值计算方法。这种方法通常…

【分布式】1、CAP 理论 | 一致性、可用性、分区容忍性

文章目录 一、CAP 理论1.1 Consistency 一致性1.2 Availbility 可用性1.3 Partition Tolerance 分区容忍性1.4 CAP 应用1.4.1 CP1.4.2 AP 二、CAP 实践2.1 ACID2.2 BASE 一、CAP 理论 是 2002 年证明的定理&#xff0c;原文&#xff0c;内容如下&#xff1a; In a distributed…

Day52: 84.柱状图中最大的矩形

84.柱状图中最大的矩形 84. 柱状图中最大的矩形 - 力扣&#xff08;LeetCode&#xff09; 思路 本题是要找每个柱子左右两边第一个小于该柱子的柱子&#xff0c;所以从栈头&#xff08;元素从栈头弹出&#xff09;到栈底的顺序是从大到小的顺序。例&#xff1a; 三种情况&a…

安装 VNC 服务器-iTOPRK3588开发板

开发板联网以后&#xff0c;在串口终端输入以下命令&#xff1a; sudo apt-get update sudo apt-get upgrade sudo apt-get install tightvncserver apt-get install xfonts-base 安装完毕之后&#xff0c;在串口终端输入以下命令运行 vnc tightvncserver 然后要求设置密…