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

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

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

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>
谷粒商城 - 个人笔记(基础篇三) 电商后台管理系统+电商系统,掌握微服务的全套方案。该系统有商品服务、仓储服务、订单服务、购物车服务、检索服务、认证中心服务、网关服务、用户服务、秒杀服务、第三方服务。使用SpringBoot+SpringCloud并配套SpringCloud Alibaba系列,引入全套微服务治理方案:Nacos注册中心/配置中心、Feign远程调用、Gateway网关、Redisson缓存、基于ElasticSearch7全文检索、异步编排与线程池、RabbitMQ柔性事务方案、Redisson分布式锁、分布式信号量等。 阅读详情

相关推荐

2020年谷粒商城md笔记

谷粒商城最新2020年课程笔记 如侵权请联系删除 技术涵盖:微服务架构+分布式+全栈+集群+部署+自动化运维+可视化CICD,对标阿里P6/P7,冲击40-60w年薪。 项目由业务集群系统+后台管理系统构成,打通了分布式开发及全栈开发技能,包含前后分离全栈开发、Restful接口、数据校验、网关、注册发现、配置中心、熔断、限流、降级、链路追踪、性能监控、压力测试、系统预警、集群部署、持续集成、持续部署

谷粒商城 - 个人笔记(基础篇二)

电商后台管理系统+电商系统,掌握微服务的全套方案。该系统有商品服务、仓储服务、订单服务、购物车服务、检索服务、认证中心服务、网关服务、用户服务、秒杀服务、第三方服务。使用SpringBoot+SpringCloud并配套SpringCloud Alibaba系列,引入全套微服务治理方案:Nacos注册中心/配置中心、Feign远程调用、Gateway网关、Redisson缓存、基于ElasticSearch7全文检索、异步编排与线程池、RabbitMQ柔性事务方案、Redisson分布式锁、分布式信号量等。

知行 2万+

手把手教你用USBasp给Arduino UNO烧写程序(ICSP模式,含BootLoader选择指南)

本文详细介绍了如何使用USBasp编程器通过ICSP模式为Arduino UNO烧写程序,包括硬件连接、软件配置、Hex文件处理和BootLoader选择指南。内容涵盖从新手入门到高级优化的全流程,帮助开发者高效完成烧写任务,特别适合需要修复BootLoader或进行量产烧写的用户。

weixin_33958381的博客 364

简单介绍一下谷粒商城这个项目。

谷粒商城(Gulimall)是一个基于 Spring Boot 和 Vue.js 的电子商务项目。通过学习和实践谷粒商城项目,您可以掌握许多有关 Web 开发、电子商务和技术栈的知识。通过学习谷粒商城项目,您将掌握许多有关 Web 开发、电子商务和技术栈的知识,为您未来的项目打下坚实的基础。

qq_44872509的博客 4159

Matlab R2024a硬件支持包安装避坑指南:以Arduino为例(附离线包下载)

本文详细介绍了Matlab R2024a硬件支持包(Support Package)的安装流程,特别是针对Arduino平台的避坑指南和高效部署方法。从环境准备、离线安装方案到Arduino支持包的专项优化,提供了全面的技术细节和解决方案,帮助开发者避免常见错误,实现快速部署。

weixin_33737134的博客 449

树莓派使用 OLED 屏显示图片及文字

树莓派默认是不带显示屏的,如果想要查看系统的一些信息,需要使用电脑登录到树莓派,或者通过 HDMI 连接外接显示器查看。这样做总是有点麻烦,我们可以通过外接一个 OLED 屏来显示一些关键参数或者图片。本文将详细介绍操作方法。 OLED 模组介绍 OLED 屏主要有两种:128×32 和 128×64 ,主要颜色是白、黄、蓝。OLED 屏的特点是功耗低,价格便宜,使用 I2C 接口与主机连接。本文所使用的是 128×64 尺寸的屏幕,外形如下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直

码到成功 3743

尚硅谷-谷粒商城-电商项目-秒杀系统-笔记

尚硅谷-谷粒商城-电商项目-秒杀系 商城项目简介 项目主要实现了一个模拟京东商城的分布式秒杀系统, 主要使用到了 nacos,用于服务注册,和配置管理,fegin用于远程调用,网关用于路由,sentinel哨兵 用于服务降级熔断,redis用来作缓存, 秒杀商品上架流程: 设置定时任务上架需要秒杀的商品(分布式锁防止重复上架) 封装最新的秒杀商品信息到redis中 商品的基本信息,商品的随机码,结束时间 设置秒杀商品的分布式信号量作为库存扣减的信息 结束 秒杀: 立即抢购 判定登录 合法性校验:秒杀时间

