谷粒商城第二篇服务功能-商品服务-三级分类

news2024/12/22 15:25:06

商品服务三级分类工程初始化及查询搭建

在数据库中插入数据
在这里插入图片描述

1.controller类

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

    /**
     * 查出所有分类以及子分类,以树形结构组装起来
     */
    @RequestMapping("/list/tree")
    public R list(){
        List<CategoryEntity> entityList = categoryService.listWithTree();

        return R.ok().put("data", entityList);
    }
}

2.service类

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Override
    public List<CategoryEntity> listWithTree(){
//       1. 查出所有分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
//        2.组装成父子的树形结构
//        查找以及分类
        List<CategoryEntity> level1Menu = categoryEntities.stream().filter((categoryEntity -> {
            return categoryEntity.getParentCid() == 0;
        })).map((menu)->{
            menu.setChildren(getChildrens(menu,categoryEntities));
            return menu;
        }).sorted((menu1,menu2)->{
            return (menu1.getSort()==null?0:menu1.getSort())-(menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());
        return level1Menu;
    }

//    递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){
        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
//            1.找到子菜单
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> {
//            2.菜单的排序
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return children;
    }
}

3.测试

在这里插入图片描述

4.启动renren前端及后端工程,进入工程创建商品目录

在这里插入图片描述

5.在商品系统下创建分类维护菜单

在这里插入图片描述

6.创建category模块并且测试

在这里插入图片描述
在这里插入图片描述

7.编写代码

category.vue

<template>
    <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>
<script>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            data: [],
            defaultProps: {
                children: 'children',
                label: 'label'
            }
        };
    },
    methods: {
        handleNodeClick(data) {
            console.log(data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(data=>{
                console.log("成功获取到菜单数据...",data)
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发
}
</script>
<style scoped></style>

访问页面路径端和端口都不对在这里插入图片描述

8.修改为所有请求调用网关

修改index.js 修改为网关地址,现在前端访问后端renrenfast会报错
在这里插入图片描述

9.将renrenfast加入注册中心中

1.在renren-fast后端工程加入gulimall-common,并且修改springboot版本,以及对应cloud版本

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>io.renren</groupId>
	<artifactId>renren-fast</artifactId>
	<version>3.0.0</version>
	<packaging>jar</packaging>
	<description>renren-fast</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
<!--	<parent>-->
<!--		<groupId>org.springframework.boot</groupId>-->
<!--		<artifactId>spring-boot-starter-parent</artifactId>-->
<!--		<version>2.6.6</version>-->
<!--	</parent>-->

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<mybatisplus.version>3.3.1</mybatisplus.version>
		<mysql.version>8.0.28</mysql.version>
		<mssql.version>4.0</mssql.version>
		<oracle.version>11.2.0.3</oracle.version>
		<druid.version>1.1.13</druid.version>
		<quartz.version>2.3.0</quartz.version>
		<commons.lang.version>2.6</commons.lang.version>
		<commons.fileupload.version>1.2.2</commons.fileupload.version>
		<commons.io.version>2.5</commons.io.version>
		<commons.codec.version>1.10</commons.codec.version>
		<commons.configuration.version>1.10</commons.configuration.version>
		<shiro.version>1.9.0</shiro.version>
		<jwt.version>0.7.0</jwt.version>
		<kaptcha.version>0.0.9</kaptcha.version>
		<qiniu.version>7.2.23</qiniu.version>
		<aliyun.oss.version>2.8.3</aliyun.oss.version>
		<qcloud.cos.version>4.4</qcloud.cos.version>
		<swagger.version>2.7.0</swagger.version>
		<joda.time.version>2.9.9</joda.time.version>
		<gson.version>2.8.5</gson.version>
		<fastjson.version>1.2.79</fastjson.version>
		<hutool.version>4.1.1</hutool.version>
		<lombok.version>1.18.4</lombok.version>

		<!--wagon plugin 配置-->
		<service-path>/work/renren</service-path>
		<pack-name>${project.artifactId}-${project.version}.jar</pack-name>
		<remote-addr>192.168.1.10:22</remote-addr>
		<remote-username>root</remote-username>
		<remote-passwd>123456</remote-passwd>
	</properties>

	<dependencies>
		<dependency>
			<groupId>com.hejiawang.gulimall</groupId>
			<artifactId>gulimall-common</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<!--<dependency>-->
			<!--<groupId>org.springframework.boot</groupId>-->
			<!--<artifactId>spring-boot-devtools</artifactId>-->
			<!--<optional>true</optional>-->
		<!--</dependency>-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>${mybatisplus.version}</version>
			<exclusions>
				<exclusion>
					<groupId>com.baomidou</groupId>
					<artifactId>mybatis-plus-generator</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.version}</version>
		</dependency>
		 <!--oracle驱动-->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>${oracle.version}</version>
		</dependency>
		 <!--mssql驱动-->
		<dependency>
			<groupId>com.microsoft.sqlserver</groupId>
			<artifactId>sqljdbc4</artifactId>
			<version>${mssql.version}</version>
		</dependency>
		 <!--postgresql驱动-->
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>${druid.version}</version>
		</dependency>
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>${quartz.version}</version>
			<exclusions>
				<exclusion>
					<groupId>com.mchange</groupId>
					<artifactId>c3p0</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>${commons.lang.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>${commons.fileupload.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>${commons.io.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
			<version>${commons.codec.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-configuration</groupId>
			<artifactId>commons-configuration</artifactId>
			<version>${commons.configuration.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>${shiro.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>${shiro.version}</version>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>${jwt.version}</version>
		</dependency>
		<dependency>
			<groupId>com.github.axet</groupId>
			<artifactId>kaptcha</artifactId>
			<version>${kaptcha.version}</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>${swagger.version}</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>${swagger.version}</version>
		</dependency>
		<dependency>
			<groupId>com.qiniu</groupId>
			<artifactId>qiniu-java-sdk</artifactId>
			<version>${qiniu.version}</version>
		</dependency>
		<dependency>
			<groupId>com.aliyun.oss</groupId>
			<artifactId>aliyun-sdk-oss</artifactId>
			<version>${aliyun.oss.version}</version>
		</dependency>
		<dependency>
			<groupId>com.qcloud</groupId>
			<artifactId>cos_api</artifactId>
			<version>${qcloud.cos.version}</version>
			<exclusions>
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-log4j12</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>joda-time</groupId>
			<artifactId>joda-time</artifactId>
			<version>${joda.time.version}</version>
		</dependency>
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
			<version>${gson.version}</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>${fastjson.version}</version>
		</dependency>
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>${hutool.version}</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>${lombok.version}</version>
		</dependency>
	</dependencies>

	<build>
		<finalName>${project.artifactId}</finalName>
		<extensions>
			<extension>
				<groupId>org.apache.maven.wagon</groupId>
				<artifactId>wagon-ssh</artifactId>
				<version>2.8</version>
			</extension>
		</extensions>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<fork>true</fork>
				</configuration>
			</plugin>
			<!-- 跳过单元测试 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<configuration>
					<skipTests>true</skipTests>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>wagon-maven-plugin</artifactId>
				<version>1.0</version>
				<configuration>
					<fromFile>target/${pack-name}</fromFile>
					<url><![CDATA[scp://${remote-username}:${remote-passwd}@${remote-addr}${service-path}]]></url>
					<commands>
						<!-- Kill Old Process -->
						<command>kill -9 `ps -ef |grep ${project.artifactId}.jar|grep -v "grep" |awk '{print $2}'`</command>
						<!-- Restart jar package,write result into renren.log -->
						<command><![CDATA[nohup java -jar ${service-path}/${pack-name} --spring.profiles.active=test > ${service-path}/renren.log 2>&1 & ]]></command>
						<command><![CDATA[netstat -nptl]]></command>
						<command><![CDATA[ps -ef | grep java | grep -v grep]]></command>
					</commands>
					<!-- 运行命令 mvn clean package wagon:upload-single wagon:sshexec-->
					<displayCommandOutputs>true</displayCommandOutputs>
				</configuration>
			</plugin>

			<plugin>
				<groupId>com.spotify</groupId>
				<artifactId>docker-maven-plugin</artifactId>
				<version>0.4.14</version>
				<!--<executions>-->
					<!--<execution>-->
						<!--<phase>package</phase>-->
						<!--<goals>-->
							<!--<goal>build</goal>-->
						<!--</goals>-->
					<!--</execution>-->
				<!--</executions>-->
				<configuration>
					<imageName>renren/fast</imageName>
					<dockerDirectory>${project.basedir}</dockerDirectory>
					<resources>
						<resource>
							<targetPath>/</targetPath>
							<directory>${project.build.directory}</directory>
							<include>${project.build.finalName}.jar</include>
						</resource>
					</resources>
				</configuration>
				<!-- 运行命令 mvn clean package docker:build 打包并生成docker镜像 -->
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>public</id>
			<name>aliyun nexus</name>
			<url>https://maven.aliyun.com/repository/public/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>public</id>
			<name>aliyun nexus</name>
			<url>https://maven.aliyun.com/repository/public/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>

</project>

2.启动参数中加上@EnableDiscouveryClient

在这里插入图片描述

3.配置文件加上注册中心配置

在这里插入图片描述

4.启动工程

在这里插入图片描述

10.修改网关工程,配置转发

修改网关配置加上/api即可路由到

spring:
#  datasource:
#    username: root
#    password: 1q1w1e1r
#    url: jdbc:mysql://127.0.0.1:3306/gulimall_sms
#    driver-class-name: com.mysql.cj.jdbc.Driver
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: test_route
          uri: https://www.baidu.com
          predicates:
            - Query=url,baidu
        - id: qq_route
          uri: https://www.qq.com
          predicates:
            - Query=url,qq
#           前端项目,/api
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**

server:
  port: 88

在这里插入图片描述
前端所有请求加上/api
在这里插入图片描述

11.测试并调整网关

访问主页有报错
在这里插入图片描述
因为网关转发的时候,未带工程前缀
http://localhost:88/api/captcha.jpg ====> http://renren-fast:8080/api/captcha.jpg
修改gateway工程加上重写的工程路径

#           前端项目,/api
#        http://localhost:88/api/captcha.jpg http://renren-fast:8080/api/captcha.jpg
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/?(?<segment>.*), /renren-fast/$\{segment}

在这里插入图片描述
重启gateway再次访问页面即可看到验证码
在这里插入图片描述

12.登录跨域问题

1.原因

由于浏览器为http://localhost:8001/#/login,前端中的js访问资源协议域名端口不一样即会有跨域问题
在这里插入图片描述
登录方法为OPTIONS在这里插入图片描述

2.跨域流程

跨域解释
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
在这里插入图片描述

3.解决办法

方法一:通过nginx将浏览器访问地址和请求地址统一

在这里插入图片描述

方法二:后端配置将预检请求允许跨域

在这里插入图片描述

13.解决登录跨域问题

本机调试采取方法二
在网关工程中添加配置类允许跨域

package com.hejiawang.gulimall.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.server.ServerWebExchange;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Configuration
public class GulimallCorsConfiguration {
    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
//        1.配置跨域
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);

    }
}

在这里插入图片描述
将renren-fast后端跨域内容注释掉,使用我们自己配的
在这里插入图片描述
重启服务即可成功登录

14.初始化gulimall-product项目

1.网关配置product对应路由转发

        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/?(?<segment>.*), /$\{segment}

2.在nacos上创建商品服务的命名空间

在这里插入图片描述

3.在nacos product命名空间下创建要管理的配置文件

在这里插入图片描述

4.商品服务客户端增加nacos配置中心相关配置

添加bootstrap文件及配置中心地址

spring.application.name=gulimall-product
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=a7d07264-e472-4466-9b28-e14caee4e0d7

在这里插入图片描述
添加注册发现注解
在这里插入图片描述
添加注册中心地址
在这里插入图片描述
启动服务看到product注册上来了在这里插入图片描述

15.尝试访问

直接访问product可以返回数据
在这里插入图片描述
通过网关访问报错 http://localhost:88/api/product/category/list/tree
在这里插入图片描述
因为网关被拦截,优先顺序,将最模糊的路由放在最后重启
在这里插入图片描述
再次访问网关http://localhost:88/api/product/category/list/tree访问成功
在这里插入图片描述

16.前端工程中配置全路径到gateway,并且配置menu请求路径

在这里插入图片描述

<template>
    <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>
<script>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            data: [],
            defaultProps: {
                children: 'children',
                label: 'label'
            }
        };
    },
    methods: {
        handleNodeClick(data) {
            console.log(data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(data=>{
                console.log("成功获取到菜单数据...",data)
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发
}
</script>
<style scoped></style>

在这里插入图片描述
请求测试返回数据
在这里插入图片描述

17.修改前端

<template>
    <el-tree :data="menus" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>
<script>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            menus: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        };
    },
    methods: {
        handleNodeClick(data) {
            console.log(data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(({data})=>{
                console.log("成功获取到菜单数据...",data.data)
                this.menus = data.data
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发
}
</script>
<style scoped></style>

即可看到菜单显示出来
在这里插入图片描述

商品服务三级分类逻辑删除后端

1.CategoryController类

    /**
     * 删除
     */
    @RequestMapping("/delete")
    public R delete(@RequestBody Long[] catIds){
//        原始方法先注释
//        categoryService.removeByIds(Arrays.asList(catIds));

//        1、检查当前删除的菜单,是否被其他地方引用到

        categoryService.removeMenuByIds(Arrays.asList(catIds));

        return R.ok();
    }

2.CategoryServiceImpl类

    @Override
    public void removeMenuByIds(List<Long> asList) {
//        TODO: 检查当前删除的菜单是否被引用,
        baseMapper.deleteBatchIds(asList);
    }

3.CategoryEntity类对对应字段加上@TableLogic

package com.hejiawang.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import lombok.Data;

/**
 * 商品三级分类
 * 
 * @author hejiawang
 * @email hejiawangs@gmail.com
 * @date 2023-06-13 14:42:51
 */
@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 分类id
	 */
	@TableId
	private Long catId;
	/**
	 * 分类名称
	 */
	private String name;
	/**
	 * 父分类id
	 */
	private Long parentCid;
	/**
	 * 层级
	 */
	private Integer catLevel;
	/**
	 * 是否显示[0-不显示,1显示]
	 */
	@TableLogic(value = "1",delval = "0")
	private Integer showStatus;
	/**
	 * 排序
	 */
	private Integer sort;
	/**
	 * 图标地址
	 */
	private String icon;
	/**
	 * 计量单位
	 */
	private String productUnit;
	/**
	 * 商品数量
	 */
	private Integer productCount;
	@TableField(exist = false)
	private List<CategoryEntity> children;

}

4.配置文件加上全局配置及

# MapperScan
# sql映射文件位置
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
#使用的地方相反可使用@TableLogic,全局默认配置
      logic-delete-value: 1
      logic-not-delete-value: 0
logging:
  level:
    com.hejiawang.gulimall: debug

5.测试接口

查询初始化数据
在这里插入图片描述
调用接口
在这里插入图片描述
结果
在这里插入图片描述
打印sql过程为update
在这里插入图片描述

6.前端修改

1.新增代码模板片段 vue.code,将模板粘贴进去

在这里插入图片描述

{
    "Print to console": {
        "prefix": "vue",
        "body": [
            "<template>",
            "<div class='$2'>$5</div>",
            "</template>",
            "",
            "<script>",
            "//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)",
            "//例如:import 《组件名称》 from '《组件路径》';",
            "",
            "export default {",
            "//import引入的组件需要注入到对象中才能使用",
            "components: {},",
            "data() {",
            "//这里存放数据",
            "return {",
            "",
            "};",
            "},",
            "//监听属性 类似于data概念",
            "computed: {},",
            "//监控data中的数据变化",
            "watch: {},",
            "//方法集合",
            "methods: {",
            "",
            "},",
            "//生命周期 - 创建完成(可以访问当前this实例)",
            "created() {",
            "",
            "},",
            "//生命周期 - 挂载完成(可以访问DOM元素)",
            "mounted() {",
            "",
            "},",
            "beforeCreate() {}, //生命周期 - 创建之前",
            "beforeMount() {}, //生命周期 - 挂载之前",
            "beforeUpdate() {}, //生命周期 - 更新之前",
            "updated() {}, //生命周期 - 更新之后",
            "beforeDestroy() {}, //生命周期 - 销毁之前",
            "destroyed() {}, //生命周期 - 销毁完成",
            "activated() {} //如果页面有keep-alive缓存功能,这个函数会触发",
            "}",
            "</script>",
            "<style scoped>",
            "$4",
            "</style>"
        ],
        "description": "生成vue模板"
    },
    "http-get请求": {
        "prefix": "httpget",
        "body": [
            "this.\\$http({",
            "url: this.\\$http.adornUrl(''),",
            "method: 'get',",
            "params: this.\\$http.adornParams({})",
            "}).then(({ data }) => {",
            "})"
        ],
        "description": "httpGET请求"
    },
    "http-post请求": {
        "prefix": "httppost",
        "body": [
            "this.\\$http({",
            "url: this.\\$http.adornUrl(''),",
            "method: 'post',",
            "data: this.\\$http.adornData(data, false)",
            "}).then(({ data }) => { });"
        ],
        "description": "httpPOST请求"
    }
}

2.修改前端删除后展开父菜单并且刷新前端

<template>
    <el-tree :data="menus" :props="defaultProps" :default-expanded-keys="expandedKey" node-key="catId" show-checkbox :expand-on-click-node="false">
        <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>
            </span>
        </span>
    </el-tree>
</template>
<script>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            expandedKey: [],
            menus: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        };
    },
    methods: {
        append(data) {
            console.log("append", data);
        },

        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 }) => {
                    console.log("删除成功");
                    this.$message({
                        message: '菜单删除成功',
                        type: 'success'
                    });

                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                    this.expandedKey= [node.parent.data.catId]
                    // if (data && data.code === 0) {
                    //     this.$message({
                    //         message: '操作成功',
                    //         type: 'success',
                    //         duration: 1500,
                    //         onClose: () => {
                    //             this.getDataList()
                    //         }
                    //     })
                    // } else {
                    //     this.$message.error(data.msg)
                    // }
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });

            console.log("remove", node, data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(({ data }) => {
                console.log("成功获取到菜单数据...", data.data)
                this.menus = data.data
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发

}
</script>
<style scoped></style>

在这里插入图片描述

商品服务三级分类添加

前端修改

<template>
    <div>
        <el-tree :data="menus" :props="defaultProps" :default-expanded-keys="expandedKey" node-key="catId" show-checkbox
            :expand-on-click-node="false">
            <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>
                </span>
            </span>
        </el-tree>
        <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
            <el-form :model="category">
                <el-form-item label="分类名称">
                    <el-input v-model="category.name" 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="addCategory">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>
<script>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0 },
            dialogVisible: false,
            expandedKey: [],
            menus: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        };
    },
    methods: {
        // 添加三级分类的方法
        addCategory() {
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/save'),
                method: 'post',
                data: this.$http.adornData(this.category, false)
            }).then(({ data }) => { 
                console.log("菜单保存成功");
                    this.$message({
                        message: '菜单保存成功',
                        type: 'success'
                    });
//                  关闭对话框
                    this.dialogVisible=false;
                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                    this.expandedKey = [this.category.parentCid];
            });
        },
        append(data) {
            console.log("append", data);
            this.dialogVisible = true;
            this.category.parentCid = data.catId;
            this.category.catLevel = data.catLevel * 1 + 1;
            this.category.showStatus = 1;
            this.category.sort = 0;
        },

        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 }) => {
                    console.log("删除成功");
                    this.$message({
                        message: '菜单删除成功',
                        type: 'success'
                    });

                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                    this.expandedKey = [node.parent.data.catId]
                    // if (data && data.code === 0) {
                    //     this.$message({
                    //         message: '操作成功',
                    //         type: 'success',
                    //         duration: 1500,
                    //         onClose: () => {
                    //             this.getDataList()
                    //         }
                    //     })
                    // } else {
                    //     this.$message.error(data.msg)
                    // }
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });

            console.log("remove", node, data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(({ data }) => {
                console.log("成功获取到菜单数据...", data.data)
                this.menus = data.data
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发

}
</script>
<style scoped></style>

测试
在这里插入图片描述

商品服务三级分类修改

前端

<template>
    <div>
        <el-tree :data="menus" :props="defaultProps" :default-expanded-keys="expandedKey" node-key="catId" show-checkbox
            :expand-on-click-node="false">
            <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 type="text" 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="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>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            title: "",
            dialogType: "", //edit,add
            category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0, catId: null, icon: "", productUnit: "" },
            dialogVisible: false,
            expandedKey: [],
            menus: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        };
    },
    methods: {
        // 添加三级分类的方法
        addCategory() {
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/save'),
                method: 'post',
                data: this.$http.adornData(this.category, false)
            }).then(({ data }) => {
                console.log("菜单保存成功");
                this.$message({
                    message: '菜单保存成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        // 修改三级分类的方法
        editCategory() {
            var {catId,name,icon,productUnit} = this.category;
            var data = {catId,name,icon,productUnit}
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/update'),
                method: 'post',
                data: this.$http.adornData(data, false)
            }).then(({ data }) => {
                console.log("菜单修改成功");
                this.$message({
                    message: '菜单修改成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        submitData() {
            if (this.dialogType == "add") {
                this.addCategory();
            }
            if (this.dialogType == "edit") {
                this.editCategory();
            }
        },
        edit(data) {
            console.log("要修改的数据", data);
            this.title = "修改分类";
            this.dialogType = "edit";
            this.dialogVisible = true;
            // 发送请求获取节点最新的数据
            this.$http({
                url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                method: 'get',
            }).then(({ data }) => {
                console.log("要回显的数据", data);
                this.category.name = data.data.name;
                this.category.catId = data.data.catId;
                this.category.icon = data.data.icon;
                this.category.productUnit = data.data.productUnit;
                this.category.parentCid = data.data.parentCid;
                this.category.catLevel = data.data.catLevel;
                this.category.sort = data.data.sort;
                this.category.showStatus = data.data.showStatus;
            })
        },
        append(data) {
            console.log("append", data);
            this.title = "添加分类";
            this.dialogType = "add";
            this.dialogVisible = true;
            this.category.parentCid = data.catId;
            this.category.catLevel = data.catLevel * 1 + 1;
            this.category.showStatus = 1;
            this.category.sort = 0;
            this.category.name = "";
            this.category.catId = null;
            this.category.icon = "";
            this.category.productUnit = "";
        },
        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 }) => {
                    console.log("删除成功");
                    this.$message({
                        message: '菜单删除成功',
                        type: 'success'
                    });

                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                    this.expandedKey = [node.parent.data.catId]
                    // if (data && data.code === 0) {
                    //     this.$message({
                    //         message: '操作成功',
                    //         type: 'success',
                    //         duration: 1500,
                    //         onClose: () => {
                    //             this.getDataList()
                    //         }
                    //     })
                    // } else {
                    //     this.$message.error(data.msg)
                    // }
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });

            console.log("remove", node, data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(({ data }) => {
                console.log("成功获取到菜单数据...", data.data)
                this.menus = data.data
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发

}
</script>
<style scoped></style>

拖拽节点

1.允许拖拽以及拖拽条件页面效果

<template>
    <div>
        <el-tree :data="menus" :props="defaultProps" :default-expanded-keys="expandedKey" node-key="catId" show-checkbox
            draggable :allow-drop="allowDrop" :expand-on-click-node="false">
            <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 type="text" 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="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>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            maxLevel: 0,
            title: "",
            dialogType: "", //edit,add
            category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0, catId: null, icon: "", productUnit: "" },
            dialogVisible: false,
            expandedKey: [],
            menus: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        };
    },
    methods: {
        allowDrop(draggingNode, dropNode, type) {
            //              1.被拖动的当前节点以及所在的父节点总层数不能大于3
            //          被拖动的当前节点的总层数
            console.log("allowDrop:", draggingNode, dropNode, type)
            this.countNodeLevel(draggingNode.data);
            // 当前正在拖动的节点+父节点所在的深度不大于3即可
            let deep = (this.maxLevel - draggingNode.parent.level);
            console.log("深度:",deep);
            console.log(this.maxLevel);
            if(type == "inner"){
                return deep + dropNode.data.catLevel <=3
            }else{
                // 前后
                return deep + dropNode.parent.level <=3
            }
            // return ;
        },
        countNodeLevel(node){
            // 找到所有子节点,求出最大深度
            if(node.children != null && node.children.length >0){
                // 有子节点则遍历
                for(let i = 0 ;i < node.children.length;i++){
                    if(node.children[i].catLevel > this.maxLevel){
                        this.maxLevel = node.children[i].catLevel;
                    }
                    this.countNodeLevel(node.children[i]);
                }
            }
        },
        // 添加三级分类的方法
        addCategory() {
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/save'),
                method: 'post',
                data: this.$http.adornData(this.category, false)
            }).then(({ data }) => {
                console.log("菜单保存成功");
                this.$message({
                    message: '菜单保存成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        // 修改三级分类的方法
        editCategory() {
            var { catId, name, icon, productUnit } = this.category;
            var data = { catId, name, icon, productUnit }
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/update'),
                method: 'post',
                data: this.$http.adornData(data, false)
            }).then(({ data }) => {
                console.log("菜单修改成功");
                this.$message({
                    message: '菜单修改成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        submitData() {
            if (this.dialogType == "add") {
                this.addCategory();
            }
            if (this.dialogType == "edit") {
                this.editCategory();
            }
        },
        edit(data) {
            console.log("要修改的数据", data);
            this.title = "修改分类";
            this.dialogType = "edit";
            this.dialogVisible = true;
            // 发送请求获取节点最新的数据
            this.$http({
                url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                method: 'get',
            }).then(({ data }) => {
                console.log("要回显的数据", data);
                this.category.name = data.data.name;
                this.category.catId = data.data.catId;
                this.category.icon = data.data.icon;
                this.category.productUnit = data.data.productUnit;
                this.category.parentCid = data.data.parentCid;
                this.category.catLevel = data.data.catLevel;
                this.category.sort = data.data.sort;
                this.category.showStatus = data.data.showStatus;
            })
        },
        append(data) {
            console.log("append", data);
            this.title = "添加分类";
            this.dialogType = "add";
            this.dialogVisible = true;
            this.category.parentCid = data.catId;
            this.category.catLevel = data.catLevel * 1 + 1;
            this.category.showStatus = 1;
            this.category.sort = 0;
            this.category.name = "";
            this.category.catId = null;
            this.category.icon = "";
            this.category.productUnit = "";
        },
        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 }) => {
                    console.log("删除成功");
                    this.$message({
                        message: '菜单删除成功',
                        type: 'success'
                    });

                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                    this.expandedKey = [node.parent.data.catId]
                    // if (data && data.code === 0) {
                    //     this.$message({
                    //         message: '操作成功',
                    //         type: 'success',
                    //         duration: 1500,
                    //         onClose: () => {
                    //             this.getDataList()
                    //         }
                    //     })
                    // } else {
                    //     this.$message.error(data.msg)
                    // }
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });

            console.log("remove", node, data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(({ data }) => {
                console.log("成功获取到菜单数据...", data.data)
                this.menus = data.data
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发

}
</script>
<style scoped></style>

2.拖拽节点保存数据库

1.前端代码

<template>
    <div>
        <el-tree :data="menus" :props="defaultProps" :default-expanded-keys="expandedKey" node-key="catId" show-checkbox
            @node-drop="handleDrop" draggable :allow-drop="allowDrop" :expand-on-click-node="false">
            <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 type="text" 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="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>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            updateNodes: [],
            maxLevel: 0,
            title: "",
            dialogType: "", //edit,add
            category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0, catId: null, icon: "", productUnit: "" },
            dialogVisible: false,
            expandedKey: [],
            menus: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        };
    },
    methods: {
        handleDrop(draggingNode, dropNode, dropType, ev) {
            console.log('handleDrop: ', draggingNode, dropNode, dropType, ev);
            // 1.当前节点最新的父节点id
            var pCid = 0;
            let sibilings = null;
            if (dropType == "before" || dropType == "after") {
                pCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId;
                sibilings = dropNode.parent.childNodes;
            } else {
                pCid = dropNode.data.catId;
                sibilings = dropNode.childNodes;
            }
            // 2.当前拖拽节点的最新顺序
            for (let i = 0; i < sibilings.length; i++) {
                if (sibilings[i].data.catId == draggingNode.data.catId) {
                    // 如果遍历是当前正在拖拽的节点
                    let catLevel = draggingNode.level
                    if (sibilings[i].level == draggingNode.level) {
                        //  当前拖拽节点的层级有变化
                        catLevel = sibilings[i].level;
                        // 递归修改子节点的层级
                        this.updateChildNodeLevel(sibilings[i]);
                    }
                    this.updateNodes.push({ catId: sibilings[i].data.catId, sort: i, parentCid: pCid, catLevel: catLevel });
                } else {
                    this.updateNodes.push({ catId: sibilings[i].data.catId, sort: i })
                }
            }
            // 3.当前拖拽节点的最新层级级
            console.log("updateNodes:", this.updateNodes);
            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.expandedKey = [pCid];
                this.updateNodes = [];
                this.maxLevel = 0;

            });

        },
        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]);
                }
            }

        },
        allowDrop(draggingNode, dropNode, type) {
            //              1.被拖动的当前节点以及所在的父节点总层数不能大于3
            //          被拖动的当前节点的总层数
            console.log("allowDrop:", draggingNode, dropNode, type)
            this.countNodeLevel(draggingNode.data);
            // 当前正在拖动的节点+父节点所在的深度不大于3即可
            let deep = (this.maxLevel - draggingNode.parent.level);
            console.log("深度:", deep);
            console.log(this.maxLevel);
            if (type == "inner") {
                return deep + dropNode.data.catLevel <= 3
            } else {
                // 前后
                return deep + dropNode.parent.level <= 3
            }
            // return ;
        },
        countNodeLevel(node) {
            // 找到所有子节点,求出最大深度
            if (node.children != null && node.children.length > 0) {
                // 有子节点则遍历
                for (let i = 0; i < node.children.length; i++) {
                    if (node.children[i].catLevel > this.maxLevel) {
                        this.maxLevel = node.children[i].catLevel;
                    }
                    this.countNodeLevel(node.children[i]);
                }
            }
        },
        // 添加三级分类的方法
        addCategory() {
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/save'),
                method: 'post',
                data: this.$http.adornData(this.category, false)
            }).then(({ data }) => {
                console.log("菜单保存成功");
                this.$message({
                    message: '菜单保存成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        // 修改三级分类的方法
        editCategory() {
            var { catId, name, icon, productUnit } = this.category;
            var data = { catId, name, icon, productUnit }
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/update'),
                method: 'post',
                data: this.$http.adornData(data, false)
            }).then(({ data }) => {
                console.log("菜单修改成功");
                this.$message({
                    message: '菜单修改成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        submitData() {
            if (this.dialogType == "add") {
                this.addCategory();
            }
            if (this.dialogType == "edit") {
                this.editCategory();
            }
        },
        edit(data) {
            console.log("要修改的数据", data);
            this.title = "修改分类";
            this.dialogType = "edit";
            this.dialogVisible = true;
            // 发送请求获取节点最新的数据
            this.$http({
                url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                method: 'get',
            }).then(({ data }) => {
                console.log("要回显的数据", data);
                this.category.name = data.data.name;
                this.category.catId = data.data.catId;
                this.category.icon = data.data.icon;
                this.category.productUnit = data.data.productUnit;
                this.category.parentCid = data.data.parentCid;
                this.category.catLevel = data.data.catLevel;
                this.category.sort = data.data.sort;
                this.category.showStatus = data.data.showStatus;
            })
        },
        append(data) {
            console.log("append", data);
            this.title = "添加分类";
            this.dialogType = "add";
            this.dialogVisible = true;
            this.category.parentCid = data.catId;
            this.category.catLevel = data.catLevel * 1 + 1;
            this.category.showStatus = 1;
            this.category.sort = 0;
            this.category.name = "";
            this.category.catId = null;
            this.category.icon = "";
            this.category.productUnit = "";
        },
        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 }) => {
                    console.log("删除成功");
                    this.$message({
                        message: '菜单删除成功',
                        type: 'success'
                    });

                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                    this.expandedKey = [node.parent.data.catId]
                    // if (data && data.code === 0) {
                    //     this.$message({
                    //         message: '操作成功',
                    //         type: 'success',
                    //         duration: 1500,
                    //         onClose: () => {
                    //             this.getDataList()
                    //         }
                    //     })
                    // } else {
                    //     this.$message.error(data.msg)
                    // }
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });

            console.log("remove", node, data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(({ data }) => {
                console.log("成功获取到菜单数据...", data.data)
                this.menus = data.data
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发

}
</script>
<style scoped></style>

2.后端代码新增批量保存

    /**
     * 修改
     */
    @RequestMapping("/update/sort")
    public R update(@RequestBody CategoryEntity[] category){
        categoryService.updateBatchById(Arrays.asList(category));
        return R.ok();
    }

3.批量保存拖拽节点保存数据库优化加上拖拽按钮

<template>
    <div>
        <el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽">
        </el-switch>
        <el-button v-if="draggable" @click="batchSave">批量保存</el-button>
        <el-tree :data="menus" :props="defaultProps" :default-expanded-keys="expandedKey" node-key="catId" show-checkbox
            @node-drop="handleDrop" :draggable="draggable" :allow-drop="allowDrop" :expand-on-click-node="false">
            <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 type="text" 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="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>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            pCid: [],
            draggable: false,
            updateNodes: [],
            maxLevel: 0,
            title: "",
            dialogType: "", //edit,add
            category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0, catId: null, icon: "", productUnit: "" },
            dialogVisible: false,
            expandedKey: [],
            menus: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        };
    },
    methods: {
        batchSave() {
            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.expandedKey = this.pCid;
                this.updateNodes = [];
                this.maxLevel = 0;
                this.pCid = [];
            });
        },
        handleDrop(draggingNode, dropNode, dropType, ev) {
            console.log('handleDrop: ', draggingNode, dropNode, dropType, ev);
            // 1.当前节点最新的父节点id
            var pCid = 0;
            let sibilings = null;
            if (dropType == "before" || dropType == "after") {
                pCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId;
                sibilings = dropNode.parent.childNodes;
            } else {
                pCid = dropNode.data.catId;
                sibilings = dropNode.childNodes;
            }
            // 2.当前拖拽节点的最新顺序
            for (let i = 0; i < sibilings.length; i++) {
                if (sibilings[i].data.catId == draggingNode.data.catId) {
                    // 如果遍历是当前正在拖拽的节点
                    let catLevel = draggingNode.level
                    if (sibilings[i].level == draggingNode.level) {
                        //  当前拖拽节点的层级有变化
                        catLevel = sibilings[i].level;
                        // 递归修改子节点的层级
                        this.updateChildNodeLevel(sibilings[i]);
                    }
                    this.updateNodes.push({ catId: sibilings[i].data.catId, sort: i, parentCid: pCid, catLevel: catLevel });
                } else {
                    this.updateNodes.push({ catId: sibilings[i].data.catId, sort: i })
                }
            }
            // 3.当前拖拽节点的最新层级级
            console.log("updateNodes:", this.updateNodes);
            this.pCid.push(pCid);
        },
        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]);
                }
            }

        },
        allowDrop(draggingNode, dropNode, type) {
            //   1.被拖动的当前节点以及所在的父节点总层数不能大于3
            //   被拖动的当前节点的总层数
            console.log("allowDrop:", draggingNode, dropNode, type)
            this.countNodeLevel(draggingNode.data);
            // 当前正在拖动的节点+父节点所在的深度不大于3即可
            let deep = (this.maxLevel - draggingNode.parent.level);
            console.log("深度:", deep);
            console.log(this.maxLevel);
            if (type == "inner") {
                return deep + dropNode.data.catLevel <= 3
            } else {
                // 前后
                return deep + dropNode.parent.level <= 3
            }
            // return ;
        },
        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]);
                }
            }
        },
        // 添加三级分类的方法
        addCategory() {
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/save'),
                method: 'post',
                data: this.$http.adornData(this.category, false)
            }).then(({ data }) => {
                console.log("菜单保存成功");
                this.$message({
                    message: '菜单保存成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        // 修改三级分类的方法
        editCategory() {
            var { catId, name, icon, productUnit } = this.category;
            var data = { catId, name, icon, productUnit }
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/update'),
                method: 'post',
                data: this.$http.adornData(data, false)
            }).then(({ data }) => {
                console.log("菜单修改成功");
                this.$message({
                    message: '菜单修改成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        submitData() {
            if (this.dialogType == "add") {
                this.addCategory();
            }
            if (this.dialogType == "edit") {
                this.editCategory();
            }
        },
        edit(data) {
            console.log("要修改的数据", data);
            this.title = "修改分类";
            this.dialogType = "edit";
            this.dialogVisible = true;
            // 发送请求获取节点最新的数据
            this.$http({
                url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                method: 'get',
            }).then(({ data }) => {
                console.log("要回显的数据", data);
                this.category.name = data.data.name;
                this.category.catId = data.data.catId;
                this.category.icon = data.data.icon;
                this.category.productUnit = data.data.productUnit;
                this.category.parentCid = data.data.parentCid;
                this.category.catLevel = data.data.catLevel;
                this.category.sort = data.data.sort;
                this.category.showStatus = data.data.showStatus;
            })
        },
        append(data) {
            console.log("append", data);
            this.title = "添加分类";
            this.dialogType = "add";
            this.dialogVisible = true;
            this.category.parentCid = data.catId;
            this.category.catLevel = data.catLevel * 1 + 1;
            this.category.showStatus = 1;
            this.category.sort = 0;
            this.category.name = "";
            this.category.catId = null;
            this.category.icon = "";
            this.category.productUnit = "";
        },
        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 }) => {
                    console.log("删除成功");
                    this.$message({
                        message: '菜单删除成功',
                        type: 'success'
                    });

                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                    this.expandedKey = [node.parent.data.catId]
                    // if (data && data.code === 0) {
                    //     this.$message({
                    //         message: '操作成功',
                    //         type: 'success',
                    //         duration: 1500,
                    //         onClose: () => {
                    //             this.getDataList()
                    //         }
                    //     })
                    // } else {
                    //     this.$message.error(data.msg)
                    // }
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });

            console.log("remove", node, data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(({ data }) => {
                console.log("成功获取到菜单数据...", data.data)
                this.menus = data.data
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发

}
</script>
<style scoped></style>

4.批量删除

前端

<template>
    <div>
        <el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽">
        </el-switch>
        <el-button v-if="draggable" @click="batchSave">批量保存</el-button>
        <el-button type="danger" @click="batchDelete">批量删除</el-button>
        <el-tree :data="menus" :props="defaultProps" :default-expanded-keys="expandedKey" node-key="catId" show-checkbox
            @node-drop="handleDrop" :draggable="draggable" :allow-drop="allowDrop" :expand-on-click-node="false"
            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 type="text" 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="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>
// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
// 例如:import 《组件名称》 from '《组件路径》';

export default {
    // import引入的组件需要注入到对象中才能使用
    components: {},
    data() {
        return {
            pCid: [],
            draggable: false,
            updateNodes: [],
            maxLevel: 0,
            title: "",
            dialogType: "", //edit,add
            category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0, catId: null, icon: "", productUnit: "" },
            dialogVisible: false,
            expandedKey: [],
            menus: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        };
    },
    methods: {
        batchDelete() {
            let catIds = []
            let checkedNodes = this.$refs.menuTree.getCheckedNodes();
            console.log("被选中的元素", checkedNodes);
            for (let i = 0; i < checkedNodes.length; i++) {
                catIds.push(checkedNodes[i].catId);
            }
            this.$confirm(`是否批量删除【${catIds}】菜单?`, '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {
                this.$http({
                    url: this.$http.adornUrl('/product/category/delete'),
                    method: 'post',
                    data: this.$http.adornData(catIds, false)
                }).then(({ data }) => {
                    console.log("删除成功");
                    this.$message({
                        message: '菜单批量删除成功',
                        type: 'success'
                    });

                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });
        },
        batchSave() {
            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.expandedKey = this.pCid;
                this.updateNodes = [];
                this.maxLevel = 0;
                this.pCid = [];
            });
        },
        handleDrop(draggingNode, dropNode, dropType, ev) {
            console.log('handleDrop: ', draggingNode, dropNode, dropType, ev);
            // 1.当前节点最新的父节点id
            var pCid = 0;
            let sibilings = null;
            if (dropType == "before" || dropType == "after") {
                pCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId;
                sibilings = dropNode.parent.childNodes;
            } else {
                pCid = dropNode.data.catId;
                sibilings = dropNode.childNodes;
            }
            // 2.当前拖拽节点的最新顺序
            for (let i = 0; i < sibilings.length; i++) {
                if (sibilings[i].data.catId == draggingNode.data.catId) {
                    // 如果遍历是当前正在拖拽的节点
                    let catLevel = draggingNode.level
                    if (sibilings[i].level == draggingNode.level) {
                        //  当前拖拽节点的层级有变化
                        catLevel = sibilings[i].level;
                        // 递归修改子节点的层级
                        this.updateChildNodeLevel(sibilings[i]);
                    }
                    this.updateNodes.push({ catId: sibilings[i].data.catId, sort: i, parentCid: pCid, catLevel: catLevel });
                } else {
                    this.updateNodes.push({ catId: sibilings[i].data.catId, sort: i })
                }
            }
            // 3.当前拖拽节点的最新层级级
            console.log("updateNodes:", this.updateNodes);
            this.pCid.push(pCid);
        },
        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]);
                }
            }

        },
        allowDrop(draggingNode, dropNode, type) {
            //   1.被拖动的当前节点以及所在的父节点总层数不能大于3
            //   被拖动的当前节点的总层数
            console.log("allowDrop:", draggingNode, dropNode, type)
            this.countNodeLevel(draggingNode.data);
            // 当前正在拖动的节点+父节点所在的深度不大于3即可
            let deep = (this.maxLevel - draggingNode.parent.level);
            console.log("深度:", deep);
            console.log(this.maxLevel);
            if (type == "inner") {
                return deep + dropNode.data.catLevel <= 3
            } else {
                // 前后
                return deep + dropNode.parent.level <= 3
            }
            // return ;
        },
        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]);
                }
            }
        },
        // 添加三级分类的方法
        addCategory() {
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/save'),
                method: 'post',
                data: this.$http.adornData(this.category, false)
            }).then(({ data }) => {
                console.log("菜单保存成功");
                this.$message({
                    message: '菜单保存成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        // 修改三级分类的方法
        editCategory() {
            var { catId, name, icon, productUnit } = this.category;
            var data = { catId, name, icon, productUnit }
            console.log("三级分类的对象", this.category);
            this.$http({
                url: this.$http.adornUrl('/product/category/update'),
                method: 'post',
                data: this.$http.adornData(data, false)
            }).then(({ data }) => {
                console.log("菜单修改成功");
                this.$message({
                    message: '菜单修改成功',
                    type: 'success'
                });
                //                  关闭对话框
                this.dialogVisible = false;
                // 刷新出新的菜单
                this.getMenus()
                // 设置删除后默认要展示的父菜单
                this.expandedKey = [this.category.parentCid];
            });
        },
        submitData() {
            if (this.dialogType == "add") {
                this.addCategory();
            }
            if (this.dialogType == "edit") {
                this.editCategory();
            }
        },
        edit(data) {
            console.log("要修改的数据", data);
            this.title = "修改分类";
            this.dialogType = "edit";
            this.dialogVisible = true;
            // 发送请求获取节点最新的数据
            this.$http({
                url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                method: 'get',
            }).then(({ data }) => {
                console.log("要回显的数据", data);
                this.category.name = data.data.name;
                this.category.catId = data.data.catId;
                this.category.icon = data.data.icon;
                this.category.productUnit = data.data.productUnit;
                this.category.parentCid = data.data.parentCid;
                this.category.catLevel = data.data.catLevel;
                this.category.sort = data.data.sort;
                this.category.showStatus = data.data.showStatus;
            })
        },
        append(data) {
            console.log("append", data);
            this.title = "添加分类";
            this.dialogType = "add";
            this.dialogVisible = true;
            this.category.parentCid = data.catId;
            this.category.catLevel = data.catLevel * 1 + 1;
            this.category.showStatus = 1;
            this.category.sort = 0;
            this.category.name = "";
            this.category.catId = null;
            this.category.icon = "";
            this.category.productUnit = "";
        },
        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 }) => {
                    console.log("删除成功");
                    this.$message({
                        message: '菜单删除成功',
                        type: 'success'
                    });

                    // 刷新出新的菜单
                    this.getMenus()
                    // 设置删除后默认要展示的父菜单
                    this.expandedKey = [node.parent.data.catId]
                    // if (data && data.code === 0) {
                    //     this.$message({
                    //         message: '操作成功',
                    //         type: 'success',
                    //         duration: 1500,
                    //         onClose: () => {
                    //             this.getDataList()
                    //         }
                    //     })
                    // } else {
                    //     this.$message.error(data.msg)
                    // }
                })
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });

            console.log("remove", node, data);
        },
        getMenus() {
            this.dataListLoading = true
            this.$http({
                url: this.$http.adornUrl('/product/category/list/tree'),
                method: 'get'
            }).then(({ data }) => {
                console.log("成功获取到菜单数据...", data.data)
                this.menus = data.data
            })
        }
    },
    // 监听属性 类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 生命周期 - 创建完成(可以访问当前this实例)
    created() {
        this.getMenus()
    },
    // 生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {

    },
    beforeCreate() { }, // 生命周期 - 创建之前
    beforeMount() { }, // 生命周期 - 挂载之前
    beforeUpdate() { }, // 生命周期 - 更新之前
    updated() { }, // 生命周期 - 更新之后
    beforeDestroy() { }, // 生命周期 - 销毁之前
    destroyed() { }, // 生命周期 - 销毁完成
    activated() { } // 如果页面有keep-alive缓存功能,这个函数会触发

}
</script>
<style scoped></style>

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

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