qq_47842191的博客 7101

基于STM32电压检测和电流检测

1.可测量直流电压0~36V,适用于低电压电子电路中。 2.可测量直流电0~5A范围内,目前采用的ACS712测量量程为5A,该模块有多个量程,可测量到20A 3.实时功率监测 4.电池电量监测(采用电压压降方式计算)

weixin_44453694的博客 6万+

谷粒商城笔记+踩坑(3)——商品服务-三级分类、网关跨域

商品服务-三级分类增删改查、跨域问题、逻辑删除

种一棵树最好的时间是十年前,其次是现在 1万+

HTB_Bike靶机之模板注入

船到桥头自然直 文章目录信息收集模板引擎SSIT 模板注入后放答案 信息收集 日常 A 扫描,两个端口 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA) | 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:.

网络安全学习 798

STM32声源定位:实现声源定位的嵌入式解决方案

通过以上代码,我们可以获取麦克风音频信号样本,并使用交叉相关法计算声源的位置。然而,我可以帮助您解答有关STM32声源定位的问题,提供相关的代码和指导。获取了麦克风音频信号后,我们可以使用信号处理算法来计算声源的位置。该算法通过计算不同麦克风之间的信号延迟来确定声源的方向。首先,我们需要获取每个麦克风的音频信号。声源定位是一项重要的技术,可以帮助我们确定声音的来源位置。在嵌入式系统中,使用STM32微控制器可以实现精确的声源定位。声源定位的基本原理是通过多个麦克风阵列接收到的声音信号来计算声源的位置。

UplKubernetes的博客 2582

谷粒商城笔记 + 完整代码 + 课件资料(基础篇)(学习记录)

谷粒商城基础篇完整代码 + 详细笔记(学习记录)一、项目介绍1. 谷粒商场微服务架构图2. 微服务划分图二、项目环境搭建1. 虚拟机搭建环境2. Linux安装docker3. docker配置mysql4. docker配置redisredis 配置文件解析redis 可视化工具安装5. 配置开发环境6. 安装vscode7. 配置git8. IDEA配置码云9. 初始化项目架构10. 初始化数据库三、快速开发1. 人人开源搭建后台管理系统2. 人人开源逆向工程搭建3. 测试基本的CRUD功能4. 快速生

weixin_44190665的博客 7万+

【雷达】一维和二维自适应波束形成(DBF))DBF附matlab代码

1 简介 数字波束形成技术是天线波束形成原理与数字信号处理技术相结合的产物,其广泛应用于阵列信号处理领域.由于电磁工作环境的恶化和大量射频干扰的存在,在极低的信干噪比(SINR)条件下进行目标检测和信息提取十分困难.对于阵列系统,往往采用自适应数字波束形成(ADBF)技术,来抑制强干扰和方向性干扰对有用信号的影响.介绍了数字波束形成器的基本原理及其DSP的实现结构.​ 2 完整代码 clc;clear all;close all;%%%%%%%%%%%%%%%%%一维DBF%%%%%%%%%%%%%

qq_59747472的博客 7267

谷粒商城商品服务模块数据库文件关系

谷粒商城商品服务的数据库文件 一、主要数据库文件设计 二、商品三级分类表(pms_category) 商品三级分类表 、 主要信息:该分类id(cat_id主键),父分类id(parent_cid),该分类是几层分类(cat_level)等 三、商品品牌表(pms_brand) 注意信息:品牌id(brand_id 主键) 四、商品品牌与商品三级分类关联表(pms_category_brand_relation) 商品品牌与商品分类是多对多关系,它之间关系建立新表 三级分类与品牌关系id(id主键)

lovewangyihui的博客 3559

置顶必读(3) |《滚雪球学SpringBoot》· 高质量专栏介绍,你值得拥有!

《滚雪球学SpringBoot》 并不是普通的技术讲解合集,它是专为开发者精心设计的系统化学习路径。通过不断“滚动学习”,我们将带领你从基础入门到高阶应用,从理解每一行代码到设计和优化大规模分布式系统,最终帮助你成长为一位精通 spring架构 的工程师。

**My Coding Family** 15万+

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

谷粒商城_04_商品CRUD

后端编写、前端展示、服务注册、配置网关、503问题、跨域、解决跨域、服务注册 配置网关、删除数据、后端接口、逻辑删除

早上真起不来的博客 1899

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

【谷粒商城基础篇】商品服务开发:基础概念、三级分类

商品服务开发:基础概念、三级分类

知识输出、记录学习 1321

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

谷粒商城——商品服务-三级分类

复制ctrl + shift + f 全局搜索。

qq_63589255的博客 1119

谷粒商城笔记+踩坑汇总篇

环境的搭建、商品服务-三级分类、品牌服务、阿里云云存储+JSR303数字校验+统一异常处理、spu+sku、分页拦截器、商品服务、仓库服务、Nginx反向代理,thymeleaf+动态展示三级分类、缓存与分布式锁,Redisson+缓存数据一致性、ElasticSearch检索服务、异步和线程池、商品详情搭建+异步编排、认证服务、阿里云短信+验证码防刷+BCrypt加密、用户名密码登录+微博社交登录+SpringSession+xxl-sso单点登录、购物车、订单服务、幂等性、库存自动解锁。MQ延迟队列

种一棵树最好的时间是十年前,其次是现在 2万+

从前慢-谷粒商城篇章1

分布式基础篇 1 项目简介 1.1 项目背景 1.2 电商模式 市面上有 5 种常见的电商模式 B2B、B2C、C2B、C2C、O2O 1.2.1 B2B 模式 B2B(Business to Business),是指商家和商家建立的商业关系,如 阿里巴巴, 1.2.2 B2C 模式 B2C(Business to Consumer) 就是我们经常看到的供应商直接把商品买个用户,即 “商对客” 模式,也就是我们呢说的商业零售,直接面向消费销售产品和服务,如苏宁易购,京东,天猫,小米商城 1.2.3 C

unique_perfect的博客 18万+

谷粒商城-商品服务-三级分类

1.在Controller层调用,2.在service层接口声明快捷键Alt+shift+回车创建3.在service层实现类中写方法体.

weixin_62485485的博客 760

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【06】【商品服务】接口文档地址_三级分类_SPU_SKU

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【06】【商品服务】接口文档地址_三级分类_SPU_SKU

Keep Reading Keep Writing Keep Thinking 1593

谷粒商城学习-笔记大全

1,谷粒商城-01-项目介绍 2,谷粒商城笔记-02-项目整体效果展示

epitomizelu的专栏 5356

谷粒商城高级篇上(未完待续)

谷粒商城高级篇(上)保姆级整理 之前整理了基础篇,Typora提示将近20000词,在学高级篇的时候,不知不觉又整理了两万多词,当做一阶段,先发出来,剩余部分整理好了再发。自己也在学习的过程中,能力有限,如果有什么问题欢迎找我讨论。 文章目录谷粒商城高级篇(上)保姆级整理ElasticSearchDocker安装es**ubuntu中vi下删除键和上下左右键的异常解决**Docker安装KibanaElasticSearch 入门检索文档查询文档更新文档删除文档&索引bulk批量API进阶操作fi

唐尧的博客 1万+

谷粒商城-分布式微服务项目-基础篇

市面上有5种常见的电商模式B2BB2CC2BC2CO2O;1B2B模式, 是指商家与商家建立的商业关系。如:阿里巴巴2B2C模式, 就是我们经常看到的供应商直接把商品卖给用户,即商对客模式,也就是通常说的商业零售,直接面向消费者销售产品和服务。如:苏宁易购、京东、天猫、小米商城3C2B模式,即消费者对企业。先有消费者需求产生而后有企业生产,即先有消费者提出需求,后有生产企业按需求组织生产4C2C模式,客户之间自己把东西放上网去卖,如:淘宝,闲鱼5。

2302_80480374的博客 1341

谷粒商城基础篇(保姆级总结)

谷粒商城基础篇 文章目录谷粒商城基础篇项目相关基础知识介绍微服务架构图和项目描述**微服务划分图**Vrgrantsystemctl命令配置环境Docker自启动命令下载mysql5.7检查所有镜像配置git创建仓库创建其他微服务安装10.15.1node.js,逆向工程Spring Cloud Alibabanacos作为注册中心Fegin声明式远程调用如何使用nacos作为配置中心统一管理配置nacos核心加载多配置集GateWay网关前端基础Vuevue模块化开发三级分类开发配置网关路由与位置重写配置

唐尧的博客 2万+
上一篇: 谷粒商城第一篇环境准备
下一篇: XXL-JOB执行器集群及分片功能
Persistence___
博客等级 码龄8年 47粉丝 59原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值