相关文章

前端Vue自定义可自由滚动新闻栏tabs选项卡标签栏标题栏组件

随着技术的发展&#xff0c;开发的复杂度也越来越高&#xff0c;传统开发方式将一个系统做成了整块应用&#xff0c;经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改&#xff0c;造成牵一发而动全身。 通过组件化开发&#xff0c;可以有效实现…

vue3.0之组合API有哪些(详解)

vue3.0之组合API有哪些 一、setup函数二、生命周期三、reactive函数四、toRef函数五、toRefs函数六、ref函数七、知识运用案例八、computed函数九、watch函数十、ref属性十一、父子通讯1.父传子2.子传父 十二、依赖注入十三、补充 v-model语法糖(简写)十四、补充 mixins语法 一…

【档案专题】三、电子档案管理系统

导读&#xff1a;主要针对电子档案管理系统相关内容介绍。对从事电子档案管理信息化的职业而言&#xff0c;不断夯实电子档案管理相关理论基础是十分重要。只有通过不断梳理相关知识体系和在实际工作当中应用实践&#xff0c;才能走出一条专业化加职业化的道路&#xff0c;从而…

el-cascader级联选择器那些事

el-cascader级联选择器那些事 1、获取选中的节点及其所有上级 vue3element-plusts 1、获取选中的节点及其所有上级 使用cascader组件提供的getCheckedNodes() <el-cascader :options"options" :show-all-levels"false" change"changeCascader&q…

cocosCreator笔记 之Spine了解

版本&#xff1a; 3.4.0 参考&#xff1a; Spine 骨骼动画资源 Spine Skeleton组件 cocosLua 之 骨骼动画 简介 使用spine动画&#xff0c;cocosCreator目前支持的版本&#xff1a; creator版本spine版本V3.0 及以上v3.8&#xff08;原生平台不支持特定版本 v3.8.75&…

北京银行发放门头沟区首笔知识产权质押贷款

6月&#xff0c;位于北京中关村门头沟科技园、专注于研制工业母机的民营企业——北京精雕科技集团有限公司&#xff08;以下简称“精雕科技集团”&#xff09;&#xff0c;因生产经营急需资金&#xff0c;但是由于缺乏抵押物而陷入了融资困境。“精雕科技集团与北京银行合作已长…

java习题3

292. Nim 游戏 难度简单 你和你的朋友&#xff0c;两个人一起玩 Nim 游戏&#xff1a; 桌子上有一堆石头。你们轮流进行自己的回合&#xff0c; 你作为先手 。每一回合&#xff0c;轮到的人拿掉 1 - 3 块石头。拿掉最后一块石头的人就是获胜者。 假设你们每一步都是最优解。…

Slf4j日志集成

Slf4j日志集成 下面就是集成步骤&#xff0c;按着做就可以了 1、logback-spring.xml 哪个服务需要记录日志就将在哪个服务的resource下新建logback-spring.xml文件&#xff0c;里面的内容如下&#xff1a; <!-- 级别从高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 …

微软宣布Win10准备热烈的迎接Docker

在DockerCon 2017大会上&#xff0c;Docker团队今天宣布了LinuxKit&#xff0c;这是一个安全、干净和便携式的Linux子系统container容器环境。LinuxKit允许工具构建自定义的Linux子系统&#xff0c;可以仅包含完全运行时平台的组件需要。所有的系统服务都是可替换的容器&#x…

Leetcode-每日一题【25.k个一组翻转链表】

题目 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内…

xml2json xml转换成json PHP phpstorm

phpstorm 的插件 xml2json快速实现 xml转换成json 1&#xff0c;先安装好Phpstorm 2 二、好用的插件 进入设置页面&#xff0c;快捷键CtrlAlts&#xff0c;或者Files->settings 作者是meizu &#xff0c;应该是魅族公司&#xff0c;贡献的插件

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界

【C语言进阶技巧】探秘字符与字符串函数的奇妙世界 1. strlen函数1.1 strlen函数的使用介绍1.2 strlen函数的模拟实现1.2.1 计数法&#xff08;使用临时变量&#xff09;1.2.1 递归法&#xff08;不使用临时变量&#xff09;1.2.3 指针减指针的方法 2. strcpy函数2.1 strcpy函数…

rust

文章目录 rustCargoCreating a rust project How to Debug Rust Programs using VSCodebasic debuggingHow to pass arguments in Rust debugging with VS Code. References rust Cargo Cargo is a package management tool used for downloading, compiling, updating, and …

Linux---gdb

Linux调试器-gdb使用 GDB&#xff08;GNU调试器&#xff09;是一个在多种操作系统&#xff08;包括Linux&#xff09;上使用的功能强大的调试器。它允许开发者对程序进行调试&#xff0c;以便找出程序中的错误、理解程序的执行过程和进行性能分析。 程序的发布有两种&#xf…

想要学习编程,有什么推荐的书籍吗?

编程是以计算机程序的形式创建创新解决方案的艺术&#xff0c;用于解决各个领域不同的问题&#xff0c;从经典的数学难题和日常生活问题到天气预报以及寻找和理解宇宙中的新奇观。 尽管编程和编码通常可以互换使用&#xff0c;但编程不仅仅是编码。编码代表编程的这一部分&…

mysql数据库 索引

目录 1.定义 2.作用 3.索引使用场景 4.索引分类 5.案例 普通索引 唯一索引 主键索引 组合索引 全文索引 删除索引 1.定义 索引是一个排序的列表 在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址 ### 可以当作目录 2.作用 方便定位信息 做…

我终于成功登上了JS 框架榜单,并且仅落后于 React 4 名!

前言 如期而至&#xff0c;我独立开发的 JavaScript 框架 Strve.js 迎来了一个大版本5.6.2。此次版本距离上次大版本发布已经接近半年之多&#xff0c;为什么这么长时间没有发布新的大版本呢&#xff1f;主要是研究 Strve.js 如何支持单文件组件&#xff0c;使代码智能提示、代…

[洛谷]P8662 [蓝桥杯 2018 省 AB] 全球变暖(dfs)

读题不规范&#xff0c;做题两年半&#xff01; 注意&#xff1a;被海水淹没后的陆地应用另一个字符表示&#xff0c;而不是把它变为海洋&#xff0c;这个点可以便利&#xff0c;但不能被当作起点&#xff0c;不然就只有 36 分。 ACocde: #include<bits/stdc.h> using…

nodejs 下载地址 阿里云开源镜像站

nodejs 下载地址 阿里云开源镜像站 https://mirrors.aliyun.com/nodejs-release/ 我们下期见&#xff0c;拜拜&#xff01;

STM32(HAL库)通过ADC读取MQ2数据

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 ADC外设配置 2.3 串口外设配置 2.4 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 ADC数据采集 3.3 主函数代 3.4 效果展示 1、简介 本文通过STM32F103C8T6单片机通过HAL库方式对M…